Compare commits
614 Commits
shift-9188
...
inertiajs
| Author | SHA1 | Date | |
|---|---|---|---|
| 4514396818 | |||
| b028856eb5 | |||
| 1925b2ef0c | |||
| f2e84b63fa | |||
| ac6257ed6d | |||
| 2486dec824 | |||
| 04e6c0d0fc | |||
| b948c42fe2 | |||
| 5d7be1a482 | |||
| 4e81d06a6e | |||
| 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 |
2
.github/workflows/laravel.yml
vendored
2
.github/workflows/laravel.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: "8.1"
|
php-version: "8.2"
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Copy .env
|
- name: Copy .env
|
||||||
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
|
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
|
||||||
|
|||||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -237,11 +237,22 @@ dist/
|
|||||||
### This Project ###
|
### This Project ###
|
||||||
/public/uploads
|
/public/uploads
|
||||||
/public/build
|
/public/build
|
||||||
# /public/tinymce
|
|
||||||
*.key
|
*.key
|
||||||
|
|
||||||
|
### TinyMCE ###
|
||||||
|
/public/tinymce
|
||||||
|
!/public/tinymce/skins/ui/stemmech/
|
||||||
|
|
||||||
### Synk ###
|
### Synk ###
|
||||||
.dccache
|
.dccache
|
||||||
|
|
||||||
### TempCodeRunner ###
|
### TempCodeRunner ###
|
||||||
tempCodeRunnerFile.*
|
tempCodeRunnerFile.*
|
||||||
|
|
||||||
|
### PHPUnit ###
|
||||||
|
.phpunit.result.cache
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
### Codesniffer ###
|
||||||
|
phpcs.phar
|
||||||
|
phpcbf.phar
|
||||||
|
|||||||
14
.ls-lint.yml
Normal file
14
.ls-lint.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
ls:
|
||||||
|
resources/js/store:
|
||||||
|
.ts: pascalcase
|
||||||
|
"*":
|
||||||
|
.js: camelcase
|
||||||
|
.ts: camelcase
|
||||||
|
.vue: pascalcase
|
||||||
|
.dir: snakecase
|
||||||
|
.type.ts: camelcase
|
||||||
|
ignore:
|
||||||
|
- node_modules
|
||||||
|
- vendor
|
||||||
|
- public/build
|
||||||
|
- public/tinymce
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"tabWidth": 4
|
"tabWidth": 4,
|
||||||
|
"htmlWhitespaceSensitivity": "css"
|
||||||
}
|
}
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -2,8 +2,7 @@
|
|||||||
"editor.formatOnType": true,
|
"editor.formatOnType": true,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": "explicit"
|
||||||
// "source.organizeImports": true // <-- when enabled, breaks tinymce required import order
|
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[vue]": {
|
"[vue]": {
|
||||||
|
|||||||
23355
_ide_helper.php
Normal file
23355
_ide_helper.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,16 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Conductors;
|
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\Database\Eloquent\Model;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use LogicException;
|
|
||||||
|
|
||||||
class AnalyticsConductor extends Conductor
|
class AnalyticsConductor extends Conductor
|
||||||
{
|
{
|
||||||
@@ -19,20 +10,14 @@ class AnalyticsConductor extends Conductor
|
|||||||
* The Model Class
|
* The Model Class
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $class = \App\Models\Analytics::class;
|
protected $class = \App\Models\AnalyticsSession::class;
|
||||||
|
|
||||||
/**
|
|
||||||
* The default sorting field
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $sort = 'created_at';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default includes to include in a request.
|
* The default includes to include in a request.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $includes = ['duration'];
|
protected $includes = ['requests.type','requests.path'];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,6 +28,7 @@ class AnalyticsConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function viewable(Model $model): bool
|
public static function viewable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||||
}
|
}
|
||||||
@@ -65,6 +51,7 @@ class AnalyticsConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||||
}
|
}
|
||||||
@@ -77,6 +64,7 @@ class AnalyticsConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\InvalidCastException;
|
|||||||
use Illuminate\Database\Eloquent\MissingAttributeException;
|
use Illuminate\Database\Eloquent\MissingAttributeException;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
|
|
||||||
class ArticleConductor extends Conductor
|
class ArticleConductor extends Conductor
|
||||||
@@ -32,16 +33,18 @@ class ArticleConductor extends Conductor
|
|||||||
*
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $includes = ['attachments', 'user'];
|
protected $includes = ['attachments', 'user', 'gallery'];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a scope query on the collection before anything else.
|
* Run a scope query on the collection before anything else.
|
||||||
*
|
*
|
||||||
* @param Builder $builder The builder in use.
|
* @param Builder $builder The builder in use.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function scope(Builder $builder): void
|
public function scope(Builder $builder): void
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||||
$builder
|
$builder
|
||||||
@@ -58,6 +61,7 @@ class ArticleConductor extends Conductor
|
|||||||
public static function viewable(Model $model): bool
|
public static function viewable(Model $model): bool
|
||||||
{
|
{
|
||||||
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||||
return false;
|
return false;
|
||||||
@@ -74,6 +78,7 @@ class ArticleConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function creatable(): bool
|
public static function creatable(): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
}
|
}
|
||||||
@@ -86,6 +91,7 @@ class ArticleConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
}
|
}
|
||||||
@@ -98,6 +104,7 @@ class ArticleConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
}
|
}
|
||||||
@@ -122,11 +129,24 @@ class ArticleConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public function includeAttachments(Model $model)
|
public function includeAttachments(Model $model)
|
||||||
{
|
{
|
||||||
return $model->attachments()->get()->map(function ($attachment) {
|
return $model->getAttachments()->map(function ($attachment) {
|
||||||
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include Gallery Field.
|
||||||
|
*
|
||||||
|
* @param Model $model Them model.
|
||||||
|
* @return mixed The model result.
|
||||||
|
*/
|
||||||
|
public function includeGallery(Model $model)
|
||||||
|
{
|
||||||
|
return $model->getGallery()->map(function ($item) {
|
||||||
|
return MediaConductor::includeModel(request(), 'gallery', $item->media);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Include User Field.
|
* Include User Field.
|
||||||
*
|
*
|
||||||
@@ -135,17 +155,27 @@ class ArticleConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public function includeUser(Model $model)
|
public function includeUser(Model $model)
|
||||||
{
|
{
|
||||||
return UserConductor::includeModel(request(), 'user', User::find($model['user_id']));
|
$cacheKey = "user:{$model['user_id']}";
|
||||||
|
$user = Cache::remember($cacheKey, now()->addDays(28), function () use ($model) {
|
||||||
|
return User::find($model['user_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
return UserConductor::includeModel(request(), 'user', $user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the Hero field.
|
* Transform the Hero field.
|
||||||
*
|
*
|
||||||
* @param mixed $value The current value.
|
* @param mixed $value The current value.
|
||||||
* @return array The new value.
|
* @return array|null The new value.
|
||||||
*/
|
*/
|
||||||
public function transformHero(mixed $value): array
|
public function transformHero(mixed $value): array|null
|
||||||
{
|
{
|
||||||
return MediaConductor::includeModel(request(), 'hero', Media::find($value));
|
$cacheKey = "media:{$value}";
|
||||||
|
$media = Cache::remember($cacheKey, now()->addDays(28), function () use ($value) {
|
||||||
|
return Media::find($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return MediaConductor::includeModel(request(), 'hero', $media);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Builder;
|
|||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Conductor
|
class Conductor
|
||||||
@@ -143,6 +143,7 @@ class Conductor
|
|||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param array|null $limitFields A list of fields to limit the filter request to.
|
* @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): void
|
private function filter(Request $request, array|null $limitFields = null): void
|
||||||
{
|
{
|
||||||
@@ -163,14 +164,18 @@ class Conductor
|
|||||||
) {
|
) {
|
||||||
$value = trim($value);
|
$value = trim($value);
|
||||||
$operator = '';
|
$operator = '';
|
||||||
$join = 'OR';
|
$join = 'AND';
|
||||||
|
|
||||||
// Check if value has a operator and remove it if it's a number
|
// Check if value has a operator and remove it if it's a number
|
||||||
if (preg_match('/^(!?=|[<>]=?|<>|!)([^=!<>].*)*$/', $value, $matches) > 0) {
|
if (preg_match('/^(!?=|[<>]=?|<>|!|\|)([^=!<>].*)*$/', $value, $matches) > 0) {
|
||||||
$operator = $matches[1];
|
$operator = $matches[1];
|
||||||
$value = ($matches[2] ?? '');
|
$value = ($matches[2] ?? '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strlen($value) === 0 && ($operator !== '==' && $operator !== '!=')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
switch ($operator) {
|
switch ($operator) {
|
||||||
case '=':
|
case '=':
|
||||||
$operator = '==';
|
$operator = '==';
|
||||||
@@ -181,15 +186,23 @@ class Conductor
|
|||||||
break;
|
break;
|
||||||
case '>':
|
case '>':
|
||||||
case '<':
|
case '<':
|
||||||
|
case '|':
|
||||||
|
$separatorPos = strpos($value, '|');
|
||||||
|
if ($separatorPos !== false) {
|
||||||
|
$operator = '==';
|
||||||
|
$valueList = explode('|', $value);
|
||||||
|
foreach ($valueList as $valueItem) {
|
||||||
|
$this->appendFilter($field, $operator, $valueItem, 'OR');
|
||||||
|
}
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case '>=':
|
case '>=':
|
||||||
case '<=':
|
case '<=':
|
||||||
case '!=':
|
case '!=':
|
||||||
break;
|
break;
|
||||||
case '<>':
|
case '<>':
|
||||||
$separatorPos = strpos($value, '|');
|
$operator = '!=';
|
||||||
if ($separatorPos === false) {
|
|
||||||
$operator = '!=';
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$operator = 'LIKE';
|
$operator = 'LIKE';
|
||||||
@@ -209,6 +222,8 @@ class Conductor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Apple the filter array to the collection.
|
* Apple the filter array to the collection.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
final public function applyFilters(): void
|
final public function applyFilters(): void
|
||||||
{
|
{
|
||||||
@@ -217,6 +232,40 @@ class Conductor
|
|||||||
$result = null;
|
$result = null;
|
||||||
$join = 'AND';
|
$join = 'AND';
|
||||||
|
|
||||||
|
$relationFilter = [];
|
||||||
|
|
||||||
|
$buildWhereFunc = function ($query, $field, $operator, $value, $join) {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
if (gettype($query) === 'array') {
|
if (gettype($query) === 'array') {
|
||||||
$item = $query;
|
$item = $query;
|
||||||
}
|
}
|
||||||
@@ -240,81 +289,72 @@ class Conductor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
list($field, $operator, $value) = $condition;
|
if (count($condition) < 3 && $condition[0] !== '') {
|
||||||
|
if (count($condition) < 2) {
|
||||||
if ($item !== null) {
|
$condition[1] = 'LIKE';
|
||||||
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 = '=';
|
|
||||||
}
|
}
|
||||||
|
$condition[2] = '%';
|
||||||
|
}
|
||||||
|
|
||||||
if ($join === 'OR') {
|
if (count($condition) === 3) {
|
||||||
if ($operator === '<>') {
|
list($field, $operator, $value) = $condition;
|
||||||
$separatorPos = strpos($value, '|');
|
|
||||||
if ($separatorPos !== false) {
|
if ($item !== null) {
|
||||||
$query->orWhereBetween(
|
if (array_key_exists($field, $item) === true) {
|
||||||
$field,
|
switch ($operator) {
|
||||||
[substr($value, 0, $separatorPos), substr($value, ($separatorPos + 1))]
|
case '==':
|
||||||
);
|
$currentResult = ($item[$field] == $value);
|
||||||
} else {
|
break;
|
||||||
$query->orWhere($field, '!=', $value);
|
case 'NOT LIKE':
|
||||||
}
|
$currentResult = (stripos($item[$field], substr($value, 1, -1)) === false);
|
||||||
} else {
|
break;
|
||||||
$query->orWhere($field, $operator, $value);
|
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 {
|
} else {
|
||||||
if ($operator === '<>') {
|
if ($operator === '==') {
|
||||||
$separatorPos = strpos($value, '|');
|
$operator = '=';
|
||||||
if ($separatorPos !== false) {
|
}
|
||||||
$query->whereBetween(
|
|
||||||
$field,
|
$relationSplit = strpos($field, '.');
|
||||||
[substr($value, 0, $separatorPos), substr($value, ($separatorPos + 1))]
|
if ($relationSplit !== false) {
|
||||||
);
|
$relation = substr($field, 0, $relationSplit);
|
||||||
} else {
|
$field = substr($field, ($relationSplit + 1));
|
||||||
$query->where($field, '!=', $value);
|
|
||||||
|
if (method_exists($this->class, $relation) === true) {
|
||||||
|
$relationFilter[$relation][] = [$field, $operator, $value, $join];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$query->where($field, $operator, $value);
|
$buildWhereFunc($query, $field, $operator, $value, $join);
|
||||||
}
|
}
|
||||||
}//end if
|
}//end if
|
||||||
}//end if
|
}//end if
|
||||||
@@ -338,6 +378,14 @@ class Conductor
|
|||||||
}//end if
|
}//end if
|
||||||
}//end foreach
|
}//end foreach
|
||||||
|
|
||||||
|
foreach ($relationFilter as $relation => $conditions) {
|
||||||
|
$query->whereHas($relation, function ($subQuery) use ($buildWhereFunc, $conditions) {
|
||||||
|
foreach ($conditions as $condition) {
|
||||||
|
$buildWhereFunc($subQuery, $condition[0], $condition[1], $condition[2], $condition[3]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -415,7 +463,7 @@ class Conductor
|
|||||||
return substr($field, 0, strpos($field, '.'));
|
return substr($field, 0, strpos($field, '.'));
|
||||||
}
|
}
|
||||||
return $field;
|
return $field;
|
||||||
}, explode(',', $request->input('fields')));
|
}, explode(',', $request->input('fields', '')));
|
||||||
if ($limitFields === null) {
|
if ($limitFields === null) {
|
||||||
$limitFields = $fields;
|
$limitFields = $fields;
|
||||||
} else {
|
} else {
|
||||||
@@ -500,6 +548,7 @@ class Conductor
|
|||||||
* @param Builder $query The custom query.
|
* @param Builder $query The custom query.
|
||||||
* @param Request $request The request.
|
* @param Request $request The request.
|
||||||
* @param array|null $limitFields Limit the request to these fields.
|
* @param array|null $limitFields Limit the request to these fields.
|
||||||
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
public static function filterQuery(Builder $query, Request $request, array|null $limitFields = null): Builder
|
public static function filterQuery(Builder $query, Request $request, array|null $limitFields = null): Builder
|
||||||
{
|
{
|
||||||
@@ -519,9 +568,9 @@ class Conductor
|
|||||||
* @param Request $request The request data.
|
* @param Request $request The request data.
|
||||||
* @param string $key The key prefix to use.
|
* @param string $key The key prefix to use.
|
||||||
* @param Model|null $model The model.
|
* @param Model|null $model The model.
|
||||||
* @return array The processed and transformed model data.
|
* @return array|null The processed and transformed model data.
|
||||||
*/
|
*/
|
||||||
final public static function includeModel(Request $request, string $key, mixed $model): array
|
final public static function includeModel(Request $request, string $key, mixed $model): array|null
|
||||||
{
|
{
|
||||||
$fields = [];
|
$fields = [];
|
||||||
|
|
||||||
@@ -547,9 +596,9 @@ class Conductor
|
|||||||
*
|
*
|
||||||
* @param mixed $fields The fields to show.
|
* @param mixed $fields The fields to show.
|
||||||
* @param Model|null $model The model.
|
* @param Model|null $model The model.
|
||||||
* @return array The processed and transformed model data.
|
* @return array|null The processed and transformed model data.
|
||||||
*/
|
*/
|
||||||
final public static function model(mixed $fields, mixed $model): array
|
final public static function model(mixed $fields, mixed $model): array|null
|
||||||
{
|
{
|
||||||
if ($model === null) {
|
if ($model === null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -558,6 +607,7 @@ class Conductor
|
|||||||
$conductor_class = get_called_class();
|
$conductor_class = get_called_class();
|
||||||
$conductor = new $conductor_class();
|
$conductor = new $conductor_class();
|
||||||
|
|
||||||
|
$requestIncludes = [];
|
||||||
$modelFields = $conductor->fields(new $conductor->class());
|
$modelFields = $conductor->fields(new $conductor->class());
|
||||||
|
|
||||||
// Limit fields
|
// Limit fields
|
||||||
@@ -615,6 +665,7 @@ class Conductor
|
|||||||
* Sort the conductor collection.
|
* Sort the conductor collection.
|
||||||
*
|
*
|
||||||
* @param mixed $fields A field name or array of field names to sort. Supports prefix of +/- to change direction.
|
* @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): void
|
final public function sort(mixed $fields = null): void
|
||||||
{
|
{
|
||||||
@@ -693,6 +744,7 @@ class Conductor
|
|||||||
*
|
*
|
||||||
* @param Model $model The model to append.
|
* @param Model $model The model to append.
|
||||||
* @param array $includes The list of includes to include.
|
* @param array $includes The list of includes to include.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
final public function applyIncludes(Model $model, array $includes): void
|
final public function applyIncludes(Model $model, array $includes): void
|
||||||
{
|
{
|
||||||
@@ -712,6 +764,7 @@ class Conductor
|
|||||||
* Limit the returned fields in the conductor collection.
|
* Limit the returned fields in the conductor collection.
|
||||||
*
|
*
|
||||||
* @param array $fields An array of field names.
|
* @param array $fields An array of field names.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
final public function limitFields(array $fields): void
|
final public function limitFields(array $fields): void
|
||||||
{
|
{
|
||||||
@@ -726,9 +779,13 @@ class Conductor
|
|||||||
* @param string $rawFilter The raw filter string to parse.
|
* @param string $rawFilter The raw filter string to parse.
|
||||||
* @param array|null $limitFields The fields to allow in the filter string.
|
* @param array|null $limitFields The fields to allow in the filter string.
|
||||||
* @param string $outerJoin The join for this filter group.
|
* @param string $outerJoin The join for this filter group.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
final public function appendFilterString(string $rawFilter, array|null $limitFields = null, string $outerJoin = 'OR'): void
|
final public function appendFilterString(
|
||||||
{
|
string $rawFilter,
|
||||||
|
array|null $limitFields = null,
|
||||||
|
string $outerJoin = 'AND'
|
||||||
|
): void {
|
||||||
if ($rawFilter === '') {
|
if ($rawFilter === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -765,8 +822,10 @@ class Conductor
|
|||||||
$field = substr($field, 1, -1);
|
$field = substr($field, 1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
$set = &$value;
|
if ($set !== $value) {
|
||||||
continue;
|
$set = &$value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
} elseif (($char === ')' && $string[($i + 1)] === ',') || $char === ',') {
|
} elseif (($char === ')' && $string[($i + 1)] === ',') || $char === ',') {
|
||||||
if ($value === null) {
|
if ($value === null) {
|
||||||
$tokens[] = $field;
|
$tokens[] = $field;
|
||||||
@@ -839,6 +898,7 @@ class Conductor
|
|||||||
* @param string $operator The operator to append.
|
* @param string $operator The operator to append.
|
||||||
* @param string $value The value to append.
|
* @param string $value The value to append.
|
||||||
* @param string $join The join to append.
|
* @param string $join The join to append.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
final public function appendFilter(string $field, string $operator, string $value, string $join = 'OR'): void
|
final public function appendFilter(string $field, string $operator, string $value, string $join = 'OR'): void
|
||||||
{
|
{
|
||||||
@@ -852,9 +912,11 @@ class Conductor
|
|||||||
* Run a scope query on the collection before anything else.
|
* Run a scope query on the collection before anything else.
|
||||||
*
|
*
|
||||||
* @param Builder $builder The builder in use.
|
* @param Builder $builder The builder in use.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function scope(Builder $builder): void
|
public function scope(Builder $builder): void
|
||||||
{
|
{
|
||||||
|
// empty
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -865,12 +927,26 @@ class Conductor
|
|||||||
*/
|
*/
|
||||||
public function fields(Model $model): array
|
public function fields(Model $model): array
|
||||||
{
|
{
|
||||||
$visibleFields = $model->getVisible();
|
$visibleFields = Cache::remember(
|
||||||
if (empty($visibleFields) === true) {
|
"model:{$model->getTable()}:visible",
|
||||||
$visibleFields = $model->getConnection()
|
now()->addDays(28),
|
||||||
->getSchemaBuilder()
|
function () use ($model) {
|
||||||
->getColumnListing($model->getTable());
|
$fields = $model->getVisible();
|
||||||
}
|
if (empty($fields) === true) {
|
||||||
|
$fields = Cache::remember(
|
||||||
|
"schema:{$model->getTable()}:columns",
|
||||||
|
now()->addDays(28),
|
||||||
|
function () use ($model) {
|
||||||
|
return $model->getConnection()
|
||||||
|
->getSchemaBuilder()
|
||||||
|
->getColumnListing($model->getTable());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
$appends = $model->getAppends();
|
$appends = $model->getAppends();
|
||||||
if (is_array($appends) === true) {
|
if (is_array($appends) === true) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use Carbon\Carbon;
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class EventConductor extends Conductor
|
class EventConductor extends Conductor
|
||||||
{
|
{
|
||||||
@@ -33,9 +34,11 @@ class EventConductor extends Conductor
|
|||||||
* Run a scope query on the collection before anything else.
|
* Run a scope query on the collection before anything else.
|
||||||
*
|
*
|
||||||
* @param Builder $builder The builder in use.
|
* @param Builder $builder The builder in use.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function scope(Builder $builder): void
|
public function scope(Builder $builder): void
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||||
$builder
|
$builder
|
||||||
@@ -53,6 +56,7 @@ class EventConductor extends Conductor
|
|||||||
public static function viewable(Model $model): bool
|
public static function viewable(Model $model): bool
|
||||||
{
|
{
|
||||||
if (strtolower($model->status) === 'draft' || Carbon::parse($model->publish_at)->isFuture() === true) {
|
if (strtolower($model->status) === 'draft' || Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||||
return false;
|
return false;
|
||||||
@@ -69,6 +73,7 @@ class EventConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function creatable(): bool
|
public static function creatable(): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||||
}
|
}
|
||||||
@@ -81,6 +86,7 @@ class EventConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||||
}
|
}
|
||||||
@@ -93,6 +99,7 @@ class EventConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||||
}
|
}
|
||||||
@@ -105,10 +112,11 @@ class EventConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public function includeAttachments(Model $model)
|
public function includeAttachments(Model $model)
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
return $model->attachments()->get()->map(function ($attachment) use ($user) {
|
return $model->getAttachments()->map(function ($attachment) use ($user) {
|
||||||
if ($attachment->private === false || ($user !== null && ($user->hasPermission('admin/events') === true || $attachment->users->contains($user) === true))) {
|
if ($attachment->private === false || ($user !== null && $user->hasPermission('admin/events') === true)) {
|
||||||
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -118,10 +126,15 @@ class EventConductor extends Conductor
|
|||||||
* Transform the Hero field.
|
* Transform the Hero field.
|
||||||
*
|
*
|
||||||
* @param mixed $value The current value.
|
* @param mixed $value The current value.
|
||||||
* @return array The new value.
|
* @return array|null The new value.
|
||||||
*/
|
*/
|
||||||
public function transformHero(mixed $value): array
|
public function transformHero(mixed $value): array|null
|
||||||
{
|
{
|
||||||
return MediaConductor::includeModel(request(), 'hero', Media::find($value));
|
$cacheKey = "media:{$value}";
|
||||||
|
$media = Cache::remember($cacheKey, now()->addDays(28), function () use ($value) {
|
||||||
|
return Media::find($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return MediaConductor::includeModel(request(), 'hero', $media);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Conductors;
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use App\Models\MediaJob;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Foundation\Auth\User;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class MediaConductor extends Conductor
|
class MediaConductor extends Conductor
|
||||||
{
|
{
|
||||||
@@ -25,16 +27,16 @@ class MediaConductor extends Conductor
|
|||||||
*
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $includes = ['user'];
|
protected $includes = ['user', 'jobs'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default filters to use in a request.
|
* The default filters to use in a request.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $defaultFilters = [
|
// protected $defaultFilters = [
|
||||||
'status' => 'OK'
|
// 'status' => 'OK'
|
||||||
];
|
// ];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,9 +49,10 @@ class MediaConductor extends Conductor
|
|||||||
{
|
{
|
||||||
$fields = parent::fields($model);
|
$fields = parent::fields($model);
|
||||||
|
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/media') === false) {
|
if ($user === null || $user->hasPermission('admin/media') === false) {
|
||||||
$fields = arrayRemoveItem($fields, ['permission', 'storage']);
|
$fields = arrayRemoveItem($fields, ['security_data', 'storage']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
@@ -59,14 +62,23 @@ class MediaConductor extends Conductor
|
|||||||
* Run a scope query on the collection before anything else.
|
* Run a scope query on the collection before anything else.
|
||||||
*
|
*
|
||||||
* @param Builder $builder The builder in use.
|
* @param Builder $builder The builder in use.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function scope(Builder $builder): void
|
public function scope(Builder $builder): void
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
$builder->where('permission', '');
|
$builder->where('security_type', '')
|
||||||
|
->orWhere('security_type', 'password');
|
||||||
} else {
|
} else {
|
||||||
$builder->where('permission', '')->orWhereIn('permission', $user->permissions);
|
$builder->where(function ($query) use ($user) {
|
||||||
|
$query->where('security_type', '')
|
||||||
|
->orWhere('security_type', 'password')
|
||||||
|
->orWhere(function ($subquery) use ($user) {
|
||||||
|
$subquery->where('security_type', 'permission')
|
||||||
|
->whereIn('security_data', $user->permissions);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +90,14 @@ class MediaConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function viewable(Model $model): bool
|
public static function viewable(Model $model): bool
|
||||||
{
|
{
|
||||||
if ($model->permission !== '') {
|
if (strcasecmp('permission', $model->security_type) === 0) {
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission($model->permission) === false) {
|
if ($user === null || $user->hasPermission($model->security_data) === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} elseif ($model->security_type !== '' && strcasecmp('password', $model->security_type) !== 0) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -107,8 +122,10 @@ class MediaConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && (strcasecmp($model->user_id, $user->id) === 0 || $user->hasPermission('admin/media') === true));
|
return ($user !== null && (strcasecmp($model->user_id, $user->id) === 0 ||
|
||||||
|
$user->hasPermission('admin/media') === true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,6 +136,7 @@ class MediaConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && ($model->user_id === $user->id || $user->hasPermission('admin/media') === true));
|
return ($user !== null && ($model->user_id === $user->id || $user->hasPermission('admin/media') === true));
|
||||||
}
|
}
|
||||||
@@ -143,6 +161,24 @@ class MediaConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public function includeUser(Model $model)
|
public function includeUser(Model $model)
|
||||||
{
|
{
|
||||||
return UserConductor::includeModel(request(), 'user', User::find($model['user_id']));
|
$user = Cache::remember("user:{$model['user_id']}", now()->addDays(28), function () use ($model) {
|
||||||
|
return User::find($model['user_id']);
|
||||||
|
});
|
||||||
|
|
||||||
|
return UserConductor::includeModel(request(), 'user', $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include job models in Media
|
||||||
|
*
|
||||||
|
* @param Model $model The reference model.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function includeJobs(Model $model)
|
||||||
|
{
|
||||||
|
$jobs = $model->jobs()
|
||||||
|
->select(['id','created_at','updated_at','user_id','status','status_text','progress'])
|
||||||
|
->orderBy('created_at', 'desc')->get();
|
||||||
|
return $jobs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
app/Conductors/MediaJobConductor.php
Normal file
76
app/Conductors/MediaJobConductor.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use App\Models\MediaJob;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class MediaJobConductor extends Conductor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Model Class
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $class = \App\Models\MediaJob::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sorting field
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sort = 'created_at';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The included fields
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $includes = ['user'];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is creatable.
|
||||||
|
*
|
||||||
|
* @return boolean Allow creating model.
|
||||||
|
*/
|
||||||
|
public static function creatable(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is updatable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow updating model.
|
||||||
|
*/
|
||||||
|
public static function updatable(Model $model): bool
|
||||||
|
{
|
||||||
|
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): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the final model data
|
||||||
|
*
|
||||||
|
* @param array $data The model data to transform.
|
||||||
|
* @return array The transformed model.
|
||||||
|
*/
|
||||||
|
public function transformFinal(array $data): array
|
||||||
|
{
|
||||||
|
unset($data['user_id']);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Conductors;
|
namespace App\Conductors;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Foundation\Auth\User;
|
|
||||||
|
|
||||||
class ShortlinkConductor extends Conductor
|
class ShortlinkConductor extends Conductor
|
||||||
{
|
{
|
||||||
@@ -28,6 +26,7 @@ class ShortlinkConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function creatable(): bool
|
public static function creatable(): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||||
}
|
}
|
||||||
@@ -40,6 +39,7 @@ class ShortlinkConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,7 @@ class ShortlinkConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ class SubscriptionConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
return ($user !== null && (
|
||||||
|
(strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) ||
|
||||||
|
$user->hasPermission('admin/subscriptions') === true
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +37,9 @@ class SubscriptionConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 &&
|
||||||
|
$user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class UserConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public function fields(Model $model): array
|
public function fields(Model $model): array
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/users') === false) {
|
if ($user === null || $user->hasPermission('admin/users') === false) {
|
||||||
return ['id', 'display_name'];
|
return ['id', 'display_name'];
|
||||||
@@ -37,16 +38,22 @@ class UserConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public function transform(Model $model): array
|
public function transform(Model $model): array
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
$data = $model->toArray();
|
$data = $model->toArray();
|
||||||
|
$limit = $this->fields($model);
|
||||||
|
|
||||||
if ($user === null || ($user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0)) {
|
if (
|
||||||
$fields = ['id', 'display_name'];
|
$user === null || (
|
||||||
$data = arrayLimitKeys($data, $fields);
|
$user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$limit = ['id', 'display_name'];
|
||||||
} else {
|
} else {
|
||||||
$data['permissions'] = $user->permissions;
|
$data['permissions'] = $user->permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$data = arrayLimitKeys($data, $limit);
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +65,7 @@ class UserConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function updatable(Model $model): bool
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
return ($user->hasPermission('admin/users') === true || strcasecmp($user->id, $model->id) === 0);
|
return ($user->hasPermission('admin/users') === true || strcasecmp($user->id, $model->id) === 0);
|
||||||
@@ -74,6 +82,7 @@ class UserConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model): bool
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/users') === true);
|
return ($user !== null && $user->hasPermission('admin/users') === true);
|
||||||
}
|
}
|
||||||
|
|||||||
46
app/Console/Commands/CleanupTempFiles.php
Normal file
46
app/Console/Commands/CleanupTempFiles.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class CleanupTempFiles extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:cleanup-temp-files';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Delete temporary files that are older that 1 day';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$keepTime = (1 * 24 * 60 * 60); // 1 Day
|
||||||
|
$currentTimeStamp = time();
|
||||||
|
$deletedFileCount = 0;
|
||||||
|
|
||||||
|
foreach (glob(storage_path('app/tmp/*')) as $filename) {
|
||||||
|
$fileModifiedTimeStamp = filemtime($filename);
|
||||||
|
if (($currentTimeStamp - $fileModifiedTimeStamp) > $keepTime) {
|
||||||
|
unlink($filename);
|
||||||
|
$deletedFileCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->comment('Deleted ' . $deletedFileCount . ' files');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +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.
|
|
||||||
*/
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this->addOption(
|
|
||||||
'replace',
|
|
||||||
null,
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Replace existing files'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
$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,66 +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.
|
|
||||||
*/
|
|
||||||
protected function configure(): void
|
|
||||||
{
|
|
||||||
$this->addOption(
|
|
||||||
'replace',
|
|
||||||
null,
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Replace existing files'
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->addOption(
|
|
||||||
'all',
|
|
||||||
null,
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Rebuild all variants'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
$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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
app/Console/Commands/RemoveStaleMediaJobs.php
Normal file
42
app/Console/Commands/RemoveStaleMediaJobs.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\MediaJob;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class RemoveStaleMediaJobs extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:remove-stale-media-jobs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Remove media_jobs that have not been modified for 48 hours';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$threshold = now()->subHours(48);
|
||||||
|
|
||||||
|
$staleJobs = MediaJob::where('updated_at', '<=', $threshold)->get();
|
||||||
|
|
||||||
|
foreach ($staleJobs as $job) {
|
||||||
|
$job->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info(count($staleJobs) . ' stale media_jobs removed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,14 +11,19 @@ class Kernel extends ConsoleKernel
|
|||||||
* Define the application's command schedule.
|
* Define the application's command schedule.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule The schedule.
|
* @param \Illuminate\Console\Scheduling\Schedule $schedule The schedule.
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
// $schedule->command('inspire')->hourly();
|
// $schedule->command('inspire')->hourly();
|
||||||
|
$schedule->command('app:cleanup-temp-files')->everyThirtyMinutes();
|
||||||
|
$schedule->command('app:remove-stale-media-jobs')->everyThirtyMinutes();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the commands for the application.
|
* Register the commands for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function commands(): void
|
protected function commands(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ class Enum
|
|||||||
/**
|
/**
|
||||||
* Returns a message from the enum subclass
|
* Returns a message from the enum subclass
|
||||||
*
|
*
|
||||||
|
* @param integer $messageIndex The message index to retrieve.
|
||||||
|
* @param string $defaultMessage Message to use if index does not exist.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getMessage(int $messageIndex, string $defaultMessage = 'Unknown'): string
|
public static function getMessage(int $messageIndex, string $defaultMessage = 'Unknown'): string
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Exceptions;
|
namespace App\Exceptions;
|
||||||
|
|
||||||
use App\Enum\HttpResponseCodes;
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use PDOException;
|
use PDOException;
|
||||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
|
||||||
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Symfony\Component\ErrorHandler\Exception\FlattenException;
|
||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
@@ -26,6 +28,8 @@ class Handler extends ExceptionHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the exception handling callbacks for the application.
|
* Register the exception handling callbacks for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
@@ -59,7 +63,36 @@ class Handler extends ExceptionHandler
|
|||||||
});
|
});
|
||||||
|
|
||||||
$this->reportable(function (Throwable $e) {
|
$this->reportable(function (Throwable $e) {
|
||||||
//
|
if ($this->shouldReport($e) === true) {
|
||||||
|
if (App::runningUnitTests() === false) {
|
||||||
|
$this->sendEmail($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send email
|
||||||
|
*
|
||||||
|
* @param Throwable $exception Throwable object.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function sendEmail(Throwable $exception)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$e = FlattenException::createFromThrowable($exception);
|
||||||
|
$handler = new HtmlErrorRenderer(true);
|
||||||
|
$css = $handler->getStylesheet();
|
||||||
|
$content = $handler->getBody($e);
|
||||||
|
|
||||||
|
Mail::send('emails.exception', compact('css', 'content'), function ($message) {
|
||||||
|
$message
|
||||||
|
->to('webmaster@stemmechanics.com.au')
|
||||||
|
->subject('Exception Generated')
|
||||||
|
;
|
||||||
|
});
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
Log::error($ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,3 +38,41 @@ function arrayLimitKeys(array $arr, array $keys): array
|
|||||||
{
|
{
|
||||||
return array_intersect_key($arr, array_flip($keys));
|
return array_intersect_key($arr, array_flip($keys));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array value or default value if it does not exist
|
||||||
|
*
|
||||||
|
* @param string $key The key value to return if exists.
|
||||||
|
* @param array $arr The array to check.
|
||||||
|
* @param mixed $value The value to return if key does not exist.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function arrayDefaultValue(string $key, array $arr, mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (array_key_exists($key, $arr) === true) {
|
||||||
|
return $arr[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if an item exists in an array, case insensitive
|
||||||
|
*
|
||||||
|
* @param string $val The value to check.
|
||||||
|
* @param array $arr The array to check.
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function existsInArray(string $val, array $arr): bool
|
||||||
|
{
|
||||||
|
$exists = false;
|
||||||
|
|
||||||
|
foreach ($arr as $el) {
|
||||||
|
if (strcasecmp($val, $el) === 0) {
|
||||||
|
$exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $exists;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,14 +6,87 @@
|
|||||||
/**
|
/**
|
||||||
* Generate a temporary file path.
|
* Generate a temporary file path.
|
||||||
*
|
*
|
||||||
* @return str The filtered array.
|
* @param string $extension The file extension to use.
|
||||||
|
* @param string $part The file part number.
|
||||||
|
* @return string The filtered array.
|
||||||
*/
|
*/
|
||||||
function generateTempFilePath(): string
|
function generateTempFilePath(string $extension = '', string $part = ''): string
|
||||||
{
|
{
|
||||||
$temporaryDir = storage_path('app/tmp');
|
$temporaryDir = storage_path('app/tmp');
|
||||||
if (is_dir($temporaryDir) === false) {
|
if (is_dir($temporaryDir) === false) {
|
||||||
mkdir($temporaryDir, 0777, true);
|
mkdir($temporaryDir, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $temporaryDir . DIRECTORY_SEPARATOR . uniqid('upload_', true);
|
return $temporaryDir . DIRECTORY_SEPARATOR . uniqid('upload_', true) . ($extension !== '' ? ".{$extension}" : '') .
|
||||||
|
($part !== '' ? ".part-{$part}" : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Temp file information
|
||||||
|
*
|
||||||
|
* @param string $filePath The temp file name.
|
||||||
|
* @return array The temp file name details.
|
||||||
|
*/
|
||||||
|
function tempFileInfo(string $filePath): array
|
||||||
|
{
|
||||||
|
$part = '';
|
||||||
|
|
||||||
|
// Extract the part if it's present
|
||||||
|
if (preg_match('/\.part-(\d+)$/', $filePath, $matches) !== false) {
|
||||||
|
$part = $matches[1];
|
||||||
|
$filePath = substr($filePath, 0, -strlen($matches[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = pathinfo($filePath);
|
||||||
|
|
||||||
|
$directory = $info['dirname'];
|
||||||
|
$name = $info['filename'];
|
||||||
|
$extension = '';
|
||||||
|
|
||||||
|
// If there's an extension, separate it
|
||||||
|
if (isset($info['extension']) === true) {
|
||||||
|
$extension = $info['extension'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dirname' => $directory,
|
||||||
|
'basename' => $name . ($extension !== '' ? ".{$extension}" : ''),
|
||||||
|
'filename' => $name,
|
||||||
|
'extension' => $extension,
|
||||||
|
'part' => $part,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a temporary file exists.
|
||||||
|
*
|
||||||
|
* @param string $dir The file parent directory.
|
||||||
|
* @param string $name The file name.
|
||||||
|
* @param string $extension The file extension to use.
|
||||||
|
* @param string $part The file part number.
|
||||||
|
* @return boolean If the file exists.
|
||||||
|
*/
|
||||||
|
function tempFileExists(string $dir, string $name, string $extension = '', string $part = ''): bool
|
||||||
|
{
|
||||||
|
$filename = constructTempFileName($dir, $name, $extension, $part);
|
||||||
|
$exists = file_exists($filename);
|
||||||
|
|
||||||
|
return $exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the temp file name based on the information
|
||||||
|
*
|
||||||
|
* @param string $dir The file parent directory.
|
||||||
|
* @param string $name The file name.
|
||||||
|
* @param string $extension The file extension to use.
|
||||||
|
* @param string $part The file part number.
|
||||||
|
* @return string The file path.
|
||||||
|
*/
|
||||||
|
function constructTempFileName(string $dir, string $name, string $extension = '', string $part = ''): string
|
||||||
|
{
|
||||||
|
$filename = $dir . DIRECTORY_SEPARATOR . $name . ($extension !== '' ? ".{$extension}" : '') .
|
||||||
|
($part !== "" ? ".part-{$part}" : '');
|
||||||
|
|
||||||
|
return $filename;
|
||||||
}
|
}
|
||||||
|
|||||||
27
app/Helpers/TypeValue.php
Normal file
27
app/Helpers/TypeValue.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/* Type Value Helper Functions */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is value true
|
||||||
|
*
|
||||||
|
* @param mixed $value Value to check.
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function isTrue(mixed $value): bool
|
||||||
|
{
|
||||||
|
if (is_bool($value) === true && $value === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($value) === true && intval($value) === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value) === true && in_array(strtolower($value), ['true', '1'], true) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -3,16 +3,10 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Conductors\AnalyticsConductor;
|
use App\Conductors\AnalyticsConductor;
|
||||||
use App\Conductors\Conductor;
|
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\AnalyticsRequest;
|
use App\Http\Requests\AnalyticsRequest;
|
||||||
use App\Models\Media;
|
use App\Models\AnalyticsItemRequest;
|
||||||
use App\Models\Analytics;
|
use App\Models\AnalyticsSession;
|
||||||
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;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class AnalyticsController extends ApiController
|
class AnalyticsController extends ApiController
|
||||||
@@ -39,27 +33,17 @@ class AnalyticsController extends ApiController
|
|||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
||||||
$searchFields = ['attribute', 'type', 'useragent', 'ip'];
|
$request->rename([
|
||||||
|
'type' => 'requests.type',
|
||||||
|
'path' => 'requests.path'
|
||||||
|
]);
|
||||||
|
|
||||||
$queryRequest = new Request();
|
list($collection, $total) = AnalyticsConductor::request($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(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$collection,
|
||||||
['isCollection' => true,
|
['resourceName' => 'session',
|
||||||
|
'isCollection' => true,
|
||||||
'appendData' => ['total' => $total]
|
'appendData' => ['total' => $total]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -71,22 +55,25 @@ class AnalyticsController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
* @param \App\Models\Analytics $analytics The analyics model.
|
* @param \App\Models\AnalyticsSession $session The analytics session.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, int $session)
|
public function show(Request $request, AnalyticsSession $session)
|
||||||
{
|
{
|
||||||
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
||||||
list($collection, $total) = AnalyticsConductor::collection($request, Analytics::query()
|
$session->load(['requests' => function ($query) {
|
||||||
->where('session', $session)
|
$query->select('session_id', 'type', 'path', 'created_at');
|
||||||
->get());
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($session->requests as $requestItem) {
|
||||||
|
$requestItem->makeHidden('session_id');
|
||||||
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$session,
|
||||||
['isCollection' => true,
|
['resourceName' => 'session']
|
||||||
'appendData' => ['total' => $total]
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,16 +94,20 @@ class AnalyticsController extends ApiController
|
|||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'type' => $request->input('type'),
|
'type' => $request->input('type'),
|
||||||
'attribute' => $request->input('attribute', ''),
|
'path' => $request->input('path', ''),
|
||||||
'useragent' => $request->userAgent(),
|
'useragent' => $request->userAgent(),
|
||||||
'ip' => $request->ip()
|
'ip' => $request->ip()
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($user !== null && $user->hasPermission('admin/analytics') === true && $request->has('session') === true) {
|
if (
|
||||||
$data['session'] = $request->input('session');
|
$user !== null &&
|
||||||
$analytics = Analytics::create($data);
|
$user->hasPermission('admin/analytics') === true &&
|
||||||
|
$request->has('session') === true
|
||||||
|
) {
|
||||||
|
$data['session_id'] = $request->input('session_id');
|
||||||
|
$analytics = AnalyticsItemRequest::create($data);
|
||||||
} else {
|
} else {
|
||||||
$analytics = Analytics::createWithSession($data);
|
$analytics = AnalyticsItemRequest::create($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
@@ -127,37 +118,4 @@ class AnalyticsController extends ApiController
|
|||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}//end if
|
}//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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,13 @@ class ApiController extends Controller
|
|||||||
* @param array $data Response data.
|
* @param array $data Response data.
|
||||||
* @param integer $respondCode Response status code.
|
* @param integer $respondCode Response status code.
|
||||||
* @param array $headers Response headers.
|
* @param array $headers Response headers.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondJson(array $data, int $respondCode = HttpResponseCodes::HTTP_OK, array $headers = []): JsonResponse
|
public function respondJson(
|
||||||
{
|
array $data,
|
||||||
|
int $respondCode = HttpResponseCodes::HTTP_OK,
|
||||||
|
array $headers = []
|
||||||
|
): JsonResponse {
|
||||||
return response()->json($data, $respondCode, $headers);
|
return response()->json($data, $respondCode, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,9 +38,11 @@ class ApiController extends Controller
|
|||||||
* Return forbidden message
|
* Return forbidden message
|
||||||
*
|
*
|
||||||
* @param string $message Response message.
|
* @param string $message Response message.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondForbidden(string $message = 'You do not have permission to access the resource.'): JsonResponse
|
public function respondForbidden(
|
||||||
{
|
string $message = 'You do not have permission to access the resource.'
|
||||||
|
): JsonResponse {
|
||||||
return response()->json(['message' => $message], HttpResponseCodes::HTTP_FORBIDDEN);
|
return response()->json(['message' => $message], HttpResponseCodes::HTTP_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +50,7 @@ class ApiController extends Controller
|
|||||||
* Return forbidden message
|
* Return forbidden message
|
||||||
*
|
*
|
||||||
* @param string $message Response message.
|
* @param string $message Response message.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondNotFound(string $message = 'The resource was not found.'): JsonResponse
|
public function respondNotFound(string $message = 'The resource was not found.'): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -54,6 +61,7 @@ class ApiController extends Controller
|
|||||||
* Return too large message
|
* Return too large message
|
||||||
*
|
*
|
||||||
* @param string $message Response message.
|
* @param string $message Response message.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondTooLarge(string $message = 'The request entity is too large.'): JsonResponse
|
public function respondTooLarge(string $message = 'The request entity is too large.'): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -61,7 +69,9 @@ class ApiController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return no content
|
* Return no content.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondNoContent(): JsonResponse
|
public function respondNoContent(): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -69,7 +79,19 @@ class ApiController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return created
|
* Return no content
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function respondNotImplemented(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([], HttpResponseCodes::HTTP_NOT_IMPLEMENTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return created.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondCreated(): JsonResponse
|
public function respondCreated(): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -77,21 +99,36 @@ class ApiController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return accepted
|
* Return accepted.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondAccepted(): JsonResponse
|
public function respondAccepted(): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json([], HttpResponseCodes::HTTP_ACCEPTED);
|
return response()->json([], HttpResponseCodes::HTTP_ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return server error.
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function respondServerError(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([], HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return single error message
|
* Return single error message
|
||||||
*
|
*
|
||||||
* @param string $message Error message.
|
* @param string $message Error message.
|
||||||
* @param integer $responseCode Resource code.
|
* @param integer $responseCode Resource code.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondError(string $message, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY): JsonResponse
|
public function respondError(
|
||||||
{
|
string $message,
|
||||||
|
int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY
|
||||||
|
): JsonResponse {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => $message
|
'message' => $message
|
||||||
], $responseCode);
|
], $responseCode);
|
||||||
@@ -102,9 +139,12 @@ class ApiController extends Controller
|
|||||||
*
|
*
|
||||||
* @param array $errors Error messages.
|
* @param array $errors Error messages.
|
||||||
* @param integer $responseCode Resource code.
|
* @param integer $responseCode Resource code.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function respondWithErrors(array $errors, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY): JsonResponse
|
public function respondWithErrors(
|
||||||
{
|
array $errors,
|
||||||
|
int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY
|
||||||
|
): JsonResponse {
|
||||||
$keys = array_keys($errors);
|
$keys = array_keys($errors);
|
||||||
$error = $errors[$keys[0]];
|
$error = $errors[$keys[0]];
|
||||||
|
|
||||||
@@ -122,17 +162,20 @@ class ApiController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Return resource data
|
* Return resource data
|
||||||
*
|
*
|
||||||
* @param array|Model|Collection $data Resource data.
|
* @param array|Model|Collection $data Resource data.
|
||||||
* @param array $options Respond options.
|
* @param array $options Respond options.
|
||||||
|
* @param callable|null $validationFn Optional validation function to check the data before responding.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
protected function respondAsResource(
|
protected function respondAsResource(
|
||||||
mixed $data,
|
mixed $data,
|
||||||
array $options = [],
|
array $options = [],
|
||||||
$validationFn = null
|
$validationFn = null
|
||||||
): JsonResponse {
|
): JsonResponse {
|
||||||
$isCollection = $options['isCollection'] ?? false;
|
$isCollection = ($options['isCollection'] ?? false);
|
||||||
$appendData = $options['appendData'] ?? null;
|
$appendData = ($options['appendData'] ?? null);
|
||||||
$resourceName = $options['resourceName'] ?? null;
|
$resourceName = ($options['resourceName'] ?? '');
|
||||||
|
$transformResourceName = ($options['transformResourceName'] ?? true);
|
||||||
$respondCode = ($options['respondCode'] ?? HttpResponseCodes::HTTP_OK);
|
$respondCode = ($options['respondCode'] ?? HttpResponseCodes::HTTP_OK);
|
||||||
|
|
||||||
if ($data === null || ($data instanceof Collection && $data->count() === 0)) {
|
if ($data === null || ($data instanceof Collection && $data->count() === 0)) {
|
||||||
@@ -146,11 +189,11 @@ class ApiController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($resourceName) === true || empty($resourceName) === true) {
|
if (empty($resourceName) === true) {
|
||||||
$resourceName = $this->resourceName;
|
$resourceName = $this->resourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($resourceName) === true || empty($resourceName) === true) {
|
if (empty($resourceName) === true) {
|
||||||
$resourceName = get_class($this);
|
$resourceName = get_class($this);
|
||||||
$resourceName = substr($resourceName, (strrpos($resourceName, '\\') + 1));
|
$resourceName = substr($resourceName, (strrpos($resourceName, '\\') + 1));
|
||||||
$resourceName = substr($resourceName, 0, strpos($resourceName, 'Controller'));
|
$resourceName = substr($resourceName, 0, strpos($resourceName, 'Controller'));
|
||||||
@@ -163,15 +206,14 @@ class ApiController extends Controller
|
|||||||
} elseif (is_array($data) === true) {
|
} elseif (is_array($data) === true) {
|
||||||
$dataArray = $data;
|
$dataArray = $data;
|
||||||
} elseif ($data instanceof Model) {
|
} elseif ($data instanceof Model) {
|
||||||
$is_multiple = false;
|
|
||||||
$dataArray = $data->toArray();
|
$dataArray = $data->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = [];
|
$resource = [];
|
||||||
if ($isCollection === true) {
|
if ($isCollection === true) {
|
||||||
$resource = [Str::plural($resourceName) => $dataArray];
|
$resource = [$transformResourceName === true ? Str::plural($resourceName) : $resourceName => $dataArray];
|
||||||
} else {
|
} else {
|
||||||
$resource = [Str::singular($resourceName) => $dataArray];
|
$resource = [$transformResourceName === true ? Str::singular($resourceName) : $resourceName => $dataArray];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($appendData !== null) {
|
if ($appendData !== null) {
|
||||||
@@ -180,4 +222,22 @@ class ApiController extends Controller
|
|||||||
|
|
||||||
return response()->json($resource, $respondCode);
|
return response()->json($resource, $respondCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Controller Model Class name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getModelClass(): string
|
||||||
|
{
|
||||||
|
$controllerClass = static::class;
|
||||||
|
|
||||||
|
$modelName = 'App\\Models\\' . Str::replaceLast('Controller', '', Str::afterLast($controllerClass, '\\'));
|
||||||
|
|
||||||
|
if (class_exists($modelName) === false) {
|
||||||
|
return $modelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $modelName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ use App\Enum\HttpResponseCodes;
|
|||||||
use App\Http\Requests\ArticleRequest;
|
use App\Http\Requests\ArticleRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use App\Models\Article;
|
use App\Models\Article;
|
||||||
|
use App\Traits\HasAttachments;
|
||||||
|
use App\Traits\HasGallery;
|
||||||
use Illuminate\Http\JsonResponse;
|
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;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ArticleController extends ApiController
|
class ArticleController extends ApiController
|
||||||
{
|
{
|
||||||
|
use HasAttachments;
|
||||||
|
use HasGallery;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ApplicationController constructor.
|
* ApplicationController constructor.
|
||||||
*/
|
*/
|
||||||
@@ -73,14 +75,23 @@ class ArticleController extends ApiController
|
|||||||
public function store(ArticleRequest $request)
|
public function store(ArticleRequest $request)
|
||||||
{
|
{
|
||||||
if (ArticleConductor::creatable() === true) {
|
if (ArticleConductor::creatable() === true) {
|
||||||
$article = Article::create($request->all());
|
$article = Article::create($request->except(['attachments', 'gallery']));
|
||||||
|
|
||||||
|
if ($request->has('attachments') === true) {
|
||||||
|
$article->addAttachments($request->get('attachments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('gallery') === true) {
|
||||||
|
$article->galleryAddMany($request->get('gallery'));
|
||||||
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
ArticleConductor::model($request, $article),
|
ArticleConductor::model($request, $article),
|
||||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}//end if
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +104,17 @@ class ArticleController extends ApiController
|
|||||||
public function update(ArticleRequest $request, Article $article)
|
public function update(ArticleRequest $request, Article $article)
|
||||||
{
|
{
|
||||||
if (ArticleConductor::updatable($article) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
$article->update($request->all());
|
if ($request->has('attachments') === true) {
|
||||||
|
$article->deleteAttachments();
|
||||||
|
$article->addAttachments($request->get('attachments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('gallery') === true) {
|
||||||
|
$article->gallery()->delete();
|
||||||
|
$article->galleryAddMany($request->get('gallery'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$article->update($request->except(['attachments', 'gallery']));
|
||||||
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,131 +136,4 @@ class ArticleController extends ApiController
|
|||||||
return $this->respondForbidden();
|
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): JsonResponse
|
|
||||||
{
|
|
||||||
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): JsonResponse
|
|
||||||
{
|
|
||||||
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.
|
|
||||||
* @throws BindingResolutionException
|
|
||||||
* @throws MassAssignmentException
|
|
||||||
*/
|
|
||||||
public function updateAttachments(Request $request, Article $article): JsonResponse
|
|
||||||
{
|
|
||||||
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.
|
|
||||||
* @throws BindingResolutionException
|
|
||||||
*/
|
|
||||||
public function deleteAttachment(Request $request, Article $article, Media $medium): JsonResponse
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,6 +31,7 @@ class AuthController extends ApiController
|
|||||||
* Current User details
|
* Current User details
|
||||||
*
|
*
|
||||||
* @param Request $request Current request data.
|
* @param Request $request Current request data.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function me(Request $request): JsonResponse
|
public function me(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -48,7 +49,11 @@ class AuthController extends ApiController
|
|||||||
{
|
{
|
||||||
$user = User::where('email', '=', $request->input('email'))->first();
|
$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 !== null &&
|
||||||
|
strlen($user->password) > 0 &&
|
||||||
|
Hash::check($request->input('password'), $user->password) === true
|
||||||
|
) {
|
||||||
if ($user->email_verified_at === null) {
|
if ($user->email_verified_at === null) {
|
||||||
return $this->respondWithErrors([
|
return $this->respondWithErrors([
|
||||||
'email' => 'Email address has not been verified.'
|
'email' => 'Email address has not been verified.'
|
||||||
@@ -86,6 +91,7 @@ class AuthController extends ApiController
|
|||||||
* Logout current user
|
* Logout current user
|
||||||
*
|
*
|
||||||
* @param Request $request Current request data.
|
* @param Request $request Current request data.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function logout(Request $request): JsonResponse
|
public function logout(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ use App\Conductors\UserConductor;
|
|||||||
use App\Http\Requests\EventRequest;
|
use App\Http\Requests\EventRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
|
||||||
class EventController extends ApiController
|
class EventController extends ApiController
|
||||||
{
|
{
|
||||||
@@ -66,7 +69,12 @@ class EventController extends ApiController
|
|||||||
public function store(EventRequest $request)
|
public function store(EventRequest $request)
|
||||||
{
|
{
|
||||||
if (EventConductor::creatable() === true) {
|
if (EventConductor::creatable() === true) {
|
||||||
$event = Event::create($request->all());
|
$event = Event::create($request->except(['attachments']));
|
||||||
|
|
||||||
|
if ($request->has('attachments') === true) {
|
||||||
|
$event->addAttachments($request->get('attachments'));
|
||||||
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
EventConductor::model($request, $event),
|
EventConductor::model($request, $event),
|
||||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||||
@@ -86,7 +94,12 @@ class EventController extends ApiController
|
|||||||
public function update(EventRequest $request, Event $event)
|
public function update(EventRequest $request, Event $event)
|
||||||
{
|
{
|
||||||
if (EventConductor::updatable($event) === true) {
|
if (EventConductor::updatable($event) === true) {
|
||||||
$event->update($request->all());
|
if ($request->has('attachments') === true) {
|
||||||
|
$event->deleteAttachments();
|
||||||
|
$event->addAttachments($request->get('attachments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->update($request->except(['attachments']));
|
||||||
return $this->respondAsResource(EventConductor::model($request, $event));
|
return $this->respondAsResource(EventConductor::model($request, $event));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,126 +123,12 @@ class EventController extends ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of attachments related to this model.
|
* List users of Event
|
||||||
*
|
* @param Request $request The HTTP request.
|
||||||
* @param Request $request The user request.
|
* @param Event $event Event model.
|
||||||
* @param Event $event The event model.
|
* @return JsonResponse
|
||||||
* @return JsonResponse Returns the event attachments.
|
|
||||||
*/
|
*/
|
||||||
public function getAttachments(Request $request, Event $event): JsonResponse
|
public function userList(Request $request, Event $event): JsonResponse
|
||||||
{
|
|
||||||
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): JsonResponse
|
|
||||||
{
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
public function updateAttachments(Request $request, Event $event): JsonResponse
|
|
||||||
{
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
public function deleteAttachment(Request $request, Event $event, Media $medium): JsonResponse
|
|
||||||
{
|
|
||||||
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();
|
$authUser = $request->user();
|
||||||
$eventUsers = $event->users;
|
$eventUsers = $event->users;
|
||||||
@@ -245,16 +144,28 @@ class EventController extends ApiController
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(UserConductor::collection($request, $eventUsers), ['isCollection' => true, 'resourceName' => 'users']);
|
return $this->respondAsResource(
|
||||||
|
UserConductor::collection($request, $eventUsers),
|
||||||
|
[
|
||||||
|
'isCollection' => true,
|
||||||
|
'resourceName' => 'users'
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondNotFound();
|
return $this->respondNotFound();
|
||||||
}
|
}//end if
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userAdd(Request $request, Event $event)
|
/**
|
||||||
|
* Add user to Event
|
||||||
|
* @param Request $request The HTTP request.
|
||||||
|
* @param Event $event Event model.
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function userAdd(Request $request, Event $event): JsonResponse
|
||||||
{
|
{
|
||||||
$authUser = $request->user();
|
$authUser = $request->user();
|
||||||
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
||||||
@@ -286,12 +197,26 @@ class EventController extends ApiController
|
|||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userUpdate(Request $request, Event $event)
|
/**
|
||||||
|
* Update user
|
||||||
|
* @param Request $request The HTTP request.
|
||||||
|
* @param Event $event Event model.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function userUpdate(Request $request, Event $event): void
|
||||||
{
|
{
|
||||||
// only admin/events permitted
|
// only admin/events permitted
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userDelete(Request $request, Event $event, User $user)
|
/**
|
||||||
|
* Delete user from event
|
||||||
|
*
|
||||||
|
* @param Request $request The HTTP request.
|
||||||
|
* @param Event $event Event model.
|
||||||
|
* @param User $user User model.
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function userDelete(Request $request, Event $event, User $user): JsonResponse
|
||||||
{
|
{
|
||||||
$authUser = $request->user();
|
$authUser = $request->user();
|
||||||
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
||||||
|
|||||||
26
app/Http/Controllers/Api/InfoController.php
Normal file
26
app/Http/Controllers/Api/InfoController.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class InfoController extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$info = [
|
||||||
|
"version" => "1.0.0",
|
||||||
|
"max_upload_size" => Media::getMaxUploadSize()
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->respondJson($info);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,12 @@ class LogController extends ApiController
|
|||||||
|
|
||||||
$before = $request->get('before');
|
$before = $request->get('before');
|
||||||
if ($before !== null) {
|
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));
|
$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) {
|
if (count($before) !== 6) {
|
||||||
$before = null;
|
$before = null;
|
||||||
}
|
}
|
||||||
@@ -53,7 +58,12 @@ class LogController extends ApiController
|
|||||||
|
|
||||||
$after = $request->get('after');
|
$after = $request->get('after');
|
||||||
if ($after !== null) {
|
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));
|
$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) {
|
if (count($after) !== 6) {
|
||||||
$after = null;
|
$after = null;
|
||||||
}
|
}
|
||||||
@@ -77,30 +87,59 @@ class LogController extends ApiController
|
|||||||
$logContent = file_get_contents($logFile['path']);
|
$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));
|
$logArray = preg_split(
|
||||||
|
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||||
|
"/(\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 = '';
|
$logContent = '';
|
||||||
$logLineCount = 0;
|
$logLineCount = 0;
|
||||||
$logLineSkip = false;
|
$logLineSkip = false;
|
||||||
foreach (array_reverse($logArray) as $logLine) {
|
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));
|
$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) {
|
if (count($lineDate) >= 6) {
|
||||||
$logLineSkip = false;
|
$logLineSkip = false;
|
||||||
|
|
||||||
// Is line before
|
// 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])) {
|
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;
|
$logLineSkip = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is line after
|
// 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])) {
|
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;
|
$logLineSkip = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$logLineCount += 1;
|
$logLineCount += 1;
|
||||||
}
|
}//end if
|
||||||
|
|
||||||
if ($logLineCount > $lines) {
|
if ($logLineCount > $lines) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -3,12 +3,18 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Conductors\MediaConductor;
|
use App\Conductors\MediaConductor;
|
||||||
|
use App\Conductors\MediaJobConductor;
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\MediaRequest;
|
use App\Http\Requests\MediaRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
|
use App\Models\MediaJob;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Laravel\Sanctum\PersonalAccessToken;
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
class MediaController extends ApiController
|
class MediaController extends ApiController
|
||||||
@@ -67,45 +73,18 @@ class MediaController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function store(MediaRequest $request)
|
public function store(MediaRequest $request)
|
||||||
{
|
{
|
||||||
if (MediaConductor::creatable() === true) {
|
// allowed to create a media item
|
||||||
$file = $request->file('file');
|
if (MediaConductor::creatable() === false) {
|
||||||
if ($file === null) {
|
return $this->respondForbidden();
|
||||||
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ($file->isValid() !== true) {
|
// check for file
|
||||||
switch ($file->getError()) {
|
$file = $request->file('file');
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
if ($file === null) {
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
||||||
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->storeOrUpdate($request, null);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,43 +96,202 @@ class MediaController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function update(MediaRequest $request, Media $medium)
|
public function update(MediaRequest $request, Media $medium)
|
||||||
{
|
{
|
||||||
if (MediaConductor::updatable($medium) === true) {
|
// allowed to update a media item
|
||||||
$file = $request->file('file');
|
if (MediaConductor::updatable($medium) === false) {
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->storeOrUpdate($request, $medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a new media resource
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\MediaRequest $request The uploaded media.
|
||||||
|
* @param \App\Models\Media|null $medium The specified media.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function storeOrUpdate(MediaRequest $request, Media|null $medium)
|
||||||
|
{
|
||||||
|
$file = $request->file('file');
|
||||||
|
if ($file !== null) {
|
||||||
|
// validate file object
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create/get media job
|
||||||
|
$mediaJob = null;
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if ($request->missing('job_id') === true) {
|
||||||
|
/** @var \App\Models\User */
|
||||||
|
$user = auth()->user();
|
||||||
|
|
||||||
|
$mediaJob = new MediaJob();
|
||||||
|
$mediaJob->user_id = $user->id;
|
||||||
|
if ($medium !== null) {
|
||||||
|
$mediaJob->media_id = $medium->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('title') === true || $file !== null) {
|
||||||
|
$data['title'] = $request->get('title', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('name') === true || $file !== null) {
|
||||||
|
$data['name'] = (
|
||||||
|
$request->has('chunk') === true ? $request->get('name', '') : $file->getClientOriginalName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($file !== null) {
|
if ($file !== null) {
|
||||||
if ($file->isValid() !== true) {
|
$data['size'] = $request->has('chunk') === true ? intval($request->get('size', 0)) : $file->getSize();
|
||||||
switch ($file->getError()) {
|
$data['mime_type'] = (
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
$request->has('chunk') === true ? $request->get('mime_type', '') : $file->getMimeType()
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
);
|
||||||
return $this->respondTooLarge();
|
}
|
||||||
case UPLOAD_ERR_PARTIAL:
|
|
||||||
return $this->respondWithErrors(['file' => 'The file upload was interrupted.']);
|
if ($request->has('storage') === true || $file !== null) {
|
||||||
default:
|
$data['storage'] = $request->get('storage', '');
|
||||||
return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']);
|
}
|
||||||
|
|
||||||
|
if ($request->has('security_type') === true || $file !== null) {
|
||||||
|
$data['security']['type'] = $request->get('security_type', '');
|
||||||
|
$data['security']['data'] = $request->get('security_data', '');
|
||||||
|
|
||||||
|
if ($data['security']['type'] === '') {
|
||||||
|
$data['security']['data'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($medium === null || strcasecmp($data['security']['type'], $medium->security_type) !== 0) {
|
||||||
|
if ($request->has('storage') === false) {
|
||||||
|
$mime_type = $request->get('mime_type', $medium === null ? '' : $medium->mime_type);
|
||||||
|
$data['storage'] = Media::recommendedStorage($mime_type, $data['security']['type']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
array_key_exists('storage', $data) === true && (
|
||||||
|
array_key_exists('security', $data) === true &&
|
||||||
|
array_key_exists('type', $data['security']) === true
|
||||||
|
) &&
|
||||||
|
array_key_exists('mime_type', $data) === true &&
|
||||||
|
$data['mime_type'] !== ""
|
||||||
|
) {
|
||||||
|
$error = Media::verifyStorage($data['mime_type'], $data['security']['type'], $data['storage']);
|
||||||
|
// Log::error($data['mime_type'] . ' - ' . $data['security']['type'] . ' - ' . $data['storage']);
|
||||||
|
switch ($error) {
|
||||||
|
case Media::STORAGE_VALID:
|
||||||
|
break;
|
||||||
|
case Media::STORAGE_MIME_MISSING:
|
||||||
|
return $this->respondWithErrors(['mime_type' => 'The file type is required.']);
|
||||||
|
case Media::STORAGE_NOT_FOUND:
|
||||||
|
return $this->respondWithErrors(['storage' => 'Storage was not found.']);
|
||||||
|
case Media::STORAGE_INVALID_SECURITY:
|
||||||
|
return $this->respondWithErrors(
|
||||||
|
['storage' => 'Storage invalid for this security requirement.']
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return $this->respondWithErrors(['storage' => 'Storage verification error occurred.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('transform') === true) {
|
||||||
|
$transform = [];
|
||||||
|
|
||||||
|
foreach (explode(',', $request->get('transform', '')) as $value) {
|
||||||
|
if (is_string($value) === true) {
|
||||||
|
if (preg_match('/^rotate-(-?\d+)$/', $value, $matches) !== false) {
|
||||||
|
$transform['rotate'] = $matches[1];
|
||||||
|
} elseif (preg_match('/^flip-([vh]|vh|hv)$/', $value, $matches) !== false) {
|
||||||
|
$transform['flip'] = $matches[1];
|
||||||
|
} elseif (preg_match('/^crop-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||||
|
$transform['crop'] = ['width' => $matches[1], 'height' => $matches[2]];
|
||||||
|
} elseif (preg_match('/^crop-(\d+)-(\d+)-(\d+)-(\d+)$/', $value, $matches) !== false) {
|
||||||
|
$transform['crop'] = [
|
||||||
|
'width' => $matches[1],
|
||||||
|
'height' => $matches[2],
|
||||||
|
'x' => $matches[3],
|
||||||
|
'y' => $matches[4]
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file->getSize() > Media::getMaxUploadSize()) {
|
if (count($transform) > 0) {
|
||||||
return $this->respondTooLarge();
|
$data['transform'] = $transform;
|
||||||
}
|
}
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
$mediaJob->setStatusWaiting();
|
||||||
|
} else {
|
||||||
|
$mediaJob = MediaJob::find($request->get('job_id'));
|
||||||
|
if ($mediaJob === null || $mediaJob->exists() === false) {
|
||||||
|
$this->respondNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
$medium->update($request->all());
|
$data = json_decode($mediaJob->data, true);
|
||||||
|
if ($data === null) {
|
||||||
if ($file !== null) {
|
Log::error(`{$mediaJob->id} contains no data`);
|
||||||
try {
|
return $this->respondServerError();
|
||||||
$medium->updateWithUploadedFile($file);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->respondWithErrors(
|
|
||||||
['file' => $e->getMessage()],
|
|
||||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
if (array_key_exists('name', $data) === false) {
|
||||||
|
Log::error(`{$mediaJob->id} data does not contain the name key`);
|
||||||
|
return $this->respondServerError();
|
||||||
|
}
|
||||||
}//end if
|
}//end if
|
||||||
|
|
||||||
return $this->respondForbidden();
|
if ($mediaJob === null) {
|
||||||
|
Log::error(`media job does not exist`);
|
||||||
|
return $this->respondServerError();
|
||||||
|
}
|
||||||
|
|
||||||
|
// save uploaded file
|
||||||
|
if ($file !== null) {
|
||||||
|
if ($data['name'] === '') {
|
||||||
|
Log::error(`filename does not exist`);
|
||||||
|
return $this->respondServerError();
|
||||||
|
}
|
||||||
|
|
||||||
|
$temporaryFilePath = generateTempFilePath(
|
||||||
|
pathinfo($data['name'], PATHINFO_EXTENSION),
|
||||||
|
$request->get('chunk', '')
|
||||||
|
);
|
||||||
|
copy($file->path(), $temporaryFilePath);
|
||||||
|
|
||||||
|
if ($request->has('chunk') === true) {
|
||||||
|
$data['chunks'][$request->get('chunk', '1')] = $temporaryFilePath;
|
||||||
|
$data['chunk_count'] = $request->get('chunk_count', 1);
|
||||||
|
} else {
|
||||||
|
$data['file'] = $temporaryFilePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaJob->data = json_encode($data, true);
|
||||||
|
$mediaJob->save();
|
||||||
|
$mediaJob->process();
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
MediaJobConductor::model($request, $mediaJob),
|
||||||
|
['resourceName' => 'media_job', 'respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,52 +314,59 @@ class MediaController extends ApiController
|
|||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
* @param \App\Models\Media $medium Specified media.
|
* @param \App\Models\Media $media Specified media.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function download(Request $request, Media $medium)
|
public function download(Request $request, Media $media): Response
|
||||||
{
|
{
|
||||||
$respondJson = in_array('application/json', explode(',', $request->header('Accept', 'application/json')));
|
|
||||||
|
|
||||||
$headers = [];
|
$headers = [];
|
||||||
$path = $medium->path();
|
|
||||||
|
|
||||||
/* File exists */
|
/* Check file exists */
|
||||||
if (file_exists($path) === false) {
|
if (Storage::disk($media->storage)->exists($media->name) === false) {
|
||||||
if ($respondJson === false) {
|
return $this->respondNotFound();
|
||||||
return redirect('/not-found');
|
|
||||||
} else {
|
|
||||||
return $this->respondNotFound();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$updated_at = Carbon::parse(filemtime($path));
|
$updated_at = Carbon::parse(Storage::disk($media->storage)->lastModified($media->name));
|
||||||
|
|
||||||
$headerPragma = 'no-cache';
|
$headerPragma = 'no-cache';
|
||||||
$headerCacheControl = 'max-age=0, must-revalidate';
|
$headerCacheControl = 'max-age=0, must-revalidate';
|
||||||
$headerExpires = $updated_at->toRfc2822String();
|
$headerExpires = $updated_at->toRfc2822String();
|
||||||
|
|
||||||
if (empty($medium->permission) === true) {
|
/* construct user if can */
|
||||||
if ($request->user() === null && $request->has('token') === true) {
|
$user = $request->user();
|
||||||
$accessToken = PersonalAccessToken::findToken(urldecode($request->input('token')));
|
if ($user === null && $request->has('token') === true) {
|
||||||
|
$accessToken = PersonalAccessToken::findToken(urldecode($request->input('token')));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$accessToken !== null && (config('sanctum.expiration') === null ||
|
$accessToken !== null && (config('sanctum.expiration') === null ||
|
||||||
$accessToken->created_at->lte(now()->subMinutes(config('sanctum.expiration'))) === false)
|
$accessToken->created_at->lte(now()->subMinutes(config('sanctum.expiration'))) === false)
|
||||||
) {
|
) {
|
||||||
$user = $accessToken->tokenable;
|
$user = $accessToken->tokenable;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($request->user() === null || $user->hasPermission($medium->permission) === false) {
|
}
|
||||||
if ($respondJson === false) {
|
|
||||||
return redirect('/login?redirect=' . $request->path());
|
if ($media->security_type === '') {
|
||||||
} else {
|
/* no security */
|
||||||
return $this->respondForbidden();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$headerPragma = 'public';
|
$headerPragma = 'public';
|
||||||
$headerExpires = $updated_at->addMonth()->toRfc2822String();
|
$headerExpires = $updated_at->addMonth()->toRfc2822String();
|
||||||
|
} elseif (strcasecmp('password', $media->security_type) === 0) {
|
||||||
|
/* password */
|
||||||
|
if (
|
||||||
|
($user === null || $user->hasPermission('admin/media') === false) &&
|
||||||
|
($request->has('password') === false || $request->get('password') !== $media->security_data)
|
||||||
|
) {
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
} elseif (strcasecmp('permission', $media->security_type) === 0) {
|
||||||
|
/* permission */
|
||||||
|
if (
|
||||||
|
$user === null || (
|
||||||
|
$user->hasPermission('admin/media') === false &&
|
||||||
|
$user->hasPermission($media->security_data) === false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
}//end if
|
}//end if
|
||||||
|
|
||||||
// deepcode ignore InsecureHash: Browsers expect Etag to be a md5 hash
|
// deepcode ignore InsecureHash: Browsers expect Etag to be a md5 hash
|
||||||
@@ -230,7 +375,7 @@ class MediaController extends ApiController
|
|||||||
|
|
||||||
$headers = [
|
$headers = [
|
||||||
'Cache-Control' => $headerCacheControl,
|
'Cache-Control' => $headerCacheControl,
|
||||||
'Content-Disposition' => sprintf('inline; filename="%s"', basename($path)),
|
'Content-Disposition' => sprintf('inline; filename="%s"', basename($media->name)),
|
||||||
'Etag' => $headerEtag,
|
'Etag' => $headerEtag,
|
||||||
'Expires' => $headerExpires,
|
'Expires' => $headerExpires,
|
||||||
'Last-Modified' => $headerLastModified,
|
'Last-Modified' => $headerLastModified,
|
||||||
@@ -249,6 +394,50 @@ class MediaController extends ApiController
|
|||||||
return response()->make('', 304, $headers);
|
return response()->make('', 304, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->file($path, $headers);
|
$headers['Content-Type'] = Storage::disk($media->storage)->mimeType($media->name);
|
||||||
|
$headers['Content-Length'] = Storage::disk($media->storage)->size($media->name);
|
||||||
|
$headers['Content-Disposition'] = 'attachment; filename="' . basename($media->name) . '"';
|
||||||
|
|
||||||
|
$stream = Storage::disk($media->storage)->readStream($media->name);
|
||||||
|
return response()->stream(
|
||||||
|
function () use ($stream) {
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
ob_end_flush();
|
||||||
|
}
|
||||||
|
fpassthru($stream);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
$headers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a File item in a request is valid
|
||||||
|
*
|
||||||
|
* @param UploadedFile $file The file to validate.
|
||||||
|
* @param string $errorKey The error key to use.
|
||||||
|
* @return JsonResponse|null
|
||||||
|
*/
|
||||||
|
private function validateFileObject(UploadedFile $file, string $errorKey = 'file'): JsonResponse|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([$errorKey => 'The file upload was interrupted.']);
|
||||||
|
default:
|
||||||
|
return $this->respondWithErrors(
|
||||||
|
[$errorKey => 'An error occurred uploading the file to the server.']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||||
|
return $this->respondTooLarge();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
app/Http/Controllers/Api/MediaJobController.php
Normal file
52
app/Http/Controllers/Api/MediaJobController.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Conductors\MediaJobConductor;
|
||||||
|
use App\Http\Controllers\Api\ApiController;
|
||||||
|
use App\Models\MediaJob;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class MediaJobController extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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) = MediaJobConductor::request($request);
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
$collection,
|
||||||
|
['isCollection' => true,
|
||||||
|
'appendData' => ['total' => $total],
|
||||||
|
'resourceName' => 'media_job'
|
||||||
|
],
|
||||||
|
function ($options) {
|
||||||
|
return $options['total'] === 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
|
* @param \App\Models\MediaJob $mediaJob The request media job.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function show(Request $request, MediaJob $mediaJob)
|
||||||
|
{
|
||||||
|
if (MediaJobConductor::viewable($mediaJob) === true) {
|
||||||
|
return $this->respondAsResource(
|
||||||
|
MediaJobConductor::model($request, $mediaJob),
|
||||||
|
['resourceName' => 'media_job']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,7 +101,10 @@ class OCRController extends ApiController
|
|||||||
$tesseractImageFilterFunc = function ($filter, $options = null) use ($curlResult, $curlSize, $ocr) {
|
$tesseractImageFilterFunc = function ($filter, $options = null) use ($curlResult, $curlSize, $ocr) {
|
||||||
$result = '';
|
$result = '';
|
||||||
$img = imagecreatefromstring($curlResult);
|
$img = imagecreatefromstring($curlResult);
|
||||||
if ($img !== false && (($options !== null && imagefilter($img, $filter, $options) === true) || ($options === null && imagefilter($img, $filter) === true))) {
|
if (
|
||||||
|
$img !== false && (($options !== null && imagefilter($img, $filter, $options) === true) ||
|
||||||
|
($options === null && imagefilter($img, $filter) === true))
|
||||||
|
) {
|
||||||
ob_start();
|
ob_start();
|
||||||
imagepng($img);
|
imagepng($img);
|
||||||
$imgData = ob_get_contents();
|
$imgData = ob_get_contents();
|
||||||
|
|||||||
@@ -2,17 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Conductors\MediaConductor;
|
|
||||||
use App\Conductors\ShortlinkConductor;
|
use App\Conductors\ShortlinkConductor;
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\MediaRequest;
|
|
||||||
use App\Http\Requests\ShortlinkRequest;
|
use App\Http\Requests\ShortlinkRequest;
|
||||||
use App\Models\Media;
|
|
||||||
use App\Models\Shortlink;
|
use App\Models\Shortlink;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Laravel\Sanctum\PersonalAccessToken;
|
|
||||||
|
|
||||||
class ShortlinkController extends ApiController
|
class ShortlinkController extends ApiController
|
||||||
{
|
{
|
||||||
@@ -85,8 +79,8 @@ class ShortlinkController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Update the media resource in storage.
|
* Update the media resource in storage.
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\ShortlinkRequest $request The update request.
|
* @param \App\Http\Requests\ShortlinkRequest $request The update request.
|
||||||
* @param \App\Models\Shortlink $medium The specified shortlink.
|
* @param \App\Models\Shortlink $shortlink The specified shortlink.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(ShortlinkRequest $request, Shortlink $shortlink)
|
public function update(ShortlinkRequest $request, Shortlink $shortlink)
|
||||||
@@ -102,7 +96,7 @@ class ShortlinkController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Remove the specified resource from storage.
|
* Remove the specified resource from storage.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Shortlink $medium Specified shortlink.
|
* @param \App\Models\Shortlink $shortlink Specified shortlink.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy(Shortlink $shortlink)
|
public function destroy(Shortlink $shortlink)
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -73,7 +73,10 @@ class UserController extends ApiController
|
|||||||
{
|
{
|
||||||
if (UserConductor::creatable() === true) {
|
if (UserConductor::creatable() === true) {
|
||||||
$user = User::create($request->all());
|
$user = User::create($request->all());
|
||||||
return $this->respondAsResource(UserConductor::model($request, $user), ['respondCode' => HttpResponseCodes::HTTP_CREATED]);
|
return $this->respondAsResource(
|
||||||
|
UserConductor::model($request, $user),
|
||||||
|
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
@@ -145,6 +148,7 @@ class UserController extends ApiController
|
|||||||
* Register a new user
|
* Register a new user
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\UserRegisterRequest $request The register user request.
|
* @param \App\Http\Requests\UserRegisterRequest $request The register user request.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function register(UserRegisterRequest $request): JsonResponse
|
public function register(UserRegisterRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -285,6 +289,7 @@ class UserController extends ApiController
|
|||||||
* Resend a new verify email
|
* Resend a new verify email
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\UserResendVerifyEmailRequest $request The resend verify email request.
|
* @param \App\Http\Requests\UserResendVerifyEmailRequest $request The resend verify email request.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function resendVerifyEmail(UserResendVerifyEmailRequest $request): JsonResponse
|
public function resendVerifyEmail(UserResendVerifyEmailRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -338,10 +343,15 @@ class UserController extends ApiController
|
|||||||
*
|
*
|
||||||
* @param Request $request The http request.
|
* @param Request $request The http request.
|
||||||
* @param User $user The specified user.
|
* @param User $user The specified user.
|
||||||
|
* @return JsonResponse
|
||||||
*/
|
*/
|
||||||
public function eventList(Request $request, User $user): JsonResponse
|
public function eventList(Request $request, User $user): JsonResponse
|
||||||
{
|
{
|
||||||
if ($request->user() !== null && ($request->user() === $user || $request->user()->hasPermission('admin/events') === true)) {
|
if (
|
||||||
|
$request->user() !== null && (
|
||||||
|
$request->user() === $user || $request->user()->hasPermission('admin/events') === true
|
||||||
|
)
|
||||||
|
) {
|
||||||
$collection = $user->events;
|
$collection = $user->events;
|
||||||
$total = $collection->count();
|
$total = $collection->count();
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class Kernel extends HttpKernel
|
|||||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
// \App\Http\Middleware\ForceJsonResponse::class,
|
// \App\Http\Middleware\ForceJsonResponse::class,
|
||||||
|
\App\Http\Middleware\UnmangleRequest::class,
|
||||||
'useSanctumGuard',
|
'useSanctumGuard',
|
||||||
\App\Http\Middleware\LogRequest::class,
|
\App\Http\Middleware\LogRequest::class,
|
||||||
],
|
],
|
||||||
@@ -62,6 +63,7 @@ class Kernel extends HttpKernel
|
|||||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
|
'unmangle' => \App\Http\Middleware\UnmangleRequest::class,
|
||||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ class Authenticate extends Middleware
|
|||||||
* Get the path the user should be redirected to when they are not authenticated.
|
* Get the path the user should be redirected to when they are not authenticated.
|
||||||
*
|
*
|
||||||
* @param mixed $request Request.
|
* @param mixed $request Request.
|
||||||
|
* @return ?string
|
||||||
*/
|
*/
|
||||||
protected function redirectTo(mixed $request): ?string
|
protected function redirectTo(mixed $request): ?string
|
||||||
{
|
{
|
||||||
if ($request->expectsJson() === false) {
|
if ($request->expectsJson() === false) {
|
||||||
return route('login');
|
return route('login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Models\AnalyticsItemRequest;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\Analytics;
|
|
||||||
|
|
||||||
class LogRequest
|
class LogRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
* @param Illuminate\Http\Request $request HTTP Request.
|
||||||
|
* @param \Closure $next Closure.
|
||||||
|
* @return Symfony\Component\HttpFoundation\Response
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
@@ -20,11 +22,9 @@ class LogRequest
|
|||||||
$response = $next($request);
|
$response = $next($request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Analytics::createWithSession([
|
AnalyticsItemRequest::create([
|
||||||
'type' => 'apirequest',
|
'type' => 'apirequest',
|
||||||
'attribute' => $request->path(),
|
'path' => $request->path(),
|
||||||
'useragent' => $request->userAgent(),
|
|
||||||
'ip' => $request->ip(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ class RedirectIfAuthenticated
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param Request $request Request.
|
* @param Request $request Request.
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
* @param \Closure $next Closure.
|
||||||
* @param string|null ...$guards Guards.
|
* @param string|null ...$guards Guards.
|
||||||
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||||
{
|
{
|
||||||
|
|||||||
47
app/Http/Middleware/UnmangleRequest.php
Normal file
47
app/Http/Middleware/UnmangleRequest.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class UnmangleRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Request $request Request.
|
||||||
|
* @param \Closure $next Next.
|
||||||
|
* @param string|null ...$guards Guards.
|
||||||
|
* @return Response response.
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||||
|
{
|
||||||
|
if (isset($_SERVER['QUERY_STRING']) === true) {
|
||||||
|
$params = $request->all();
|
||||||
|
|
||||||
|
$string = $_SERVER['QUERY_STRING'];
|
||||||
|
$parts = explode('&', $string);
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$key = $part;
|
||||||
|
$splitPos = strpos($key, '=');
|
||||||
|
if ($splitPos !== false) {
|
||||||
|
$key = urldecode(substr($key, 0, $splitPos));
|
||||||
|
}
|
||||||
|
|
||||||
|
$replace_key = str_replace('.', '_', $key);
|
||||||
|
if (strpos($key, '.') !== false && array_key_exists($replace_key, $params) === true) {
|
||||||
|
$params[$key] = $params[$replace_key];
|
||||||
|
unset($params[$replace_key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->replace($params);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ class UseSanctumGuard
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
* @param Request $request Request object.
|
||||||
|
* @param \Closure $next Closure object.
|
||||||
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,12 +9,18 @@ class BaseRequest extends FormRequest
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Determine if the user is authorized to make this request.
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
if (request()->isMethod('post') === true && method_exists($this, 'postAuthorize') === true) {
|
if (request()->isMethod('post') === true && method_exists($this, 'postAuthorize') === true) {
|
||||||
return $this->postAuthorize();
|
return $this->postAuthorize();
|
||||||
} elseif ((request()->isMethod('put') === true || request()->isMethod('patch') === true) && method_exists($this, 'putAuthorize') === true) {
|
} elseif (
|
||||||
|
(
|
||||||
|
request()->isMethod('put') === true || request()->isMethod('patch') === true
|
||||||
|
) && method_exists($this, 'putAuthorize') === true
|
||||||
|
) {
|
||||||
return $this->putAuthorize();
|
return $this->putAuthorize();
|
||||||
} elseif (request()->isMethod('delete') === true && method_exists($this, 'destroyAuthorize') === true) {
|
} elseif (request()->isMethod('delete') === true && method_exists($this, 'destroyAuthorize') === true) {
|
||||||
return $this->deleteAuthorize();
|
return $this->deleteAuthorize();
|
||||||
@@ -38,7 +44,11 @@ class BaseRequest extends FormRequest
|
|||||||
|
|
||||||
if (method_exists($this, 'postRules') === true && request()->isMethod('post') === true) {
|
if (method_exists($this, 'postRules') === true && request()->isMethod('post') === true) {
|
||||||
$rules = $this->mergeRules($rules, $this->postRules());
|
$rules = $this->mergeRules($rules, $this->postRules());
|
||||||
} elseif (method_exists($this, 'putRules') === true && (request()->isMethod('put') === true || request()->isMethod('patch') === true)) {
|
} elseif (
|
||||||
|
method_exists($this, 'putRules') === true && (
|
||||||
|
request()->isMethod('put') === true || request()->isMethod('patch') === true
|
||||||
|
)
|
||||||
|
) {
|
||||||
$rules = $this->mergeRules($rules, $this->putRules());
|
$rules = $this->mergeRules($rules, $this->putRules());
|
||||||
} elseif (method_exists($this, 'destroyRules') === true && request()->isMethod('delete') === true) {
|
} elseif (method_exists($this, 'destroyRules') === true && request()->isMethod('delete') === true) {
|
||||||
$rules = $this->mergeRules($rules, $this->destroyRules());
|
$rules = $this->mergeRules($rules, $this->destroyRules());
|
||||||
@@ -52,6 +62,7 @@ class BaseRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @param array $collection1 The first collection of rules.
|
* @param array $collection1 The first collection of rules.
|
||||||
* @param array $collection2 The second collection of rules to merge.
|
* @param array $collection2 The second collection of rules to merge.
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function mergeRules(array $collection1, array $collection2): array
|
private function mergeRules(array $collection1, array $collection2): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class EventRequest extends BaseRequest
|
|||||||
'end_at' => 'date|after:start_date',
|
'end_at' => 'date|after:start_date',
|
||||||
'publish_at' => 'date|nullable',
|
'publish_at' => 'date|nullable',
|
||||||
'status' => [
|
'status' => [
|
||||||
Rule::in(['draft', 'soon', 'open', 'closed', 'cancelled']),
|
Rule::in(['draft', 'soon', 'open', 'closed', 'cancelled', 'scheduled', 'full']),
|
||||||
],
|
],
|
||||||
'registration_type' => [
|
'registration_type' => [
|
||||||
Rule::in(['none', 'email', 'link', 'message']),
|
Rule::in(['none', 'email', 'link', 'message']),
|
||||||
@@ -34,6 +34,7 @@ class EventRequest extends BaseRequest
|
|||||||
Rule::when(strcasecmp('message', $this->attributes->get('registration_type')) == 0, 'required|message'),
|
Rule::when(strcasecmp('message', $this->attributes->get('registration_type')) == 0, 'required|message'),
|
||||||
],
|
],
|
||||||
'hero' => 'uuid|exists:media,id',
|
'hero' => 'uuid|exists:media,id',
|
||||||
|
'location_url' => 'sometimes|string|max:255',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class MediaRequest extends BaseRequest
|
class MediaRequest extends BaseRequest
|
||||||
{
|
{
|
||||||
/* empty */
|
/**
|
||||||
|
* POST request rules
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function postRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'job_id' => [
|
||||||
|
Rule::requiredIf(function () {
|
||||||
|
return request()->has('chunk') && request('chunk') != 1;
|
||||||
|
}),
|
||||||
|
'string',
|
||||||
|
],
|
||||||
|
'name' => [
|
||||||
|
Rule::requiredIf(function () {
|
||||||
|
return request()->has('chunk') && request('chunk') == 1;
|
||||||
|
}),
|
||||||
|
'string',
|
||||||
|
],
|
||||||
|
'chunk' => 'required_with:chunk_count|integer|min:1|max:999|lte:chunk_count',
|
||||||
|
'chunk_count' => 'required_with:chunk|integer|min:1',
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class SubscriptionRequest extends BaseRequest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the custom error messages.
|
* Get the custom error messages.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function messages(): array
|
public function messages(): array
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ class UserRequest extends BaseRequest
|
|||||||
$isAdminUser = $user->hasPermission('admin/users');
|
$isAdminUser = $user->hasPermission('admin/users');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'first_name' => ($isAdminUser === true ? 'required_with:last_name,display_name,phone' : 'required') . '|string|max:255|min:2',
|
'first_name' => (
|
||||||
'last_name' => ($isAdminUser === true ? 'required_with:first_name,display_name,phone' : 'required') . '|string|max:255|min:2',
|
$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' => [
|
'display_name' => [
|
||||||
$isAdminUser === true ? 'required_with:first_name,last_name,phone' : 'required',
|
$isAdminUser === true ? 'required_with:first_name,last_name,phone' : 'required',
|
||||||
'string',
|
'string',
|
||||||
|
|||||||
393
app/Jobs/MediaWorkerJob.php
Normal file
393
app/Jobs/MediaWorkerJob.php
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\MediaJob;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use FFMpeg;
|
||||||
|
use FFMpeg\Coordinate\Dimension;
|
||||||
|
use FFMpeg\FFProbe;
|
||||||
|
use FFMpeg\Format\VideoInterface;
|
||||||
|
use Intervention\Image\Facades\Image;
|
||||||
|
|
||||||
|
/** @property on $format */
|
||||||
|
class MediaWorkerJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MediaJob item
|
||||||
|
*
|
||||||
|
* @var MediaJob
|
||||||
|
*/
|
||||||
|
protected $mediaJob;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param MediaJob $mediaJob The mediaJob model.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(MediaJob $mediaJob)
|
||||||
|
{
|
||||||
|
$this->mediaJob = $mediaJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$media = $this->mediaJob->media()->first();
|
||||||
|
$newMedia = false;
|
||||||
|
$data = json_decode($this->mediaJob->data, true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// FILE
|
||||||
|
if (array_key_exists('file', $data) === true) {
|
||||||
|
if (file_exists($data['file']) === false) {
|
||||||
|
$this->throwMediaJobFailure('temporary upload file no longer exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert HEIC files to JPG
|
||||||
|
$fileExtension = File::extension($data['file']);
|
||||||
|
if ($fileExtension === 'heic') {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'converting image');
|
||||||
|
|
||||||
|
// Get the path without the file name
|
||||||
|
$uploadedFileDirectory = dirname($data['file']);
|
||||||
|
|
||||||
|
// Convert the HEIC file to JPG
|
||||||
|
$jpgFileName = pathinfo($data['file'], PATHINFO_FILENAME) . '.jpg';
|
||||||
|
$jpgFilePath = $uploadedFileDirectory . '/' . $jpgFileName;
|
||||||
|
if (file_exists($jpgFilePath) === true) {
|
||||||
|
$this->throwMediaJobFailure('file already exists on server');
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::make($data['file'])->save($jpgFilePath);
|
||||||
|
|
||||||
|
// Update the uploaded file path and file name
|
||||||
|
unlink($data['file']);
|
||||||
|
$data['file'] = $jpgFileName;
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
// get security
|
||||||
|
$security = [];
|
||||||
|
if ($media === null) {
|
||||||
|
if (array_key_exists('security', $data) === true) {
|
||||||
|
$security = $data['security'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$security['type'] = $media->security_type;
|
||||||
|
$security['data'] = $media->security_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get storage
|
||||||
|
$storage = '';
|
||||||
|
if ($media === null) {
|
||||||
|
if (array_key_exists('storage', $data) === true) {
|
||||||
|
$storage = $data['storage'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$storage = $media->storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($storage === '') {
|
||||||
|
if (count($security) === 0 || $security['type'] === '') {
|
||||||
|
if (strpos($data['mime_type'], 'image/') === 0) {
|
||||||
|
$storage = 'local';
|
||||||
|
} else {
|
||||||
|
$storage = 'cdn';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$storage = 'private';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file already exists
|
||||||
|
$exists = Storage::disk($storage)->exists($data['name']);
|
||||||
|
if ($exists === true) {
|
||||||
|
if (array_key_exists('noreplace', $data) === true && isTrue($data['noreplace']) === true) {
|
||||||
|
$this->throwMediaJobFailure('file already exists on server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($exists === true) {
|
||||||
|
$pathInfo = pathinfo($data['name']);
|
||||||
|
$basename = $pathInfo['filename'];
|
||||||
|
$extension = $pathInfo['extension'];
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$index++;
|
||||||
|
$data['name'] = $basename . '-' . $index . '.' . $extension;
|
||||||
|
} while (Storage::disk($storage)->exists($data['name']) === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($media === null) {
|
||||||
|
$newMedia = true;
|
||||||
|
$media = new Media([
|
||||||
|
'user_id' => $this->mediaJob->user_id,
|
||||||
|
'title' => $data['title'],
|
||||||
|
'name' => $data['name'],
|
||||||
|
'mime_type' => $data['mime_type'],
|
||||||
|
'size' => $data['size'],
|
||||||
|
'security_type' => $data['security']['type'],
|
||||||
|
'security_data' => $data['security']['data'],
|
||||||
|
'storage' => $storage,
|
||||||
|
]);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
$media->setStagingFile($data['file']);
|
||||||
|
} else {
|
||||||
|
if ($media === null) {
|
||||||
|
$this->throwMediaJobFailure('The media item no longer exists');
|
||||||
|
}
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
if (array_key_exists('transform', $data) === true) {
|
||||||
|
$media->createStagingFile();
|
||||||
|
|
||||||
|
// Modifications
|
||||||
|
if (strpos($media->mime_type, 'image/') === 0) {
|
||||||
|
$modified = false;
|
||||||
|
$image = Image::make($media->getStagingFilePath());
|
||||||
|
|
||||||
|
// ROTATE
|
||||||
|
if (array_key_exists("rotate", $data['transform']) === true) {
|
||||||
|
$rotate = intval($data['transform']['rotate']);
|
||||||
|
if ($rotate !== 0) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'rotating image');
|
||||||
|
$image = $image->rotate($rotate);
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FLIP-H/V
|
||||||
|
if (array_key_exists('flip', $data['transform']) === true) {
|
||||||
|
if (stripos($data['transform']['flip'], 'h') !== false) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'flipping image');
|
||||||
|
$image = $image->flip('h');
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stripos($data['transform']['flip'], 'v') !== false) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'flipping image');
|
||||||
|
$image = $image->flip('v');
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CROP
|
||||||
|
if (array_key_exists("crop", $data['transform']) === true) {
|
||||||
|
$cropData = $data['transform']['crop'];
|
||||||
|
$width = intval(arrayDefaultValue("width", $cropData, $image->getWidth()));
|
||||||
|
$height = intval(arrayDefaultValue("height", $cropData, $image->getHeight()));
|
||||||
|
$x = intval(arrayDefaultValue("x", $cropData, 0));
|
||||||
|
$y = intval(arrayDefaultValue("y", $cropData, 0));
|
||||||
|
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'cropping image');
|
||||||
|
$image = $image->crop($width, $height, $x, $y);
|
||||||
|
$modified = true;
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
if ($modified === true) {
|
||||||
|
$image->save();
|
||||||
|
}
|
||||||
|
} elseif (strpos($data['mime_type'], 'video/') === 0) {
|
||||||
|
$stagingFilePath = $media->getStagingFilePath();
|
||||||
|
$ffmpeg = FFMpeg\FFMpeg::create();
|
||||||
|
$video = $ffmpeg->open($stagingFilePath);
|
||||||
|
$format = $this->detectVideoFormat($stagingFilePath);
|
||||||
|
$modified = false;
|
||||||
|
|
||||||
|
if ($format === null) {
|
||||||
|
$this->mediaJob->setStatusFailed('Unsupported video format');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var FFMpeg\Media\Video::filters */
|
||||||
|
$filters = $video->filters();
|
||||||
|
|
||||||
|
// ROTATE
|
||||||
|
if (array_key_exists("rotate", $data['transform']) === true) {
|
||||||
|
$rotate = intval($data['transform']['rotate']);
|
||||||
|
$rotate = (($rotate % 360 + 360) % 360); // remove excess rotations
|
||||||
|
$rotate = intval(round($rotate / 90) * 90); // round to nearest 90%
|
||||||
|
|
||||||
|
if ($rotate > 0) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'rotating video');
|
||||||
|
|
||||||
|
if ($rotate === 90) {
|
||||||
|
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_270);
|
||||||
|
$modified = true;
|
||||||
|
} elseif ($rotate === 180) {
|
||||||
|
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_180);
|
||||||
|
$modified = true;
|
||||||
|
} elseif ($rotate === 270) {
|
||||||
|
$filters->rotate(FFMpeg\Filters\Video\RotateFilter::ROTATE_90);
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FLIP-H/V
|
||||||
|
if (array_key_exists('flip', $data['transform']) === true) {
|
||||||
|
if (stripos($data['transform']['flip'], 'h') !== false) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'flipping video');
|
||||||
|
$filters->hflip()->synchronize();
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stripos($data['transform']['flip'], 'v') !== false) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'flipping video');
|
||||||
|
$filters->vflip()->synchronize();
|
||||||
|
$modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CROP
|
||||||
|
if (array_key_exists("crop", $data['transform']) === true) {
|
||||||
|
$cropData = $data['transform']['crop'];
|
||||||
|
$videoStream = $video->getStreams()->videos()->first();
|
||||||
|
|
||||||
|
$width = intval(arrayDefaultValue("width", $cropData, $videoStream->get('width')));
|
||||||
|
$height = intval(arrayDefaultValue("height", $cropData, $videoStream->get('height')));
|
||||||
|
$x = intval(arrayDefaultValue("x", $cropData, 0));
|
||||||
|
$y = intval(arrayDefaultValue("y", $cropData, 0));
|
||||||
|
|
||||||
|
$cropDimension = new Dimension($width, $height);
|
||||||
|
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'cropping video');
|
||||||
|
$filters->crop($cropDimension, $x, $y)->synchronize();
|
||||||
|
$modified = true;
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
$tempFilePath = generateTempFilePath(pathinfo($stagingFilePath, PATHINFO_EXTENSION));
|
||||||
|
if (method_exists($format, 'on') === true) {
|
||||||
|
$mediaJob = $this->mediaJob;
|
||||||
|
$format->on('progress', function ($video, $format, $percentage) use ($mediaJob) {
|
||||||
|
$mediaJob->setStatusProcessing($percentage, 100, 'transcoded');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($modified === true) {
|
||||||
|
$video->save($format, $tempFilePath);
|
||||||
|
$media->changeStagingFile($tempFilePath);
|
||||||
|
}
|
||||||
|
}//end if
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
// Update attributes
|
||||||
|
if (array_key_exists('title', $data) === true) {
|
||||||
|
$media->title = $data['title'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relocate file (if requested)
|
||||||
|
if (array_key_exists('security', $data) === true && array_key_exists('type', $data['security']) === true) {
|
||||||
|
$media->security_type = $data['security']['type'];
|
||||||
|
$media->security_data = $data['security']['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('storage', $data) === true) {
|
||||||
|
if ($media->storage !== $data['storage']) {
|
||||||
|
$media->createStagingFile();
|
||||||
|
Storage::disk($media->storage)->delete($media->name);
|
||||||
|
$media->storage = $data['storage'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish media object
|
||||||
|
if ($media->hasStagingFile() === true) {
|
||||||
|
$this->mediaJob->setStatusProcessing(0, 0, 'transferring to cdn');
|
||||||
|
$media->deleteFile();
|
||||||
|
$media->saveStagingFile(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$media->save();
|
||||||
|
$this->mediaJob->media_id = $media->id;
|
||||||
|
$this->mediaJob->setStatusComplete();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if ($this->mediaJob->status !== 'failed') {
|
||||||
|
$this->mediaJob->setStatusFailed('Unexpected server error occurred');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($media !== null) {
|
||||||
|
$media->deleteStagingFile();
|
||||||
|
if ($newMedia === true) {
|
||||||
|
$media->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::error($e->getMessage() . "\n" . $e->getFile() . " - " . $e->getLine() . "\n" . $e->getTraceAsString());
|
||||||
|
$this->fail($e);
|
||||||
|
}//end try
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the format of a video using FFProbe
|
||||||
|
*
|
||||||
|
* @param string $videoPath The video file path.
|
||||||
|
* @return VideoInterface | null
|
||||||
|
*/
|
||||||
|
public function detectVideoFormat(string $videoPath): VideoInterface | null
|
||||||
|
{
|
||||||
|
$ffprobe = FFProbe::create();
|
||||||
|
|
||||||
|
$videoStream = $ffprobe
|
||||||
|
->streams($videoPath) // Provide the path to the video file
|
||||||
|
->videos() // Filter video streams
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$codecName = $videoStream->get('codec_name');
|
||||||
|
|
||||||
|
$codecToFormatClass = [
|
||||||
|
'h264' => 'FFMpeg\Format\Video\X264',
|
||||||
|
'wmv2' => 'FFMpeg\Format\Video\WMV',
|
||||||
|
'vp9' => 'FFMpeg\Format\Video\WebM',
|
||||||
|
'theora' => 'FFMpeg\Format\Video\Ogg',
|
||||||
|
'mpeg4' => 'FFMpeg\Format\Video\Mpeg4',
|
||||||
|
// Add more mappings as needed
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($codecToFormatClass[$codecName]) === false) {
|
||||||
|
Log::info("Unsupported codec: $codecName");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatClassName = $codecToFormatClass[$codecName];
|
||||||
|
|
||||||
|
if (class_exists($formatClassName) === false) {
|
||||||
|
Log::info("Format class does not exist: $formatClassName");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $formatClassName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set failure status of MediaJob and throw exception.
|
||||||
|
*
|
||||||
|
* @param string $error The failure message.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function throwMediaJobFailure(string $error): void
|
||||||
|
{
|
||||||
|
$this->mediaJob->setStatusFailed($error);
|
||||||
|
throw new \Exception($error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,82 +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.
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -47,6 +47,8 @@ class SendEmailJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,177 +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.
|
|
||||||
*/
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
$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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,6 +54,8 @@ class ChangeEmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -64,6 +66,8 @@ class ChangeEmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ class ChangedEmail extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -64,6 +66,8 @@ class ChangedEmail extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class ChangedPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -46,6 +48,8 @@ class ChangedPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ class Contact extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -63,6 +65,8 @@ class Contact extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class EmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -55,6 +57,8 @@ class EmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
59
app/Mail/ExceptionMail.php
Normal file
59
app/Mail/ExceptionMail.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?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 ExceptionMail extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new message instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
|
*/
|
||||||
|
public function envelope(): Envelope
|
||||||
|
{
|
||||||
|
return new Envelope(
|
||||||
|
subject: 'Exception Mail',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
|
*/
|
||||||
|
public function content(): Content
|
||||||
|
{
|
||||||
|
return new Content(
|
||||||
|
view: 'view.name',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments for the message.
|
||||||
|
*
|
||||||
|
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||||
|
*/
|
||||||
|
public function attachments(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,8 @@ class ForgotPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -55,6 +57,8 @@ class ForgotPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class SubscriptionConfirm extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -46,6 +48,8 @@ class SubscriptionConfirm extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class SubscriptionUnsubscribed extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Envelope
|
||||||
*/
|
*/
|
||||||
public function envelope(): Envelope
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
@@ -46,6 +48,8 @@ class SubscriptionUnsubscribed extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Mail\Mailables\Content
|
||||||
*/
|
*/
|
||||||
public function content(): Content
|
public function content(): Content
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class Analytics extends Model
|
|
||||||
{
|
|
||||||
use HasFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that aren't mass assignable.
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected $guarded = [];
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new row in the analytics table with the given attributes,
|
|
||||||
* automatically assigning a session value based on previous rows.
|
|
||||||
*
|
|
||||||
* @param array $attributes Model attributes.
|
|
||||||
*/
|
|
||||||
public static function createWithSession(array $attributes): static
|
|
||||||
{
|
|
||||||
$previousRow = self::where('useragent', $attributes['useragent'])
|
|
||||||
->where('ip', $attributes['ip'])
|
|
||||||
->where('created_at', '>=', now()->subMinutes(30))
|
|
||||||
->whereNotNull('session')
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($previousRow !== null) {
|
|
||||||
$attributes['session'] = $previousRow->session;
|
|
||||||
} else {
|
|
||||||
$lastSession = self::max('session');
|
|
||||||
$attributes['session'] = ($lastSession + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return static::create($attributes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
73
app/Models/AnalyticsItemRequest.php
Normal file
73
app/Models/AnalyticsItemRequest.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class AnalyticsItemRequest extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $table = 'analytics_requests';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'type',
|
||||||
|
'path'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model Boot.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function boot()
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
static::creating(function (AnalyticsItemRequest $analytics) {
|
||||||
|
if (isset($analytics->session_id) !== true) {
|
||||||
|
$request = request();
|
||||||
|
if ($request !== null) {
|
||||||
|
$session = AnalyticsSession::where('ip', $request->ip())
|
||||||
|
->where('useragent', $request->userAgent())
|
||||||
|
->where('ended_at', '>=', now()->subMinutes(30))
|
||||||
|
->first();
|
||||||
|
if ($session === null) {
|
||||||
|
$session = AnalyticsSession::create([
|
||||||
|
'ip' => $request->ip(),
|
||||||
|
'useragent' => $request->userAgent(),
|
||||||
|
'ended_at' => now()
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$session->update(['ended_at' => now()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$analytics->session_id = $session->id;
|
||||||
|
}
|
||||||
|
}//end if
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Analytics Session model.
|
||||||
|
*
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function session(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(AnalyticsSession::class, 'id', 'session_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
44
app/Models/AnalyticsSession.php
Normal file
44
app/Models/AnalyticsSession.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class AnalyticsSession extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'ip',
|
||||||
|
'useragent',
|
||||||
|
'ended_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the "useragent" attribute.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setUseragentAttribute($value)
|
||||||
|
{
|
||||||
|
$this->attributes['useragent'] = $value !== null ? $value : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the related requests for this session.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
|
*/
|
||||||
|
public function requests(): HasMany {
|
||||||
|
return $this->hasMany(AnalyticsItemRequest::class, 'session_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasAttachments;
|
||||||
|
use App\Traits\HasGallery;
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class Article extends Model
|
class Article extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuids;
|
use Uuids;
|
||||||
|
use HasGallery;
|
||||||
|
use HasAttachments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@@ -29,17 +33,11 @@ class Article extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the article user
|
* Get the article user
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all of the article's attachments.
|
|
||||||
*/
|
|
||||||
public function attachments(): MorphMany
|
|
||||||
{
|
|
||||||
return $this->morphMany(\App\Models\Attachment::class, 'attachable');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class Attachment extends Model
|
class Attachment extends Model
|
||||||
{
|
{
|
||||||
@@ -27,23 +27,60 @@ class Attachment extends Model
|
|||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
'private' => 'false',
|
'private' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get attachments attachable
|
|
||||||
*/
|
|
||||||
public function attachable(): MorphTo
|
|
||||||
{
|
|
||||||
return $this->morphTo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the media for this attachment.
|
* Get the media for this attachment.
|
||||||
|
*
|
||||||
|
* @return BelongsTo
|
||||||
*/
|
*/
|
||||||
public function media(): BelongsTo
|
public function media(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Media::class);
|
return $this->belongsTo(Media::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get associated Media object.
|
||||||
|
*
|
||||||
|
* @return null|Media
|
||||||
|
*/
|
||||||
|
public function getMediaAttribute(): ?Media
|
||||||
|
{
|
||||||
|
$mediaId = '0';
|
||||||
|
$media = null;
|
||||||
|
|
||||||
|
if (Cache::has("attachment:{$this->id}:media") === true) {
|
||||||
|
$mediaId = Cache::get("attachment:{$this->id}:media");
|
||||||
|
} else {
|
||||||
|
$media = $this->media()->first();
|
||||||
|
if ($media === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaId = $media->id;
|
||||||
|
Cache::put("attachment:{$this->id}:media", $mediaId, now()->addDays(28));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache::remember("media:{$mediaId}", now()->addDays(28), function () use ($media) {
|
||||||
|
if ($media !== null) {
|
||||||
|
return $media;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->media()->first();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the media for this item.
|
||||||
|
*
|
||||||
|
* @param Media $media The media model.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setMediaAttribute(Media $media): void
|
||||||
|
{
|
||||||
|
$this->media()->associate($media)->save();
|
||||||
|
Cache::put("attachment:{$this->id}:media", $media->id, now()->addDays(28));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\HasAttachments;
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@@ -10,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany;
|
|||||||
|
|
||||||
class Event extends Model
|
class Event extends Model
|
||||||
{
|
{
|
||||||
|
use HasAttachments;
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuids;
|
use Uuids;
|
||||||
|
|
||||||
@@ -33,19 +35,14 @@ class Event extends Model
|
|||||||
'content',
|
'content',
|
||||||
'price',
|
'price',
|
||||||
'ages',
|
'ages',
|
||||||
|
'open_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all of the article's attachments.
|
|
||||||
*/
|
|
||||||
public function attachments(): MorphMany
|
|
||||||
{
|
|
||||||
return $this->morphMany(\App\Models\Attachment::class, 'attachable');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the associated users.
|
* Get all the associated users.
|
||||||
|
*
|
||||||
|
* @return BelongsToMany
|
||||||
*/
|
*/
|
||||||
public function users(): BelongsToMany
|
public function users(): BelongsToMany
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
|||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class EventUser extends Model
|
class EventUser extends Model
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,8 @@ class EventUser extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the event for this attachment.
|
* Get the event for this attachment.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
public function event(): BelongsTo
|
public function event(): BelongsTo
|
||||||
{
|
{
|
||||||
@@ -32,6 +35,8 @@ class EventUser extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the user for this attachment.
|
* Get the user for this attachment.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
|
|||||||
107
app/Models/Gallery.php
Normal file
107
app/Models/Gallery.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class Gallery extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use Uuids;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'media_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boot the model.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
$clearCache = function ($gallery) {
|
||||||
|
Cache::forget("gallery:{$gallery->id}:media");
|
||||||
|
};
|
||||||
|
|
||||||
|
static::saving($clearCache);
|
||||||
|
static::deleting($clearCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get gallery addendum model.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\MorphTo Addenum model.
|
||||||
|
*/
|
||||||
|
public function addendum(): MorphTo
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the media for this item.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo The media model.
|
||||||
|
*/
|
||||||
|
public function media(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Media::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the media for this item.
|
||||||
|
*
|
||||||
|
* @return null|Media The media model.
|
||||||
|
*/
|
||||||
|
public function getMediaAttribute(): ?Media
|
||||||
|
{
|
||||||
|
$mediaId = '0';
|
||||||
|
$media = null;
|
||||||
|
|
||||||
|
if (Cache::has("gallery:{$this->id}:media") === true) {
|
||||||
|
$mediaId = Cache::get("gallery:{$this->id}:media");
|
||||||
|
} else {
|
||||||
|
$media = $this->media()->first();
|
||||||
|
if ($media === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediaId = $media->id;
|
||||||
|
Cache::put("gallery:{$this->id}:media", $mediaId, now()->addDays(28));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache::remember("media:{$mediaId}", now()->addDays(28), function () use ($media) {
|
||||||
|
if ($media !== null) {
|
||||||
|
return $media;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->media()->first();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the media for this item.
|
||||||
|
*
|
||||||
|
* @param Media $media The media model.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setMediaAttribute(Media $media): void
|
||||||
|
{
|
||||||
|
$this->media()->associate($media)->save();
|
||||||
|
Cache::put("gallery:{$this->id}:media", $media->id, now()->addDays(28));
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
239
app/Models/MediaJob.php
Normal file
239
app/Models/MediaJob.php
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Jobs\MediaWorkerJob;
|
||||||
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class MediaJob extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use Uuids;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default attributes.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'user_id' => null,
|
||||||
|
'media_id' => null,
|
||||||
|
'status' => '',
|
||||||
|
'status_text' => '',
|
||||||
|
'progress' => 0,
|
||||||
|
'progress_max' => 0,
|
||||||
|
'data' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJob status to failed.
|
||||||
|
*
|
||||||
|
* @param string $statusText The failed reason.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStatusFailed(string $statusText = ''): void
|
||||||
|
{
|
||||||
|
$this->setStatus('failed', $statusText, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJob status to queued.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStatusQueued(): void
|
||||||
|
{
|
||||||
|
$this->setStatus('queued', '', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJob status to waiting.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStatusWaiting(): void
|
||||||
|
{
|
||||||
|
$this->setStatus('waiting', '', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJob status to processing.
|
||||||
|
*
|
||||||
|
* @param integer $progress The processing progress value.
|
||||||
|
* @param integer $progressMax The processing progress maximum value.
|
||||||
|
* @param string $statusText The processing status text.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStatusProcessing(int $progress = 0, int $progressMax = 0, string $statusText = ''): void
|
||||||
|
{
|
||||||
|
if ($statusText === '') {
|
||||||
|
$statusText = $this->status_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setStatus('processing', $statusText, $progress, $progressMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJob status to complete.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStatusComplete(): void
|
||||||
|
{
|
||||||
|
$this->setStatus('complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJon status to invalid.
|
||||||
|
*
|
||||||
|
* @param string $text The status text.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setStatusInvalid(string $text = ''): void
|
||||||
|
{
|
||||||
|
$this->setStatus('invalid', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set MediaJob status details.
|
||||||
|
*
|
||||||
|
* @param string $status The status string.
|
||||||
|
* @param string $text The status text.
|
||||||
|
* @param integer $progress The status progress value.
|
||||||
|
* @param integer $progress_max The status progress maximum value.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function setStatus(string $status, string $text = '', int $progress = 0, int $progress_max = 0): void
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
$this->status_text = $text;
|
||||||
|
$this->progress = $progress;
|
||||||
|
$this->progress_max = $progress_max;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the MediaJob.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function process(): void
|
||||||
|
{
|
||||||
|
$data = json_decode($this->data, true);
|
||||||
|
if ($data !== null) {
|
||||||
|
if (array_key_exists('chunks', $data) === true) {
|
||||||
|
if (array_key_exists('chunk_count', $data) === false) {
|
||||||
|
$this->setStatusInvalid('chunk_count is missing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('name', $data) === false) {
|
||||||
|
$this->setStatusInvalid('name is missing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$numChunks = count($data['chunks']);
|
||||||
|
$maxChunks = intval($data['chunk_count']);
|
||||||
|
if ($numChunks >= $maxChunks) {
|
||||||
|
// merge file and dispatch
|
||||||
|
$this->setStatusProcessing(0, $maxChunks, 'combining chunks');
|
||||||
|
|
||||||
|
$newFile = generateTempFilePath(pathinfo($data['name'], PATHINFO_EXTENSION));
|
||||||
|
$failed = false;
|
||||||
|
|
||||||
|
for ($index = 1; $index <= $maxChunks; $index++) {
|
||||||
|
if (array_key_exists($index, $data['chunks']) === false) {
|
||||||
|
$failed = `{$index} chunk is missing`;
|
||||||
|
} else {
|
||||||
|
$tempFileName = $data['chunks'][$index];
|
||||||
|
|
||||||
|
if ($failed === false) {
|
||||||
|
$chunkContents = file_get_contents($tempFileName);
|
||||||
|
if ($chunkContents === false) {
|
||||||
|
$failed = `{$index} chunk is empty`;
|
||||||
|
} else {
|
||||||
|
file_put_contents($newFile, $chunkContents, FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink($tempFileName);
|
||||||
|
$this->setStatusProcessing($index, $maxChunks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($data['chunks']);
|
||||||
|
$this->data = json_encode($data);
|
||||||
|
|
||||||
|
if ($failed !== false) {
|
||||||
|
$this->setStatusInvalid($failed);
|
||||||
|
} else {
|
||||||
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||||
|
$mime = finfo_file($finfo, $newFile);
|
||||||
|
finfo_close($finfo);
|
||||||
|
|
||||||
|
$data['file'] = $newFile;
|
||||||
|
$data['size'] = filesize($newFile);
|
||||||
|
$data['mime_type'] = $mime;
|
||||||
|
|
||||||
|
if (
|
||||||
|
array_key_exists('storage', $data) === true &&
|
||||||
|
array_key_exists('security_type', $data) === true &&
|
||||||
|
array_key_exists('mime_type', $data) === true &&
|
||||||
|
$data['mime_type'] !== ""
|
||||||
|
) {
|
||||||
|
$error = Media::verifyStorage($data['mime_type'], $data['security_type'], $data['storage']);
|
||||||
|
switch ($error) {
|
||||||
|
case Media::STORAGE_VALID:
|
||||||
|
break;
|
||||||
|
case Media::STORAGE_MIME_MISSING:
|
||||||
|
$this->setStatusInvalid('The file type cannot be determined.');
|
||||||
|
return;
|
||||||
|
case Media::STORAGE_NOT_FOUND:
|
||||||
|
$this->setStatusInvalid('Storage was not found.');
|
||||||
|
return;
|
||||||
|
case Media::STORAGE_INVALID_SECURITY:
|
||||||
|
$this->setStatusInvalid('Storage invalid for security value.');
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
$this->setStatusInvalid('Storage verification error occurred.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data = json_encode($data);
|
||||||
|
$this->setStatusQueued();
|
||||||
|
MediaWorkerJob::dispatch($this)->onQueue('media');
|
||||||
|
}//end if
|
||||||
|
}//end if
|
||||||
|
} else {
|
||||||
|
$this->setStatusQueued();
|
||||||
|
MediaWorkerJob::dispatch($this)->onQueue('media');
|
||||||
|
}//end if
|
||||||
|
}//end if
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the job owner
|
||||||
|
*
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the media item
|
||||||
|
*
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function media(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Media::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
|||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class Permission extends Model
|
class Permission extends Model
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,8 @@ class Permission extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the User associated with this model
|
* Get the User associated with this model
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Jobs\MoveMediaJob;
|
|
||||||
use App\Jobs\OptimizeMediaJob;
|
use App\Jobs\OptimizeMediaJob;
|
||||||
use App\Jobs\StoreUploadedFileJob;
|
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ namespace App\Models;
|
|||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use OwenIt\Auditing\Contracts\Auditable;
|
use OwenIt\Auditing\Contracts\Auditable;
|
||||||
|
|
||||||
@@ -78,7 +81,29 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of files of the user
|
* Boot the model.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function boot(): void
|
||||||
|
{
|
||||||
|
parent::boot();
|
||||||
|
|
||||||
|
$clearCache = function ($user) {
|
||||||
|
Cache::forget(
|
||||||
|
"user:{$user->id}",
|
||||||
|
"user:{$user->id}:permissions"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
static::saving($clearCache);
|
||||||
|
static::deleting($clearCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of permissions of the user
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
*/
|
*/
|
||||||
public function permissions(): HasMany
|
public function permissions(): HasMany
|
||||||
{
|
{
|
||||||
@@ -87,26 +112,50 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the permission attribute
|
* Get the permission attribute
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function getPermissionsAttribute(): array
|
public function getPermissionsAttribute(): array
|
||||||
{
|
{
|
||||||
return $this->permissions()->pluck('permission')->toArray();
|
$cacheKey = "user:{$this->id}:permissions";
|
||||||
|
return Cache::remember($cacheKey, now()->addDays(28), function () {
|
||||||
|
return $this->permissions()->pluck('permission')->toArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the permission attribute
|
||||||
|
*
|
||||||
|
* @param array $newPermissions The new permissions to set to the user.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setPermissionsAttribute(array $newPermissions): void
|
||||||
|
{
|
||||||
|
$existingPermissions = $this->permissions->pluck('permission')->toArray();
|
||||||
|
|
||||||
|
$this->revokePermission(array_diff($this->permissions, $newPermissions));
|
||||||
|
$this->givePermission(array_diff($newPermissions, $this->permissions));
|
||||||
|
|
||||||
|
$cacheKey = "user:{$this->id}:permissions";
|
||||||
|
Cache::delete($cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if user has permission
|
* Test if user has permission
|
||||||
*
|
*
|
||||||
* @param string $permission Permission to test.
|
* @param string $permission Permission to test.
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function hasPermission(string $permission): bool
|
public function hasPermission(string $permission): bool
|
||||||
{
|
{
|
||||||
return ($this->permissions()->where('permission', $permission)->first() !== null);
|
return in_array($permission, $this->permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give permissions to the user
|
* Give permissions to the user
|
||||||
*
|
*
|
||||||
* @param string|array $permissions The permission(s) to give.
|
* @param string|array $permissions The permission(s) to give.
|
||||||
|
* @return Illuminate\Database\Eloquent\Collection
|
||||||
*/
|
*/
|
||||||
public function givePermission($permissions): Collection
|
public function givePermission($permissions): Collection
|
||||||
{
|
{
|
||||||
@@ -114,16 +163,14 @@ class User extends Authenticatable implements Auditable
|
|||||||
$permissions = [$permissions];
|
$permissions = [$permissions];
|
||||||
}
|
}
|
||||||
|
|
||||||
$permissions = collect($permissions)->map(function ($permission) {
|
$newPermissions = array_map(function ($permission) {
|
||||||
return ['permission' => $permission];
|
return ['permission' => $permission];
|
||||||
});
|
}, array_diff($permissions, $this->permissions));
|
||||||
|
|
||||||
$existingPermissions = $this->permissions()->whereIn('permission', $permissions->pluck('permission'))->get();
|
$cacheKey = "user:{$this->id}:permissions";
|
||||||
$newPermissions = $permissions->reject(function ($permission) use ($existingPermissions) {
|
Cache::forget($cacheKey);
|
||||||
return $existingPermissions->contains('permission', $permission['permission']);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this->permissions()->createMany($newPermissions->toArray());
|
return $this->permissions()->createMany($newPermissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -131,6 +178,7 @@ class User extends Authenticatable implements Auditable
|
|||||||
* Revoke permissions from the user
|
* Revoke permissions from the user
|
||||||
*
|
*
|
||||||
* @param string|array $permissions The permission(s) to revoke.
|
* @param string|array $permissions The permission(s) to revoke.
|
||||||
|
* @return integer
|
||||||
*/
|
*/
|
||||||
public function revokePermission($permissions): int
|
public function revokePermission($permissions): int
|
||||||
{
|
{
|
||||||
@@ -138,6 +186,9 @@ class User extends Authenticatable implements Auditable
|
|||||||
$permissions = [$permissions];
|
$permissions = [$permissions];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$cacheKey = "user:{$this->id}:permissions";
|
||||||
|
Cache::forget($cacheKey);
|
||||||
|
|
||||||
return $this->permissions()
|
return $this->permissions()
|
||||||
->whereIn('permission', $permissions)
|
->whereIn('permission', $permissions)
|
||||||
->delete();
|
->delete();
|
||||||
@@ -145,6 +196,8 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of files of the user
|
* Get the list of files of the user
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
*/
|
*/
|
||||||
public function media(): HasMany
|
public function media(): HasMany
|
||||||
{
|
{
|
||||||
@@ -153,6 +206,8 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of files of the user
|
* Get the list of files of the user
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
*/
|
*/
|
||||||
public function articles(): HasMany
|
public function articles(): HasMany
|
||||||
{
|
{
|
||||||
@@ -161,6 +216,8 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get associated user codes
|
* Get associated user codes
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
*/
|
*/
|
||||||
public function codes(): HasMany
|
public function codes(): HasMany
|
||||||
{
|
{
|
||||||
@@ -169,6 +226,8 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of logins of the user
|
* Get the list of logins of the user
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
*/
|
*/
|
||||||
public function logins(): HasMany
|
public function logins(): HasMany
|
||||||
{
|
{
|
||||||
@@ -177,6 +236,8 @@ class User extends Authenticatable implements Auditable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the events associated with the user.
|
* Get the events associated with the user.
|
||||||
|
*
|
||||||
|
* @return BelongsToMany
|
||||||
*/
|
*/
|
||||||
public function events(): BelongsToMany
|
public function events(): BelongsToMany
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class UserCode extends Model
|
class UserCode extends Model
|
||||||
{
|
{
|
||||||
@@ -23,6 +24,8 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Boot function from Laravel.
|
* Boot function from Laravel.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected static function boot(): void
|
protected static function boot(): void
|
||||||
{
|
{
|
||||||
@@ -44,6 +47,8 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate new code
|
* Generate new code
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function regenerate(): void
|
public function regenerate(): void
|
||||||
{
|
{
|
||||||
@@ -58,6 +63,8 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear expired user codes
|
* Clear expired user codes
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function clearExpired(): void
|
public static function clearExpired(): void
|
||||||
{
|
{
|
||||||
@@ -66,6 +73,8 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get associated user
|
* Get associated user
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
|||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class UserLogins extends Model
|
class UserLogins extends Model
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,8 @@ class UserLogins extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the file user
|
* Get the file user
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
*/
|
*/
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,20 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Rules\RequiredIfAny;
|
use Illuminate\Http\Request;
|
||||||
use App\Rules\Uniqueish;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Exception;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use PDOException;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Register any application services.
|
* Register any application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
@@ -24,9 +20,31 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
Request::macro('rename', function ($param, $newParam = null) {
|
||||||
|
if (is_array($param) === false) {
|
||||||
|
if ($newParam === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$param = [$param => $newParam];
|
||||||
|
}
|
||||||
|
|
||||||
|
$paramArray = $this->all();
|
||||||
|
foreach ($param as $oldParam => $newParam) {
|
||||||
|
if (isset($paramArray[$oldParam]) === true) {
|
||||||
|
$paramArray[$newParam] = $paramArray[$oldParam];
|
||||||
|
unset($paramArray[$oldParam]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->replace($paramArray);
|
||||||
|
});
|
||||||
|
|
||||||
Storage::macro('public', function ($diskName) {
|
Storage::macro('public', function ($diskName) {
|
||||||
$public = config("filesystems.disks.{$diskName}.public", false);
|
$public = config("filesystems.disks.{$diskName}.public", false);
|
||||||
return $public;
|
return $public;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any authentication / authorization services.
|
* Register any authentication / authorization services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ class BroadcastServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any events for your application.
|
* Register any events for your application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
@@ -34,6 +36,8 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if events and listeners should be automatically discovered.
|
* Determine if events and listeners should be automatically discovered.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function shouldDiscoverEvents(): bool
|
public function shouldDiscoverEvents(): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Define your route model bindings, pattern filters, and other route configuration.
|
* Define your route model bindings, pattern filters, and other route configuration.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
@@ -31,9 +33,10 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
// });
|
// });
|
||||||
|
|
||||||
$rateLimitEnabled = true;
|
$rateLimitEnabled = true;
|
||||||
|
/** @var \App\Models\User */
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
|
|
||||||
if (app()->environment('testing')) {
|
if (app()->environment('testing') === true) {
|
||||||
$rateLimitEnabled = false;
|
$rateLimitEnabled = false;
|
||||||
} elseif ($user !== null && $user->hasPermission('admin/ratelimit') === true) {
|
} elseif ($user !== null && $user->hasPermission('admin/ratelimit') === true) {
|
||||||
// Admin users with the "admin/ratelimit" permission are not rate limited
|
// Admin users with the "admin/ratelimit" permission are not rate limited
|
||||||
@@ -42,7 +45,7 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
if ($rateLimitEnabled === true) {
|
if ($rateLimitEnabled === true) {
|
||||||
RateLimiter::for('api', function (Request $request) {
|
RateLimiter::for('api', function (Request $request) {
|
||||||
return Limit::perMinute(180)->by($request->user()?->id ?: $request->ip());
|
return Limit::perMinute(800)->by(isset($request->user()->id) === true ?: $request->ip());
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
RateLimiter::for('api', function () {
|
RateLimiter::for('api', function () {
|
||||||
@@ -59,20 +62,29 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
->group(base_path('routes/web.php'));
|
->group(base_path('routes/web.php'));
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::macro('apiAttachmentResource', function ($uri, $controller) {
|
Route::macro('apiAddendumResource', function ($addendum, $uri, $controller) {
|
||||||
$singularUri = Str::singular($uri);
|
$singularUri = Str::singular($uri);
|
||||||
|
$signularAddendum = Str::singular((strtolower($addendum)));
|
||||||
|
$pluralAddendum = Str::plural($signularAddendum);
|
||||||
|
|
||||||
Route::get("$uri/{{$singularUri}}/attachments", [$controller, 'getAttachments'])
|
Route::get("{$uri}/{{$singularUri}}/{$pluralAddendum}", [$controller, "{$signularAddendum}Index"])
|
||||||
->name("{{$singularUri}}.attachments.index");
|
->name("{$singularUri}.{$signularAddendum}.index");
|
||||||
|
|
||||||
Route::post("$uri/{{$singularUri}}/attachments", [$controller, 'storeAttachment'])
|
Route::post("{$uri}/{{$singularUri}}/{$pluralAddendum}", [$controller, "{$signularAddendum}Store"])
|
||||||
->name("{{$singularUri}}.attachments.store");
|
->name("{$singularUri}.{$signularAddendum}.store");
|
||||||
|
|
||||||
Route::match(['put', 'patch'], "$uri/{{$singularUri}}/attachments", [$controller, 'updateAttachments'])
|
Route::match(
|
||||||
->name("{{$singularUri}}.attachments.update");
|
['put', 'patch'],
|
||||||
|
"{$uri}/{{$singularUri}}/{$pluralAddendum}",
|
||||||
|
[$controller, "{$signularAddendum}Update"]
|
||||||
|
)
|
||||||
|
->name("{$singularUri}.{$signularAddendum}.update");
|
||||||
|
|
||||||
Route::delete("$uri/{{$singularUri}}/attachments/{medium}", [$controller, 'deleteAttachment'])
|
Route::delete(
|
||||||
->name("{{$singularUri}}.attachments.destroy");
|
"{$uri}/{{$singularUri}}/{$pluralAddendum}/{medium}",
|
||||||
|
[$controller,"{$signularAddendum}Delete"]
|
||||||
|
)
|
||||||
|
->name("{$singularUri}.{$signularAddendum}.destroy");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class Recaptcha implements Rule
|
|||||||
*
|
*
|
||||||
* @param mixed $attribute Attribute name.
|
* @param mixed $attribute Attribute name.
|
||||||
* @param mixed $value Attribute value.
|
* @param mixed $value Attribute value.
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function passes(mixed $attribute, mixed $value): bool
|
public function passes(mixed $attribute, mixed $value): bool
|
||||||
{
|
{
|
||||||
@@ -41,6 +42,8 @@ class Recaptcha implements Rule
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation error message.
|
* Get the validation error message.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function message(): string
|
public function message(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,15 +20,19 @@ class UniqueFileName implements Rule
|
|||||||
/**
|
/**
|
||||||
* Determine if the validation rule passes.
|
* Determine if the validation rule passes.
|
||||||
*
|
*
|
||||||
* @param mixed $value
|
* @param mixed $attribute Attribute name.
|
||||||
|
* @param mixed $value Attribute value.
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function passes(string $attribute, $value): bool
|
public function passes(mixed $attribute, mixed $value): bool
|
||||||
{
|
{
|
||||||
return (Media::fileExists($value) === false);
|
return (Media::fileExists($value) === false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation error message.
|
* Get the validation error message.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function message(): string
|
public function message(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class Uniqueish implements Rule
|
|||||||
* Set the ID of the record to be ignored.
|
* Set the ID of the record to be ignored.
|
||||||
*
|
*
|
||||||
* @param mixed $id The ID to ignore.
|
* @param mixed $id The ID to ignore.
|
||||||
|
* @return App\Rules\Uniqueish
|
||||||
*/
|
*/
|
||||||
public function ignore(mixed $id): static
|
public function ignore(mixed $id): static
|
||||||
{
|
{
|
||||||
@@ -59,6 +60,7 @@ class Uniqueish implements Rule
|
|||||||
*
|
*
|
||||||
* @param mixed $attribute Not used.
|
* @param mixed $attribute Not used.
|
||||||
* @param mixed $value The value to compare.
|
* @param mixed $value The value to compare.
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function passes(mixed $attribute, mixed $value): bool
|
public function passes(mixed $attribute, mixed $value): bool
|
||||||
{
|
{
|
||||||
@@ -83,7 +85,7 @@ class Uniqueish implements Rule
|
|||||||
|
|
||||||
foreach ($results as $result) {
|
foreach ($results as $result) {
|
||||||
$resultValue = preg_replace('/[^A-Za-z0-9]/', '', strtolower($result->{$columnName}));
|
$resultValue = preg_replace('/[^A-Za-z0-9]/', '', strtolower($result->{$columnName}));
|
||||||
if ($resultValue === $similarValue && $result->id != $this->ignoreId) {
|
if ($resultValue === $similarValue && $result->id !== $this->ignoreId) {
|
||||||
return false; // Value already exists in the table
|
return false; // Value already exists in the table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,6 +97,8 @@ class Uniqueish implements Rule
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation error message.
|
* Get the validation error message.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function message(): string
|
public function message(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,630 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
class AnimatedGifService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Check if a GIF file at a path is animated or not
|
|
||||||
*
|
|
||||||
* @param string $filenameOrBlob GIF file path or data blob if dataSize > 0.
|
|
||||||
* @param integer $dataSize GIF blob size.
|
|
||||||
* @return boolean GIF file/blob is animated.
|
|
||||||
*/
|
|
||||||
public static function isAnimatedGif(string $filenameOrBlob, int $dataSize = 0): bool
|
|
||||||
{
|
|
||||||
$regex = '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s';
|
|
||||||
$count = 0;
|
|
||||||
|
|
||||||
if ($dataSize > 0) {
|
|
||||||
if (($fh = @fopen($filenameOrBlob, 'rb')) === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$chunk = false;
|
|
||||||
while (feof($fh) === false && $count < 2) {
|
|
||||||
$chunk = ($chunk !== '' ? substr($chunk, -20) : "") . fread($fh, (1024 * 100)); //read 100kb at a time
|
|
||||||
$count += preg_match_all($regex, $chunk, $matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($fh);
|
|
||||||
} else {
|
|
||||||
$count = preg_match_all($regex, $filenameOrBlob, $matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $count > 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract frames of a GIF
|
|
||||||
*
|
|
||||||
* @param string $filenameOrBlob GIF filename path
|
|
||||||
* @param integer $dataSize GIF blob size.
|
|
||||||
* @param boolean $originalFrames Get original frames (with transparent background)
|
|
||||||
*/
|
|
||||||
public function extract(string $filenameOrBlob, int $dataSize = 0, bool $originalFrames = false): array
|
|
||||||
{
|
|
||||||
if (self::isAnimatedGif($filenameOrBlob) === false) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->reset();
|
|
||||||
$this->parseFramesInfo($filename);
|
|
||||||
$prevImg = null;
|
|
||||||
|
|
||||||
for ($i = 0; $i < count($this->frameSources); $i++) {
|
|
||||||
$this->frames[$i] = [];
|
|
||||||
$this->frameDurations[$i] = $this->frames[$i]['duration'] = $this->frameSources[$i]['delay_time'];
|
|
||||||
|
|
||||||
$img = imagecreatefromstring($this->fileHeader["gifheader"] . $this->frameSources[$i]["graphicsextension"] . $this->frameSources[$i]["imagedata"] . chr(0x3b));
|
|
||||||
|
|
||||||
if (!$originalFrames) {
|
|
||||||
if ($i > 0) {
|
|
||||||
$prevImg = $this->frames[($i - 1)]['image'];
|
|
||||||
} else {
|
|
||||||
$prevImg = $img;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sprite = imagecreate($this->gifMaxWidth, $this->gifMaxHeight);
|
|
||||||
imagesavealpha($sprite, true);
|
|
||||||
|
|
||||||
$transparent = imagecolortransparent($prevImg);
|
|
||||||
|
|
||||||
if ($transparent > -1 && imagecolorstotal($prevImg) > $transparent) {
|
|
||||||
$actualTrans = imagecolorsforindex($prevImg, $transparent);
|
|
||||||
imagecolortransparent($sprite, imagecolorallocate($sprite, $actualTrans['red'], $actualTrans['green'], $actualTrans['blue']));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((int) $this->frameSources[$i]['disposal_method'] == 1 && $i > 0) {
|
|
||||||
imagecopy($sprite, $prevImg, 0, 0, 0, 0, $this->gifMaxWidth, $this->gifMaxHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
imagecopyresampled($sprite, $img, $this->frameSources[$i]["offset_left"], $this->frameSources[$i]["offset_top"], 0, 0, $this->gifMaxWidth, $this->gifMaxHeight, $this->gifMaxWidth, $this->gifMaxHeight);
|
|
||||||
$img = $sprite;
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
$this->frameImages[$i] = $this->frames[$i]['image'] = $img;
|
|
||||||
}//end for
|
|
||||||
|
|
||||||
return $this->frames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GifFrameExtractor
|
|
||||||
{
|
|
||||||
// Properties
|
|
||||||
// ===================================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var resource
|
|
||||||
*/
|
|
||||||
private $gif;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $frames;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $frameDurations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $frameImages;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $framePositions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
private $frameDimensions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*
|
|
||||||
* (old: $this->index)
|
|
||||||
*/
|
|
||||||
private $frameNumber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*
|
|
||||||
* (old: $this->imagedata)
|
|
||||||
*/
|
|
||||||
private $frameSources;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*
|
|
||||||
* (old: $this->fileHeader)
|
|
||||||
*/
|
|
||||||
private $fileHeader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer The reader pointer in the file source
|
|
||||||
*
|
|
||||||
* (old: $this->pointer)
|
|
||||||
*/
|
|
||||||
private $pointer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $gifMaxWidth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $gifMaxHeight;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $totalDuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var integer
|
|
||||||
*/
|
|
||||||
private $handle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*
|
|
||||||
* (old: globaldata)
|
|
||||||
*/
|
|
||||||
private $globaldata;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array
|
|
||||||
*
|
|
||||||
* (old: orgvars)
|
|
||||||
*/
|
|
||||||
private $orgvars;
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
// ===================================================================================
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the frame informations contained in the GIF file
|
|
||||||
*
|
|
||||||
* @param string $filename GIF filename path
|
|
||||||
*/
|
|
||||||
private function parseFramesInfo(string $filename)
|
|
||||||
{
|
|
||||||
$this->openFile($filename);
|
|
||||||
$this->parseGifHeader();
|
|
||||||
$this->parseGraphicsExtension(0);
|
|
||||||
$this->getApplicationData();
|
|
||||||
$this->getApplicationData();
|
|
||||||
$this->getFrameString(0);
|
|
||||||
$this->parseGraphicsExtension(1);
|
|
||||||
$this->getCommentData();
|
|
||||||
$this->getApplicationData();
|
|
||||||
$this->getFrameString(1);
|
|
||||||
|
|
||||||
while (!$this->checkByte(0x3b) && !$this->checkEOF()) {
|
|
||||||
$this->getCommentData(1);
|
|
||||||
$this->parseGraphicsExtension(2);
|
|
||||||
$this->getFrameString(2);
|
|
||||||
$this->getApplicationData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the gif header (old: get_gif_header)
|
|
||||||
*/
|
|
||||||
private function parseGifHeader()
|
|
||||||
{
|
|
||||||
$this->pointerForward(10);
|
|
||||||
|
|
||||||
if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) {
|
|
||||||
$this->pointerForward(2);
|
|
||||||
$this->pointerForward(pow(2, ($this->readBits($mybyte, 5, 3) + 1)) * 3);
|
|
||||||
} else {
|
|
||||||
$this->pointerForward(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->fileHeader["gifheader"] = $this->dataPart(0, $this->pointer);
|
|
||||||
|
|
||||||
// Decoding
|
|
||||||
$this->orgvars["gifheader"] = $this->fileHeader["gifheader"];
|
|
||||||
$this->orgvars["background_color"] = $this->orgvars["gifheader"][11];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the application data of the frames (old: get_application_data)
|
|
||||||
*/
|
|
||||||
private function getApplicationData()
|
|
||||||
{
|
|
||||||
$startdata = $this->readByte(2);
|
|
||||||
|
|
||||||
if ($startdata == chr(0x21) . chr(0xff)) {
|
|
||||||
$start = ($this->pointer - 2);
|
|
||||||
$this->pointerForward($this->readByteInt());
|
|
||||||
$this->readDataStream($this->readByteInt());
|
|
||||||
$this->fileHeader["applicationdata"] = $this->dataPart($start, ($this->pointer - $start));
|
|
||||||
} else {
|
|
||||||
$this->pointerRewind(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the comment data of the frames (old: get_comment_data)
|
|
||||||
*/
|
|
||||||
private function getCommentData()
|
|
||||||
{
|
|
||||||
$startdata = $this->readByte(2);
|
|
||||||
|
|
||||||
if ($startdata == chr(0x21) . chr(0xfe)) {
|
|
||||||
$start = ($this->pointer - 2);
|
|
||||||
$this->readDataStream($this->readByteInt());
|
|
||||||
$this->fileHeader["commentdata"] = $this->dataPart($start, ($this->pointer - $start));
|
|
||||||
} else {
|
|
||||||
$this->pointerRewind(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the graphic extension of the frames (old: get_graphics_extension)
|
|
||||||
*/
|
|
||||||
private function parseGraphicsExtension(int $type)
|
|
||||||
{
|
|
||||||
$startdata = $this->readByte(2);
|
|
||||||
|
|
||||||
if ($startdata == chr(0x21) . chr(0xf9)) {
|
|
||||||
$start = ($this->pointer - 2);
|
|
||||||
$this->pointerForward($this->readByteInt());
|
|
||||||
$this->pointerForward(1);
|
|
||||||
|
|
||||||
if ($type == 2) {
|
|
||||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->dataPart($start, ($this->pointer - $start));
|
|
||||||
} elseif ($type == 1) {
|
|
||||||
$this->orgvars["hasgx_type_1"] = 1;
|
|
||||||
$this->globaldata["graphicsextension"] = $this->dataPart($start, ($this->pointer - $start));
|
|
||||||
} elseif ($type == 0) {
|
|
||||||
$this->orgvars["hasgx_type_0"] = 1;
|
|
||||||
$this->globaldata["graphicsextension_0"] = $this->dataPart($start, ($this->pointer - $start));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->pointerRewind(2);
|
|
||||||
}//end if
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the full frame string block (old: get_image_block)
|
|
||||||
*/
|
|
||||||
private function getFrameString(int $type)
|
|
||||||
{
|
|
||||||
if ($this->checkByte(0x2c)) {
|
|
||||||
$start = $this->pointer;
|
|
||||||
$this->pointerForward(9);
|
|
||||||
|
|
||||||
if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) {
|
|
||||||
$this->pointerForward(pow(2, ($this->readBits($mybyte, 5, 3) + 1)) * 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->pointerForward(1);
|
|
||||||
$this->readDataStream($this->readByteInt());
|
|
||||||
$this->frameSources[$this->frameNumber]["imagedata"] = $this->dataPart($start, ($this->pointer - $start));
|
|
||||||
|
|
||||||
if ($type == 0) {
|
|
||||||
$this->orgvars["hasgx_type_0"] = 0;
|
|
||||||
|
|
||||||
if (isset($this->globaldata["graphicsextension_0"])) {
|
|
||||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"];
|
|
||||||
} else {
|
|
||||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($this->globaldata["graphicsextension_0"]);
|
|
||||||
} elseif ($type == 1) {
|
|
||||||
if (isset($this->orgvars["hasgx_type_1"]) && $this->orgvars["hasgx_type_1"] == 1) {
|
|
||||||
$this->orgvars["hasgx_type_1"] = 0;
|
|
||||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension"];
|
|
||||||
unset($this->globaldata["graphicsextension"]);
|
|
||||||
} else {
|
|
||||||
$this->orgvars["hasgx_type_0"] = 0;
|
|
||||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"];
|
|
||||||
unset($this->globaldata["graphicsextension_0"]);
|
|
||||||
}
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
$this->parseFrameData();
|
|
||||||
$this->frameNumber++;
|
|
||||||
}//end if
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse frame data string into an array (old: parse_image_data)
|
|
||||||
*/
|
|
||||||
private function parseFrameData()
|
|
||||||
{
|
|
||||||
$this->frameSources[$this->frameNumber]["disposal_method"] = $this->getImageDataBit("ext", 3, 3, 3);
|
|
||||||
$this->frameSources[$this->frameNumber]["user_input_flag"] = $this->getImageDataBit("ext", 3, 6, 1);
|
|
||||||
$this->frameSources[$this->frameNumber]["transparent_color_flag"] = $this->getImageDataBit("ext", 3, 7, 1);
|
|
||||||
$this->frameSources[$this->frameNumber]["delay_time"] = $this->dualByteVal($this->getImageDataByte("ext", 4, 2));
|
|
||||||
$this->totalDuration += (int) $this->frameSources[$this->frameNumber]["delay_time"];
|
|
||||||
$this->frameSources[$this->frameNumber]["transparent_color_index"] = ord($this->getImageDataByte("ext", 6, 1));
|
|
||||||
$this->frameSources[$this->frameNumber]["offset_left"] = $this->dualByteVal($this->getImageDataByte("dat", 1, 2));
|
|
||||||
$this->frameSources[$this->frameNumber]["offset_top"] = $this->dualByteVal($this->getImageDataByte("dat", 3, 2));
|
|
||||||
$this->frameSources[$this->frameNumber]["width"] = $this->dualByteVal($this->getImageDataByte("dat", 5, 2));
|
|
||||||
$this->frameSources[$this->frameNumber]["height"] = $this->dualByteVal($this->getImageDataByte("dat", 7, 2));
|
|
||||||
$this->frameSources[$this->frameNumber]["local_color_table_flag"] = $this->getImageDataBit("dat", 9, 0, 1);
|
|
||||||
$this->frameSources[$this->frameNumber]["interlace_flag"] = $this->getImageDataBit("dat", 9, 1, 1);
|
|
||||||
$this->frameSources[$this->frameNumber]["sort_flag"] = $this->getImageDataBit("dat", 9, 2, 1);
|
|
||||||
$this->frameSources[$this->frameNumber]["color_table_size"] = (pow(2, ($this->getImageDataBit("dat", 9, 5, 3) + 1)) * 3);
|
|
||||||
$this->frameSources[$this->frameNumber]["color_table"] = substr($this->frameSources[$this->frameNumber]["imagedata"], 10, $this->frameSources[$this->frameNumber]["color_table_size"]);
|
|
||||||
$this->frameSources[$this->frameNumber]["lzw_code_size"] = ord($this->getImageDataByte("dat", 10, 1));
|
|
||||||
|
|
||||||
$this->framePositions[$this->frameNumber] = [
|
|
||||||
'x' => $this->frameSources[$this->frameNumber]["offset_left"],
|
|
||||||
'y' => $this->frameSources[$this->frameNumber]["offset_top"],
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->frameDimensions[$this->frameNumber] = [
|
|
||||||
'width' => $this->frameSources[$this->frameNumber]["width"],
|
|
||||||
'height' => $this->frameSources[$this->frameNumber]["height"],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Decoding
|
|
||||||
$this->orgvars[$this->frameNumber]["transparent_color_flag"] = $this->frameSources[$this->frameNumber]["transparent_color_flag"];
|
|
||||||
$this->orgvars[$this->frameNumber]["transparent_color_index"] = $this->frameSources[$this->frameNumber]["transparent_color_index"];
|
|
||||||
$this->orgvars[$this->frameNumber]["delay_time"] = $this->frameSources[$this->frameNumber]["delay_time"];
|
|
||||||
$this->orgvars[$this->frameNumber]["disposal_method"] = $this->frameSources[$this->frameNumber]["disposal_method"];
|
|
||||||
$this->orgvars[$this->frameNumber]["offset_left"] = $this->frameSources[$this->frameNumber]["offset_left"];
|
|
||||||
$this->orgvars[$this->frameNumber]["offset_top"] = $this->frameSources[$this->frameNumber]["offset_top"];
|
|
||||||
|
|
||||||
// Updating the max width
|
|
||||||
if ($this->gifMaxWidth < $this->frameSources[$this->frameNumber]["width"]) {
|
|
||||||
$this->gifMaxWidth = $this->frameSources[$this->frameNumber]["width"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating the max height
|
|
||||||
if ($this->gifMaxHeight < $this->frameSources[$this->frameNumber]["height"]) {
|
|
||||||
$this->gifMaxHeight = $this->frameSources[$this->frameNumber]["height"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the image data byte (old: get_imagedata_byte)
|
|
||||||
*/
|
|
||||||
private function getImageDataByte(string $type, int $start, int $length): string
|
|
||||||
{
|
|
||||||
if ($type == "ext") {
|
|
||||||
return substr($this->frameSources[$this->frameNumber]["graphicsextension"], $start, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// "dat"
|
|
||||||
return substr($this->frameSources[$this->frameNumber]["imagedata"], $start, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the image data bit (old: get_imagedata_bit)
|
|
||||||
*/
|
|
||||||
private function getImageDataBit(string $type, int $byteIndex, int $bitStart, int $bitLength): number
|
|
||||||
{
|
|
||||||
if ($type == "ext") {
|
|
||||||
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["graphicsextension"], $byteIndex, 1)), $bitStart, $bitLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
// "dat"
|
|
||||||
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["imagedata"], $byteIndex, 1)), $bitStart, $bitLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the value of 2 ASCII chars (old: dualbyteval)
|
|
||||||
*/
|
|
||||||
private function dualByteVal(string $s): int
|
|
||||||
{
|
|
||||||
$i = (ord($s[1]) * 256 + ord($s[0]));
|
|
||||||
|
|
||||||
return $i;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the data stream (old: read_data_stream)
|
|
||||||
*/
|
|
||||||
private function readDataStream(int $firstLength)
|
|
||||||
{
|
|
||||||
$this->pointerForward($firstLength);
|
|
||||||
$length = $this->readByteInt();
|
|
||||||
|
|
||||||
if ($length != 0) {
|
|
||||||
while ($length != 0) {
|
|
||||||
$this->pointerForward($length);
|
|
||||||
$length = $this->readByteInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the gif file (old: loadfile)
|
|
||||||
*/
|
|
||||||
private function openFile(string $filename)
|
|
||||||
{
|
|
||||||
$this->handle = fopen($filename, "rb");
|
|
||||||
$this->pointer = 0;
|
|
||||||
|
|
||||||
$imageSize = getimagesize($filename);
|
|
||||||
$this->gifWidth = $imageSize[0];
|
|
||||||
$this->gifHeight = $imageSize[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the read gif file (old: closefile)
|
|
||||||
*/
|
|
||||||
private function closeFile()
|
|
||||||
{
|
|
||||||
fclose($this->handle);
|
|
||||||
$this->handle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the file from the beginning to $byteCount in binary (old: readbyte)
|
|
||||||
*/
|
|
||||||
private function readByte(int $byteCount): string
|
|
||||||
{
|
|
||||||
$data = fread($this->handle, $byteCount);
|
|
||||||
$this->pointer += $byteCount;
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a byte and return ASCII value (old: readbyte_int)
|
|
||||||
*/
|
|
||||||
private function readByteInt(): int
|
|
||||||
{
|
|
||||||
$data = fread($this->handle, 1);
|
|
||||||
$this->pointer++;
|
|
||||||
|
|
||||||
return ord($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a $byte to decimal (old: readbits)
|
|
||||||
*/
|
|
||||||
private function readBits(string $byte, int $start, int $length): number
|
|
||||||
{
|
|
||||||
$bin = str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
|
|
||||||
$data = substr($bin, $start, $length);
|
|
||||||
|
|
||||||
return bindec($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rewind the file pointer reader (old: p_rewind)
|
|
||||||
*/
|
|
||||||
private function pointerRewind(int $length)
|
|
||||||
{
|
|
||||||
$this->pointer -= $length;
|
|
||||||
fseek($this->handle, $this->pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forward the file pointer reader (old: p_forward)
|
|
||||||
*/
|
|
||||||
private function pointerForward(int $length)
|
|
||||||
{
|
|
||||||
$this->pointer += $length;
|
|
||||||
fseek($this->handle, $this->pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a section of the data from $start to $start + $length (old: datapart)
|
|
||||||
*/
|
|
||||||
private function dataPart(int $start, int $length): string
|
|
||||||
{
|
|
||||||
fseek($this->handle, $start);
|
|
||||||
$data = fread($this->handle, $length);
|
|
||||||
fseek($this->handle, $this->pointer);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a character if a byte (old: checkbyte)
|
|
||||||
*/
|
|
||||||
private function checkByte(int $byte): bool
|
|
||||||
{
|
|
||||||
if (fgetc($this->handle) == chr($byte)) {
|
|
||||||
fseek($this->handle, $this->pointer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek($this->handle, $this->pointer);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the end of the file (old: checkEOF)
|
|
||||||
*/
|
|
||||||
private function checkEOF(): bool
|
|
||||||
{
|
|
||||||
if (fgetc($this->handle) === false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek($this->handle, $this->pointer);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset and clear this current object
|
|
||||||
*/
|
|
||||||
private function reset()
|
|
||||||
{
|
|
||||||
$this->gif = null;
|
|
||||||
$this->totalDuration = $this->gifMaxHeight = $this->gifMaxWidth = $this->handle = $this->pointer = $this->frameNumber = 0;
|
|
||||||
$this->frameDimensions = $this->framePositions = $this->frameImages = $this->frameDurations = $this->globaldata = $this->orgvars = $this->frames = $this->fileHeader = $this->frameSources = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getter / Setter
|
|
||||||
// ===================================================================================
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the total of all added frame duration
|
|
||||||
*/
|
|
||||||
public function getTotalDuration(): int
|
|
||||||
{
|
|
||||||
return $this->totalDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the number of extracted frames
|
|
||||||
*/
|
|
||||||
public function getFrameNumber(): int
|
|
||||||
{
|
|
||||||
return $this->frameNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extracted frames (images and durations)
|
|
||||||
*/
|
|
||||||
public function getFrames(): array
|
|
||||||
{
|
|
||||||
return $this->frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extracted frame positions
|
|
||||||
*/
|
|
||||||
public function getFramePositions(): array
|
|
||||||
{
|
|
||||||
return $this->framePositions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extracted frame dimensions
|
|
||||||
*/
|
|
||||||
public function getFrameDimensions(): array
|
|
||||||
{
|
|
||||||
return $this->frameDimensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extracted frame images
|
|
||||||
*/
|
|
||||||
public function getFrameImages(): array
|
|
||||||
{
|
|
||||||
return $this->frameImages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the extracted frame durations
|
|
||||||
*/
|
|
||||||
public function getFrameDurations(): array
|
|
||||||
{
|
|
||||||
return $this->frameDurations;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
98
app/Traits/HasAttachments.php
Normal file
98
app/Traits/HasAttachments.php
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
trait HasAttachments
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Boot function from Laravel.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function bootHasAttachments(): void
|
||||||
|
{
|
||||||
|
static::deleting(function ($model) {
|
||||||
|
$model->deleteAttachments();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attachments associated to this item.
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getAttachments(): Collection
|
||||||
|
{
|
||||||
|
return Cache::remember($this->attachmentsCacheKey(), now()->addDays(28), function () {
|
||||||
|
return $this->attachments()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add multiple attachments items to the model.
|
||||||
|
*
|
||||||
|
* @param array|string $ids The media ids to add.
|
||||||
|
* @param string $delimiter The split delimiter if $ids is a string.
|
||||||
|
* @param boolean $allowDuplicates Whether to allow duplicate media IDs or not.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function addAttachments(array|string $ids, string $delimiter = ',', bool $allowDuplicates = false): void
|
||||||
|
{
|
||||||
|
if (is_array($ids) === false) {
|
||||||
|
$ids = explode($delimiter, $ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_map('trim', $ids);
|
||||||
|
$existingIds = $this->getAttachments()->pluck('media_id')->toArray();
|
||||||
|
|
||||||
|
$attachmentItems = [];
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if ($allowDuplicates === false && in_array($id, $existingIds) === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Media::where('id', $id)->exists() === true) {
|
||||||
|
$attachmentItems[] = ['media_id' => $id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache::forget($this->attachmentsCacheKey());
|
||||||
|
$this->attachments()->createMany($attachmentItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete associated attachments.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteAttachments(): void
|
||||||
|
{
|
||||||
|
Cache::forget($this->attachmentsCacheKey());
|
||||||
|
$this->morphMany(\App\Models\Attachment::class, 'addendum')->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the article's attachments.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\MorphMany The attachments items
|
||||||
|
*/
|
||||||
|
public function attachments(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(\App\Models\Attachment::class, 'addendum');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the attachment cache key.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function attachmentsCacheKey(): string
|
||||||
|
{
|
||||||
|
return "attachments:{$this->getTable()}:{$this->id}";
|
||||||
|
}
|
||||||
|
}
|
||||||
88
app/Traits/HasGallery.php
Normal file
88
app/Traits/HasGallery.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
trait HasGallery
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Boot function from Laravel.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected static function bootHasGallery(): void
|
||||||
|
{
|
||||||
|
static::deleting(function ($model) {
|
||||||
|
$model->gallery()->delete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add multiple gallery items to the model.
|
||||||
|
*
|
||||||
|
* @param array|string $ids The media ids to add.
|
||||||
|
* @param string $delimiter The split delimiter if $ids is a string.
|
||||||
|
* @param boolean $allowDuplicates Whether to allow duplicate media IDs or not.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function galleryAddMany(array|string $ids, string $delimiter = ',', bool $allowDuplicates = false): void
|
||||||
|
{
|
||||||
|
if (is_array($ids) === false) {
|
||||||
|
$ids = explode($delimiter, $ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = array_map('trim', $ids);
|
||||||
|
$existingIds = $this->gallery()->pluck('media_id')->toArray();
|
||||||
|
|
||||||
|
$galleryItems = [];
|
||||||
|
foreach ($ids as $id) {
|
||||||
|
if ($allowDuplicates === false && in_array($id, $existingIds) === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$media = Media::find($id);
|
||||||
|
if ($media !== null) {
|
||||||
|
$galleryItems[] = ['media_id' => $id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache::forget($this->galleryCacheKey());
|
||||||
|
$this->gallery()->createMany($galleryItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the article's gallery.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Relations\MorphMany The gallery items
|
||||||
|
*/
|
||||||
|
public function gallery(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(\App\Models\Gallery::class, 'addendum');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the article's gallery collection.
|
||||||
|
*
|
||||||
|
* @return Illuminate\Database\Eloquent\Collection The gallery collection
|
||||||
|
*/
|
||||||
|
public function getGallery(): Collection
|
||||||
|
{
|
||||||
|
return Cache::remember($this->galleryCacheKey(), now()->addDays(28), function () {
|
||||||
|
return $this->gallery()->get();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the attachment cache key.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function galleryCacheKey(): string
|
||||||
|
{
|
||||||
|
return "gallery:{$this->getTable()}:{$this->id}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ trait Uuids
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Boot function from Laravel.
|
* Boot function from Laravel.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected static function bootUuids(): void
|
protected static function bootUuids(): void
|
||||||
{
|
{
|
||||||
@@ -20,6 +22,8 @@ trait Uuids
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value indicating whether the IDs are incrementing.
|
* Get the value indicating whether the IDs are incrementing.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function getIncrementing(): bool
|
public function getIncrementing(): bool
|
||||||
{
|
{
|
||||||
@@ -28,6 +32,8 @@ trait Uuids
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the auto-incrementing key type.
|
* Get the auto-incrementing key type.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getKeyType(): string
|
public function getKeyType(): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,45 +8,42 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1",
|
"php": "^8.2",
|
||||||
"doctrine/dbal": "^3.5",
|
"doctrine/dbal": "^3.5",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"inertiajs/inertia-laravel": "^0.6.11",
|
||||||
"intervention/image": "^2.7",
|
"laravel/framework": "^10.10",
|
||||||
"laravel/framework": "^10.12",
|
"laravel/sanctum": "^3.3",
|
||||||
"laravel/sanctum": "^3.2",
|
|
||||||
"laravel/tinker": "^2.8",
|
"laravel/tinker": "^2.8",
|
||||||
"league/flysystem-aws-s3-v3": "^3.12",
|
"owen-it/laravel-auditing": "^13.5",
|
||||||
"owen-it/laravel-auditing": "^13.1",
|
"spatie/image": "^2.2",
|
||||||
"php-ffmpeg/php-ffmpeg": "^1.1",
|
"spatie/laravel-permission": "^6.2",
|
||||||
"sunspikes/clamav-validator": "*",
|
"tightenco/ziggy": "^1.8"
|
||||||
"thiagoalessio/tesseract_ocr": "^2.12",
|
|
||||||
"vlucas/phpdotenv": "^5.5"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"barryvdh/laravel-ide-helper": "^2.13",
|
||||||
"fakerphp/faker": "^1.9.1",
|
"fakerphp/faker": "^1.9.1",
|
||||||
"laravel/pint": "^1.0",
|
"laravel/pint": "^1.0",
|
||||||
"laravel/sail": "^1.18",
|
"laravel/sail": "^1.18",
|
||||||
"mockery/mockery": "^1.4.4",
|
"mockery/mockery": "^1.4.4",
|
||||||
"nunomaduro/collision": "^7.1",
|
"nunomaduro/collision": "^7.0",
|
||||||
"phpunit/phpunit": "^10.1.3",
|
"phpunit/phpunit": "^10.1",
|
||||||
"spatie/laravel-ignition": "^2.0"
|
"spatie/laravel-ignition": "^2.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"files": [
|
"files": [
|
||||||
"app/Helpers/Array.php",
|
"app/Helpers/Array.php",
|
||||||
"app/Helpers/Temp.php"
|
"app/Helpers/Temp.php",
|
||||||
|
"app/Helpers/TypeValue.php"
|
||||||
],
|
],
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
"Database\\Factories\\": "database/factories/",
|
"Database\\Factories\\": "database/factories/",
|
||||||
"Database\\Seeders\\": "database/seeders/",
|
"Database\\Seeders\\": "database/seeders/"
|
||||||
"Faker\\Provider\\": "faker/provider/"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload-dev": {
|
"autoload-dev": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Tests\\": "tests/",
|
"Tests\\": "tests/"
|
||||||
"Faker\\Provider\\": "faker/provider/"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -74,7 +71,8 @@
|
|||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true
|
"pestphp/pest-plugin": true,
|
||||||
|
"php-http/discovery": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
|
|||||||
3769
composer.lock
generated
3769
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -36,7 +36,7 @@ return [
|
|||||||
'secret' => env('PUSHER_APP_SECRET'),
|
'secret' => env('PUSHER_APP_SECRET'),
|
||||||
'app_id' => env('PUSHER_APP_ID'),
|
'app_id' => env('PUSHER_APP_ID'),
|
||||||
'options' => [
|
'options' => [
|
||||||
'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com',
|
'host' => env('PUSHER_HOST') === null ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com',
|
||||||
'port' => env('PUSHER_PORT', 443),
|
'port' => env('PUSHER_PORT', 443),
|
||||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||||
'encrypted' => true,
|
'encrypted' => true,
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Socket connect timeout
|
| Socket connect timeout
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| This option defines the maximum time to wait in seconds for socket connection attempts before failure or timeout, default null = no limit.
|
| This option defines the maximum time to wait in seconds for socket connection attempts before failure or timeout,
|
||||||
|
| default null = no limit.
|
||||||
*/
|
*/
|
||||||
'socket_connect_timeout' => env('CLAMAV_SOCKET_CONNECT_TIMEOUT', null),
|
'socket_connect_timeout' => env('CLAMAV_SOCKET_CONNECT_TIMEOUT', null),
|
||||||
|
|
||||||
|
|||||||
416
config/clockwork.php
Normal file
416
config/clockwork.php
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Enable Clockwork
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork is enabled by default only when your application is in debug mode. Here you can explicitly enable or
|
||||||
|
| disable Clockwork. When disabled, no data is collected and the api and web ui are inactive.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enable' => env('CLOCKWORK_ENABLE', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Features
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query
|
||||||
|
| threshold for database queries).
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'features' => [
|
||||||
|
|
||||||
|
// Cache usage stats and cache queries including results
|
||||||
|
'cache' => [
|
||||||
|
'enabled' => env('CLOCKWORK_CACHE_ENABLED', true),
|
||||||
|
|
||||||
|
// Collect cache queries
|
||||||
|
'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', true),
|
||||||
|
|
||||||
|
// Collect values from cache queries (high performance impact with a very high number of queries)
|
||||||
|
'collect_values' => env('CLOCKWORK_CACHE_COLLECT_VALUES', false)
|
||||||
|
],
|
||||||
|
|
||||||
|
// Database usage stats and queries
|
||||||
|
'database' => [
|
||||||
|
'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true),
|
||||||
|
|
||||||
|
// Collect database queries (high performance impact with a very high number of queries)
|
||||||
|
'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true),
|
||||||
|
|
||||||
|
// Collect details of models updates (high performance impact with a lot of model updates)
|
||||||
|
'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true),
|
||||||
|
|
||||||
|
// Collect details of retrieved models (very high performance impact with a lot of models retrieved)
|
||||||
|
'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false),
|
||||||
|
|
||||||
|
// Query execution time threshold in milliseconds after which the query will be marked as slow
|
||||||
|
'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'),
|
||||||
|
|
||||||
|
// Collect only slow database queries
|
||||||
|
'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false),
|
||||||
|
|
||||||
|
// Detect and report duplicate queries
|
||||||
|
'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false)
|
||||||
|
],
|
||||||
|
|
||||||
|
// Dispatched events
|
||||||
|
'events' => [
|
||||||
|
'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true),
|
||||||
|
|
||||||
|
// Ignored events (framework events are ignored by default)
|
||||||
|
'ignored_events' => [
|
||||||
|
// App\Events\UserRegistered::class,
|
||||||
|
// 'user.registered'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Laravel log (you can still log directly to Clockwork with laravel log disabled)
|
||||||
|
'log' => [
|
||||||
|
'enabled' => env('CLOCKWORK_LOG_ENABLED', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
// Sent notifications
|
||||||
|
'notifications' => [
|
||||||
|
'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Performance metrics
|
||||||
|
'performance' => [
|
||||||
|
// Allow collecting of client metrics. Requires separate clockwork-browser npm package.
|
||||||
|
'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
// Dispatched queue jobs
|
||||||
|
'queue' => [
|
||||||
|
'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
// Redis commands
|
||||||
|
'redis' => [
|
||||||
|
'enabled' => env('CLOCKWORK_REDIS_ENABLED', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
// Routes list
|
||||||
|
'routes' => [
|
||||||
|
'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false),
|
||||||
|
|
||||||
|
// Collect only routes from particular namespaces (only application routes by default)
|
||||||
|
'only_namespaces' => [ 'App' ]
|
||||||
|
],
|
||||||
|
|
||||||
|
// Rendered views
|
||||||
|
'views' => [
|
||||||
|
'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true),
|
||||||
|
|
||||||
|
// Collect views including view data (high performance impact with a high number of views)
|
||||||
|
'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false),
|
||||||
|
|
||||||
|
// Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does
|
||||||
|
// not support collecting view data)
|
||||||
|
'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false)
|
||||||
|
]
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Enable web UI
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork comes with a web UI accessible via http://your.app/clockwork. Here you can enable or disable this
|
||||||
|
| feature. You can also set a custom path for the web UI.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'web' => env('CLOCKWORK_WEB', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Enable toolbar
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature.
|
||||||
|
| Requires a separate clockwork-browser npm library.
|
||||||
|
| For installation instructions see https://underground.works/clockwork/#docs-viewing-data
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'toolbar' => env('CLOCKWORK_TOOLBAR', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| HTTP requests collection
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'requests' => [
|
||||||
|
// With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you
|
||||||
|
// manually pass a "clockwork-profile" cookie or get/post data key.
|
||||||
|
// Optionally you can specify a "secret" that has to be passed as the value to enable profiling.
|
||||||
|
'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false),
|
||||||
|
|
||||||
|
// Collect only errors (requests with HTTP 4xx and 5xx responses)
|
||||||
|
'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false),
|
||||||
|
|
||||||
|
// Response time threshold in milliseconds after which the request will be marked as slow
|
||||||
|
'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'),
|
||||||
|
|
||||||
|
// Collect only slow requests
|
||||||
|
'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false),
|
||||||
|
|
||||||
|
// Sample the collected requests (e.g. set to 100 to collect only 1 in 100 requests)
|
||||||
|
'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false),
|
||||||
|
|
||||||
|
// List of URIs that should not be collected
|
||||||
|
'except' => [
|
||||||
|
'/horizon/.*', // Laravel Horizon requests
|
||||||
|
'/telescope/.*', // Laravel Telescope requests
|
||||||
|
'/_debugbar/.*', // Laravel DebugBar requests
|
||||||
|
],
|
||||||
|
|
||||||
|
// List of URIs that should be collected, any other URI will not be collected if not empty
|
||||||
|
'only' => [
|
||||||
|
// '/api/.*'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest
|
||||||
|
'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Artisan commands collection
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands
|
||||||
|
| should be collected.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'artisan' => [
|
||||||
|
// Enable or disable collection of executed Artisan commands
|
||||||
|
'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false),
|
||||||
|
|
||||||
|
// List of commands that should not be collected (built-in commands are not collected by default)
|
||||||
|
'except' => [
|
||||||
|
// 'inspire'
|
||||||
|
],
|
||||||
|
|
||||||
|
// List of commands that should be collected, any other command will not be collected if not empty
|
||||||
|
'only' => [
|
||||||
|
// 'inspire'
|
||||||
|
],
|
||||||
|
|
||||||
|
// Enable or disable collection of command output
|
||||||
|
'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false),
|
||||||
|
|
||||||
|
// Enable or disable collection of built-in Laravel commands
|
||||||
|
'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true)
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Queue jobs collection
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should
|
||||||
|
| be collected.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'queue' => [
|
||||||
|
// Enable or disable collection of executed queue jobs
|
||||||
|
'collect' => env('CLOCKWORK_QUEUE_COLLECT', false),
|
||||||
|
|
||||||
|
// List of queue jobs that should not be collected
|
||||||
|
'except' => [
|
||||||
|
// App\Jobs\ExpensiveJob::class
|
||||||
|
],
|
||||||
|
|
||||||
|
// List of queue jobs that should be collected, any other queue job will not be collected if not empty
|
||||||
|
'only' => [
|
||||||
|
// App\Jobs\BuggyJob::class
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Tests collection
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork can collect data about executed tests. Here you can enable and configure which tests should be
|
||||||
|
| collected.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'tests' => [
|
||||||
|
// Enable or disable collection of ran tests
|
||||||
|
'collect' => env('CLOCKWORK_TESTS_COLLECT', false),
|
||||||
|
|
||||||
|
// List of tests that should not be collected
|
||||||
|
'except' => [
|
||||||
|
// Tests\Unit\ExampleTest::class
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Enable data collection when Clockwork is disabled
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| You can enable this setting to collect data even when Clockwork is disabled, e.g. for future analysis.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Metadata storage
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Configure how is the metadata collected by Clockwork stored. Two options are available:
|
||||||
|
| - files - A simple fast storage implementation storing data in one-per-request files.
|
||||||
|
| - sql - Stores requests in a sql database. Supports MySQL, PostgreSQL and SQLite. Requires PDO.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'storage' => env('CLOCKWORK_STORAGE', 'files'),
|
||||||
|
|
||||||
|
// Path where the Clockwork metadata is stored
|
||||||
|
'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')),
|
||||||
|
|
||||||
|
// Compress the metadata files using gzip, trading a little bit of performance for lower disk usage
|
||||||
|
'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false),
|
||||||
|
|
||||||
|
// SQL database to use, can be a name of database configured in database.php or a path to a SQLite file
|
||||||
|
'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')),
|
||||||
|
|
||||||
|
// SQL table name to use, the table is automatically created and updated when needed
|
||||||
|
'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'),
|
||||||
|
|
||||||
|
// Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable
|
||||||
|
'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', (60 * 24 * 7)),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Authentication
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork can be configured to require authentication before allowing access to the collected data. This might be
|
||||||
|
| useful when the application is publicly accessible. Setting to true will enable a simple authentication with a
|
||||||
|
| pre-configured password. You can also pass a class name of a custom implementation.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'authentication' => env('CLOCKWORK_AUTHENTICATION', false),
|
||||||
|
|
||||||
|
// Password for the simple authentication
|
||||||
|
'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Stack traces collection
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set
|
||||||
|
| whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting
|
||||||
|
| long stack traces considerably increases metadata size.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stack_traces' => [
|
||||||
|
// Enable or disable collecting of stack traces
|
||||||
|
'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true),
|
||||||
|
|
||||||
|
// Limit the number of frames to be collected
|
||||||
|
'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10),
|
||||||
|
|
||||||
|
// List of vendor names to skip when determining caller, common vendors are automatically added
|
||||||
|
'skip_vendors' => [
|
||||||
|
// 'phpunit'
|
||||||
|
],
|
||||||
|
|
||||||
|
// List of namespaces to skip when determining caller
|
||||||
|
'skip_namespaces' => [
|
||||||
|
// 'Laravel'
|
||||||
|
],
|
||||||
|
|
||||||
|
// List of class names to skip when determining caller
|
||||||
|
'skip_classes' => [
|
||||||
|
// App\CustomLog::class
|
||||||
|
]
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Serialization
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects
|
||||||
|
| of serialization. Serialization has a large effect on the cpu time and memory usage.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Maximum depth of serialized multi-level arrays and objects
|
||||||
|
'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10),
|
||||||
|
|
||||||
|
// A list of classes that will never be serialized (e.g. a common service container class)
|
||||||
|
'serialization_blackbox' => [
|
||||||
|
\Illuminate\Container\Container::class,
|
||||||
|
\Illuminate\Foundation\Application::class,
|
||||||
|
\Laravel\Lumen\Application::class
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Register helpers
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to
|
||||||
|
| access the Clockwork instance.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Send headers for AJAX request
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When trying to collect data, the AJAX method can sometimes fail if it is missing required headers. For example, an
|
||||||
|
| API might require a version number using Accept headers to route the HTTP request to the correct codebase.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'headers' => [
|
||||||
|
// 'Accept' => 'application/vnd.com.whatever.v1+json',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
| Server timing
|
||||||
|
|------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics
|
||||||
|
| in a cross-browser way. E.g. in Chrome, your app, database and timeline event timings will be shown in the Dev
|
||||||
|
| Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false
|
||||||
|
| will disable the feature.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10)
|
||||||
|
|
||||||
|
];
|
||||||
@@ -58,7 +58,7 @@ return [
|
|||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'engine' => null,
|
'engine' => null,
|
||||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
'options' => extension_loaded('pdo_mysql') === true ? array_filter([
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
]) : [],
|
]) : [],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ return [
|
|||||||
'local' => [
|
'local' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => storage_path('app/public'),
|
'root' => storage_path('app/public'),
|
||||||
'url' => env('APP_URL') . "/storage",
|
'url' => env('APP_URL') . "/storage/{name}",
|
||||||
'public' => true,
|
'public' => true,
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
],
|
],
|
||||||
@@ -44,7 +44,7 @@ return [
|
|||||||
'secret' => env('AWS_PUBLIC_SECRET_ACCESS_KEY'),
|
'secret' => env('AWS_PUBLIC_SECRET_ACCESS_KEY'),
|
||||||
'region' => env('AWS_PUBLIC_DEFAULT_REGION'),
|
'region' => env('AWS_PUBLIC_DEFAULT_REGION'),
|
||||||
'bucket' => env('AWS_PUBLIC_BUCKET'),
|
'bucket' => env('AWS_PUBLIC_BUCKET'),
|
||||||
'url' => env('AWS_PUBLIC_URL'),
|
'url' => env('AWS_PUBLIC_URL') . '/{name}',
|
||||||
'endpoint' => env('AWS_PUBLIC_ENDPOINT'),
|
'endpoint' => env('AWS_PUBLIC_ENDPOINT'),
|
||||||
'use_path_style_endpoint' => env('AWS_PUBLIC_USE_PATH_STYLE_ENDPOINT', false),
|
'use_path_style_endpoint' => env('AWS_PUBLIC_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
@@ -55,41 +55,20 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'private' => [
|
'private' => [
|
||||||
'driver' => 's3',
|
'driver' => 'local',
|
||||||
'key' => env('AWS_PRIVATE_ACCESS_KEY_ID'),
|
'root' => storage_path('app/private'),
|
||||||
'secret' => env('AWS_PRIVATE_SECRET_ACCESS_KEY'),
|
'url' => env('APP_URL_API') . '/media/{id}/download',
|
||||||
'region' => env('AWS_PRIVATE_DEFAULT_REGION'),
|
'visibility' => 'private',
|
||||||
'bucket' => env('AWS_PRIVATE_BUCKET'),
|
|
||||||
'url' => env('AWS_PRIVATE_URL'),
|
|
||||||
'endpoint' => env('AWS_PRIVATE_ENDPOINT'),
|
|
||||||
'use_path_style_endpoint' => env('AWS_PRIVATE_USE_PATH_STYLE_ENDPOINT', false),
|
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
'public' => false,
|
|
||||||
'options' => [
|
|
||||||
'ACL' => '',
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'public' => [
|
'public' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => storage_path('app/public'),
|
'root' => storage_path('app/public'),
|
||||||
'url' => env('APP_URL') . '/storage',
|
'url' => env('APP_URL') . '/storage/{name}',
|
||||||
'visibility' => 'public',
|
'visibility' => 'public',
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
's3' => [
|
|
||||||
'driver' => 's3',
|
|
||||||
'key' => env('AWS_ACCESS_KEY_ID'),
|
|
||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
|
||||||
'region' => env('AWS_DEFAULT_REGION'),
|
|
||||||
'bucket' => env('AWS_BUCKET'),
|
|
||||||
'url' => env('AWS_URL'),
|
|
||||||
'endpoint' => env('AWS_ENDPOINT'),
|
|
||||||
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
|
|
||||||
'throw' => false,
|
|
||||||
],
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -30,14 +30,4 @@ return [
|
|||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
],
|
],
|
||||||
<<<<<<< HEAD
|
|
||||||
|
|
||||||
'google_recaptcha' => [
|
|
||||||
'url' => 'https://www.google.com/recaptcha/api/siteverify',
|
|
||||||
'site_key' => env('GOOGLE_RECAPTCHA_SITE_KEY'),
|
|
||||||
'secret_key' => env('GOOGLE_RECAPTCHA_SECRET_SITE_KEY'),
|
|
||||||
],
|
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> 086cf9e (removed obsolete recaptcha)
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -58,39 +58,6 @@ return new class extends Migration
|
|||||||
$lastSessionUpdate = $sameSessionRows->first()->created_at;
|
$lastSessionUpdate = $sameSessionRows->first()->created_at;
|
||||||
} while(true);
|
} while(true);
|
||||||
} while(true);
|
} while(true);
|
||||||
|
|
||||||
|
|
||||||
// Loop through the rows and update `session` based on the logic you described
|
|
||||||
// foreach ($rows as $row) {
|
|
||||||
// // Check if this is the first row
|
|
||||||
// if ($row->created_at === $rows->first()->created_at) {
|
|
||||||
// DB::table('analytics')
|
|
||||||
// ->where('id', $row->id)
|
|
||||||
// ->update(['session' => $session]);
|
|
||||||
// } else {
|
|
||||||
// // Look for a previous row with the same useragent and ip within the last 30 minutes
|
|
||||||
// $previousRow = DB::table('analytics')
|
|
||||||
// ->where('useragent', $row->useragent)
|
|
||||||
// ->where('ip', $row->ip)
|
|
||||||
// ->where('created_at', '>=', date('Y-m-d H:i:s', strtotime('-30 minutes', strtotime($row->created_at))))
|
|
||||||
// ->whereNotNull('session')
|
|
||||||
// ->orderBy('created_at', 'desc')
|
|
||||||
// ->first();
|
|
||||||
|
|
||||||
// if ($previousRow) {
|
|
||||||
// // If a previous row is found, set the session to the same value
|
|
||||||
// DB::table('analytics')
|
|
||||||
// ->where('id', $row->id)
|
|
||||||
// ->update(['session' => $previousRow->session]);
|
|
||||||
// } else {
|
|
||||||
// // If no previous row is found, increment the session value
|
|
||||||
// $session++;
|
|
||||||
// DB::table('analytics')
|
|
||||||
// ->where('id', $row->id)
|
|
||||||
// ->update(['session' => $session]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ return new class extends Migration
|
|||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::table('shortlinks', function (Blueprint $table) {
|
Schema::table('shortlinks', function (Blueprint $table) {
|
||||||
//
|
$table->dropColumn('used');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user