Compare commits
1017 Commits
shift-9188
...
84f08b548e
| Author | SHA1 | Date | |
|---|---|---|---|
| 84f08b548e | |||
| 462bce226d | |||
|
|
2c3ae4ee86 | ||
| f4d6277646 | |||
| 5e47287593 | |||
| dd5eb6782d | |||
| 0312e1dbea | |||
| b29ea655ba | |||
|
|
8d06374bef | ||
| 5ca0afc385 | |||
| cc5a0de05d | |||
| fe84e20645 | |||
| 2ba8881f3b | |||
| ce5c97290f | |||
|
|
0c7407c11b | ||
| fe5ab7b0bf | |||
| 954b83ebba | |||
| 9bfe23df9c | |||
| 5f78a9e500 | |||
| d47672dbe3 | |||
| c3b898d99e | |||
| b13b22c359 | |||
| fa505f56ee | |||
| 464c7d5aa1 | |||
| 260b794111 | |||
| e2567c1b97 | |||
| 7302a36067 | |||
| ac7b02f320 | |||
| 0fbeb192c1 | |||
| 5c3788eb6d | |||
|
|
f01051107e | ||
|
|
cf029bc86e | ||
|
|
17ab10c1c5 | ||
|
|
dc5c387f7a | ||
|
|
3b4d2b2784 | ||
|
|
3b3dc276fc | ||
|
|
ee460ec076 | ||
|
|
24e7c6a008 | ||
|
|
1a6a1dab47 | ||
|
|
c85031ab49 | ||
|
|
24efcab8da | ||
|
|
182e4e8de8 | ||
|
|
6b62e45acf | ||
|
|
a37f744caa | ||
|
|
8eca69335f | ||
|
|
87e70704c1 | ||
|
|
db1821bcef | ||
|
|
62583c4869 | ||
|
|
066b7b1790 | ||
|
|
d38422e16c | ||
|
|
ed22521e6f | ||
|
|
3218f55a31 | ||
|
|
be1fa8f432 | ||
|
|
c0c572397a | ||
|
|
049d4849aa | ||
|
|
e89656367f | ||
|
|
e910ef7e84 | ||
|
|
db0e927056 | ||
|
|
cec55028d6 | ||
|
|
0ae09c40ae | ||
|
|
1cf2ec1ce7 | ||
|
|
693df3a05b | ||
|
|
c0d1913660 | ||
|
|
8c387280a6 | ||
|
|
a1ab7b6257 | ||
|
|
36d4f08aaf | ||
|
|
f7de4f49d3 | ||
|
|
06018c405d | ||
|
|
03c17200a0 | ||
| 519c2a0fec | |||
|
|
2b8788f716 | ||
|
|
72be071536 | ||
| 37b923fbda | |||
|
|
292901a02e | ||
|
|
12a6629a4b | ||
| 9d9a8ed9f5 | |||
| 1a03fed3bd | |||
| 43e66b2004 | |||
| 8babb4c836 | |||
| 4eb3dfbb64 | |||
| 33d390a612 | |||
| cad78c30ae | |||
| f8acdae237 | |||
|
|
63582dc306 | ||
|
|
c96fb95f27 | ||
| 1160c8b077 | |||
| a4b3405f8a | |||
| 9d5396ca9a | |||
| de1483d409 | |||
|
|
a37386565a | ||
|
|
ff8b6549ce | ||
|
|
d550bc60e2 | ||
|
|
307573a3a8 | ||
|
|
d9fc6c95f3 | ||
|
|
a65f0eead6 | ||
| ca025ca2e8 | |||
| 74aef68edc | |||
| c0e595f88a | |||
| aab4bc0d46 | |||
| f2708e1325 | |||
|
|
2e2b70ab7c | ||
|
|
e42d54554a | ||
| 6cb24f1500 | |||
| f7cc086f37 | |||
| 8463da7842 | |||
|
|
322d547c92 | ||
| 30104ece71 | |||
| 44f359ff9c | |||
| 20f36d519a | |||
| e358e9fb5d | |||
| b882d92328 | |||
| 3257aa9ee9 | |||
| 0bcd6f5e86 | |||
| 75d958856a | |||
| 71eb00d010 | |||
| eab3d062f5 | |||
| 1afa22e2f4 | |||
| b85d039c36 | |||
| c1a4fd13d5 | |||
| 9a1ffe835c | |||
| c3b9482d35 | |||
| bc8f9149dc | |||
| c60213257b | |||
| 6a78ba2bb2 | |||
| a5f7ce8393 | |||
| 4e1505c5c2 | |||
| e967bdde71 | |||
| 74e9e39722 | |||
| 0df4033fca | |||
| e02770cc85 | |||
| 3687af2656 | |||
| b168931266 | |||
| b669dd319e | |||
| e37b9a30a4 | |||
| 436d4b8acf | |||
| a2eb1d5d1b | |||
| be4fdb2f80 | |||
| 538f324ff4 | |||
| 59ca73519d | |||
| 6bc2b888a4 | |||
| be8b2d48b3 | |||
| 5f631a5c3d | |||
| fea3756eab | |||
| 6d8db2cd80 | |||
| 9725f4944f | |||
| 9b1b92d0cf | |||
| b10b6b712e | |||
| db018e9120 | |||
| 1444bc9aa4 | |||
| 9e7fc79fa1 | |||
| 06460d9677 | |||
| beed9f9c11 | |||
| 38b3d5d367 | |||
| ad080b19a2 | |||
| 274d9759b6 | |||
| d992570ee8 | |||
| d72c08b4c9 | |||
| 7baea36628 | |||
| b20c79b679 | |||
| 5cbebd8840 | |||
| d36979cbbd | |||
| 1c28cd7902 | |||
| df19e43112 | |||
| 5a65517d2b | |||
| 49eb388041 | |||
| 659ae2e3ac | |||
| 8f8d12065d | |||
| 391b17c1e7 | |||
| 742da4bf17 | |||
| 39ea570f3a | |||
| 714a15e6d0 | |||
| a5e4e93edb | |||
| 5f166deee9 | |||
| 2468bff5fb | |||
| 277805044a | |||
| 680be0535d | |||
| b438846c3c | |||
| 6a76dacdae | |||
| 3358cf8dea | |||
| 7b6e17ba40 | |||
| 3e891912b0 | |||
| 53d0c46aa0 | |||
| e4d5307dfe | |||
| 28aebcfe58 | |||
| cdc5d1e8d3 | |||
| 4c7dadfab0 | |||
| 4ac1322b8c | |||
| 822838fe29 | |||
| fb4c8c240b | |||
| 30e308466b | |||
| e1dcc7452b | |||
| 76690962c5 | |||
| 629ae22b78 | |||
| 1e1df33711 | |||
| f835d4a21b | |||
| 1219c9a02e | |||
| bbffddf9ae | |||
| 74cc11e124 | |||
| b020f5cbb6 | |||
| 09bd9a22c9 | |||
| ebf97a8242 | |||
| 6d51cc1395 | |||
| ededb36856 | |||
| b7ee043bf3 | |||
| 48131d3064 | |||
| d4f3e24c33 | |||
| 0f253e1047 | |||
| 4d2f6de3c8 | |||
| 346c945937 | |||
| 4391abaabb | |||
| 50169e9905 | |||
| 7a6a3ec2d8 | |||
| 5d715b096f | |||
| c696a8bd2e | |||
| d3bf78d5a8 | |||
| cd08c9a5c8 | |||
| bd8f453aea | |||
| c719da2933 | |||
| a5184be13d | |||
| 47a2afff86 | |||
| b8db85abca | |||
| 67df8f85ff | |||
| 6bb45c38a9 | |||
| 02dbacbdc0 | |||
| bea4f8db18 | |||
| 3919a3ce1c | |||
| d1b94f9183 | |||
| bb440497eb | |||
| 4b87c5e112 | |||
| a2def6abc0 | |||
| a5be12aee3 | |||
| e2fed71896 | |||
| befebc44cb | |||
| b4456d7771 | |||
| c3350823dc | |||
| 96ba9edf6a | |||
| 39609edc9e | |||
| c702253837 | |||
| bad020924d | |||
| a57be26b75 | |||
| 1d63186fd5 | |||
| 6e9d14728d | |||
| 7c9dab2cf0 | |||
| 885a909e57 | |||
| a26b669daa | |||
| 3bd3b30609 | |||
| 1201a6f0e6 | |||
| 8a32fc41a8 | |||
| 6131b378b8 | |||
| 2a80a1ad62 | |||
| 4e0e7f6b7b | |||
| 7bc62d5600 | |||
| 6208676207 | |||
| 70f9d736d1 | |||
| 09c49ab279 | |||
| 6ac8fb4f89 | |||
| 3a2735f00d | |||
| 7b89fad650 | |||
| 9bc3e5e963 | |||
| ea10ead824 | |||
| 4a4b42bed0 | |||
| 1053fbc797 | |||
| 988dbd4edc | |||
| 4c9d1667b6 | |||
| 2b924fac4b | |||
| 18e0a2afd2 | |||
| 558a432960 | |||
| f46dbd887b | |||
| 50de666304 | |||
| d592f8bd19 | |||
| d2b69061b5 | |||
| b24fa48d85 | |||
| 28b2ffa4a3 | |||
| 4a45c0f505 | |||
| f7e7ed9d7a | |||
| 232f737a10 | |||
| feecd0d7f5 | |||
| 2d872ae289 | |||
| d6b6cb49cf | |||
| 735d39f52e | |||
| 46faf195a7 | |||
| caff5c8160 | |||
| 39d5b29ed3 | |||
| b4f3fa6d07 | |||
| eae8af936b | |||
| 257e241aea | |||
| 2587ea624a | |||
| 8cd19f6b8a | |||
| 8dea8e5995 | |||
| dedcf1a379 | |||
| 2e0df186c6 | |||
| 2e39b9cb2a | |||
| 0e19f6da87 | |||
| 34092291dd | |||
| 67f2967823 | |||
| 33f0d83cf7 | |||
| 3a35c28f1d | |||
| 2a1db0b088 | |||
| 79fb978127 | |||
| 9e2cfd8abc | |||
| 88889ca329 | |||
| aed29200a7 | |||
| d6b03826c1 | |||
| 0a99b1789b | |||
| 80bc35bf4c | |||
| f0ae8cd7ad | |||
| 6dbc9f0281 | |||
| 88b1dcdfae | |||
| ff811d961f | |||
| 633e0557f6 | |||
| bdf8846278 | |||
| f0d3e739f8 | |||
| 00afc7a1d3 | |||
| 71f048fb00 | |||
| fd72954165 | |||
| da90616aa0 | |||
| 566092a567 | |||
| ef5e35229c | |||
| 005b29ff5a | |||
| 2203731ed8 | |||
| f59265cc00 | |||
| 0eab9ba2ce | |||
| e2f18a87b8 | |||
| 27941b2017 | |||
| b60e368cb9 | |||
| a1d966327f | |||
| 07ab627af4 | |||
| b84f78b00b | |||
| d6a84e2496 | |||
| 3ea9cbda9e | |||
| 363d8cfcdb | |||
| 32f7770e75 | |||
| 53df7d2fbe | |||
| 550bdd8249 | |||
| 55462d0437 | |||
| 5b7da699bd | |||
| 5fbca80a3c | |||
| 4fa1410790 | |||
| 411fc5f37f | |||
| f7c20719f7 | |||
| e5a7aeede8 | |||
|
|
cfbf3a4033 | ||
| 89cda7c415 | |||
| e211436675 | |||
| d1be96a1df | |||
|
|
7e87312d99 | ||
|
|
73c277acfb | ||
| 0728233206 | |||
| b127ce0514 | |||
| 5924222296 | |||
| 6d2c601b5d | |||
| f2fb993596 | |||
| c40232099f | |||
|
|
17d33a9ad2 | ||
|
|
e380bafbb0 | ||
|
|
4c63e68625 | ||
|
|
8b5d150fe2 | ||
|
|
10808f3622 | ||
|
|
37ba8f0f29 | ||
|
|
51fb0150d9 | ||
|
|
03dd12cea1 | ||
|
|
56ae719a2e | ||
|
|
68b7e50b9e | ||
|
|
386e2009c4 | ||
|
|
3da55bca81 | ||
|
|
5d8f1457d4 | ||
|
|
39d9ebc549 | ||
|
|
b2f3664909 | ||
|
|
06c2f0e3d0 | ||
|
|
2d2392c8ae | ||
|
|
5e7061bc61 | ||
|
|
8451a2bd4b | ||
|
|
dd135f10ec | ||
|
|
247eb03cea | ||
|
|
19c16cfbf1 | ||
|
|
456687edc5 | ||
|
|
aab63bb627 | ||
|
|
efb3cac129 | ||
|
|
36314339fb | ||
|
|
c6247e445f | ||
| b028856eb5 | |||
| 1925b2ef0c | |||
|
|
0d1bd4522e | ||
| f2e84b63fa | |||
| ac6257ed6d | |||
| 2486dec824 | |||
| 04e6c0d0fc | |||
| b948c42fe2 | |||
| 5d7be1a482 | |||
| 4e81d06a6e | |||
|
|
0f2400ff2b | ||
| 7ed2332a3e | |||
|
|
c432b32d08 | ||
|
|
8cf628153c | ||
|
|
ab0bd9e7d9 | ||
|
|
51cd70f2ec | ||
| 235d4e3784 | |||
|
|
dd0bbad18f | ||
| f32059c3e7 | |||
|
|
92578ef738 | ||
| 367205f955 | |||
| 1fdf36669f | |||
|
|
dfa06b148f | ||
|
|
139b437118 | ||
| 4dca636359 | |||
| 4f1d2a179b | |||
|
|
ede28f375a | ||
|
|
06dfb8d7f6 | ||
| 2d4090922f | |||
| 3d58a09c21 | |||
| 05da5f016c | |||
|
|
256eead944 | ||
|
|
dcb8ffaa0d | ||
|
|
def671dd47 | ||
| 717f360952 | |||
|
|
1c0b91f52c | ||
| c733d39d30 | |||
| 87bdea3bba | |||
| efe80694f9 | |||
| be7e6ee400 | |||
| 319a7b27c5 | |||
| dbe20ac724 | |||
|
|
dc6ed3f43a | ||
| ecddb5c6d3 | |||
| 34e5a37e24 | |||
| eaca6bcc72 | |||
|
|
bad588d4b2 | ||
|
|
d5daf20351 | ||
|
|
f512ab96fc | ||
|
|
0cfa9a5483 | ||
|
|
979f934f22 | ||
|
|
1fa5820550 | ||
| 8233afa825 | |||
| ba6f67798d | |||
| daabbaa3e4 | |||
| caa68b24d6 | |||
| 114db744b4 | |||
| 8c910ed5f3 | |||
| 1ef4332df6 | |||
| 6e98269a0b | |||
|
|
c918b91438 | ||
| 2a1e634b34 | |||
|
|
d45165d759 | ||
| a0a885e004 | |||
| 52f7070311 | |||
| e0650da5e9 | |||
| bf5cf3bce4 | |||
| 07d1dc4955 | |||
| 11db41b900 | |||
| 3bca959d49 | |||
| 3fee673314 | |||
| 3121d12f3a | |||
| b8731c3f37 | |||
| b445a42896 | |||
| dbab224a9d | |||
| 03f9659243 | |||
| 0fb56133ff | |||
| afa7ca629c | |||
| 9a9c382d7f | |||
| 518b8d67bc | |||
| ff0f8194e0 | |||
| f84f982c29 | |||
| 420d81866c | |||
| 1cd78ce93a | |||
| 7cae8af89a | |||
| 5088cea413 | |||
| d670d52083 | |||
| 004aa116fb | |||
| 32a08e1e10 | |||
| 30d39c5326 | |||
| 8affea3360 | |||
| 3b6b4dc388 | |||
| 9a9e75d96d | |||
| ad6e4f0a23 | |||
| f66bc6d8aa | |||
| 845bb4763b | |||
| e476b203c7 | |||
| dd163630cb | |||
| 5d8a5ede7d | |||
| 35ccb03c2b | |||
| f6df068787 | |||
| b60a3601cb | |||
| 8676844930 | |||
| 7e80e19eec | |||
| e21c7ce1d0 | |||
| 42f2baca5e | |||
| d9c0c8f1d8 | |||
| b98e184d32 | |||
| f764466199 | |||
| 6adfd9f3fa | |||
| dbf9db1c92 | |||
| 6cb7a8cb43 | |||
| 2913960bcb | |||
| 8d0382a279 | |||
| aa5ef50ed0 | |||
| d87b869633 | |||
| 99782bdf67 | |||
| 6fa459e88e | |||
| 954f2beea7 | |||
| f216413a23 | |||
| 63572a2740 | |||
| aeb7595166 | |||
| f507d198a7 | |||
| 9b5166c570 | |||
| 2271aae557 | |||
| 8ed07e539f | |||
| b029574fa5 | |||
| e9d903ce79 | |||
| 1c9100e5de | |||
| 1a59ac955d | |||
| 4d8e58f920 | |||
|
|
55ce8e0d9e | ||
|
|
0d1a3f8dbf | ||
|
|
9687a3c7a8 | ||
|
|
471534021e | ||
| 8c75abf16a | |||
| 472abaf30b | |||
| 35426e1189 | |||
| 6507d57424 | |||
| 4252878019 | |||
|
|
8451cc1547 | ||
|
|
70ecefb007 | ||
| 4e7be1a18c | |||
| d1cc468dfa | |||
| 484512f2c7 | |||
| 83b4df3462 | |||
| 8ea46b4ea8 | |||
| a68faa11b0 | |||
| 541cd93c99 | |||
| c5a7982945 | |||
| 5cee36453e | |||
| 47ea87b614 | |||
| 4ad0df2c39 | |||
| f2ff8398eb | |||
| 3f915d6964 | |||
| 3e6a9da0d1 | |||
|
|
ca5f4ffd84 | ||
|
|
b66f9f79d2 | ||
|
|
dc0bfe6d7a | ||
|
|
5de463bd02 | ||
|
|
f3d858d785 | ||
|
|
0e537a6b46 | ||
| 8d78b15751 | |||
|
|
e1fc2fa271 | ||
|
|
27466ad3a6 | ||
|
|
f0d63b4120 | ||
|
|
163dd98b7b | ||
| 8610a1678d | |||
|
|
d20c7832da | ||
|
|
fcba4015ed | ||
|
|
bb34a3c33a | ||
|
|
dbded3ba14 | ||
|
|
3a654e0480 | ||
|
|
4b358fba4f | ||
|
|
5aaeab686d | ||
|
|
5ff0a4e86f | ||
|
|
0c866ed009 | ||
|
|
f09bf02c10 | ||
|
|
ee2c30f938 | ||
|
|
82f28bc712 | ||
|
|
8020baf9d1 | ||
|
|
b1cac44432 | ||
|
|
ab46b6118d | ||
|
|
52a1bfe804 | ||
|
|
c7c5b5298c | ||
|
|
4c07917542 | ||
|
|
fe06a2dd55 | ||
| 143f002ead | |||
| 83c0ae5841 | |||
| 0c05386804 | |||
| 9a70d4de0d | |||
| 8950b5f50b | |||
| 59e077fd6a | |||
| c9fc5b2157 | |||
| e128bcfaf9 | |||
| 7cf6b0bba6 | |||
| c36d4783e7 | |||
| 29acd76051 | |||
| eacf33b642 | |||
| 46666db231 | |||
| 3dfa753f33 | |||
| 41ae64a8c9 | |||
| fcf5400f71 | |||
| 2257736ad9 | |||
| 147b55ae78 | |||
| 5b6c951dc0 | |||
| 0840d65011 | |||
| 6394234960 | |||
| e1dde655cb | |||
| e01d59cbd8 | |||
| 22d1e195ed | |||
| c776e7b102 | |||
|
|
3cd95a5a6b | ||
| aba7845a4b | |||
| f23c4c42b5 | |||
| 05149dc723 | |||
| a615c9d1d8 | |||
| eed9ac8ff4 | |||
| 258e7fe307 | |||
| a49a86a597 | |||
| b01c5d57c7 | |||
| 4b0c113941 | |||
| 277a0ae2e3 | |||
| a61aafdc6a | |||
| 3a2899f913 | |||
| 05b172cd47 | |||
| 351259909c | |||
| 0af44ffe02 | |||
| 06799a90b0 | |||
| c7fc9a1480 | |||
| d5b8a89776 | |||
| eada83aaf8 | |||
| 9a63a9de80 | |||
| b4859fd97f | |||
| 794a18cfa0 | |||
| 581b732b05 | |||
| 0119875d22 | |||
| 0f34c7dfb9 | |||
| 04be7fdb3c | |||
| 123d45d172 | |||
|
|
864ae189af | ||
|
|
13b2620d2e | ||
|
|
75c4ad73ed | ||
|
|
2c2f5621df | ||
|
|
855accab4f | ||
|
|
afaa702a02 | ||
|
|
15637d398e | ||
|
|
f346625bb1 | ||
| 9764fef01c | |||
| 338d6c6f9d | |||
| 9da6f39e6e | |||
| 5759794f71 | |||
| ee9d96a5bf | |||
| 9197a86670 | |||
| 64e45b1892 | |||
| 6890a0939d | |||
| 61c78534d8 | |||
| 5a611f96a8 | |||
| 3e7f34d5c4 | |||
|
|
78d16bec5f | ||
| 33f658e088 | |||
| 0b46c34abe | |||
| f1ad20cb0b | |||
| 511da54909 | |||
| 104407646c | |||
| 622229cfc9 | |||
| f50e6ad209 | |||
| d7529cef80 | |||
|
|
f7432158e1 | ||
|
|
2df6597771 | ||
| 60a15c2227 | |||
| a369db9ca1 | |||
|
|
ac35d61ae2 | ||
| 489668a2ff | |||
| c9087d5df4 | |||
| ac06b3700c | |||
| 31578f5ed3 | |||
| 9b178ade4b | |||
| 92522b5494 | |||
| ccebb4eb73 | |||
| 16873e7477 | |||
| 79318e2f28 | |||
| f44a4f5d9d | |||
| b28925079b | |||
| cbf6803812 | |||
|
|
a0e804aa62 | ||
| d1b87bd64b | |||
| 5b783035a3 | |||
| a06363da75 | |||
| a68c691f75 | |||
|
|
8a61d258fb | ||
|
|
fd3f220752 | ||
|
|
0a0b2927ae | ||
|
|
4c19773818 | ||
|
|
071af03577 | ||
|
|
58da0b54b3 | ||
|
|
528ea4bcfb | ||
|
|
ca124d34cd | ||
| 70b53ce21a | |||
| 6dc226ab0b | |||
| d6524942f7 | |||
| 3ca5e81e34 | |||
| 561b5ad1b9 | |||
| 229bb9a5e0 | |||
| 280d3e9043 | |||
| a2384f4b35 | |||
| d65bf5bcd5 | |||
| e763a9140c | |||
| 3a06a375ef | |||
| bdf3f38190 | |||
| 5b2f6e44b1 | |||
| 250401fd62 | |||
| c1fb03add1 | |||
|
|
56bf7efcef | ||
| cc0508683e | |||
|
|
b232b65860 | ||
| 406d1d31c0 | |||
| f8fe5c92aa | |||
| 69623bd248 | |||
| 6d4995074b | |||
| ac94068864 | |||
| 31ed355d64 | |||
| 8a54fb272a | |||
| 6ecf807081 | |||
| 728472b897 | |||
| 30a8c8a03a | |||
| 00e7490037 | |||
| 1d152d730a | |||
| a010d91da2 | |||
| 3717248764 | |||
| 27753e903b | |||
| b621f71b54 | |||
| d4b3300b88 | |||
| 242fb9dda0 | |||
| 4f505cd155 | |||
| dca70a0f53 | |||
| b9a8c826ec | |||
| 363deb3ca7 | |||
| f77b5f7556 | |||
| d60efe38e2 | |||
| f779342e02 | |||
| 204e62b3bb | |||
| 5adc24f997 | |||
| ab25b0f64a | |||
| dd8e59cd4f | |||
| 0c4c36eb74 | |||
|
|
3481f10033 | ||
|
|
fdf61fef3d | ||
|
|
30c3c5d33f | ||
|
|
6cd7df79d9 | ||
|
|
b5b0b5d7bd | ||
|
|
d0266c7d0c | ||
|
|
283fd30c19 | ||
|
|
5d13135bd0 | ||
|
|
6c2961eef6 | ||
|
|
f9c006403e | ||
|
|
1f65f0fe30 | ||
|
|
a49a287855 | ||
| bf7f830f9e | |||
| ffce564a47 | |||
| 2cdcd9a424 | |||
| ffbc733571 | |||
| 3ea7188253 | |||
| 97c6a4c4ef | |||
| f3fe112427 | |||
| 189e4ef230 | |||
| d35b895ff4 | |||
| c47c511674 | |||
| 20be21c87d | |||
| 1d57dc519f | |||
| 644b42afe9 | |||
| 9ad11f24c2 | |||
| 1e2564f753 | |||
| f1ab544aae | |||
|
|
aa85d24952 | ||
|
|
b0f8817aae | ||
|
|
b9e3e9f5f2 | ||
|
|
ea05f5992d | ||
|
|
9c138d6bee | ||
|
|
7325880a70 | ||
|
|
320631e91d | ||
|
|
0997864cd6 | ||
|
|
9e0451d541 | ||
|
|
84245c5584 | ||
|
|
d72c0ea9f5 | ||
|
|
bf9500f960 | ||
|
|
f18cda6c00 | ||
| 0ab20a75ac | |||
| 133a6ed4ea | |||
| bc424b7a00 | |||
| be3c6d89ee | |||
| d814ed1972 | |||
| 8322229001 | |||
| da2a76d6cf | |||
|
|
abf7a2783c | ||
| 6690c69efe | |||
| 67044b7cf9 | |||
| 048d3af39b | |||
| 816989d472 | |||
| 8f6310b463 | |||
| 3cebdd838b | |||
| 9996404e29 | |||
| 1598efac35 | |||
| bfacb86f35 | |||
|
|
e5e5c7e19c | ||
|
|
9ede1d7eea | ||
| ff8cf83f1b | |||
| 326c8e7b81 | |||
| daeecfbd4f | |||
| 15c330c784 | |||
| 7ba80ccc58 | |||
| ef4efe723f | |||
| 64ef8e9acd | |||
| d88b6f6e95 | |||
| 440d66322c | |||
|
|
469a646ae3 | ||
| 9cb9ebbe4f | |||
|
|
d004855dd8 | ||
|
|
911645c9f5 | ||
|
|
6a16e78d80 | ||
| 937c5024cb | |||
|
|
7e12ba1a00 | ||
| 89d57bb915 | |||
| a111387a3f | |||
| 234fcb13af | |||
| 377fdad6e0 | |||
| 68020b0201 | |||
| f0628c5c05 | |||
| 66d2a2c48c | |||
| 6f1229418e | |||
| a430fee568 | |||
| 53b7ce651e | |||
| 1028064abe | |||
| ab285870e3 | |||
| 1a02d46151 | |||
| 1bb70bbe66 | |||
| c158155194 | |||
| 7bfd4c1198 | |||
| ea959d1a10 | |||
| 9fc5d81877 | |||
| 719aac6727 | |||
| c8398920c5 | |||
|
|
55bd914b8f | ||
|
|
2b06a52705 | ||
|
|
2c9c997d5a | ||
|
|
17f305d9cb | ||
|
|
44d071696f | ||
|
|
fa8f44135c | ||
|
|
b38e79ef8f | ||
|
|
e2e39fbdf5 | ||
|
|
a8262932cc | ||
|
|
6d3e9b3c1d | ||
|
|
b91619801d | ||
|
|
f1e32096f3 | ||
|
|
f5a327e72c | ||
| 4fe05e39f7 | |||
|
|
c3779bf371 | ||
| 54aa7e249e | |||
| 1744d192b5 | |||
| 3f7426c550 | |||
| c4e017c7b7 | |||
| e723831548 | |||
| 56b5dcd7d8 | |||
|
|
913d229630 | ||
| 3dfb11d692 | |||
| 759ab14899 | |||
| 329ff7c478 | |||
| a6022f7251 | |||
| d345be889e | |||
| 2dbe9955d9 | |||
|
|
8fcd33e0a1 | ||
| 14aa5e3b28 | |||
| 2cb9aac070 | |||
| 1c6a5c9d04 | |||
| 936aabf412 | |||
| fa7478a5b5 | |||
| 8992554ba6 | |||
| c8895137fa | |||
| f8bc75f1f9 | |||
| 43faa0f020 | |||
| 8e76a18590 | |||
| 9c1df26b4b | |||
| 6d89a499b1 | |||
| 03430c272f | |||
| baf15296ce | |||
| 56f74964ed | |||
| afad2df8f5 | |||
| d17ff1f2e7 | |||
| 4d9be34b4e | |||
|
|
5213b28c2e | ||
|
|
e0df5a53dc | ||
|
|
f13022ee1d | ||
|
|
c063badb38 | ||
|
|
693cf4cd2a | ||
| cc70b1a67f | |||
| 08d3366fe1 | |||
| 080b2fcfc4 | |||
| 5b1d02e3c1 | |||
| b322a405c7 | |||
| 0ed05b103b | |||
| 5431da3930 | |||
| 2a6e5b7992 | |||
| 85af95a8a0 | |||
| e0b04a57ae | |||
| 0533cd24fe | |||
| 9598031dd1 | |||
| 9f808717d2 | |||
| b4aa71b81b | |||
|
|
321a8f0946 | ||
|
|
93124cdad0 | ||
|
|
26eae9b1cf | ||
| f803bc0a59 | |||
| be5925f40d | |||
| b25b697e7b | |||
| 66e2a68cde | |||
| 673f6051c1 | |||
| 0478ab9eb2 | |||
| b7fde520be | |||
| 6f031f78e8 | |||
| 2f850ce152 | |||
|
|
a6240a3ca9 | ||
| 8906936e51 | |||
| fd6867740e | |||
| 183231206b | |||
| d4e832b526 | |||
| 667ff5cf94 | |||
| 6d2db21a62 | |||
| df398faddb | |||
| e07e2d2e2c | |||
| ca29189f9e | |||
|
|
15a1a3f666 | ||
|
|
8c4c09bc90 | ||
|
|
5a926d2d41 | ||
|
|
5127d8b283 | ||
|
|
c043a43fc5 | ||
|
|
013930d7c3 | ||
|
|
6b0d66be25 | ||
|
|
3b492d2005 | ||
|
|
29f4bfd3cc | ||
|
|
86e42c36cb | ||
|
|
c1d81c65d2 | ||
|
|
fd864b03d9 | ||
|
|
97eb51a434 | ||
|
|
76f9b96369 | ||
|
|
13e5cf2133 | ||
|
|
1c92f6449e | ||
|
|
3ed6cc8d5c | ||
|
|
5b79e415e0 | ||
|
|
98036c7266 | ||
|
|
8c8c59d8bd | ||
| fdbadbac7f | |||
| 9d788f919a | |||
| fac00bc4f6 | |||
| 6e511fc4a9 | |||
| 2f6214c48d | |||
| b2f791fc5f | |||
| 39140611ca | |||
| 567ee3062b | |||
| 584e0903c8 | |||
| 8990006d4c | |||
| 7feb78ba70 | |||
| d67ca133eb | |||
| f95557aa3a | |||
| f539ab7294 | |||
|
|
79284e2271 | ||
|
|
e303b565fc | ||
|
|
985e8a5cd6 | ||
|
|
541e1c4c2c | ||
|
|
10c1d500f3 | ||
|
|
678f16788c | ||
|
|
8871a1b7ae | ||
|
|
05c2f28f25 | ||
|
|
30179fe353 | ||
|
|
6846dd760d | ||
|
|
408f285cf3 | ||
|
|
fa76fc0f78 | ||
|
|
ade9cd6b05 | ||
|
|
933d18b784 | ||
| 48bae3b181 | |||
| 78f6ed1ef6 | |||
| 6d503939a5 | |||
| 3677f1c45b | |||
|
|
a23ce4e0e6 | ||
|
|
e7c43fa53a | ||
|
|
0de6735855 | ||
|
|
aa274b1df4 | ||
|
|
51261a4a5c | ||
|
|
9e92561502 | ||
| 2b28ebc014 | |||
| 196d9c358c | |||
|
|
38d58d92b4 | ||
| e136c910b5 | |||
| d0c4f7eea2 | |||
| bf9b0f3973 | |||
| 4534b46e97 | |||
| 68affe4e4c | |||
| aa2671f167 | |||
| 2178b61602 | |||
| 6a6b8ed9b2 | |||
| 784e8cf787 | |||
| 89a8b6926a | |||
| 9c926d15c1 | |||
|
|
1b1144c628 | ||
|
|
e9b1802af3 | ||
|
|
fe7c1d93ae | ||
|
|
8d1be827a7 | ||
|
|
cd4fe13117 | ||
|
|
34e169adba | ||
|
|
d064756cc6 | ||
|
|
bf7e1658f4 | ||
| 8de3c5dcba | |||
| e8827cbd50 | |||
| f839003f5e | |||
| b68d97455b | |||
|
|
e9dcb8b3f0 | ||
|
|
5e838ad61e | ||
|
|
a365977702 | ||
| 8b2542ebef | |||
|
|
69c5018367 | ||
|
|
4f536ae5a9 | ||
|
|
95395d1da7 | ||
|
|
fbf437ac99 | ||
|
|
5faf49688d | ||
|
|
4d7d0ed74d | ||
|
|
979b9f704c | ||
|
|
4124cf39db | ||
|
|
c83e21d588 | ||
|
|
c88630e9af | ||
|
|
40b265e145 | ||
|
|
3ad2b2fb8e | ||
|
|
8b671065e9 | ||
|
|
028e1a191e | ||
|
|
a133f82997 | ||
|
|
c4f3eb9a4e | ||
|
|
8a52c4529f | ||
|
|
d0493f3dd0 | ||
|
|
b845552c37 |
@@ -1,3 +0,0 @@
|
||||
.github/
|
||||
.vscode/
|
||||
vendor/
|
||||
22
.eslintrc.js
22
.eslintrc.js
@@ -1,22 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-strongly-recommended",
|
||||
"prettier",
|
||||
"plugin:jsdoc/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
rules: {
|
||||
"vue/multi-word-component-names": "off",
|
||||
indent: ["off", 4, { ignoredNodes: ["ConditionalExpression"] }],
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
},
|
||||
plugins: ["jsdoc", "@typescript-eslint"],
|
||||
parser: "vue-eslint-parser",
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
},
|
||||
};
|
||||
@@ -3,40 +3,55 @@ name: Laravel
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
laravel-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.1"
|
||||
- uses: actions/checkout@v3
|
||||
php-version: "8.4"
|
||||
|
||||
- name: Copy .env
|
||||
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
|
||||
- name: Install Dependencies
|
||||
|
||||
- name: Install PHP Dependencies
|
||||
run: composer install -q --no-interaction --no-progress --prefer-dist
|
||||
|
||||
- name: Generate key
|
||||
run: php artisan key:generate
|
||||
|
||||
- name: Directory Permissions
|
||||
run: chmod -R 777 storage bootstrap/cache
|
||||
|
||||
- name: Create Database
|
||||
run: |
|
||||
mkdir -p database
|
||||
touch database/database.sqlite
|
||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build frontend
|
||||
run: npm run build
|
||||
|
||||
- name: Run migrations
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: database/database.sqlite
|
||||
run: php artisan migrate --force
|
||||
|
||||
- name: Run PHPUnit
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: database/database.sqlite
|
||||
run: vendor/bin/phpunit
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16.x"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run Vue tests
|
||||
env:
|
||||
LARAVEL_BYPASS_ENV_CHECK: "1"
|
||||
run: npm run test
|
||||
24
.gitea/workflows/renovate.yaml
Normal file
24
.gitea/workflows/renovate.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: renovate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "@daily"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/renovatebot/renovate:43.2.3
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: renovate
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
env:
|
||||
RENOVATE_CONFIG_FILE: "renovate-config.json"
|
||||
LOG_LEVEL: "info"
|
||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||
|
||||
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
@@ -1,15 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -15,7 +15,7 @@ app/storage/
|
||||
|
||||
# Laravel 5 & Lumen specific
|
||||
public/storage
|
||||
public/hot
|
||||
public/hot*
|
||||
|
||||
# Laravel 5 & Lumen specific with changed public path
|
||||
public_html/storage
|
||||
@@ -26,7 +26,7 @@ storage/*.key
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
.phpunit.result.cache
|
||||
/.phpunit.cache
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
@@ -237,11 +237,26 @@ dist/
|
||||
### This Project ###
|
||||
/public/uploads
|
||||
/public/build
|
||||
# /public/tinymce
|
||||
*.key
|
||||
|
||||
### TinyMCE ###
|
||||
/public/tinymce
|
||||
!/public/tinymce/skins/ui/stemmech/
|
||||
|
||||
### Synk ###
|
||||
.dccache
|
||||
|
||||
### TempCodeRunner ###
|
||||
tempCodeRunnerFile.*
|
||||
tempCodeRunnerFile.*
|
||||
|
||||
### PHPUnit ###
|
||||
.phpunit.result.cache
|
||||
.gitignore
|
||||
|
||||
### Codesniffer ###
|
||||
phpcs.phar
|
||||
phpcbf.phar
|
||||
|
||||
### PHPStorm ###
|
||||
.idea/
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"bracketSameLine": true,
|
||||
"tabWidth": 4
|
||||
}
|
||||
20
.vscode/settings.json
vendored
20
.vscode/settings.json
vendored
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"editor.formatOnType": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
// "source.organizeImports": true // <-- when enabled, breaks tinymce required import order
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[php]": {
|
||||
// "editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
|
||||
"editor.defaultFormatter": "wongjn.php-sniffer"
|
||||
},
|
||||
"cSpell.words": ["TIMESTAMPDIFF"]
|
||||
}
|
||||
38
README.md
38
README.md
@@ -1,7 +1,7 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://travis-ci.org/laravel/framework"><img src="https://travis-ci.org/laravel/framework.svg" alt="Build Status"></a>
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
@@ -27,27 +27,45 @@ Laravel has the most extensive and thorough [documentation](https://laravel.com/
|
||||
|
||||
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
|
||||
### Premium Partners
|
||||
|
||||
- **[Vehikl](https://vehikl.com/)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[WebReinvent](https://webreinvent.com/)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Cubet Techno Labs](https://cubettech.com)**
|
||||
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||
- **[Many](https://www.many.co.uk)**
|
||||
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
|
||||
- **[DevSquad](https://devsquad.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Jump24](https://jump24.co.uk)**
|
||||
- **[Redberry](https://redberry.international/laravel/)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
- **[byte5](https://byte5.de)**
|
||||
- **[OP.GG](https://op.gg)**
|
||||
- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
|
||||
- **[Lendio](https://lendio.com)**
|
||||
|
||||
## Code Style
|
||||
|
||||
This project uses [Laravel Pint](https://laravel.com/docs/pint) for code styling. Pint is an opinionated PHP code style fixer for minimalists, built on top of PHP-CS-Fixer.
|
||||
|
||||
To automatically fix code style issues, run:
|
||||
|
||||
```bash
|
||||
composer pint
|
||||
```
|
||||
|
||||
To check for code style issues without fixing them:
|
||||
|
||||
```bash
|
||||
composer pint-test
|
||||
```
|
||||
|
||||
The code style configuration can be found in `pint.json`.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
7
api.http
Normal file
7
api.http
Normal file
@@ -0,0 +1,7 @@
|
||||
### Get media items
|
||||
GET http://127.0.0.1:8001/media
|
||||
Accept: application/json
|
||||
|
||||
### Get media item
|
||||
GET http://127.0.0.1:8001/media/SC-After-Dark.png
|
||||
Accept: application/json
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\MissingAttributeException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use LogicException;
|
||||
|
||||
class AnalyticsConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Analytics';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = 'created_at';
|
||||
|
||||
/**
|
||||
* The default includes to include in a request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $includes = ['duration'];
|
||||
|
||||
|
||||
/**
|
||||
* Return if the current model is visible.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow model to be visible.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\MissingAttributeException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use LogicException;
|
||||
|
||||
class ArticleConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Article';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = '-publish_at';
|
||||
|
||||
/**
|
||||
* The included fields
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $includes = ['attachments', 'user'];
|
||||
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||
$builder
|
||||
->where('publish_at', '<=', now());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is visible.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow model to be visible.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the final model data
|
||||
*
|
||||
* @param array $data The model data to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transformFinal(array $data)
|
||||
{
|
||||
unset($data['user_id']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include Attachments Field.
|
||||
*
|
||||
* @param Model $model Them model.
|
||||
* @return mixed The model result.
|
||||
*/
|
||||
public function includeAttachments(Model $model)
|
||||
{
|
||||
return $model->attachments()->get()->map(function ($attachment) {
|
||||
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Include User Field.
|
||||
*
|
||||
* @param Model $model Them model.
|
||||
* @return mixed The model result.
|
||||
*/
|
||||
public function includeUser(Model $model)
|
||||
{
|
||||
return UserConductor::includeModel(request(), 'user', User::find($model['user_id']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the Hero field.
|
||||
*
|
||||
* @param mixed $value The current value.
|
||||
* @return array The new value.
|
||||
*/
|
||||
public function transformHero(mixed $value)
|
||||
{
|
||||
return MediaConductor::includeModel(request(), 'hero', Media::find($value));
|
||||
}
|
||||
}
|
||||
@@ -1,989 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Conductor
|
||||
{
|
||||
/**
|
||||
* The Conductors Model class.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $class = null;
|
||||
|
||||
/**
|
||||
* The default sorting fields of a collection. Can be an array. Supports - and + prefixes.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $sort = "id";
|
||||
|
||||
/**
|
||||
* The default collection size limit per request.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $limit = 50;
|
||||
|
||||
/**
|
||||
* The maximum collection size limit per request.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $maxLimit = 100;
|
||||
|
||||
/**
|
||||
* The default includes to include in a request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $includes = [];
|
||||
|
||||
/**
|
||||
* The default filters to use in a request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFilters = [];
|
||||
|
||||
/**
|
||||
* The conductor collection.
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
protected $collection = null;
|
||||
|
||||
/**
|
||||
* The collection filter to apply.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filterArray = [];
|
||||
|
||||
/**
|
||||
* The conductor query.
|
||||
*
|
||||
* @var Builder
|
||||
*/
|
||||
private $query = null;
|
||||
|
||||
|
||||
/**
|
||||
* Split a string on commas, keeping quotes intact.
|
||||
*
|
||||
* @param string $string The string to split.
|
||||
* @return array The split string.
|
||||
*/
|
||||
private function splitString(string $string)
|
||||
{
|
||||
$parts = [];
|
||||
$start = 0;
|
||||
$len = strlen($string);
|
||||
|
||||
while ($start < $len) {
|
||||
$commaPos = strpos($string, ',', $start);
|
||||
$singlePos = strpos($string, '\'', $start);
|
||||
$doublePos = strpos($string, '"', $start);
|
||||
|
||||
// Find the smallest position that is not false
|
||||
$minPos = false;
|
||||
if ($commaPos !== false) {
|
||||
$minPos = $commaPos;
|
||||
}
|
||||
if ($singlePos !== false && ($minPos === false || $singlePos < $minPos)) {
|
||||
$minPos = $singlePos;
|
||||
}
|
||||
if ($doublePos !== false && ($minPos === false || $doublePos < $minPos)) {
|
||||
$minPos = $doublePos;
|
||||
}
|
||||
|
||||
if ($minPos === false) {
|
||||
// No more commas, single quotes, or double quotes found
|
||||
$part = substr($string, $start);
|
||||
$parts[] = trim($part);
|
||||
break;
|
||||
} else {
|
||||
// Add the current part to the parts array
|
||||
$part = substr($string, $start, ($minPos - $start));
|
||||
$parts[] = trim($part);
|
||||
|
||||
// Update the start position to the next character after the comma, single quote, or double quote
|
||||
if ($string[$minPos] === ',') {
|
||||
$start = ($minPos + 1);
|
||||
} else {
|
||||
$quoteChar = $string[$minPos];
|
||||
$endPos = strpos($string, $quoteChar, ($minPos + 1));
|
||||
if ($endPos === false) {
|
||||
$part = substr($string, ($minPos + 1));
|
||||
$parts[] = trim($part);
|
||||
break;
|
||||
} else {
|
||||
$part = substr($string, ($minPos + 1), ($endPos - $minPos - 1));
|
||||
$parts[] = trim($part);
|
||||
$start = ($endPos + 1);
|
||||
}
|
||||
}
|
||||
}//end if
|
||||
}//end while
|
||||
|
||||
return array_filter($parts, function ($value) {
|
||||
return $value !== '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Collection based on the Request.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param array|null $limitFields A list of fields to limit the filter request to.
|
||||
* @return void
|
||||
*/
|
||||
private function filter(Request $request, array|null $limitFields = null)
|
||||
{
|
||||
if (is_array($limitFields) === true && count($limitFields) === 0) {
|
||||
$limitFields = null;
|
||||
}
|
||||
|
||||
$filterFields = $request->all();
|
||||
if ($limitFields !== null) {
|
||||
$filterFields = array_intersect_key($filterFields, array_flip($limitFields));
|
||||
}
|
||||
$filterFields += $this->defaultFilters;
|
||||
|
||||
foreach ($filterFields as $field => $value) {
|
||||
if (
|
||||
is_array($limitFields) === false ||
|
||||
in_array(strtolower($field), array_map('strtolower', $limitFields)) !== false
|
||||
) {
|
||||
$value = trim($value);
|
||||
$operator = '';
|
||||
$join = 'OR';
|
||||
|
||||
// Check if value has a operator and remove it if it's a number
|
||||
if (preg_match('/^(!?=|[<>]=?|<>|!)([^=!<>].*)*$/', $value, $matches) > 0) {
|
||||
$operator = $matches[1];
|
||||
$value = ($matches[2] ?? '');
|
||||
}
|
||||
|
||||
switch ($operator) {
|
||||
case '=':
|
||||
$operator = '==';
|
||||
break;
|
||||
case '!':
|
||||
$operator = 'NOT LIKE';
|
||||
$value = "%{$value}%";
|
||||
break;
|
||||
case '>':
|
||||
case '<':
|
||||
case '>=':
|
||||
case '<=':
|
||||
case '!=':
|
||||
break;
|
||||
case '<>':
|
||||
$separatorPos = strpos($value, '|');
|
||||
if ($separatorPos === false) {
|
||||
$operator = '!=';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$operator = 'LIKE';
|
||||
$value = "%{$value}%";
|
||||
break;
|
||||
}//end switch
|
||||
|
||||
$this->appendFilter($field, $operator, $value, $join);
|
||||
}//end if
|
||||
}//end foreach
|
||||
if ($request->has('filter') === true) {
|
||||
$this->appendFilterString($request->input('filter', ''), $limitFields);
|
||||
}
|
||||
|
||||
$this->applyFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apple the filter array to the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
final public function applyFilters()
|
||||
{
|
||||
$parseFunc = function ($filterArray, $query) use (&$parseFunc) {
|
||||
$item = null;
|
||||
$result = null;
|
||||
$join = 'AND';
|
||||
|
||||
if (gettype($query) === 'array') {
|
||||
$item = $query;
|
||||
}
|
||||
|
||||
foreach ($filterArray as $condition) {
|
||||
$currentResult = false;
|
||||
|
||||
if (is_array($condition) === true) {
|
||||
if (isset($condition[0]) === true && is_array($condition[0]) === true) {
|
||||
if ($item !== null) {
|
||||
$currentResult = $parseFunc($condition, $item);
|
||||
} else {
|
||||
if ($join === 'OR') {
|
||||
$query->orWhere(function ($subQuery) use ($parseFunc, $condition) {
|
||||
$parseFunc($condition, $subQuery);
|
||||
});
|
||||
} else {
|
||||
$query->where(function ($subQuery) use ($parseFunc, $condition) {
|
||||
$parseFunc($condition, $subQuery);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list($field, $operator, $value) = $condition;
|
||||
|
||||
if ($item !== null) {
|
||||
if (array_key_exists($field, $item) === true) {
|
||||
switch ($operator) {
|
||||
case '==':
|
||||
$currentResult = ($item[$field] == $value);
|
||||
break;
|
||||
case 'NOT LIKE':
|
||||
$currentResult = (stripos($item[$field], substr($value, 1, -1)) === false);
|
||||
break;
|
||||
case '>':
|
||||
$currentResult = ($item[$field] > $value);
|
||||
break;
|
||||
case '<':
|
||||
$currentResult = ($item[$field] < $value);
|
||||
break;
|
||||
case '>=':
|
||||
$currentResult = ($item[$field] >= $value);
|
||||
break;
|
||||
case '<=':
|
||||
$currentResult = ($item[$field] <= $value);
|
||||
break;
|
||||
case '!=':
|
||||
$currentResult = ($item[$field] != $value);
|
||||
break;
|
||||
case '<>':
|
||||
$separatorPos = strpos($value, '|');
|
||||
if ($separatorPos !== false) {
|
||||
$fieldInt = intval($item[$field]);
|
||||
$currentResult = (
|
||||
$fieldInt > intVal(
|
||||
substr($value, 0, $separatorPos)
|
||||
) && $fieldInt < intVal(substr($value, ($separatorPos + 1))));
|
||||
} else {
|
||||
$currentResult = ($item[$field] != $value);
|
||||
}
|
||||
break;
|
||||
case 'LIKE':
|
||||
$currentResult = (stripos($item[$field], substr($value, 1, -1)) !== false);
|
||||
break;
|
||||
}//end switch
|
||||
}//end if
|
||||
} else {
|
||||
if ($operator === '==') {
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
if ($join === 'OR') {
|
||||
if ($operator === '<>') {
|
||||
$separatorPos = strpos($value, '|');
|
||||
if ($separatorPos !== false) {
|
||||
$query->orWhereBetween(
|
||||
$field,
|
||||
[substr($value, 0, $separatorPos), substr($value, ($separatorPos + 1))]
|
||||
);
|
||||
} else {
|
||||
$query->orWhere($field, '!=', $value);
|
||||
}
|
||||
} else {
|
||||
$query->orWhere($field, $operator, $value);
|
||||
}
|
||||
} else {
|
||||
if ($operator === '<>') {
|
||||
$separatorPos = strpos($value, '|');
|
||||
if ($separatorPos !== false) {
|
||||
$query->whereBetween(
|
||||
$field,
|
||||
[substr($value, 0, $separatorPos), substr($value, ($separatorPos + 1))]
|
||||
);
|
||||
} else {
|
||||
$query->where($field, '!=', $value);
|
||||
}
|
||||
} else {
|
||||
$query->where($field, $operator, $value);
|
||||
}
|
||||
}//end if
|
||||
}//end if
|
||||
}//end if
|
||||
|
||||
if ($item !== null) {
|
||||
if ($result === null) {
|
||||
$result = $currentResult;
|
||||
} else {
|
||||
if ($join === 'OR') {
|
||||
$result = $result || $currentResult;
|
||||
} else {
|
||||
$result = $result && $currentResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$join = 'OR';
|
||||
} else {
|
||||
$join = $condition;
|
||||
}//end if
|
||||
}//end foreach
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
$filterArray = $this->filterArray;
|
||||
if (count($filterArray) === 0) {
|
||||
$filterArray = $this->defaultFilters;
|
||||
}
|
||||
if (count($filterArray) !== 0) {
|
||||
if ($this->collection !== null) {
|
||||
$this->collection = $this->collection->filter(function ($item) use ($parseFunc) {
|
||||
return $parseFunc($this->filterArray, $item);
|
||||
});
|
||||
} else {
|
||||
$parseFunc($this->filterArray, $this->query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the conductor on a Request to generate a collection and total.
|
||||
*
|
||||
* @param Request $request The request data.
|
||||
* @return array The processed and transformed collection | the total rows found.
|
||||
*/
|
||||
final public static function request(Request $request)
|
||||
{
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$total = 0;
|
||||
|
||||
try {
|
||||
$conductor->query = $conductor->class::query();
|
||||
} catch (\Throwable $e) {
|
||||
throw new \Exception('Failed to create query builder instance for ' . $conductor->class . '.', 0, $e);
|
||||
}
|
||||
|
||||
// Filter request
|
||||
$limitFields = $conductor->fields(new $conductor->class());
|
||||
if (is_array($limitFields) === false) {
|
||||
$limitFields = [];
|
||||
}
|
||||
$conductor->filter($request, $limitFields);
|
||||
|
||||
// After Scope query
|
||||
$conductor->query->where(function ($query) use ($conductor) {
|
||||
$conductor->scope($query);
|
||||
});
|
||||
|
||||
// Sort request
|
||||
$sort = $request->input('sort', $conductor->sort);
|
||||
if (strlen($sort) === 0) {
|
||||
if (strlen($conductor->sort) > 0) {
|
||||
$conductor->sort($conductor->sort);
|
||||
}
|
||||
} else {
|
||||
$conductor->sort($sort);
|
||||
}
|
||||
|
||||
// Get total
|
||||
$total = $conductor->count();
|
||||
|
||||
// Paginate
|
||||
$conductor->paginate($request->input('page', 1), $request->input('limit', -1), $request->input('offset', 0));
|
||||
|
||||
// Filter request
|
||||
$fields = $conductor->fields(new $conductor->class());
|
||||
if (is_array($fields) === false) {
|
||||
$fields = [];
|
||||
}
|
||||
|
||||
// Limit fields
|
||||
$limitFields = array_map(function ($field) {
|
||||
if (strpos($field, '.') !== false) {
|
||||
return substr($field, 0, strpos($field, '.'));
|
||||
}
|
||||
return $field;
|
||||
}, explode(',', $request->input('fields')));
|
||||
if ($limitFields === null) {
|
||||
$limitFields = $fields;
|
||||
} else {
|
||||
$limitFields = array_intersect($limitFields, $fields);
|
||||
}
|
||||
$conductor->limitFields($limitFields);
|
||||
$conductor->collection = $conductor->query->get();
|
||||
|
||||
// Transform and Includes
|
||||
$includes = $conductor->includes;
|
||||
if (count($limitFields) > 0) {
|
||||
$includes = array_intersect($limitFields, $conductor->includes);
|
||||
}
|
||||
|
||||
$conductor->collection = $conductor->collection->map(
|
||||
function ($model) use ($conductor, $includes, $limitFields) {
|
||||
$conductor->applyIncludes($model, $includes);
|
||||
|
||||
if (count($limitFields) > 0) {
|
||||
$model->setAppends(array_intersect($model->getAppends(), $limitFields));
|
||||
}
|
||||
|
||||
$model = $conductor->transformModel($model);
|
||||
|
||||
return $model;
|
||||
}
|
||||
);
|
||||
|
||||
return [$conductor->collection, $total];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the conductor on a collection with the data stored in a Request.
|
||||
*
|
||||
* @param Request $request The request data.
|
||||
* @param Collection $collection The collection.
|
||||
* @return array The processed and transformed model data.
|
||||
*/
|
||||
final public static function collection(Request $request, Collection $collection)
|
||||
{
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$conductor->collection = collect();
|
||||
|
||||
foreach ($collection as $item) {
|
||||
if ($conductor->viewable($item) === true) {
|
||||
$conductor->collection->push($conductor->transformModel($item));
|
||||
}
|
||||
}
|
||||
|
||||
// Filter request
|
||||
$limitFields = $conductor->fields(new $conductor->class());
|
||||
if (is_array($limitFields) === false) {
|
||||
$limitFields = [];
|
||||
}
|
||||
$conductor->filter($request, $limitFields);
|
||||
|
||||
// Get total
|
||||
$total = $conductor->collection->count();
|
||||
|
||||
// Sort request
|
||||
$sort = $request->input('sort', $conductor->sort);
|
||||
if (strlen($sort) === 0) {
|
||||
if (strlen($conductor->sort) > 0) {
|
||||
$conductor->sort($sort);
|
||||
}
|
||||
} else {
|
||||
$conductor->sort($sort);
|
||||
}
|
||||
|
||||
// Paginate
|
||||
$conductor->paginate($request->input('page', 1), $request->input('limit', -1), $request->input('offset', 0));
|
||||
|
||||
|
||||
return [$conductor->collection, $total];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a custom query on a user request.
|
||||
*
|
||||
* @param Builder $query The custom query.
|
||||
* @param Request $request The request.
|
||||
* @param array|null $limitFields Limit the request to these fields.
|
||||
* @return Builder
|
||||
*/
|
||||
public static function filterQuery(Builder $query, Request $request, array|null $limitFields = null)
|
||||
{
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$conductor->query = $query;
|
||||
$conductor->filter($request, $limitFields);
|
||||
|
||||
return $conductor->query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the conductor on a Model with the data stored in a Request.
|
||||
*
|
||||
* @param Request $request The request data.
|
||||
* @param string $key The key prefix to use.
|
||||
* @param Model|null $model The model.
|
||||
* @return array The processed and transformed model data.
|
||||
*/
|
||||
final public static function includeModel(Request $request, string $key, mixed $model)
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
if ($request !== null && $request->has('fields') === true) {
|
||||
$requestFields = $request->input('fields');
|
||||
if ($requestFields !== null) {
|
||||
$requestFields = explode(',', $requestFields);
|
||||
if (in_array($key, $requestFields) === false) {
|
||||
foreach ($requestFields as $field) {
|
||||
if (strpos($field, $key . '.') === 0) {
|
||||
$fields[] = substr($field, (strlen($key) + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static::model($fields, $model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the conductor on a Model with the data stored in a Request.
|
||||
*
|
||||
* @param mixed $fields The fields to show.
|
||||
* @param Model|null $model The model.
|
||||
* @return array The processed and transformed model data.
|
||||
*/
|
||||
final public static function model(mixed $fields, mixed $model)
|
||||
{
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$modelFields = $conductor->fields(new $conductor->class());
|
||||
|
||||
// Limit fields
|
||||
$limitFields = $modelFields;
|
||||
if ($fields instanceof Request) {
|
||||
if ($fields !== null && $fields->has('fields') === true) {
|
||||
$requestFields = $fields->input('fields');
|
||||
if ($requestFields !== null) {
|
||||
$limitFields = array_intersect(explode(',', $requestFields), $modelFields);
|
||||
}
|
||||
}
|
||||
} elseif (is_array($fields) === true && count($fields) > 0) {
|
||||
$limitFields = array_intersect($fields, $modelFields);
|
||||
}
|
||||
|
||||
if (empty($limitFields) === false) {
|
||||
$modelAppends = $model->getAppends();
|
||||
|
||||
foreach (array_diff($modelFields, $limitFields) as $attribute) {
|
||||
$key = array_search($attribute, $modelAppends);
|
||||
if ($key !== false) {
|
||||
unset($modelAppends[$key]);
|
||||
} else {
|
||||
unset($model[$attribute]);
|
||||
}
|
||||
}
|
||||
$model->setAppends($modelAppends);
|
||||
}
|
||||
|
||||
// Includes
|
||||
$includes = array_intersect($limitFields, $conductor->includes);
|
||||
$conductor->applyIncludes($model, $includes);
|
||||
|
||||
// Transform
|
||||
$model = $conductor->transformModel($model);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current conductor collection count.
|
||||
*
|
||||
* @return integer The current collection count.
|
||||
*/
|
||||
final public function count()
|
||||
{
|
||||
if ($this->query !== null) {
|
||||
return $this->query->count();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the conductor collection.
|
||||
*
|
||||
* @param mixed $fields A field name or array of field names to sort. Supports prefix of +/- to change direction.
|
||||
* @return void
|
||||
*/
|
||||
final public function sort(mixed $fields = null)
|
||||
{
|
||||
$collectionSort = [];
|
||||
|
||||
if (is_string($fields) === true) {
|
||||
$fields = explode(',', $fields);
|
||||
} elseif ($fields === null) {
|
||||
$fields = $this->sort;
|
||||
}
|
||||
|
||||
if (is_array($fields) === true) {
|
||||
foreach ($fields as $orderByField) {
|
||||
$direction = 'asc';
|
||||
$directionChar = substr($orderByField, 0, 1);
|
||||
|
||||
if (in_array($directionChar, ['-', '+']) === true) {
|
||||
$orderByField = substr($orderByField, 1);
|
||||
if ($directionChar === '-') {
|
||||
$direction = 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->collection !== null) {
|
||||
$collectionSort[] = [trim($orderByField), $direction];
|
||||
} else {
|
||||
$this->query->orderBy(trim($orderByField), $direction);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Expected string or array, got ' . gettype($fields));
|
||||
}//end if
|
||||
|
||||
if ($this->collection !== null) {
|
||||
$this->collection = $this->collection->sortBy($collectionSort)->values();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the conductor collection.
|
||||
*
|
||||
* @param integer $page The current page to return.
|
||||
* @param integer $limit The limit of items to include or use default.
|
||||
* @param integer $offset Offset the page count after this count of rows.
|
||||
* @return mixed
|
||||
*/
|
||||
final public function paginate(int $page = 1, int $limit = -1, int $offset = 0)
|
||||
{
|
||||
// Limit
|
||||
if ($limit < 1) {
|
||||
$limit = $this->limit;
|
||||
} else {
|
||||
$limit = min($limit, $this->maxLimit);
|
||||
}
|
||||
|
||||
// Page
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
// After
|
||||
if ($offset < 0) {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
if ($this->collection !== null) {
|
||||
$this->collection = $this->collection->splice(((($page - 1) * $limit) + $offset), $limit);
|
||||
} else {
|
||||
$this->query->limit($limit);
|
||||
$this->query->offset((($page - 1) * $limit) + $offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a list of includes to the model.
|
||||
*
|
||||
* @param Model $model The model to append.
|
||||
* @param array $includes The list of includes to include.
|
||||
* @return void
|
||||
*/
|
||||
final public function applyIncludes(Model $model, array $includes)
|
||||
{
|
||||
foreach ($includes as $include) {
|
||||
$includeMethodName = 'include' . Str::studly($include);
|
||||
if (method_exists($this, $includeMethodName) === true) {
|
||||
$attributeName = Str::snake($include);
|
||||
$attributeValue = $this->{$includeMethodName}($model);
|
||||
if ($attributeValue !== null) {
|
||||
$model->$attributeName = $this->{$includeMethodName}($model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the returned fields in the conductor collection.
|
||||
*
|
||||
* @param array $fields An array of field names.
|
||||
* @return void
|
||||
*/
|
||||
final public function limitFields(array $fields)
|
||||
{
|
||||
if (empty($fields) !== true) {
|
||||
$this->query->select(array_diff($fields, $this->includes));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the conductor collection using raw data.
|
||||
*
|
||||
* @param string $rawFilter The raw filter string to parse.
|
||||
* @param array|null $limitFields The fields to allow in the filter string.
|
||||
* @param string $outerJoin The join for this filter group.
|
||||
* @return void
|
||||
*/
|
||||
final public function appendFilterString(string $rawFilter, array|null $limitFields = null, string $outerJoin = 'OR')
|
||||
{
|
||||
if ($rawFilter === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (substr($rawFilter, -1) !== ',') {
|
||||
$rawFilter .= ',';
|
||||
}
|
||||
|
||||
$parseFunc = function ($string, &$i = 0) use (&$parseFunc, $limitFields) {
|
||||
$tokens = [];
|
||||
$ignoreUntil = '';
|
||||
$skipUntil = '';
|
||||
$field = '';
|
||||
$value = null;
|
||||
$set = &$field;
|
||||
|
||||
for (; $i < strlen($string); $i++) {
|
||||
$char = $string[$i];
|
||||
|
||||
if ($skipUntil !== '' && $char !== $skipUntil) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($ignoreUntil === '') {
|
||||
if ($char === '\'' || $char === '"') {
|
||||
$ignoreUntil = $char;
|
||||
} elseif ($char === ':') {
|
||||
if ($field === '') {
|
||||
$skipUntil = ',';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($field[0] === '\'' || $field[0] === '"') {
|
||||
$field = substr($field, 1, -1);
|
||||
}
|
||||
|
||||
$set = &$value;
|
||||
continue;
|
||||
} elseif (($char === ')' && $string[($i + 1)] === ',') || $char === ',') {
|
||||
if ($value === null) {
|
||||
$tokens[] = $field;
|
||||
} else {
|
||||
$value = trim($value);
|
||||
$operator = 'LIKE';
|
||||
|
||||
// Check if value has a operator and remove it if it's a number
|
||||
if (preg_match('/^(!?=|[<>]=?|<>|!)([^=!<>].*)*$/', $value, $matches) > 0) {
|
||||
$operator = $matches[1];
|
||||
$value = ($matches[2] ?? '');
|
||||
}
|
||||
|
||||
if ($value[0] === '\'' || $value[0] === '"') {
|
||||
$value = substr($value, 1, -1);
|
||||
}
|
||||
|
||||
if ($operator === 'LIKE') {
|
||||
$value = "%{$value}%";
|
||||
}
|
||||
|
||||
if (
|
||||
is_array($limitFields) === false ||
|
||||
in_array(strtolower($field), array_map('strtolower', $limitFields)) !== false
|
||||
) {
|
||||
$tokens[] = [$field, $operator, $value];
|
||||
}
|
||||
}//end if
|
||||
|
||||
$field = '';
|
||||
$value = null;
|
||||
$set = &$field;
|
||||
|
||||
if ($char === ')') {
|
||||
$i++;
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
continue;
|
||||
} elseif ($char === '(') {
|
||||
if ($field === '') {
|
||||
$i++;
|
||||
$tokens[] = $parseFunc($string, $i);
|
||||
continue;
|
||||
}
|
||||
}//end if
|
||||
} elseif ($char === $ignoreUntil) {
|
||||
$ignoreUntil = '';
|
||||
}//end if
|
||||
|
||||
$set .= $char;
|
||||
}//end for
|
||||
|
||||
return $tokens;
|
||||
};
|
||||
|
||||
$i = 0;
|
||||
$filterArray = $parseFunc($rawFilter, $i);
|
||||
|
||||
if (count($this->filterArray) !== 0) {
|
||||
$this->filterArray[] = $outerJoin;
|
||||
}
|
||||
$this->filterArray[] = $filterArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a field to the filter array.
|
||||
*
|
||||
* @param string $field The field name to append.
|
||||
* @param string $operator The operator to append.
|
||||
* @param string $value The value to append.
|
||||
* @param string $join The join to append.
|
||||
* @return void
|
||||
*/
|
||||
final public function appendFilter(string $field, string $operator, string $value, string $join = 'OR')
|
||||
{
|
||||
if (count($this->filterArray) !== 0) {
|
||||
$this->filterArray[] = $join;
|
||||
}
|
||||
$this->filterArray[] = [$field, $operator, $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of model fields visible to the current user.
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return array The array of field names.
|
||||
*/
|
||||
public function fields(Model $model)
|
||||
{
|
||||
$visibleFields = $model->getVisible();
|
||||
if (empty($visibleFields) === true) {
|
||||
$visibleFields = $model->getConnection()
|
||||
->getSchemaBuilder()
|
||||
->getColumnListing($model->getTable());
|
||||
}
|
||||
|
||||
$appends = $model->getAppends();
|
||||
if (is_array($appends) === true) {
|
||||
$visibleFields = array_merge($visibleFields, $appends);
|
||||
}
|
||||
|
||||
if (is_array($this->includes) === true) {
|
||||
$visibleFields = array_merge($visibleFields, $this->includes);
|
||||
}
|
||||
|
||||
return $visibleFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the passed Model to an array
|
||||
*
|
||||
* @param Model $model The model to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
protected function transformModel(Model $model)
|
||||
{
|
||||
$result = $this->transform($model);
|
||||
foreach ($result as $key => $value) {
|
||||
$transformFunction = 'transform' . Str::studly($key);
|
||||
if (method_exists($this, $transformFunction) === true) {
|
||||
$result[$key] = $this->$transformFunction($value);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->transformFinal($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the passed Model to an array
|
||||
*
|
||||
* @param Model $model The model to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transform(Model $model)
|
||||
{
|
||||
$result = $model->toArray();
|
||||
|
||||
$fields = $this->fields($model);
|
||||
|
||||
if (is_array($fields) === true) {
|
||||
$result = array_intersect_key($result, array_flip($fields));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final Transform of the model array
|
||||
*
|
||||
* @param array $data The model array to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transformFinal(array $data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the passed model viewable by the current user?
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return boolean Is the model viewable.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the model creatable by the current user?
|
||||
*
|
||||
* @return boolean Is the model creatable.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the passed model updatable by the current user?
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return boolean Is the model updatable.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the passed model destroyable by the current user?
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return boolean Is the model destroyable.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use App\Models\Media;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EventConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Event';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = '-start_at';
|
||||
|
||||
/**
|
||||
* The included fields
|
||||
* @var string[]
|
||||
*/
|
||||
protected $includes = ['attachments'];
|
||||
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||
$builder
|
||||
->where('status', '!=', 'draft')
|
||||
->where('publish_at', '<=', now());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is visible.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow model to be visible.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
if (strtolower($model->status) === 'draft' || Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include Attachments Field.
|
||||
*
|
||||
* @param Model $model Them model.
|
||||
* @return mixed The model result.
|
||||
*/
|
||||
public function includeAttachments(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
return $model->attachments()->get()->map(function ($attachment) use ($user) {
|
||||
if ($attachment->private === false || ($user !== null && ($user->hasPermission('admin/events') === true || $attachment->users->contains($user) === true))) {
|
||||
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the Hero field.
|
||||
*
|
||||
* @param mixed $value The current value.
|
||||
* @return array The new value.
|
||||
*/
|
||||
public function transformHero(mixed $value)
|
||||
{
|
||||
return MediaConductor::includeModel(request(), 'hero', Media::find($value));
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Auth\User;
|
||||
|
||||
class MediaConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Media';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = 'created_at';
|
||||
|
||||
/**
|
||||
* The included fields
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $includes = ['user'];
|
||||
|
||||
/**
|
||||
* The default filters to use in a request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFilters = [
|
||||
'status' => 'OK'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of model fields visible to the current user.
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return array The array of field names.
|
||||
*/
|
||||
public function fields(Model $model)
|
||||
{
|
||||
$fields = parent::fields($model);
|
||||
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/media') === false) {
|
||||
$fields = arrayRemoveItem($fields, ['permission', 'storage']);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null) {
|
||||
$builder->where('permission', '');
|
||||
} else {
|
||||
$builder->where('permission', '')->orWhereIn('permission', $user->permissions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is visible.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow model to be visible.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
if ($model->permission !== '') {
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission($model->permission) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && (strcasecmp($model->user_id, $user->id) === 0 || $user->hasPermission('admin/media') === true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && ($model->user_id === $user->id || $user->hasPermission('admin/media') === true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the final model data
|
||||
*
|
||||
* @param array $data The model data to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transformFinal(array $data)
|
||||
{
|
||||
unset($data['user_id']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include User Field.
|
||||
*
|
||||
* @param Model $model Them model.
|
||||
* @return mixed The model result.
|
||||
*/
|
||||
public function includeUser(Model $model)
|
||||
{
|
||||
return UserConductor::includeModel(request(), 'user', User::find($model['user_id']));
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Auth\User;
|
||||
|
||||
class ShortlinkConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Shortlink';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = 'created_at';
|
||||
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SubscriptionConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Subscription';
|
||||
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\User';
|
||||
|
||||
|
||||
/**
|
||||
* Return the visible API fields.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return string[] The fields visible.
|
||||
*/
|
||||
public function fields(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/users') === false) {
|
||||
return ['id', 'display_name'];
|
||||
}
|
||||
|
||||
return parent::fields($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the passed Model to an array
|
||||
*
|
||||
* @param Model $model The model to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transform(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$data = $model->toArray();
|
||||
|
||||
if ($user === null || ($user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0)) {
|
||||
$fields = ['id', 'display_name'];
|
||||
$data = arrayLimitKeys($data, $fields);
|
||||
} else {
|
||||
$data['permissions'] = $user->permissions;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user !== null) {
|
||||
return ($user->hasPermission('admin/users') === true || strcasecmp($user->id, $model->id) === 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/users') === true);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\StoreUploadedFileJob;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Media;
|
||||
use File;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class MediaMigrate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'media:migrate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Migrate the uploads folder to the CDN';
|
||||
|
||||
|
||||
/**
|
||||
* Configure the command options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption(
|
||||
'replace',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Replace existing files'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$replace = $this->option('replace');
|
||||
|
||||
$files = File::allFiles(public_path('uploads'));
|
||||
|
||||
foreach ($files as $file) {
|
||||
$filename = pathinfo($file, PATHINFO_BASENAME);
|
||||
|
||||
$medium = Media::where('name', $filename)->first();
|
||||
|
||||
if ($medium !== null) {
|
||||
$medium->update(['status' => 'Processing media']);
|
||||
StoreUploadedFileJob::dispatch($medium, $file, $replace)->onQueue('media');
|
||||
} else {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\StoreUploadedFileJob;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Media;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class MediaRebuild extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'media:rebuild';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rebuild the media table';
|
||||
|
||||
|
||||
/**
|
||||
* Configure the command options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->addOption(
|
||||
'replace',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Replace existing files'
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'all',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Rebuild all variants'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$replace = $this->option('replace');
|
||||
$all = $this->option('replace');
|
||||
|
||||
$media = [];
|
||||
if ($all === true) {
|
||||
$media = Media::all();
|
||||
} else {
|
||||
$media = Media::where(['variants' => ''])->orWhere(['variants' => '[]'])->orWhere(['variants' => '{}'])->get();
|
||||
}
|
||||
|
||||
foreach ($media as $medium) {
|
||||
StoreUploadedFileJob::dispatch($medium, '', $replace)->onQueue('media');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule The schedule.
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
class CurlErrorCodes extends Enum
|
||||
{
|
||||
public const CURLE_UNSUPPORTED_PROTOCOL = 1;
|
||||
public const CURLE_FAILED_INIT = 2;
|
||||
public const CURLE_URL_MALFORMAT = 3;
|
||||
public const CURLE_URL_MALFORMAT_USER = 4;
|
||||
public const CURLE_COULDNT_RESOLVE_PROXY = 5;
|
||||
public const CURLE_COULDNT_RESOLVE_HOST = 6;
|
||||
public const CURLE_COULDNT_CONNECT = 7;
|
||||
public const CURLE_FTP_WEIRD_SERVER_REPLY = 8;
|
||||
public const CURLE_REMOTE_ACCESS_DENIED = 9;
|
||||
public const CURLE_FTP_WEIRD_PASS_REPLY = 11;
|
||||
public const CURLE_FTP_WEIRD_PASV_REPLY = 13;
|
||||
public const CURLE_FTP_WEIRD_227_FORMAT = 14;
|
||||
public const CURLE_FTP_CANT_GET_HOST = 15;
|
||||
public const CURLE_FTP_COULDNT_SET_TYPE = 17;
|
||||
public const CURLE_PARTIAL_FILE = 18;
|
||||
public const CURLE_FTP_COULDNT_RETR_FILE = 19;
|
||||
public const CURLE_QUOTE_ERROR = 21;
|
||||
public const CURLE_HTTP_RETURNED_ERROR = 22;
|
||||
public const CURLE_WRITE_ERROR = 23;
|
||||
public const CURLE_UPLOAD_FAILED = 25;
|
||||
public const CURLE_READ_ERROR = 26;
|
||||
public const CURLE_OUT_OF_MEMORY = 27;
|
||||
public const CURLE_OPERATION_TIMEDOUT = 28;
|
||||
public const CURLE_FTP_PORT_FAILED = 30;
|
||||
public const CURLE_FTP_COULDNT_USE_REST = 31;
|
||||
public const CURLE_RANGE_ERROR = 33;
|
||||
public const CURLE_HTTP_POST_ERROR = 34;
|
||||
public const CURLE_SSL_CONNECT_ERROR = 35;
|
||||
public const CURLE_BAD_DOWNLOAD_RESUME = 36;
|
||||
public const CURLE_FILE_COULDNT_READ_FILE = 37;
|
||||
public const CURLE_LDAP_CANNOT_BIND = 38;
|
||||
public const CURLE_LDAP_SEARCH_FAILED = 39;
|
||||
public const CURLE_FUNCTION_NOT_FOUND = 41;
|
||||
public const CURLE_ABORTED_BY_CALLBACK = 42;
|
||||
public const CURLE_BAD_FUNCTION_ARGUMENT = 43;
|
||||
public const CURLE_INTERFACE_FAILED = 45;
|
||||
public const CURLE_TOO_MANY_REDIRECTS = 47;
|
||||
public const CURLE_UNKNOWN_TELNET_OPTION = 48;
|
||||
public const CURLE_TELNET_OPTION_SYNTAX = 49;
|
||||
public const CURLE_PEER_FAILED_VERIFICATION = 51;
|
||||
public const CURLE_GOT_NOTHING = 52;
|
||||
public const CURLE_SSL_ENGINE_NOTFOUND = 53;
|
||||
public const CURLE_SSL_ENGINE_SETFAILED = 54;
|
||||
public const CURLE_SEND_ERROR = 55;
|
||||
public const CURLE_RECV_ERROR = 56;
|
||||
public const CURLE_SSL_CERTPROBLEM = 58;
|
||||
public const CURLE_SSL_CIPHER = 59;
|
||||
public const CURLE_SSL_CACERT = 60;
|
||||
public const CURLE_BAD_CONTENT_ENCODING = 61;
|
||||
public const CURLE_LDAP_INVALID_URL = 62;
|
||||
public const CURLE_FILESIZE_EXCEEDED = 63;
|
||||
public const CURLE_USE_SSL_FAILED = 64;
|
||||
public const CURLE_SEND_FAIL_REWIND = 65;
|
||||
public const CURLE_SSL_ENGINE_INITFAILED = 66;
|
||||
public const CURLE_LOGIN_DENIED = 67;
|
||||
public const CURLE_TFTP_NOTFOUND = 68;
|
||||
public const CURLE_TFTP_PERM = 69;
|
||||
public const CURLE_REMOTE_DISK_FULL = 70;
|
||||
public const CURLE_TFTP_ILLEGAL = 71;
|
||||
public const CURLE_TFTP_UNKNOWNID = 72;
|
||||
public const CURLE_REMOTE_FILE_EXISTS = 73;
|
||||
public const CURLE_TFTP_NOSUCHUSER = 74;
|
||||
public const CURLE_CONV_FAILED = 75;
|
||||
public const CURLE_CONV_REQD = 76;
|
||||
public const CURLE_SSL_CACERT_BADFILE = 77;
|
||||
public const CURLE_REMOTE_FILE_NOT_FOUND = 78;
|
||||
public const CURLE_SSH = 79;
|
||||
public const CURLE_SSL_SHUTDOWN_FAILED = 80;
|
||||
public const CURLE_AGAIN = 81;
|
||||
public const CURLE_SSL_CRL_BADFILE = 82;
|
||||
public const CURLE_SSL_ISSUER_ERROR = 83;
|
||||
public const CURLE_FTP_PRET_FAILED = 84;
|
||||
public const CURLE_RTSP_CSEQ_ERROR = 85;
|
||||
public const CURLE_RTSP_SESSION_ERROR = 86;
|
||||
public const CURLE_FTP_BAD_FILE_LIST = 87;
|
||||
public const CURLE_CHUNK_FAILED = 88;
|
||||
|
||||
|
||||
/**
|
||||
* Curl Error messages
|
||||
* @var string[]
|
||||
*/
|
||||
public static $messages = [
|
||||
1 => 'Unsupported protocol.',
|
||||
2 => 'Failed initalization.',
|
||||
3 => 'Invalid URL format.',
|
||||
4 => 'CURLE_URL_MALFORMAT_USER.',
|
||||
5 => 'Could not resolve proxy.',
|
||||
6 => 'Could not resolve host.',
|
||||
7 => 'Could not connect to host.',
|
||||
8 => 'Invalid reply from FTP server.',
|
||||
9 => 'Access denied on host.',
|
||||
11 => 'Invalid pass reply from FTP server.',
|
||||
13 => 'Invalid pasv reply from FTP server.',
|
||||
14 => 'Invalid 227 format from FTP server.',
|
||||
15 => 'Could not get FTP host.',
|
||||
17 => 'Could not set type for FTP transfer.',
|
||||
18 => 'Invalid partial size.',
|
||||
19 => 'Could not retrieve file from FTP server.',
|
||||
21 => 'Quote error.',
|
||||
22 => 'HTTP server returned error.',
|
||||
23 => 'File write error.',
|
||||
25 => 'Upload file error.',
|
||||
26 => 'File read error.',
|
||||
27 => 'Out of memory.',
|
||||
28 => 'File transfer timed out.',
|
||||
30 => 'Invalid port for FTP server.',
|
||||
31 => 'Could not use rest for FTP server.',
|
||||
33 => 'File range error.',
|
||||
34 => 'Invalid POST for HTTP server.',
|
||||
35 => 'SSL connectio error.',
|
||||
36 => 'Invalid resume download.',
|
||||
37 => 'Could not read file.',
|
||||
38 => 'Could not bind to LDAP.',
|
||||
39 => 'LDAP search failed.',
|
||||
41 => 'Function not found.',
|
||||
42 => 'Aborted by callback.',
|
||||
43 => 'Bad function argument.',
|
||||
45 => 'Interface failed.',
|
||||
47 => 'Too many redirects.',
|
||||
48 => 'Unknown telnet option.',
|
||||
49 => 'Telnet option syntax invalid.',
|
||||
51 => 'Peer failed verification.',
|
||||
52 => 'Did not receive any data.',
|
||||
53 => 'SSL engine was not found.',
|
||||
54 => 'SSL engine failed.',
|
||||
55 => 'Send data error.',
|
||||
56 => 'Receive data error.',
|
||||
58 => 'SSL certificate error.',
|
||||
59 => 'SSL cipher error.',
|
||||
60 => 'SSL CACertificate failed.',
|
||||
61 => 'Invalid content encoding.',
|
||||
62 => 'Invalid LDAP url.',
|
||||
63 => 'Filesize exceeded.',
|
||||
64 => 'SSL Failed.',
|
||||
65 => 'CURLE_SEND_FAIL_REWIND.',
|
||||
66 => 'SSL engine initalization failed.',
|
||||
67 => 'CURLE_LOGIN_DENIED.',
|
||||
68 => 'CURLE_TFTP_NOTFOUND.',
|
||||
69 => 'CURLE_TFTP_PERM.',
|
||||
70 => 'CURLE_REMOTE_DISK_FULL.',
|
||||
71 => 'CURLE_TFTP_ILLEGAL.',
|
||||
72 => 'CURLE_TFTP_UNKNOWNID.',
|
||||
73 => 'Remote file already exists.',
|
||||
74 => 'No such user on FTP server.',
|
||||
75 => 'Conversion failed.',
|
||||
76 => 'Conversion required.',
|
||||
77 => 'SSL CACertificate bad file.',
|
||||
78 => 'Remove file not found.',
|
||||
79 => 'SSH error.',
|
||||
80 => 'SSL Shutdown failed.',
|
||||
81 => 'Again.',
|
||||
82 => 'SSL bad CRL file.',
|
||||
83 => 'SSL issuer error.',
|
||||
84 => 'FTP pret failed.',
|
||||
85 => 'CURLE_RTSP_CSEQ_ERROR.',
|
||||
86 => 'CURLE_RTSP_SESSION_ERROR.',
|
||||
87 => 'CURLE_FTP_BAD_FILE_LIST.',
|
||||
88 => 'CURLE_CHUNK_FAILED.',
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
use ReflectionClass;
|
||||
|
||||
class Enum
|
||||
{
|
||||
/**
|
||||
* Message list
|
||||
*
|
||||
* @var array<string<static>>
|
||||
*/
|
||||
public static $messages = [];
|
||||
|
||||
/**
|
||||
* Caches reflections of enum subclasses.
|
||||
*
|
||||
* @var array<class-string<static>, ReflectionClass<static>>
|
||||
*/
|
||||
public static $reflectionCache = [];
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reflection of the enum subclass.
|
||||
*
|
||||
* @return ReflectionClass<static>
|
||||
*/
|
||||
public static function getReflection(): ReflectionClass
|
||||
{
|
||||
$class = static::class;
|
||||
|
||||
return static::$reflectionCache[$class] ??= new ReflectionClass($class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the constants in the enum subclass
|
||||
*
|
||||
* @return array<static>
|
||||
*/
|
||||
public static function getConstants(): array
|
||||
{
|
||||
return static::getReflection()->getConstants();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the constants values in the enum subclass
|
||||
*
|
||||
* @return array<static>
|
||||
*/
|
||||
public static function getConstantValues(): array
|
||||
{
|
||||
return array_values(static::getReflection()->getConstants());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a message from the enum subclass
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMessage(int $messageIndex, string $defaultMessage = 'Unknown'): string
|
||||
{
|
||||
if (array_key_exists($messageIndex, self::$messages) === true) {
|
||||
return self::$messages[$messageIndex];
|
||||
}
|
||||
|
||||
return $defaultMessage;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
class HttpResponseCodes extends Enum
|
||||
{
|
||||
public const HTTP_CONTINUE = 100;
|
||||
public const HTTP_SWITCHING_PROTOCOLS = 101;
|
||||
public const HTTP_PROCESSING = 102;
|
||||
|
||||
public const HTTP_OK = 200;
|
||||
public const HTTP_CREATED = 201;
|
||||
public const HTTP_ACCEPTED = 202;
|
||||
public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
|
||||
public const HTTP_NO_CONTENT = 204;
|
||||
public const HTTP_RESET_CONTENT = 205;
|
||||
public const HTTP_PARTIAL_CONTENT = 206;
|
||||
public const HTTP_MULTI_STATUS = 207;
|
||||
|
||||
public const HTTP_ALREADY_REPORTED = 208;
|
||||
|
||||
public const HTTP_IM_USED = 226;
|
||||
|
||||
public const HTTP_MULTIPLE_CHOICES = 300;
|
||||
public const HTTP_MOVED_PERMANENTLY = 301;
|
||||
public const HTTP_FOUND = 302;
|
||||
public const HTTP_SEE_OTHER = 303;
|
||||
public const HTTP_NOT_MODIFIED = 304;
|
||||
public const HTTP_USE_PROXY = 305;
|
||||
public const HTTP_RESERVED = 306;
|
||||
public const HTTP_TEMPORARY_REDIRECT = 307;
|
||||
public const HTTP_PERMANENTLY_REDIRECT = 308;
|
||||
|
||||
public const HTTP_BAD_REQUEST = 400;
|
||||
public const HTTP_UNAUTHORIZED = 401;
|
||||
public const HTTP_PAYMENT_REQUIRED = 402;
|
||||
public const HTTP_FORBIDDEN = 403;
|
||||
public const HTTP_NOT_FOUND = 404;
|
||||
public const HTTP_METHOD_NOT_ALLOWED = 405;
|
||||
public const HTTP_NOT_ACCEPTABLE = 406;
|
||||
public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
|
||||
public const HTTP_REQUEST_TIMEOUT = 408;
|
||||
public const HTTP_CONFLICT = 409;
|
||||
public const HTTP_GONE = 410;
|
||||
public const HTTP_LENGTH_REQUIRED = 411;
|
||||
public const HTTP_PRECONDITION_FAILED = 412;
|
||||
public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
|
||||
public const HTTP_REQUEST_URI_TOO_LONG = 414;
|
||||
public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
|
||||
public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
|
||||
public const HTTP_EXPECTATION_FAILED = 417;
|
||||
public const HTTP_I_AM_A_TEAPOT = 418;
|
||||
|
||||
public const HTTP_MISDIRECTED_REQUEST = 421;
|
||||
public const HTTP_UNPROCESSABLE_ENTITY = 422;
|
||||
public const HTTP_LOCKED = 423;
|
||||
public const HTTP_FAILED_DEPENDENCY = 424;
|
||||
public const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425;
|
||||
public const HTTP_UPGRADE_REQUIRED = 426;
|
||||
public const HTTP_PRECONDITION_REQUIRED = 428;
|
||||
public const HTTP_TOO_MANY_REQUESTS = 429;
|
||||
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
|
||||
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
|
||||
|
||||
public const HTTP_INTERNAL_SERVER_ERROR = 500;
|
||||
public const HTTP_NOT_IMPLEMENTED = 501;
|
||||
public const HTTP_BAD_GATEWAY = 502;
|
||||
public const HTTP_SERVICE_UNAVAILABLE = 503;
|
||||
public const HTTP_GATEWAY_TIMEOUT = 504;
|
||||
public const HTTP_VERSION_NOT_SUPPORTED = 505;
|
||||
public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506;
|
||||
public const HTTP_INSUFFICIENT_STORAGE = 507;
|
||||
public const HTTP_LOOP_DETECTED = 508;
|
||||
public const HTTP_NOT_EXTENDED = 510;
|
||||
public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;
|
||||
|
||||
/**
|
||||
* HTTP Response Messages
|
||||
* @var string[]
|
||||
*/
|
||||
public static $statusTexts = [
|
||||
100 => 'Continue.',
|
||||
101 => 'Switching Protocols.',
|
||||
102 => 'Processing.',
|
||||
// RFC2518
|
||||
200 => 'OK.',
|
||||
201 => 'Created.',
|
||||
202 => 'Accepted.',
|
||||
203 => 'Non-Authoritative Information.',
|
||||
204 => 'No Content.',
|
||||
205 => 'Reset Content.',
|
||||
206 => 'Partial Content.',
|
||||
207 => 'Multi-Status.',
|
||||
// RFC4918
|
||||
208 => 'Already Reported.',
|
||||
// RFC5842
|
||||
226 => 'IM Used.',
|
||||
// RFC3229
|
||||
300 => 'Multiple Choices.',
|
||||
301 => 'Moved Permanently.',
|
||||
302 => 'Found.',
|
||||
303 => 'See Other.',
|
||||
304 => 'Not Modified.',
|
||||
305 => 'Use Proxy.',
|
||||
307 => 'Temporary Redirect.',
|
||||
308 => 'Permanent Redirect.',
|
||||
// RFC7238
|
||||
400 => 'Bad Request.',
|
||||
401 => 'Unauthorized.',
|
||||
402 => 'Payment Required.',
|
||||
403 => 'Forbidden.',
|
||||
404 => 'Not Found.',
|
||||
405 => 'Method Not Allowed.',
|
||||
406 => 'Not Acceptable.',
|
||||
407 => 'Proxy Authentication Required.',
|
||||
408 => 'Request Timeout.',
|
||||
409 => 'Conflict.',
|
||||
410 => 'Gone.',
|
||||
411 => 'Length Required.',
|
||||
412 => 'Precondition Failed.',
|
||||
413 => 'Payload Too Large.',
|
||||
414 => 'URI Too Long.',
|
||||
415 => 'Unsupported Media Type.',
|
||||
416 => 'Range Not Satisfiable.',
|
||||
417 => 'Expectation Failed.',
|
||||
418 => 'I\'m a teapot.',
|
||||
// RFC2324
|
||||
421 => 'Misdirected Request.',
|
||||
// RFC7540
|
||||
422 => 'Unprocessable Entity.',
|
||||
// RFC4918
|
||||
423 => 'Locked.',
|
||||
// RFC4918
|
||||
424 => 'Failed Dependency.',
|
||||
// RFC4918
|
||||
425 => 'Reserved for WebDAV advanced collections expired proposal.',
|
||||
// RFC2817
|
||||
426 => 'Upgrade Required.',
|
||||
// RFC2817
|
||||
428 => 'Precondition Required.',
|
||||
// RFC6585
|
||||
429 => 'Too Many Requests.',
|
||||
// RFC6585
|
||||
431 => 'Request Header Fields Too Large.',
|
||||
// RFC6585
|
||||
451 => 'Unavailable For Legal Reasons.',
|
||||
// RFC7725
|
||||
500 => 'Internal Server Error.',
|
||||
501 => 'Not Implemented.',
|
||||
502 => 'Bad Gateway.',
|
||||
503 => 'Service Unavailable.',
|
||||
504 => 'Gateway Timeout.',
|
||||
505 => 'HTTP Version Not Supported.',
|
||||
506 => 'Variant Also Negotiates.',
|
||||
// RFC2295
|
||||
507 => 'Insufficient Storage.',
|
||||
// RFC4918
|
||||
508 => 'Loop Detected.',
|
||||
// RFC5842
|
||||
510 => 'Not Extended.',
|
||||
// RFC2774
|
||||
511 => 'Network Authentication Required.',
|
||||
// RFC6585
|
||||
];
|
||||
}
|
||||
37
app/Exceptions/FileInvalidException.php
Normal file
37
app/Exceptions/FileInvalidException.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class FileInvalidException extends Exception
|
||||
{
|
||||
/**
|
||||
* The error code of the exception.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* The error message of the exception.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $message, int $code = 0)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
||||
37
app/Exceptions/FileTooLargeException.php
Normal file
37
app/Exceptions/FileTooLargeException.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class FileTooLargeException extends Exception
|
||||
{
|
||||
/**
|
||||
* The error code of the exception.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* The error message of the exception.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Create a new exception instance.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $message, int $code = 0)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Throwable;
|
||||
use PDOException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||
*/
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// $this->renderable(function (HttpException $e, $request) {
|
||||
// if ($request->is('api/*')) {
|
||||
// $message = $e->getMessage();
|
||||
// if ($message === '') {
|
||||
// $message = HttpResponseCodes::$statusTexts[$e->getStatusCode()];
|
||||
// }
|
||||
|
||||
// return response()->json([
|
||||
// 'message' => $message
|
||||
// ], $e->getStatusCode());
|
||||
// }
|
||||
// });
|
||||
|
||||
$this->renderable(function (NotFoundHttpException $e, $request) {
|
||||
if ($request->is('api/*') === true) {
|
||||
return response()->json([
|
||||
'message' => 'Resource not found'
|
||||
], 404);
|
||||
}
|
||||
});
|
||||
|
||||
$this->renderable(function (PDOException $e, $request) {
|
||||
if ($request->is('api/*') === true) {
|
||||
return response()->json([
|
||||
'message' => 'The server is currently unavailable'
|
||||
], 503);
|
||||
}
|
||||
});
|
||||
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Subscriber;
|
||||
|
||||
class SubscriptionFilter extends FilterAbstract
|
||||
{
|
||||
/**
|
||||
* The model class to filter
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $class = '\App\Models\Subscription';
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of attributes visible in the results
|
||||
*
|
||||
* @param array $attributes Attributes currently visible.
|
||||
* @param User|null $user Current logged in user or null.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function seeAttributes(array $attributes, mixed $user)
|
||||
{
|
||||
if ($user?->hasPermission('admin/users') !== true) {
|
||||
return ['id', 'email', 'confirmed_at'];
|
||||
}
|
||||
}
|
||||
}
|
||||
181
app/Helpers.php
Normal file
181
app/Helpers.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use DateTime;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
/**
|
||||
* Get the maximum upload size in bytes.
|
||||
*/
|
||||
public static function getMaxUploadSize(): int
|
||||
{
|
||||
return min(
|
||||
self::stringToBytes(ini_get('post_max_size')),
|
||||
self::stringToBytes(ini_get('upload_max_filesize'))
|
||||
);
|
||||
}
|
||||
public static function stringToBytes(string $val): int
|
||||
{
|
||||
if (empty($val)) {
|
||||
$val = 0;
|
||||
}
|
||||
$val = trim($val);
|
||||
$last = strtolower($val[strlen($val) - 1]);
|
||||
$val = floatval($val);
|
||||
switch ($last) {
|
||||
case 'g':
|
||||
$val *= (1024 * 1024 * 1024); //1073741824
|
||||
break;
|
||||
case 'm':
|
||||
$val *= (1024 * 1024); //1048576
|
||||
break;
|
||||
case 'k':
|
||||
$val *= 1024;
|
||||
break;
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
public static function bytesToString(int|float|string $bytes): string
|
||||
{
|
||||
if (!is_numeric($bytes)) {
|
||||
return '0 bytes';
|
||||
}
|
||||
|
||||
$units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
for ($i = 0; $bytes > 1024; $i++) {
|
||||
$bytes /= 1024;
|
||||
}
|
||||
return round($bytes, 2) . ' ' . $units[$i];
|
||||
}
|
||||
|
||||
public static function arrayToString(array $array, string $separator = ','): string
|
||||
{
|
||||
return implode($separator, array_map(function ($item) use ($separator) {
|
||||
if (str_contains($item, $separator)) {
|
||||
return '"' . str_replace('"', '\\"', $item) . '"';
|
||||
} else {
|
||||
return $item;
|
||||
}
|
||||
}, $array));
|
||||
}
|
||||
|
||||
public static function stringToArray(string $string, string $separator = ','): array
|
||||
{
|
||||
return array_map(function ($item) {
|
||||
// Remove quotes and unescape any escaped quotes within the string
|
||||
return str_replace('\\"', '"', trim($item, '"'));
|
||||
}, explode($separator, $string));
|
||||
}
|
||||
|
||||
public static function timestampNoSeconds(string $timestamp): string
|
||||
{
|
||||
if(empty($timestamp)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$datetime = new DateTime($timestamp);
|
||||
return $datetime->format('Y-m-d\TH:i');
|
||||
}
|
||||
|
||||
public static function isUnderAge(mixed $ages): bool
|
||||
{
|
||||
if(!is_string($ages)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
preg_match('/\d+/', $ages, $matches);
|
||||
if (empty($matches)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$firstNumber = $matches[0];
|
||||
return ($firstNumber <= 8);
|
||||
}
|
||||
|
||||
public static function createTimeDurationStr(string $startStr, string $endStr): array
|
||||
{
|
||||
try {
|
||||
$start = new DateTime($startStr);
|
||||
$end = new DateTime($endStr);
|
||||
|
||||
if ($start->format('Y-m-d') === $end->format('Y-m-d')) {
|
||||
return [
|
||||
$start->format('l j M Y'),
|
||||
$start->format('g:i a') . ' - ' . $end->format('g:i a')
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
$start->format('D j/m/Y') . ' - ' . $end->format('D j/m/Y')
|
||||
];
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
return ['Error parsing date'];
|
||||
}
|
||||
}
|
||||
|
||||
public static function matchesMimeType(string $mimeType, string|array $patterns): bool
|
||||
{
|
||||
if (is_string($patterns)) {
|
||||
$patterns = [$patterns];
|
||||
}
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
$pattern = str_replace('\*', '.*', preg_quote($pattern, '/'));
|
||||
$regex = '/^' . $pattern . '$/';
|
||||
if (preg_match($regex, $mimeType) === 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function findMatchingMimeTypeKey(string $mimeType, array $patterns): string|bool
|
||||
{
|
||||
$match = '';
|
||||
|
||||
foreach ($patterns as $key => $value) {
|
||||
$keys = explode(',', $key);
|
||||
foreach($keys as $key) {
|
||||
$pattern = str_replace('\*', '.*', preg_quote($key, '/'));
|
||||
$regex = '/^' . $pattern . '$/';
|
||||
if (preg_match($regex, $mimeType) === 1) {
|
||||
if($match !== $mimeType) {
|
||||
$match = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($match !== '') {
|
||||
return $match;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function cleanFileName(string $name): string
|
||||
{
|
||||
$name = strtolower($name);
|
||||
$name = mb_ereg_replace('/^\.+/', '', $name);
|
||||
$name = mb_ereg_replace("([\s_])", '-', $name);
|
||||
$name = mb_ereg_replace("([^\w\s\d\-_.])", '', $name);
|
||||
$name = mb_ereg_replace("([\.]{2,})", '', $name);
|
||||
$name = mb_ereg_replace("([\-]{2,})", '-', $name);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
public static function filenameToTitle(string $filename): string
|
||||
{
|
||||
$title = pathinfo($filename, PATHINFO_FILENAME);
|
||||
$title = str_replace(['-', '_', '.'], ' ', $title);
|
||||
$title = ucwords($title);
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* Array Helper Functions */
|
||||
|
||||
|
||||
/**
|
||||
* Remove an item from an array.
|
||||
*
|
||||
* @param array $arr The array to check.
|
||||
* @param string|array $item The item or items to remove.
|
||||
* @return array The filtered array.
|
||||
*/
|
||||
function arrayRemoveItem(array $arr, string|array $item): array
|
||||
{
|
||||
$filteredArr = $arr;
|
||||
|
||||
if (is_string($item) === true) {
|
||||
$item = [$item];
|
||||
}
|
||||
|
||||
foreach ($item as $str) {
|
||||
$filteredArr = array_filter($arr, function ($item) use ($str) {
|
||||
return $item !== $str;
|
||||
});
|
||||
}
|
||||
|
||||
return $filteredArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with specified the keys
|
||||
*
|
||||
* @param array $arr The array to filter.
|
||||
* @param string|array $keys The keys to keep.
|
||||
* @return array The filtered array.
|
||||
*/
|
||||
function arrayLimitKeys(array $arr, array $keys): array
|
||||
{
|
||||
return array_intersect_key($arr, array_flip($keys));
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
/* Temp File Helper Functions */
|
||||
|
||||
|
||||
/**
|
||||
* Generate a temporary file path.
|
||||
*
|
||||
* @return str The filtered array.
|
||||
*/
|
||||
function generateTempFilePath(): string
|
||||
{
|
||||
$temporaryDir = storage_path('app/tmp');
|
||||
if (is_dir($temporaryDir) === false) {
|
||||
mkdir($temporaryDir, 0777, true);
|
||||
}
|
||||
|
||||
return $temporaryDir . DIRECTORY_SEPARATOR . uniqid('upload_', true);
|
||||
}
|
||||
240
app/Http/Controllers/AccountController.php
Normal file
240
app/Http/Controllers/AccountController.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers;
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Mail\UserEmailUpdateRequest;
|
||||
use App\Models\User;
|
||||
use App\Providers\QRCodeProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(User $user)
|
||||
{
|
||||
return view('account', compact('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(string $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'firstname' => 'required',
|
||||
'surname' => 'required',
|
||||
'email' => ['required', 'email', 'unique:users,email,' . $user->id],
|
||||
'phone' => 'required',
|
||||
|
||||
'shipping_address' => 'required_with:shipping_city,shipping_postcode,shipping_country,shipping_state',
|
||||
'shipping_city' => 'required_with:shipping_address,shipping_postcode,shipping_country,shipping_state',
|
||||
'shipping_postcode' => 'required_with:shipping_address,shipping_city,shipping_country,shipping_state',
|
||||
'shipping_country' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_state',
|
||||
'shipping_state' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_country',
|
||||
|
||||
'billing_address' => 'required_with:billing_city,billing_postcode,billing_country,billing_state',
|
||||
'billing_city' => 'required_with:billing_address,billing_postcode,billing_country,billing_state',
|
||||
'billing_postcode' => 'required_with:billing_address,billing_city,billing_country,billing_state',
|
||||
'billing_country' => 'required_with:billing_address,billing_city,billing_postcode,billing_state',
|
||||
'billing_state' => 'required_with:billing_address,billing_city,billing_postcode,billing_country',
|
||||
], [
|
||||
'firstname.required' => __('validation.custom_messages.firstname_required'),
|
||||
'surname.required' => __('validation.custom_messages.surname_required'),
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid'),
|
||||
'phone.required' => __('validation.custom_messages.phone_required'),
|
||||
|
||||
'shipping_address.required' => __('validation.custom_messages.shipping_address_required'),
|
||||
'shipping_city.required' => __('validation.custom_messages.shipping_city_required'),
|
||||
'shipping_postcode.required' => __('validation.custom_messages.shipping_postcode_required'),
|
||||
'shipping_country.required' => __('validation.custom_messages.shipping_country_required'),
|
||||
'shipping_state.required' => __('validation.custom_messages.shipping_state_required'),
|
||||
|
||||
'billing_address.required' => __('validation.custom_messages.billing_address_required'),
|
||||
'billing_city.required' => __('validation.custom_messages.billing_city_required'),
|
||||
'billing_postcode.required' => __('validation.custom_messages.billing_postcode_required'),
|
||||
'billing_country.required' => __('validation.custom_messages.billing_country_required'),
|
||||
'billing_state.required' => __('validation.custom_messages.billing_state_required'),
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$userData = $request->all();
|
||||
|
||||
$newEmail = $userData['email'];
|
||||
unset($userData['email']);
|
||||
|
||||
if (strtolower($user->email) !== strtolower($newEmail)) {
|
||||
$user->tokens()->where('type', 'email-update')->delete();
|
||||
|
||||
$token = $user->tokens()->create([
|
||||
'type' => 'email-update',
|
||||
'data' => [
|
||||
'email' => $newEmail,
|
||||
],
|
||||
'expires_at' => now()->addMinutes(30),
|
||||
]);
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserEmailUpdateRequest($token->id, $user->email, $newEmail)))->onQueue('mail');
|
||||
}
|
||||
|
||||
$userData['subscribed'] = ($request->get('subscribed', false) === 'on');
|
||||
$user->update($userData);
|
||||
$user->save();
|
||||
|
||||
session()->flash('message', 'Your account details have been saved');
|
||||
session()->flash('message-title', 'Details updated');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
auth()->logout();
|
||||
|
||||
$user->delete();
|
||||
|
||||
session()->flash('message', 'Your account has been deleted');
|
||||
session()->flash('message-title', 'Account Deleted');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
public static function getTFAInstance()
|
||||
{
|
||||
$tfa = new TwoFactorAuth(new QRCodeProvider(), 'STEMMechanics', 6, 30, Algorithm::Sha512);
|
||||
$tfa->ensureCorrectTime();
|
||||
return $tfa;
|
||||
}
|
||||
|
||||
public function show_tfa()
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user->tfa_secret === null) {
|
||||
$tfa = self::getTFAInstance();
|
||||
$secret = $tfa->createSecret();
|
||||
|
||||
return response()->json([
|
||||
'secret' => $secret,
|
||||
]);
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
public function show_tfa_image(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user->tfa_secret === null && $request->has('secret')) {
|
||||
$tfa = self::getTFAInstance();
|
||||
|
||||
$qrCodeProvider = new QRCodeProvider();
|
||||
$qrCode = $qrCodeProvider->getQRCodeImage(
|
||||
$tfa->getQRText($user->email, $request->get('secret')),
|
||||
200
|
||||
);
|
||||
|
||||
return response()->stream(function () use ($qrCode) {
|
||||
echo $qrCode;
|
||||
}, 200, ['Content-Type' => $qrCodeProvider->getMimeType()]);
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
public function post_tfa(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->tfa_secret === null && $request->has('secret') && $request->has('code')) {
|
||||
$secret = $request->get('secret');
|
||||
$code = $request->get('code');
|
||||
|
||||
$tfa = self::getTFAInstance();
|
||||
|
||||
if ($tfa->verifyCode($secret, $code, 4)) {
|
||||
$user->tfa_secret = $secret;
|
||||
$user->save();
|
||||
|
||||
$codes = $user->generateBackupCodes();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'codes' => $codes
|
||||
]);
|
||||
} else {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy_tfa(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->tfa_secret !== null) {
|
||||
$user->tfa_secret = null;
|
||||
$user->save();
|
||||
|
||||
$user->backupCodes()->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
]);
|
||||
} else {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
public function post_tfa_reset_backup_codes(Request $request)
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if ($user->tfa_secret !== null) {
|
||||
$codes = $user->generateBackupCodes();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'codes' => $codes
|
||||
]);
|
||||
} else {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\AnalyticsConductor;
|
||||
use App\Conductors\Conductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\AnalyticsRequest;
|
||||
use App\Models\Media;
|
||||
use App\Models\Analytics;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\MassAssignmentException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AnalyticsController extends ApiController
|
||||
{
|
||||
/**
|
||||
* AnalyticsController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only([
|
||||
'index',
|
||||
'update',
|
||||
'delete'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
||||
$searchFields = ['attribute', 'type', 'useragent', 'ip'];
|
||||
|
||||
$queryRequest = new Request();
|
||||
$queryRequest->merge($request->only($searchFields));
|
||||
foreach ($searchFields as $field) {
|
||||
unset($request[$field]);
|
||||
}
|
||||
|
||||
$query = Analytics::query()
|
||||
->selectRaw('session,
|
||||
MIN(created_at) as created_at,
|
||||
TIMESTAMPDIFF(MINUTE, MIN(created_at), MAX(created_at)) as duration');
|
||||
$query = Conductor::filterQuery($query, $queryRequest);
|
||||
|
||||
list($collection, $total) = AnalyticsConductor::collection($request, $query
|
||||
->groupBy('session')
|
||||
->get());
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Analytics $analytics The analyics model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, int $session)
|
||||
{
|
||||
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
||||
list($collection, $total) = AnalyticsConductor::collection($request, Analytics::query()
|
||||
->where('session', $session)
|
||||
->get());
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\AnalyticsRequest $request The user request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(AnalyticsRequest $request)
|
||||
{
|
||||
if (AnalyticsConductor::creatable() === true) {
|
||||
$analytics = null;
|
||||
$user = $request->user();
|
||||
|
||||
$data = [
|
||||
'type' => $request->input('type'),
|
||||
'attribute' => $request->input('attribute', ''),
|
||||
'useragent' => $request->userAgent(),
|
||||
'ip' => $request->ip()
|
||||
];
|
||||
|
||||
if ($user !== null && $user->hasPermission('admin/analytics') === true && $request->has('session') === true) {
|
||||
$data['session'] = $request->input('session');
|
||||
$analytics = Analytics::create($data);
|
||||
} else {
|
||||
$analytics = Analytics::createWithSession($data);
|
||||
}
|
||||
|
||||
return $this->respondAsResource(
|
||||
AnalyticsConductor::model($request, $analytics),
|
||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||
);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}//end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\AnalyticsRequest $request The analytics update request.
|
||||
* @param \App\Models\Analytics $analytics The specified analytics.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(AnalyticsRequest $request, Analytics $analytics)
|
||||
{
|
||||
if (AnalyticsConductor::updatable($analytics) === true) {
|
||||
$analytics->update($request->all());
|
||||
return $this->respondAsResource(AnalyticsConductor::model($request, $analytics));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Analytics $analytics The specified analytics.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Analytics $analytics)
|
||||
{
|
||||
if (AnalyticsConductor::destroyable($analytics) === true) {
|
||||
$analytics->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Resource name
|
||||
* @var string
|
||||
*/
|
||||
protected $resourceName = '';
|
||||
|
||||
|
||||
/**
|
||||
* Return generic json response with the given data.
|
||||
*
|
||||
* @param array $data Response data.
|
||||
* @param integer $respondCode Response status code.
|
||||
* @param array $headers Response headers.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondJson(array $data, int $respondCode = HttpResponseCodes::HTTP_OK, array $headers = [])
|
||||
{
|
||||
return response()->json($data, $respondCode, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return forbidden message
|
||||
*
|
||||
* @param string $message Response message.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondForbidden(string $message = 'You do not have permission to access the resource.')
|
||||
{
|
||||
return response()->json(['message' => $message], HttpResponseCodes::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return forbidden message
|
||||
*
|
||||
* @param string $message Response message.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondNotFound(string $message = 'The resource was not found.')
|
||||
{
|
||||
return response()->json(['message' => $message], HttpResponseCodes::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return too large message
|
||||
*
|
||||
* @param string $message Response message.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondTooLarge(string $message = 'The request entity is too large.')
|
||||
{
|
||||
return response()->json(['message' => $message], HttpResponseCodes::HTTP_REQUEST_ENTITY_TOO_LARGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return no content
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondNoContent()
|
||||
{
|
||||
return response()->json([], HttpResponseCodes::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return created
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondCreated()
|
||||
{
|
||||
return response()->json([], HttpResponseCodes::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return accepted
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondAccepted()
|
||||
{
|
||||
return response()->json([], HttpResponseCodes::HTTP_ACCEPTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return single error message
|
||||
*
|
||||
* @param string $message Error message.
|
||||
* @param integer $responseCode Resource code.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondError(string $message, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY)
|
||||
{
|
||||
return response()->json([
|
||||
'message' => $message
|
||||
], $responseCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return formatted errors
|
||||
*
|
||||
* @param array $errors Error messages.
|
||||
* @param integer $responseCode Resource code.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function respondWithErrors(array $errors, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY)
|
||||
{
|
||||
$keys = array_keys($errors);
|
||||
$error = $errors[$keys[0]];
|
||||
|
||||
if (count($keys) > 1) {
|
||||
$additional_errors = (count($keys) - 1);
|
||||
$error .= sprintf(' (and %d more %s', $additional_errors, Str::plural('error', $additional_errors));
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => $error,
|
||||
'errors' => $errors
|
||||
], $responseCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return resource data
|
||||
*
|
||||
* @param array|Model|Collection $data Resource data.
|
||||
* @param array $options Respond options.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
protected function respondAsResource(
|
||||
mixed $data,
|
||||
array $options = [],
|
||||
$validationFn = null
|
||||
) {
|
||||
$isCollection = $options['isCollection'] ?? false;
|
||||
$appendData = $options['appendData'] ?? null;
|
||||
$resourceName = $options['resourceName'] ?? null;
|
||||
$respondCode = ($options['respondCode'] ?? HttpResponseCodes::HTTP_OK);
|
||||
|
||||
if ($data === null || ($data instanceof Collection && $data->count() === 0)) {
|
||||
$validationData = [];
|
||||
if (array_key_exists('appendData', $options) === true) {
|
||||
$validationData = $options['appendData'];
|
||||
}
|
||||
|
||||
if ($validationFn === null || $validationFn($validationData) === true) {
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($resourceName) === true || empty($resourceName) === true) {
|
||||
$resourceName = $this->resourceName;
|
||||
}
|
||||
|
||||
if (is_null($resourceName) === true || empty($resourceName) === true) {
|
||||
$resourceName = get_class($this);
|
||||
$resourceName = substr($resourceName, (strrpos($resourceName, '\\') + 1));
|
||||
$resourceName = substr($resourceName, 0, strpos($resourceName, 'Controller'));
|
||||
$resourceName = strtolower($resourceName);
|
||||
}
|
||||
|
||||
$dataArray = [];
|
||||
if ($data instanceof Collection) {
|
||||
$dataArray = $data->toArray();
|
||||
} elseif (is_array($data) === true) {
|
||||
$dataArray = $data;
|
||||
} elseif ($data instanceof Model) {
|
||||
$is_multiple = false;
|
||||
$dataArray = $data->toArray();
|
||||
}
|
||||
|
||||
$resource = [];
|
||||
if ($isCollection === true) {
|
||||
$resource = [Str::plural($resourceName) => $dataArray];
|
||||
} else {
|
||||
$resource = [Str::singular($resourceName) => $dataArray];
|
||||
}
|
||||
|
||||
if ($appendData !== null) {
|
||||
$resource += $appendData;
|
||||
}
|
||||
|
||||
return response()->json($resource, $respondCode);
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Conductors\ArticleConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\ArticleRequest;
|
||||
use App\Models\Media;
|
||||
use App\Models\Article;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\MassAssignmentException;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ArticleController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only([
|
||||
'store',
|
||||
'update',
|
||||
'delete'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = ArticleConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Article $article The article model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Article $article)
|
||||
{
|
||||
if (ArticleConductor::viewable($article) === true) {
|
||||
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\ArticleRequest $request The user request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(ArticleRequest $request)
|
||||
{
|
||||
if (ArticleConductor::creatable() === true) {
|
||||
$article = Article::create($request->all());
|
||||
return $this->respondAsResource(
|
||||
ArticleConductor::model($request, $article),
|
||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||
);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\ArticleRequest $request The article update request.
|
||||
* @param \App\Models\Article $article The specified article.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(ArticleRequest $request, Article $article)
|
||||
{
|
||||
if (ArticleConductor::updatable($article) === true) {
|
||||
$article->update($request->all());
|
||||
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Article $article The specified article.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Article $article)
|
||||
{
|
||||
if (ArticleConductor::destroyable($article) === true) {
|
||||
$article->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Article $article The article model.
|
||||
* @return JsonResponse Returns the article attachments.
|
||||
* @throws InvalidFormatException
|
||||
* @throws BindingResolutionException
|
||||
* @throws InvalidCastException
|
||||
*/
|
||||
public function getAttachments(Request $request, Article $article)
|
||||
{
|
||||
if (ArticleConductor::viewable($article) === true) {
|
||||
$medium = $article->attachments->map(function ($attachment) {
|
||||
return $attachment->media;
|
||||
});
|
||||
|
||||
return $this->respondAsResource(MediaConductor::collection($request, $medium), ['isCollection' => true, 'resourceName' => 'attachment']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an attachment related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Article $article The article model.
|
||||
* @return JsonResponse The response.
|
||||
* @throws BindingResolutionException
|
||||
* @throws MassAssignmentException
|
||||
*/
|
||||
public function storeAttachment(Request $request, Article $article)
|
||||
{
|
||||
if (ArticleConductor::updatable($article) === true) {
|
||||
if ($request->has("medium") && Media::find($request->medium)) {
|
||||
$article->attachments()->create(['media_id' => $request->medium]);
|
||||
return $this->respondCreated();
|
||||
}
|
||||
|
||||
return $this->respondWithErrors(['media' => 'The media ID was not found']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/replace attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Article $article The related model.
|
||||
* @return JsonResponse
|
||||
* @throws BindingResolutionException
|
||||
* @throws MassAssignmentException
|
||||
*/
|
||||
public function updateAttachments(Request $request, Article $article)
|
||||
{
|
||||
if (ArticleConductor::updatable($article) === true) {
|
||||
$mediaIds = $request->attachments;
|
||||
if (is_array($mediaIds) === false) {
|
||||
$mediaIds = explode(',', $request->attachments);
|
||||
}
|
||||
|
||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
||||
$attachments = $article->attachments;
|
||||
|
||||
// Delete attachments that are not in $mediaIds
|
||||
foreach ($attachments as $attachment) {
|
||||
if (!in_array($attachment->media_id, $mediaIds)) {
|
||||
$attachment->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create new attachments for media IDs that are not already in $article->attachments()
|
||||
foreach ($mediaIds as $mediaId) {
|
||||
$found = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id == $mediaId) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$article->attachments()->create(['media_id' => $mediaId]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific related attachment.
|
||||
* @param Request $request The user request.
|
||||
* @param Article $article The model.
|
||||
* @param Media $medium The attachment medium.
|
||||
* @return JsonResponse
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function deleteAttachment(Request $request, Article $article, Media $medium)
|
||||
{
|
||||
if (ArticleConductor::updatable($article) === true) {
|
||||
$attachments = $article->attachments;
|
||||
$deleted = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id === $medium->id) {
|
||||
$attachment->delete();
|
||||
$deleted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted) {
|
||||
// Attachment was deleted successfully
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
// Attachment with matching media ID was not found
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Attachment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AttachmentController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->except(['store', 'destroyByEmail']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\AuthLoginRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class AuthController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Resource name
|
||||
* @var string
|
||||
*/
|
||||
protected $resourceName = 'user';
|
||||
|
||||
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// $this->middleware('auth:sanctum')
|
||||
// ->only(['me']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current User details
|
||||
*
|
||||
* @param Request $request Current request data.
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function me(Request $request)
|
||||
{
|
||||
$user = $request->user()->makeVisible(['permissions']);
|
||||
return $this->respondAsResource($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login user with supplied creditials
|
||||
*
|
||||
* @param App\Http\Controllers\Api\AuthLoginRequest $request Created request data.
|
||||
* @return JsonResponse|void
|
||||
*/
|
||||
public function login(AuthLoginRequest $request)
|
||||
{
|
||||
$user = User::where('email', '=', $request->input('email'))->first();
|
||||
|
||||
if ($user !== null && strlen($user->password) > 0 && Hash::check($request->input('password'), $user->password) === true) {
|
||||
if ($user->email_verified_at === null) {
|
||||
return $this->respondWithErrors([
|
||||
'email' => 'Email address has not been verified.'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($user->disabled === true) {
|
||||
return $this->respondWithErrors([
|
||||
'email' => 'Account has been disabled.'
|
||||
]);
|
||||
}
|
||||
|
||||
$token = $user->createToken('user_token')->plainTextToken;
|
||||
|
||||
$user->logins()->create([
|
||||
'token' => $token,
|
||||
'login' => now(),
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent()
|
||||
]);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$user->makeVisible(['permissions']),
|
||||
['appendData' => ['token' => $token]]
|
||||
);
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors([
|
||||
'email' => 'Invalid email or password',
|
||||
'password' => 'Invalid email or password',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout current user
|
||||
*
|
||||
* @param Request $request Current request data.
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$user->logins()->where('token', $user->currentAccessToken())->update(['logout' => now()]);
|
||||
$user->currentAccessToken()->delete();
|
||||
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Requests\ContactSendRequest;
|
||||
use App\Jobs\SendEmailJob;
|
||||
use App\Mail\Contact;
|
||||
|
||||
class ContactController extends ApiController
|
||||
{
|
||||
/**
|
||||
* Send the request to the site admin by email
|
||||
*
|
||||
* @param \App\Http\Requests\User\ContactSendRequest $request Request data.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function send(ContactSendRequest $request)
|
||||
{
|
||||
dispatch((new SendEmailJob(
|
||||
config('contact.contact_address'),
|
||||
new Contact(
|
||||
$request->input('name'),
|
||||
$request->input('email'),
|
||||
$request->input('content')
|
||||
)
|
||||
)))->onQueue('mail');
|
||||
|
||||
return $this->respondCreated();
|
||||
}
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Models\Event;
|
||||
use App\Conductors\EventConductor;
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Conductors\UserConductor;
|
||||
use App\Http\Requests\EventRequest;
|
||||
use App\Models\Media;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only(['store','update','destroy', 'userAdd', 'userUpdate', 'userDelete']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = EventConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::viewable($event) === true) {
|
||||
return $this->respondAsResource(EventConductor::model($request, $event));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\EventRequest $request The request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(EventRequest $request)
|
||||
{
|
||||
if (EventConductor::creatable() === true) {
|
||||
$event = Event::create($request->all());
|
||||
return $this->respondAsResource(
|
||||
EventConductor::model($request, $event),
|
||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||
);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\EventRequest $request The endpoint request.
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(EventRequest $request, Event $event)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
$event->update($request->all());
|
||||
return $this->respondAsResource(EventConductor::model($request, $event));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Event $event)
|
||||
{
|
||||
if (EventConductor::destroyable($event) === true) {
|
||||
$event->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Event $event The event model.
|
||||
* @return JsonResponse Returns the event attachments.
|
||||
*/
|
||||
public function getAttachments(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::viewable($event) === true) {
|
||||
$medium = $event->attachments->map(function ($attachment) {
|
||||
return $attachment->media;
|
||||
});
|
||||
|
||||
return $this->respondAsResource(MediaConductor::collection($request, $medium), ['isCollection' => true, 'resourceName' => 'attachment']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an attachment related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Event $event The event model.
|
||||
* @return JsonResponse The response.
|
||||
*/
|
||||
public function storeAttachment(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
if ($request->has("medium") === true && Media::find($request->medium) !== null) {
|
||||
$event->attachments()->create(['media_id' => $request->medium]);
|
||||
return $this->respondCreated();
|
||||
}
|
||||
|
||||
return $this->respondWithErrors(['media' => 'The media ID was not found']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/replace attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Event $event The related model.
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function updateAttachments(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
$mediaIds = $request->attachments;
|
||||
if (is_array($mediaIds) === false) {
|
||||
$mediaIds = explode(',', $request->attachments);
|
||||
}
|
||||
|
||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
||||
$attachments = $event->attachments;
|
||||
|
||||
// Delete attachments that are not in $mediaIds
|
||||
foreach ($attachments as $attachment) {
|
||||
if (in_array($attachment->media_id, $mediaIds) === false) {
|
||||
$attachment->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create new attachments for media IDs that are not already in $article->attachments()
|
||||
foreach ($mediaIds as $mediaId) {
|
||||
$found = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id === $mediaId) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found === false) {
|
||||
$event->attachments()->create(['media_id' => $mediaId]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific related attachment.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Event $event The model.
|
||||
* @param Media $medium The attachment medium.
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function deleteAttachment(Request $request, Event $event, Media $medium)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
$attachments = $event->attachments;
|
||||
$deleted = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id === $medium->id) {
|
||||
$attachment->delete();
|
||||
$deleted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted === true) {
|
||||
// Attachment was deleted successfully
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
// Attachment with matching media ID was not found
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
public function userList(Request $request, Event $event)
|
||||
{
|
||||
$authUser = $request->user();
|
||||
$eventUsers = $event->users;
|
||||
|
||||
if ($authUser !== null) {
|
||||
$isAdmin = $authUser->hasPermission('admin/events');
|
||||
$isEventUser = $eventUsers->contains($authUser->id);
|
||||
|
||||
if ($isAdmin === true || $isEventUser === true) {
|
||||
if ($isAdmin === false) {
|
||||
$eventUsers = $eventUsers->filter(function ($user) use ($authUser) {
|
||||
return $user->id === $authUser->id;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->respondAsResource(UserConductor::collection($request, $eventUsers), ['isCollection' => true, 'resourceName' => 'users']);
|
||||
}
|
||||
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
public function userAdd(Request $request, Event $event)
|
||||
{
|
||||
$authUser = $request->user();
|
||||
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
||||
if ($request->has("users") === true) {
|
||||
$eventUsers = $event->users()->pluck('user_id')->toArray(); // Get the current users in the event
|
||||
$requestedUsers = $request->input("users"); // Get the requested users
|
||||
|
||||
$usersToAdd = array_diff($requestedUsers, $eventUsers); // Users to add
|
||||
$usersToRemove = array_diff($eventUsers, $requestedUsers); // Users to remove
|
||||
|
||||
// Add missing users
|
||||
foreach ($usersToAdd as $userToAdd) {
|
||||
if (User::find($userToAdd) !== null) {
|
||||
$event->users()->attach($userToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove extra users
|
||||
foreach ($usersToRemove as $userToRemove) {
|
||||
$event->users()->detach($userToRemove);
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors(['users' => 'The user list was not found']);
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
public function userUpdate(Request $request, Event $event)
|
||||
{
|
||||
// only admin/events permitted
|
||||
}
|
||||
|
||||
public function userDelete(Request $request, Event $event, User $user)
|
||||
{
|
||||
$authUser = $request->user();
|
||||
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
||||
$eventUsers = $event->users;
|
||||
if ($eventUsers->find($user->id) !== null) {
|
||||
$eventUsers->detach($user->id);
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LogController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only(['show']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param Request $request The log request.
|
||||
* @param string $name The log name.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, string $name)
|
||||
{
|
||||
if ($request->user()?->hasPermission('logs/' . $name) === true) {
|
||||
switch (strtolower($name)) {
|
||||
case 'discord':
|
||||
$data = [];
|
||||
|
||||
$log = $request->get('log');
|
||||
if ($log === null) {
|
||||
$log = ['output', 'error'];
|
||||
} else {
|
||||
$log = explode(',', strtolower($log));
|
||||
}
|
||||
|
||||
$lines = intval($request->get('lines', 50));
|
||||
if ($lines > 100) {
|
||||
$lines = 100;
|
||||
} elseif ($lines < 0) {
|
||||
$lines = 1;
|
||||
}
|
||||
|
||||
$before = $request->get('before');
|
||||
if ($before !== null) {
|
||||
$before = preg_split("/([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/", $before, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
if (count($before) !== 6) {
|
||||
$before = null;
|
||||
}
|
||||
}
|
||||
|
||||
$after = $request->get('after');
|
||||
if ($after !== null) {
|
||||
$after = preg_split("/([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/", $after, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
if (count($after) !== 6) {
|
||||
$after = null;
|
||||
}
|
||||
}
|
||||
|
||||
$logFiles = [
|
||||
[
|
||||
'name' => 'output',
|
||||
'path' => '/home/discordbot/.pm2/logs/stemmech-discordbot-out-0.log'
|
||||
],[
|
||||
'name' => 'error',
|
||||
'path' => '/home/discordbot/.pm2/logs/stemmech-discordbot-error-0.log'
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($logFiles as $logFile) {
|
||||
if (in_array($logFile['name'], $log) === true) {
|
||||
$logContent = '';
|
||||
|
||||
if (file_exists($logFile['path']) === true) {
|
||||
$logContent = file_get_contents($logFile['path']);
|
||||
}
|
||||
|
||||
$logArray = preg_split("/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}: (?:(?!\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}: )[\s\S])*)/", $logContent, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
|
||||
$logContent = '';
|
||||
$logLineCount = 0;
|
||||
$logLineSkip = false;
|
||||
foreach (array_reverse($logArray) as $logLine) {
|
||||
$lineDate = preg_split("/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}): /", $logLine, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
if (count($lineDate) >= 6) {
|
||||
$logLineSkip = false;
|
||||
|
||||
// Is line before
|
||||
if ($before !== null && ($lineDate[0] > $before[0] || $lineDate[1] > $before[1] || $lineDate[2] > $before[2] || $lineDate[3] > $before[3] || $lineDate[4] > $before[4] || $lineDate[5] > $before[5])) {
|
||||
$logLineSkip = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is line after
|
||||
if ($after !== null && ($after[0] > $lineDate[0] || $after[1] > $lineDate[1] || $after[2] > $lineDate[2] || $after[3] > $lineDate[3] || $after[4] > $lineDate[4] || $after[5] > $lineDate[5])) {
|
||||
$logLineSkip = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$logLineCount += 1;
|
||||
}
|
||||
|
||||
if ($logLineCount > $lines) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($logLineSkip === false) {
|
||||
$logContent .= $logLine;
|
||||
}
|
||||
}//end foreach
|
||||
|
||||
$data[$logFile['name']] = $logContent;
|
||||
}//end if
|
||||
}//end foreach
|
||||
|
||||
return $this->respondJson([
|
||||
'log' => $data
|
||||
]);
|
||||
}//end switch
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\MediaRequest;
|
||||
use App\Models\Media;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
|
||||
class MediaController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only(['store','update','destroy']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = MediaConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
],
|
||||
function ($options) {
|
||||
return $options['total'] === 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Media $medium The request media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Media $medium)
|
||||
{
|
||||
if (MediaConductor::viewable($medium) === true) {
|
||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new media resource
|
||||
*
|
||||
* @param \App\Http\Requests\MediaRequest $request The uploaded media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(MediaRequest $request)
|
||||
{
|
||||
if (MediaConductor::creatable() === true) {
|
||||
$file = $request->file('file');
|
||||
if ($file === null) {
|
||||
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
||||
}
|
||||
|
||||
if ($file->isValid() !== true) {
|
||||
switch ($file->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return $this->respondTooLarge();
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return $this->respondWithErrors(['file' => 'The file upload was interrupted.']);
|
||||
default:
|
||||
return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
|
||||
try {
|
||||
$media = Media::createFromUploadedFile($request, $file);
|
||||
} catch (\Exception $e) {
|
||||
if ($e->getCode() === Media::FILE_SIZE_EXCEEDED_ERROR) {
|
||||
return $this->respondTooLarge();
|
||||
} else {
|
||||
return $this->respondWithErrors(['file' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondAsResource(
|
||||
MediaConductor::model($request, $media),
|
||||
['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||
);
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the media resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\MediaRequest $request The update request.
|
||||
* @param \App\Models\Media $medium The specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(MediaRequest $request, Media $medium)
|
||||
{
|
||||
if (MediaConductor::updatable($medium) === true) {
|
||||
$file = $request->file('file');
|
||||
if ($file !== null) {
|
||||
if ($file->isValid() !== true) {
|
||||
switch ($file->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return $this->respondTooLarge();
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return $this->respondWithErrors(['file' => 'The file upload was interrupted.']);
|
||||
default:
|
||||
return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
}
|
||||
|
||||
$medium->update($request->all());
|
||||
|
||||
if ($file !== null) {
|
||||
try {
|
||||
$medium->updateWithUploadedFile($file);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respondWithErrors(
|
||||
['file' => $e->getMessage()],
|
||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Media $medium Specified media file.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Media $medium)
|
||||
{
|
||||
if (MediaConductor::destroyable($medium) === true) {
|
||||
$medium->delete();
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Media $medium Specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download(Request $request, Media $medium)
|
||||
{
|
||||
$respondJson = in_array('application/json', explode(',', $request->header('Accept', 'application/json')));
|
||||
|
||||
$headers = [];
|
||||
$path = $medium->path();
|
||||
|
||||
/* File exists */
|
||||
if (file_exists($path) === false) {
|
||||
if ($respondJson === false) {
|
||||
return redirect('/not-found');
|
||||
} else {
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
$updated_at = Carbon::parse(filemtime($path));
|
||||
|
||||
$headerPragma = 'no-cache';
|
||||
$headerCacheControl = 'max-age=0, must-revalidate';
|
||||
$headerExpires = $updated_at->toRfc2822String();
|
||||
|
||||
if (empty($medium->permission) === true) {
|
||||
if ($request->user() === null && $request->has('token') === true) {
|
||||
$accessToken = PersonalAccessToken::findToken(urldecode($request->input('token')));
|
||||
|
||||
if (
|
||||
$accessToken !== null && (config('sanctum.expiration') === null ||
|
||||
$accessToken->created_at->lte(now()->subMinutes(config('sanctum.expiration'))) === false)
|
||||
) {
|
||||
$user = $accessToken->tokenable;
|
||||
}
|
||||
}
|
||||
if ($request->user() === null || $user->hasPermission($medium->permission) === false) {
|
||||
if ($respondJson === false) {
|
||||
return redirect('/login?redirect=' . $request->path());
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$headerPragma = 'public';
|
||||
$headerExpires = $updated_at->addMonth()->toRfc2822String();
|
||||
}//end if
|
||||
|
||||
// deepcode ignore InsecureHash: Browsers expect Etag to be a md5 hash
|
||||
$headerEtag = md5($updated_at->format('U'));
|
||||
$headerLastModified = $updated_at->toRfc2822String();
|
||||
|
||||
$headers = [
|
||||
'Cache-Control' => $headerCacheControl,
|
||||
'Content-Disposition' => sprintf('inline; filename="%s"', basename($path)),
|
||||
'Etag' => $headerEtag,
|
||||
'Expires' => $headerExpires,
|
||||
'Last-Modified' => $headerLastModified,
|
||||
'Pragma' => $headerPragma,
|
||||
];
|
||||
|
||||
$server = request()->server;
|
||||
|
||||
$requestModifiedSince = $server->has('HTTP_IF_MODIFIED_SINCE') &&
|
||||
$server->get('HTTP_IF_MODIFIED_SINCE') === $headerLastModified;
|
||||
|
||||
$requestNoneMatch = $server->has('HTTP_IF_NONE_MATCH') &&
|
||||
$server->get('HTTP_IF_NONE_MATCH') === $headerEtag;
|
||||
|
||||
if ($requestModifiedSince === true || $requestNoneMatch === true) {
|
||||
return response()->make('', 304, $headers);
|
||||
}
|
||||
|
||||
return response()->file($path, $headers);
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use thiagoalessio\TesseractOCR\TesseractOCR;
|
||||
use FFMpeg;
|
||||
use App\Enum\CurlErrorCodes;
|
||||
|
||||
class OCRController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// $this->middleware('auth:sanctum')
|
||||
// ->only(['show']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param Request $request The log request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
// if ($request->user()?->hasPermission('logs/' . $name) === true) {
|
||||
$url = $request->get('url');
|
||||
if ($url !== null) {
|
||||
$data = ['ocr' => []];
|
||||
|
||||
$filters = $request->get('filters', ['tesseract']);
|
||||
if (is_array($filters) === false) {
|
||||
$filters = explode(',', $filters);
|
||||
}
|
||||
|
||||
$tesseractOEM = $request->get('tesseract.oem');
|
||||
$tesseractDigits = $request->get('tesseract.digits');
|
||||
$tesseractAllowlist = $request->get('tesseract.allowlist');
|
||||
|
||||
// Download URL
|
||||
$urlDownloadFilePath = tempnam(sys_get_temp_dir(), 'download');
|
||||
$maxDownloadSize = (1024 * 1024); // 1MB
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
// We need progress updates to break the connection mid-way
|
||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // more progress info
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function (
|
||||
$downloadSize,
|
||||
$downloaded,
|
||||
$uploadSize,
|
||||
$uploaded
|
||||
) use ($maxDownloadSize) {
|
||||
return ($downloaded > $maxDownloadSize) ? 1 : 0;
|
||||
});
|
||||
|
||||
$curlResult = curl_exec($ch);
|
||||
$curlError = curl_errno($ch);
|
||||
$curlSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
|
||||
curl_close($ch);
|
||||
if ($curlError !== 0) {
|
||||
$error = 'File size is larger then allowed';
|
||||
if ($curlError !== CurlErrorCodes::CURLE_ABORTED_BY_CALLBACK) {
|
||||
$error = CurlErrorCodes::getMessage($curlError);
|
||||
}
|
||||
|
||||
return $this->respondWithErrors(['url' => $error]);
|
||||
}
|
||||
|
||||
// Save url file
|
||||
file_put_contents($urlDownloadFilePath, $curlResult);
|
||||
$urlDownloadFilePathBase = preg_replace('/\\.[^.\\s]{3,4}$/', '', $urlDownloadFilePath);
|
||||
|
||||
// tesseract (overall)
|
||||
$ocr = null;
|
||||
foreach ($filters as $filterItem) {
|
||||
if (str_starts_with($filterItem, 'tesseract') === true) {
|
||||
$ocr = new TesseractOCR();
|
||||
$ocr->image($urlDownloadFilePath);
|
||||
if ($tesseractOEM !== null) {
|
||||
$ocr->oem($tesseractOEM);
|
||||
}
|
||||
if ($tesseractDigits !== null) {
|
||||
$ocr->digits();
|
||||
}
|
||||
if ($tesseractAllowlist !== null) {
|
||||
$ocr->allowlist($tesseractAllowlist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Image Filter Function
|
||||
$tesseractImageFilterFunc = function ($filter, $options = null) use ($curlResult, $curlSize, $ocr) {
|
||||
$result = '';
|
||||
$img = imagecreatefromstring($curlResult);
|
||||
if ($img !== false && (($options !== null && imagefilter($img, $filter, $options) === true) || ($options === null && imagefilter($img, $filter) === true))) {
|
||||
ob_start();
|
||||
imagepng($img);
|
||||
$imgData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$imgDataSize = strlen($imgData);
|
||||
|
||||
$ocr->imageData($imgData, $imgDataSize);
|
||||
imagedestroy($img);
|
||||
|
||||
$result = $ocr->run(500);
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
// Image Scale Function
|
||||
$tesseractImageScaleFunc = function ($scaleFunc) use ($curlResult, $ocr) {
|
||||
$result = '';
|
||||
$srcImage = imagecreatefromstring($curlResult);
|
||||
$srcWidth = imagesx($srcImage);
|
||||
$srcHeight = imagesy($srcImage);
|
||||
|
||||
$dstWidth = $scaleFunc($srcWidth);
|
||||
$dstHeight = $scaleFunc($srcHeight);
|
||||
$dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
|
||||
|
||||
imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
|
||||
|
||||
ob_start();
|
||||
imagepng($dstImage);
|
||||
$imgData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$imgDataSize = strlen($imgData);
|
||||
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($dstImage);
|
||||
|
||||
$ocr->imageData($imgData, $imgDataSize);
|
||||
$result = $ocr->run(500);
|
||||
return $result;
|
||||
};
|
||||
|
||||
// filter: tesseract
|
||||
if (in_array('tesseract', $filters) === true) {
|
||||
$data['ocr']['tesseract'] = $ocr->run(500);
|
||||
}
|
||||
|
||||
// filter: tesseract.grayscale
|
||||
if (in_array('tesseract.grayscale', $filters) === true) {
|
||||
$data['ocr']['tesseract.grayscale'] = $tesseractImageFilterFunc(IMG_FILTER_GRAYSCALE);
|
||||
}
|
||||
|
||||
// filter: tesseract.double_scale
|
||||
if (in_array('tesseract.double_scale', $filters) === true) {
|
||||
$data['ocr']['tesseract.double_scale'] = $tesseractImageScaleFunc(function ($size) {
|
||||
return $size * 2;
|
||||
});
|
||||
}
|
||||
|
||||
// filter: tesseract.half_scale
|
||||
if (in_array('tesseract.half_scale', $filters) === true) {
|
||||
$data['ocr']['tesseract.half_scale'] = $tesseractImageScaleFunc(function ($size) {
|
||||
return $size / 2;
|
||||
});
|
||||
}
|
||||
|
||||
// filter: tesseract.edgedetect
|
||||
if (in_array('tesseract.edgedetect', $filters) === true) {
|
||||
$data['ocr']['tesseract.edgedetect'] = $tesseractImageFilterFunc(IMG_FILTER_EDGEDETECT);
|
||||
}
|
||||
|
||||
// filter: tesseract.mean_removal
|
||||
if (in_array('tesseract.mean_removal', $filters) === true) {
|
||||
$data['ocr']['tesseract.mean_removal'] = $tesseractImageFilterFunc(IMG_FILTER_MEAN_REMOVAL);
|
||||
}
|
||||
|
||||
// filter: tesseract.negate
|
||||
if (in_array('tesseract.negate', $filters) === true) {
|
||||
$data['ocr']['tesseract.negate'] = $tesseractImageFilterFunc(IMG_FILTER_NEGATE);
|
||||
}
|
||||
|
||||
// filter: tesseract.pixelate
|
||||
if (in_array('tesseract.pixelate', $filters) === true) {
|
||||
$data['ocr']['tesseract.pixelate'] = $tesseractImageFilterFunc(IMG_FILTER_PIXELATE, 3);
|
||||
}
|
||||
|
||||
// filter: keras
|
||||
if (in_array('keras', $filters) === true) {
|
||||
$cmd = '/usr/bin/python3 ' . base_path() . '/scripts/keras_oc.py ' . urlencode($url);
|
||||
$command = escapeshellcmd($cmd);
|
||||
$output = shell_exec($cmd);
|
||||
if ($output !== null && strlen($output) > 0) {
|
||||
$output = substr($output, (strpos($output, '----------START----------') + 25));
|
||||
} else {
|
||||
$output = '';
|
||||
}
|
||||
$data['ocr']['keras'] = $output;
|
||||
}
|
||||
|
||||
unlink($urlDownloadFilePath);
|
||||
return $this->respondJson($data);
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors(['url' => 'url is missing']);
|
||||
}
|
||||
|
||||
// $ffmpeg = FFMpeg\FFMpeg::create();
|
||||
|
||||
// // Load the input video
|
||||
// $inputFile = $ffmpeg->open('input.mp4');
|
||||
|
||||
// // Split the video into individual frames
|
||||
// $videoFrames = $inputFile->frames();
|
||||
// foreach ($videoFrames as $frame) {
|
||||
// // Save the frame as a PNG
|
||||
// $frame->save(new FFMpeg\Format\Video\PNG(), 'frame-' . $frame->getMetadata('pts') . '.png');
|
||||
|
||||
// // Pass the PNG to Tesseract for processing
|
||||
// exec("tesseract frame-" . $frame->getMetadata('pts') . ".png output");
|
||||
// }
|
||||
|
||||
// // Read the output from Tesseract
|
||||
// $text = file_get_contents("output.txt");
|
||||
|
||||
// // Do something with the text from Tesseract
|
||||
// echo $text;
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Conductors\ShortlinkConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\MediaRequest;
|
||||
use App\Http\Requests\ShortlinkRequest;
|
||||
use App\Models\Media;
|
||||
use App\Models\Shortlink;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
|
||||
class ShortlinkController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only(['store','update','destroy']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = ShortlinkConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
],
|
||||
function ($options) {
|
||||
return $options['total'] === 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Shortlink $shortlink The request shortlink.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Shortlink $shortlink)
|
||||
{
|
||||
if (ShortlinkConductor::viewable($shortlink) === true) {
|
||||
return $this->respondAsResource(ShortlinkConductor::model($request, $shortlink));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new media resource
|
||||
*
|
||||
* @param \App\Http\Requests\ShortlinkRequest $request The shortlink.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(ShortlinkRequest $request)
|
||||
{
|
||||
if (ShortlinkConductor::creatable() === true) {
|
||||
$shortlink = Shortlink::create($request->all());
|
||||
|
||||
return $this->respondAsResource(
|
||||
ShortlinkConductor::model($request, $shortlink),
|
||||
['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||
);
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the media resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\ShortlinkRequest $request The update request.
|
||||
* @param \App\Models\Shortlink $medium The specified shortlink.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(ShortlinkRequest $request, Shortlink $shortlink)
|
||||
{
|
||||
if (ShortlinkConductor::updatable($shortlink) === true) {
|
||||
$shortlink->update($request->all());
|
||||
return $this->respondAsResource(ShortlinkConductor::model($request, $shortlink));
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Shortlink $medium Specified shortlink.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Shortlink $shortlink)
|
||||
{
|
||||
if (ShortlinkConductor::destroyable($shortlink) === true) {
|
||||
$shortlink->delete();
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\SubscriptionConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Models\Subscription;
|
||||
use App\Http\Requests\SubscriptionRequest;
|
||||
use App\Jobs\SendEmailJob;
|
||||
use App\Mail\SubscriptionConfirm;
|
||||
use App\Mail\SubscriptionUnsubscribed;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SubscriptionController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->except(['store', 'destroyByEmail']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of subscribers.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = SubscriptionConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Subscription $subscription The subscription model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Subscription $subscription)
|
||||
{
|
||||
if (SubscriptionConductor::viewable($subscription) === true) {
|
||||
return $this->respondAsResource(SubscriptionConductor::model($request, $subscription));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a subscriber email in the database.
|
||||
*
|
||||
* @param \App\Http\Requests\SubscriptionRequest $request The subscriber update request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(SubscriptionRequest $request)
|
||||
{
|
||||
if (SubscriptionConductor::creatable() === true) {
|
||||
Subscription::create($request->all());
|
||||
dispatch((new SendEmailJob($request->email, new SubscriptionConfirm($request->email))))->onQueue('mail');
|
||||
|
||||
return $this->respondCreated();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\SubscriptionRequest $request The subscription update request.
|
||||
* @param \App\Models\Subscription $subscription The specified subscription.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(SubscriptionRequest $request, Subscription $subscription)
|
||||
{
|
||||
// if (EventConductor::updatable($event) === true) {
|
||||
// $event->update($request->all());
|
||||
// return $this->respondAsResource(EventConductor::model($request, $event));
|
||||
// }
|
||||
|
||||
// return $this->respondForbidden();
|
||||
|
||||
|
||||
// $input = [];
|
||||
// $updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password'];
|
||||
|
||||
// if ($request->user()->hasPermission('admin/user') === true) {
|
||||
// $updatable = array_merge($updatable, ['email_verified_at']);
|
||||
// } elseif ($request->user()->is($user) !== true) {
|
||||
// return $this->respondForbidden();
|
||||
// }
|
||||
|
||||
// $input = $request->only($updatable);
|
||||
// if (array_key_exists('password', $input) === true) {
|
||||
// $input['password'] = Hash::make($request->input('password'));
|
||||
// }
|
||||
|
||||
// $user->update($input);
|
||||
|
||||
// return $this->respondAsResource((new UserFilter($request))->filter($user));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the user from the database.
|
||||
*
|
||||
* @param Subscription $subscription The specified subscription.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Subscription $subscription)
|
||||
{
|
||||
if (SubscriptionConductor::destroyable($subscription) === true) {
|
||||
$subscription->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the user from the database.
|
||||
*
|
||||
* @param SubscriptionRequest $request The specified subscription.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroyByEmail(SubscriptionRequest $request)
|
||||
{
|
||||
$subscription = Subscription::where('email', $request->email)->first();
|
||||
if ($subscription !== null) {
|
||||
$subscription->delete();
|
||||
dispatch((new SendEmailJob($request->email, new SubscriptionUnsubscribed($request->email))))->onQueue('mail');
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\EventConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\UserRequest;
|
||||
use App\Http\Requests\UserForgotPasswordRequest;
|
||||
use App\Http\Requests\UserRegisterRequest;
|
||||
use App\Http\Requests\UserResendVerifyEmailRequest;
|
||||
use App\Http\Requests\UserResetPasswordRequest;
|
||||
use App\Http\Requests\UserVerifyEmailRequest;
|
||||
use App\Jobs\SendEmailJob;
|
||||
use App\Mail\ChangedEmail;
|
||||
use App\Mail\ChangedPassword;
|
||||
use App\Mail\ChangeEmailVerify;
|
||||
use App\Mail\ForgotPassword;
|
||||
use App\Mail\EmailVerify;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCode;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Conductors\UserConductor;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
|
||||
class UserController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->except([
|
||||
'index',
|
||||
'show',
|
||||
'register',
|
||||
'exists',
|
||||
'forgotPassword',
|
||||
'resetPassword',
|
||||
'verifyEmail',
|
||||
'resendVerifyEmailCode',
|
||||
'eventList',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = UserConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created user in the database.
|
||||
*
|
||||
* @param \App\Http\Requests\UserRequest $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(UserRequest $request)
|
||||
{
|
||||
if (UserConductor::creatable() === true) {
|
||||
$user = User::create($request->all());
|
||||
return $this->respondAsResource(UserConductor::model($request, $user), ['respondCode' => HttpResponseCodes::HTTP_CREATED]);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\User $user The user model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, User $user)
|
||||
{
|
||||
if (UserConductor::viewable($user) === true) {
|
||||
return $this->respondAsResource(UserConductor::model($request, $user));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\UserRequest $request The user update request.
|
||||
* @param \App\Models\User $user The specified user.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(UserRequest $request, User $user)
|
||||
{
|
||||
if (UserConductor::updatable($user) === true) {
|
||||
$input = [];
|
||||
$updatable = ['first_name', 'last_name', 'email', 'phone', 'password', 'display_name'];
|
||||
|
||||
if ($request->user()->hasPermission('admin/user') === true) {
|
||||
$updatable = array_merge($updatable, ['email_verified_at']);
|
||||
}
|
||||
|
||||
$input = $request->only($updatable);
|
||||
if (array_key_exists('password', $input) === true) {
|
||||
$input['password'] = Hash::make($request->input('password'));
|
||||
}
|
||||
|
||||
$user->update($input);
|
||||
|
||||
return $this->respondAsResource(UserConductor::model($request, $user));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the user from the database.
|
||||
*
|
||||
* @param \App\Models\User $user The specified user.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
if (UserConductor::destroyable($user) === true) {
|
||||
$user->delete();
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user
|
||||
*
|
||||
* @param \App\Http\Requests\UserRegisterRequest $request The register user request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function register(UserRegisterRequest $request)
|
||||
{
|
||||
try {
|
||||
$userData = $request->only([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
'password',
|
||||
'display_name',
|
||||
]);
|
||||
|
||||
$userData['password'] = Hash::make($userData['password']);
|
||||
|
||||
$user = User::where('email', $request->input('email'))
|
||||
->whereNull('password')
|
||||
->first();
|
||||
|
||||
if ($user === null) {
|
||||
$user = User::create($userData);
|
||||
} else {
|
||||
unset($userData['email']);
|
||||
$user->update($userData);
|
||||
}//end if
|
||||
|
||||
$code = $user->codes()->create([
|
||||
'action' => 'verify-email',
|
||||
]);
|
||||
|
||||
dispatch((new SendEmailJob($user->email, new EmailVerify($user, $code->code))))->onQueue('mail');
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Check your email for a welcome code.'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'message' => 'A server error occurred. Please try again later' . $e
|
||||
], 500);
|
||||
}//end try
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new reset password code
|
||||
*
|
||||
* @param \App\Http\Requests\UserForgotPasswordRequest $request The reset password request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function forgotPassword(UserForgotPasswordRequest $request)
|
||||
{
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user !== null) {
|
||||
$user->codes()->where('action', 'reset-password')->delete();
|
||||
$code = $user->codes()->create([
|
||||
'action' => 'reset-password'
|
||||
]);
|
||||
|
||||
dispatch((new SendEmailJob($user->email, new ForgotPassword($user, $code->code))))->onQueue('mail');
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets a user password
|
||||
*
|
||||
* @param \App\Http\Requests\UserResetPasswordRequest $request The reset password request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function resetPassword(UserResetPasswordRequest $request)
|
||||
{
|
||||
UserCode::clearExpired();
|
||||
|
||||
$code = UserCode::where('code', $request->input('code'))->where('action', 'reset-password')->first();
|
||||
if ($code !== null) {
|
||||
$user = $code->user()->first();
|
||||
|
||||
$code->delete();
|
||||
$user->codes()->where('action', 'verify-email')->delete();
|
||||
|
||||
$user->password = Hash::make($request->input('password'));
|
||||
|
||||
if ($user->email_verified_at === null) {
|
||||
$user->email_verified_at = now();
|
||||
}
|
||||
|
||||
$user->save();
|
||||
|
||||
dispatch((new SendEmailJob($user->email, new ChangedPassword($user))))->onQueue('mail');
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondError([
|
||||
'code' => 'The code was not found or has expired.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an email code
|
||||
*
|
||||
* @param \App\Http\Requests\UserVerifyEmailRequest $request The verify email request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function verifyEmail(UserVerifyEmailRequest $request)
|
||||
{
|
||||
UserCode::clearExpired();
|
||||
|
||||
$code = UserCode::where('code', $request->input('code'))->where('action', 'verify-email')->first();
|
||||
if ($code !== null) {
|
||||
$user = $code->user()->first();
|
||||
$new_email = $code->data;
|
||||
|
||||
if ($new_email === null) {
|
||||
if ($user->email_verified_at === null) {
|
||||
$user->email_verified_at = now();
|
||||
}
|
||||
} else {
|
||||
dispatch((new SendEmailJob($user->email, new ChangedEmail($user, $user->email, $new_email))))
|
||||
->onQueue('mail');
|
||||
|
||||
$user->email = $new_email;
|
||||
$user->email_verified_at = now();
|
||||
}
|
||||
|
||||
$code->delete();
|
||||
$user->save();
|
||||
|
||||
return $this->respondNoContent();
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors([
|
||||
'code' => 'The code was not found or has expired.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend a new verify email
|
||||
*
|
||||
* @param \App\Http\Requests\UserResendVerifyEmailRequest $request The resend verify email request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function resendVerifyEmail(UserResendVerifyEmailRequest $request)
|
||||
{
|
||||
UserCode::clearExpired();
|
||||
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user !== null) {
|
||||
$code = $user->codes()->where('action', 'verify-email')->first();
|
||||
$code->regenerate();
|
||||
$code->save();
|
||||
|
||||
if ($code->data === null) {
|
||||
dispatch((new SendEmailJob($user->email, new EmailVerify($user, $code->code))))->onQueue('mail');
|
||||
} else {
|
||||
dispatch((new SendEmailJob($user->email, new ChangeEmailVerify($user, $code->code, $code->data))))
|
||||
->onQueue('mail');
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Verify email sent if user registered and required']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend verification email
|
||||
*
|
||||
* @param \App\Http\Requests\UserResendVerifyEmailRequest $request The resend user request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function resendVerifyEmailCode(UserResendVerifyEmailRequest $request)
|
||||
{
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user !== null) {
|
||||
$user->codes()->where('action', 'verify-email')->delete();
|
||||
|
||||
if ($user->email_verified_at === null) {
|
||||
$code = $user->codes()->create([
|
||||
'action' => 'verify-email'
|
||||
]);
|
||||
|
||||
dispatch((new SendEmailJob($user->email, new EmailVerify($user, $code->code))))->onQueue('mail');
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON event list of a user.
|
||||
*
|
||||
* @param Request $request The http request.
|
||||
* @param User $user The specified user.
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function eventList(Request $request, User $user)
|
||||
{
|
||||
if ($request->user() !== null && ($request->user() === $user || $request->user()->hasPermission('admin/events') === true)) {
|
||||
$collection = $user->events;
|
||||
$total = $collection->count();
|
||||
|
||||
$collection = EventConductor::collection($request, $collection);
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
319
app/Http/Controllers/AuthController.php
Normal file
319
app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Mail\UserEmailUpdateConfirm;
|
||||
use App\Mail\UserLogin;
|
||||
use App\Mail\UserLoginBackupCode;
|
||||
use App\Mail\UserRegister;
|
||||
use App\Mail\UserWelcome;
|
||||
use App\Models\Token;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the login form or if token present, process the login
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function showLogin(Request $request): View|RedirectResponse
|
||||
{
|
||||
if (auth()->check()) {
|
||||
return redirect()->action([HomeController::class, 'index']);
|
||||
}
|
||||
|
||||
$token = $request->query('token');
|
||||
if ($token) {
|
||||
return $this->LoginByToken($token);
|
||||
}
|
||||
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the login form
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function postLogin(Request $request): View|RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
'captcha' => 'required_captcha',
|
||||
], [
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid'),
|
||||
]);
|
||||
|
||||
$forceEmailLogin = false;
|
||||
|
||||
if($request->has('code')) {
|
||||
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
|
||||
if($user) {
|
||||
$tfa = AccountController::getTFAInstance();
|
||||
if ($request->code && $tfa->verifyCode($user->tfa_secret, $request->code, 4)) {
|
||||
$data = ['url' => session()->pull('url.intended', null)];
|
||||
return $this->loginByUser($user, $data);
|
||||
}
|
||||
}
|
||||
|
||||
return view('auth.login-2fa', ['email' => $request->email])->withErrors([
|
||||
'code' => 'The 2FA code is not valid',
|
||||
]);
|
||||
}
|
||||
|
||||
if($request->has('backup_code')) {
|
||||
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
|
||||
if($user) {
|
||||
if($user->verifyBackupCode($request->backup_code)) {
|
||||
$data = ['url' => session()->pull('url.intended', null)];
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserLoginBackupCode($user->email)))->onQueue('mail');
|
||||
|
||||
return $this->loginByUser($user, $data);
|
||||
}
|
||||
}
|
||||
|
||||
return view('auth.login-2fa', ['email' => $request->email, 'method' => 'backup'])->withErrors([
|
||||
'backup_code' => 'The backup code is not valid',
|
||||
]);
|
||||
}
|
||||
|
||||
if($request->has('method')) {
|
||||
if($request->get('method') === 'email') {
|
||||
$forceEmailLogin = true;
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
|
||||
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
|
||||
if ($user) {
|
||||
if (!$forceEmailLogin && $user->tfa_secret !== null) {
|
||||
return view('auth.login-2fa', ['user' => $user]);
|
||||
}
|
||||
|
||||
$token = $user->tokens()->create([
|
||||
'type' => 'login',
|
||||
'data' => ['url' => session()->pull('url.intended', null)],
|
||||
]);
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserLogin($token->id, $user->getName(), $user->email)))->onQueue('mail');
|
||||
return view('auth.login-link');
|
||||
}
|
||||
|
||||
session()->flash('status', 'not-found');
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the login by token
|
||||
*
|
||||
* @param string $tokenStr
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function loginByToken(string $tokenStr): View|RedirectResponse
|
||||
{
|
||||
$token = Token::where('id', $tokenStr)
|
||||
->where('type', 'login')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if ($token) {
|
||||
$user = $token->user;
|
||||
if($user) {
|
||||
$token->delete();
|
||||
return $this->loginByUser($user, $token->data);
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash('message', 'That token has expired or is invalid');
|
||||
session()->flash('message-title', 'Log in failed');
|
||||
session()->flash('message-type', 'danger');
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the login by user
|
||||
*
|
||||
* @param User $user
|
||||
* @param array $data
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function loginByUser(User $user, array $data = [])
|
||||
{
|
||||
$url = null;
|
||||
if($data && isset($data->url) && $data->url) {
|
||||
$url = $data->url;
|
||||
}
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
session()->flash('message', 'You have been logged in');
|
||||
session()->flash('message-title', 'Logged in');
|
||||
session()->flash('message-type', 'success');
|
||||
|
||||
if($url) {
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
return redirect()->action([HomeController::class, 'index']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the user logout
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function logout(): RedirectResponse
|
||||
{
|
||||
auth()->logout();
|
||||
|
||||
session()->flash('message', 'You have been logged out');
|
||||
session()->flash('message-title', 'Logged out');
|
||||
session()->flash('message-type', 'warning');
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the registration form or if token present, process the registration
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function showRegister(Request $request): View|RedirectResponse
|
||||
{
|
||||
if (auth()->check()) {
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
$tokenStr = $request->query('token');
|
||||
if ($tokenStr) {
|
||||
$token = Token::where('id', $tokenStr)
|
||||
->where('type', 'register')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if ($token) {
|
||||
$user = $token->user;
|
||||
if ($user) {
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
|
||||
$user->tokens()->where('type', 'register')->delete();
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserWelcome($user->email)))->onQueue('mail');
|
||||
|
||||
$this->loginByUser($user);
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash('message', 'That token has expired or is invalid');
|
||||
session()->flash('message-title', 'Registration failed');
|
||||
session()->flash('message-type', 'danger');
|
||||
}
|
||||
|
||||
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the registration form
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function postRegister(Request $request): View|RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
'captcha' => 'required_captcha',
|
||||
], [
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid')
|
||||
]);
|
||||
|
||||
$key = $request->get('name', '');
|
||||
$passHoneypot = ($key === 'AC9E94587F163AD93174FBF3DFDF9645B886960F2F8DD6D60F81CDB6DCDA3BC33');
|
||||
|
||||
$user = User::where('email', $request->email)->first();
|
||||
if($user) {
|
||||
if($user->email_verified_at !== null) {
|
||||
return redirect()->back()->withInput()->withErrors([
|
||||
'email' => __('validation.custom_messages.email_exists'),
|
||||
]);
|
||||
}
|
||||
} else if($passHoneypot) {
|
||||
$user = User::create([
|
||||
'email' => $request->email,
|
||||
]);
|
||||
}
|
||||
|
||||
if($passHoneypot) {
|
||||
Log::channel('honeypot')->info('Valid key used for registration using email: ' . $request->email . ', ip address: ' . $request->ip() . ', user agent: ' . $request->userAgent());
|
||||
$user->tokens()->where('type', 'register')->delete();
|
||||
$token = $user->tokens()->create([
|
||||
'type' => 'register',
|
||||
'data' => ['url' => session()->pull('url.intended', null)],
|
||||
]);
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserRegister($token->id, $user->email)))->onQueue('mail');
|
||||
} else {
|
||||
Log::channel('honeypot')->info('Invalid key used for registration using email: ' . $request->email . ', ip address: ' . $request->ip() . ', user agent: ' . $request->userAgent() . ', key: ' . $key);
|
||||
}
|
||||
|
||||
return view('auth.register-link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user email update.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function updateEmail(Request $request): RedirectResponse
|
||||
{
|
||||
$tokenStr = $request->query('token');
|
||||
|
||||
$token = Token::where('id', $tokenStr)
|
||||
->where('type', 'email-update')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if($token && $token->user) {
|
||||
if($token->data && isset($token->data['email'])) {
|
||||
$user = $token->user;
|
||||
$user->email = $token->data['email'];
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
|
||||
$user->tokens()->where('type', 'email-update')->delete();
|
||||
|
||||
session()->flash('message', 'Your email has been updated');
|
||||
session()->flash('message-title', 'Email updated');
|
||||
session()->flash('message-type', 'success');
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserEmailUpdateConfirm($user->email)))->onQueue('mail');
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash('message', 'That token has expired or is invalid');
|
||||
session()->flash('message-title', 'Email update failed');
|
||||
session()->flash('message-type', 'danger');
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
abstract class Controller
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
use DispatchesJobs;
|
||||
use ValidatesRequests;
|
||||
//
|
||||
}
|
||||
|
||||
20
app/Http/Controllers/HomeController.php
Normal file
20
app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
use App\Models\Workshop;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// $posts = Post::query()->orderBy('created_at', 'desc')->limit(4)->get();
|
||||
$workshops = Workshop::query()->where('starts_at', '>', now())->where('status', '!=', 'private')->orderBy('starts_at', 'asc')->limit(4)->get();
|
||||
|
||||
return view('home', [
|
||||
// 'posts' => $posts,
|
||||
'workshops' => $workshops,
|
||||
]);
|
||||
}
|
||||
}
|
||||
102
app/Http/Controllers/LocationController.php
Normal file
102
app/Http/Controllers/LocationController.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Location;
|
||||
use App\Models\Post;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LocationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Location::query();
|
||||
|
||||
if($request->has('search')) {
|
||||
$query->where('name', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('address', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
$locations = $query->orderBy('name')->paginate(12)->onEachSide(1);
|
||||
|
||||
return view('admin.location.index', [
|
||||
'locations' => $locations
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.location.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required',
|
||||
'address_url' => 'nullable|url',
|
||||
], [
|
||||
// 'firstname.required' => __('validation.custom_messages.firstname_required'),
|
||||
// 'surname.required' => __('validation.custom_messages.surname_required'),
|
||||
]);
|
||||
|
||||
Location::create(array_merge(
|
||||
$request->all(),
|
||||
));
|
||||
|
||||
session()->flash('message', 'Location has been created');
|
||||
session()->flash('message-title', 'Location created');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.location.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Location $location)
|
||||
{
|
||||
return view('admin.location.edit', ['location' => $location]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Location $location)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required',
|
||||
'address_url' => 'url',
|
||||
], [
|
||||
// 'firstname.required' => __('validation.custom_messages.firstname_required'),
|
||||
// 'surname.required' => __('validation.custom_messages.surname_required'),
|
||||
]);
|
||||
|
||||
$location->update($request->all());
|
||||
|
||||
session()->flash('message', 'Location has been updated');
|
||||
session()->flash('message-title', 'Location updated');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.location.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Location $location)
|
||||
{
|
||||
$location->delete();
|
||||
session()->flash('message', 'Location has been deleted');
|
||||
session()->flash('message-title', 'Location deleted');
|
||||
session()->flash('message-type', 'danger');
|
||||
|
||||
return redirect()->route('admin.location.index');
|
||||
}
|
||||
}
|
||||
549
app/Http/Controllers/MediaController.php
Normal file
549
app/Http/Controllers/MediaController.php
Normal file
@@ -0,0 +1,549 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\FileInvalidException;
|
||||
use App\Exceptions\FileTooLargeException;
|
||||
use App\Helpers;
|
||||
use App\Models\Media;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class MediaController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if(!$request->wantsJson()) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$media = $this->getMedia($request);
|
||||
return response()->json($media);
|
||||
}
|
||||
|
||||
public function admin_index(Request $request)
|
||||
{
|
||||
$media = $this->getMedia($request);
|
||||
|
||||
return view('admin.media.index', [
|
||||
'media' => $media,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function getMedia(Request $request)
|
||||
{
|
||||
$query = Media::query();
|
||||
$perPage = $request->input('per_page', 25);
|
||||
|
||||
if(!empty($request->get('search'))) {
|
||||
$query->where(function($query) use ($request) {
|
||||
$query->where('title', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('name', 'like', '%' . $request->search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
if($request->has('mime_type')) {
|
||||
$mime_types = explode(',', $request->mime_type);
|
||||
$query->where(function ($query) use ($mime_types) {
|
||||
foreach ($mime_types as $mime_type) {
|
||||
$mime_type = str_replace('*', '%', $mime_type);
|
||||
$query->orWhere('mime_type', 'like', $mime_type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$media = $query->orderBy('created_at', 'desc');
|
||||
|
||||
if($request->wantsJson() && !(empty($request->input('selected'))) && empty($request->get('search')) && !$request->has('page')) {
|
||||
$selected = $request->input('selected')[0];
|
||||
$selectedMedia = $media->get();
|
||||
$selectedMediaIndex = $selectedMedia->search(function ($item) use ($selected) {
|
||||
return $item->name == $selected;
|
||||
});
|
||||
|
||||
if ($selectedMediaIndex !== false) {
|
||||
$page = intdiv($selectedMediaIndex, $perPage) + 1;
|
||||
$request->merge(['page' => $page]);
|
||||
}
|
||||
}
|
||||
|
||||
$media = $media->paginate($perPage)->onEachSide(1);
|
||||
|
||||
// Transform the 'password' field of each item in the collection
|
||||
$media->getCollection()->transform(function ($item) {
|
||||
$item->password = $item->password ? 'yes' : null;
|
||||
return $item;
|
||||
});
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Request $request, Media $media)
|
||||
{
|
||||
if(!$request->wantsJson()) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return response()->json($media);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function admin_create()
|
||||
{
|
||||
return view('admin.media.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function admin_store(Request $request)
|
||||
{
|
||||
$file = null;
|
||||
$cleanupPath = null;
|
||||
|
||||
// Check if the endpoint received a file...
|
||||
if($request->hasFile('file')) {
|
||||
try {
|
||||
$file = $this->upload($request);
|
||||
|
||||
if(is_array($file) && !empty($file['chunk'])) {
|
||||
return response()->json([
|
||||
'message' => 'Chunk stored',
|
||||
'upload_token' => $file['token'] ?? null,
|
||||
]);
|
||||
} else if(!$file) {
|
||||
return response()->json([
|
||||
'message' => 'An error occurred processing the file.',
|
||||
'errors' => [
|
||||
'file' => 'An error occurred processing the file.'
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
if(!$request->has('title')) {
|
||||
return response()->json([
|
||||
'message' => 'The file ' . $file->getClientOriginalName() . ' has been uploaded',
|
||||
'file' => [
|
||||
'name' => $file->getClientOriginalName(),
|
||||
'size' => $file->getSize(),
|
||||
'mime_type' => $file->getMimeType()
|
||||
]
|
||||
]);
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
return response()->json([
|
||||
'message' => $e->getMessage(),
|
||||
'errors' => [
|
||||
'file' => $e->getMessage()
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
// else check if it received a file name of a previous upload...
|
||||
} else if($request->has('upload_token') || $request->has('file')) {
|
||||
$uploadToken = $request->input('upload_token', $request->input('file'));
|
||||
$chunkUploads = session()->get('chunk_uploads', []);
|
||||
|
||||
if(!is_string($uploadToken) || !isset($chunkUploads[$uploadToken])) {
|
||||
return response()->json([
|
||||
'message' => 'Could not find the referenced file on the server.',
|
||||
'errors' => [
|
||||
'file' => 'Could not find the referenced file on the server.'
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
$tempFileName = $chunkUploads[$uploadToken];
|
||||
if(!file_exists($tempFileName)) {
|
||||
return response()->json([
|
||||
'message' => 'Could not find the referenced file on the server.',
|
||||
'errors' => [
|
||||
'file' => 'Could not find the referenced file on the server ('.$tempFileName.').'
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
$fileMime = mime_content_type($tempFileName);
|
||||
if($fileMime === false) {
|
||||
$fileMime = 'application/octet-stream';
|
||||
}
|
||||
$fileName = $request->input('filename', 'upload');
|
||||
$fileName = Helpers::cleanFileName($fileName);
|
||||
if ($fileName === '') {
|
||||
$fileName = 'upload';
|
||||
}
|
||||
|
||||
$file = new UploadedFile($tempFileName, $fileName, $fileMime, null, true);
|
||||
$cleanupPath = $tempFileName;
|
||||
unset($chunkUploads[$uploadToken]);
|
||||
session()->put('chunk_uploads', $chunkUploads);
|
||||
}
|
||||
|
||||
// Check there is an actual file
|
||||
if(!$file) {
|
||||
return response()->json([
|
||||
'message' => 'A file is required.',
|
||||
'errors' => [
|
||||
'file' => 'A file is required.'
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
if(!$request->has('title')) {
|
||||
return response()->json([
|
||||
'message' => 'A title is required',
|
||||
'errors' => [
|
||||
'title' => 'A title is required'
|
||||
]
|
||||
], 422);
|
||||
}
|
||||
|
||||
$fileName = $file->getClientOriginalName();
|
||||
$name = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$name = Helpers::cleanFileName($name);
|
||||
|
||||
if(Media::find($name . '.' . $extension) !== null) {
|
||||
$increment = 1;
|
||||
$name = preg_replace('/-\d+$/', '', $name);
|
||||
|
||||
while(Media::find($name . '-' . $increment . '.' . $extension) !== null) {
|
||||
$increment++;
|
||||
}
|
||||
|
||||
$fileName = $name . '-' . $increment . '.' . $extension;
|
||||
}
|
||||
|
||||
$hash = hash_file('sha256', $file->path());
|
||||
|
||||
$storage = Storage::disk('media');
|
||||
$exists = $storage->exists($hash);
|
||||
if(!$exists) {
|
||||
if($file->storeAs('/', $hash, 'media') === false) {
|
||||
if($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'message' => 'A server error occurred uploading the file.',
|
||||
], 500);
|
||||
} else {
|
||||
session()->flash('message', 'A server error occurred uploading the file.');
|
||||
session()->flash('message-title', 'Upload failed');
|
||||
session()->flash('message-type', 'danger');
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$media = Media::Create([
|
||||
'title' => $request->get('title', Helpers::filenameToTitle($fileName)),
|
||||
'user_id' => auth()->id(),
|
||||
'name' => $fileName,
|
||||
'size' => $file->getSize(),
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'hash' => $hash
|
||||
]);
|
||||
|
||||
if(!$exists) {
|
||||
$media->generateVariants(false);
|
||||
} else {
|
||||
// find media with the same hash that also has variants and copy them
|
||||
$mediaWithVariants = Media::where('hash', $hash)->where('variants', '!=', '')->orderBy('created_at')->first();
|
||||
if($mediaWithVariants) {
|
||||
$media->variants = $mediaWithVariants->variants;
|
||||
$media->save();
|
||||
}
|
||||
}
|
||||
|
||||
if(is_string($cleanupPath)) {
|
||||
$realPath = realpath($cleanupPath);
|
||||
$tempDir = realpath(sys_get_temp_dir());
|
||||
if($realPath !== false && $tempDir !== false && str_starts_with($realPath, $tempDir . DIRECTORY_SEPARATOR)) {
|
||||
@unlink($realPath);
|
||||
}
|
||||
}
|
||||
|
||||
if($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'message' => 'File has been uploaded',
|
||||
'name' => $media->name,
|
||||
'size' => $media->size,
|
||||
'mime_type' => $media->mime_type
|
||||
]);
|
||||
} else {
|
||||
session()->flash('message', 'Media has been uploaded');
|
||||
session()->flash('message-title', 'Media uploaded');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.media.index');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function admin_edit(Media $media)
|
||||
{
|
||||
return view('admin.media.edit', ['medium' => $media]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function admin_update(Request $request, Media $media)
|
||||
{
|
||||
$max_size = Helpers::getMaxUploadSize();
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required',
|
||||
// 'file' => 'nullable|file|max:' . (max(round($max_size / 1024),0)),
|
||||
], [
|
||||
'title.required' => __('validation.custom_messages.title_required'),
|
||||
// 'file.required' => __('validation.custom_messages.file_required'),
|
||||
// 'file.file' => __('validation.custom_messages.file_file'),
|
||||
// 'file.max' => __('validation.custom_messages.file_max', ['max' => Helpers::bytesToString($max_size)])
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
|
||||
$mediaData = $request->all();
|
||||
|
||||
// $file = null;
|
||||
// if($request->has('file')) {
|
||||
// $file = $request->file('file');
|
||||
//
|
||||
// $name = $file->getClientOriginalName();
|
||||
// $name = Helpers::cleanFileName($name);
|
||||
// if ($name !== $media->name) {
|
||||
// if (Media::find($name) !== null) {
|
||||
// $increment = 2;
|
||||
// while (Media::find($name . '-' . $increment) !== null) {
|
||||
// $increment++;
|
||||
// }
|
||||
//
|
||||
// $name = $name . '-' . $increment;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// $hash = hash_file('sha256', $file->path());
|
||||
//
|
||||
// $storage = Storage::disk('media');
|
||||
// if (!$storage->exists($hash)) {
|
||||
// if ($file->storeAs('/', $hash, 'media') === false) {
|
||||
// session()->flash('message', 'A server error occurred uploading the file.');
|
||||
// session()->flash('message-title', 'Upload failed');
|
||||
// session()->flash('message-type', 'danger');
|
||||
// return redirect()->back();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// $mediaData['name'] = $name;
|
||||
// $mediaData['size'] = $file->getSize();
|
||||
// $mediaData['mime_type'] = $file->getMimeType();
|
||||
// $mediaData['hash'] = $hash;
|
||||
// }
|
||||
|
||||
if($request->get('password_clear') === 'on') {
|
||||
$mediaData['password'] = null;
|
||||
} else {
|
||||
$password = $request->get('password');
|
||||
|
||||
if($password !== null && $password !== '') {
|
||||
$mediaData['password'] = password_hash($request->get('password'), PASSWORD_DEFAULT);
|
||||
} else {
|
||||
unset($mediaData['password']);
|
||||
}
|
||||
}
|
||||
|
||||
$media->update($mediaData);
|
||||
|
||||
// if($file) {
|
||||
// $media->generateVariants(false);
|
||||
// unlink($file);
|
||||
// }
|
||||
|
||||
session()->flash('message', 'Media has been updated');
|
||||
session()->flash('message-title', 'Media updated');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.media.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function admin_destroy(Request $request, Media $media)
|
||||
{
|
||||
$media->delete();
|
||||
session()->flash('message', 'Media has been deleted');
|
||||
session()->flash('message-title', 'Media deleted');
|
||||
session()->flash('message-type', 'danger');
|
||||
|
||||
if($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'redirect' => route('admin.media.index'),
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.media.index');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws FileInvalidException
|
||||
* @throws FileTooLargeException
|
||||
*/
|
||||
private function upload(Request $request)
|
||||
{
|
||||
$max_size = Helpers::getMaxUploadSize();
|
||||
|
||||
$file = $request->file('file');
|
||||
if(!$file->isValid()) {
|
||||
throw new FileInvalidException('The file is invalid');
|
||||
}
|
||||
|
||||
$fileName = $request->input('filename', $file->getClientOriginalName());
|
||||
$fileName = Helpers::cleanFileName($fileName);
|
||||
if ($fileName === '') {
|
||||
$extension = strtolower($file->getClientOriginalExtension());
|
||||
$fileName = 'upload' . ($extension !== '' ? '.' . $extension : '');
|
||||
}
|
||||
|
||||
if(($request->has('filestart') || $request->has('fileappend')) && $request->has('filesize')) {
|
||||
$fileSize = $request->get('filesize');
|
||||
|
||||
if($fileSize > $max_size) {
|
||||
throw new FileTooLargeException('The file is larger than the maximum size allowed of ' . Helpers::bytesToString($max_size));
|
||||
}
|
||||
|
||||
$chunkUploads = session()->get('chunk_uploads', []);
|
||||
$uploadToken = $request->input('upload_token');
|
||||
|
||||
if($request->has('filestart')) {
|
||||
$uploadToken = bin2hex(random_bytes(16));
|
||||
$tempFilePath = tempnam(sys_get_temp_dir(), 'chunk-' . Auth::id() . '-');
|
||||
if($tempFilePath === false) {
|
||||
throw new FileInvalidException('Unable to create a temporary upload file.');
|
||||
}
|
||||
|
||||
$chunkUploads[$uploadToken] = $tempFilePath;
|
||||
session()->put('chunk_uploads', $chunkUploads);
|
||||
} else {
|
||||
if(!is_string($uploadToken) || !isset($chunkUploads[$uploadToken])) {
|
||||
throw new FileInvalidException('Invalid upload token.');
|
||||
}
|
||||
|
||||
$tempFilePath = $chunkUploads[$uploadToken];
|
||||
}
|
||||
|
||||
$filemode = 'a';
|
||||
if($request->has('filestart')) {
|
||||
$filemode = 'w';
|
||||
}
|
||||
|
||||
// Append the chunk to the temporary file
|
||||
$fp = fopen($tempFilePath, $filemode);
|
||||
if ($fp) {
|
||||
fwrite($fp, file_get_contents($file->getRealPath()));
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
// Check if the upload is complete
|
||||
if (filesize($tempFilePath) >= $fileSize) {
|
||||
$fileMime = mime_content_type($tempFilePath);
|
||||
if($fileMime === false) {
|
||||
$fileMime = 'application/octet-stream';
|
||||
}
|
||||
|
||||
if(is_string($uploadToken) && isset($chunkUploads[$uploadToken])) {
|
||||
unset($chunkUploads[$uploadToken]);
|
||||
session()->put('chunk_uploads', $chunkUploads);
|
||||
}
|
||||
|
||||
return new UploadedFile($tempFilePath, $fileName, $fileMime, null, true);
|
||||
} else {
|
||||
return [
|
||||
'chunk' => true,
|
||||
'token' => $uploadToken,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
|
||||
public function download(Request $request, Media $media)
|
||||
{
|
||||
$file = $media->path();
|
||||
if($file === null) {
|
||||
abort(404, 'File not found');
|
||||
}
|
||||
|
||||
if($media->password !== null && !Auth::user()?->isAdmin()) {
|
||||
if(!$request->has('password')) {
|
||||
return view('media-password');
|
||||
} else {
|
||||
$password = $request->get('password');
|
||||
|
||||
if($password === '' || $password === null) {
|
||||
return view('media-password', [
|
||||
'error' => 'Password is required',
|
||||
]);
|
||||
}
|
||||
|
||||
if(!password_verify(base64_decode($password), $media->password)) {
|
||||
return view('media-password', [
|
||||
'error' => 'Password is incorrect',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$variant = '';
|
||||
$download = false;
|
||||
$variants = array_keys($media->getVariantTypes());
|
||||
$query = $request->getQueryString();
|
||||
if($query !== '') {
|
||||
$queryList = explode('&', $query);
|
||||
foreach($queryList as $queryItem) {
|
||||
$parts = explode('=', $queryItem);
|
||||
if($variant === '' && in_array($parts[0], $variants) && ($parts[1] === '' || filter_var($parts[1], FILTER_VALIDATE_BOOLEAN))) {
|
||||
$variant = $parts[0];
|
||||
}
|
||||
|
||||
if($parts[0] === 'download' && ($parts[1] === '' || filter_var($parts[1], FILTER_VALIDATE_BOOLEAN))) {
|
||||
$download = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$mime_type = $media->mime_type;
|
||||
$name = $media->name;
|
||||
|
||||
if($variant !== '') {
|
||||
$variantFile = $media->getClosestVariant($variant);
|
||||
$file = $variantFile['file'];
|
||||
$mime_type = $variantFile['mime_type'];
|
||||
$name = $variantFile['name'];
|
||||
}
|
||||
|
||||
$headers = [
|
||||
'Content-Type' => $mime_type,
|
||||
'Content-Disposition' => ($download ? 'attachment; ' : '') . 'filename="' . $name . '"',
|
||||
];
|
||||
|
||||
return response()->file($file, $headers);
|
||||
}
|
||||
}
|
||||
147
app/Http/Controllers/PostController.php
Normal file
147
app/Http/Controllers/PostController.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers;
|
||||
use App\Models\Media;
|
||||
use App\Models\Post;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PostController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Post::query();
|
||||
|
||||
$query->where('status', 'published');
|
||||
|
||||
if($request->has('search')) {
|
||||
$query->where('title', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('content', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
$posts = $query->orderBy('created_at', 'desc')->paginate(12)->onEachSide(1);
|
||||
|
||||
return view('post.index', [
|
||||
'posts' => $posts
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function admin_index(Request $request)
|
||||
{
|
||||
$query = Post::query();
|
||||
|
||||
if($request->has('search')) {
|
||||
$query->where('title', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('content', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
$posts = $query->orderBy('created_at', 'desc')->paginate(12)->onEachSide(1);
|
||||
|
||||
return view('admin.post.index', [
|
||||
'posts' => $posts
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function admin_create()
|
||||
{
|
||||
return view('admin.post.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function admin_store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'content' => 'required',
|
||||
'hero_media_name' => 'required|exists:media,name',
|
||||
], [
|
||||
'title.required' => __('validation.custom_messages.title_required'),
|
||||
'content.required' => __('validation.custom_messages.content_required'),
|
||||
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
|
||||
]);
|
||||
|
||||
$postData = $request->all();
|
||||
$postData['user_id'] = auth()->user()->id;
|
||||
|
||||
$post = Post::create($postData);
|
||||
$post->updateFiles($request->input('files'));
|
||||
$post->updateFiles($request->input('gallery'), 'gallery');
|
||||
$post->updateFiles($request->input('videos'), 'videos');
|
||||
|
||||
session()->flash('message', 'Post has been created');
|
||||
session()->flash('message-title', 'Post created');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.post.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Post $post)
|
||||
{
|
||||
return view('post.show', ['post' => $post]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function admin_edit(Post $post)
|
||||
{
|
||||
return view('admin.post.edit', ['post' => $post]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function admin_update(Request $request, Post $post)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'content' => 'required',
|
||||
'hero_media_name' => 'required|exists:media,name',
|
||||
], [
|
||||
'title.required' => __('validation.custom_messages.title_required'),
|
||||
'content.required' => __('validation.custom_messages.content_required'),
|
||||
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
|
||||
]);
|
||||
|
||||
$postData = $request->all();
|
||||
$post->update($postData);
|
||||
$post->updateFiles($request->input('files'));
|
||||
$post->updateFiles($request->input('gallery'), 'gallery');
|
||||
$post->updateFiles($request->input('videos'), 'videos');
|
||||
|
||||
session()->flash('message', 'Post has been updated');
|
||||
session()->flash('message-title', 'Post updated');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.post.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function admin_destroy(Post $post)
|
||||
{
|
||||
$post->delete();
|
||||
session()->flash('message', 'Post has been deleted');
|
||||
session()->flash('message-title', 'Post deleted');
|
||||
session()->flash('message-type', 'danger');
|
||||
|
||||
return redirect()->route('admin.post.index');
|
||||
}
|
||||
}
|
||||
57
app/Http/Controllers/SearchController.php
Normal file
57
app/Http/Controllers/SearchController.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Post;
|
||||
use App\Models\Workshop;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$search = $request->get('q', '');
|
||||
$search_words = explode(' ', $search); // Split the search query into words[1]
|
||||
|
||||
$workshopQuery = Workshop::query()->where('status', '!=', 'draft');
|
||||
|
||||
$workshopQuery->where(function ($query) use ($search_words) {
|
||||
foreach ($search_words as $word) {
|
||||
$query->orWhere(function ($subQuery) use ($word) {
|
||||
$subQuery->where('title', 'like', '%' . $word . '%')
|
||||
->orWhere('content', 'like', '%' . $word . '%')
|
||||
->orWhereHas('location', function ($locationQuery) use ($word) {
|
||||
$locationQuery->where('name', 'like', '%' . $word . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$workshops = $workshopQuery->orderBy('starts_at', 'desc')
|
||||
->paginate(6, ['*'], 'workshop');
|
||||
|
||||
// $postQuery = Post::query()->where('status', 'published');
|
||||
// $postQuery->where(function ($query) use ($search_words) {
|
||||
// foreach ($search_words as $word) {
|
||||
// $query->where(function ($subQuery) use ($word) {
|
||||
// $subQuery->where('title', 'like', '%' . $word . '%')
|
||||
// ->orWhere('content', 'like', '%' . $word . '%');
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// $posts = $postQuery->orderBy('created_at', 'desc')
|
||||
// ->paginate(6, ['*'], 'post')
|
||||
// ->onEachSide(1);
|
||||
|
||||
return view('search', [
|
||||
'workshops' => $workshops,
|
||||
// 'posts' => $posts,
|
||||
'search' => $search,
|
||||
]);
|
||||
}
|
||||
}
|
||||
44
app/Http/Controllers/SubscribeController.php
Normal file
44
app/Http/Controllers/SubscribeController.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EmailSubscriptions;
|
||||
use App\Models\SentEmail;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SubscribeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function destroy($email)
|
||||
{
|
||||
$emailModel = SentEmail::where('id', $email)->first();
|
||||
|
||||
if (!$emailModel) {
|
||||
// Email not found, redirect to home page with a message
|
||||
return redirect()->route('index')->with([
|
||||
'message' => 'The unsubscribe link is invalid or has expired.',
|
||||
'message-title' => 'Invalid Unsubscribe Link',
|
||||
'message-type' => 'warning'
|
||||
]);
|
||||
}
|
||||
|
||||
// Existing unsubscribe logic
|
||||
$subscriptions = EmailSubscriptions::where('email', $emailModel->recipient)->get();
|
||||
|
||||
if ($subscriptions->isEmpty()) {
|
||||
session()->flash('message', 'You are already unsubscribed.');
|
||||
session()->flash('message-title', 'Already Unsubscribed');
|
||||
session()->flash('message-type', 'info');
|
||||
} else {
|
||||
EmailSubscriptions::where('email', $emailModel->recipient)->delete();
|
||||
|
||||
session()->flash('message', 'You have been successfully unsubscribed.');
|
||||
session()->flash('message-title', 'Unsubscribed');
|
||||
session()->flash('message-type', 'success');
|
||||
}
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
65
app/Http/Controllers/TicketController.php
Normal file
65
app/Http/Controllers/TicketController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Ticket;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Ticket $ticket)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Ticket $ticket)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Ticket $ticket)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Ticket $ticket)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
167
app/Http/Controllers/UserController.php
Normal file
167
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
if($request->has('search')) {
|
||||
$query->where('firstname', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('surname', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('phone', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('email', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
$users = $query->orderBy('created_at', 'desc')->paginate(12)->onEachSide(1);
|
||||
|
||||
return view('admin.user.index', [
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('admin.user.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'firstname' => '',
|
||||
'surname' => '',
|
||||
'email' => 'email|unique:users',
|
||||
'phone' => '',
|
||||
|
||||
'shipping_address' => 'required_with:shipping_city,shipping_postcode,shipping_country,shipping_state',
|
||||
'shipping_city' => 'required_with:shipping_address,shipping_postcode,shipping_country,shipping_state',
|
||||
'shipping_postcode' => 'required_with:shipping_address,shipping_city,shipping_country,shipping_state',
|
||||
'shipping_country' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_state',
|
||||
'shipping_state' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_country',
|
||||
|
||||
'billing_address' => 'required_with:billing_city,billing_postcode,billing_country,billing_state',
|
||||
'billing_city' => 'required_with:billing_address,billing_postcode,billing_country,billing_state',
|
||||
'billing_postcode' => 'required_with:billing_address,billing_city,billing_country,billing_state',
|
||||
'billing_country' => 'required_with:billing_address,billing_city,billing_postcode,billing_state',
|
||||
'billing_state' => 'required_with:billing_address,billing_city,billing_postcode,billing_country',
|
||||
], [
|
||||
'firstname.required' => __('validation.custom_messages.firstname_required'),
|
||||
'surname.required' => __('validation.custom_messages.surname_required'),
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid'),
|
||||
'phone.required' => __('validation.custom_messages.phone_required'),
|
||||
|
||||
'shipping_address.required' => __('validation.custom_messages.shipping_address_required'),
|
||||
'shipping_city.required' => __('validation.custom_messages.shipping_city_required'),
|
||||
'shipping_postcode.required' => __('validation.custom_messages.shipping_postcode_required'),
|
||||
'shipping_country.required' => __('validation.custom_messages.shipping_country_required'),
|
||||
'shipping_state.required' => __('validation.custom_messages.shipping_state_required'),
|
||||
|
||||
'billing_address.required' => __('validation.custom_messages.billing_address_required'),
|
||||
'billing_city.required' => __('validation.custom_messages.billing_city_required'),
|
||||
'billing_postcode.required' => __('validation.custom_messages.billing_postcode_required'),
|
||||
'billing_country.required' => __('validation.custom_messages.billing_country_required'),
|
||||
'billing_state.required' => __('validation.custom_messages.billing_state_required'),
|
||||
]);
|
||||
|
||||
User::create($request->all());
|
||||
|
||||
session()->flash('message', 'User has been created');
|
||||
session()->flash('message-title', 'User created');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.user.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(User $user)
|
||||
{
|
||||
return view('admin.user.edit', compact('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, User $user)
|
||||
{
|
||||
$request->validate([
|
||||
'firstname' => '',
|
||||
'surname' => '',
|
||||
'email' => ['email', Rule::unique('users')->ignore($user->id)],
|
||||
'phone' => '',
|
||||
|
||||
'shipping_address' => 'required_with:shipping_city,shipping_postcode,shipping_country,shipping_state',
|
||||
'shipping_city' => 'required_with:shipping_address,shipping_postcode,shipping_country,shipping_state',
|
||||
'shipping_postcode' => 'required_with:shipping_address,shipping_city,shipping_country,shipping_state',
|
||||
'shipping_country' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_state',
|
||||
'shipping_state' => 'required_with:shipping_address,shipping_city,shipping_postcode,shipping_country',
|
||||
|
||||
'billing_address' => 'required_with:billing_city,billing_postcode,billing_country,billing_state',
|
||||
'billing_city' => 'required_with:billing_address,billing_postcode,billing_country,billing_state',
|
||||
'billing_postcode' => 'required_with:billing_address,billing_city,billing_country,billing_state',
|
||||
'billing_country' => 'required_with:billing_address,billing_city,billing_postcode,billing_state',
|
||||
'billing_state' => 'required_with:billing_address,billing_city,billing_postcode,billing_country',
|
||||
], [
|
||||
'firstname.required' => __('validation.custom_messages.firstname_required'),
|
||||
'surname.required' => __('validation.custom_messages.surname_required'),
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid'),
|
||||
'phone.required' => __('validation.custom_messages.phone_required'),
|
||||
|
||||
'shipping_address.required' => __('validation.custom_messages.shipping_address_required'),
|
||||
'shipping_city.required' => __('validation.custom_messages.shipping_city_required'),
|
||||
'shipping_postcode.required' => __('validation.custom_messages.shipping_postcode_required'),
|
||||
'shipping_country.required' => __('validation.custom_messages.shipping_country_required'),
|
||||
'shipping_state.required' => __('validation.custom_messages.shipping_state_required'),
|
||||
|
||||
'billing_address.required' => __('validation.custom_messages.billing_address_required'),
|
||||
'billing_city.required' => __('validation.custom_messages.billing_city_required'),
|
||||
'billing_postcode.required' => __('validation.custom_messages.billing_postcode_required'),
|
||||
'billing_country.required' => __('validation.custom_messages.billing_country_required'),
|
||||
'billing_state.required' => __('validation.custom_messages.billing_state_required'),
|
||||
]);
|
||||
|
||||
$user->update($request->all());
|
||||
|
||||
session()->flash('message', 'User details have been updated');
|
||||
session()->flash('message-title', 'Details updated');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
if($user->id !== '1') {
|
||||
$user->delete();
|
||||
session()->flash('message', 'User has been deleted');
|
||||
session()->flash('message-title', 'User deleted');
|
||||
session()->flash('message-type', 'success');
|
||||
} else {
|
||||
session()->flash('message', 'You cannot delete the main admin user');
|
||||
session()->flash('message-title', 'User not deleted');
|
||||
session()->flash('message-type', 'error');
|
||||
}
|
||||
|
||||
return redirect()->route('admin.user.index');
|
||||
}
|
||||
}
|
||||
209
app/Http/Controllers/WorkshopController.php
Normal file
209
app/Http/Controllers/WorkshopController.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Workshop;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class WorkshopController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$query = Workshop::query();
|
||||
$query = $query->where('starts_at', '>=', Carbon::now()->subDays(8))
|
||||
->orderBy('starts_at', 'asc');
|
||||
|
||||
$workshops = $query->paginate(12);
|
||||
return view('workshop.index', [
|
||||
'workshops' => $workshops
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function past_index()
|
||||
{
|
||||
$query = Workshop::query();
|
||||
$query = $query->where('starts_at', '<', Carbon::now())
|
||||
->orderBy('starts_at', 'desc');
|
||||
|
||||
$workshops = $query->paginate(12);
|
||||
return view('workshop.index', [
|
||||
'workshops' => $workshops
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function admin_index(Request $request)
|
||||
{
|
||||
$query = Workshop::query();
|
||||
|
||||
if($request->has('search')) {
|
||||
$query->where('title', 'like', '%' . $request->search . '%');
|
||||
$query->orWhere('content', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
$workshops = $query->orderBy('starts_at', 'desc')->paginate(12)->onEachSide(1);
|
||||
|
||||
return view('admin.workshop.index', [
|
||||
'workshops' => $workshops
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function admin_create()
|
||||
{
|
||||
return view('admin.workshop.edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function admin_store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'content' => 'required',
|
||||
'starts_at' => 'required',
|
||||
'ends_at' => 'required|after:starts_at',
|
||||
'publish_at' => 'required',
|
||||
'closes_at' => 'required',
|
||||
'status' => 'required',
|
||||
'hero_media_name' => 'required|exists:media,name',
|
||||
'registration_data' => 'required_unless:registration,none',
|
||||
], [
|
||||
'title.required' => __('validation.custom_messages.title_required'),
|
||||
'content.required' => __('validation.custom_messages.content_required'),
|
||||
'starts_at.required' => __('validation.custom_messages.starts_at_required'),
|
||||
'ends_at.required' => __('validation.custom_messages.ends_at_required'),
|
||||
'ends_at.after' => __('validation.custom_messages.ends_at_after'),
|
||||
'publish_at.required' => __('validation.custom_messages.publish_at_required'),
|
||||
'closes_at.required' => __('validation.custom_messages.closes_at_required'),
|
||||
'status.required' => __('validation.custom_messages.status_required'),
|
||||
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
|
||||
'hero_media_name.exists' => __('validation.custom_messages.hero_media_name_exists'),
|
||||
'registration_data.required_unless' => __('validation.custom_messages.registration_data_required_unless'),
|
||||
]);
|
||||
|
||||
$workshopData = $request->all();
|
||||
$workshopData['user_id'] = auth()->user()->id;
|
||||
|
||||
if($workshopData['status'] === 'open' && Carbon::parse($workshopData['starts_at'])->lt(Carbon::now())) {
|
||||
$workshopData['status'] = 'closed';
|
||||
}
|
||||
|
||||
$workshop = Workshop::create($workshopData);
|
||||
$workshop->updateFiles($request->input('files'));
|
||||
|
||||
session()->flash('message', 'Workshop has been created');
|
||||
session()->flash('message-title', 'Workshop created');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.workshop.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Workshop $workshop)
|
||||
{
|
||||
if(!auth()->user()?->admin && $workshop->status == 'draft') {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
return view('workshop.show', ['workshop' => $workshop]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function admin_edit(Workshop $workshop)
|
||||
{
|
||||
return view('admin.workshop.edit', ['workshop' => $workshop]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function admin_update(Request $request, Workshop $workshop)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'content' => 'required',
|
||||
'starts_at' => 'required',
|
||||
'ends_at' => 'required|after:starts_at',
|
||||
'publish_at' => 'required',
|
||||
'closes_at' => 'required',
|
||||
'status' => 'required',
|
||||
'hero_media_name' => 'required|exists:media,name',
|
||||
'registration_data' => 'required_unless:registration,none',
|
||||
], [
|
||||
'title.required' => __('validation.custom_messages.title_required'),
|
||||
'content.required' => __('validation.custom_messages.content_required'),
|
||||
'starts_at.required' => __('validation.custom_messages.starts_at_required'),
|
||||
'ends_at.required' => __('validation.custom_messages.ends_at_required'),
|
||||
'ends_at.after' => __('validation.custom_messages.ends_at_after'),
|
||||
'publish_at.required' => __('validation.custom_messages.publish_at_required'),
|
||||
'closes_at.required' => __('validation.custom_messages.closes_at_required'),
|
||||
'status.required' => __('validation.custom_messages.status_required'),
|
||||
'hero_media_name.required' => __('validation.custom_messages.hero_media_name_required'),
|
||||
'hero_media_name.exists' => __('validation.custom_messages.hero_media_name_exists'),
|
||||
'registration_data.required_unless' => __('validation.custom_messages.registration_data_required_unless'),
|
||||
]);
|
||||
|
||||
$workshopData = $request->all();
|
||||
if($workshopData['status'] === 'open' && Carbon::parse($workshopData['starts_at'])->lt(Carbon::now())) {
|
||||
$workshopData['status'] = 'closed';
|
||||
}
|
||||
|
||||
$workshop->update($workshopData);
|
||||
$workshop->updateFiles($request->input('files'));
|
||||
|
||||
session()->flash('message', 'Workshop has been updated');
|
||||
session()->flash('message-title', 'Workshop updated');
|
||||
session()->flash('message-type', 'success');
|
||||
return redirect()->route('admin.workshop.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function admin_destroy(Workshop $workshop)
|
||||
{
|
||||
$workshop->delete();
|
||||
session()->flash('message', 'Workshop has been deleted');
|
||||
session()->flash('message-title', 'Workshop deleted');
|
||||
session()->flash('message-type', 'danger');
|
||||
|
||||
return redirect()->route('admin.workshop.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the specified resource.
|
||||
*/
|
||||
public function admin_duplicate(Workshop $workshop)
|
||||
{
|
||||
$newWorkshop = $workshop->replicate();
|
||||
$newWorkshop->title = $newWorkshop->title . ' (copy)';
|
||||
$newWorkshop->status = 'draft';
|
||||
$newWorkshop->save();
|
||||
|
||||
foreach($workshop->files as $file) {
|
||||
$newWorkshop->files()->attach($file->name);
|
||||
}
|
||||
|
||||
session()->flash('message', 'Workshop has been duplicated');
|
||||
session()->flash('message-title', 'Workshop duplicated');
|
||||
session()->flash('message-type', 'success');
|
||||
|
||||
return redirect()->route('admin.workshop.edit', $newWorkshop);
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
// \App\Http\Middleware\TrimStrings::class,
|
||||
// \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
// \App\Http\Middleware\ForceJsonResponse::class,
|
||||
'useSanctumGuard',
|
||||
\App\Http\Middleware\LogRequest::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'useSanctumGuard' => \App\Http\Middleware\UseSanctumGuard::class
|
||||
];
|
||||
}
|
||||
34
app/Http/Middleware/Admin.php
Normal file
34
app/Http/Middleware/Admin.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\User;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Admin
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
/* @var User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
if ($user) {
|
||||
if($user->admin == 1) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
abort(403, 'Forbidden');
|
||||
}
|
||||
|
||||
session()->put('url.intended', url()->current());
|
||||
return redirect()->route('login');
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*
|
||||
* @param mixed $request Request.
|
||||
* @return string|null
|
||||
*/
|
||||
protected function redirectTo(mixed $request)
|
||||
{
|
||||
if ($request->expectsJson() === false) {
|
||||
return route('login');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ForceJsonResponse
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$request->headers->set('Accept', 'application/json');
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Analytics;
|
||||
|
||||
class LogRequest
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
// Make it an after middleware
|
||||
$response = $next($request);
|
||||
|
||||
try {
|
||||
Analytics::createWithSession([
|
||||
'type' => 'apirequest',
|
||||
'attribute' => $request->path(),
|
||||
'useragent' => $request->userAgent(),
|
||||
'ip' => $request->ip(),
|
||||
]);
|
||||
|
||||
return $response;
|
||||
} catch (\Error $e) {
|
||||
report($e);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||
|
||||
class PreventRequestsDuringMaintenance extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request Request.
|
||||
* @param Closure(Request): (Response|RedirectResponse) $next Next.
|
||||
* @param string|null ...$guards Guards.
|
||||
* @return Response|RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$guards)
|
||||
{
|
||||
$guards = empty($guards) === true ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check() === true) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts()
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
// @codingStandardsIgnoreStart
|
||||
protected $headers = (Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB);
|
||||
// @codingStandardsIgnoreEnd
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UseSanctumGuard
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
Auth::shouldUse('sanctum');
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AnalyticsRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to POST requests.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'type' => 'required|string',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to PUT request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function putRules()
|
||||
{
|
||||
return [
|
||||
'type' => 'string',
|
||||
'useragent' => 'string',
|
||||
'ip' => 'ipv4|ipv6',
|
||||
'session' => 'number',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ArticleRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to POST requests.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'slug' => 'required|string|min:6|unique:articles',
|
||||
'title' => 'required|string|min:6|max:255',
|
||||
'publish_at' => 'required|date',
|
||||
'user_id' => 'required|uuid|exists:users,id',
|
||||
'content' => 'required|string|min:6',
|
||||
'hero' => 'required|uuid|exists:media,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to PUT request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function putRules()
|
||||
{
|
||||
return [
|
||||
'slug' => [
|
||||
'string',
|
||||
'min:6',
|
||||
Rule::unique('articles')->ignoreModel($this->article),
|
||||
],
|
||||
'title' => 'string|min:6|max:255',
|
||||
'publish_at' => 'date',
|
||||
'user_id' => 'uuid|exists:users,id',
|
||||
'content' => 'string|min:6',
|
||||
'hero' => 'uuid|exists:media,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AuthLoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|string|min:6|max:255',
|
||||
'password' => 'required|string|min:6',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class BaseRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
if (request()->isMethod('post') === true && method_exists($this, 'postAuthorize') === true) {
|
||||
return $this->postAuthorize();
|
||||
} elseif ((request()->isMethod('put') === true || request()->isMethod('patch') === true) && method_exists($this, 'putAuthorize') === true) {
|
||||
return $this->putAuthorize();
|
||||
} elseif (request()->isMethod('delete') === true && method_exists($this, 'destroyAuthorize') === true) {
|
||||
return $this->deleteAuthorize();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if (method_exists($this, 'baseRules') === true) {
|
||||
$rules = $this->baseRules();
|
||||
}
|
||||
|
||||
if (method_exists($this, 'postRules') === true && request()->isMethod('post') === true) {
|
||||
$rules = $this->mergeRules($rules, $this->postRules());
|
||||
} elseif (method_exists($this, 'putRules') === true && (request()->isMethod('put') === true || request()->isMethod('patch') === true)) {
|
||||
$rules = $this->mergeRules($rules, $this->putRules());
|
||||
} elseif (method_exists($this, 'destroyRules') === true && request()->isMethod('delete') === true) {
|
||||
$rules = $this->mergeRules($rules, $this->destroyRules());
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two collections of rules.
|
||||
*
|
||||
* @param array $collection1 The first collection of rules.
|
||||
* @param array $collection2 The second collection of rules to merge.
|
||||
* @return array
|
||||
*/
|
||||
private function mergeRules(array $collection1, array $collection2)
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
foreach ($collection1 as $key => $ruleset) {
|
||||
if (array_key_exists($key, $collection2) === true) {
|
||||
if (is_string($collection1[$key]) === true && is_string($collection2[$key]) === true) {
|
||||
$rules[$key] = $collection1[$key] . '|' . $collection2[$key];
|
||||
} else {
|
||||
$key_ruleset = [];
|
||||
|
||||
if (is_array($collection1[$key]) === true) {
|
||||
$key_ruleset = $collection1[$key];
|
||||
} elseif (is_string($collection1[$key]) === true) {
|
||||
$key_ruleset = explode('|', $collection1[$key]);
|
||||
}
|
||||
|
||||
if (is_array($collection2[$key]) === true) {
|
||||
$key_ruleset = array_merge($key_ruleset, $collection2[$key]);
|
||||
} elseif (is_string($collection2[$key]) === true) {
|
||||
$key_ruleset = array_merge($key_ruleset, explode('|', $collection2[$key]));
|
||||
}
|
||||
|
||||
if (count($key_ruleset) > 0) {
|
||||
$rules[$key] = $key_ruleset;
|
||||
}
|
||||
}//end if
|
||||
} else {
|
||||
$rules[$key] = $ruleset;
|
||||
}//end if
|
||||
}//end foreach
|
||||
|
||||
foreach ($collection2 as $key => $ruleset) {
|
||||
if (array_key_exists($key, $rules) === false) {
|
||||
$rules[$key] = $collection2[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ContactSendRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'name' => 'required|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'content' => 'required|max:2000',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class EventRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Apply the base rules to this request
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function baseRules()
|
||||
{
|
||||
return [
|
||||
'title' => 'min:6',
|
||||
'location' => [
|
||||
Rule::in(['online', 'physical']),
|
||||
],
|
||||
'address' => 'string|nullable',
|
||||
'start_at' => 'date',
|
||||
'end_at' => 'date|after:start_date',
|
||||
'publish_at' => 'date|nullable',
|
||||
'status' => [
|
||||
Rule::in(['draft', 'soon', 'open', 'closed', 'cancelled']),
|
||||
],
|
||||
'registration_type' => [
|
||||
Rule::in(['none', 'email', 'link', 'message']),
|
||||
],
|
||||
'registration_data' => [
|
||||
Rule::when(strcasecmp('email', $this->attributes->get('registration_type')) == 0, 'required|email'),
|
||||
Rule::when(strcasecmp('link', $this->attributes->get('registration_type')) == 0, 'required|url'),
|
||||
Rule::when(strcasecmp('message', $this->attributes->get('registration_type')) == 0, 'required|message'),
|
||||
],
|
||||
'hero' => 'uuid|exists:media,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the additional POST base rules to this request
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function postRules()
|
||||
{
|
||||
return [
|
||||
'title' => 'required',
|
||||
'location' => 'required',
|
||||
'address' => 'required_if:location,physical',
|
||||
'start_at' => 'required',
|
||||
'end_at' => 'required',
|
||||
'status' => 'required',
|
||||
'registration_type' => 'required',
|
||||
'hero' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
class MediaRequest extends BaseRequest
|
||||
{
|
||||
/* empty */
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ShortlinkRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Apply the additional POST base rules to this request
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'code' => 'required|string|max:255|min:2|unique:shortlinks',
|
||||
'url' => 'required|string|max:255|min:2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to PUT request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function putRules()
|
||||
{
|
||||
$shortlink = $this->route('shortlink');
|
||||
|
||||
return [
|
||||
'code' => ['required', 'string', 'max:255', 'min:2', Rule::unique('shortlinks')->ignore($shortlink->id)],
|
||||
'url' => 'required|string|max:255|min:2',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
|
||||
class SubscriptionRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email|unique:subscriptions',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function destroyRules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom error messages.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'email.unique' => 'This email address has already subscribed',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserForgotPasswordRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|exists:users,email',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Uniqueish;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserRegisterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'display_name' => ['required','string','max:255', new Uniqueish('users')],
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'password' => 'required|string|min:8',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\RequiredIfAny;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\RequiredIf;
|
||||
use App\Rules\Uniqueish;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class UserRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Apply the additional POST base rules to this request
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isAdminUser = $user->hasPermission('admin/users');
|
||||
|
||||
return [
|
||||
'first_name' => ($isAdminUser === true ? 'required_with:last_name,display_name,phone' : 'required') . '|string|max:255|min:2',
|
||||
'last_name' => ($isAdminUser === true ? 'required_with:first_name,display_name,phone' : 'required') . '|string|max:255|min:2',
|
||||
'display_name' => [
|
||||
$isAdminUser === true ? 'required_with:first_name,last_name,phone' : 'required',
|
||||
'string',
|
||||
'max:255',
|
||||
new Uniqueish('users')
|
||||
],
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'phone' => ['string', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'email_verified_at' => 'date'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to PUT request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function putRules()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$ruleUser = $this->route('user');
|
||||
$isAdminUser = $user->hasPermission('admin/users');
|
||||
|
||||
$requiredIfFieldsPresent = function (array $fields) use ($ruleUser): RequiredIf {
|
||||
return new RequiredIf(function () use ($fields, $ruleUser) {
|
||||
$input = $this->all();
|
||||
$values = Arr::only($input, $fields);
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
if ($value !== null && $value !== '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$fields = array_diff($fields, array_keys($values));
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if ($ruleUser->$field !== '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
return [
|
||||
'first_name' => [
|
||||
'sometimes',
|
||||
$isAdminUser === true ? $requiredIfFieldsPresent(['last_name', 'display_name', 'phone']) : 'required',
|
||||
'string',
|
||||
'between:2,255',
|
||||
],
|
||||
'last_name' => [
|
||||
'sometimes',
|
||||
$isAdminUser === true ? $requiredIfFieldsPresent(['first_name', 'last_name', 'phone']) : 'required',
|
||||
'string',
|
||||
'between:2,255',
|
||||
],
|
||||
'display_name' => [
|
||||
'sometimes',
|
||||
$isAdminUser === true ? $requiredIfFieldsPresent(['first_name', 'display_name', 'phone']) : 'required',
|
||||
'string',
|
||||
'between:2,255',
|
||||
(new Uniqueish('users', 'display_name'))->ignore($ruleUser->id)
|
||||
],
|
||||
'email' => [
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($ruleUser->id)->when(
|
||||
$this->email !== $ruleUser->email,
|
||||
function ($query) {
|
||||
return $query->where('email', $this->email);
|
||||
}
|
||||
),
|
||||
],
|
||||
'phone' => ['nullable', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'password' => "nullable|string|min:8"
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserResendVerifyEmailRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|exists:users,email',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserResetPasswordRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'code' => 'required|digits:6',
|
||||
'password' => 'required|string|min:8',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserVerifyEmailRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'code' => 'required|digits:6',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
}
|
||||
207
app/Jobs/Media/GenerateVariants.php
Normal file
207
app/Jobs/Media/GenerateVariants.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Media;
|
||||
|
||||
use App\Models\Media;
|
||||
use App\Helpers;
|
||||
use FFMpeg\Coordinate\TimeCode;
|
||||
use FFMpeg\FFMpeg;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Drivers\Imagick\Driver;
|
||||
|
||||
class GenerateVariants implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Media ID
|
||||
*
|
||||
* @var String
|
||||
*/
|
||||
public $media_name;
|
||||
|
||||
/**
|
||||
* Overwrite existing
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $overwrite;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Media $media The media to process
|
||||
*/
|
||||
public function __construct(Media $media, bool $overwrite = true)
|
||||
{
|
||||
$this->media_name = $media->name;
|
||||
$this->overwrite = $overwrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array<int, object>
|
||||
*/
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->media_name)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$media = Media::find($this->media_name);
|
||||
if ($media === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(Storage::disk('media')->exists($media->hash) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$variantData = $media->getVariantTypes($matchingMimeType);
|
||||
if(count($variantData) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$temp = $media->getAsTempFile();
|
||||
if($temp === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempDir = pathinfo($temp, PATHINFO_DIRNAME);
|
||||
$media->deleteAllVariants();
|
||||
|
||||
/* Images */
|
||||
if($matchingMimeType === 'image/*') {
|
||||
$manager = new ImageManager(new Driver());
|
||||
$image = $manager->read($temp);
|
||||
|
||||
$isPortrait = $image->height() > $image->width();
|
||||
|
||||
foreach ($variantData as $variantName => $size) {
|
||||
$image = $manager->read($temp);
|
||||
|
||||
if($isPortrait === true) {
|
||||
$width = $size['height'];
|
||||
$height = $size['width'];
|
||||
} else {
|
||||
$width = $size['width'];
|
||||
$height = $size['height'];
|
||||
}
|
||||
|
||||
if($variantName !== 'scaled' && ($image->height() < $height || $image->width() < $width)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$image->scaleDown($width, $height);
|
||||
$variantFile = $tempDir . '/' . $media->hash . '-' . $variantName . '.webp';
|
||||
$image->save($variantFile, quality: 75);
|
||||
|
||||
$media->addVariant($variantName, 'image/webp', 'webp', $variantFile);
|
||||
unset($variantFile);
|
||||
}//end foreach
|
||||
} else if($matchingMimeType === 'text/plain') {
|
||||
/* Text */
|
||||
$width = $variantData['thumbnail']['width'];
|
||||
$height = $variantData['thumbnail']['height'];
|
||||
|
||||
$manager = new ImageManager(new Driver());
|
||||
$image = $manager->create($width, $height)->fill('fff');
|
||||
|
||||
// Read the first few lines of the text file
|
||||
$numLines = 5;
|
||||
$text = file_get_contents($temp);
|
||||
$lines = explode("\n", $text);
|
||||
$previewText = implode("\n", array_slice($lines, 0, $numLines));
|
||||
|
||||
// Center the text on the image
|
||||
$fontSize = 8;
|
||||
$textColor = '#000000'; // Black text color
|
||||
|
||||
// Calculate the position to start drawing the text
|
||||
$x = 10; // Left padding
|
||||
$y = 10; // Top padding
|
||||
|
||||
// Draw the text on the canvas with text wrapping
|
||||
$lines = explode("\n", wordwrap($previewText, 30, "\n", true));
|
||||
foreach ($lines as $line) {
|
||||
$image->text($line, $x, $y, function ($font) use ($fontSize, $textColor) {
|
||||
$font->file(1);
|
||||
$font->size($fontSize);
|
||||
$font->color($textColor);
|
||||
});
|
||||
|
||||
// Move to the next line
|
||||
$y += ($fontSize + 4); // Add some vertical spacing between lines (adjust as needed)
|
||||
}
|
||||
|
||||
$variantFile = $tempDir . '/' . $media->hash . '-thumbnail.webp';
|
||||
$image->save($variantFile, quality: 75);
|
||||
$media->addVariant('thumbnail', 'image/webp', 'webp', $variantFile);
|
||||
unset($variantFile);
|
||||
|
||||
} else if($matchingMimeType === 'application/pdf') {
|
||||
/* PDF */
|
||||
$width = $variantData['thumbnail']['width'];
|
||||
$height = $variantData['thumbnail']['height'];
|
||||
|
||||
$manager = new ImageManager(new Driver());
|
||||
|
||||
$imagick = new \Imagick();
|
||||
$imagick->readImage($temp . '[0]'); // Read the first page of the PDF
|
||||
$imagick->setImageFormat('png');
|
||||
|
||||
$image = $manager->read($imagick);
|
||||
$image->scaleDown($width, $height);
|
||||
|
||||
$variantFile = $tempDir . '/' . $media->hash . '-thumbnail.webp';
|
||||
$image->save($variantFile, quality: 75);
|
||||
$media->addVariant('thumbnail', 'image/webp', 'webp', $variantFile);
|
||||
unset($variantFile);
|
||||
|
||||
} else if($matchingMimeType === 'video/*') {
|
||||
/* Video */
|
||||
$tempImage = $tempDir . '/' . $media->hash . '-temp-frame.jpg';
|
||||
$variantFile = $tempDir . '/' . $media->hash . '-thumbnail.webp';
|
||||
|
||||
try {
|
||||
$ffmpeg = FFMpeg::create();
|
||||
$video = $ffmpeg->open($temp);
|
||||
$frame = $video->frame(TimeCode::fromSeconds(5));
|
||||
$frame->save($tempImage);
|
||||
|
||||
$width = $variantData['thumbnail']['width'];
|
||||
$height = $variantData['thumbnail']['height'];
|
||||
|
||||
$manager = new ImageManager(new Driver());
|
||||
$image = $manager->read($tempImage);
|
||||
$image->scaleDown($width, $height);
|
||||
$image->save($variantFile, quality: 75);
|
||||
|
||||
$media->addVariant('thumbnail', 'image/webp', 'webp', $variantFile);
|
||||
unset($variantFile);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
|
||||
if(file_exists($tempImage)) {
|
||||
unlink($tempImage);
|
||||
}
|
||||
}
|
||||
|
||||
$media->status = 'ready';
|
||||
$media->save();
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Media;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MoveMediaJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Media item
|
||||
*
|
||||
* @var Media
|
||||
*/
|
||||
public $media;
|
||||
|
||||
/**
|
||||
* New storage ID
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $newStorage;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Media $media The media model.
|
||||
* @param string $newStorage The new storage ID.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Media $media, string $newStorage)
|
||||
{
|
||||
$this->media = $media;
|
||||
$this->newStorage = $newStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Don't continue if the media is already on the new storage disk
|
||||
if ($this->media->storage === $this->newStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->media->status = 'Moving file';
|
||||
$this->media->save();
|
||||
|
||||
$files = ["/{$this->media->name}"];
|
||||
if (empty($this->media->variants) === false) {
|
||||
foreach ($this->media->variants as $variant => $name) {
|
||||
$files[] = "/{$name}";
|
||||
}
|
||||
}
|
||||
|
||||
$this->media->invalidateCFCache();
|
||||
|
||||
// Move the files from the old storage disk to the new storage disk
|
||||
foreach ($files as $file) {
|
||||
Storage::disk($this->newStorage)->put($file, Storage::disk($this->media->storage)->get($file));
|
||||
Storage::disk($this->media->storage)->delete($file);
|
||||
}
|
||||
|
||||
// Update the media model with the new storage and save it to the database
|
||||
$this->media->storage = $this->newStorage;
|
||||
$this->media->status = 'OK';
|
||||
$this->media->save();
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,19 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\SentEmail;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendEmailJob implements ShouldQueue
|
||||
class SendEmail implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Mail to receipt
|
||||
@@ -31,7 +30,6 @@ class SendEmailJob implements ShouldQueue
|
||||
*/
|
||||
public $mailable;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@@ -50,8 +48,20 @@ class SendEmailJob implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): void
|
||||
{
|
||||
// Record sent email
|
||||
$sentEmail = SentEmail::create([
|
||||
'recipient' => $this->to,
|
||||
'mailable_class' => get_class($this->mailable)
|
||||
]);
|
||||
|
||||
// Add unsubscribe link if mailable supports it
|
||||
if (method_exists($this->mailable, 'withUnsubscribeLink')) {
|
||||
$unsubscribeLink = route('unsubscribe', ['email' => $sentEmail->id]);
|
||||
$this->mailable->withUnsubscribeLink($unsubscribeLink);
|
||||
}
|
||||
|
||||
Mail::to($this->to)->send($this->mailable);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Media;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use SplFileInfo;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use Spatie\ImageOptimizer\OptimizerChainFactory;
|
||||
|
||||
class StoreUploadedFileJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Media item
|
||||
*
|
||||
* @var Media
|
||||
*/
|
||||
protected $media;
|
||||
|
||||
/**
|
||||
* Uploaded file item
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $uploadedFilePath;
|
||||
|
||||
/**
|
||||
* Replace existing files
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $replaceExisting;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Media $media The media model.
|
||||
* @param string $filePath The uploaded file.
|
||||
* @param boolean $replaceExisting Replace existing files.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Media $media, string $filePath, bool $replaceExisting = true)
|
||||
{
|
||||
$this->media = $media;
|
||||
$this->uploadedFilePath = $filePath;
|
||||
$this->replaceExisting = $replaceExisting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$storageDisk = $this->media->storage;
|
||||
$fileName = $this->media->name;
|
||||
|
||||
try {
|
||||
$this->media->status = "Uploading to CDN";
|
||||
$this->media->save();
|
||||
|
||||
if (strlen($this->uploadedFilePath) > 0) {
|
||||
if (Storage::disk($storageDisk)->exists($fileName) === false || $this->replaceExisting === true) {
|
||||
Storage::disk($storageDisk)->putFileAs('/', new SplFileInfo($this->uploadedFilePath), $fileName);
|
||||
Log::info("uploading file {$storageDisk} / {$fileName} / {$this->uploadedFilePath}");
|
||||
} else {
|
||||
Log::info("file {$fileName} already exists in {$storageDisk} / {$this->uploadedFilePath}. Not replacing file and using local {$fileName} for variants.");
|
||||
}
|
||||
} else {
|
||||
if (Storage::disk($storageDisk)->exists($fileName) === true) {
|
||||
Log::info("file {$fileName} already exists in {$storageDisk} / {$this->uploadedFilePath}. No local {$fileName} for variants, downloading from CDN.");
|
||||
$readStream = Storage::disk($storageDisk)->readStream($fileName);
|
||||
$tempFilePath = tempnam(sys_get_temp_dir(), 'download-');
|
||||
$writeStream = fopen($tempFilePath, 'w');
|
||||
while (feof($readStream) !== true) {
|
||||
fwrite($writeStream, fread($readStream, 8192));
|
||||
}
|
||||
fclose($readStream);
|
||||
fclose($writeStream);
|
||||
$this->uploadedFilePath = $tempFilePath;
|
||||
} else {
|
||||
$errorStr = "cannot upload file {$storageDisk} / {$fileName} / {$this->uploadedFilePath} as temp file is empty";
|
||||
Log::info($errorStr);
|
||||
throw new \Exception($errorStr);
|
||||
}
|
||||
}//end if
|
||||
|
||||
if (strpos($this->media->mime_type, 'image/') === 0) {
|
||||
$this->media->status = "Optimizing image";
|
||||
$this->media->save();
|
||||
|
||||
// Generate additional image sizes
|
||||
$sizes = Media::getTypeVariants('image');
|
||||
|
||||
$originalImage = Image::make($this->uploadedFilePath);
|
||||
|
||||
$dimensions = [$originalImage->getWidth(), $originalImage->getHeight()];
|
||||
$this->media->dimensions = implode('x', $dimensions);
|
||||
|
||||
foreach ($sizes as $variantName => $size) {
|
||||
$postfix = "{$size['width']}x{$size['height']}";
|
||||
if ($variantName === 'scaled') {
|
||||
$postfix = 'scaled';
|
||||
}
|
||||
|
||||
if (is_array($this->media->variants) === true && array_key_exists($postfix, $this->media->variants) === true && Storage::disk($storageDisk)->exists($this->media->variants[$postfix]) === true && $this->replaceExisting === true) {
|
||||
Storage::disk($storageDisk)->delete($this->media->variants[$postfix]);
|
||||
}
|
||||
|
||||
$newFilename = pathinfo($this->media->name, PATHINFO_FILENAME) . "-$postfix.webp";
|
||||
|
||||
if (Storage::disk($storageDisk)->exists($newFilename) === false || $this->replaceExisting === true) {
|
||||
// Get the largest available variant
|
||||
if ($dimensions[0] >= $size['width'] && $dimensions[1] >= $size['height']) {
|
||||
// Store the variant in the variants array
|
||||
$variants[$variantName] = $newFilename;
|
||||
|
||||
// Resize the image to the variant size if its dimensions are greater than the specified size
|
||||
$image = clone $originalImage;
|
||||
|
||||
$imageSize = $image->getSize();
|
||||
if ($imageSize->getWidth() > $size['width'] || $imageSize->getHeight() > $size['height']) {
|
||||
$image->resize($size['width'], $size['height'], function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
$constraint->upsize();
|
||||
});
|
||||
$image->resizeCanvas($size['width'], $size['height'], 'center', false, '#FFFFFF');
|
||||
}
|
||||
|
||||
// Optimize and store image
|
||||
$tempImagePath = tempnam(sys_get_temp_dir(), 'optimize');
|
||||
$image->encode('webp', 75)->save($tempImagePath);
|
||||
Storage::disk($storageDisk)->putFileAs('/', new SplFileInfo($tempImagePath), $newFilename);
|
||||
unlink($tempImagePath);
|
||||
}//end if
|
||||
} else {
|
||||
Log::info("variant {$variantName} already exists for file {$fileName}");
|
||||
}//end if
|
||||
}//end foreach
|
||||
|
||||
// Set missing variants to the largest available variant
|
||||
foreach ($sizes as $variantName => $size) {
|
||||
if (isset($variants[$variantName]) === false) {
|
||||
$variants[$variantName] = $this->media->name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->media->variants = $variants;
|
||||
}//end if
|
||||
|
||||
if (strlen($this->uploadedFilePath) > 0) {
|
||||
unlink($this->uploadedFilePath);
|
||||
}
|
||||
|
||||
$this->media->status = 'OK';
|
||||
$this->media->save();
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e->getMessage());
|
||||
$this->media->status = "Failed";
|
||||
$this->media->save();
|
||||
$this->fail($e);
|
||||
}//end try
|
||||
}
|
||||
}
|
||||
104
app/Livewire/EmailSubscribe.php
Normal file
104
app/Livewire/EmailSubscribe.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Jobs\SendEmail;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Component;
|
||||
use App\Models\EmailSubscriptions;
|
||||
use App\Mail\UserWelcome;
|
||||
|
||||
class EmailSubscribe extends Component
|
||||
{
|
||||
public string $email = '';
|
||||
public bool $success = false;
|
||||
public string $message = '';
|
||||
public string $trap = '';
|
||||
public int $renderedAt; // unix timestamp
|
||||
|
||||
protected $rules = [
|
||||
'email' => 'required|email|max:255',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->renderedAt = now()->timestamp;
|
||||
}
|
||||
|
||||
public function subscribe(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
// 1. Honeypot - if this hidden field is filled, treat as success but do nothing
|
||||
if (! empty($this->trap)) {
|
||||
$this->reset(['email', 'trap']);
|
||||
$this->success = true;
|
||||
$this->message = 'Thanks, you have been subscribed to our newsletter.';
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Block submits in first 10 seconds after render
|
||||
if (now()->timestamp - $this->renderedAt < 4) {
|
||||
$this->success = false;
|
||||
$this->message = 'That was a bit quick. Please wait a few seconds and try again.';
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Enforce 30 seconds between attempts per session
|
||||
$lastAttempt = session('subscribe_last_attempt'); // int timestamp or null
|
||||
if (! is_int($lastAttempt)) {
|
||||
$lastAttempt = null;
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
if ($lastAttempt && ($now - $lastAttempt) < 20) {
|
||||
$this->success = false;
|
||||
$this->message = 'Please wait a little before trying again.';
|
||||
return;
|
||||
}
|
||||
|
||||
session(['subscribe_last_attempt' => $now]);
|
||||
|
||||
// 4. Limit to 5 attempts per session (your existing logic)
|
||||
$attempts = session('subscribe_attempts', 0);
|
||||
if ($attempts >= 5) {
|
||||
$this->success = false;
|
||||
$this->message = 'Too many attempts. Please try again in a little while.';
|
||||
return;
|
||||
}
|
||||
session(['subscribe_attempts' => $attempts + 1]);
|
||||
|
||||
// Look up existing subscription by email
|
||||
$subscription = EmailSubscriptions::where('email', $this->email)->first();
|
||||
|
||||
// If already confirmed, do not create a new record or resend confirmation
|
||||
if ($subscription && $subscription->confirmed) {
|
||||
// Optionally you could set a different flag or message here
|
||||
$this->success = false;
|
||||
$this->message = 'That email is already subscribed to our newsletter.';
|
||||
} else {
|
||||
// If no subscription exists, create a new unconfirmed one
|
||||
if (!$subscription) {
|
||||
$subscription = EmailSubscriptions::create([
|
||||
'email' => $this->email,
|
||||
'confirmed' => Carbon::now()
|
||||
]);
|
||||
|
||||
$subscription->save();
|
||||
}
|
||||
|
||||
dispatch(new SendEmail($subscription->email, new UserWelcome($subscription->email)))->onQueue('mail');
|
||||
|
||||
$this->success = true;
|
||||
$this->message = 'Thanks, you have been subscribed to our newsletter.';
|
||||
}
|
||||
|
||||
$this->reset(['email', 'trap']);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.email-subscribe');
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ChangeEmailVerify extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The user instance.
|
||||
*
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* The registration code.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* The new email address.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $new_email;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user The user the email applies to.
|
||||
* @param integer $code The action code.
|
||||
* @param string $new_email The new email address.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user, int $code, string $new_email)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->code = $code;
|
||||
$this->new_email = $new_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '👋🏻 Lets change your email!',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.change_email_verify',
|
||||
text: 'emails.user.change_email_verify_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ChangedEmail extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The user instance.
|
||||
*
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* The old email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $old_email;
|
||||
|
||||
/**
|
||||
* The new email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $new_email;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user The user the email applies to.
|
||||
* @param string $old_email The previous email address.
|
||||
* @param string $new_email The new email address.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user, string $old_email, string $new_email)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->old_email = $old_email;
|
||||
$this->new_email = $new_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '👍 Your email has been changed!',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.changed_email',
|
||||
text: 'emails.user.changed_email_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ChangedPassword extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The user instance.
|
||||
*
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user The user the email applies to.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '👍 Your password has been changed!',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.changed_password',
|
||||
text: 'emails.user.changed_password_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Contact extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The contact name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The contact email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* The contact content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param string $name The contact name.
|
||||
* @param string $email The contact email.
|
||||
* @param string $content The contact content.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $name, string $email, string $content)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->email = $email;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: config('contact.contact_subject'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.contact',
|
||||
text: 'emails.user.contact_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EmailVerify extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The user instance.
|
||||
*
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* The registration code.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $code;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user The user the email applies to.
|
||||
* @param integer $code The action code.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user, int $code)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '👋🏻 Welcome to STEMMechanics!',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.email_verify',
|
||||
text: 'emails.user.email_verify_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ForgotPassword extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The user
|
||||
*
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* The reset code
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $code;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param User $user The user the email applies to.
|
||||
* @param integer $code The action code.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(User $user, int $code)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '🤦 Forgot your password?',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.forgot_password',
|
||||
text: 'emails.user.forgot_password_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubscriptionConfirm extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The email address.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $email;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param string $email The email address.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '🗞️ You\'re on the mailing list!',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.subscription_confirm',
|
||||
text: 'emails.user.subscription_confirm_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubscriptionUnsubscribed extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The email address.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $email;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param string $email The email address.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'You have been unsubscribed',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.subscription_unsubscribed',
|
||||
text: 'emails.user.subscription_unsubscribed_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
58
app/Mail/UpcomingWorkshops.php
Normal file
58
app/Mail/UpcomingWorkshops.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Workshop;
|
||||
use App\Traits\HasUnsubscribeLink;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class UpcomingWorkshops extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels, HasUnsubscribeLink;
|
||||
|
||||
public $subject;
|
||||
public $email;
|
||||
public $workshops;
|
||||
|
||||
public function __construct($email, $subject = 'Upcoming Workshops 🌟')
|
||||
{
|
||||
$this->subject = $subject;
|
||||
$this->email = $email;
|
||||
$this->workshops = $this->getUpcomingWorkshops();
|
||||
}
|
||||
|
||||
private function getUpcomingWorkshops()
|
||||
{
|
||||
$startDate = Carbon::now()->addDays(3);
|
||||
$endDate = Carbon::now()->addDays(42);
|
||||
|
||||
return Workshop::select('workshops.*', 'locations.name as location_name')
|
||||
->join('locations', 'workshops.location_id', '=', 'locations.id')
|
||||
->whereIn('workshops.status', ['open','scheduled'])
|
||||
->whereBetween('workshops.starts_at', [$startDate, $endDate])
|
||||
->where('locations.name', 'not like', '%private%')
|
||||
->orderBy('locations.name')
|
||||
->orderBy('workshops.starts_at')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
// Bail if there are no upcoming workshops
|
||||
if ($this->workshops->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this
|
||||
->subject($this->subject)
|
||||
->markdown('emails.upcoming-workshops')
|
||||
->with([
|
||||
'email' => $this->email,
|
||||
'workshops' => $this->workshops,
|
||||
'unsubscribeLink' => $this->unsubscribeLink
|
||||
]);
|
||||
}
|
||||
}
|
||||
30
app/Mail/UserEmailUpdateConfirm.php
Normal file
30
app/Mail/UserEmailUpdateConfirm.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserEmailUpdateConfirm extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $email;
|
||||
|
||||
public function __construct($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Your STEMMechanics account has been updated 👍')
|
||||
->markdown('emails.email-update-confirm')
|
||||
->with([
|
||||
'email' => $this->email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user