Compare commits
506 Commits
media-upda
...
shift-9188
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f536ae5a9 | ||
|
|
95395d1da7 | ||
|
|
c6c639afc2 | ||
| 41147b26f2 | |||
|
|
fbf437ac99 | ||
|
|
5faf49688d | ||
|
|
4d7d0ed74d | ||
|
|
979b9f704c | ||
|
|
4124cf39db | ||
|
|
c83e21d588 | ||
|
|
c88630e9af | ||
|
|
40b265e145 | ||
|
|
3ad2b2fb8e | ||
|
|
8b671065e9 | ||
|
|
028e1a191e | ||
|
|
a133f82997 | ||
|
|
c4f3eb9a4e | ||
|
|
8a52c4529f | ||
|
|
d0493f3dd0 | ||
|
|
b845552c37 | ||
| 4e97209494 | |||
| aa0b010bed | |||
| cb79ea64cf | |||
|
|
38409d0d63 | ||
|
|
32dfb4eef3 | ||
|
|
a6de64a089 | ||
|
|
b226814676 | ||
|
|
805de3291b | ||
|
|
b0ab63e30e | ||
|
|
13bfc52b77 | ||
|
|
bbce78cab4 | ||
|
|
1e084d5131 | ||
|
|
af699161da | ||
| 2e1c2cd0b2 | |||
| cd7366b8ff | |||
| ec74f6594c | |||
| 5d2e9affc0 | |||
| 06b7ce4db0 | |||
|
|
4bf695f559 | ||
| 59daa1ff08 | |||
| 04044673e2 | |||
| 3b837dc6b0 | |||
| 2f029e2523 | |||
| 66d477795f | |||
| 78f23db801 | |||
| e62a21c469 | |||
| 9756622148 | |||
| 065cb1b746 | |||
| 54a7ad86dc | |||
| 0e7c86ac2b | |||
| e023964cb2 | |||
| 16f4eb65ef | |||
| 11eb12324e | |||
| 7a0d3fc8a0 | |||
| 3ed2aadc34 | |||
| b2004e3483 | |||
| 55f363a64f | |||
| 7a6ed9f7f4 | |||
| 8fa8c85077 | |||
| 245ffc9d45 | |||
| 6a9a2f0a9e | |||
| 9dbefe5a8a | |||
| cce2a79ee4 | |||
| 8e94ab2d7d | |||
| 6e0337cdeb | |||
| 1c3b8f065e | |||
| c43d5574b4 | |||
| 0e5c654b02 | |||
| 14d6d59581 | |||
| 3796961293 | |||
| c471a97a23 | |||
| fc853bd5f1 | |||
| d0ea0ae4d3 | |||
| 8a6d1281bb | |||
| 8797d51ef4 | |||
| 2c8ac1f155 | |||
| 42706de9df | |||
| 3ce99b8751 | |||
| 44c4f16c5c | |||
| cdccde528e | |||
| f32655c156 | |||
| d7255f004d | |||
| 56a1aaa19c | |||
| 86491bfb2e | |||
| 8dc43ccfce | |||
| 7ff49700fd | |||
| e14c7aafb3 | |||
| 0f727168be | |||
| 0de47b3104 | |||
| 6b3eb97568 | |||
| bd4ba41b0b | |||
| fb2f2d9739 | |||
| a468ae01ff | |||
| 6d534bd1c3 | |||
| 31820317de | |||
| e2efa1f1bd | |||
| 86a0936cd4 | |||
| 645b623a40 | |||
| 96bd56a828 | |||
| 870f1c5194 | |||
| 5d1adf7af8 | |||
| a1170a1347 | |||
| 9b5aab6e6e | |||
| a25776fbbb | |||
| 54b5929aa4 | |||
| 729fc3fd39 | |||
| b4cf05ad44 | |||
| 4da8b32b1a | |||
| c35342df59 | |||
| 7d8d407d07 | |||
| 1ceb109a28 | |||
| a8181ff2b7 | |||
| e42c4c3023 | |||
| ff040eec58 | |||
| 5d663d21b3 | |||
| b73c2d3726 | |||
| ee96acbe4f | |||
| fd22b79d42 | |||
| 99f56b9ef8 | |||
| 9a686c1112 | |||
| ffcf823a7f | |||
| a85c4bf115 | |||
| 29d7167c24 | |||
| 3c6a570394 | |||
| 419fa322a3 | |||
| 71a2e1b6dd | |||
| a352c21198 | |||
| 4accb60a24 | |||
| 88b92a9572 | |||
| e39fa78981 | |||
| 4205113b00 | |||
| ad47efefaf | |||
| 890399dd74 | |||
| 2698ede55e | |||
| c9f0ea2512 | |||
| 228f9c7c6b | |||
| 63e05e924a | |||
| a66cde3934 | |||
| 0b8a2904ec | |||
| 5fffa97ea7 | |||
| 1d14d86d8e | |||
| 217ab89667 | |||
| ac2dd23ad7 | |||
| 7a4f72378d | |||
| 8190839823 | |||
| 4e9d97268f | |||
| 985f7c94da | |||
| 3d28e73369 | |||
| 4076b138b9 | |||
|
|
99d4c709bd | ||
| 93951cfbc8 | |||
| 4ac86c434e | |||
| 171cfa7aab | |||
| a494dbe662 | |||
| c9d02fb11c | |||
| 3ba65385c5 | |||
| 58a2da1996 | |||
| 845d6ba12b | |||
| f0b55b7b2e | |||
| e6dd75c2a8 | |||
| 7b7154085e | |||
| 1c119e80e9 | |||
| 3d86f859c6 | |||
| 0bfad00df7 | |||
| 64efa723b3 | |||
| 0072b28965 | |||
| 21fa5d24af | |||
| 16ec3c515e | |||
| 6868144e25 | |||
| 0e5af96900 | |||
| ded5caf271 | |||
| e8e1e91d1c | |||
| 6a16d545ec | |||
| 976b6fbb78 | |||
| efc5571fb3 | |||
| 6ad4b3a6c4 | |||
| cc0fe080cf | |||
|
|
1de89fba5f | ||
| 0c668c9c62 | |||
| 6a3c3a3566 | |||
| e17f79e0b1 | |||
| f0461fb65f | |||
| aa38927522 | |||
| 881d06deea | |||
| 683869214b | |||
| 3322a6e005 | |||
| 65a48454ba | |||
| 0de8e17593 | |||
| 17beb4152b | |||
| b09097294f | |||
| c6e1b0248d | |||
| 0a956e1fc5 | |||
| e2dee426bf | |||
| 37a738c094 | |||
| bef4c3440b | |||
| b36ad8042f | |||
| 6ec38853ff | |||
| 69144a665f | |||
| 382a1d0ef8 | |||
| 41c751a76d | |||
| 01e46042fb | |||
| f1a28b6efe | |||
| 7fddeeeaae | |||
| bacf35bb4b | |||
| 4a83c7e171 | |||
| 3cbce25394 | |||
|
|
c2b58cc82d | ||
| ac326d2d74 | |||
| 50aaf8b343 | |||
| f667ac6430 | |||
| e2e599ed35 | |||
| 897e422e15 | |||
| 0031be6882 | |||
| 45880ed7a8 | |||
| b7e964174a | |||
| 77b8f60cb1 | |||
| 4cbde00ef3 | |||
| 196472181e | |||
| 2fa7a0c7a5 | |||
| 38e982e70b | |||
| 8a9b57547a | |||
| 6dd3d6255d | |||
| 656de567d7 | |||
| 24e80d7851 | |||
| bc642b48da | |||
| cc07998a8a | |||
| 178309bb1e | |||
| 40c19f47c7 | |||
| 7f82b24b0c | |||
| f31a8da0e1 | |||
| 8ed158ab3c | |||
| 08af379a57 | |||
| 79dbdd3e5a | |||
| f7e8d5bdf7 | |||
| 52eba56e34 | |||
| 4c42276deb | |||
| b4eb772662 | |||
| 825730c3f9 | |||
| b7a2253b01 | |||
| ce1174d41b | |||
| f2da168a03 | |||
| fec4b29261 | |||
| 01b8dadd5f | |||
| 3ee97468f9 | |||
| c6d318bbc3 | |||
| 4ebb07a79a | |||
| 2580d0874f | |||
| 2168e693d8 | |||
| bc2a25346b | |||
| 03e969e08c | |||
| 37e3872782 | |||
| 71442e4160 | |||
| c2b69a769a | |||
| c0bc0e03c0 | |||
| f49bef1112 | |||
| 191b2978ec | |||
| 2771cdd053 | |||
| 2d576645d8 | |||
| be9884b468 | |||
| df280ce7a6 | |||
| c1182b3b90 | |||
| 425a3561ac | |||
| 12be354cc9 | |||
| 7e2917d447 | |||
| cb0de6a2d5 | |||
| 95318d1b36 | |||
| b92456c178 | |||
| bcb25b5d5e | |||
| 6a79204204 | |||
| d3776a8d3f | |||
| 325c6a0448 | |||
| 7c089eed80 | |||
| 8b40a46c1b | |||
| ef4061b96c | |||
|
|
506849b450 | ||
|
|
e73ce8c943 | ||
| bb4543bc65 | |||
|
|
f8d5850a89 | ||
| cce994d35c | |||
| 02cfb53147 | |||
| 0755df03cf | |||
| 51f0ad7497 | |||
| b4a49d20c8 | |||
| 44ccaf10d4 | |||
| 967c14b93a | |||
| 0e42fc657b | |||
| 2d7a91e368 | |||
| 072ab038fe | |||
| 6723c27d5e | |||
| f5fc700886 | |||
| 96a5ba0ceb | |||
| dfe5e72526 | |||
| d1cbebee84 | |||
| eac313feb8 | |||
| a71ed56cf2 | |||
| e3bc72e8d2 | |||
| 0fadfed7f6 | |||
| 031db78590 | |||
| 6fd32fb84b | |||
| 98e6464be6 | |||
| 52ce2afad2 | |||
| 28f89c9469 | |||
| 6f7de8da66 | |||
| 50caf2753d | |||
| 30c0caa04d | |||
| e53d8c14a9 | |||
| eca89358db | |||
| 1e58c71e67 | |||
| 43beaefc07 | |||
| f74faadace | |||
| 77aa622610 | |||
| 95aadd45ee | |||
| 89880016ea | |||
| beb91553ef | |||
| b2037c9575 | |||
| ce4cec5589 | |||
| e8a597ec6b | |||
| a663e2bd56 | |||
| 84bfd3cda2 | |||
| 3dfe96fa89 | |||
| 93cbcef93f | |||
| 5c758536a4 | |||
| bc5a9aa9f1 | |||
| b8ed77f6d5 | |||
| 6c25cd029f | |||
| 68d59eda69 | |||
| 2534d4c159 | |||
| 54252e768c | |||
| 7a2f263061 | |||
| 5ae6e02ce8 | |||
| fb9944ef14 | |||
| 190493179f | |||
| 4b1bc23622 | |||
| 2ad5b04a48 | |||
| afbbbcb4d1 | |||
| 820c3aec9d | |||
| eafbcd8389 | |||
| f0459b3f6e | |||
| ff93265890 | |||
| 320e282dc8 | |||
| d23c911c78 | |||
| 51df812a6c | |||
| a96aba57f7 | |||
| 36c71da4bb | |||
| b53fca9648 | |||
| 9fafb8bd2a | |||
| 41c7ba35a0 | |||
| 4cc5702da7 | |||
| 65f626f15e | |||
| 2abf6f67af | |||
| 72cde997ab | |||
| 8b27cb4690 | |||
| fb0cec0850 | |||
| a9a0bfdad0 | |||
| 00a752173d | |||
| 24caa9a4f4 | |||
| a1075e000a | |||
| c416902280 | |||
| a29d707183 | |||
| 69d08a85ac | |||
| 475ea08517 | |||
| b4c97c20d6 | |||
| f7da2c8185 | |||
| a1a630fc02 | |||
| 22ef117493 | |||
| 04b80d5ff8 | |||
| b764979c3b | |||
| a78c0491ef | |||
| 6082beb964 | |||
| 9d9a5fd9d2 | |||
| 4332f389a1 | |||
| a26b60e726 | |||
| 465d76cd08 | |||
| e0300148cf | |||
| 4442c6c625 | |||
| 289eb86d97 | |||
| 59724777e9 | |||
| 99e0b297b2 | |||
| 7036747042 | |||
| b9cd3e3f9f | |||
| c8e90b6887 | |||
| 56973b62f6 | |||
| 990a13e777 | |||
| cd37623746 | |||
| 193620e4e4 | |||
| 78d85e2440 | |||
| 9fa9689db9 | |||
| 81fc33183c | |||
| 2600011736 | |||
| e5c297eb7c | |||
| a3766aca6c | |||
| 857689dc22 | |||
| 2ed5917e96 | |||
| 84380bf333 | |||
| e7d517f264 | |||
| 40a9cc424e | |||
| b725bc2b5b | |||
| 50306c319e | |||
| c1e86c6897 | |||
| eb02142afc | |||
| 5f0526eef7 | |||
| cbdc55df8f | |||
| e0022b15c5 | |||
| 983edc53d1 | |||
| 2af1dcd24e | |||
| 2814a5f044 | |||
| 802fd87850 | |||
| 50a6a39632 | |||
| 49d0d3b35a | |||
| 8017f017f2 | |||
| 864798be7c | |||
| e20ef40e02 | |||
| 955f9021f7 | |||
| e006090be2 | |||
| 1c6bc56e08 | |||
| aa2da29b4c | |||
| 9e47d28660 | |||
| a5383c87c7 | |||
| 152a637e31 | |||
| 5d947000ca | |||
| bf4f378108 | |||
| 7f03228efa | |||
| 7e6fd1859e | |||
| 979c77c1b9 | |||
| 15c9603902 | |||
| 20dd8bcb3a | |||
| bec4b03a17 | |||
| 2686a162e7 | |||
| 7d9c982cf5 | |||
| d1c09ce74e | |||
| fe5f429039 | |||
| 8937571214 | |||
| f9591951cb | |||
| 956d2a25f2 | |||
| 365bec10a6 | |||
| c69c11b0fe | |||
| 40b8414f8a | |||
| 06cb735b68 | |||
| 985f32e06e | |||
| 36469b20b3 | |||
| 2173e4c6b8 | |||
| d7a35e651e | |||
| 4238c977f3 | |||
| 74e9a3204f | |||
| 7bf94ced84 | |||
| 1b3a40c22a | |||
| 28bef07e37 | |||
| 3b7cb57e7a | |||
| 3f069e6d22 | |||
| 12e7269591 | |||
| 0127cf0a6b | |||
| fd1522a2ca | |||
| c79ec065d3 | |||
| 19fe484049 | |||
| db3d831bc0 | |||
| d27e707044 | |||
| c142440068 | |||
| df6456f5d2 | |||
| 1578a2b8d1 | |||
| 4a43c152d5 | |||
| 0d1ee37272 | |||
| 5c0b97cd1e | |||
| 511e8d6074 | |||
| 826e4a7de2 | |||
| 1ac66b4ece | |||
| 8f58de9f4e | |||
| 611d997df9 | |||
| 3f66e3f1f1 | |||
| 6154fa5dcc | |||
| dd0914cd89 | |||
| b8000d9a64 | |||
| d94bd66c54 | |||
| 2ebd2018db | |||
| 04b41e16e1 | |||
| 7ad73f3c84 | |||
| 359698d54f | |||
| 26ea658f9c | |||
| a13be0530f | |||
| b018b11c57 | |||
| aac023351a | |||
| fd2fbea03f | |||
| f3bbdec77c | |||
| b54ace0272 | |||
| 55bc78d9cb | |||
| fabe027d54 | |||
| b4f4450573 | |||
| 990cc66600 | |||
| aa76147144 | |||
| ee46af08ca | |||
| c33de944ef | |||
| 79e5103b08 | |||
| 4797b213ee | |||
| c232042af5 | |||
| 252448f4a9 | |||
| ec4febe5e9 | |||
| 35ca0d90f7 | |||
| eae3d4689b | |||
| 67c9d4084c | |||
| fb52428219 | |||
| 23e620e168 | |||
| 324054b3db | |||
| d9dcbeef7b | |||
| 9b31d52d7e | |||
| 260d2d28ad | |||
| dd74bdda6a | |||
| 7486390da5 |
23
.env.example
23
.env.example
@@ -38,11 +38,24 @@ MAIL_ENCRYPTION=null
|
|||||||
MAIL_FROM_ADDRESS="hello@example.com"
|
MAIL_FROM_ADDRESS="hello@example.com"
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_PUBLIC_ACCESS_KEY_ID=
|
||||||
AWS_SECRET_ACCESS_KEY=
|
AWS_PUBLIC_SECRET_ACCESS_KEY=
|
||||||
AWS_DEFAULT_REGION=us-east-1
|
AWS_PUBLIC_DEFAULT_REGION="us-west-002"
|
||||||
AWS_BUCKET=
|
AWS_PUBLIC_BUCKET=
|
||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_PUBLIC_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
AWS_PUBLIC_ENDPOINT=
|
||||||
|
AWS_PUBLIC_URL=
|
||||||
|
|
||||||
|
AWS_PRIVATE_ACCESS_KEY_ID=
|
||||||
|
AWS_PRIVATE_SECRET_ACCESS_KEY=
|
||||||
|
AWS_PRIVATE_DEFAULT_REGION="us-west-002"
|
||||||
|
AWS_PRIVATE_BUCKET=
|
||||||
|
AWS_PRIVATE_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
AWS_PRIVATE_ENDPOINT=
|
||||||
|
AWS_PRIVATE_URL=
|
||||||
|
|
||||||
|
CLOUDFLARE_ZONE_ID=
|
||||||
|
CLOUDFLARE_API_KEY=
|
||||||
|
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
PUSHER_APP_KEY=
|
PUSHER_APP_KEY=
|
||||||
|
|||||||
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "composer"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -237,7 +237,7 @@ dist/
|
|||||||
### This Project ###
|
### This Project ###
|
||||||
/public/uploads
|
/public/uploads
|
||||||
/public/build
|
/public/build
|
||||||
/public/tinymce
|
# /public/tinymce
|
||||||
*.key
|
*.key
|
||||||
|
|
||||||
### Synk ###
|
### Synk ###
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -15,5 +15,6 @@
|
|||||||
"[php]": {
|
"[php]": {
|
||||||
// "editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
|
// "editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
|
||||||
"editor.defaultFormatter": "wongjn.php-sniffer"
|
"editor.defaultFormatter": "wongjn.php-sniffer"
|
||||||
}
|
},
|
||||||
|
"cSpell.words": ["TIMESTAMPDIFF"]
|
||||||
}
|
}
|
||||||
|
|||||||
83
app/Conductors/AnalyticsConductor.php
Normal file
83
app/Conductors/AnalyticsConductor.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\User;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||||
|
use Illuminate\Database\Eloquent\MissingAttributeException;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
|
class AnalyticsConductor extends Conductor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Model Class
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $class = \App\Models\Analytics::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sorting field
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sort = 'created_at';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default includes to include in a request.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $includes = ['duration'];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is visible.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow model to be visible.
|
||||||
|
*/
|
||||||
|
public static function viewable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is creatable.
|
||||||
|
*
|
||||||
|
* @return boolean Allow creating model.
|
||||||
|
*/
|
||||||
|
public static function creatable(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is updatable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow updating model.
|
||||||
|
*/
|
||||||
|
public static function updatable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is destroyable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow deleting model.
|
||||||
|
*/
|
||||||
|
public static function destroyable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/analytics') === true);
|
||||||
|
}
|
||||||
|
}
|
||||||
151
app/Conductors/ArticleConductor.php
Normal file
151
app/Conductors/ArticleConductor.php
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\User;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||||
|
use Illuminate\Database\Eloquent\MissingAttributeException;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
|
class ArticleConductor extends Conductor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Model Class
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $class = \App\Models\Article::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sorting field
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sort = '-publish_at';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The included fields
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $includes = ['attachments', 'user'];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a scope query on the collection before anything else.
|
||||||
|
*
|
||||||
|
* @param Builder $builder The builder in use.
|
||||||
|
*/
|
||||||
|
public function scope(Builder $builder): void
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||||
|
$builder
|
||||||
|
->where('publish_at', '<=', now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is visible.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow model to be visible.
|
||||||
|
*/
|
||||||
|
public static function viewable(Model $model): bool
|
||||||
|
{
|
||||||
|
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||||
|
$user = auth()->user();
|
||||||
|
if ($user === null || $user->hasPermission('admin/articles') === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is creatable.
|
||||||
|
*
|
||||||
|
* @return boolean Allow creating model.
|
||||||
|
*/
|
||||||
|
public static function creatable(): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is updatable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow updating model.
|
||||||
|
*/
|
||||||
|
public static function updatable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is destroyable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow deleting model.
|
||||||
|
*/
|
||||||
|
public static function destroyable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/articles') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the final model data
|
||||||
|
*
|
||||||
|
* @param array $data The model data to transform.
|
||||||
|
* @return array The transformed model.
|
||||||
|
*/
|
||||||
|
public function transformFinal(array $data): array
|
||||||
|
{
|
||||||
|
unset($data['user_id']);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include Attachments Field.
|
||||||
|
*
|
||||||
|
* @param Model $model Them model.
|
||||||
|
* @return mixed The model result.
|
||||||
|
*/
|
||||||
|
public function includeAttachments(Model $model)
|
||||||
|
{
|
||||||
|
return $model->attachments()->get()->map(function ($attachment) {
|
||||||
|
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include User Field.
|
||||||
|
*
|
||||||
|
* @param Model $model Them model.
|
||||||
|
* @return mixed The model result.
|
||||||
|
*/
|
||||||
|
public function includeUser(Model $model)
|
||||||
|
{
|
||||||
|
return UserConductor::includeModel(request(), 'user', User::find($model['user_id']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the Hero field.
|
||||||
|
*
|
||||||
|
* @param mixed $value The current value.
|
||||||
|
* @return array The new value.
|
||||||
|
*/
|
||||||
|
public function transformHero(mixed $value): array
|
||||||
|
{
|
||||||
|
return MediaConductor::includeModel(request(), 'hero', Media::find($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Conductors;
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||||
@@ -13,22 +14,27 @@ class EventConductor extends Conductor
|
|||||||
* The Model Class
|
* The Model Class
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $class = '\App\Models\Event';
|
protected $class = \App\Models\Event::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default sorting field
|
* The default sorting field
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $sort = 'start_at';
|
protected $sort = '-start_at';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The included fields
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $includes = ['attachments'];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)
|
public function scope(Builder $builder): void
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||||
@@ -44,7 +50,7 @@ class EventConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow model to be visible.
|
* @return boolean Allow model to be visible.
|
||||||
*/
|
*/
|
||||||
public static function viewable(Model $model)
|
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) {
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
@@ -61,7 +67,7 @@ class EventConductor extends Conductor
|
|||||||
*
|
*
|
||||||
* @return boolean Allow creating model.
|
* @return boolean Allow creating model.
|
||||||
*/
|
*/
|
||||||
public static function creatable()
|
public static function creatable(): bool
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||||
@@ -73,7 +79,7 @@ class EventConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow updating model.
|
* @return boolean Allow updating model.
|
||||||
*/
|
*/
|
||||||
public static function updatable(Model $model)
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||||
@@ -85,26 +91,37 @@ class EventConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow deleting model.
|
* @return boolean Allow deleting model.
|
||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model)
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the model
|
* Include Attachments Field.
|
||||||
*
|
*
|
||||||
* @param Model $model The model to transform.
|
* @param Model $model Them model.
|
||||||
* @return array The transformed model.
|
* @return mixed The model result.
|
||||||
* @throws InvalidCastException Cannot cast item to model.
|
|
||||||
*/
|
*/
|
||||||
public function transform(Model $model)
|
public function includeAttachments(Model $model)
|
||||||
{
|
{
|
||||||
$result = $model->toArray();
|
$user = auth()->user();
|
||||||
$result['attachments'] = $model->attachments()->get()->map(function ($attachment) {
|
|
||||||
return MediaConductor::model(request(), $attachment->media);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $result;
|
return $model->attachments()->get()->map(function ($attachment) use ($user) {
|
||||||
|
if ($attachment->private === false || ($user !== null && ($user->hasPermission('admin/events') === true || $attachment->users->contains($user) === true))) {
|
||||||
|
return MediaConductor::includeModel(request(), 'attachments', $attachment->media);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the Hero field.
|
||||||
|
*
|
||||||
|
* @param mixed $value The current value.
|
||||||
|
* @return array The new value.
|
||||||
|
*/
|
||||||
|
public function transformHero(mixed $value): array
|
||||||
|
{
|
||||||
|
return MediaConductor::includeModel(request(), 'hero', Media::find($value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Conductors;
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
class MediaConductor extends Conductor
|
class MediaConductor extends Conductor
|
||||||
{
|
{
|
||||||
@@ -11,7 +12,7 @@ class MediaConductor extends Conductor
|
|||||||
* The Model Class
|
* The Model Class
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $class = '\App\Models\Media';
|
protected $class = \App\Models\Media::class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default sorting field
|
* The default sorting field
|
||||||
@@ -19,6 +20,22 @@ class MediaConductor extends Conductor
|
|||||||
*/
|
*/
|
||||||
protected $sort = 'created_at';
|
protected $sort = 'created_at';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The included fields
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $includes = ['user'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default filters to use in a request.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $defaultFilters = [
|
||||||
|
'status' => 'OK'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of model fields visible to the current user.
|
* Return an array of model fields visible to the current user.
|
||||||
@@ -26,13 +43,13 @@ class MediaConductor extends Conductor
|
|||||||
* @param Model $model The model in question.
|
* @param Model $model The model in question.
|
||||||
* @return array The array of field names.
|
* @return array The array of field names.
|
||||||
*/
|
*/
|
||||||
public function fields(Model $model)
|
public function fields(Model $model): array
|
||||||
{
|
{
|
||||||
$fields = parent::fields($model);
|
$fields = parent::fields($model);
|
||||||
|
|
||||||
$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');
|
$fields = arrayRemoveItem($fields, ['permission', 'storage']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
@@ -42,15 +59,14 @@ 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)
|
public function scope(Builder $builder): void
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
$builder->whereNull('permission');
|
$builder->where('permission', '');
|
||||||
} else {
|
} else {
|
||||||
$builder->whereNull('permission')->orWhereIn('permission', $user->permissions);
|
$builder->where('permission', '')->orWhereIn('permission', $user->permissions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +76,9 @@ class MediaConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow model to be visible.
|
* @return boolean Allow model to be visible.
|
||||||
*/
|
*/
|
||||||
public static function viewable(Model $model)
|
public static function viewable(Model $model): bool
|
||||||
{
|
{
|
||||||
if ($model->permission !== null) {
|
if ($model->permission !== '') {
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission($model->permission) === false) {
|
if ($user === null || $user->hasPermission($model->permission) === false) {
|
||||||
return false;
|
return false;
|
||||||
@@ -77,7 +93,7 @@ class MediaConductor extends Conductor
|
|||||||
*
|
*
|
||||||
* @return boolean Allow creating model.
|
* @return boolean Allow creating model.
|
||||||
*/
|
*/
|
||||||
public static function creatable()
|
public static function creatable(): bool
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null);
|
return ($user !== null);
|
||||||
@@ -89,7 +105,7 @@ class MediaConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow updating model.
|
* @return boolean Allow updating model.
|
||||||
*/
|
*/
|
||||||
public static function updatable(Model $model)
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
$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));
|
||||||
@@ -101,9 +117,32 @@ class MediaConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow deleting model.
|
* @return boolean Allow deleting model.
|
||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model)
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
$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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include User Field.
|
||||||
|
*
|
||||||
|
* @param Model $model Them model.
|
||||||
|
* @return mixed The model result.
|
||||||
|
*/
|
||||||
|
public function includeUser(Model $model)
|
||||||
|
{
|
||||||
|
return UserConductor::includeModel(request(), 'user', User::find($model['user_id']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,109 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Conductors;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class PostConductor extends Conductor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The Model Class
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $class = '\App\Models\Post';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default sorting field
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $sort = '-publish_at';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a scope query on the collection before anything else.
|
|
||||||
*
|
|
||||||
* @param Builder $builder The builder in use.
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function scope(Builder $builder)
|
|
||||||
{
|
|
||||||
$user = auth()->user();
|
|
||||||
if ($user === null || $user->hasPermission('admin/posts') === false) {
|
|
||||||
$builder
|
|
||||||
->where('publish_at', '<=', now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return if the current model is visible.
|
|
||||||
*
|
|
||||||
* @param Model $model The model.
|
|
||||||
* @return boolean Allow model to be visible.
|
|
||||||
*/
|
|
||||||
public static function viewable(Model $model)
|
|
||||||
{
|
|
||||||
if (Carbon::parse($model->publish_at)->isFuture() === true) {
|
|
||||||
$user = auth()->user();
|
|
||||||
if ($user === null || $user->hasPermission('admin/posts') === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return if the current model is creatable.
|
|
||||||
*
|
|
||||||
* @return boolean Allow creating model.
|
|
||||||
*/
|
|
||||||
public static function creatable()
|
|
||||||
{
|
|
||||||
$user = auth()->user();
|
|
||||||
return ($user !== null && $user->hasPermission('admin/posts') === true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return if the current model is updatable.
|
|
||||||
*
|
|
||||||
* @param Model $model The model.
|
|
||||||
* @return boolean Allow updating model.
|
|
||||||
*/
|
|
||||||
public static function updatable(Model $model)
|
|
||||||
{
|
|
||||||
$user = auth()->user();
|
|
||||||
return ($user !== null && $user->hasPermission('admin/posts') === true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return if the current model is destroyable.
|
|
||||||
*
|
|
||||||
* @param Model $model The model.
|
|
||||||
* @return boolean Allow deleting model.
|
|
||||||
*/
|
|
||||||
public static function destroyable(Model $model)
|
|
||||||
{
|
|
||||||
$user = auth()->user();
|
|
||||||
return ($user !== null && $user->hasPermission('admin/posts') === true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform the model
|
|
||||||
*
|
|
||||||
* @param Model $model The model to transform.
|
|
||||||
* @return array The transformed model.
|
|
||||||
* @throws InvalidCastException Cannot cast item to model.
|
|
||||||
*/
|
|
||||||
public function transform(Model $model)
|
|
||||||
{
|
|
||||||
$result = $model->toArray();
|
|
||||||
$result['attachments'] = $model->attachments()->get()->map(function ($attachment) {
|
|
||||||
return MediaConductor::model(request(), $attachment->media);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
app/Conductors/ShortlinkConductor.php
Normal file
58
app/Conductors/ShortlinkConductor.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Conductors;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Foundation\Auth\User;
|
||||||
|
|
||||||
|
class ShortlinkConductor extends Conductor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Model Class
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $class = \App\Models\Shortlink::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default sorting field
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sort = 'created_at';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is creatable.
|
||||||
|
*
|
||||||
|
* @return boolean Allow creating model.
|
||||||
|
*/
|
||||||
|
public static function creatable(): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is updatable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow updating model.
|
||||||
|
*/
|
||||||
|
public static function updatable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if the current model is destroyable.
|
||||||
|
*
|
||||||
|
* @param Model $model The model.
|
||||||
|
* @return boolean Allow deleting model.
|
||||||
|
*/
|
||||||
|
public static function destroyable(Model $model): bool
|
||||||
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
return ($user !== null && $user->hasPermission('admin/shortlinks') === true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ class SubscriptionConductor extends Conductor
|
|||||||
* The Model Class
|
* The Model Class
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $class = '\App\Models\Subscription';
|
protected $class = \App\Models\Subscription::class;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +19,7 @@ class SubscriptionConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow updating model.
|
* @return boolean Allow updating model.
|
||||||
*/
|
*/
|
||||||
public static function updatable(Model $model)
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
$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));
|
||||||
@@ -31,7 +31,7 @@ class SubscriptionConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow deleting model.
|
* @return boolean Allow deleting model.
|
||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model)
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
$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));
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class UserConductor extends Conductor
|
|||||||
* The Model Class
|
* The Model Class
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $class = '\App\Models\User';
|
protected $class = \App\Models\User::class;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,11 +19,11 @@ class UserConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return string[] The fields visible.
|
* @return string[] The fields visible.
|
||||||
*/
|
*/
|
||||||
public function fields(Model $model)
|
public function fields(Model $model): array
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user === null || $user->hasPermission('admin/users') === false) {
|
if ($user === null || $user->hasPermission('admin/users') === false) {
|
||||||
return ['id', 'username'];
|
return ['id', 'display_name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::fields($model);
|
return parent::fields($model);
|
||||||
@@ -35,14 +35,16 @@ class UserConductor extends Conductor
|
|||||||
* @param Model $model The model to transform.
|
* @param Model $model The model to transform.
|
||||||
* @return array The transformed model.
|
* @return array The transformed model.
|
||||||
*/
|
*/
|
||||||
public function transform(Model $model)
|
public function transform(Model $model): array
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
$data = $model->toArray();
|
$data = $model->toArray();
|
||||||
|
|
||||||
if ($user === null || ($user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0)) {
|
if ($user === null || ($user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0)) {
|
||||||
$fields = ['id', 'username'];
|
$fields = ['id', 'display_name'];
|
||||||
$data = arrayLimitKeys($data, $fields);
|
$data = arrayLimitKeys($data, $fields);
|
||||||
|
} else {
|
||||||
|
$data['permissions'] = $user->permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
@@ -54,7 +56,7 @@ class UserConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow updating model.
|
* @return boolean Allow updating model.
|
||||||
*/
|
*/
|
||||||
public static function updatable(Model $model)
|
public static function updatable(Model $model): bool
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
@@ -70,7 +72,7 @@ class UserConductor extends Conductor
|
|||||||
* @param Model $model The model.
|
* @param Model $model The model.
|
||||||
* @return boolean Allow deleting model.
|
* @return boolean Allow deleting model.
|
||||||
*/
|
*/
|
||||||
public static function destroyable(Model $model)
|
public static function destroyable(Model $model): bool
|
||||||
{
|
{
|
||||||
$user = auth()->user();
|
$user = auth()->user();
|
||||||
return ($user !== null && $user->hasPermission('admin/users') === true);
|
return ($user !== null && $user->hasPermission('admin/users') === true);
|
||||||
|
|||||||
63
app/Console/Commands/MediaMigrate.php
Normal file
63
app/Console/Commands/MediaMigrate.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/Console/Commands/MediaRebuild.php
Normal file
66
app/Console/Commands/MediaRebuild.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,19 +11,16 @@ 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)
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
// $schedule->command('inspire')->hourly();
|
// $schedule->command('inspire')->hourly();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register the commands for the application.
|
* Register the commands for the application.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected function commands()
|
protected function commands(): void
|
||||||
{
|
{
|
||||||
$this->load(__DIR__ . '/Commands');
|
$this->load(__DIR__ . '/Commands');
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class Enum
|
|||||||
*/
|
*/
|
||||||
public static function getMessage(int $messageIndex, string $defaultMessage = 'Unknown'): string
|
public static function getMessage(int $messageIndex, string $defaultMessage = 'Unknown'): string
|
||||||
{
|
{
|
||||||
if(array_key_exists($messageIndex, self::$messages) === true) {
|
if (array_key_exists($messageIndex, self::$messages) === true) {
|
||||||
return self::$messages[$messageIndex];
|
return self::$messages[$messageIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,24 +12,6 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
|
|||||||
|
|
||||||
class Handler extends ExceptionHandler
|
class Handler extends ExceptionHandler
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* A list of exception types with their corresponding custom log levels.
|
|
||||||
*
|
|
||||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
|
||||||
*/
|
|
||||||
protected $levels = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of the exception types that are not reported.
|
|
||||||
*
|
|
||||||
* @var array<int, class-string<\Throwable>>
|
|
||||||
*/
|
|
||||||
protected $dontReport = [
|
|
||||||
//
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||||
*
|
*
|
||||||
@@ -44,10 +26,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()
|
public function register(): void
|
||||||
{
|
{
|
||||||
// $this->renderable(function (HttpException $e, $request) {
|
// $this->renderable(function (HttpException $e, $request) {
|
||||||
// if ($request->is('api/*')) {
|
// if ($request->is('api/*')) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class SubscriptionFilter extends FilterAbstract
|
|||||||
*
|
*
|
||||||
* @var mixed
|
* @var mixed
|
||||||
*/
|
*/
|
||||||
protected $class = '\App\Models\Subscription';
|
protected $class = \App\Models\Subscription::class;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
19
app/Helpers/Temp.php
Normal file
19
app/Helpers/Temp.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/* Temp File Helper Functions */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a temporary file path.
|
||||||
|
*
|
||||||
|
* @return str The filtered array.
|
||||||
|
*/
|
||||||
|
function generateTempFilePath(): string
|
||||||
|
{
|
||||||
|
$temporaryDir = storage_path('app/tmp');
|
||||||
|
if (is_dir($temporaryDir) === false) {
|
||||||
|
mkdir($temporaryDir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $temporaryDir . DIRECTORY_SEPARATOR . uniqid('upload_', true);
|
||||||
|
}
|
||||||
163
app/Http/Controllers/Api/AnalyticsController.php
Normal file
163
app/Http/Controllers/Api/AnalyticsController.php
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Conductors\AnalyticsConductor;
|
||||||
|
use App\Conductors\Conductor;
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Http\Requests\AnalyticsRequest;
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\Analytics;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Carbon\Exceptions\InvalidFormatException;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||||
|
use Illuminate\Database\Eloquent\MassAssignmentException;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class AnalyticsController extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* AnalyticsController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth:sanctum')
|
||||||
|
->only([
|
||||||
|
'index',
|
||||||
|
'update',
|
||||||
|
'delete'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
||||||
|
$searchFields = ['attribute', 'type', 'useragent', 'ip'];
|
||||||
|
|
||||||
|
$queryRequest = new Request();
|
||||||
|
$queryRequest->merge($request->only($searchFields));
|
||||||
|
foreach ($searchFields as $field) {
|
||||||
|
unset($request[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = Analytics::query()
|
||||||
|
->selectRaw('session,
|
||||||
|
MIN(created_at) as created_at,
|
||||||
|
TIMESTAMPDIFF(MINUTE, MIN(created_at), MAX(created_at)) as duration');
|
||||||
|
$query = Conductor::filterQuery($query, $queryRequest);
|
||||||
|
|
||||||
|
list($collection, $total) = AnalyticsConductor::collection($request, $query
|
||||||
|
->groupBy('session')
|
||||||
|
->get());
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
$collection,
|
||||||
|
['isCollection' => true,
|
||||||
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
|
* @param \App\Models\Analytics $analytics The analyics model.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function show(Request $request, int $session)
|
||||||
|
{
|
||||||
|
if ($request->user() !== null && $request->user()?->hasPermission('admin/analytics') === true) {
|
||||||
|
list($collection, $total) = AnalyticsConductor::collection($request, Analytics::query()
|
||||||
|
->where('session', $session)
|
||||||
|
->get());
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
$collection,
|
||||||
|
['isCollection' => true,
|
||||||
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\AnalyticsRequest $request The user request.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function store(AnalyticsRequest $request)
|
||||||
|
{
|
||||||
|
if (AnalyticsConductor::creatable() === true) {
|
||||||
|
$analytics = null;
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'type' => $request->input('type'),
|
||||||
|
'attribute' => $request->input('attribute', ''),
|
||||||
|
'useragent' => $request->userAgent(),
|
||||||
|
'ip' => $request->ip()
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($user !== null && $user->hasPermission('admin/analytics') === true && $request->has('session') === true) {
|
||||||
|
$data['session'] = $request->input('session');
|
||||||
|
$analytics = Analytics::create($data);
|
||||||
|
} else {
|
||||||
|
$analytics = Analytics::createWithSession($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
AnalyticsConductor::model($request, $analytics),
|
||||||
|
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}//end if
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\AnalyticsRequest $request The analytics update request.
|
||||||
|
* @param \App\Models\Analytics $analytics The specified analytics.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function update(AnalyticsRequest $request, Analytics $analytics)
|
||||||
|
{
|
||||||
|
if (AnalyticsConductor::updatable($analytics) === true) {
|
||||||
|
$analytics->update($request->all());
|
||||||
|
return $this->respondAsResource(AnalyticsConductor::model($request, $analytics));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Analytics $analytics The specified analytics.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function destroy(Analytics $analytics)
|
||||||
|
{
|
||||||
|
if (AnalyticsConductor::destroyable($analytics) === true) {
|
||||||
|
$analytics->delete();
|
||||||
|
return $this->respondNoContent();
|
||||||
|
} else {
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@@ -23,9 +24,8 @@ 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 \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondJson(array $data, int $respondCode = HttpResponseCodes::HTTP_OK, array $headers = [])
|
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 +34,8 @@ class ApiController extends Controller
|
|||||||
* Return forbidden message
|
* Return forbidden message
|
||||||
*
|
*
|
||||||
* @param string $message Response message.
|
* @param string $message Response message.
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondForbidden(string $message = 'You do not have permission to access the resource.')
|
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);
|
||||||
}
|
}
|
||||||
@@ -45,9 +44,8 @@ class ApiController extends Controller
|
|||||||
* Return forbidden message
|
* Return forbidden message
|
||||||
*
|
*
|
||||||
* @param string $message Response message.
|
* @param string $message Response message.
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondNotFound(string $message = 'The resource was not found.')
|
public function respondNotFound(string $message = 'The resource was not found.'): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json(['message' => $message], HttpResponseCodes::HTTP_NOT_FOUND);
|
return response()->json(['message' => $message], HttpResponseCodes::HTTP_NOT_FOUND);
|
||||||
}
|
}
|
||||||
@@ -56,39 +54,43 @@ class ApiController extends Controller
|
|||||||
* Return too large message
|
* Return too large message
|
||||||
*
|
*
|
||||||
* @param string $message Response message.
|
* @param string $message Response message.
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondTooLarge(string $message = 'The request entity is too large.')
|
public function respondTooLarge(string $message = 'The request entity is too large.'): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json(['message' => $message], HttpResponseCodes::HTTP_REQUEST_ENTITY_TOO_LARGE);
|
return response()->json(['message' => $message], HttpResponseCodes::HTTP_REQUEST_ENTITY_TOO_LARGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return no content
|
* Return no content
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondNoContent()
|
public function respondNoContent(): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json([], HttpResponseCodes::HTTP_NO_CONTENT);
|
return response()->json([], HttpResponseCodes::HTTP_NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return created
|
* Return created
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondCreated()
|
public function respondCreated(): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json([], HttpResponseCodes::HTTP_CREATED);
|
return response()->json([], HttpResponseCodes::HTTP_CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return accepted
|
||||||
|
*/
|
||||||
|
public function respondAccepted(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([], HttpResponseCodes::HTTP_ACCEPTED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondError(string $message, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY)
|
public function respondError(string $message, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY): JsonResponse
|
||||||
{
|
{
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => $message
|
'message' => $message
|
||||||
@@ -100,9 +102,8 @@ 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 \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
public function respondWithErrors(array $errors, int $responseCode = HttpResponseCodes::HTTP_UNPROCESSABLE_ENTITY)
|
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]];
|
||||||
@@ -121,28 +122,35 @@ 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.
|
||||||
* @return \Illuminate\Http\JsonResponse
|
|
||||||
*/
|
*/
|
||||||
protected function respondAsResource(
|
protected function respondAsResource(
|
||||||
mixed $data,
|
mixed $data,
|
||||||
array $options = [],
|
array $options = [],
|
||||||
) {
|
$validationFn = null
|
||||||
|
): JsonResponse {
|
||||||
$isCollection = $options['isCollection'] ?? false;
|
$isCollection = $options['isCollection'] ?? false;
|
||||||
$appendData = $options['appendData'] ?? null;
|
$appendData = $options['appendData'] ?? null;
|
||||||
$resourceName = $options['resourceName'] ?? null;
|
$resourceName = $options['resourceName'] ?? null;
|
||||||
$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)) {
|
||||||
return $this->respondNotFound();
|
$validationData = [];
|
||||||
|
if (array_key_exists('appendData', $options) === true) {
|
||||||
|
$validationData = $options['appendData'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($validationFn === null || $validationFn($validationData) === true) {
|
||||||
|
return $this->respondNotFound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_null($resourceName) === true || empty($resourceName) === true) {
|
if (is_null($resourceName) === true || empty($resourceName) === true) {
|
||||||
$resourceName = $this->resourceName;
|
$resourceName = $this->resourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_null($resourceName) === true || empty($resourceName) === true) {
|
if (is_null($resourceName) === true || 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'));
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Conductors\MediaConductor;
|
use App\Conductors\MediaConductor;
|
||||||
use App\Conductors\PostConductor;
|
use App\Conductors\ArticleConductor;
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\PostRequest;
|
use App\Http\Requests\ArticleRequest;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use App\Models\Post;
|
use App\Models\Article;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Carbon\Exceptions\InvalidFormatException;
|
use Carbon\Exceptions\InvalidFormatException;
|
||||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
@@ -15,7 +15,7 @@ use Illuminate\Database\Eloquent\InvalidCastException;
|
|||||||
use Illuminate\Database\Eloquent\MassAssignmentException;
|
use Illuminate\Database\Eloquent\MassAssignmentException;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class PostController extends ApiController
|
class ArticleController extends ApiController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* ApplicationController constructor.
|
* ApplicationController constructor.
|
||||||
@@ -38,12 +38,13 @@ class PostController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
list($collection, $total) = PostConductor::request($request);
|
list($collection, $total) = ArticleConductor::request($request);
|
||||||
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$collection,
|
||||||
['isCollection' => true,
|
['isCollection' => true,
|
||||||
'appendData' => ['total' => $total]]
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,13 +52,13 @@ class PostController 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\Post $post The post model.
|
* @param \App\Models\Article $article The article model.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function show(Request $request, Post $post)
|
public function show(Request $request, Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::viewable($post) === true) {
|
if (ArticleConductor::viewable($article) === true) {
|
||||||
return $this->respondAsResource(PostConductor::model($request, $post));
|
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
@@ -66,15 +67,15 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* Store a newly created resource in storage.
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\PostRequest $request The user request.
|
* @param \App\Http\Requests\ArticleRequest $request The user request.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function store(PostRequest $request)
|
public function store(ArticleRequest $request)
|
||||||
{
|
{
|
||||||
if (PostConductor::creatable() === true) {
|
if (ArticleConductor::creatable() === true) {
|
||||||
$post = Post::create($request->all());
|
$article = Article::create($request->all());
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
PostConductor::model($request, $post),
|
ArticleConductor::model($request, $article),
|
||||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -85,15 +86,15 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\PostRequest $request The post update request.
|
* @param \App\Http\Requests\ArticleRequest $request The article update request.
|
||||||
* @param \App\Models\Post $post The specified post.
|
* @param \App\Models\Article $article The specified article.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(PostRequest $request, Post $post)
|
public function update(ArticleRequest $request, Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::updatable($post) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
$post->update($request->all());
|
$article->update($request->all());
|
||||||
return $this->respondAsResource(PostConductor::model($request, $post));
|
return $this->respondAsResource(ArticleConductor::model($request, $article));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
@@ -102,13 +103,13 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Remove the specified resource from storage.
|
* Remove the specified resource from storage.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Post $post The specified post.
|
* @param \App\Models\Article $article The specified article.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy(Post $post)
|
public function destroy(Article $article)
|
||||||
{
|
{
|
||||||
if (PostConductor::destroyable($post) === true) {
|
if (ArticleConductor::destroyable($article) === true) {
|
||||||
$post->delete();
|
$article->delete();
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
} else {
|
} else {
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
@@ -119,16 +120,16 @@ class PostController extends ApiController
|
|||||||
* Get a list of attachments related to this model.
|
* Get a list of attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Article $article The article model.
|
||||||
* @return JsonResponse Returns the post attachments.
|
* @return JsonResponse Returns the article attachments.
|
||||||
* @throws InvalidFormatException
|
* @throws InvalidFormatException
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws InvalidCastException
|
* @throws InvalidCastException
|
||||||
*/
|
*/
|
||||||
public function getAttachments(Request $request, Post $post)
|
public function getAttachments(Request $request, Article $article): JsonResponse
|
||||||
{
|
{
|
||||||
if (PostConductor::viewable($post) === true) {
|
if (ArticleConductor::viewable($article) === true) {
|
||||||
$medium = $post->attachments->map(function ($attachment) {
|
$medium = $article->attachments->map(function ($attachment) {
|
||||||
return $attachment->media;
|
return $attachment->media;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,16 +143,16 @@ class PostController extends ApiController
|
|||||||
* Store an attachment related to this model.
|
* Store an attachment related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Article $article The article model.
|
||||||
* @return JsonResponse The response.
|
* @return JsonResponse The response.
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws MassAssignmentException
|
* @throws MassAssignmentException
|
||||||
*/
|
*/
|
||||||
public function storeAttachment(Request $request, Post $post)
|
public function storeAttachment(Request $request, Article $article): JsonResponse
|
||||||
{
|
{
|
||||||
if (PostConductor::updatable($post) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
if($request->has("medium") && Media::find($request->medium)) {
|
if ($request->has("medium") && Media::find($request->medium)) {
|
||||||
$post->attachments()->create(['media_id' => $request->medium]);
|
$article->attachments()->create(['media_id' => $request->medium]);
|
||||||
return $this->respondCreated();
|
return $this->respondCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,21 +166,20 @@ class PostController extends ApiController
|
|||||||
* Update/replace attachments related to this model.
|
* Update/replace attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The related model.
|
* @param Article $article The related model.
|
||||||
* @return JsonResponse
|
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
* @throws MassAssignmentException
|
* @throws MassAssignmentException
|
||||||
*/
|
*/
|
||||||
public function updateAttachments(Request $request, Post $post)
|
public function updateAttachments(Request $request, Article $article): JsonResponse
|
||||||
{
|
{
|
||||||
if (PostConductor::updatable($post) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
$mediaIds = $request->attachments;
|
$mediaIds = $request->attachments;
|
||||||
if(is_array($mediaIds) === false) {
|
if (is_array($mediaIds) === false) {
|
||||||
$mediaIds = explode(',', $request->attachments);
|
$mediaIds = explode(',', $request->attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
||||||
$attachments = $post->attachments;
|
$attachments = $article->attachments;
|
||||||
|
|
||||||
// Delete attachments that are not in $mediaIds
|
// Delete attachments that are not in $mediaIds
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
@@ -188,7 +188,7 @@ class PostController extends ApiController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new attachments for media IDs that are not already in $post->attachments()
|
// Create new attachments for media IDs that are not already in $article->attachments()
|
||||||
foreach ($mediaIds as $mediaId) {
|
foreach ($mediaIds as $mediaId) {
|
||||||
$found = false;
|
$found = false;
|
||||||
|
|
||||||
@@ -200,12 +200,12 @@ class PostController extends ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
$post->attachments()->create(['media_id' => $mediaId]);
|
$article->attachments()->create(['media_id' => $mediaId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
}
|
}//end if
|
||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
@@ -213,15 +213,14 @@ class PostController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Delete a specific related attachment.
|
* Delete a specific related attachment.
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The model.
|
* @param Article $article The model.
|
||||||
* @param Media $medium The attachment medium.
|
* @param Media $medium The attachment medium.
|
||||||
* @return JsonResponse
|
|
||||||
* @throws BindingResolutionException
|
* @throws BindingResolutionException
|
||||||
*/
|
*/
|
||||||
public function deleteAttachment(Request $request, Post $post, Media $medium)
|
public function deleteAttachment(Request $request, Article $article, Media $medium): JsonResponse
|
||||||
{
|
{
|
||||||
if (PostConductor::updatable($post) === true) {
|
if (ArticleConductor::updatable($article) === true) {
|
||||||
$attachments = $post->attachments;
|
$attachments = $article->attachments;
|
||||||
$deleted = false;
|
$deleted = false;
|
||||||
|
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
@@ -29,7 +29,7 @@ class AttachmentController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Store a newly created resource in storage.
|
* Store a newly created resource in storage.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
@@ -40,7 +40,7 @@ class AttachmentController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Attachment $attachment
|
* @param \App\Models\Attachment $attachment
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function show(Attachment $attachment)
|
public function show(Attachment $attachment)
|
||||||
@@ -51,7 +51,7 @@ class AttachmentController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Show the form for editing the specified resource.
|
* Show the form for editing the specified resource.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Attachment $attachment
|
* @param \App\Models\Attachment $attachment
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function edit(Attachment $attachment)
|
public function edit(Attachment $attachment)
|
||||||
@@ -62,11 +62,11 @@ class AttachmentController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @param \App\Models\Attachment $attachment
|
* @param \App\Models\Attachment $attachment
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(Request $request, Attachment $attachment)
|
public function update(Request $request, Attachment $attachment)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@@ -74,10 +74,10 @@ class AttachmentController extends ApiController
|
|||||||
/**
|
/**
|
||||||
* Remove the specified resource from storage.
|
* Remove the specified resource from storage.
|
||||||
*
|
*
|
||||||
* @param \App\Models\Attachment $attachment
|
* @param \App\Models\Attachment $attachment
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy(Attachment $attachment)
|
public function destroy(Attachment $attachment)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ 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)
|
public function me(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$user = $request->user()->makeVisible(['permissions']);
|
$user = $request->user()->makeVisible(['permissions']);
|
||||||
return $this->respondAsResource($user);
|
return $this->respondAsResource($user);
|
||||||
@@ -47,18 +46,18 @@ class AuthController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function login(AuthLoginRequest $request)
|
public function login(AuthLoginRequest $request)
|
||||||
{
|
{
|
||||||
$user = User::where('username', '=', $request->input('username'))->first();
|
$user = User::where('email', '=', $request->input('email'))->first();
|
||||||
|
|
||||||
if ($user !== null && 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([
|
||||||
'username' => 'Email address has not been verified.'
|
'email' => 'Email address has not been verified.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->disabled === true) {
|
if ($user->disabled === true) {
|
||||||
return $this->respondWithErrors([
|
return $this->respondWithErrors([
|
||||||
'username' => 'Account has been disabled.'
|
'email' => 'Account has been disabled.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +77,8 @@ class AuthController extends ApiController
|
|||||||
}//end if
|
}//end if
|
||||||
|
|
||||||
return $this->respondWithErrors([
|
return $this->respondWithErrors([
|
||||||
'username' => 'Invalid username or password',
|
'email' => 'Invalid email or password',
|
||||||
'password' => 'Invalid username or password',
|
'password' => 'Invalid email or password',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +86,8 @@ 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)
|
public function logout(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ use App\Enum\HttpResponseCodes;
|
|||||||
use App\Models\Event;
|
use App\Models\Event;
|
||||||
use App\Conductors\EventConductor;
|
use App\Conductors\EventConductor;
|
||||||
use App\Conductors\MediaConductor;
|
use App\Conductors\MediaConductor;
|
||||||
|
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 Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class EventController extends ApiController
|
class EventController extends ApiController
|
||||||
@@ -18,7 +20,7 @@ class EventController extends ApiController
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth:sanctum')
|
$this->middleware('auth:sanctum')
|
||||||
->only(['store','update','destroy']);
|
->only(['store','update','destroy', 'userAdd', 'userUpdate', 'userDelete']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,13 +113,10 @@ class EventController extends ApiController
|
|||||||
* Get a list of attachments related to this model.
|
* Get a list of attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Event $event The event model.
|
||||||
* @return JsonResponse Returns the post attachments.
|
* @return JsonResponse Returns the event attachments.
|
||||||
* @throws InvalidFormatException
|
|
||||||
* @throws BindingResolutionException
|
|
||||||
* @throws InvalidCastException
|
|
||||||
*/
|
*/
|
||||||
public function getAttachments(Request $request, Event $event)
|
public function getAttachments(Request $request, Event $event): JsonResponse
|
||||||
{
|
{
|
||||||
if (EventConductor::viewable($event) === true) {
|
if (EventConductor::viewable($event) === true) {
|
||||||
$medium = $event->attachments->map(function ($attachment) {
|
$medium = $event->attachments->map(function ($attachment) {
|
||||||
@@ -134,15 +133,13 @@ class EventController extends ApiController
|
|||||||
* Store an attachment related to this model.
|
* Store an attachment related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The post model.
|
* @param Event $event The event model.
|
||||||
* @return JsonResponse The response.
|
* @return JsonResponse The response.
|
||||||
* @throws BindingResolutionException
|
|
||||||
* @throws MassAssignmentException
|
|
||||||
*/
|
*/
|
||||||
public function storeAttachment(Request $request, Event $event)
|
public function storeAttachment(Request $request, Event $event): JsonResponse
|
||||||
{
|
{
|
||||||
if (EventConductor::updatable($event) === true) {
|
if (EventConductor::updatable($event) === true) {
|
||||||
if ($request->has("medium") && Media::find($request->medium)) {
|
if ($request->has("medium") === true && Media::find($request->medium) !== null) {
|
||||||
$event->attachments()->create(['media_id' => $request->medium]);
|
$event->attachments()->create(['media_id' => $request->medium]);
|
||||||
return $this->respondCreated();
|
return $this->respondCreated();
|
||||||
}
|
}
|
||||||
@@ -157,12 +154,9 @@ class EventController extends ApiController
|
|||||||
* Update/replace attachments related to this model.
|
* Update/replace attachments related to this model.
|
||||||
*
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The related model.
|
* @param Event $event The related model.
|
||||||
* @return JsonResponse
|
|
||||||
* @throws BindingResolutionException
|
|
||||||
* @throws MassAssignmentException
|
|
||||||
*/
|
*/
|
||||||
public function updateAttachments(Request $request, Event $event)
|
public function updateAttachments(Request $request, Event $event): JsonResponse
|
||||||
{
|
{
|
||||||
if (EventConductor::updatable($event) === true) {
|
if (EventConductor::updatable($event) === true) {
|
||||||
$mediaIds = $request->attachments;
|
$mediaIds = $request->attachments;
|
||||||
@@ -175,23 +169,23 @@ class EventController extends ApiController
|
|||||||
|
|
||||||
// Delete attachments that are not in $mediaIds
|
// Delete attachments that are not in $mediaIds
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
if (!in_array($attachment->media_id, $mediaIds)) {
|
if (in_array($attachment->media_id, $mediaIds) === false) {
|
||||||
$attachment->delete();
|
$attachment->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new attachments for media IDs that are not already in $post->attachments()
|
// Create new attachments for media IDs that are not already in $article->attachments()
|
||||||
foreach ($mediaIds as $mediaId) {
|
foreach ($mediaIds as $mediaId) {
|
||||||
$found = false;
|
$found = false;
|
||||||
|
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
if ($attachment->media_id == $mediaId) {
|
if ($attachment->media_id === $mediaId) {
|
||||||
$found = true;
|
$found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$found) {
|
if ($found === false) {
|
||||||
$event->attachments()->create(['media_id' => $mediaId]);
|
$event->attachments()->create(['media_id' => $mediaId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,13 +198,12 @@ class EventController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a specific related attachment.
|
* Delete a specific related attachment.
|
||||||
|
*
|
||||||
* @param Request $request The user request.
|
* @param Request $request The user request.
|
||||||
* @param Post $post The model.
|
* @param Event $event The model.
|
||||||
* @param Media $medium The attachment medium.
|
* @param Media $medium The attachment medium.
|
||||||
* @return JsonResponse
|
|
||||||
* @throws BindingResolutionException
|
|
||||||
*/
|
*/
|
||||||
public function deleteAttachment(Request $request, Event $event, Media $medium)
|
public function deleteAttachment(Request $request, Event $event, Media $medium): JsonResponse
|
||||||
{
|
{
|
||||||
if (EventConductor::updatable($event) === true) {
|
if (EventConductor::updatable($event) === true) {
|
||||||
$attachments = $event->attachments;
|
$attachments = $event->attachments;
|
||||||
@@ -224,7 +217,7 @@ class EventController extends ApiController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($deleted) {
|
if ($deleted === true) {
|
||||||
// Attachment was deleted successfully
|
// Attachment was deleted successfully
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
} else {
|
} else {
|
||||||
@@ -235,4 +228,82 @@ class EventController extends ApiController
|
|||||||
|
|
||||||
return $this->respondForbidden();
|
return $this->respondForbidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function userList(Request $request, Event $event)
|
||||||
|
{
|
||||||
|
$authUser = $request->user();
|
||||||
|
$eventUsers = $event->users;
|
||||||
|
|
||||||
|
if ($authUser !== null) {
|
||||||
|
$isAdmin = $authUser->hasPermission('admin/events');
|
||||||
|
$isEventUser = $eventUsers->contains($authUser->id);
|
||||||
|
|
||||||
|
if ($isAdmin === true || $isEventUser === true) {
|
||||||
|
if ($isAdmin === false) {
|
||||||
|
$eventUsers = $eventUsers->filter(function ($user) use ($authUser) {
|
||||||
|
return $user->id === $authUser->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondAsResource(UserConductor::collection($request, $eventUsers), ['isCollection' => true, 'resourceName' => 'users']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondNotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userAdd(Request $request, Event $event)
|
||||||
|
{
|
||||||
|
$authUser = $request->user();
|
||||||
|
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
||||||
|
if ($request->has("users") === true) {
|
||||||
|
$eventUsers = $event->users()->pluck('user_id')->toArray(); // Get the current users in the event
|
||||||
|
$requestedUsers = $request->input("users"); // Get the requested users
|
||||||
|
|
||||||
|
$usersToAdd = array_diff($requestedUsers, $eventUsers); // Users to add
|
||||||
|
$usersToRemove = array_diff($eventUsers, $requestedUsers); // Users to remove
|
||||||
|
|
||||||
|
// Add missing users
|
||||||
|
foreach ($usersToAdd as $userToAdd) {
|
||||||
|
if (User::find($userToAdd) !== null) {
|
||||||
|
$event->users()->attach($userToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove extra users
|
||||||
|
foreach ($usersToRemove as $userToRemove) {
|
||||||
|
$event->users()->detach($userToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondNoContent();
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return $this->respondWithErrors(['users' => 'The user list was not found']);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userUpdate(Request $request, Event $event)
|
||||||
|
{
|
||||||
|
// only admin/events permitted
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userDelete(Request $request, Event $event, User $user)
|
||||||
|
{
|
||||||
|
$authUser = $request->user();
|
||||||
|
if ($authUser !== null && $authUser->hasPermission('admin/events') === true) {
|
||||||
|
$eventUsers = $event->users;
|
||||||
|
if ($eventUsers->find($user->id) !== null) {
|
||||||
|
$eventUsers->detach($user->id);
|
||||||
|
return $this->respondNoContent();
|
||||||
|
} else {
|
||||||
|
return $this->respondNotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Http\Requests\MediaRequest;
|
|||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Laravel\Sanctum\PersonalAccessToken;
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
class MediaController extends ApiController
|
class MediaController extends ApiController
|
||||||
@@ -34,7 +35,11 @@ class MediaController extends ApiController
|
|||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$collection,
|
||||||
['isCollection' => true,
|
['isCollection' => true,
|
||||||
'appendData' => ['total' => $total]]
|
'appendData' => ['total' => $total]
|
||||||
|
],
|
||||||
|
function ($options) {
|
||||||
|
return $options['total'] === 0;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,31 +85,23 @@ class MediaController extends ApiController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file->getSize() > Media::maxUploadSize()) {
|
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||||
return $this->respondTooLarge();
|
return $this->respondTooLarge();
|
||||||
}
|
}
|
||||||
|
|
||||||
$title = $file->getClientOriginalName();
|
try {
|
||||||
$mime = $file->getMimeType();
|
$media = Media::createFromUploadedFile($request, $file);
|
||||||
$fileInfo = Media::store($file, empty($request->input('permission')));
|
} catch (\Exception $e) {
|
||||||
if ($fileInfo === null) {
|
if ($e->getCode() === Media::FILE_SIZE_EXCEEDED_ERROR) {
|
||||||
return $this->respondWithErrors(
|
return $this->respondTooLarge();
|
||||||
['file' => 'The file could not be stored on the server'],
|
} else {
|
||||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
return $this->respondWithErrors(['file' => $e->getMessage()]);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->merge([
|
|
||||||
'title' => $title,
|
|
||||||
'mime' => $mime,
|
|
||||||
'name' => $fileInfo['name'],
|
|
||||||
'size' => filesize($fileInfo['path'])
|
|
||||||
]);
|
|
||||||
|
|
||||||
$media = $request->user()->media()->create($request->all());
|
|
||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
MediaConductor::model($request, $media),
|
MediaConductor::model($request, $media),
|
||||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||||
);
|
);
|
||||||
}//end if
|
}//end if
|
||||||
|
|
||||||
@@ -123,32 +120,36 @@ class MediaController extends ApiController
|
|||||||
if (MediaConductor::updatable($medium) === true) {
|
if (MediaConductor::updatable($medium) === true) {
|
||||||
$file = $request->file('file');
|
$file = $request->file('file');
|
||||||
if ($file !== null) {
|
if ($file !== null) {
|
||||||
if ($file->getSize() > Media::maxUploadSize()) {
|
if ($file->isValid() !== true) {
|
||||||
return $this->respondTooLarge();
|
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.']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$oldPath = $medium->path();
|
if ($file->getSize() > Media::getMaxUploadSize()) {
|
||||||
$fileInfo = Media::store($file, empty($request->input('permission')));
|
return $this->respondTooLarge();
|
||||||
if ($fileInfo === null) {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$medium->update($request->all());
|
||||||
|
|
||||||
|
if ($file !== null) {
|
||||||
|
try {
|
||||||
|
$medium->updateWithUploadedFile($file);
|
||||||
|
} catch (\Exception $e) {
|
||||||
return $this->respondWithErrors(
|
return $this->respondWithErrors(
|
||||||
['file' => 'The file could not be stored on the server'],
|
['file' => $e->getMessage()],
|
||||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (file_exists($oldPath) === true) {
|
|
||||||
unlink($oldPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
$request->merge([
|
|
||||||
'title' => $file->getClientOriginalName(),
|
|
||||||
'mime' => $file->getMimeType(),
|
|
||||||
'name' => $fileInfo['name'],
|
|
||||||
'size' => filesize($fileInfo['path'])
|
|
||||||
]);
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
$medium->update($request->all());
|
|
||||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
||||||
}//end if
|
}//end if
|
||||||
|
|
||||||
@@ -164,10 +165,6 @@ class MediaController extends ApiController
|
|||||||
public function destroy(Media $medium)
|
public function destroy(Media $medium)
|
||||||
{
|
{
|
||||||
if (MediaConductor::destroyable($medium) === true) {
|
if (MediaConductor::destroyable($medium) === true) {
|
||||||
if (file_exists($medium->path()) === true) {
|
|
||||||
unlink($medium->path());
|
|
||||||
}
|
|
||||||
|
|
||||||
$medium->delete();
|
$medium->delete();
|
||||||
return $this->respondNoContent();
|
return $this->respondNoContent();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class OCRController extends ApiController
|
|||||||
$data = ['ocr' => []];
|
$data = ['ocr' => []];
|
||||||
|
|
||||||
$filters = $request->get('filters', ['tesseract']);
|
$filters = $request->get('filters', ['tesseract']);
|
||||||
if(is_array($filters) === false) {
|
if (is_array($filters) === false) {
|
||||||
$filters = explode(',', $filters);
|
$filters = explode(',', $filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +52,12 @@ class OCRController extends ApiController
|
|||||||
// We need progress updates to break the connection mid-way
|
// We need progress updates to break the connection mid-way
|
||||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // more progress info
|
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // more progress info
|
||||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function(
|
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function (
|
||||||
$downloadSize, $downloaded, $uploadSize, $uploaded
|
$downloadSize,
|
||||||
) use($maxDownloadSize) {
|
$downloaded,
|
||||||
|
$uploadSize,
|
||||||
|
$uploaded
|
||||||
|
) use ($maxDownloadSize) {
|
||||||
return ($downloaded > $maxDownloadSize) ? 1 : 0;
|
return ($downloaded > $maxDownloadSize) ? 1 : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,9 +65,9 @@ class OCRController extends ApiController
|
|||||||
$curlError = curl_errno($ch);
|
$curlError = curl_errno($ch);
|
||||||
$curlSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
|
$curlSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
if($curlError !== 0) {
|
if ($curlError !== 0) {
|
||||||
$error = 'File size is larger then allowed';
|
$error = 'File size is larger then allowed';
|
||||||
if($curlError !== CurlErrorCodes::CURLE_ABORTED_BY_CALLBACK) {
|
if ($curlError !== CurlErrorCodes::CURLE_ABORTED_BY_CALLBACK) {
|
||||||
$error = CurlErrorCodes::getMessage($curlError);
|
$error = CurlErrorCodes::getMessage($curlError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +80,8 @@ class OCRController extends ApiController
|
|||||||
|
|
||||||
// tesseract (overall)
|
// tesseract (overall)
|
||||||
$ocr = null;
|
$ocr = null;
|
||||||
foreach($filters as $filterItem) {
|
foreach ($filters as $filterItem) {
|
||||||
if(str_starts_with($filterItem, 'tesseract') === true) {
|
if (str_starts_with($filterItem, 'tesseract') === true) {
|
||||||
$ocr = new TesseractOCR();
|
$ocr = new TesseractOCR();
|
||||||
$ocr->image($urlDownloadFilePath);
|
$ocr->image($urlDownloadFilePath);
|
||||||
if ($tesseractOEM !== null) {
|
if ($tesseractOEM !== null) {
|
||||||
@@ -95,11 +98,10 @@ class OCRController extends ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Image Filter Function
|
// Image Filter Function
|
||||||
$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();
|
||||||
@@ -116,7 +118,7 @@ class OCRController extends ApiController
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Image Scale Function
|
// Image Scale Function
|
||||||
$tesseractImageScaleFunc = function($scaleFunc) use ($curlResult, $ocr) {
|
$tesseractImageScaleFunc = function ($scaleFunc) use ($curlResult, $ocr) {
|
||||||
$result = '';
|
$result = '';
|
||||||
$srcImage = imagecreatefromstring($curlResult);
|
$srcImage = imagecreatefromstring($curlResult);
|
||||||
$srcWidth = imagesx($srcImage);
|
$srcWidth = imagesx($srcImage);
|
||||||
@@ -143,7 +145,7 @@ class OCRController extends ApiController
|
|||||||
};
|
};
|
||||||
|
|
||||||
// filter: tesseract
|
// filter: tesseract
|
||||||
if(in_array('tesseract', $filters) === true) {
|
if (in_array('tesseract', $filters) === true) {
|
||||||
$data['ocr']['tesseract'] = $ocr->run(500);
|
$data['ocr']['tesseract'] = $ocr->run(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,14 +156,14 @@ class OCRController extends ApiController
|
|||||||
|
|
||||||
// filter: tesseract.double_scale
|
// filter: tesseract.double_scale
|
||||||
if (in_array('tesseract.double_scale', $filters) === true) {
|
if (in_array('tesseract.double_scale', $filters) === true) {
|
||||||
$data['ocr']['tesseract.double_scale'] = $tesseractImageScaleFunc(function($size) {
|
$data['ocr']['tesseract.double_scale'] = $tesseractImageScaleFunc(function ($size) {
|
||||||
return $size * 2;
|
return $size * 2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter: tesseract.half_scale
|
// filter: tesseract.half_scale
|
||||||
if (in_array('tesseract.half_scale', $filters) === true) {
|
if (in_array('tesseract.half_scale', $filters) === true) {
|
||||||
$data['ocr']['tesseract.half_scale'] = $tesseractImageScaleFunc(function($size) {
|
$data['ocr']['tesseract.half_scale'] = $tesseractImageScaleFunc(function ($size) {
|
||||||
return $size / 2;
|
return $size / 2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -187,12 +189,12 @@ class OCRController extends ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter: keras
|
// filter: keras
|
||||||
if(in_array('keras', $filters) === true) {
|
if (in_array('keras', $filters) === true) {
|
||||||
$cmd = '/usr/bin/python3 ' . base_path() . '/scripts/keras_oc.py ' . urlencode($url);
|
$cmd = '/usr/bin/python3 ' . base_path() . '/scripts/keras_oc.py ' . urlencode($url);
|
||||||
$command = escapeshellcmd($cmd);
|
$command = escapeshellcmd($cmd);
|
||||||
$output = shell_exec($cmd);
|
$output = shell_exec($cmd);
|
||||||
if ($output !== null && strlen($output) > 0) {
|
if ($output !== null && strlen($output) > 0) {
|
||||||
$output = substr($output, strpos($output, '----------START----------') + 25);
|
$output = substr($output, (strpos($output, '----------START----------') + 25));
|
||||||
} else {
|
} else {
|
||||||
$output = '';
|
$output = '';
|
||||||
}
|
}
|
||||||
|
|||||||
117
app/Http/Controllers/Api/ShortlinkController.php
Normal file
117
app/Http/Controllers/Api/ShortlinkController.php
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Conductors\MediaConductor;
|
||||||
|
use App\Conductors\ShortlinkConductor;
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Http\Requests\MediaRequest;
|
||||||
|
use App\Http\Requests\ShortlinkRequest;
|
||||||
|
use App\Models\Media;
|
||||||
|
use App\Models\Shortlink;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Laravel\Sanctum\PersonalAccessToken;
|
||||||
|
|
||||||
|
class ShortlinkController extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* ApplicationController constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth:sanctum')
|
||||||
|
->only(['store','update','destroy']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
list($collection, $total) = ShortlinkConductor::request($request);
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
$collection,
|
||||||
|
['isCollection' => true,
|
||||||
|
'appendData' => ['total' => $total]
|
||||||
|
],
|
||||||
|
function ($options) {
|
||||||
|
return $options['total'] === 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||||
|
* @param \App\Models\Shortlink $shortlink The request shortlink.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function show(Request $request, Shortlink $shortlink)
|
||||||
|
{
|
||||||
|
if (ShortlinkConductor::viewable($shortlink) === true) {
|
||||||
|
return $this->respondAsResource(ShortlinkConductor::model($request, $shortlink));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a new media resource
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\ShortlinkRequest $request The shortlink.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function store(ShortlinkRequest $request)
|
||||||
|
{
|
||||||
|
if (ShortlinkConductor::creatable() === true) {
|
||||||
|
$shortlink = Shortlink::create($request->all());
|
||||||
|
|
||||||
|
return $this->respondAsResource(
|
||||||
|
ShortlinkConductor::model($request, $shortlink),
|
||||||
|
['respondCode' => HttpResponseCodes::HTTP_ACCEPTED]
|
||||||
|
);
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the media resource in storage.
|
||||||
|
*
|
||||||
|
* @param \App\Http\Requests\ShortlinkRequest $request The update request.
|
||||||
|
* @param \App\Models\Shortlink $medium The specified shortlink.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function update(ShortlinkRequest $request, Shortlink $shortlink)
|
||||||
|
{
|
||||||
|
if (ShortlinkConductor::updatable($shortlink) === true) {
|
||||||
|
$shortlink->update($request->all());
|
||||||
|
return $this->respondAsResource(ShortlinkConductor::model($request, $shortlink));
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*
|
||||||
|
* @param \App\Models\Shortlink $medium Specified shortlink.
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function destroy(Shortlink $shortlink)
|
||||||
|
{
|
||||||
|
if (ShortlinkConductor::destroyable($shortlink) === true) {
|
||||||
|
$shortlink->delete();
|
||||||
|
return $this->respondNoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,8 @@ class SubscriptionController extends ApiController
|
|||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$collection,
|
||||||
['isCollection' => true,
|
['isCollection' => true,
|
||||||
'appendData' => ['total' => $total]]
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Conductors\EventConductor;
|
||||||
use App\Enum\HttpResponseCodes;
|
use App\Enum\HttpResponseCodes;
|
||||||
use App\Http\Requests\UserRequest;
|
use App\Http\Requests\UserRequest;
|
||||||
use App\Http\Requests\UserForgotPasswordRequest;
|
use App\Http\Requests\UserForgotPasswordRequest;
|
||||||
use App\Http\Requests\UserForgotUsernameRequest;
|
|
||||||
use App\Http\Requests\UserRegisterRequest;
|
use App\Http\Requests\UserRegisterRequest;
|
||||||
use App\Http\Requests\UserResendVerifyEmailRequest;
|
use App\Http\Requests\UserResendVerifyEmailRequest;
|
||||||
use App\Http\Requests\UserResetPasswordRequest;
|
use App\Http\Requests\UserResetPasswordRequest;
|
||||||
@@ -14,7 +14,6 @@ use App\Jobs\SendEmailJob;
|
|||||||
use App\Mail\ChangedEmail;
|
use App\Mail\ChangedEmail;
|
||||||
use App\Mail\ChangedPassword;
|
use App\Mail\ChangedPassword;
|
||||||
use App\Mail\ChangeEmailVerify;
|
use App\Mail\ChangeEmailVerify;
|
||||||
use App\Mail\ForgotUsername;
|
|
||||||
use App\Mail\ForgotPassword;
|
use App\Mail\ForgotPassword;
|
||||||
use App\Mail\EmailVerify;
|
use App\Mail\EmailVerify;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@@ -22,6 +21,8 @@ use App\Models\UserCode;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use App\Conductors\UserConductor;
|
use App\Conductors\UserConductor;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
|
||||||
class UserController extends ApiController
|
class UserController extends ApiController
|
||||||
{
|
{
|
||||||
@@ -37,10 +38,10 @@ class UserController extends ApiController
|
|||||||
'register',
|
'register',
|
||||||
'exists',
|
'exists',
|
||||||
'forgotPassword',
|
'forgotPassword',
|
||||||
'forgotUsername',
|
|
||||||
'resetPassword',
|
'resetPassword',
|
||||||
'verifyEmail',
|
'verifyEmail',
|
||||||
'resendVerifyEmailCode'
|
'resendVerifyEmailCode',
|
||||||
|
'eventList',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +58,8 @@ class UserController extends ApiController
|
|||||||
return $this->respondAsResource(
|
return $this->respondAsResource(
|
||||||
$collection,
|
$collection,
|
||||||
['isCollection' => true,
|
['isCollection' => true,
|
||||||
'appendData' => ['total' => $total]]
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,14 +99,14 @@ class UserController extends ApiController
|
|||||||
* Update the specified resource in storage.
|
* Update the specified resource in storage.
|
||||||
*
|
*
|
||||||
* @param \App\Http\Requests\UserRequest $request The user update request.
|
* @param \App\Http\Requests\UserRequest $request The user update request.
|
||||||
* @param \App\Models\User $user The specified user.
|
* @param \App\Models\User $user The specified user.
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function update(UserRequest $request, User $user)
|
public function update(UserRequest $request, User $user)
|
||||||
{
|
{
|
||||||
if (UserConductor::updatable($user) === true) {
|
if (UserConductor::updatable($user) === true) {
|
||||||
$input = [];
|
$input = [];
|
||||||
$updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password'];
|
$updatable = ['first_name', 'last_name', 'email', 'phone', 'password', 'display_name'];
|
||||||
|
|
||||||
if ($request->user()->hasPermission('admin/user') === true) {
|
if ($request->user()->hasPermission('admin/user') === true) {
|
||||||
$updatable = array_merge($updatable, ['email_verified_at']);
|
$updatable = array_merge($updatable, ['email_verified_at']);
|
||||||
@@ -143,20 +145,32 @@ 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 \Illuminate\Http\Response
|
|
||||||
*/
|
*/
|
||||||
public function register(UserRegisterRequest $request)
|
public function register(UserRegisterRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$user = User::create([
|
$userData = $request->only([
|
||||||
'first_name' => $request->input('first_name'),
|
'first_name',
|
||||||
'last_name' => $request->input('last_name'),
|
'last_name',
|
||||||
'username' => $request->input('username'),
|
'email',
|
||||||
'email' => $request->input('email'),
|
'phone',
|
||||||
'phone' => $request->input('phone'),
|
'password',
|
||||||
'password' => Hash::make($request->input('password'))
|
'display_name',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$userData['password'] = Hash::make($userData['password']);
|
||||||
|
|
||||||
|
$user = User::where('email', $request->input('email'))
|
||||||
|
->whereNull('password')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
$user = User::create($userData);
|
||||||
|
} else {
|
||||||
|
unset($userData['email']);
|
||||||
|
$user->update($userData);
|
||||||
|
}//end if
|
||||||
|
|
||||||
$code = $user->codes()->create([
|
$code = $user->codes()->create([
|
||||||
'action' => 'verify-email',
|
'action' => 'verify-email',
|
||||||
]);
|
]);
|
||||||
@@ -173,26 +187,6 @@ class UserController extends ApiController
|
|||||||
}//end try
|
}//end try
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an email with all the usernames registered at that address
|
|
||||||
*
|
|
||||||
* @param \App\Http\Requests\UserForgotUsernameRequest $request The forgot username request.
|
|
||||||
* @return \Illuminate\Http\Response
|
|
||||||
*/
|
|
||||||
public function forgotUsername(UserForgotUsernameRequest $request)
|
|
||||||
{
|
|
||||||
$users = User::where('email', $request->input('email'))->whereNotNull('email_verified_at')->get();
|
|
||||||
if ($users->count() > 0) {
|
|
||||||
dispatch((new SendEmailJob(
|
|
||||||
$users->first()->email,
|
|
||||||
new ForgotUsername($users->pluck('username')->toArray())
|
|
||||||
)))->onQueue('mail');
|
|
||||||
return $this->respondNoContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->respondJson(['message' => 'Username send to the email address if registered']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new reset password code
|
* Generates a new reset password code
|
||||||
*
|
*
|
||||||
@@ -201,7 +195,7 @@ class UserController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function forgotPassword(UserForgotPasswordRequest $request)
|
public function forgotPassword(UserForgotPasswordRequest $request)
|
||||||
{
|
{
|
||||||
$user = User::where('username', $request->input('username'))->first();
|
$user = User::where('email', $request->input('email'))->first();
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
$user->codes()->where('action', 'reset-password')->delete();
|
$user->codes()->where('action', 'reset-password')->delete();
|
||||||
$code = $user->codes()->create([
|
$code = $user->codes()->create([
|
||||||
@@ -291,13 +285,12 @@ 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 \Illuminate\Http\Response
|
|
||||||
*/
|
*/
|
||||||
public function resendVerifyEmail(UserResendVerifyEmailRequest $request)
|
public function resendVerifyEmail(UserResendVerifyEmailRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
UserCode::clearExpired();
|
UserCode::clearExpired();
|
||||||
|
|
||||||
$user = User::where('username', $request->input('username'))->first();
|
$user = User::where('email', $request->input('email'))->first();
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
$code = $user->codes()->where('action', 'verify-email')->first();
|
$code = $user->codes()->where('action', 'verify-email')->first();
|
||||||
$code->regenerate();
|
$code->regenerate();
|
||||||
@@ -322,7 +315,7 @@ class UserController extends ApiController
|
|||||||
*/
|
*/
|
||||||
public function resendVerifyEmailCode(UserResendVerifyEmailRequest $request)
|
public function resendVerifyEmailCode(UserResendVerifyEmailRequest $request)
|
||||||
{
|
{
|
||||||
$user = User::where('username', $request->input('username'))->first();
|
$user = User::where('email', $request->input('email'))->first();
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
$user->codes()->where('action', 'verify-email')->delete();
|
$user->codes()->where('action', 'verify-email')->delete();
|
||||||
|
|
||||||
@@ -339,4 +332,28 @@ class UserController extends ApiController
|
|||||||
|
|
||||||
return $this->respondNotFound();
|
return $this->respondNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a JSON event list of a user.
|
||||||
|
*
|
||||||
|
* @param Request $request The http request.
|
||||||
|
* @param User $user The specified user.
|
||||||
|
*/
|
||||||
|
public function eventList(Request $request, User $user): JsonResponse
|
||||||
|
{
|
||||||
|
if ($request->user() !== null && ($request->user() === $user || $request->user()->hasPermission('admin/events') === true)) {
|
||||||
|
$collection = $user->events;
|
||||||
|
$total = $collection->count();
|
||||||
|
|
||||||
|
$collection = EventConductor::collection($request, $collection);
|
||||||
|
return $this->respondAsResource(
|
||||||
|
$collection,
|
||||||
|
['isCollection' => true,
|
||||||
|
'appendData' => ['total' => $total]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return $this->respondForbidden();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,11 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
|
||||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
use Illuminate\Routing\Controller as BaseController;
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
|
||||||
class Controller extends BaseController
|
class Controller extends BaseController
|
||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
use DispatchesJobs;
|
|
||||||
use ValidatesRequests;
|
use ValidatesRequests;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Kernel extends HttpKernel
|
|||||||
|
|
||||||
'api' => [
|
'api' => [
|
||||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||||
'throttle: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,
|
||||||
'useSanctumGuard',
|
'useSanctumGuard',
|
||||||
@@ -49,13 +49,13 @@ class Kernel extends HttpKernel
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The application's route middleware.
|
* The application's middleware aliases.
|
||||||
*
|
*
|
||||||
* These middleware may be assigned to groups or used individually.
|
* Aliases may be used to conveniently assign middleware to routes and groups.
|
||||||
*
|
*
|
||||||
* @var array<string, class-string|string>
|
* @var array<string, class-string|string>
|
||||||
*/
|
*/
|
||||||
protected $routeMiddleware = [
|
protected $middlewareAliases = [
|
||||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ 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|null
|
|
||||||
*/
|
*/
|
||||||
protected function redirectTo(mixed $request)
|
protected function redirectTo(mixed $request): ?string
|
||||||
{
|
{
|
||||||
if ($request->expectsJson() === false) {
|
if ($request->expectsJson() === false) {
|
||||||
return route('login');
|
return route('login');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -10,11 +11,9 @@ class ForceJsonResponse
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
$request->headers->set('Accept', 'application/json');
|
$request->headers->set('Accept', 'application/json');
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\Analytics;
|
use App\Models\Analytics;
|
||||||
@@ -11,18 +12,16 @@ class LogRequest
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
// Make it an after middleware
|
// Make it an after middleware
|
||||||
$response = $next($request);
|
$response = $next($request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Analytics::create([
|
Analytics::createWithSession([
|
||||||
'type' => 'pageview',
|
'type' => 'apirequest',
|
||||||
'attribute' => $request->path(),
|
'attribute' => $request->path(),
|
||||||
'useragent' => $request->userAgent(),
|
'useragent' => $request->userAgent(),
|
||||||
'ip' => $request->ip(),
|
'ip' => $request->ip(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -12,12 +13,11 @@ class RedirectIfAuthenticated
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param Request $request Request.
|
* @param Request $request Request.
|
||||||
* @param Closure(Request): (Response|RedirectResponse) $next Next.
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
* @param string|null ...$guards Guards.
|
* @param string|null ...$guards Guards.
|
||||||
* @return Response|RedirectResponse
|
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next, ...$guards)
|
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||||
{
|
{
|
||||||
$guards = empty($guards) === true ? [null] : $guards;
|
$guards = empty($guards) === true ? [null] : $guards;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class TrustHosts extends Middleware
|
|||||||
*
|
*
|
||||||
* @return array<int, string|null>
|
* @return array<int, string|null>
|
||||||
*/
|
*/
|
||||||
public function hosts()
|
public function hosts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
$this->allSubdomainsOfApplicationUrl(),
|
$this->allSubdomainsOfApplicationUrl(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
@@ -11,11 +12,9 @@ class UseSanctumGuard
|
|||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @param \Illuminate\Http\Request $request
|
|
||||||
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
Auth::shouldUse('sanctum');
|
Auth::shouldUse('sanctum');
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
|||||||
35
app/Http/Requests/AnalyticsRequest.php
Normal file
35
app/Http/Requests/AnalyticsRequest.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class AnalyticsRequest extends BaseRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to POST requests.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function postRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'required|string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to PUT request.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function putRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'string',
|
||||||
|
'useragent' => 'string',
|
||||||
|
'ip' => 'ipv4|ipv6',
|
||||||
|
'session' => 'number',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,17 @@ namespace App\Http\Requests;
|
|||||||
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class PostRequest extends BaseRequest
|
class ArticleRequest extends BaseRequest
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the validation rules that apply to POST requests.
|
* Get the validation rules that apply to POST requests.
|
||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function postRules()
|
public function postRules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'slug' => 'required|string|min:6|unique:posts',
|
'slug' => 'required|string|min:6|unique:articles',
|
||||||
'title' => 'required|string|min:6|max:255',
|
'title' => 'required|string|min:6|max:255',
|
||||||
'publish_at' => 'required|date',
|
'publish_at' => 'required|date',
|
||||||
'user_id' => 'required|uuid|exists:users,id',
|
'user_id' => 'required|uuid|exists:users,id',
|
||||||
@@ -28,13 +28,13 @@ class PostRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function putRules()
|
public function putRules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'slug' => [
|
'slug' => [
|
||||||
'string',
|
'string',
|
||||||
'min:6',
|
'min:6',
|
||||||
Rule::unique('posts')->ignoreModel($this->post),
|
Rule::unique('articles')->ignoreModel($this->article),
|
||||||
],
|
],
|
||||||
'title' => 'string|min:6|max:255',
|
'title' => 'string|min:6|max:255',
|
||||||
'publish_at' => 'date',
|
'publish_at' => 'date',
|
||||||
@@ -11,10 +11,10 @@ class AuthLoginRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'username' => 'required|string|min:6|max:255',
|
'email' => 'required|string|min:6|max:255',
|
||||||
'password' => 'required|string|min:6',
|
'password' => 'required|string|min:6',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ 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()
|
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();
|
||||||
@@ -30,7 +28,7 @@ class BaseRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
$rules = [];
|
$rules = [];
|
||||||
|
|
||||||
@@ -54,9 +52,8 @@ 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)
|
private function mergeRules(array $collection1, array $collection2): array
|
||||||
{
|
{
|
||||||
$rules = [];
|
$rules = [];
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ class ContactSendRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'required|max:255',
|
'name' => 'required|max:255',
|
||||||
'email' => 'required|email|max:255',
|
'email' => 'required|email|max:255',
|
||||||
'content' => 'required|max:2000',
|
'content' => 'required|max:2000',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class EventRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function baseRules()
|
public function baseRules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'title' => 'min:6',
|
'title' => 'min:6',
|
||||||
@@ -42,7 +42,7 @@ class EventRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
protected function postRules()
|
protected function postRules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'title' => 'required',
|
'title' => 'required',
|
||||||
|
|||||||
36
app/Http/Requests/ShortlinkRequest.php
Normal file
36
app/Http/Requests/ShortlinkRequest.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class ShortlinkRequest extends BaseRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Apply the additional POST base rules to this request
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function postRules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => 'required|string|max:255|min:2|unique:shortlinks',
|
||||||
|
'url' => 'required|string|max:255|min:2',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to PUT request.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function putRules(): array
|
||||||
|
{
|
||||||
|
$shortlink = $this->route('shortlink');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'code' => ['required', 'string', 'max:255', 'min:2', Rule::unique('shortlinks')->ignore($shortlink->id)],
|
||||||
|
'url' => 'required|string|max:255|min:2',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,11 +11,11 @@ class SubscriptionRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function postRules()
|
public function postRules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email' => 'required|email|unique:subscriptions',
|
'email' => 'required|email|unique:subscriptions',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,20 +24,18 @@ class SubscriptionRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function destroyRules()
|
public function destroyRules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the custom error messages.
|
* Get the custom error messages.
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function messages()
|
public function messages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email.unique' => 'This email address has already subscribed',
|
'email.unique' => 'This email address has already subscribed',
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ class UserForgotPasswordRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'username' => 'required|exists:users,username',
|
'email' => 'required|exists:users,email',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests;
|
|
||||||
|
|
||||||
use App\Rules\Recaptcha;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
|
|
||||||
class UserForgotUsernameRequest extends FormRequest
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Get the validation rules that apply to the request.
|
|
||||||
*
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function rules()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'email' => 'required|email|max:255',
|
|
||||||
'captcha_token' => [new Recaptcha()],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Rules\Uniqueish;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class UserRegisterRequest extends FormRequest
|
class UserRegisterRequest extends FormRequest
|
||||||
@@ -11,13 +12,11 @@ class UserRegisterRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'first_name' => 'required|string|max:255',
|
'display_name' => ['required','string','max:255', new Uniqueish('users')],
|
||||||
'last_name' => 'required|string|max:255',
|
'email' => 'required|string|email|max:255|unique:users',
|
||||||
'email' => 'required|string|email|max:255',
|
|
||||||
'username' => 'required|string|min:4|max:255|unique:users',
|
|
||||||
'password' => 'required|string|min:8',
|
'password' => 'required|string|min:8',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Rules\RequiredIfAny;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Illuminate\Validation\Rules\RequiredIf;
|
||||||
|
use App\Rules\Uniqueish;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
class UserRequest extends BaseRequest
|
class UserRequest extends BaseRequest
|
||||||
{
|
{
|
||||||
@@ -11,13 +15,21 @@ class UserRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function postRules()
|
public function postRules(): array
|
||||||
{
|
{
|
||||||
|
$user = auth()->user();
|
||||||
|
$isAdminUser = $user->hasPermission('admin/users');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'username' => 'required|string|max:255|min:4|unique:users',
|
'first_name' => ($isAdminUser === true ? 'required_with:last_name,display_name,phone' : 'required') . '|string|max:255|min:2',
|
||||||
'first_name' => 'required|string|max:255|min:2',
|
'last_name' => ($isAdminUser === true ? 'required_with:first_name,display_name,phone' : 'required') . '|string|max:255|min:2',
|
||||||
'last_name' => 'required|string|max:255|min:2',
|
'display_name' => [
|
||||||
'email' => 'required|string|email|max:255',
|
$isAdminUser === true ? 'required_with:first_name,last_name,phone' : 'required',
|
||||||
|
'string',
|
||||||
|
'max:255',
|
||||||
|
new Uniqueish('users')
|
||||||
|
],
|
||||||
|
'email' => 'required|string|email|max:255|unique:users',
|
||||||
'phone' => ['string', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
'phone' => ['string', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||||
'email_verified_at' => 'date'
|
'email_verified_at' => 'date'
|
||||||
];
|
];
|
||||||
@@ -28,27 +40,68 @@ class UserRequest extends BaseRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function putRules()
|
public function putRules(): array
|
||||||
{
|
{
|
||||||
$user = $this->route('user');
|
$user = auth()->user();
|
||||||
|
$ruleUser = $this->route('user');
|
||||||
|
$isAdminUser = $user->hasPermission('admin/users');
|
||||||
|
|
||||||
|
$requiredIfFieldsPresent = function (array $fields) use ($ruleUser): RequiredIf {
|
||||||
|
return new RequiredIf(function () use ($fields, $ruleUser) {
|
||||||
|
$input = $this->all();
|
||||||
|
$values = Arr::only($input, $fields);
|
||||||
|
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
if ($value !== null && $value !== '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fields = array_diff($fields, array_keys($values));
|
||||||
|
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
if ($ruleUser->$field !== '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'username' => [
|
'first_name' => [
|
||||||
|
'sometimes',
|
||||||
|
$isAdminUser === true ? $requiredIfFieldsPresent(['last_name', 'display_name', 'phone']) : 'required',
|
||||||
'string',
|
'string',
|
||||||
|
'between:2,255',
|
||||||
|
],
|
||||||
|
'last_name' => [
|
||||||
|
'sometimes',
|
||||||
|
$isAdminUser === true ? $requiredIfFieldsPresent(['first_name', 'last_name', 'phone']) : 'required',
|
||||||
|
'string',
|
||||||
|
'between:2,255',
|
||||||
|
],
|
||||||
|
'display_name' => [
|
||||||
|
'sometimes',
|
||||||
|
$isAdminUser === true ? $requiredIfFieldsPresent(['first_name', 'display_name', 'phone']) : 'required',
|
||||||
|
'string',
|
||||||
|
'between:2,255',
|
||||||
|
(new Uniqueish('users', 'display_name'))->ignore($ruleUser->id)
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'string',
|
||||||
|
'email',
|
||||||
'max:255',
|
'max:255',
|
||||||
'min:4',
|
Rule::unique('users')->ignore($ruleUser->id)->when(
|
||||||
Rule::unique('users')->ignore($user->id)->when(
|
$this->email !== $ruleUser->email,
|
||||||
$this->username !== $user->username,
|
|
||||||
function ($query) {
|
function ($query) {
|
||||||
return $query->where('username', $this->username);
|
return $query->where('email', $this->email);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
'first_name' => 'string|max:255|min:2',
|
'phone' => ['nullable', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||||
'last_name' => 'string|max:255|min:2',
|
'password' => "nullable|string|min:8"
|
||||||
'email' => 'string|email|max:255',
|
|
||||||
'phone' => ['nullable','regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
|
||||||
'password' => 'string|min:8'
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ class UserResendVerifyEmailRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'username' => 'required|exists:users,username',
|
'email' => 'required|exists:users,email',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ class UserResetPasswordRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'code' => 'required|digits:6',
|
'code' => 'required|digits:6',
|
||||||
'password' => 'required|string|min:8',
|
'password' => 'required|string|min:8',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ class UserVerifyEmailRequest extends FormRequest
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function rules()
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'code' => 'required|digits:6',
|
'code' => 'required|digits:6',
|
||||||
'captcha_token' => [new Recaptcha()],
|
// 'captcha_token' => [new Recaptcha()],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
app/Jobs/MoveMediaJob.php
Normal file
82
app/Jobs/MoveMediaJob.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?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,10 +47,8 @@ class SendEmailJob implements ShouldQueue
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle(): void
|
||||||
{
|
{
|
||||||
Mail::to($this->to)->send($this->mailable);
|
Mail::to($this->to)->send($this->mailable);
|
||||||
}
|
}
|
||||||
|
|||||||
177
app/Jobs/StoreUploadedFileJob.php
Normal file
177
app/Jobs/StoreUploadedFileJob.php
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<?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,10 +54,8 @@ class ChangeEmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: '👋🏻 Lets change your email!',
|
subject: '👋🏻 Lets change your email!',
|
||||||
@@ -66,10 +64,8 @@ class ChangeEmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.change_email_verify',
|
view: 'emails.user.change_email_verify',
|
||||||
|
|||||||
@@ -54,10 +54,8 @@ class ChangedEmail extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: '👍 Your email has been changed!',
|
subject: '👍 Your email has been changed!',
|
||||||
@@ -66,10 +64,8 @@ class ChangedEmail extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.changed_email',
|
view: 'emails.user.changed_email',
|
||||||
|
|||||||
@@ -36,10 +36,8 @@ class ChangedPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: '👍 Your password has been changed!',
|
subject: '👍 Your password has been changed!',
|
||||||
@@ -48,10 +46,8 @@ class ChangedPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.changed_password',
|
view: 'emails.user.changed_password',
|
||||||
|
|||||||
@@ -53,10 +53,8 @@ class Contact extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: config('contact.contact_subject'),
|
subject: config('contact.contact_subject'),
|
||||||
@@ -65,10 +63,8 @@ class Contact extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.contact',
|
view: 'emails.user.contact',
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ class EmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: '👋🏻 Welcome to STEMMechanics!',
|
subject: '👋🏻 Welcome to STEMMechanics!',
|
||||||
@@ -57,10 +55,8 @@ class EmailVerify extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.email_verify',
|
view: 'emails.user.email_verify',
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ class ForgotPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: '🤦 Forgot your password?',
|
subject: '🤦 Forgot your password?',
|
||||||
@@ -57,10 +55,8 @@ class ForgotPassword extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.forgot_password',
|
view: 'emails.user.forgot_password',
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Mail;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Mail\Mailable;
|
|
||||||
use Illuminate\Mail\Mailables\Content;
|
|
||||||
use Illuminate\Mail\Mailables\Envelope;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class ForgotUsername extends Mailable
|
|
||||||
{
|
|
||||||
use Queueable;
|
|
||||||
use SerializesModels;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of usernames
|
|
||||||
*
|
|
||||||
* @var string[]
|
|
||||||
*/
|
|
||||||
public $usernames;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new message instance.
|
|
||||||
*
|
|
||||||
* @param array $usernames The usernames.
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(array $usernames)
|
|
||||||
{
|
|
||||||
$this->usernames = $usernames;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the message envelope.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
|
||||||
public function envelope()
|
|
||||||
{
|
|
||||||
return new Envelope(
|
|
||||||
subject: '🤦 Forgot your username?',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the message content definition.
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
|
||||||
public function content()
|
|
||||||
{
|
|
||||||
return new Content(
|
|
||||||
view: 'emails.user.forgot_username',
|
|
||||||
text: 'emails.user.forgot_username_plain',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,10 +36,8 @@ class SubscriptionConfirm extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: '🗞️ You\'re on the mailing list!',
|
subject: '🗞️ You\'re on the mailing list!',
|
||||||
@@ -48,10 +46,8 @@ class SubscriptionConfirm extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.subscription_confirm',
|
view: 'emails.user.subscription_confirm',
|
||||||
|
|||||||
@@ -36,10 +36,8 @@ class SubscriptionUnsubscribed extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Envelope
|
|
||||||
*/
|
*/
|
||||||
public function envelope()
|
public function envelope(): Envelope
|
||||||
{
|
{
|
||||||
return new Envelope(
|
return new Envelope(
|
||||||
subject: 'You have been unsubscribed',
|
subject: 'You have been unsubscribed',
|
||||||
@@ -48,10 +46,8 @@ class SubscriptionUnsubscribed extends Mailable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message content definition.
|
* Get the message content definition.
|
||||||
*
|
|
||||||
* @return \Illuminate\Mail\Mailables\Content
|
|
||||||
*/
|
*/
|
||||||
public function content()
|
public function content(): Content
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'emails.user.subscription_unsubscribed',
|
view: 'emails.user.subscription_unsubscribed',
|
||||||
|
|||||||
@@ -15,4 +15,30 @@ class Analytics extends Model
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $guarded = [];
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ 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\MorphMany;
|
||||||
|
|
||||||
class Post extends Model
|
class Article extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuids;
|
use Uuids;
|
||||||
@@ -27,20 +28,18 @@ class Post extends Model
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the post user
|
* Get the article user
|
||||||
*
|
|
||||||
* @return BelongsTo
|
|
||||||
*/
|
*/
|
||||||
public function user()
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all of the post's attachments.
|
* Get all of the article's attachments.
|
||||||
*/
|
*/
|
||||||
public function attachments()
|
public function attachments(): MorphMany
|
||||||
{
|
{
|
||||||
return $this->morphMany('App\Models\Attachment', 'attachable');
|
return $this->morphMany(\App\Models\Attachment::class, 'attachable');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@ 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\MorphTo;
|
||||||
|
|
||||||
class Attachment extends Model
|
class Attachment extends Model
|
||||||
{
|
{
|
||||||
@@ -16,13 +18,23 @@ class Attachment extends Model
|
|||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'media_id',
|
'media_id',
|
||||||
|
'private',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default attributes.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'private' => 'false',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get attachments attachable
|
* Get attachments attachable
|
||||||
*/
|
*/
|
||||||
public function attachable()
|
public function attachable(): MorphTo
|
||||||
{
|
{
|
||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
@@ -30,7 +42,7 @@ class Attachment extends Model
|
|||||||
/**
|
/**
|
||||||
* Get the media for this attachment.
|
* Get the media for this attachment.
|
||||||
*/
|
*/
|
||||||
public function media()
|
public function media(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Media::class);
|
return $this->belongsTo(Media::class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
|
||||||
class Event extends Model
|
class Event extends Model
|
||||||
{
|
{
|
||||||
@@ -19,6 +21,7 @@ class Event extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'title',
|
'title',
|
||||||
'location',
|
'location',
|
||||||
|
'location_url',
|
||||||
'address',
|
'address',
|
||||||
'start_at',
|
'start_at',
|
||||||
'end_at',
|
'end_at',
|
||||||
@@ -34,10 +37,18 @@ class Event extends Model
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all of the post's attachments.
|
* Get all of the article's attachments.
|
||||||
*/
|
*/
|
||||||
public function attachments()
|
public function attachments(): MorphMany
|
||||||
{
|
{
|
||||||
return $this->morphMany('App\Models\Attachment', 'attachable');
|
return $this->morphMany(\App\Models\Attachment::class, 'attachable');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the associated users.
|
||||||
|
*/
|
||||||
|
public function users(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(User::class, 'event_user', 'event_id', 'user_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
app/Models/EventUsers.php
Normal file
40
app/Models/EventUsers.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class EventUser extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
use Uuids;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'event_id',
|
||||||
|
'user_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the event for this attachment.
|
||||||
|
*/
|
||||||
|
public function event(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Event::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user for this attachment.
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,19 +2,37 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Jobs\MoveMediaJob;
|
||||||
|
use App\Jobs\OptimizeMediaJob;
|
||||||
|
use App\Jobs\StoreUploadedFileJob;
|
||||||
use App\Traits\Uuids;
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
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\Http\UploadedFile;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Intervention\Image\Facades\Image;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\ImageOptimizer\OptimizerChainFactory;
|
use InvalidArgumentException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
class Media extends Model
|
class Media extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Uuids;
|
use Uuids;
|
||||||
|
use DispatchesJobs;
|
||||||
|
|
||||||
|
public const INVALID_FILE_ERROR = 1;
|
||||||
|
public const FILE_SIZE_EXCEEDED_ERROR = 2;
|
||||||
|
public const FILE_NAME_EXISTS_ERROR = 3;
|
||||||
|
public const TEMP_FILE_ERROR = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
@@ -23,20 +41,14 @@ class Media extends Model
|
|||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'title',
|
'title',
|
||||||
'name',
|
|
||||||
'mime',
|
|
||||||
'user_id',
|
'user_id',
|
||||||
|
'mime_type',
|
||||||
|
'permission',
|
||||||
|
'storage',
|
||||||
|
'description',
|
||||||
|
'name',
|
||||||
'size',
|
'size',
|
||||||
'permission'
|
'status',
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that are hidden.
|
|
||||||
*
|
|
||||||
* @var array<int, string>
|
|
||||||
*/
|
|
||||||
protected $hidden = [
|
|
||||||
'path',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,13 +60,48 @@ class Media extends Model
|
|||||||
'url',
|
'url',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default attributes.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'storage' => 'cdn',
|
||||||
|
'variants' => '[]',
|
||||||
|
'description' => '',
|
||||||
|
'dimensions' => '',
|
||||||
|
'permission' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The storage file list cache.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $storageFileListCache = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The variant types.
|
||||||
|
*
|
||||||
|
* @var int[][][]
|
||||||
|
*/
|
||||||
|
protected static $variantTypes = [
|
||||||
|
'image' => [
|
||||||
|
'thumb' => ['width' => 150, 'height' => 150],
|
||||||
|
'small' => ['width' => 300, 'height' => 225],
|
||||||
|
'medium' => ['width' => 768, 'height' => 576],
|
||||||
|
'large' => ['width' => 1024, 'height' => 768],
|
||||||
|
'xlarge' => ['width' => 1536, 'height' => 1152],
|
||||||
|
'xxlarge' => ['width' => 2048, 'height' => 1536],
|
||||||
|
'scaled' => ['width' => 2560, 'height' => 1920]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model Boot
|
* Model Boot
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected static function boot()
|
protected static function boot(): void
|
||||||
{
|
{
|
||||||
parent::boot();
|
parent::boot();
|
||||||
|
|
||||||
@@ -63,153 +110,379 @@ class Media extends Model
|
|||||||
$origPermission = $media->getOriginal()['permission'];
|
$origPermission = $media->getOriginal()['permission'];
|
||||||
$newPermission = $media->permission;
|
$newPermission = $media->permission;
|
||||||
|
|
||||||
$origPath = Storage::disk(Media::getStorageId(empty($origPermission)))->path($media->name);
|
$newPermissionLen = strlen($newPermission);
|
||||||
$newPath = Storage::disk(Media::getStorageId(empty($newPermission)))->path($media->name);
|
|
||||||
|
|
||||||
if ($origPath !== $newPath) {
|
if ($newPermissionLen !== strlen($origPermission)) {
|
||||||
if (file_exists($origPath) === true) {
|
if ($newPermissionLen === 0) {
|
||||||
if (file_exists($newPath) === true) {
|
$this->moveToStorage('cdn');
|
||||||
$fileParts = pathinfo($newPath);
|
} else {
|
||||||
$newName = '';
|
$this->moveToStorage('private');
|
||||||
|
}
|
||||||
// need a new name!
|
}
|
||||||
$tmpPath = $newPath;
|
}
|
||||||
while (file_exists($tmpPath) === true) {
|
|
||||||
$newName = uniqid('', true) . $fileParts['extension'];
|
|
||||||
$tmpPath = $fileParts['dirname'] . '/' . $newName;
|
|
||||||
}
|
|
||||||
|
|
||||||
$media->name = $newName;
|
|
||||||
}
|
|
||||||
|
|
||||||
rename($origPath, $newPath);
|
|
||||||
}//end if
|
|
||||||
}//end if
|
|
||||||
}//end if
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static::deleting(function ($media) {
|
||||||
|
$media->deleteFile();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Type Variants.
|
||||||
|
*
|
||||||
|
* @param string $type The variant type to get.
|
||||||
|
* @return array The variant data.
|
||||||
|
*/
|
||||||
|
public static function getTypeVariants(string $type): array
|
||||||
|
{
|
||||||
|
if (isset(self::$variantTypes[$type]) === true) {
|
||||||
|
return self::$variantTypes[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variants Get Mutator.
|
||||||
|
*
|
||||||
|
* @param mixed $value The value to mutate.
|
||||||
|
* @return array The mutated value.
|
||||||
|
*/
|
||||||
|
public function getVariantsAttribute(mixed $value): array
|
||||||
|
{
|
||||||
|
if (is_string($value) === true) {
|
||||||
|
return json_decode($value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variants Set Mutator.
|
||||||
|
*
|
||||||
|
* @param mixed $value The value to mutate.
|
||||||
|
*/
|
||||||
|
public function setVariantsAttribute(mixed $value): void
|
||||||
|
{
|
||||||
|
if (is_array($value) !== true) {
|
||||||
|
$value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->attributes['variants'] = json_encode(($value ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get previous variant.
|
||||||
|
*
|
||||||
|
* @param string $type The variant type.
|
||||||
|
* @param string $variant The initial variant.
|
||||||
|
* @return string The previous variant name (or '').
|
||||||
|
*/
|
||||||
|
public function getPreviousVariant(string $type, string $variant): string
|
||||||
|
{
|
||||||
|
if (isset(self::$variantTypes[$type]) === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$variants = self::$variantTypes[$type];
|
||||||
|
$keys = array_keys($variants);
|
||||||
|
|
||||||
|
$currentIndex = array_search($variant, $keys);
|
||||||
|
if ($currentIndex === false || $currentIndex === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keys[($currentIndex - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next variant.
|
||||||
|
*
|
||||||
|
* @param string $type The variant type.
|
||||||
|
* @param string $variant The initial variant.
|
||||||
|
* @return string The next variant name (or '').
|
||||||
|
*/
|
||||||
|
public function getNextVariant(string $type, string $variant): string
|
||||||
|
{
|
||||||
|
if (isset(self::$variantTypes[$type]) === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$variants = self::$variantTypes[$type];
|
||||||
|
$keys = array_keys($variants);
|
||||||
|
|
||||||
|
$currentIndex = array_search($variant, $keys);
|
||||||
|
if ($currentIndex === false || $currentIndex === (count($keys) - 1)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keys[($currentIndex + 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get variant URL.
|
||||||
|
*
|
||||||
|
* @param string $variant The variant to find.
|
||||||
|
* @param boolean $returnNearest Return the nearest variant if request is not found.
|
||||||
|
* @return string The URL.
|
||||||
|
*/
|
||||||
|
public function getVariantURL(string $variant, bool $returnNearest = true): string
|
||||||
|
{
|
||||||
|
$variants = $this->variants;
|
||||||
|
if (isset($variants[$variant]) === true) {
|
||||||
|
return self::getUrlPath() . $variants[$variant];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($returnNearest === true) {
|
||||||
|
$variantType = explode('/', $this->mime_type)[0];
|
||||||
|
$previousVariant = $variant;
|
||||||
|
while (empty($previousVariant) === false) {
|
||||||
|
$previousVariant = $this->getPreviousVariant($variantType, $previousVariant);
|
||||||
|
if (empty($previousVariant) === false && isset($variants[$previousVariant]) === true) {
|
||||||
|
return self::getUrlPath() . $variants[$previousVariant];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete file and associated files with the modal.
|
||||||
|
*/
|
||||||
|
public function deleteFile(): void
|
||||||
|
{
|
||||||
|
$fileName = $this->name;
|
||||||
|
$baseName = pathinfo($fileName, PATHINFO_FILENAME);
|
||||||
|
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
$files = Storage::disk($this->storage)->files();
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (preg_match("/{$baseName}(-[a-zA-Z0-9]+)?\.{$extension}/", $file) === 1) {
|
||||||
|
Storage::disk($this->storage)->delete($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->invalidateCFCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate Cloudflare Cache.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException Exception.
|
||||||
|
*/
|
||||||
|
private function invalidateCFCache(): void
|
||||||
|
{
|
||||||
|
$zone_id = env("CLOUDFLARE_ZONE_ID");
|
||||||
|
$api_key = env("CLOUDFLARE_API_KEY");
|
||||||
|
if ($zone_id !== null && $api_key !== null && $this->url !== "") {
|
||||||
|
$urls = [$this->url];
|
||||||
|
|
||||||
|
foreach ($this->variants as $variant => $name) {
|
||||||
|
$urls[] = str_replace($this->name, $name, $this->url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_URL => "https://api.cloudflare.com/client/v4/zones/" . $zone_id . "/purge_cache",
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_CUSTOMREQUEST => "DELETE",
|
||||||
|
CURLOPT_POSTFIELDS => json_encode(["files" => $urls]),
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
"Content-Type: application/json",
|
||||||
|
"Authorization: Bearer " . $api_key
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
}//end if
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL path
|
||||||
|
*/
|
||||||
|
public function getUrlPath(): string
|
||||||
|
{
|
||||||
|
$url = config("filesystems.disks.$this->storage.url");
|
||||||
|
return "$url/";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the file URL
|
* Return the file URL
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getUrlAttribute()
|
public function getUrlAttribute(): string
|
||||||
{
|
{
|
||||||
$url = config('filesystems.disks.' . Media::getStorageId($this) . '.url');
|
if (isset($this->attributes['name']) === true) {
|
||||||
if (empty($url) === false) {
|
return self::getUrlPath() . $this->name;
|
||||||
$replace = [
|
}
|
||||||
'id' => $this->id,
|
|
||||||
'name' => $this->name
|
|
||||||
];
|
|
||||||
|
|
||||||
$url = str_ireplace(array_map(function ($item) {
|
|
||||||
return '%' . $item . '%';
|
|
||||||
}, array_keys($replace)), array_values($replace), $url);
|
|
||||||
|
|
||||||
return $url;
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the file owner
|
* Return the file owner
|
||||||
*
|
|
||||||
* @return BelongsTo
|
|
||||||
*/
|
*/
|
||||||
public function user()
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the file full local path
|
* Move files to new storage device.
|
||||||
*
|
*
|
||||||
* @return string
|
* @param string $storage The storage ID to move to.
|
||||||
*/
|
*/
|
||||||
public function path()
|
public function moveToStorage(string $storage): void
|
||||||
{
|
{
|
||||||
return Storage::disk(Media::getStorageId($this))->path($this->name);
|
if ($storage !== $this->storage && Config::has("filesystems.disks.$storage") === true) {
|
||||||
|
$this->status = "Processing media";
|
||||||
|
MoveMediaJob::dispatch($this, $storage)->onQueue('media');
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Storage ID
|
* Create new Media from UploadedFile data.
|
||||||
*
|
*
|
||||||
* @param mixed $mediaOrPublic Media object or if file is public.
|
* @param App\Models\Request $request The request data.
|
||||||
* @return string
|
* @param Illuminate\Http\UploadedFile $file The file.
|
||||||
|
* @return null|Media The result or null if not successful.
|
||||||
*/
|
*/
|
||||||
public static function getStorageId(mixed $mediaOrPublic)
|
public static function createFromUploadedFile(Request $request, UploadedFile $file): ?Media
|
||||||
{
|
{
|
||||||
$isPublic = true;
|
$request->merge([
|
||||||
|
'title' => $request->get('title', ''),
|
||||||
|
'name' => '',
|
||||||
|
'size' => 0,
|
||||||
|
'mime_type' => '',
|
||||||
|
'status' => '',
|
||||||
|
]);
|
||||||
|
|
||||||
if ($mediaOrPublic instanceof Media) {
|
if ($request->get('storage') === null) {
|
||||||
$isPublic = empty($mediaOrPublic->permission);
|
// We store images by default locally
|
||||||
} else {
|
if (strpos($file->getMimeType(), 'image/') === 0) {
|
||||||
$isPublic = boolval($mediaOrPublic);
|
$request->merge([
|
||||||
|
'storage' => 'local',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$request->merge([
|
||||||
|
'storage' => 'cdn',
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $isPublic === true ? 'public' : 'local';
|
$mediaItem = $request->user()->media()->create($request->all());
|
||||||
|
$mediaItem->updateWithUploadedFile($file);
|
||||||
|
|
||||||
|
return $mediaItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Place uploaded file into storage. Return full path or null
|
* Update Media with UploadedFile data.
|
||||||
*
|
*
|
||||||
* @param UploadedFile $file File to put into storage.
|
* @param Illuminate\Http\UploadedFile $file The file.
|
||||||
* @param boolean $public Is the file available to the public.
|
* @return null|Media The media item.
|
||||||
* @return array|null
|
|
||||||
*/
|
*/
|
||||||
public static function store(UploadedFile $file, bool $public = true)
|
public function updateWithUploadedFile(UploadedFile $file): ?Media
|
||||||
{
|
{
|
||||||
$storage = Media::getStorageId($public);
|
if ($file === null || $file->isValid() !== true) {
|
||||||
$name = $file->store('', ['disk' => $storage]);
|
throw new \Exception('The file is invalid.', self::INVALID_FILE_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file->getSize() > static::getMaxUploadSize()) {
|
||||||
|
throw new \Exception('The file size is larger then permitted.', self::FILE_SIZE_EXCEEDED_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = static::generateUniqueFileName($file->getClientOriginalName());
|
||||||
if ($name === false) {
|
if ($name === false) {
|
||||||
return null;
|
throw new \Exception('The file name already exists in storage.', self::FILE_NAME_EXISTS_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = Storage::disk($storage)->path($name);
|
// remove file if there is an existing entry in this medium item
|
||||||
if (in_array($file->getClientOriginalExtension(), ['jpg', 'jpeg', 'png', 'gif']) === true) {
|
if (strlen($this->name) > 0 && strlen($this->storage) > 0) {
|
||||||
// Generate additional image sizes
|
Storage::disk($this->storage)->delete($this->name);
|
||||||
$sizes = [
|
foreach ($this->variants as $variantName => $fileName) {
|
||||||
'thumb' => [150, 150],
|
Storage::disk($this->storage)->delete($fileName);
|
||||||
'small' => [300, 300],
|
|
||||||
'medium' => [640, 640],
|
|
||||||
'large' => [1024, 1024],
|
|
||||||
'xlarge' => [1536, 1536],
|
|
||||||
'xxlarge' => [2560, 2560],
|
|
||||||
];
|
|
||||||
$images = ['full' => $path];
|
|
||||||
foreach ($sizes as $sizeName => $size) {
|
|
||||||
$image = Image::make($path);
|
|
||||||
$image->resize($size[0], $size[1], function ($constraint) {
|
|
||||||
$constraint->aspectRatio();
|
|
||||||
$constraint->upsize();
|
|
||||||
});
|
|
||||||
$newPath = pathinfo($path, PATHINFO_DIRNAME) . '/' . pathinfo($path, PATHINFO_FILENAME) . "-$sizeName." . pathinfo($path, PATHINFO_EXTENSION);
|
|
||||||
$image->save($newPath);
|
|
||||||
$images[$sizeName] = $newPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize all images
|
$this->name = '';
|
||||||
$optimizerChain = OptimizerChainFactory::create();
|
$this->variants = [];
|
||||||
foreach ($images as $imagePath) {
|
}
|
||||||
$optimizerChain->optimize($imagePath);
|
|
||||||
}
|
|
||||||
}//end if
|
|
||||||
|
|
||||||
return [
|
if (strlen($this->title) === 0) {
|
||||||
'name' => $name,
|
$this->title = $name;
|
||||||
'path' => $path
|
}
|
||||||
];
|
|
||||||
|
$this->name = $name;
|
||||||
|
$this->size = $file->getSize();
|
||||||
|
$this->mime_type = $file->getMimeType();
|
||||||
|
$this->status = 'Processing media';
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
$temporaryFilePath = generateTempFilePath();
|
||||||
|
copy($file->path(), $temporaryFilePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
StoreUploadedFileJob::dispatch($this, $temporaryFilePath)->onQueue('media');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->status = 'Error';
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}//end try
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the file from the storage to the user.
|
||||||
|
*
|
||||||
|
* @param string $variant The variant to download or null if none.
|
||||||
|
* @param boolean $fallback Fallback to the original file if the variant is not found.
|
||||||
|
* @return JsonResponse|StreamedResponse The response.
|
||||||
|
* @throws BindingResolutionException The Exception.
|
||||||
|
*/
|
||||||
|
public function download(string $variant = null, bool $fallback = true)
|
||||||
|
{
|
||||||
|
$path = $this->name;
|
||||||
|
if ($variant !== null) {
|
||||||
|
if (array_key_exists($variant, $this->variant) === true) {
|
||||||
|
$path = $this->variant[$variant];
|
||||||
|
} else {
|
||||||
|
return response()->json(['message' => 'The resource was not found.'], HttpResponseCodes::HTTP_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$disk = Storage::disk($this->storage);
|
||||||
|
if ($disk->exists($path) === true) {
|
||||||
|
$stream = $disk->readStream($path);
|
||||||
|
$response = response()->stream(
|
||||||
|
function () use ($stream) {
|
||||||
|
fpassthru($stream);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-Type' => $this->mime_type,
|
||||||
|
'Content-Length' => $disk->size($path),
|
||||||
|
'Content-Disposition' => 'attachment; filename="' . basename($path) . '"',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'The resource was not found.'], HttpResponseCodes::HTTP_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the server maximum upload size
|
* Get the server maximum upload size
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
*/
|
||||||
public static function maxUploadSize()
|
public static function getMaxUploadSize(): int
|
||||||
{
|
{
|
||||||
$sizes = [
|
$sizes = [
|
||||||
ini_get('upload_max_filesize'),
|
ini_get('upload_max_filesize'),
|
||||||
@@ -237,31 +510,122 @@ class Media extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize filename for upload
|
* Generate a file name that is available within storage.
|
||||||
*
|
*
|
||||||
* @param string $filename Filename to sanitize.
|
* @param string $fileName The proposed file name.
|
||||||
* @return string
|
* @return string|boolean The available file name or false if failed.
|
||||||
*/
|
*/
|
||||||
public static function sanitizeFilename(string $filename)
|
public static function generateUniqueFileName(string $fileName)
|
||||||
|
{
|
||||||
|
$index = 1;
|
||||||
|
$maxTries = 100;
|
||||||
|
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||||
|
$fileName = static::sanitizeFilename(pathinfo($fileName, PATHINFO_FILENAME));
|
||||||
|
|
||||||
|
if (static::fileNameHasSuffix($fileName) === true || static::fileExistsInStorage("$fileName.$extension") === true || Media::where('name', "$fileName.$extension")->where('status', 'not like', 'failed%')->exists() === true) {
|
||||||
|
$fileName .= '-';
|
||||||
|
for ($i = 1; $i < $maxTries; $i++) {
|
||||||
|
$fileNameIndex = $fileName . $index;
|
||||||
|
if (static::fileExistsInStorage("$fileNameIndex.$extension") !== true && Media::where('name', "$fileNameIndex.$extension")->where('status', 'not like', 'Failed%')->exists() !== true) {
|
||||||
|
return "$fileNameIndex.$extension";
|
||||||
|
}
|
||||||
|
|
||||||
|
++$index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "$fileName.$extension";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the file name exists in any of the storage disks.
|
||||||
|
*
|
||||||
|
* @param string $fileName The file name to check.
|
||||||
|
* @param boolean $ignoreCache Ignore the file list cache.
|
||||||
|
* @return boolean If the file exists on any storage disks.
|
||||||
|
*/
|
||||||
|
public static function fileExistsInStorage(string $fileName, bool $ignoreCache = false): bool
|
||||||
|
{
|
||||||
|
$disks = array_keys(Config::get('filesystems.disks'));
|
||||||
|
|
||||||
|
if ($ignoreCache === false) {
|
||||||
|
if (count(static::$storageFileListCache) === 0) {
|
||||||
|
$disks = array_keys(Config::get('filesystems.disks'));
|
||||||
|
|
||||||
|
foreach ($disks as $disk) {
|
||||||
|
try {
|
||||||
|
static::$storageFileListCache[$disk] = Storage::disk($disk)->allFiles();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
throw new \Exception("Cannot get a file list for storage device '$disk'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (static::$storageFileListCache as $disk => $files) {
|
||||||
|
if (in_array($fileName, $files) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$disks = array_keys(Config::get('filesystems.disks'));
|
||||||
|
|
||||||
|
foreach ($disks as $disk) {
|
||||||
|
try {
|
||||||
|
if (Storage::disk($disk)->exists($fileName) === true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error($e->getMessage());
|
||||||
|
throw new \Exception("Cannot verify if file '$fileName' already exists in storage device '$disk'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}//end if
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the file name contains a special suffix.
|
||||||
|
*
|
||||||
|
* @param string $fileName The file name to test.
|
||||||
|
* @return boolean If the file name contains the special suffix.
|
||||||
|
*/
|
||||||
|
public static function fileNameHasSuffix(string $fileName): bool
|
||||||
|
{
|
||||||
|
$suffix = '/(-\d+x\d+|-scaled)$/i';
|
||||||
|
$fileNameWithoutExtension = pathinfo($fileName, PATHINFO_FILENAME);
|
||||||
|
|
||||||
|
return preg_match($suffix, $fileNameWithoutExtension) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize fileName for upload
|
||||||
|
*
|
||||||
|
* @param string $fileName Filename to sanitize.
|
||||||
|
*/
|
||||||
|
private static function sanitizeFilename(string $fileName): string
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
# file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
# file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||||
[<>:"/\\\|?*]|
|
[<>:"/\\\|?*]|
|
||||||
|
|
||||||
# control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
# control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||||
[\x00-\x1F]|
|
[\x00-\x1F]|
|
||||||
|
|
||||||
# non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
# non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
||||||
[\x7F\xA0\xAD]|
|
[\x7F\xA0\xAD]|
|
||||||
|
|
||||||
# URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
|
# URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
|
||||||
[#\[\]@!$&\'()+,;=]|
|
[#\[\]@!$&\'()+,;=]|
|
||||||
|
|
||||||
# URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
# URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||||
[{}^\~`]
|
[{}^\~`]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$filename = preg_replace(
|
$fileName = preg_replace(
|
||||||
'~
|
'~
|
||||||
[<>:"/\\\|?*]|
|
[<>:"/\\\|?*]|
|
||||||
[\x00-\x1F]|
|
[\x00-\x1F]|
|
||||||
@@ -270,37 +634,37 @@ class Media extends Model
|
|||||||
[{}^\~`]
|
[{}^\~`]
|
||||||
~x',
|
~x',
|
||||||
'-',
|
'-',
|
||||||
$filename
|
$fileName
|
||||||
);
|
);
|
||||||
|
|
||||||
$filename = ltrim($filename, '.-');
|
$fileName = ltrim($fileName, '.-');
|
||||||
|
|
||||||
$filename = preg_replace([
|
$fileName = preg_replace([
|
||||||
// "file name.zip" becomes "file-name.zip"
|
// "file name.zip" becomes "file-name.zip"
|
||||||
'/ +/',
|
'/ +/',
|
||||||
// "file___name.zip" becomes "file-name.zip"
|
// "file___name.zip" becomes "file-name.zip"
|
||||||
'/_+/',
|
'/_+/',
|
||||||
// "file---name.zip" becomes "file-name.zip"
|
// "file---name.zip" becomes "file-name.zip"
|
||||||
'/-+/'
|
'/-+/'
|
||||||
], '-', $filename);
|
], '-', $fileName);
|
||||||
$filename = preg_replace([
|
$fileName = preg_replace([
|
||||||
// "file--.--.-.--name.zip" becomes "file.name.zip"
|
// "file--.--.-.--name.zip" becomes "file.name.zip"
|
||||||
'/-*\.-*/',
|
'/-*\.-*/',
|
||||||
// "file...name..zip" becomes "file.name.zip"
|
// "file...name..zip" becomes "file.name.zip"
|
||||||
'/\.{2,}/'
|
'/\.{2,}/'
|
||||||
], '.', $filename);
|
], '.', $fileName);
|
||||||
// lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625
|
// lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625
|
||||||
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
|
$fileName = mb_strtolower($fileName, mb_detect_encoding($fileName));
|
||||||
// ".file-name.-" becomes "file-name"
|
// ".file-name.-" becomes "file-name"
|
||||||
$filename = trim($filename, '.-');
|
$fileName = trim($fileName, '.-');
|
||||||
|
|
||||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
$ext = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||||
$filename = mb_strcut(
|
$fileName = mb_strcut(
|
||||||
pathinfo($filename, PATHINFO_FILENAME),
|
pathinfo($fileName, PATHINFO_FILENAME),
|
||||||
0,
|
0,
|
||||||
(255 - ($ext !== '' ? strlen($ext) + 1 : 0)),
|
(255 - ($ext !== '' ? strlen($ext) + 1 : 0)),
|
||||||
mb_detect_encoding($filename)
|
mb_detect_encoding($fileName)
|
||||||
) . ($ext !== '' ? '.' . $ext : '');
|
) . ($ext !== '' ? '.' . $ext : '');
|
||||||
return $filename;
|
return $fileName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,8 @@ class Permission extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the User associated with this model
|
* Get the User associated with this model
|
||||||
*
|
|
||||||
* @return BelongsTo
|
|
||||||
*/
|
*/
|
||||||
public function user()
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|||||||
39
app/Models/Shortlink.php
Normal file
39
app/Models/Shortlink.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enum\HttpResponseCodes;
|
||||||
|
use App\Jobs\MoveMediaJob;
|
||||||
|
use App\Jobs\OptimizeMediaJob;
|
||||||
|
use App\Jobs\StoreUploadedFileJob;
|
||||||
|
use App\Traits\Uuids;
|
||||||
|
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
|
||||||
|
class Shortlink extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'code',
|
||||||
|
'url',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -6,6 +6,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\Relations\BelongsToMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
@@ -25,12 +26,12 @@ class User extends Authenticatable implements Auditable
|
|||||||
* @var array<int, string>
|
* @var array<int, string>
|
||||||
*/
|
*/
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'username',
|
|
||||||
'first_name',
|
'first_name',
|
||||||
'last_name',
|
'last_name',
|
||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'password',
|
'password',
|
||||||
|
'display_name',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,28 +67,28 @@ class User extends Authenticatable implements Auditable
|
|||||||
'permissions'
|
'permissions'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
// public function getPermissionsAttribute() {
|
* The default attributes.
|
||||||
// return $this->permissions()->pluck('permission')->toArray();
|
*
|
||||||
// }
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $attributes = [
|
||||||
|
'phone' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of files of the user
|
* Get the list of files of the user
|
||||||
*
|
|
||||||
* @return HasMany
|
|
||||||
*/
|
*/
|
||||||
public function permissions()
|
public function permissions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Permission::class);
|
return $this->hasMany(Permission::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the permission attribute
|
* Get the permission attribute
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getPermissionsAttribute()
|
public function getPermissionsAttribute(): array
|
||||||
{
|
{
|
||||||
return $this->permissions()->pluck('permission')->toArray();
|
return $this->permissions()->pluck('permission')->toArray();
|
||||||
}
|
}
|
||||||
@@ -96,9 +97,8 @@ class User extends Authenticatable implements Auditable
|
|||||||
* 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)
|
public function hasPermission(string $permission): bool
|
||||||
{
|
{
|
||||||
return ($this->permissions()->where('permission', $permission)->first() !== null);
|
return ($this->permissions()->where('permission', $permission)->first() !== null);
|
||||||
}
|
}
|
||||||
@@ -107,11 +107,10 @@ class User extends Authenticatable implements Auditable
|
|||||||
* 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 Collection
|
|
||||||
*/
|
*/
|
||||||
public function givePermission($permissions)
|
public function givePermission($permissions): Collection
|
||||||
{
|
{
|
||||||
if (!is_array($permissions)) {
|
if (is_array($permissions) === false) {
|
||||||
$permissions = [$permissions];
|
$permissions = [$permissions];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,11 +131,10 @@ 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 int
|
|
||||||
*/
|
*/
|
||||||
public function revokePermission($permissions)
|
public function revokePermission($permissions): int
|
||||||
{
|
{
|
||||||
if (!is_array($permissions)) {
|
if (is_array($permissions) === false) {
|
||||||
$permissions = [$permissions];
|
$permissions = [$permissions];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,41 +145,41 @@ 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()
|
public function media(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Media::class);
|
return $this->hasMany(Media::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of files of the user
|
* Get the list of files of the user
|
||||||
*
|
|
||||||
* @return HasMany
|
|
||||||
*/
|
*/
|
||||||
public function posts()
|
public function articles(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Post::class);
|
return $this->hasMany(Article::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get associated user codes
|
* Get associated user codes
|
||||||
*
|
|
||||||
* @return HasMany
|
|
||||||
*/
|
*/
|
||||||
public function codes()
|
public function codes(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(UserCode::class);
|
return $this->hasMany(UserCode::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of logins of the user
|
* Get the list of logins of the user
|
||||||
*
|
|
||||||
* @return HasMany
|
|
||||||
*/
|
*/
|
||||||
public function logins()
|
public function logins(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(UserLogins::class);
|
return $this->hasMany(UserLogins::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the events associated with the user.
|
||||||
|
*/
|
||||||
|
public function events(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Event::class, 'event_user', 'user_id', 'event_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Boot function from Laravel.
|
* Boot function from Laravel.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected static function boot()
|
protected static function boot(): void
|
||||||
{
|
{
|
||||||
parent::boot();
|
parent::boot();
|
||||||
static::creating(function ($model) {
|
static::creating(function ($model) {
|
||||||
@@ -46,10 +44,8 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate new code
|
* Generate new code
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function regenerate()
|
public function regenerate(): void
|
||||||
{
|
{
|
||||||
while (true) {
|
while (true) {
|
||||||
$code = random_int(100000, 999999);
|
$code = random_int(100000, 999999);
|
||||||
@@ -62,20 +58,16 @@ class UserCode extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear expired user codes
|
* Clear expired user codes
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public static function clearExpired()
|
public static function clearExpired(): void
|
||||||
{
|
{
|
||||||
UserCode::where('updated_at', '<=', now()->subDays(5))->delete();
|
UserCode::where('updated_at', '<=', now()->subDays(5))->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get associated user
|
* Get associated user
|
||||||
*
|
|
||||||
* @return BelongsTo
|
|
||||||
*/
|
*/
|
||||||
public function user()
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ class UserLogins extends Model
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the file user
|
* Get the file user
|
||||||
*
|
|
||||||
* @return BelongsTo
|
|
||||||
*/
|
*/
|
||||||
public function user()
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,30 +2,34 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Rules\RequiredIfAny;
|
||||||
|
use App\Rules\Uniqueish;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use PDOException;
|
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()
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
Storage::macro('public', function ($diskName) {
|
||||||
|
$public = config("filesystems.disks.{$diskName}.public", false);
|
||||||
|
return $public;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,9 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any authentication / authorization services.
|
* Register any authentication / authorization services.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
$this->registerPolicies();
|
|
||||||
|
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ class BroadcastServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Bootstrap any application services.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Broadcast::routes();
|
Broadcast::routes();
|
||||||
|
|
||||||
|
|||||||
@@ -26,35 +26,16 @@ class EventServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register any events for your application.
|
* Register any events for your application.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function boot()
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Queue::after(function (JobProcessed $event) {
|
//
|
||||||
// Log::info($event->connectionName);
|
|
||||||
// Log::info('ID: ' . $event->job->getJobId());
|
|
||||||
// Log::info('Attempts: ' . $event->job->attempts());
|
|
||||||
// Log::info('Name: ' . $event->job->getName());
|
|
||||||
// Log::info('ResolveNAme: ' . $event->job->resolveName());
|
|
||||||
// Log::info('Queue: ' . $event->job->getQueue());
|
|
||||||
// Log::info('Body: ' . $event->job->getRawBody());
|
|
||||||
// Log::info(print_r($event->job->payload(), true));
|
|
||||||
|
|
||||||
// $payload = $event->job->payload();
|
|
||||||
// $data = unserialize($payload['data']['command']);
|
|
||||||
|
|
||||||
// Log::info('MAIL: ' . $data->to);
|
|
||||||
// Log::info('MAIL: ' . get_class($data->mailable));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if events and listeners should be automatically discovered.
|
* Determine if events and listeners should be automatically discovered.
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
*/
|
||||||
public function shouldDiscoverEvents()
|
public function shouldDiscoverEvents(): bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvi
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class RouteServiceProvider extends ServiceProvider
|
class RouteServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -22,29 +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()
|
public function boot(): void
|
||||||
{
|
|
||||||
$this->configureRateLimiting();
|
|
||||||
|
|
||||||
$this->routes(function () {
|
|
||||||
Route::middleware('api')
|
|
||||||
->prefix('api')
|
|
||||||
->group(base_path('routes/api.php'));
|
|
||||||
|
|
||||||
Route::middleware('web')
|
|
||||||
->group(base_path('routes/web.php'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure the rate limiters for the application.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function configureRateLimiting()
|
|
||||||
{
|
{
|
||||||
// RateLimiter::for('api', function (Request $request) {
|
// RateLimiter::for('api', function (Request $request) {
|
||||||
// return Limit::perMinute(60)->by($request->user()?->id !== null ?: $request->ip());
|
// return Limit::perMinute(60)->by($request->user()?->id !== null ?: $request->ip());
|
||||||
@@ -69,5 +49,30 @@ class RouteServiceProvider extends ServiceProvider
|
|||||||
return Limit::none();
|
return Limit::none();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->routes(function () {
|
||||||
|
Route::middleware('api')
|
||||||
|
->prefix('api')
|
||||||
|
->group(base_path('routes/api.php'));
|
||||||
|
|
||||||
|
Route::middleware('web')
|
||||||
|
->group(base_path('routes/web.php'));
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::macro('apiAttachmentResource', function ($uri, $controller) {
|
||||||
|
$singularUri = Str::singular($uri);
|
||||||
|
|
||||||
|
Route::get("$uri/{{$singularUri}}/attachments", [$controller, 'getAttachments'])
|
||||||
|
->name("{{$singularUri}}.attachments.index");
|
||||||
|
|
||||||
|
Route::post("$uri/{{$singularUri}}/attachments", [$controller, 'storeAttachment'])
|
||||||
|
->name("{{$singularUri}}.attachments.store");
|
||||||
|
|
||||||
|
Route::match(['put', 'patch'], "$uri/{{$singularUri}}/attachments", [$controller, 'updateAttachments'])
|
||||||
|
->name("{{$singularUri}}.attachments.update");
|
||||||
|
|
||||||
|
Route::delete("$uri/{{$singularUri}}/attachments/{medium}", [$controller, 'deleteAttachment'])
|
||||||
|
->name("{{$singularUri}}.attachments.destroy");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ 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)
|
public function passes(mixed $attribute, mixed $value): bool
|
||||||
{
|
{
|
||||||
$endpoint = config('services.google_recaptcha');
|
$endpoint = config('services.google_recaptcha');
|
||||||
|
|
||||||
@@ -42,10 +41,8 @@ class Recaptcha implements Rule
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the validation error message.
|
* Get the validation error message.
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function message()
|
public function message(): string
|
||||||
{
|
{
|
||||||
return 'Captcha failed. Refresh the page and try again';
|
return 'Captcha failed. Refresh the page and try again';
|
||||||
}
|
}
|
||||||
|
|||||||
37
app/Rules/UniqueFileName.php
Normal file
37
app/Rules/UniqueFileName.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use App\Models\Media;
|
||||||
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
|
||||||
|
class UniqueFileName implements Rule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new rule instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the validation rule passes.
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
*/
|
||||||
|
public function passes(string $attribute, $value): bool
|
||||||
|
{
|
||||||
|
return (Media::fileExists($value) === false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation error message.
|
||||||
|
*/
|
||||||
|
public function message(): string
|
||||||
|
{
|
||||||
|
return 'The file name already exists.';
|
||||||
|
}
|
||||||
|
}
|
||||||
103
app/Rules/Uniqueish.php
Normal file
103
app/Rules/Uniqueish.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class Uniqueish implements Rule
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The table name to compare.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The column name to compare.
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $column;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the record to be ignored.
|
||||||
|
*
|
||||||
|
* @var mixed
|
||||||
|
*/
|
||||||
|
protected $ignoreId;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new rule instance.
|
||||||
|
*
|
||||||
|
* @param string $table The table name to compare.
|
||||||
|
* @param string $column The column name to compare.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(string $table, string $column = null)
|
||||||
|
{
|
||||||
|
$this->table = $table;
|
||||||
|
$this->column = $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ID of the record to be ignored.
|
||||||
|
*
|
||||||
|
* @param mixed $id The ID to ignore.
|
||||||
|
*/
|
||||||
|
public function ignore(mixed $id): static
|
||||||
|
{
|
||||||
|
$this->ignoreId = $id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the validation rule passes.
|
||||||
|
*
|
||||||
|
* @param mixed $attribute Not used.
|
||||||
|
* @param mixed $value The value to compare.
|
||||||
|
*/
|
||||||
|
public function passes(mixed $attribute, mixed $value): bool
|
||||||
|
{
|
||||||
|
$columnName = ($this->column ?? $attribute);
|
||||||
|
$similarValue = preg_replace('/[^A-Za-z]/', '', strtolower($value));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = DB::table($this->table)
|
||||||
|
->where($columnName, 'like', '%' . $similarValue . '%');
|
||||||
|
|
||||||
|
if ($this->ignoreId !== null) {
|
||||||
|
$query->where('id', '<>', $this->ignoreId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->whereRaw('LOWER(REGEXP_REPLACE(' . $columnName . ', \'[^A-Za-z0-9]\', \'\')) = ?', [$similarValue]);
|
||||||
|
$result = $query->first();
|
||||||
|
} catch (\Illuminate\Database\QueryException $e) {
|
||||||
|
// Fall back to performing the regex replace in PHP
|
||||||
|
// $results = $query->get();
|
||||||
|
$query = DB::table($this->table);
|
||||||
|
$results = $query->get();
|
||||||
|
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$resultValue = preg_replace('/[^A-Za-z0-9]/', '', strtolower($result->{$columnName}));
|
||||||
|
if ($resultValue === $similarValue && $result->id != $this->ignoreId) {
|
||||||
|
return false; // Value already exists in the table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true; // Value does not exist in the table
|
||||||
|
}//end try
|
||||||
|
|
||||||
|
return $result === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation error message.
|
||||||
|
*/
|
||||||
|
public function message(): string
|
||||||
|
{
|
||||||
|
return 'The :attribute is similar to one that already exists. Please choose another.';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ class AnimatedGifService
|
|||||||
* @param integer $dataSize GIF blob size.
|
* @param integer $dataSize GIF blob size.
|
||||||
* @return boolean GIF file/blob is animated.
|
* @return boolean GIF file/blob is animated.
|
||||||
*/
|
*/
|
||||||
public static function isAnimatedGif(string $filenameOrBlob, int $dataSize = 0)
|
public static function isAnimatedGif(string $filenameOrBlob, int $dataSize = 0): bool
|
||||||
{
|
{
|
||||||
$regex = '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s';
|
$regex = '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s';
|
||||||
$count = 0;
|
$count = 0;
|
||||||
@@ -41,10 +41,8 @@ class AnimatedGifService
|
|||||||
* @param string $filenameOrBlob GIF filename path
|
* @param string $filenameOrBlob GIF filename path
|
||||||
* @param integer $dataSize GIF blob size.
|
* @param integer $dataSize GIF blob size.
|
||||||
* @param boolean $originalFrames Get original frames (with transparent background)
|
* @param boolean $originalFrames Get original frames (with transparent background)
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function extract(string $filenameOrBlob, int $dataSize = 0, $originalFrames = false)
|
public function extract(string $filenameOrBlob, int $dataSize = 0, bool $originalFrames = false): array
|
||||||
{
|
{
|
||||||
if (self::isAnimatedGif($filenameOrBlob) === false) {
|
if (self::isAnimatedGif($filenameOrBlob) === false) {
|
||||||
return [];
|
return [];
|
||||||
@@ -198,7 +196,7 @@ class GifFrameExtractor
|
|||||||
*
|
*
|
||||||
* @param string $filename GIF filename path
|
* @param string $filename GIF filename path
|
||||||
*/
|
*/
|
||||||
private function parseFramesInfo($filename)
|
private function parseFramesInfo(string $filename)
|
||||||
{
|
{
|
||||||
$this->openFile($filename);
|
$this->openFile($filename);
|
||||||
$this->parseGifHeader();
|
$this->parseGifHeader();
|
||||||
@@ -275,10 +273,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the graphic extension of the frames (old: get_graphics_extension)
|
* Parse the graphic extension of the frames (old: get_graphics_extension)
|
||||||
*
|
|
||||||
* @param integer $type
|
|
||||||
*/
|
*/
|
||||||
private function parseGraphicsExtension($type)
|
private function parseGraphicsExtension(int $type)
|
||||||
{
|
{
|
||||||
$startdata = $this->readByte(2);
|
$startdata = $this->readByte(2);
|
||||||
|
|
||||||
@@ -303,10 +299,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the full frame string block (old: get_image_block)
|
* Get the full frame string block (old: get_image_block)
|
||||||
*
|
|
||||||
* @param integer $type
|
|
||||||
*/
|
*/
|
||||||
private function getFrameString($type)
|
private function getFrameString(int $type)
|
||||||
{
|
{
|
||||||
if ($this->checkByte(0x2c)) {
|
if ($this->checkByte(0x2c)) {
|
||||||
$start = $this->pointer;
|
$start = $this->pointer;
|
||||||
@@ -400,14 +394,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the image data byte (old: get_imagedata_byte)
|
* Get the image data byte (old: get_imagedata_byte)
|
||||||
*
|
|
||||||
* @param string $type
|
|
||||||
* @param integer $start
|
|
||||||
* @param integer $length
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
private function getImageDataByte($type, $start, $length)
|
private function getImageDataByte(string $type, int $start, int $length): string
|
||||||
{
|
{
|
||||||
if ($type == "ext") {
|
if ($type == "ext") {
|
||||||
return substr($this->frameSources[$this->frameNumber]["graphicsextension"], $start, $length);
|
return substr($this->frameSources[$this->frameNumber]["graphicsextension"], $start, $length);
|
||||||
@@ -419,15 +407,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the image data bit (old: get_imagedata_bit)
|
* Get the image data bit (old: get_imagedata_bit)
|
||||||
*
|
|
||||||
* @param string $type
|
|
||||||
* @param integer $byteIndex
|
|
||||||
* @param integer $bitStart
|
|
||||||
* @param integer $bitLength
|
|
||||||
*
|
|
||||||
* @return number
|
|
||||||
*/
|
*/
|
||||||
private function getImageDataBit($type, $byteIndex, $bitStart, $bitLength)
|
private function getImageDataBit(string $type, int $byteIndex, int $bitStart, int $bitLength): number
|
||||||
{
|
{
|
||||||
if ($type == "ext") {
|
if ($type == "ext") {
|
||||||
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["graphicsextension"], $byteIndex, 1)), $bitStart, $bitLength);
|
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["graphicsextension"], $byteIndex, 1)), $bitStart, $bitLength);
|
||||||
@@ -439,12 +420,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the value of 2 ASCII chars (old: dualbyteval)
|
* Return the value of 2 ASCII chars (old: dualbyteval)
|
||||||
*
|
|
||||||
* @param string $s
|
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
*/
|
||||||
private function dualByteVal($s)
|
private function dualByteVal(string $s): int
|
||||||
{
|
{
|
||||||
$i = (ord($s[1]) * 256 + ord($s[0]));
|
$i = (ord($s[1]) * 256 + ord($s[0]));
|
||||||
|
|
||||||
@@ -453,10 +430,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the data stream (old: read_data_stream)
|
* Read the data stream (old: read_data_stream)
|
||||||
*
|
|
||||||
* @param integer $firstLength
|
|
||||||
*/
|
*/
|
||||||
private function readDataStream($firstLength)
|
private function readDataStream(int $firstLength)
|
||||||
{
|
{
|
||||||
$this->pointerForward($firstLength);
|
$this->pointerForward($firstLength);
|
||||||
$length = $this->readByteInt();
|
$length = $this->readByteInt();
|
||||||
@@ -471,10 +446,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the gif file (old: loadfile)
|
* Open the gif file (old: loadfile)
|
||||||
*
|
|
||||||
* @param string $filename
|
|
||||||
*/
|
*/
|
||||||
private function openFile($filename)
|
private function openFile(string $filename)
|
||||||
{
|
{
|
||||||
$this->handle = fopen($filename, "rb");
|
$this->handle = fopen($filename, "rb");
|
||||||
$this->pointer = 0;
|
$this->pointer = 0;
|
||||||
@@ -495,12 +468,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the file from the beginning to $byteCount in binary (old: readbyte)
|
* Read the file from the beginning to $byteCount in binary (old: readbyte)
|
||||||
*
|
|
||||||
* @param integer $byteCount
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
private function readByte($byteCount)
|
private function readByte(int $byteCount): string
|
||||||
{
|
{
|
||||||
$data = fread($this->handle, $byteCount);
|
$data = fread($this->handle, $byteCount);
|
||||||
$this->pointer += $byteCount;
|
$this->pointer += $byteCount;
|
||||||
@@ -510,10 +479,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a byte and return ASCII value (old: readbyte_int)
|
* Read a byte and return ASCII value (old: readbyte_int)
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
*/
|
||||||
private function readByteInt()
|
private function readByteInt(): int
|
||||||
{
|
{
|
||||||
$data = fread($this->handle, 1);
|
$data = fread($this->handle, 1);
|
||||||
$this->pointer++;
|
$this->pointer++;
|
||||||
@@ -523,14 +490,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a $byte to decimal (old: readbits)
|
* Convert a $byte to decimal (old: readbits)
|
||||||
*
|
|
||||||
* @param string $byte
|
|
||||||
* @param integer $start
|
|
||||||
* @param integer $length
|
|
||||||
*
|
|
||||||
* @return number
|
|
||||||
*/
|
*/
|
||||||
private function readBits($byte, $start, $length)
|
private function readBits(string $byte, int $start, int $length): number
|
||||||
{
|
{
|
||||||
$bin = str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
|
$bin = str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
|
||||||
$data = substr($bin, $start, $length);
|
$data = substr($bin, $start, $length);
|
||||||
@@ -540,10 +501,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewind the file pointer reader (old: p_rewind)
|
* Rewind the file pointer reader (old: p_rewind)
|
||||||
*
|
|
||||||
* @param integer $length
|
|
||||||
*/
|
*/
|
||||||
private function pointerRewind($length)
|
private function pointerRewind(int $length)
|
||||||
{
|
{
|
||||||
$this->pointer -= $length;
|
$this->pointer -= $length;
|
||||||
fseek($this->handle, $this->pointer);
|
fseek($this->handle, $this->pointer);
|
||||||
@@ -551,10 +510,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Forward the file pointer reader (old: p_forward)
|
* Forward the file pointer reader (old: p_forward)
|
||||||
*
|
|
||||||
* @param integer $length
|
|
||||||
*/
|
*/
|
||||||
private function pointerForward($length)
|
private function pointerForward(int $length)
|
||||||
{
|
{
|
||||||
$this->pointer += $length;
|
$this->pointer += $length;
|
||||||
fseek($this->handle, $this->pointer);
|
fseek($this->handle, $this->pointer);
|
||||||
@@ -562,13 +519,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a section of the data from $start to $start + $length (old: datapart)
|
* Get a section of the data from $start to $start + $length (old: datapart)
|
||||||
*
|
|
||||||
* @param integer $start
|
|
||||||
* @param integer $length
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
private function dataPart($start, $length)
|
private function dataPart(int $start, int $length): string
|
||||||
{
|
{
|
||||||
fseek($this->handle, $start);
|
fseek($this->handle, $start);
|
||||||
$data = fread($this->handle, $length);
|
$data = fread($this->handle, $length);
|
||||||
@@ -579,12 +531,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a character if a byte (old: checkbyte)
|
* Check if a character if a byte (old: checkbyte)
|
||||||
*
|
|
||||||
* @param integer $byte
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
*/
|
||||||
private function checkByte($byte)
|
private function checkByte(int $byte): bool
|
||||||
{
|
{
|
||||||
if (fgetc($this->handle) == chr($byte)) {
|
if (fgetc($this->handle) == chr($byte)) {
|
||||||
fseek($this->handle, $this->pointer);
|
fseek($this->handle, $this->pointer);
|
||||||
@@ -598,10 +546,8 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the end of the file (old: checkEOF)
|
* Check the end of the file (old: checkEOF)
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
*/
|
||||||
private function checkEOF()
|
private function checkEOF(): bool
|
||||||
{
|
{
|
||||||
if (fgetc($this->handle) === false) {
|
if (fgetc($this->handle) === false) {
|
||||||
return true;
|
return true;
|
||||||
@@ -628,70 +574,56 @@ class GifFrameExtractor
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the total of all added frame duration
|
* Get the total of all added frame duration
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
*/
|
||||||
public function getTotalDuration()
|
public function getTotalDuration(): int
|
||||||
{
|
{
|
||||||
return $this->totalDuration;
|
return $this->totalDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of extracted frames
|
* Get the number of extracted frames
|
||||||
*
|
|
||||||
* @return integer
|
|
||||||
*/
|
*/
|
||||||
public function getFrameNumber()
|
public function getFrameNumber(): int
|
||||||
{
|
{
|
||||||
return $this->frameNumber;
|
return $this->frameNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extracted frames (images and durations)
|
* Get the extracted frames (images and durations)
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getFrames()
|
public function getFrames(): array
|
||||||
{
|
{
|
||||||
return $this->frames;
|
return $this->frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extracted frame positions
|
* Get the extracted frame positions
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getFramePositions()
|
public function getFramePositions(): array
|
||||||
{
|
{
|
||||||
return $this->framePositions;
|
return $this->framePositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extracted frame dimensions
|
* Get the extracted frame dimensions
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getFrameDimensions()
|
public function getFrameDimensions(): array
|
||||||
{
|
{
|
||||||
return $this->frameDimensions;
|
return $this->frameDimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extracted frame images
|
* Get the extracted frame images
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getFrameImages()
|
public function getFrameImages(): array
|
||||||
{
|
{
|
||||||
return $this->frameImages;
|
return $this->frameImages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extracted frame durations
|
* Get the extracted frame durations
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function getFrameDurations()
|
public function getFrameDurations(): array
|
||||||
{
|
{
|
||||||
return $this->frameDurations;
|
return $this->frameDurations;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ trait Uuids
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Boot function from Laravel.
|
* Boot function from Laravel.
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
protected static function bootUuids()
|
protected static function bootUuids(): void
|
||||||
{
|
{
|
||||||
static::creating(function ($model) {
|
static::creating(function ($model) {
|
||||||
if (empty($model->{$model->getKeyName()}) === true) {
|
if (empty($model->{$model->getKeyName()}) === true) {
|
||||||
@@ -22,20 +20,16 @@ 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()
|
public function getIncrementing(): bool
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the auto-incrementing key type.
|
* Get the auto-incrementing key type.
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function getKeyType()
|
public function getKeyType(): string
|
||||||
{
|
{
|
||||||
return 'string';
|
return 'string';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,30 +8,33 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0.2",
|
"php": "^8.1",
|
||||||
"doctrine/dbal": "^3.5",
|
"doctrine/dbal": "^3.5",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
"intervention/image": "^2.7",
|
"intervention/image": "^2.7",
|
||||||
"laravel/framework": "^9.19",
|
"laravel/framework": "^10.12",
|
||||||
"laravel/sanctum": "^3.0",
|
"laravel/sanctum": "^3.2",
|
||||||
"laravel/tinker": "^2.7",
|
"laravel/tinker": "^2.8",
|
||||||
"owen-it/laravel-auditing": "^13.0",
|
"league/flysystem-aws-s3-v3": "^3.12",
|
||||||
|
"owen-it/laravel-auditing": "^13.1",
|
||||||
"php-ffmpeg/php-ffmpeg": "^1.1",
|
"php-ffmpeg/php-ffmpeg": "^1.1",
|
||||||
"spatie/image-optimizer": "^1.6",
|
"sunspikes/clamav-validator": "*",
|
||||||
"thiagoalessio/tesseract_ocr": "^2.12"
|
"thiagoalessio/tesseract_ocr": "^2.12",
|
||||||
|
"vlucas/phpdotenv": "^5.5"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.9.1",
|
"fakerphp/faker": "^1.9.1",
|
||||||
"laravel/pint": "^1.0",
|
"laravel/pint": "^1.0",
|
||||||
"laravel/sail": "^1.0.1",
|
"laravel/sail": "^1.18",
|
||||||
"mockery/mockery": "^1.4.4",
|
"mockery/mockery": "^1.4.4",
|
||||||
"nunomaduro/collision": "^6.1",
|
"nunomaduro/collision": "^7.1",
|
||||||
"phpunit/phpunit": "^9.5.10",
|
"phpunit/phpunit": "^10.1.3",
|
||||||
"spatie/laravel-ignition": "^1.0"
|
"spatie/laravel-ignition": "^2.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"files": [
|
"files": [
|
||||||
"app/Helpers/Array.php"
|
"app/Helpers/Array.php",
|
||||||
|
"app/Helpers/Temp.php"
|
||||||
],
|
],
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
@@ -74,6 +77,6 @@
|
|||||||
"pestphp/pest-plugin": true
|
"pestphp/pest-plugin": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
|||||||
2046
composer.lock
generated
2046
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Support\Facades\Facade;
|
use Illuminate\Support\Facades\Facade;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -154,34 +155,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'providers' => [
|
'providers' => ServiceProvider::defaultProviders()->merge([
|
||||||
|
|
||||||
/*
|
|
||||||
* Laravel Framework Service Providers...
|
|
||||||
*/
|
|
||||||
Illuminate\Auth\AuthServiceProvider::class,
|
|
||||||
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
|
||||||
Illuminate\Bus\BusServiceProvider::class,
|
|
||||||
Illuminate\Cache\CacheServiceProvider::class,
|
|
||||||
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
|
||||||
Illuminate\Cookie\CookieServiceProvider::class,
|
|
||||||
Illuminate\Database\DatabaseServiceProvider::class,
|
|
||||||
Illuminate\Encryption\EncryptionServiceProvider::class,
|
|
||||||
Illuminate\Filesystem\FilesystemServiceProvider::class,
|
|
||||||
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
|
||||||
Illuminate\Hashing\HashServiceProvider::class,
|
|
||||||
Illuminate\Mail\MailServiceProvider::class,
|
|
||||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
|
||||||
Illuminate\Pagination\PaginationServiceProvider::class,
|
|
||||||
Illuminate\Pipeline\PipelineServiceProvider::class,
|
|
||||||
Illuminate\Queue\QueueServiceProvider::class,
|
|
||||||
Illuminate\Redis\RedisServiceProvider::class,
|
|
||||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
|
||||||
Illuminate\Session\SessionServiceProvider::class,
|
|
||||||
Illuminate\Translation\TranslationServiceProvider::class,
|
|
||||||
Illuminate\Validation\ValidationServiceProvider::class,
|
|
||||||
Illuminate\View\ViewServiceProvider::class,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Package Service Providers...
|
* Package Service Providers...
|
||||||
*/
|
*/
|
||||||
@@ -196,8 +170,7 @@ return [
|
|||||||
// App\Providers\BroadcastServiceProvider::class,
|
// App\Providers\BroadcastServiceProvider::class,
|
||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
])->toArray(),
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -80,16 +80,20 @@ return [
|
|||||||
| than one user table or model in the application and you want to have
|
| than one user table or model in the application and you want to have
|
||||||
| separate password reset settings based on the specific user types.
|
| separate password reset settings based on the specific user types.
|
||||||
|
|
|
|
||||||
| The expire time is the number of minutes that each reset token will be
|
| The expiry time is the number of minutes that each reset token will be
|
||||||
| considered valid. This security feature keeps tokens short-lived so
|
| considered valid. This security feature keeps tokens short-lived so
|
||||||
| they have less time to be guessed. You may change this as needed.
|
| they have less time to be guessed. You may change this as needed.
|
||||||
|
|
|
|
||||||
|
| The throttle setting is the number of seconds a user must wait before
|
||||||
|
| generating more password reset tokens. This prevents the user from
|
||||||
|
| quickly generating a very large amount of password reset tokens.
|
||||||
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'passwords' => [
|
'passwords' => [
|
||||||
'users' => [
|
'users' => [
|
||||||
'provider' => 'users',
|
'provider' => 'users',
|
||||||
'table' => 'password_resets',
|
'table' => 'password_reset_tokens',
|
||||||
'expire' => 60,
|
'expire' => 60,
|
||||||
'throttle' => 60,
|
'throttle' => 60,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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') === true ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com',
|
'host' => env('PUSHER_HOST') ?: '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,
|
||||||
|
|||||||
68
config/clamav.php
Normal file
68
config/clamav.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Preferred socket
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This option controls the socket which is used, which is unix_socket or tcp_socket.
|
||||||
|
|
|
||||||
|
| Please note if the unix_socket is used and the socket-file is not found the tcp socket will be
|
||||||
|
| used as fallback.
|
||||||
|
*/
|
||||||
|
'preferred_socket' => env('CLAMAV_PREFERRED_SOCKET', 'unix_socket'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Unix Socket
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| This option defines the location to the unix socket-file. For example
|
||||||
|
| /var/run/clamav/clamd.ctl
|
||||||
|
*/
|
||||||
|
'unix_socket' => env('CLAMAV_UNIX_SOCKET', '/var/run/clamav/clamd.ctl'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| TCP Socket
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| This option defines the TCP socket to the ClamAV instance.
|
||||||
|
*/
|
||||||
|
'tcp_socket' => env('CLAMAV_TCP_SOCKET', 'tcp://127.0.0.1:3310'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 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.
|
||||||
|
*/
|
||||||
|
'socket_connect_timeout' => env('CLAMAV_SOCKET_CONNECT_TIMEOUT', null),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Socket read timeout
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| This option defines the maximum time to wait in seconds for a read.
|
||||||
|
*/
|
||||||
|
'socket_read_timeout' => env('CLAMAV_SOCKET_READ_TIMEOUT', 30),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Throw exceptions instead of returning failures when scan fails.
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| This makes it easier for a developer to find the source of a clamav
|
||||||
|
| failure, but an end user may only see a 500 error for the user
|
||||||
|
| if exceptions are not displayed.
|
||||||
|
*/
|
||||||
|
'client_exceptions' => env('CLAMAV_CLIENT_EXCEPTIONS', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Skip validation
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| This skips the virus validation for current environment.
|
||||||
|
|
|
||||||
|
| Please note when true it won't connect to ClamAV and will skip the virus validation.
|
||||||
|
*/
|
||||||
|
'skip_validation' => env('CLAMAV_SKIP_VALIDATION', false),
|
||||||
|
];
|
||||||
@@ -58,9 +58,8 @@ return [
|
|||||||
'prefix_indexes' => true,
|
'prefix_indexes' => true,
|
||||||
'strict' => true,
|
'strict' => true,
|
||||||
'engine' => null,
|
'engine' => null,
|
||||||
'options' => extension_loaded('pdo_mysql') === true ? array_filter([
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||||
PDO::ATTR_TIMEOUT => env('DB_TIMEOUT', 30),
|
|
||||||
]) : [],
|
]) : [],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -32,16 +32,50 @@ return [
|
|||||||
|
|
||||||
'local' => [
|
'local' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => storage_path('app/uploads'),
|
'root' => storage_path('app/public'),
|
||||||
|
'url' => env('APP_URL') . "/storage",
|
||||||
|
'public' => true,
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
'url' => env('STORAGE_LOCAL_URL'),
|
],
|
||||||
|
|
||||||
|
'cdn' => [
|
||||||
|
'driver' => 's3',
|
||||||
|
'key' => env('AWS_PUBLIC_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_PUBLIC_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_PUBLIC_DEFAULT_REGION'),
|
||||||
|
'bucket' => env('AWS_PUBLIC_BUCKET'),
|
||||||
|
'url' => env('AWS_PUBLIC_URL'),
|
||||||
|
'endpoint' => env('AWS_PUBLIC_ENDPOINT'),
|
||||||
|
'use_path_style_endpoint' => env('AWS_PUBLIC_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
|
'throw' => false,
|
||||||
|
'public' => true,
|
||||||
|
'options' => [
|
||||||
|
'ACL' => '',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
|
'private' => [
|
||||||
|
'driver' => 's3',
|
||||||
|
'key' => env('AWS_PRIVATE_ACCESS_KEY_ID'),
|
||||||
|
'secret' => env('AWS_PRIVATE_SECRET_ACCESS_KEY'),
|
||||||
|
'region' => env('AWS_PRIVATE_DEFAULT_REGION'),
|
||||||
|
'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,
|
||||||
|
'public' => false,
|
||||||
|
'options' => [
|
||||||
|
'ACL' => '',
|
||||||
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
'public' => [
|
'public' => [
|
||||||
'driver' => 'local',
|
'driver' => 'local',
|
||||||
'root' => public_path('uploads'),
|
'root' => storage_path('app/public'),
|
||||||
|
'url' => env('APP_URL') . '/storage',
|
||||||
|
'visibility' => 'public',
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
'url' => env('STORAGE_PUBLIC_URL'),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
's3' => [
|
's3' => [
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use Monolog\Handler\NullHandler;
|
use Monolog\Handler\NullHandler;
|
||||||
use Monolog\Handler\StreamHandler;
|
use Monolog\Handler\StreamHandler;
|
||||||
use Monolog\Handler\SyslogUdpHandler;
|
use Monolog\Handler\SyslogUdpHandler;
|
||||||
|
use Monolog\Processor\PsrLogMessageProcessor;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ return [
|
|||||||
'driver' => 'single',
|
'driver' => 'single',
|
||||||
'path' => storage_path('logs/laravel.log'),
|
'path' => storage_path('logs/laravel.log'),
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'daily' => [
|
'daily' => [
|
||||||
@@ -68,6 +70,7 @@ return [
|
|||||||
'path' => storage_path('logs/laravel.log'),
|
'path' => storage_path('logs/laravel.log'),
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
'days' => 14,
|
'days' => 14,
|
||||||
|
'replace_placeholders' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'slack' => [
|
'slack' => [
|
||||||
@@ -76,6 +79,7 @@ return [
|
|||||||
'username' => 'Laravel Log',
|
'username' => 'Laravel Log',
|
||||||
'emoji' => ':boom:',
|
'emoji' => ':boom:',
|
||||||
'level' => env('LOG_LEVEL', 'critical'),
|
'level' => env('LOG_LEVEL', 'critical'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'papertrail' => [
|
'papertrail' => [
|
||||||
@@ -87,6 +91,7 @@ return [
|
|||||||
'port' => env('PAPERTRAIL_PORT'),
|
'port' => env('PAPERTRAIL_PORT'),
|
||||||
'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'),
|
'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'),
|
||||||
],
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
'stderr' => [
|
'stderr' => [
|
||||||
@@ -97,16 +102,20 @@ return [
|
|||||||
'with' => [
|
'with' => [
|
||||||
'stream' => 'php://stderr',
|
'stream' => 'php://stderr',
|
||||||
],
|
],
|
||||||
|
'processors' => [PsrLogMessageProcessor::class],
|
||||||
],
|
],
|
||||||
|
|
||||||
'syslog' => [
|
'syslog' => [
|
||||||
'driver' => 'syslog',
|
'driver' => 'syslog',
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'facility' => LOG_USER,
|
||||||
|
'replace_placeholders' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'errorlog' => [
|
'errorlog' => [
|
||||||
'driver' => 'errorlog',
|
'driver' => 'errorlog',
|
||||||
'level' => env('LOG_LEVEL', 'debug'),
|
'level' => env('LOG_LEVEL', 'debug'),
|
||||||
|
'replace_placeholders' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'null' => [
|
'null' => [
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ return [
|
|||||||
| sending an e-mail. You will specify which one you are using for your
|
| sending an e-mail. You will specify which one you are using for your
|
||||||
| mailers below. You are free to add additional mailers as required.
|
| mailers below. You are free to add additional mailers as required.
|
||||||
|
|
|
|
||||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
| Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
|
||||||
| "postmark", "log", "array", "failover"
|
| "postmark", "log", "array", "failover"
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
@@ -51,10 +51,16 @@ return [
|
|||||||
|
|
||||||
'mailgun' => [
|
'mailgun' => [
|
||||||
'transport' => 'mailgun',
|
'transport' => 'mailgun',
|
||||||
|
// 'client' => [
|
||||||
|
// 'timeout' => 5,
|
||||||
|
// ],
|
||||||
],
|
],
|
||||||
|
|
||||||
'postmark' => [
|
'postmark' => [
|
||||||
'transport' => 'postmark',
|
'transport' => 'postmark',
|
||||||
|
// 'client' => [
|
||||||
|
// 'timeout' => 5,
|
||||||
|
// ],
|
||||||
],
|
],
|
||||||
|
|
||||||
'sendmail' => [
|
'sendmail' => [
|
||||||
|
|||||||
@@ -36,4 +36,5 @@ return [
|
|||||||
'site_key' => env('GOOGLE_RECAPTCHA_SITE_KEY'),
|
'site_key' => env('GOOGLE_RECAPTCHA_SITE_KEY'),
|
||||||
'secret_key' => env('GOOGLE_RECAPTCHA_SECRET_SITE_KEY'),
|
'secret_key' => env('GOOGLE_RECAPTCHA_SECRET_SITE_KEY'),
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||||||
/**
|
/**
|
||||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
||||||
*/
|
*/
|
||||||
class PostFactory extends Factory
|
class ArticleFactory extends Factory
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Define the model's default state.
|
* Define the model's default state.
|
||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function definition()
|
public function definition(): array
|
||||||
{
|
{
|
||||||
$publishDate = Carbon::parse($this->faker->dateTimeBetween('-1 month', '+1 month'));
|
$publishDate = Carbon::parse($this->faker->dateTimeBetween('-1 month', '+1 month'));
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ class PostFactory extends Factory
|
|||||||
'slug' => $this->faker->slug(),
|
'slug' => $this->faker->slug(),
|
||||||
'publish_at' => $publishDate,
|
'publish_at' => $publishDate,
|
||||||
'content' => $this->faker->paragraphs(3, true),
|
'content' => $this->faker->paragraphs(3, true),
|
||||||
'user_id' => $this->faker->uuid,
|
'user_id' => $this->faker->uuid(),
|
||||||
'hero' => $this->faker->uuid,
|
'hero' => $this->faker->uuid(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ class EventFactory extends Factory
|
|||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public function definition()
|
public function definition(): array
|
||||||
{
|
{
|
||||||
$startDate = Carbon::parse($this->faker->dateTimeBetween('now', '+1 year'));
|
$startDate = Carbon::parse($this->faker->dateTimeBetween('now', '+1 year'));
|
||||||
$endDate = Carbon::parse($this->faker->dateTimeBetween($startDate, '+1 year'));
|
$endDate = Carbon::parse($this->faker->dateTimeBetween($startDate, '+1 year'));
|
||||||
@@ -24,14 +24,14 @@ class EventFactory extends Factory
|
|||||||
return [
|
return [
|
||||||
'title' => $this->faker->sentence(),
|
'title' => $this->faker->sentence(),
|
||||||
'location' => $this->faker->randomElement(['online', 'physical']),
|
'location' => $this->faker->randomElement(['online', 'physical']),
|
||||||
'address' => $this->faker->address,
|
'address' => $this->faker->address(),
|
||||||
'start_at' => $startDate,
|
'start_at' => $startDate,
|
||||||
'end_at' => $endDate,
|
'end_at' => $endDate,
|
||||||
'publish_at' => $publishDate,
|
'publish_at' => $publishDate,
|
||||||
'status' => $this->faker->randomElement(['draft', 'soon', 'open', 'closed', 'cancelled']),
|
'status' => $this->faker->randomElement(['draft', 'soon', 'open', 'closed', 'cancelled']),
|
||||||
'registration_type' => $this->faker->randomElement(['none', 'email', 'link', 'message']),
|
'registration_type' => $this->faker->randomElement(['none', 'email', 'link', 'message']),
|
||||||
'registration_data' => $this->faker->sentence(),
|
'registration_data' => $this->faker->sentence(),
|
||||||
'hero' => $this->faker->uuid,
|
'hero' => $this->faker->uuid(),
|
||||||
'content' => $this->faker->paragraphs(3, true),
|
'content' => $this->faker->paragraphs(3, true),
|
||||||
'price' => $this->faker->numberBetween(0, 150),
|
'price' => $this->faker->numberBetween(0, 150),
|
||||||
'ages' => $this->faker->regexify('\d+(\+|\-\d+)?'),
|
'ages' => $this->faker->regexify('\d+(\+|\-\d+)?'),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user