Compare commits
472 Commits
dev
...
media-upda
| Author | SHA1 | Date | |
|---|---|---|---|
| eb32c99764 | |||
| 8305f16dae | |||
| a74ace3bbd | |||
| 96c8774e31 | |||
| 45895bddae | |||
| e29e443078 | |||
| f213aeb93a | |||
| b68b0f1583 | |||
| 52fc4c5c34 | |||
| d7f81c2f03 | |||
| 9b6e9aeb1c | |||
| ea3bb13661 | |||
| ec12679426 | |||
| 25a6d60e73 | |||
| 24c3a1ef30 | |||
| fbaef5392f | |||
| f52cd448e2 | |||
| 7f3eada0c1 | |||
| 181cd2fce1 | |||
| a5f600e73b | |||
| c96b3d8349 | |||
| 06c9d48126 | |||
| 0d29fbce45 | |||
| dc7fd81fc5 | |||
| f7503d1f20 | |||
| 56673fceaa | |||
| 3dccc56d16 | |||
| 75566e27fa | |||
| a8627ca89e | |||
| f49b6fef0f | |||
| 8b83b0c212 | |||
| 57292ab8de | |||
| a26b522356 | |||
| e1468e82e4 | |||
| 76102637dd | |||
| a55fac1bfa | |||
| 052a256422 | |||
| 3bfcb0c0d4 | |||
| e18292e352 | |||
| d71a1f5940 | |||
| d63f180030 | |||
| aebdb0b599 | |||
| 3a562005e5 | |||
| 320516fd8d | |||
| 971074777d | |||
| 22c3b5800d | |||
| b0a1197e22 | |||
| 79704e2f2b | |||
| 9f9faf5554 | |||
| a927334e06 | |||
| 74c4c5d2bc | |||
| 2bd8acc00f | |||
| 22ea843f60 | |||
| c7fb636ab5 | |||
| 59761e8eac | |||
| 4f8efa9c90 | |||
| 132ece89c1 | |||
| d0d5cc6841 | |||
| 498fd89239 | |||
| 0f99d3e83b | |||
| d0fa4d649b | |||
| 7d1d74e48a | |||
| 65e81eaae7 | |||
| 85cfdfd24f | |||
| ad5b47f2a5 | |||
| 238189fd9a | |||
| 5691a051a6 | |||
| d11ac9240c | |||
| 7c9f901a7a | |||
| 55fffef5cb | |||
| eb1c475fd9 | |||
| 7cfeea9641 | |||
| 59a7f02893 | |||
| ab4ef89c87 | |||
| 3d8b85dcf4 | |||
| 4b9867bd16 | |||
| b45dd84f0f | |||
| b21468f265 | |||
| 19c5bd5c25 | |||
| 64fd34ff1c | |||
| c0e7adcc42 | |||
| 0f48f21dde | |||
| 5f960cca71 | |||
| 630418cf02 | |||
| dd5ac3a2b4 | |||
| 0385672364 | |||
| c2a0f04cc0 | |||
| 9be9f4329b | |||
| 667972f05e | |||
| 0bbb1f0eba | |||
| a7219861f4 | |||
| 09376e8f98 | |||
| ac45a6b5ef | |||
|
|
ea732301fd | ||
| d21d1b6993 | |||
| 655c003969 | |||
| 23288e15e0 | |||
| cf3c35ffa3 | |||
| 85c37ba748 | |||
| 154dffeee4 | |||
| 2cea90c2c8 | |||
| 44b123307a | |||
| 7ecec70520 | |||
| 7605a826d6 | |||
| 3126991e8f | |||
| 7b58303cde | |||
| 58d302fc38 | |||
| af4b9b95e7 | |||
| dc56edf486 | |||
| 14dd2bb336 | |||
| 7a1499a0b3 | |||
| be8ccbd41a | |||
| 970618f561 | |||
| 8a3d9eec03 | |||
| 46de5cc0c9 | |||
| 0c2ac5d0a5 | |||
| c18b740f46 | |||
| 615abcc8e3 | |||
| 3d13fc6864 | |||
| 8244230268 | |||
| 3f48f11838 | |||
| e16ba2d096 | |||
| 874339348c | |||
| d1833d7b8d | |||
| b658e96425 | |||
| 0ab92d95ea | |||
| a9b480994a | |||
| 3bd5c064c3 | |||
| 6bee6b1ba7 | |||
| e11211fcc7 | |||
| d5a703026a | |||
| 44481fe107 | |||
| 57092e1b26 | |||
| b62a3b9d63 | |||
| 7fd65ede5f | |||
| d3e7938231 | |||
| 2afb59d4a2 | |||
| d54f2159d9 | |||
| e032fb666f | |||
| dca56831af | |||
| 937b70e1fd | |||
| 3b0c7d8388 | |||
| d5093110f7 | |||
| 25ddcef978 | |||
| 5c613df087 | |||
| 0aa44e70de | |||
| 2b69d9985e | |||
| 86f44c8bab | |||
| fa66ee14ee | |||
| 40ed36e0e2 | |||
| b7c8a9ece5 | |||
| 29dfb852c8 | |||
| 9de83c3436 | |||
| 9a0768a2d5 | |||
| 07aa82e7d9 | |||
| bc79951700 | |||
| 155a83cfdd | |||
| 1ffa7cdbcf | |||
| 0f2e1478a8 | |||
| 3efe31c91f | |||
| 22ebefc46a | |||
| 1f8646438d | |||
| 9ac7769dbc | |||
| 5f3cc1a3c6 | |||
| 7b882d6705 | |||
| 33aa01fc78 | |||
| 542bf9c189 | |||
| d39d599570 | |||
| 082e2fe2bd | |||
| 9600d1e7d1 | |||
| 6ab68df4a0 | |||
| 9ae8fa4055 | |||
| 52d40cb8a1 | |||
| 30e51dddd7 | |||
| ffbe78fdc1 | |||
| adae8888f8 | |||
| a3641828fc | |||
| b91d079787 | |||
| 9e2bb48f13 | |||
| 22495294fc | |||
| c83cfff556 | |||
| 35c3108db7 | |||
| 6155a8768f | |||
| 9d16889216 | |||
| 9fd46b9fd9 | |||
| 041dd4b314 | |||
| 13b2c448b5 | |||
| a41e209123 | |||
| c24c1f54be | |||
| 96076af037 | |||
| 1c9bce3b33 | |||
| ef048f77bb | |||
| 361ead1351 | |||
| f031c51ce4 | |||
| ce7ba83fff | |||
| eb692de59a | |||
| fe66dedecf | |||
| 79eea9ea25 | |||
| 83c25f20bb | |||
| 95c5ed6a32 | |||
| 3f0ebca4da | |||
| 03ec852648 | |||
| 6ccc33f762 | |||
| d1bc5c2fe5 | |||
| ee0d5c363c | |||
| 86d5706aac | |||
| 2ca199d98f | |||
| 562d7a603e | |||
| c078189f44 | |||
| d060b1f56c | |||
| d91d51a60a | |||
| 4677400b4f | |||
| f7bac335db | |||
| ad3e6e59f2 | |||
| cc38a45fd0 | |||
| 7320094a87 | |||
| f05691d911 | |||
| 3f9e40dbc8 | |||
| d399427d7f | |||
| bf53784135 | |||
| 988d48996a | |||
| 037873778d | |||
| 835e8be4dc | |||
| e384229d7c | |||
| efcb61781a | |||
| a20551db7a | |||
| 26a3191226 | |||
|
|
f6a390595a | ||
| e0ac364dff | |||
| 1b6c5a57e4 | |||
| 62a3180e53 | |||
| 91a4a2c2a4 | |||
| c40f448aa3 | |||
| 7bc89f4463 | |||
| 30b487e48c | |||
| db7ee08937 | |||
| d810340425 | |||
| 48192240c4 | |||
| 5c6b0085c8 | |||
| c6b088fdf9 | |||
| b1769a3326 | |||
| 61f9efe32d | |||
| f940278f4d | |||
|
|
0665286657 | ||
| eb0064e477 | |||
| 955c06f5aa | |||
| 1ee2a1189d | |||
| c8e49ba49c | |||
| aeb7939c6e | |||
| 910330c7dd | |||
| 1315d6368c | |||
| c9e2350155 | |||
| 35f5b382fa | |||
| a530b98fe2 | |||
| 26b93442b1 | |||
| 1142033d57 | |||
| 808304303a | |||
| 2b298b7c76 | |||
| 3231063bc2 | |||
| 212c5410e1 | |||
| 34a90bf218 | |||
| 56f786cd2b | |||
| cff787f541 | |||
| 55ec88e11f | |||
| b718212702 | |||
| c1dbde2fb7 | |||
| 2eac3f8b0b | |||
| 05cc7767bf | |||
| 7c897a7e12 | |||
| 06655e2378 | |||
| aad927fb96 | |||
| 533fdab150 | |||
| 93eb6da68d | |||
| cc89d45690 | |||
| f993913438 | |||
| 158da60922 | |||
| bb0be1dadf | |||
| c3379f2796 | |||
| 531e1f53fd | |||
| 62f09f738f | |||
| 79d5218b16 | |||
| a068516aa0 | |||
| 9f92bc710c | |||
| 716cc0eb58 | |||
| c3bb2179b1 | |||
| a82e8df06d | |||
| 40dce90aa0 | |||
| 37e59bb8a5 | |||
| 982c124d3b | |||
| 6ebb915c68 | |||
| 4ee8fd2400 | |||
| 19b40d50c1 | |||
| 6b14a4bb24 | |||
| 25790d1c45 | |||
| 4261a35ca7 | |||
| 66e2783d97 | |||
| 55bf78497f | |||
| ac35ce6c47 | |||
| 3b443aafc5 | |||
| a2ab077325 | |||
| 4adb0c953b | |||
| 72a78ba27b | |||
| e4cdd25922 | |||
| 24e2637408 | |||
| b538eb1929 | |||
| 4e40e8b1db | |||
| e6aef415f5 | |||
| 0f8f18943b | |||
| f0accc2e33 | |||
| 14ea157b7a | |||
| 686b0a167d | |||
| bde47e1251 | |||
| 9aaa2e5926 | |||
| ad74d7bdeb | |||
| ab42eeb370 | |||
| a25fc2e7e8 | |||
| ca40db79f7 | |||
| fad2f82b6b | |||
| 1b68387865 | |||
| b2ef15d45c | |||
| 998e69085e | |||
| 77d2786937 | |||
| b7814ac2a4 | |||
| 516410a649 | |||
| 3018048140 | |||
| 8e4044dffb | |||
| 4123845783 | |||
| fd9dcc182f | |||
| a3a6fbcaa4 | |||
| d9e4ccfca8 | |||
| ca288a088a | |||
| a63e73d692 | |||
| 5eda87b1ef | |||
| 2ee1bd3658 | |||
| cd689ea1a8 | |||
| 3ca5db394a | |||
| 5ee6d7ba78 | |||
| d15a44e7d7 | |||
| 4bb557e055 | |||
| f57466b243 | |||
| bdceb6d774 | |||
| fadf6a3d21 | |||
| 3cd03499c9 | |||
| 9406088f1f | |||
| a8f650d530 | |||
| caffbb6c45 | |||
| 4a6a65c5d2 | |||
| b50acd3b74 | |||
| 83f6bce923 | |||
| 7c4f377e2b | |||
| 8851a7149f | |||
| ced5089caf | |||
| ce53c94ea3 | |||
| 5cfcbacb80 | |||
| 5acf5e5297 | |||
| 9337ee292c | |||
| 3de48f002c | |||
| dad736b0d9 | |||
| 7b2e88d089 | |||
| 0db1bd50fd | |||
| 0fcc9b0d7e | |||
| 0349225ede | |||
| 5ee55b298f | |||
| 9877f3883a | |||
| 7cfc24cf39 | |||
| 9025581db8 | |||
| 731e33a986 | |||
| 336e7a7d41 | |||
| 3906ef119b | |||
| 4de4642c23 | |||
| 56eee105d5 | |||
| d7e530b2dd | |||
| 54855e2651 | |||
| 7de134683d | |||
| 1d0030e34f | |||
| ccc30a8b7a | |||
| 9961aba160 | |||
| 5414c0b232 | |||
| 584e146af0 | |||
| e035128c30 | |||
| 03f5c8d90f | |||
| ff75f142b3 | |||
| 094779a4fd | |||
| cfa84faab4 | |||
| c18d7c56c1 | |||
| 127fea1dfd | |||
| ebe1069a7f | |||
| 9f42f92c2e | |||
| c2542472e7 | |||
| 2fe0a8c3f0 | |||
| e97427c7d7 | |||
| d1e586bb39 | |||
| 9e2c7c2565 | |||
| 29deea73d4 | |||
| b1e30a603c | |||
| afc3c94b04 | |||
| ac4d3d8ad0 | |||
| 2b939f417c | |||
| 57236d814d | |||
| 839eaa6db9 | |||
| 78ccd558aa | |||
| a5a9db9669 | |||
| 0b70a52177 | |||
| f9105577ce | |||
| a7e06bef91 | |||
| 0bed3aa096 | |||
| 305c018d39 | |||
| 9404df9a1d | |||
| a2894791e2 | |||
| c671fb9bf6 | |||
| 5f22142a6d | |||
| cc1a389eba | |||
| e6174a4456 | |||
| 145b5559ef | |||
| 09751208a1 | |||
| 22ff92e3a7 | |||
| 8f7f1a35d5 | |||
| 1452d6f340 | |||
| 6080c12a7a | |||
| d16231d338 | |||
| 14e2e2da3e | |||
| 0fba7849b4 | |||
| 6cf5cd1697 | |||
| be39577466 | |||
| fe56acd818 | |||
| e84dfcacd1 | |||
| e639801e37 | |||
| 636ff8c4a9 | |||
| 5da4cca80f | |||
| c5cc91b075 | |||
| e40a2bad16 | |||
| 4367fa4440 | |||
| a2332451b0 | |||
| f6377a6f3a | |||
| ba3846b374 | |||
| 42651ec256 | |||
| d248204bf9 | |||
| 8b84f1c02c | |||
| 3904fe784e | |||
| e5a68fd805 | |||
| 32d39cdc1c | |||
| 9188461b1f | |||
| 44f6ef4a0b | |||
| b0ae6b0a3d | |||
|
|
b6268b3fab | ||
| ba436e5dcc | |||
| dd01a075d8 | |||
| 89ebab5134 | |||
| a135c7e8fd | |||
| 1f8b83fc8c | |||
| c378d683db | |||
| 0d1a0cf130 | |||
| 4ec4b5b10b | |||
| d60383211c | |||
| 900909aa5b | |||
| e33e8ad6f6 | |||
| 7369eb469e | |||
| c3ebec38d7 | |||
| 7f1d0b127d | |||
| cb8dc3cfa5 | |||
| ac68b4856a | |||
| 26348d9e19 | |||
| d8805a791e | |||
| 8fdc7f8600 | |||
| 5681990112 | |||
| ab36df319b | |||
| f48704804a | |||
| 05e9315ef2 | |||
|
|
a885d15341 | ||
| ad69e5c0e2 | |||
| 824ab331b4 | |||
| 1f21e22dce |
59
.env.testing
Normal file
@@ -0,0 +1,59 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=base64:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://127.0.0.1
|
||||
APP_URL_API="${APP_URL}/api/"
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
DB_DATABASE=:memory:
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=array
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_HOST=null
|
||||
MAIL_PORT=null
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
CONTACT_ADDRESS="hello@stemmechanics.com.au"
|
||||
CONTACT_SUBJECT="Contact from website"
|
||||
|
||||
STORAGE_LOCAL_URL="${APP_URL}/api/media/%ID%/download"
|
||||
STORAGE_PUBLIC_URL="${APP_URL}/uploads/%NAME%"
|
||||
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
.github/
|
||||
.vscode/
|
||||
vendor/
|
||||
@@ -4,14 +4,15 @@ module.exports = {
|
||||
},
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:vue/vue3-strongly-recommended",
|
||||
"prettier",
|
||||
"plugin:jsdoc/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
],
|
||||
rules: {
|
||||
"vue/multi-word-component-names": "off",
|
||||
indent: ["error", 4],
|
||||
indent: ["off", 4, { ignoredNodes: ["ConditionalExpression"] }],
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
},
|
||||
plugins: ["jsdoc", "@typescript-eslint"],
|
||||
parser: "vue-eslint-parser",
|
||||
|
||||
42
.github/workflows/laravel.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Laravel
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
laravel-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.1"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Copy .env
|
||||
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
|
||||
- name: Install Dependencies
|
||||
run: composer install -q --no-interaction --no-progress --prefer-dist
|
||||
- name: Generate key
|
||||
run: php artisan key:generate
|
||||
- name: Directory Permissions
|
||||
run: chmod -R 777 storage bootstrap/cache
|
||||
- name: Create Database
|
||||
run: |
|
||||
mkdir -p database
|
||||
touch database/database.sqlite
|
||||
- name: Execute tests (Unit and Feature tests) via PHPUnit
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
DB_DATABASE: database/database.sqlite
|
||||
run: vendor/bin/phpunit
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16.x"
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Run Vue tests
|
||||
env:
|
||||
LARAVEL_BYPASS_ENV_CHECK: "1"
|
||||
run: npm run test
|
||||
9
.gitignore
vendored
@@ -237,4 +237,11 @@ dist/
|
||||
### This Project ###
|
||||
/public/uploads
|
||||
/public/build
|
||||
*.key
|
||||
/public/tinymce
|
||||
*.key
|
||||
|
||||
### Synk ###
|
||||
.dccache
|
||||
|
||||
### TempCodeRunner ###
|
||||
tempCodeRunnerFile.*
|
||||
1
.vscode/settings.json
vendored
@@ -3,6 +3,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
// "source.organizeImports": true // <-- when enabled, breaks tinymce required import order
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[vue]": {
|
||||
|
||||
715
app/Conductors/Conductor.php
Normal file
@@ -0,0 +1,715 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Conductor
|
||||
{
|
||||
/**
|
||||
* The Conductors Model class.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $class = null;
|
||||
|
||||
/**
|
||||
* The default sorting fields of a collection. Can be an array. Supports - and + prefixes.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $sort = "id";
|
||||
|
||||
/**
|
||||
* The default collection size limit per request.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $limit = 50;
|
||||
|
||||
/**
|
||||
* The maximum collection size limit per request.
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $maxLimit = 100;
|
||||
|
||||
/**
|
||||
* The default includes to include in a request.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $includes = [];
|
||||
|
||||
/**
|
||||
* The conductor collection.
|
||||
*
|
||||
* @var Collection
|
||||
*/
|
||||
private $collection = null;
|
||||
|
||||
/**
|
||||
* The conductor query.
|
||||
*
|
||||
* @var Builder
|
||||
*/
|
||||
private $query = null;
|
||||
|
||||
|
||||
/**
|
||||
* Split a string on commas, keeping quotes intact.
|
||||
*
|
||||
* @param string $string The string to split.
|
||||
* @return array The split string.
|
||||
*/
|
||||
private function splitString(string $string)
|
||||
{
|
||||
$parts = [];
|
||||
$start = 0;
|
||||
$len = strlen($string);
|
||||
|
||||
while ($start < $len) {
|
||||
$commaPos = strpos($string, ',', $start);
|
||||
$singlePos = strpos($string, '\'', $start);
|
||||
$doublePos = strpos($string, '"', $start);
|
||||
|
||||
// Find the smallest position that is not false
|
||||
$minPos = false;
|
||||
if ($commaPos !== false) {
|
||||
$minPos = $commaPos;
|
||||
}
|
||||
if ($singlePos !== false && ($minPos === false || $singlePos < $minPos)) {
|
||||
$minPos = $singlePos;
|
||||
}
|
||||
if ($doublePos !== false && ($minPos === false || $doublePos < $minPos)) {
|
||||
$minPos = $doublePos;
|
||||
}
|
||||
|
||||
if ($minPos === false) {
|
||||
// No more commas, single quotes, or double quotes found
|
||||
$part = substr($string, $start);
|
||||
$parts[] = trim($part);
|
||||
break;
|
||||
} else {
|
||||
// Add the current part to the parts array
|
||||
$part = substr($string, $start, ($minPos - $start));
|
||||
$parts[] = trim($part);
|
||||
|
||||
// Update the start position to the next character after the comma, single quote, or double quote
|
||||
if ($string[$minPos] === ',') {
|
||||
$start = ($minPos + 1);
|
||||
} else {
|
||||
$quoteChar = $string[$minPos];
|
||||
$endPos = strpos($string, $quoteChar, ($minPos + 1));
|
||||
if ($endPos === false) {
|
||||
$part = substr($string, ($minPos + 1));
|
||||
$parts[] = trim($part);
|
||||
break;
|
||||
} else {
|
||||
$part = substr($string, ($minPos + 1), ($endPos - $minPos - 1));
|
||||
$parts[] = trim($part);
|
||||
$start = ($endPos + 1);
|
||||
}
|
||||
}
|
||||
}//end if
|
||||
}//end while
|
||||
|
||||
return array_filter($parts, function ($value) {
|
||||
return $value !== '';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a field with a specific Builder object
|
||||
*
|
||||
* @param Builder $builder The builder object to append.
|
||||
* @param string $field The field name.
|
||||
* @param mixed $value The value or array of values to filter.
|
||||
* @param string $boolean The comparision boolean (AND or OR).
|
||||
* @return void
|
||||
*/
|
||||
private function filterFieldWithBuilder(Builder $builder, string $field, mixed $value, string $boolean = 'AND')
|
||||
{
|
||||
$values = [];
|
||||
|
||||
// Split by comma, but respect quotation marks
|
||||
if (is_string($value) === true) {
|
||||
$values = $this->splitString($value);
|
||||
} elseif (is_array($value) === true) {
|
||||
$values = $value;
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Expected string or array, got ' . gettype($value));
|
||||
}
|
||||
|
||||
// Add each AND check to the query
|
||||
$builder->where(function ($query) use ($field, $values) {
|
||||
foreach ($values as $value) {
|
||||
$value = trim($value);
|
||||
$prefix = '';
|
||||
|
||||
// Check if value has a prefix and remove it if it's a number
|
||||
if (preg_match('/^(!?=|[<>]=?|<>|!)([^=!<>].*)$/', $value, $matches) > 0) {
|
||||
$prefix = $matches[1];
|
||||
$value = $matches[2];
|
||||
}
|
||||
|
||||
// Apply the prefix to the query if the value is a number
|
||||
switch ($prefix) {
|
||||
case '=':
|
||||
$query->orWhere($field, '=', $value);
|
||||
break;
|
||||
case '!':
|
||||
$query->orWhere($field, 'NOT LIKE', "%$value%");
|
||||
break;
|
||||
case '>':
|
||||
$query->orWhere($field, '>', $value);
|
||||
break;
|
||||
case '<':
|
||||
$query->orWhere($field, '<', $value);
|
||||
break;
|
||||
case '>=':
|
||||
$query->orWhere($field, '>=', $value);
|
||||
break;
|
||||
case '<=':
|
||||
$query->orWhere($field, '<=', $value);
|
||||
break;
|
||||
case '!=':
|
||||
$query->orWhere($field, '!=', $value);
|
||||
break;
|
||||
case '<>':
|
||||
$seperatorPos = strpos($value, '|');
|
||||
if ($seperatorPos !== false) {
|
||||
$query->orWhereBetween($field, [substr($value, 0, $seperatorPos), substr($value, ($seperatorPos + 1))]);
|
||||
} else {
|
||||
$query->orWhere($field, '!=', $value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$query->orWhere($field, 'LIKE', "%$value%");
|
||||
break;
|
||||
}//end switch
|
||||
}//end foreach
|
||||
}, null, null, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the conductor on a Request to generate a collection and total.
|
||||
*
|
||||
* @param Request $request The request data.
|
||||
* @return array The processed and transformed collection | the total rows found.
|
||||
*/
|
||||
final public static function request(Request $request)
|
||||
{
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$total = 0;
|
||||
|
||||
try {
|
||||
$conductor->query = $conductor->class::query();
|
||||
} catch (\Throwable $e) {
|
||||
throw new \Exception('Failed to create query builder instance for ' . $conductor->class . '.', 0, $e);
|
||||
}
|
||||
|
||||
// Filter request
|
||||
$fields = $conductor->fields(new $conductor->class());
|
||||
if (is_array($fields) === false) {
|
||||
$fields = [];
|
||||
}
|
||||
|
||||
$params = $request->all();
|
||||
$filterFields = array_intersect_key($params, array_flip($fields));
|
||||
$conductor->filter($filterFields);
|
||||
if ($request->has('filter') === true) {
|
||||
$conductor->filterRaw($request->input('filter', ''), $fields);
|
||||
}
|
||||
|
||||
// After Scope query
|
||||
$conductor->query->where(function ($query) use ($conductor) {
|
||||
$conductor->scope($query);
|
||||
});
|
||||
|
||||
// Sort request
|
||||
$conductor->sort($request->input('sort', $conductor->sort));
|
||||
|
||||
// Get total
|
||||
$total = $conductor->count();
|
||||
|
||||
// Paginate
|
||||
$conductor->paginate($request->input('page', 1), $request->input('limit', -1));
|
||||
|
||||
// Limit fields
|
||||
$limitFields = explode(',', $request->input('fields'));
|
||||
if ($limitFields === null) {
|
||||
$limitFields = $fields;
|
||||
} else {
|
||||
$limitFields = array_intersect($limitFields, $fields);
|
||||
}
|
||||
$conductor->limitFields($limitFields);
|
||||
|
||||
$conductor->collection = $conductor->query->get();
|
||||
|
||||
|
||||
// Transform and Includes
|
||||
$includes = $conductor->includes;
|
||||
if ($request->has('includes') === true) {
|
||||
$includes = explode(',', $request->input('includes'));
|
||||
}
|
||||
|
||||
$conductor->collection = $conductor->collection->map(function ($model) use ($conductor, $includes) {
|
||||
$conductor->includes($model, $includes);
|
||||
$model = $conductor->transform($model);
|
||||
|
||||
return $model;
|
||||
});
|
||||
|
||||
return [$conductor->collection, $total];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the conductor on a collection with the data stored in a Request.
|
||||
*
|
||||
* @param Request $request The request data.
|
||||
* @param Collection $collection The collection.
|
||||
* @return array The processed and transformed model data.
|
||||
*/
|
||||
final public static function collection(Request $request, Collection $collection)
|
||||
{
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$transformedCollection = collect();
|
||||
|
||||
foreach ($collection as $item) {
|
||||
if ($conductor->viewable($item)) {
|
||||
$transformedCollection->push($conductor->transform($item));
|
||||
}
|
||||
}
|
||||
|
||||
return $transformedCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the conductor on a Model with the data stored in a Request.
|
||||
*
|
||||
* @param Request $request The request data.
|
||||
* @param Model $model The model.
|
||||
* @return array The processed and transformed model data.
|
||||
*/
|
||||
final public static function model(Request $request, Model $model)
|
||||
{
|
||||
$conductor_class = get_called_class();
|
||||
$conductor = new $conductor_class();
|
||||
|
||||
$fields = $conductor->fields(new $conductor->class());
|
||||
|
||||
// Limit fields
|
||||
$limitFields = $fields;
|
||||
if ($request !== null && $request->has('fields') === true) {
|
||||
$requestFields = $request->input('fields');
|
||||
if ($requestFields !== null) {
|
||||
$limitFields = array_intersect(explode(',', $requestFields), $fields);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($limitFields) === false) {
|
||||
$modelSubset = new $conductor->class();
|
||||
foreach ($limitFields as $field) {
|
||||
$modelSubset->setAttribute($field, $model->$field);
|
||||
}
|
||||
$model = $modelSubset;
|
||||
}
|
||||
|
||||
// Includes
|
||||
$includes = $conductor->includes;
|
||||
if ($request !== null && $request->has('includes') === true) {
|
||||
$includes = explode(',', $request->input('includes', ''));
|
||||
}
|
||||
$conductor->includes($model, $includes);
|
||||
|
||||
// Transform
|
||||
$model = $conductor->transform($model);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a single field in the conductor collection.
|
||||
*
|
||||
* @param string $field The field name.
|
||||
* @param mixed $value The value or array of values to filter.
|
||||
* @param string $boolean The comparision boolean (AND or OR).
|
||||
* @return void
|
||||
*/
|
||||
final public function filterField(string $field, mixed $value, string $boolean = 'AND')
|
||||
{
|
||||
$this->filterFieldWithBuilder($this->query, $field, $value, $boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or Set the conductor collection.
|
||||
*
|
||||
* @param Collection $collection If not null, use the passed collection.
|
||||
* @return Collection The current conductor collection.
|
||||
*/
|
||||
// final public function collection(Collection $collection = null)
|
||||
// {
|
||||
// if ($collection !== null) {
|
||||
// $this->collection = $collection;
|
||||
// }
|
||||
|
||||
// return $this->collection;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Return the current conductor collection count.
|
||||
*
|
||||
* @return integer The current collection count.
|
||||
*/
|
||||
final public function count()
|
||||
{
|
||||
if ($this->query !== null) {
|
||||
return $this->query->count();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the conductor collection.
|
||||
*
|
||||
* @param mixed $fields A field name or array of field names to sort. Supports a prefix of + or - to change direction.
|
||||
* @return void
|
||||
*/
|
||||
final public function sort(mixed $fields = null)
|
||||
{
|
||||
if (is_string($fields) === true) {
|
||||
$fields = explode(',', $fields);
|
||||
} elseif ($fields === null) {
|
||||
$fields = $this->sort;
|
||||
}
|
||||
|
||||
if (is_array($fields) === true) {
|
||||
foreach ($fields as $orderByField) {
|
||||
$direction = 'asc';
|
||||
$directionChar = substr($orderByField, 0, 1);
|
||||
|
||||
if (in_array($directionChar, ['-', '+']) === true) {
|
||||
$orderByField = substr($orderByField, 1);
|
||||
if ($directionChar === '-') {
|
||||
$direction = 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
$this->query->orderBy(trim($orderByField), $direction);
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Expected string or array, got ' . gettype($fields));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the conductor collection based on an array of field => value.
|
||||
*
|
||||
* @param array $filters An array of field => value to filter.
|
||||
* @return void
|
||||
*/
|
||||
final public function filter(array $filters)
|
||||
{
|
||||
foreach ($filters as $param => $value) {
|
||||
$this->filterField($param, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the conductor collection.
|
||||
*
|
||||
* @param integer $page The current page to return.
|
||||
* @param integer $limit The limit of items to include or use default.
|
||||
* @return void
|
||||
*/
|
||||
final public function paginate(int $page = 1, int $limit = -1)
|
||||
{
|
||||
// Limit
|
||||
if ($limit < 1) {
|
||||
$limit = $this->limit;
|
||||
} else {
|
||||
$limit = min($limit, $this->maxLimit);
|
||||
}
|
||||
$this->query->limit($limit);
|
||||
|
||||
// Page
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
$this->query->offset(($page - 1) * $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a list of includes to the model.
|
||||
*
|
||||
* @param Model $model The model to append.
|
||||
* @param array $includes The list of includes to include.
|
||||
* @return void
|
||||
*/
|
||||
final public function includes(Model $model, array $includes)
|
||||
{
|
||||
foreach ($includes as $include) {
|
||||
$includeMethodName = 'include' . Str::studly($include);
|
||||
if (method_exists($this, $includeMethodName) === true) {
|
||||
$attributeName = Str::snake($include);
|
||||
$attributeValue = $this->{$includeMethodName}($model);
|
||||
if ($attributeValue !== null) {
|
||||
$model->$attributeName = $this->{$includeMethodName}($model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit the returned fields in the conductor collection.
|
||||
*
|
||||
* @param array $fields An array of field names.
|
||||
* @return void
|
||||
*/
|
||||
final public function limitFields(array $fields)
|
||||
{
|
||||
if (empty($fields) !== true) {
|
||||
$this->query->select($fields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the conductor collection using raw data.
|
||||
*
|
||||
* @param string $filterString The raw filter string to parse.
|
||||
* @param array|null $limitFields The fields to ignore in the filter string.
|
||||
* @return void
|
||||
*/
|
||||
final public function filterRaw(string $filterString, array|null $limitFields = null)
|
||||
{
|
||||
if (is_array($limitFields) === false || empty($limitFields) === true) {
|
||||
$limitFields = null;
|
||||
} else {
|
||||
$limitFields = array_map('strtolower', $limitFields);
|
||||
}
|
||||
|
||||
$tokens = preg_split('/([()]|,OR,|,AND,|,)/', $filterString, -1, (PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
|
||||
$glued = [];
|
||||
$glueToken = '';
|
||||
foreach ($tokens as $item) {
|
||||
if ($glueToken === '') {
|
||||
if (preg_match('/(?<!\\\\)[\'"]/', $item, $matches, PREG_OFFSET_CAPTURE) === 1) {
|
||||
$glueToken = $matches[0][0];
|
||||
$item = substr($item, 0, $matches[0][1]) . substr($item, ($matches[0][1] + 1));
|
||||
$item = str_replace("\\$glueToken", $glueToken, $item);
|
||||
}
|
||||
|
||||
$glued[] = $item;
|
||||
} else {
|
||||
// search for ending glue token
|
||||
if (preg_match('/(?<!\\\\)' . $glueToken . '/', $item, $matches, PREG_OFFSET_CAPTURE) === 1) {
|
||||
$item = substr($item, 0, $matches[0][1]) . substr($item, ($matches[0][1] + 1));
|
||||
$glueToken = '';
|
||||
}
|
||||
|
||||
$item = str_replace("\\$glueToken", $glueToken, $item);
|
||||
|
||||
$glued[(count($glued) - 1)] .= $item;
|
||||
}
|
||||
}//end foreach
|
||||
$tokens = $glued;
|
||||
|
||||
$parseTokens = function ($tokenList, $level, $index, $groupBoolean = null) use ($limitFields, &$parseTokens) {
|
||||
$tokenGroup = [];
|
||||
$firstToken = false;
|
||||
$tokenGroupBoolean = 'AND';
|
||||
|
||||
if ($groupBoolean !== null) {
|
||||
$firstToken = true;
|
||||
$tokenGroupBoolean = $groupBoolean;
|
||||
}
|
||||
|
||||
while ($index < count($tokenList)) {
|
||||
$token = $tokenList[$index];
|
||||
|
||||
++$index;
|
||||
if ($token === '(') {
|
||||
// next group
|
||||
$nextGroupBoolean = null;
|
||||
if (count($tokenGroup) > 0 && strlen($tokenGroup[(count($tokenGroup) - 1)]['field']) === 0) {
|
||||
$nextGroupBoolean = $tokenGroup[(count($tokenGroup) - 1)]['boolean'];
|
||||
unset($tokenGroup[(count($tokenGroup) - 1)]);
|
||||
}
|
||||
|
||||
$index = $parseTokens($tokenList, $level + 1, $index, $nextGroupBoolean);
|
||||
} elseif ($token === ')') {
|
||||
// end group
|
||||
break;
|
||||
} elseif (in_array(strtoupper($token), [',AND,', ',OR,']) === true) {
|
||||
// update boolean
|
||||
$boolean = trim(strtoupper($token), ',');
|
||||
|
||||
if ($firstToken === false && $level > 0) {
|
||||
$tokenGroupBoolean = $boolean;
|
||||
} else {
|
||||
$firstToken = true;
|
||||
$tokenGroup[] = [
|
||||
'field' => '',
|
||||
'value' => '',
|
||||
'boolean' => $boolean
|
||||
];
|
||||
}
|
||||
} elseif (strpos($token, ':') !== false) {
|
||||
// set tokenGroup
|
||||
$firstToken = true;
|
||||
$field = substr($token, 0, strpos($token, ':'));
|
||||
$value = substr($token, (strpos($token, ':') + 1));
|
||||
$boolean = 'AND';
|
||||
|
||||
if (count($tokenGroup) > 0 && strlen($tokenGroup[(count($tokenGroup) - 1)]['field']) === 0) {
|
||||
$tokenGroup[(count($tokenGroup) - 1)]['field'] = $field;
|
||||
$tokenGroup[(count($tokenGroup) - 1)]['value'] = $value;
|
||||
$boolean = $tokenGroup[(count($tokenGroup) - 1)]['boolean'];
|
||||
} else {
|
||||
$tokenGroup[] = [
|
||||
'field' => $field,
|
||||
'value' => $value,
|
||||
'boolean' => 'AND'
|
||||
];
|
||||
}
|
||||
|
||||
if ($limitFields === null || in_array(strtolower($field), $limitFields) !== true) {
|
||||
unset($tokenGroup[(count($tokenGroup) - 1)]);
|
||||
}
|
||||
|
||||
if ($level === 0) {
|
||||
$this->filterFieldWithBuilder($this->query, $field, $value, $boolean);
|
||||
}
|
||||
}//end if
|
||||
}//end while
|
||||
|
||||
if ($level > 0) {
|
||||
if ($tokenGroupBoolean === 'OR') {
|
||||
$this->query->orWhere(function ($query) use ($tokenGroup) {
|
||||
foreach ($tokenGroup as $tokenItem) {
|
||||
if (strlen($tokenItem['field']) > 0) {
|
||||
$this->filterFieldWithBuilder($query, $tokenItem['field'], $tokenItem['value'], $tokenItem['boolean']);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$this->query->where(function ($query) use ($tokenGroup) {
|
||||
foreach ($tokenGroup as $tokenItem) {
|
||||
if (strlen($tokenItem['field']) > 0) {
|
||||
$this->filterFieldWithBuilder($query, $tokenItem['field'], $tokenItem['value'], $tokenItem['boolean']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}//end if
|
||||
|
||||
return $index;
|
||||
};
|
||||
|
||||
$parseTokens($tokens, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of model fields visible to the current user.
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return array The array of field names.
|
||||
*/
|
||||
public function fields(Model $model)
|
||||
{
|
||||
$visibleFields = $model->getVisible();
|
||||
if (empty($visibleFields) === true) {
|
||||
$visibleFields = $model->getConnection()
|
||||
->getSchemaBuilder()
|
||||
->getColumnListing($model->getTable());
|
||||
}
|
||||
|
||||
$appends = $model->getAppends();
|
||||
if(is_array($appends) === true) {
|
||||
$visibleFields = array_merge($visibleFields, $appends);
|
||||
}
|
||||
|
||||
return $visibleFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the passed Model to an array
|
||||
*
|
||||
* @param Model $model The model to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transform(Model $model)
|
||||
{
|
||||
$result = $model->toArray();
|
||||
|
||||
$fields = $this->fields($model);
|
||||
if(is_array($fields) === true) {
|
||||
$result = array_intersect_key($result, array_flip($fields));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the passed model viewable by the current user?
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return boolean Is the model viewable.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the model creatable by the current user?
|
||||
*
|
||||
* @return boolean Is the model creatable.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the passed model updateable by the current user?
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return boolean Is the model updateable.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the passed model destroyable by the current user?
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return boolean Is the model destroyable.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
110
app/Conductors/EventConductor.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\InvalidCastException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EventConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Event';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = 'start_at';
|
||||
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||
$builder
|
||||
->where('status', '!=', 'draft')
|
||||
->where('publish_at', '<=', now());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is visible.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow model to be visible.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
if (strtolower($model->status) === 'draft' || Carbon::parse($model->publish_at)->isFuture() === true) {
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/events') === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/events') === true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
109
app/Conductors/MediaConductor.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MediaConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Media';
|
||||
|
||||
/**
|
||||
* The default sorting field
|
||||
* @var string
|
||||
*/
|
||||
protected $sort = 'created_at';
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of model fields visible to the current user.
|
||||
*
|
||||
* @param Model $model The model in question.
|
||||
* @return array The array of field names.
|
||||
*/
|
||||
public function fields(Model $model)
|
||||
{
|
||||
$fields = parent::fields($model);
|
||||
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/media') === false) {
|
||||
$fields = arrayRemoveItem($fields, 'permission');
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a scope query on the collection before anything else.
|
||||
*
|
||||
* @param Builder $builder The builder in use.
|
||||
* @return void
|
||||
*/
|
||||
public function scope(Builder $builder)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null) {
|
||||
$builder->whereNull('permission');
|
||||
} else {
|
||||
$builder->whereNull('permission')->orWhereIn('permission', $user->permissions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is visible.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow model to be visible.
|
||||
*/
|
||||
public static function viewable(Model $model)
|
||||
{
|
||||
if ($model->permission !== null) {
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission($model->permission) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is creatable.
|
||||
*
|
||||
* @return boolean Allow creating model.
|
||||
*/
|
||||
public static function creatable()
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && (strcasecmp($model->user_id, $user->id) === 0 || $user->hasPermission('admin/media') === true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && ($model->user_id === $user->id || $user->hasPermission('admin/media') === true));
|
||||
}
|
||||
}
|
||||
109
app/Conductors/PostConductor.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
39
app/Conductors/SubscriptionConductor.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SubscriptionConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Subscription';
|
||||
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && ((strcasecmp($model->email, $user->email) === 0 && $user->email_verified_at !== null) || $user->hasPermission('admin/subscriptions') === true));
|
||||
}
|
||||
}
|
||||
78
app/Conductors/UserConductor.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Conductors;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserConductor extends Conductor
|
||||
{
|
||||
/**
|
||||
* The Model Class
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\User';
|
||||
|
||||
|
||||
/**
|
||||
* Return the visible API fields.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return string[] The fields visible.
|
||||
*/
|
||||
public function fields(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/users') === false) {
|
||||
return ['id', 'username'];
|
||||
}
|
||||
|
||||
return parent::fields($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the passed Model to an array
|
||||
*
|
||||
* @param Model $model The model to transform.
|
||||
* @return array The transformed model.
|
||||
*/
|
||||
public function transform(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
$data = $model->toArray();
|
||||
|
||||
if ($user === null || ($user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0)) {
|
||||
$fields = ['id', 'username'];
|
||||
$data = arrayLimitKeys($data, $fields);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is updatable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow updating model.
|
||||
*/
|
||||
public static function updatable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user !== null) {
|
||||
return ($user->hasPermission('admin/users') === true || strcasecmp($user->id, $model->id) === 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the current model is destroyable.
|
||||
*
|
||||
* @param Model $model The model.
|
||||
* @return boolean Allow deleting model.
|
||||
*/
|
||||
public static function destroyable(Model $model)
|
||||
{
|
||||
$user = auth()->user();
|
||||
return ($user !== null && $user->hasPermission('admin/users') === true);
|
||||
}
|
||||
}
|
||||
169
app/Enum/CurlErrorCodes.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enum;
|
||||
|
||||
class CurlErrorCodes extends Enum
|
||||
{
|
||||
public const CURLE_UNSUPPORTED_PROTOCOL = 1;
|
||||
public const CURLE_FAILED_INIT = 2;
|
||||
public const CURLE_URL_MALFORMAT = 3;
|
||||
public const CURLE_URL_MALFORMAT_USER = 4;
|
||||
public const CURLE_COULDNT_RESOLVE_PROXY = 5;
|
||||
public const CURLE_COULDNT_RESOLVE_HOST = 6;
|
||||
public const CURLE_COULDNT_CONNECT = 7;
|
||||
public const CURLE_FTP_WEIRD_SERVER_REPLY = 8;
|
||||
public const CURLE_REMOTE_ACCESS_DENIED = 9;
|
||||
public const CURLE_FTP_WEIRD_PASS_REPLY = 11;
|
||||
public const CURLE_FTP_WEIRD_PASV_REPLY = 13;
|
||||
public const CURLE_FTP_WEIRD_227_FORMAT = 14;
|
||||
public const CURLE_FTP_CANT_GET_HOST = 15;
|
||||
public const CURLE_FTP_COULDNT_SET_TYPE = 17;
|
||||
public const CURLE_PARTIAL_FILE = 18;
|
||||
public const CURLE_FTP_COULDNT_RETR_FILE = 19;
|
||||
public const CURLE_QUOTE_ERROR = 21;
|
||||
public const CURLE_HTTP_RETURNED_ERROR = 22;
|
||||
public const CURLE_WRITE_ERROR = 23;
|
||||
public const CURLE_UPLOAD_FAILED = 25;
|
||||
public const CURLE_READ_ERROR = 26;
|
||||
public const CURLE_OUT_OF_MEMORY = 27;
|
||||
public const CURLE_OPERATION_TIMEDOUT = 28;
|
||||
public const CURLE_FTP_PORT_FAILED = 30;
|
||||
public const CURLE_FTP_COULDNT_USE_REST = 31;
|
||||
public const CURLE_RANGE_ERROR = 33;
|
||||
public const CURLE_HTTP_POST_ERROR = 34;
|
||||
public const CURLE_SSL_CONNECT_ERROR = 35;
|
||||
public const CURLE_BAD_DOWNLOAD_RESUME = 36;
|
||||
public const CURLE_FILE_COULDNT_READ_FILE = 37;
|
||||
public const CURLE_LDAP_CANNOT_BIND = 38;
|
||||
public const CURLE_LDAP_SEARCH_FAILED = 39;
|
||||
public const CURLE_FUNCTION_NOT_FOUND = 41;
|
||||
public const CURLE_ABORTED_BY_CALLBACK = 42;
|
||||
public const CURLE_BAD_FUNCTION_ARGUMENT = 43;
|
||||
public const CURLE_INTERFACE_FAILED = 45;
|
||||
public const CURLE_TOO_MANY_REDIRECTS = 47;
|
||||
public const CURLE_UNKNOWN_TELNET_OPTION = 48;
|
||||
public const CURLE_TELNET_OPTION_SYNTAX = 49;
|
||||
public const CURLE_PEER_FAILED_VERIFICATION = 51;
|
||||
public const CURLE_GOT_NOTHING = 52;
|
||||
public const CURLE_SSL_ENGINE_NOTFOUND = 53;
|
||||
public const CURLE_SSL_ENGINE_SETFAILED = 54;
|
||||
public const CURLE_SEND_ERROR = 55;
|
||||
public const CURLE_RECV_ERROR = 56;
|
||||
public const CURLE_SSL_CERTPROBLEM = 58;
|
||||
public const CURLE_SSL_CIPHER = 59;
|
||||
public const CURLE_SSL_CACERT = 60;
|
||||
public const CURLE_BAD_CONTENT_ENCODING = 61;
|
||||
public const CURLE_LDAP_INVALID_URL = 62;
|
||||
public const CURLE_FILESIZE_EXCEEDED = 63;
|
||||
public const CURLE_USE_SSL_FAILED = 64;
|
||||
public const CURLE_SEND_FAIL_REWIND = 65;
|
||||
public const CURLE_SSL_ENGINE_INITFAILED = 66;
|
||||
public const CURLE_LOGIN_DENIED = 67;
|
||||
public const CURLE_TFTP_NOTFOUND = 68;
|
||||
public const CURLE_TFTP_PERM = 69;
|
||||
public const CURLE_REMOTE_DISK_FULL = 70;
|
||||
public const CURLE_TFTP_ILLEGAL = 71;
|
||||
public const CURLE_TFTP_UNKNOWNID = 72;
|
||||
public const CURLE_REMOTE_FILE_EXISTS = 73;
|
||||
public const CURLE_TFTP_NOSUCHUSER = 74;
|
||||
public const CURLE_CONV_FAILED = 75;
|
||||
public const CURLE_CONV_REQD = 76;
|
||||
public const CURLE_SSL_CACERT_BADFILE = 77;
|
||||
public const CURLE_REMOTE_FILE_NOT_FOUND = 78;
|
||||
public const CURLE_SSH = 79;
|
||||
public const CURLE_SSL_SHUTDOWN_FAILED = 80;
|
||||
public const CURLE_AGAIN = 81;
|
||||
public const CURLE_SSL_CRL_BADFILE = 82;
|
||||
public const CURLE_SSL_ISSUER_ERROR = 83;
|
||||
public const CURLE_FTP_PRET_FAILED = 84;
|
||||
public const CURLE_RTSP_CSEQ_ERROR = 85;
|
||||
public const CURLE_RTSP_SESSION_ERROR = 86;
|
||||
public const CURLE_FTP_BAD_FILE_LIST = 87;
|
||||
public const CURLE_CHUNK_FAILED = 88;
|
||||
|
||||
|
||||
/**
|
||||
* Curl Error messages
|
||||
* @var string[]
|
||||
*/
|
||||
public static $messages = [
|
||||
1 => 'Unsupported protocol.',
|
||||
2 => 'Failed initalization.',
|
||||
3 => 'Invalid URL format.',
|
||||
4 => 'CURLE_URL_MALFORMAT_USER.',
|
||||
5 => 'Could not resolve proxy.',
|
||||
6 => 'Could not resolve host.',
|
||||
7 => 'Could not connect to host.',
|
||||
8 => 'Invalid reply from FTP server.',
|
||||
9 => 'Access denied on host.',
|
||||
11 => 'Invalid pass reply from FTP server.',
|
||||
13 => 'Invalid pasv reply from FTP server.',
|
||||
14 => 'Invalid 227 format from FTP server.',
|
||||
15 => 'Could not get FTP host.',
|
||||
17 => 'Could not set type for FTP transfer.',
|
||||
18 => 'Invalid partial size.',
|
||||
19 => 'Could not retrieve file from FTP server.',
|
||||
21 => 'Quote error.',
|
||||
22 => 'HTTP server returned error.',
|
||||
23 => 'File write error.',
|
||||
25 => 'Upload file error.',
|
||||
26 => 'File read error.',
|
||||
27 => 'Out of memory.',
|
||||
28 => 'File transfer timed out.',
|
||||
30 => 'Invalid port for FTP server.',
|
||||
31 => 'Could not use rest for FTP server.',
|
||||
33 => 'File range error.',
|
||||
34 => 'Invalid POST for HTTP server.',
|
||||
35 => 'SSL connectio error.',
|
||||
36 => 'Invalid resume download.',
|
||||
37 => 'Could not read file.',
|
||||
38 => 'Could not bind to LDAP.',
|
||||
39 => 'LDAP search failed.',
|
||||
41 => 'Function not found.',
|
||||
42 => 'Aborted by callback.',
|
||||
43 => 'Bad function argument.',
|
||||
45 => 'Interface failed.',
|
||||
47 => 'Too many redirects.',
|
||||
48 => 'Unknown telnet option.',
|
||||
49 => 'Telnet option syntax invalid.',
|
||||
51 => 'Peer failed verification.',
|
||||
52 => 'Did not receive any data.',
|
||||
53 => 'SSL engine was not found.',
|
||||
54 => 'SSL engine failed.',
|
||||
55 => 'Send data error.',
|
||||
56 => 'Receive data error.',
|
||||
58 => 'SSL certificate error.',
|
||||
59 => 'SSL cipher error.',
|
||||
60 => 'SSL CACertificate failed.',
|
||||
61 => 'Invalid content encoding.',
|
||||
62 => 'Invalid LDAP url.',
|
||||
63 => 'Filesize exceeded.',
|
||||
64 => 'SSL Failed.',
|
||||
65 => 'CURLE_SEND_FAIL_REWIND.',
|
||||
66 => 'SSL engine initalization failed.',
|
||||
67 => 'CURLE_LOGIN_DENIED.',
|
||||
68 => 'CURLE_TFTP_NOTFOUND.',
|
||||
69 => 'CURLE_TFTP_PERM.',
|
||||
70 => 'CURLE_REMOTE_DISK_FULL.',
|
||||
71 => 'CURLE_TFTP_ILLEGAL.',
|
||||
72 => 'CURLE_TFTP_UNKNOWNID.',
|
||||
73 => 'Remote file already exists.',
|
||||
74 => 'No such user on FTP server.',
|
||||
75 => 'Conversion failed.',
|
||||
76 => 'Conversion required.',
|
||||
77 => 'SSL CACertificate bad file.',
|
||||
78 => 'Remove file not found.',
|
||||
79 => 'SSH error.',
|
||||
80 => 'SSL Shutdown failed.',
|
||||
81 => 'Again.',
|
||||
82 => 'SSL bad CRL file.',
|
||||
83 => 'SSL issuer error.',
|
||||
84 => 'FTP pret failed.',
|
||||
85 => 'CURLE_RTSP_CSEQ_ERROR.',
|
||||
86 => 'CURLE_RTSP_SESSION_ERROR.',
|
||||
87 => 'CURLE_FTP_BAD_FILE_LIST.',
|
||||
88 => 'CURLE_CHUNK_FAILED.',
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
@@ -6,6 +6,13 @@ use ReflectionClass;
|
||||
|
||||
class Enum
|
||||
{
|
||||
/**
|
||||
* Message list
|
||||
*
|
||||
* @var array<string<static>>
|
||||
*/
|
||||
public static $messages = [];
|
||||
|
||||
/**
|
||||
* Caches reflections of enum subclasses.
|
||||
*
|
||||
@@ -47,4 +54,18 @@ class Enum
|
||||
{
|
||||
return array_values(static::getReflection()->getConstants());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a message from the enum subclass
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMessage(int $messageIndex, string $defaultMessage = 'Unknown'): string
|
||||
{
|
||||
if(array_key_exists($messageIndex, self::$messages) === true) {
|
||||
return self::$messages[$messageIndex];
|
||||
}
|
||||
|
||||
return $defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class AuditFilter
|
||||
{
|
||||
// public static function filter(Collection $collection): array
|
||||
// {
|
||||
// $collection->transform(function ($item, $key) {
|
||||
// $row = $item->toArray();
|
||||
|
||||
// unset($row['user_type']);
|
||||
// unset($row['auditable_type']);
|
||||
|
||||
// if (array_key_exists('password', $row['old_values'])) {
|
||||
// $row['old_values']['password'] = '###';
|
||||
// }
|
||||
// if (array_key_exists('password', $row['new_values'])) {
|
||||
// $row['new_values']['password'] = '###';
|
||||
// }
|
||||
|
||||
// return $row;
|
||||
// });
|
||||
|
||||
// return $collection->toArray();
|
||||
// }
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Event;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
|
||||
class EventFilter extends FilterAbstract
|
||||
{
|
||||
/**
|
||||
* Class name of Model
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Event';
|
||||
|
||||
/**
|
||||
* Default column sorting (prefix with - for descending)
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $defaultSort = '-start_at';
|
||||
|
||||
/**
|
||||
* Filter columns for q param
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $q = [
|
||||
'_' => ['title','content'],
|
||||
'location' => ['location','address'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the user can view the media model
|
||||
*
|
||||
* @param Event $event The event instance.
|
||||
* @param mixed $user The current logged in user.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function viewable(Event $event, mixed $user)
|
||||
{
|
||||
return (strcasecmp($event->status, 'draft') !== 0 && $event->publish_at <= now())
|
||||
|| $user?->hasPermission('admin/events') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the prebuild query to limit results
|
||||
*
|
||||
* @param EloquentBuilder $builder The builder instance.
|
||||
* @param mixed $user The current logged in user.
|
||||
* @return EloquentBuilder|null
|
||||
*/
|
||||
protected function prebuild(Builder $builder, mixed $user)
|
||||
{
|
||||
if (
|
||||
$user?->hasPermission('admin/events') !== true
|
||||
) {
|
||||
return $builder
|
||||
->where('status', '!=', 'draft')
|
||||
->where('publish_at', '<=', now());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,596 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Schema;
|
||||
|
||||
abstract class FilterAbstract
|
||||
{
|
||||
/**
|
||||
* The model class to filter
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $class;
|
||||
|
||||
/**
|
||||
* The filter request
|
||||
*
|
||||
* @var \Illuminate\Http\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* The models table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = '';
|
||||
|
||||
/**
|
||||
* Array of columns that can be filtered by the api
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $filterable = null;
|
||||
|
||||
/**
|
||||
* Default column sorting (prefix with - for descending)
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $defaultSort = 'id';
|
||||
|
||||
/**
|
||||
* Default collection result limit
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $defaultLimit = 50;
|
||||
|
||||
/**
|
||||
* Found records from query
|
||||
* @var integer
|
||||
*/
|
||||
protected $foundTotal = 0;
|
||||
|
||||
/**
|
||||
* Maximum collection result limit
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $maxLimit = 100;
|
||||
|
||||
/**
|
||||
* Only return these attributes in the results
|
||||
* (minus any excludes)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $only = [];
|
||||
|
||||
/**
|
||||
* Exclude these attributes from the results
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $exclude = [];
|
||||
|
||||
/**
|
||||
* Filter columns for q param
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $q = [];
|
||||
|
||||
|
||||
/**
|
||||
* Filter constructor.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request Request object.
|
||||
*/
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only include the specified attributes in the results.
|
||||
*
|
||||
* @param string|array $only Only return these attributes.
|
||||
* @return void
|
||||
*/
|
||||
public function only(mixed $only)
|
||||
{
|
||||
if (is_array($only) === true) {
|
||||
$this->only = $only;
|
||||
} else {
|
||||
$this->only = [$only];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude the specified attributes in the results.
|
||||
*
|
||||
* @param string|array $exclude Attributes to exclude.
|
||||
* @return void
|
||||
*/
|
||||
public function exclude(mixed $exclude)
|
||||
{
|
||||
if (is_array($exclude) === true) {
|
||||
$this->exclude = $exclude;
|
||||
} else {
|
||||
$this->exclude = [$exclude];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model is viewable by the user
|
||||
*
|
||||
* @param mixed $model Model instance.
|
||||
* @param mixed $user Current user.
|
||||
* @return boolean
|
||||
*/
|
||||
// protected function viewable(mixed $model, mixed $user)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Prepend action to the builder to limit the results
|
||||
*
|
||||
* @param Builder $builder Builder instance.
|
||||
* @param mixed $user Current user.
|
||||
* @return Builder|null
|
||||
*/
|
||||
// protected function prebuild(Builder $builder, mixed $user)
|
||||
// {
|
||||
// return $builder;
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of attributes visible in the results
|
||||
*
|
||||
* @param array $attributes Attributes currently visible.
|
||||
* @param User|null $user Current logged in user or null.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function seeAttributes(array $attributes, mixed $user)
|
||||
{
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all the requested filters if available.
|
||||
*
|
||||
* @param Model $model Model object to filter. If null create query.
|
||||
* @return Builder|Model
|
||||
*/
|
||||
public function filter(Model $model = null)
|
||||
{
|
||||
$this->foundTotal = 0;
|
||||
|
||||
$builder = $this->class::query();
|
||||
|
||||
/* Get the related model */
|
||||
$classModel = $model;
|
||||
if ($model === null) {
|
||||
$classModel = $builder->getModel();
|
||||
}
|
||||
|
||||
/* Get table name */
|
||||
if ($this->table === '') {
|
||||
if ($model === null) {
|
||||
$this->table = $classModel->getTable();
|
||||
} else {
|
||||
$this->table = $model->getTable();
|
||||
}
|
||||
}
|
||||
|
||||
/* Run query prebuilder or viewable */
|
||||
if ($model === null) {
|
||||
if (method_exists($this, 'prebuild') === true) {
|
||||
$prebuilder = $this->prebuild($builder, $this->request->user());
|
||||
if ($prebuilder instanceof Builder) {
|
||||
$builder = $prebuilder;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (method_exists($this, 'viewable') === true) {
|
||||
if ($this->viewable($model, $this->request->user()) === false) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get attributes from table or use 'only' */
|
||||
$attributes = [];
|
||||
if (is_array($this->only) === true && count($this->only) > 0) {
|
||||
$attributes = $this->only;
|
||||
} else {
|
||||
$attributes = Schema::getColumnListing($this->table);
|
||||
}
|
||||
|
||||
/* Run attribute modifiers*/
|
||||
$modifiedAttribs = $this->seeAttributes($attributes, $this->request->user());
|
||||
if (is_array($modifiedAttribs) === true) {
|
||||
$attributes = $modifiedAttribs;
|
||||
}
|
||||
|
||||
foreach ($attributes as $key => $column) {
|
||||
$method = 'see' . Str::studly($column) . 'Attribute';
|
||||
if (
|
||||
method_exists($this, $method) === true &&
|
||||
$this->$method($this->request->user()) === false
|
||||
) {
|
||||
unset($attributes[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($this->exclude) === true && count($this->exclude) > 0) {
|
||||
$attributes = array_diff($attributes, $this->exclude);
|
||||
}
|
||||
|
||||
/* Setup attributes and appends */
|
||||
// $attributesAppends = array_merge($attributes, $classModel->getAppends());
|
||||
|
||||
/* Apply ?fields= request to attributes */
|
||||
if ($this->request->has('fields') === true) {
|
||||
$attributes = array_intersect($attributes, explode(',', $this->request->fields));
|
||||
}
|
||||
|
||||
/* Hide remaining attributes in model (if present) and return */
|
||||
if ($model !== null) {
|
||||
// TODO: Also show $this->request->fields that are appends
|
||||
|
||||
$model->makeHidden(array_diff(Schema::getColumnListing($this->table), $attributes));
|
||||
return $model;
|
||||
}
|
||||
|
||||
/* Are there attributes left? */
|
||||
if (count($attributes) === 0) {
|
||||
$this->foundTotal = 0;
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
/* apply select! */
|
||||
$builder->select($attributes);
|
||||
|
||||
/* Setup filterables if not present */
|
||||
if ($this->filterable === null) {
|
||||
$this->filterable = $attributes;
|
||||
}
|
||||
|
||||
/* Filter values */
|
||||
$filterRequest = array_filter($this->request->only(array_intersect($attributes, $this->filterable)));
|
||||
$this->builderArrayFilter($builder, $filterRequest);
|
||||
|
||||
if (is_array($this->q) === true && count($this->q) > 0) {
|
||||
$qQueries = [];
|
||||
foreach ($this->q as $key => $value) {
|
||||
if (is_array($value) === true) {
|
||||
$qKey = $key === '_' ? '' : $key;
|
||||
foreach ($value as $subvalue) {
|
||||
$qQueries[$key][$subvalue] = $this->request->get("q" . $qKey);
|
||||
}
|
||||
} elseif ($this->request->has("q") === true) {
|
||||
$qQueries['_'][$value] = $this->request->get("q");
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($qQueries as $key => $value) {
|
||||
$builder->where(function ($query) use ($value) {
|
||||
$this->builderArrayFilter($query, $value, 'or');
|
||||
});
|
||||
}
|
||||
}//end if
|
||||
|
||||
/* Apply sorting */
|
||||
$sortList = $this->defaultSort;
|
||||
if ($this->request->has('sort') === true) {
|
||||
$sortList = explode(',', $this->request->sort);
|
||||
}
|
||||
|
||||
/* Transform sort list to array */
|
||||
if (is_array($sortList) === false) {
|
||||
if (strlen($sortList) > 0) {
|
||||
$sortList = [$sortList];
|
||||
} else {
|
||||
$sortList = [];
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove non-viewable attributes from sort list */
|
||||
if (count($sortList) > 0) {
|
||||
$sortList = array_filter($sortList, function ($item) use ($attributes) {
|
||||
$parsedItem = $item;
|
||||
if (substr($parsedItem, 0, 1) === '-') {
|
||||
$parsedItem = substr($parsedItem, 1);
|
||||
}
|
||||
|
||||
return in_array($parsedItem, $attributes);
|
||||
});
|
||||
}
|
||||
|
||||
/* Do we have any sort element left? */
|
||||
if (count($sortList) > 0) {
|
||||
foreach ($sortList as $sortAttribute) {
|
||||
$prefix = substr($sortAttribute, 0, 1);
|
||||
$direction = 'asc';
|
||||
|
||||
if (in_array($prefix, ['-', '+']) === true) {
|
||||
$sortAttribute = substr($sortAttribute, 1);
|
||||
if ($prefix === '-') {
|
||||
$direction = 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
$builder->orderBy($sortAttribute, $direction);
|
||||
}//end foreach
|
||||
}//end if
|
||||
|
||||
/* save found count */
|
||||
$this->foundTotal = $builder->count();
|
||||
|
||||
/* Apply result limit */
|
||||
$limit = $this->defaultLimit;
|
||||
if ($this->request->has('limit') === true) {
|
||||
$limit = intval($this->request->limit);
|
||||
}
|
||||
if ($limit < 1) {
|
||||
$limit = 1;
|
||||
}
|
||||
if ($limit > $this->maxLimit && $this->maxLimit !== 0) {
|
||||
$limit = $this->maxLimit;
|
||||
}
|
||||
|
||||
$builder->limit($limit);
|
||||
|
||||
/* Apply page offset */
|
||||
if ($this->request->has('page') === true) {
|
||||
$page = intval($this->request->page);
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$builder->offset((intval($this->request->page) - 1) * $limit);
|
||||
}
|
||||
|
||||
/* run spot run */
|
||||
$collection = $builder->get();
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter content based on the filterRequest
|
||||
* @param mixed $builder Builder object
|
||||
* @param array $filterRequest Filter key/value
|
||||
* @param string $defaultBoolean Default where boolean
|
||||
* @return void
|
||||
*/
|
||||
protected function builderArrayFilter(mixed $builder, array $filterRequest, string $defaultBoolean = 'and')
|
||||
{
|
||||
foreach ($filterRequest as $filterAttribute => $filterValue) {
|
||||
$tags = [];
|
||||
$boolean = $defaultBoolean;
|
||||
|
||||
$matches = preg_split('/(?<!\\\\)"/', $filterValue, -1, PREG_SPLIT_OFFSET_CAPTURE);
|
||||
foreach ($matches as $idx => $match_info) {
|
||||
if (($idx % 2) === true) {
|
||||
if (substr($filterValue, ($match_info[1] - 2), 1) === ',') {
|
||||
$tags[] = ['operator' => '', 'tag' => stripslashes(trim($match_info[0]))];
|
||||
} else {
|
||||
$tags[(count($tags) - 1)]['tag'] .= stripslashes(trim($match_info[0]));
|
||||
}
|
||||
} else {
|
||||
$innerTags = [$match_info[0]];
|
||||
if (strpos($match_info[0], ',') !== false) {
|
||||
$innerTags = preg_split('/(?<!\\\\),/', $match_info[0]);
|
||||
}
|
||||
|
||||
foreach ($innerTags as $tag) {
|
||||
$tag = stripslashes(trim($tag));
|
||||
if (strlen($tag) > 0) {
|
||||
$operator = '=';
|
||||
|
||||
$single = substr($tag, 0, 1);
|
||||
$double = substr($tag . ' ', 0, 2); // add empty space incase len $tag < 2
|
||||
|
||||
// check for operators at start
|
||||
if (in_array($double, ['!=', '<>', '><', '>=', '<=', '=>', '=<']) === true) {
|
||||
if ($double === '<>' || $double === '><') {
|
||||
$double = '!=';
|
||||
} elseif ($double === '=>') {
|
||||
$double = '>=';
|
||||
} elseif ($double === '=<') {
|
||||
$double == '>=';
|
||||
}
|
||||
|
||||
$operator = $double;
|
||||
$tag = substr($tag, 2);
|
||||
} else {
|
||||
if (in_array($single, ['=', '!', '>', '<', '~', '%']) === true) {
|
||||
if ($single === '=') {
|
||||
$single = '=='; // a single '=' is actually a double '=='
|
||||
}
|
||||
|
||||
$operator = $single;
|
||||
$tag = substr($tag, 1);
|
||||
}
|
||||
}//end if
|
||||
|
||||
$tags[] = ['operator' => $operator, 'tag' => $tag];
|
||||
}//end if
|
||||
}//end foreach
|
||||
}//end if
|
||||
}//end foreach
|
||||
|
||||
if (count($tags) > 1) {
|
||||
$boolean = 'or';
|
||||
}
|
||||
|
||||
foreach ($tags as $tag_data) {
|
||||
$operator = $tag_data['operator'];
|
||||
$value = $tag_data['tag'];
|
||||
$table = $this->table;
|
||||
$column = $filterAttribute;
|
||||
|
||||
if (($dotPos = strpos($filterAttribute, '.')) !== false) {
|
||||
$table = substr($filterAttribute, 0, $dotPos);
|
||||
$column = substr($filterAttribute, ($dotPos + 1));
|
||||
}
|
||||
|
||||
$columnType = DB::getSchemaBuilder()->getColumnType($table, $column);
|
||||
|
||||
if (
|
||||
in_array($columnType, ['tinyint', 'smallint', 'mediumint', 'int', 'integer', 'bigint',
|
||||
'decimal', 'float', 'double', 'real', 'double precision'
|
||||
]) === true
|
||||
) {
|
||||
if (in_array($operator, ['=', '>', '<', '>=', '<=', '%', '!']) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnType = 'numeric';
|
||||
} elseif (in_array($columnType, ['date', 'time', 'datetime', 'timestamp', 'year']) === true) {
|
||||
if (in_array($operator, ['=', '>', '<', '>=', '<=', '!']) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnType = 'datetime';
|
||||
} elseif (
|
||||
in_array($columnType, ['string', 'char', 'varchar', 'timeblob', 'blob', 'mediumblob',
|
||||
'longblob', 'tinytext', 'text', 'mediumtext', 'longtext', 'enum'
|
||||
]) === true
|
||||
) {
|
||||
if (in_array($operator, ['=', '==', '!', '!=', '~']) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnType = 'text';
|
||||
|
||||
if ($value === "''" || $value === '""') {
|
||||
$value = '';
|
||||
} elseif (strcasecmp($value, 'null') !== 0) {
|
||||
if ($operator === '!') {
|
||||
$operator = 'NOT LIKE';
|
||||
$value = '%' . $value . '%';
|
||||
} elseif ($operator === '=') {
|
||||
$operator = 'LIKE';
|
||||
$value = '%' . $value . '%';
|
||||
} elseif ($operator === '~') {
|
||||
$operator = 'SOUNDS LIKE';
|
||||
} elseif ($operator === '==') {
|
||||
$operator = '=';
|
||||
}
|
||||
}
|
||||
} elseif ($columnType === 'boolean') {
|
||||
if (in_array($operator, ['=', '!']) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strtolower($value) === 'true') {
|
||||
$value = 1;
|
||||
} elseif (strtolower($value) === 'false') {
|
||||
$value = 0;
|
||||
}
|
||||
}//end if
|
||||
|
||||
$betweenSeperator = strpos($value, '<>');
|
||||
if (
|
||||
$operator === '=' && $betweenSeperator !== false && in_array($columnType, ['numeric',
|
||||
'datetime'
|
||||
]) === true
|
||||
) {
|
||||
$value = explode('<>', $value);
|
||||
$operator = '<>';
|
||||
}
|
||||
|
||||
if ($operator !== '') {
|
||||
$this->builderWhere($builder, $table, $column, $operator, $value, $boolean);
|
||||
}
|
||||
}//end foreach
|
||||
}//end foreach
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a where statement into the builder, taking the filter map into consideration
|
||||
*
|
||||
* @param Builder $builder Builder instance.
|
||||
* @param string $table Table name.
|
||||
* @param string $column Column name.
|
||||
* @param string $operator Where operator.
|
||||
* @param mixed $value Value to test.
|
||||
* @param string $boolean Use Or comparison.
|
||||
* @return void
|
||||
* @throws RuntimeException Error applying statement.
|
||||
* @throws InvalidArgumentException Error applying statement.
|
||||
*/
|
||||
protected function builderWhere(
|
||||
Builder &$builder,
|
||||
string $table,
|
||||
string $column,
|
||||
string $operator,
|
||||
mixed $value,
|
||||
string $boolean
|
||||
) {
|
||||
if (
|
||||
(is_string($value) === true && $operator !== '<>') || (is_array($value) === true && count($value) === 2 &&
|
||||
$operator === '<>')
|
||||
) {
|
||||
if ($table !== '' && $table !== $this->table) {
|
||||
$builder->whereHas($table, function ($query) use ($column, $operator, $value, $boolean) {
|
||||
if ($operator !== '<>') {
|
||||
if (strcasecmp($value, 'null') === 0) {
|
||||
if ($operator === '!') {
|
||||
$query->whereNotNull($column, $boolean);
|
||||
} else {
|
||||
$query->whereNull($column, $boolean);
|
||||
}
|
||||
} else {
|
||||
$query->where($column, $operator, $value, $boolean);
|
||||
}
|
||||
} else {
|
||||
$query->whereBetween($column, $value, $boolean);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if ($operator !== '<>') {
|
||||
if (strcasecmp($value, 'null') === 0) {
|
||||
if ($operator === '!') {
|
||||
$builder->whereNotNull($column, $boolean);
|
||||
} else {
|
||||
$builder->whereNull($column, $boolean);
|
||||
}
|
||||
} else {
|
||||
$builder->where($column, $operator, $value, $boolean);
|
||||
}
|
||||
} else {
|
||||
$builder->whereBetween($column, $value, $boolean);
|
||||
}
|
||||
}//end if
|
||||
}//end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the found total of items
|
||||
* @return integer
|
||||
*/
|
||||
public function foundTotal()
|
||||
{
|
||||
return $this->foundTotal;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Media;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
|
||||
class MediaFilter extends FilterAbstract
|
||||
{
|
||||
/**
|
||||
* Class name of Model
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Media';
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the user can view the media model
|
||||
*
|
||||
* @param Media $media The media instance.
|
||||
* @param mixed $user The current logged in user.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function viewable(Media $media, mixed $user)
|
||||
{
|
||||
if (empty($media->permission) === false) {
|
||||
return ($user?->hasPermission('admin/media') || $user?->hasPermission($media->permission));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the prebuild query to limit results
|
||||
*
|
||||
* @param EloquentBuilder $builder The builder instance.
|
||||
* @param mixed $user The current logged in user.
|
||||
* @return EloquentBuilder|null
|
||||
*/
|
||||
protected function prebuild(Builder $builder, mixed $user)
|
||||
{
|
||||
if ($user === null) {
|
||||
return $builder->whereNull('permission');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the permission attribute in the results
|
||||
*
|
||||
* @param User|null $user Current logged in user or null.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function seePermissionAttribute(mixed $user)
|
||||
{
|
||||
return ($user?->hasPermission('admin/media'));
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Post;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
|
||||
class PostFilter extends FilterAbstract
|
||||
{
|
||||
/**
|
||||
* Class name of Model
|
||||
* @var string
|
||||
*/
|
||||
protected $class = '\App\Models\Post';
|
||||
|
||||
/**
|
||||
* Default column sorting (prefix with - for descending)
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $defaultSort = '-publish_at';
|
||||
|
||||
|
||||
/**
|
||||
* Determine if the user can view the media model
|
||||
*
|
||||
* @param Post $post The post instance.
|
||||
* @param mixed $user The current logged in user.
|
||||
* @return boolean
|
||||
*/
|
||||
protected function viewable(Post $post, mixed $user)
|
||||
{
|
||||
if ($user?->hasPermission('admin/posts') !== true) {
|
||||
return ($post->publish_at <= now());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the prebuild query to limit results
|
||||
*
|
||||
* @param EloquentBuilder $builder The builder instance.
|
||||
* @param mixed $user The current logged in user.
|
||||
* @return EloquentBuilder|null
|
||||
*/
|
||||
protected function prebuild(Builder $builder, mixed $user)
|
||||
{
|
||||
if ($user?->hasPermission('admin/posts') !== true) {
|
||||
return $builder->where('publish_at', '<=', Carbon::now());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UserFilter extends FilterAbstract
|
||||
{
|
||||
/**
|
||||
* The model class to filter
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $class = '\App\Models\User';
|
||||
|
||||
|
||||
/**
|
||||
* Return an array of attributes visible in the results
|
||||
*
|
||||
* @param array $attributes Attributes currently visible.
|
||||
* @param User|null $user Current logged in user or null.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function seeAttributes(array $attributes, mixed $user)
|
||||
{
|
||||
if ($user?->hasPermission('admin/users') !== true) {
|
||||
return ['id', 'username'];
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/Helpers/Array.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/* Array Helper Functions */
|
||||
|
||||
|
||||
/**
|
||||
* Remove an item from an array.
|
||||
*
|
||||
* @param array $arr The array to check.
|
||||
* @param string|array $item The item or items to remove.
|
||||
* @return array The filtered array.
|
||||
*/
|
||||
function arrayRemoveItem(array $arr, string|array $item): array
|
||||
{
|
||||
$filteredArr = $arr;
|
||||
|
||||
if (is_string($item) === true) {
|
||||
$item = [$item];
|
||||
}
|
||||
|
||||
foreach ($item as $str) {
|
||||
$filteredArr = array_filter($arr, function ($item) use ($str) {
|
||||
return $item !== $str;
|
||||
});
|
||||
}
|
||||
|
||||
return $filteredArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with specified the keys
|
||||
*
|
||||
* @param array $arr The array to filter.
|
||||
* @param string|array $keys The keys to keep.
|
||||
* @return array The filtered array.
|
||||
*/
|
||||
function arrayLimitKeys(array $arr, array $keys): array
|
||||
{
|
||||
return array_intersect_key($arr, array_flip($keys));
|
||||
}
|
||||
@@ -121,31 +121,34 @@ class ApiController extends Controller
|
||||
/**
|
||||
* Return resource data
|
||||
*
|
||||
* @param array|Model|Collection $data Resource data.
|
||||
* @param array|null $appendData Data to append to response.
|
||||
* @param integer $respondCode Resource code.
|
||||
* @param array|Model|Collection $data Resource data.
|
||||
* @param array $options Respond options.
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
protected function respondAsResource(
|
||||
mixed $data,
|
||||
mixed $appendData = null,
|
||||
int $respondCode = HttpResponseCodes::HTTP_OK
|
||||
array $options = [],
|
||||
) {
|
||||
$isCollection = $options['isCollection'] ?? false;
|
||||
$appendData = $options['appendData'] ?? null;
|
||||
$resourceName = $options['resourceName'] ?? null;
|
||||
$respondCode = $options['respondCode'] ?? HttpResponseCodes::HTTP_OK;
|
||||
|
||||
if ($data === null || ($data instanceof Collection && $data->count() === 0)) {
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
|
||||
$resourceName = $this->resourceName;
|
||||
if(is_null($resourceName) === true || empty($resourceName) === true) {
|
||||
$resourceName = $this->resourceName;
|
||||
}
|
||||
|
||||
if ($this->resourceName === '') {
|
||||
if(is_null($resourceName) === true || empty($resourceName) === true) {
|
||||
$resourceName = get_class($this);
|
||||
$resourceName = substr($resourceName, (strrpos($resourceName, '\\') + 1));
|
||||
$resourceName = substr($resourceName, 0, strpos($resourceName, 'Controller'));
|
||||
$resourceName = strtolower($resourceName);
|
||||
}
|
||||
|
||||
$is_multiple = true;
|
||||
|
||||
$dataArray = [];
|
||||
if ($data instanceof Collection) {
|
||||
$dataArray = $data->toArray();
|
||||
@@ -157,7 +160,7 @@ class ApiController extends Controller
|
||||
}
|
||||
|
||||
$resource = [];
|
||||
if ($is_multiple === true) {
|
||||
if ($isCollection === true) {
|
||||
$resource = [Str::plural($resourceName) => $dataArray];
|
||||
} else {
|
||||
$resource = [Str::singular($resourceName) => $dataArray];
|
||||
|
||||
84
app/Http/Controllers/Api/AttachmentController.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Attachment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AttachmentController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->except(['store', 'destroyByEmail']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Attachment $attachment
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Attachment $attachment)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ class AuthController extends ApiController
|
||||
|
||||
return $this->respondAsResource(
|
||||
$user->makeVisible(['permissions']),
|
||||
['token' => $token]
|
||||
['appendData' => ['token' => $token]]
|
||||
);
|
||||
}//end if
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Filters\EventFilter;
|
||||
use App\Http\Requests\EventRequest;
|
||||
use App\Models\Event;
|
||||
use App\Conductors\EventConductor;
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Http\Requests\EventRequest;
|
||||
use App\Models\Media;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventController extends ApiController
|
||||
@@ -22,56 +24,71 @@ class EventController extends ApiController
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param EventFilter $filter The event filter.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(EventFilter $filter)
|
||||
public function index(Request $request)
|
||||
{
|
||||
return $this->respondAsResource(
|
||||
$filter->filter(),
|
||||
['total' => $filter->foundTotal()]
|
||||
);
|
||||
}
|
||||
list($collection, $total) = EventConductor::request($request);
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param EventRequest $request The event store request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(EventRequest $request)
|
||||
{
|
||||
$event = Event::create($request->all());
|
||||
return $this->respondAsResource(
|
||||
(new EventFilter($request))->filter($event),
|
||||
null,
|
||||
HttpResponseCodes::HTTP_CREATED
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param EventFilter $filter The event filter.
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(EventFilter $filter, Event $event)
|
||||
public function show(Request $request, Event $event)
|
||||
{
|
||||
return $this->respondAsResource($filter->filter($event));
|
||||
if (EventConductor::viewable($event) === true) {
|
||||
return $this->respondAsResource(EventConductor::model($request, $event));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\EventRequest $request The request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(EventRequest $request)
|
||||
{
|
||||
if (EventConductor::creatable() === true) {
|
||||
$event = Event::create($request->all());
|
||||
return $this->respondAsResource(
|
||||
EventConductor::model($request, $event),
|
||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||
);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param EventRequest $request The event update request.
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @param \App\Http\Requests\EventRequest $request The endpoint request.
|
||||
* @param \App\Models\Event $event The specified event.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(EventRequest $request, Event $event)
|
||||
{
|
||||
$event->update($request->all());
|
||||
return $this->respondAsResource((new EventFilter($request))->filter($event));
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
$event->update($request->all());
|
||||
return $this->respondAsResource(EventConductor::model($request, $event));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +99,140 @@ class EventController extends ApiController
|
||||
*/
|
||||
public function destroy(Event $event)
|
||||
{
|
||||
$event->delete();
|
||||
return $this->respondNoContent();
|
||||
if (EventConductor::destroyable($event) === true) {
|
||||
$event->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The post model.
|
||||
* @return JsonResponse Returns the post attachments.
|
||||
* @throws InvalidFormatException
|
||||
* @throws BindingResolutionException
|
||||
* @throws InvalidCastException
|
||||
*/
|
||||
public function getAttachments(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::viewable($event) === true) {
|
||||
$medium = $event->attachments->map(function ($attachment) {
|
||||
return $attachment->media;
|
||||
});
|
||||
|
||||
return $this->respondAsResource(MediaConductor::collection($request, $medium), ['isCollection' => true, 'resourceName' => 'attachment']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an attachment related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The post model.
|
||||
* @return JsonResponse The response.
|
||||
* @throws BindingResolutionException
|
||||
* @throws MassAssignmentException
|
||||
*/
|
||||
public function storeAttachment(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
if ($request->has("medium") && Media::find($request->medium)) {
|
||||
$event->attachments()->create(['media_id' => $request->medium]);
|
||||
return $this->respondCreated();
|
||||
}
|
||||
|
||||
return $this->respondWithErrors(['media' => 'The media ID was not found']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/replace attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The related model.
|
||||
* @return JsonResponse
|
||||
* @throws BindingResolutionException
|
||||
* @throws MassAssignmentException
|
||||
*/
|
||||
public function updateAttachments(Request $request, Event $event)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
$mediaIds = $request->attachments;
|
||||
if (is_array($mediaIds) === false) {
|
||||
$mediaIds = explode(',', $request->attachments);
|
||||
}
|
||||
|
||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
||||
$attachments = $event->attachments;
|
||||
|
||||
// Delete attachments that are not in $mediaIds
|
||||
foreach ($attachments as $attachment) {
|
||||
if (!in_array($attachment->media_id, $mediaIds)) {
|
||||
$attachment->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create new attachments for media IDs that are not already in $post->attachments()
|
||||
foreach ($mediaIds as $mediaId) {
|
||||
$found = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id == $mediaId) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$event->attachments()->create(['media_id' => $mediaId]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific related attachment.
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The model.
|
||||
* @param Media $medium The attachment medium.
|
||||
* @return JsonResponse
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function deleteAttachment(Request $request, Event $event, Media $medium)
|
||||
{
|
||||
if (EventConductor::updatable($event) === true) {
|
||||
$attachments = $event->attachments;
|
||||
$deleted = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id === $medium->id) {
|
||||
$attachment->delete();
|
||||
$deleted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted) {
|
||||
// Attachment was deleted successfully
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
// Attachment with matching media ID was not found
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
126
app/Http/Controllers/Api/LogController.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LogController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth:sanctum')
|
||||
->only(['show']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param Request $request The log request.
|
||||
* @param string $name The log name.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, string $name)
|
||||
{
|
||||
if ($request->user()?->hasPermission('logs/' . $name) === true) {
|
||||
switch (strtolower($name)) {
|
||||
case 'discord':
|
||||
$data = [];
|
||||
|
||||
$log = $request->get('log');
|
||||
if ($log === null) {
|
||||
$log = ['output', 'error'];
|
||||
} else {
|
||||
$log = explode(',', strtolower($log));
|
||||
}
|
||||
|
||||
$lines = intval($request->get('lines', 50));
|
||||
if ($lines > 100) {
|
||||
$lines = 100;
|
||||
} elseif ($lines < 0) {
|
||||
$lines = 1;
|
||||
}
|
||||
|
||||
$before = $request->get('before');
|
||||
if ($before !== null) {
|
||||
$before = preg_split("/([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/", $before, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
if (count($before) !== 6) {
|
||||
$before = null;
|
||||
}
|
||||
}
|
||||
|
||||
$after = $request->get('after');
|
||||
if ($after !== null) {
|
||||
$after = preg_split("/([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/", $after, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
if (count($after) !== 6) {
|
||||
$after = null;
|
||||
}
|
||||
}
|
||||
|
||||
$logFiles = [
|
||||
[
|
||||
'name' => 'output',
|
||||
'path' => '/home/discordbot/.pm2/logs/stemmech-discordbot-out-0.log'
|
||||
],[
|
||||
'name' => 'error',
|
||||
'path' => '/home/discordbot/.pm2/logs/stemmech-discordbot-error-0.log'
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($logFiles as $logFile) {
|
||||
if (in_array($logFile['name'], $log) === true) {
|
||||
$logContent = '';
|
||||
|
||||
if (file_exists($logFile['path']) === true) {
|
||||
$logContent = file_get_contents($logFile['path']);
|
||||
}
|
||||
|
||||
$logArray = preg_split("/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}: (?:(?!\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}: )[\s\S])*)/", $logContent, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
|
||||
$logContent = '';
|
||||
$logLineCount = 0;
|
||||
$logLineSkip = false;
|
||||
foreach (array_reverse($logArray) as $logLine) {
|
||||
$lineDate = preg_split("/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}): /", $logLine, -1, (PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY));
|
||||
if (count($lineDate) >= 6) {
|
||||
$logLineSkip = false;
|
||||
|
||||
// Is line before
|
||||
if ($before !== null && ($lineDate[0] > $before[0] || $lineDate[1] > $before[1] || $lineDate[2] > $before[2] || $lineDate[3] > $before[3] || $lineDate[4] > $before[4] || $lineDate[5] > $before[5])) {
|
||||
$logLineSkip = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is line after
|
||||
if ($after !== null && ($after[0] > $lineDate[0] || $after[1] > $lineDate[1] || $after[2] > $lineDate[2] || $after[3] > $lineDate[3] || $after[4] > $lineDate[4] || $after[5] > $lineDate[5])) {
|
||||
$logLineSkip = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$logLineCount += 1;
|
||||
}
|
||||
|
||||
if ($logLineCount > $lines) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($logLineSkip === false) {
|
||||
$logContent .= $logLine;
|
||||
}
|
||||
}//end foreach
|
||||
|
||||
$data[$logFile['name']] = $logContent;
|
||||
}//end if
|
||||
}//end foreach
|
||||
|
||||
return $this->respondJson([
|
||||
'log' => $data
|
||||
]);
|
||||
}//end switch
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Filters\MediaFilter;
|
||||
use App\Http\Requests\MediaStoreRequest;
|
||||
use App\Http\Requests\MediaUpdateRequest;
|
||||
use App\Http\Requests\MediaRequest;
|
||||
use App\Models\Media;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
|
||||
class MediaController extends ApiController
|
||||
@@ -26,135 +24,146 @@ class MediaController extends ApiController
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \App\Filters\MediaFilter $filter Created filter object.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(MediaFilter $filter)
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = MediaConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$filter->filter(),
|
||||
['total' => $filter->foundTotal()]
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param MediaFilter $filter The request filter.
|
||||
* @param Media $medium The request media.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Media $medium The request media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(MediaFilter $filter, Media $medium)
|
||||
public function show(Request $request, Media $medium)
|
||||
{
|
||||
return $this->respondAsResource($filter->filter($medium));
|
||||
if (MediaConductor::viewable($medium) === true) {
|
||||
return $this->respondAsResource(MediaConductor::model($request, $medium));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new media resource
|
||||
*
|
||||
* @param MediaStoreRequest $request The uploaded media.
|
||||
* @param \App\Http\Requests\MediaRequest $request The uploaded media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(MediaStoreRequest $request)
|
||||
public function store(MediaRequest $request)
|
||||
{
|
||||
$file = $request->file('file');
|
||||
if ($file === null) {
|
||||
return $this->respondError(['file' => 'An error occurred uploading the file to the server.']);
|
||||
}
|
||||
|
||||
if ($file->isValid() !== true) {
|
||||
switch ($file->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return $this->respondTooLarge();
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return $this->respondError(['file' => 'The file upload was interrupted.']);
|
||||
default:
|
||||
return $this->respondError(['file' => 'An error occurred uploading the file to the server.']);
|
||||
if (MediaConductor::creatable() === true) {
|
||||
$file = $request->file('file');
|
||||
if ($file === null) {
|
||||
return $this->respondWithErrors(['file' => 'The browser did not upload the file correctly to the server.']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($file->getSize() > Media::maxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
if ($file->isValid() !== true) {
|
||||
switch ($file->getError()) {
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
return $this->respondTooLarge();
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
return $this->respondWithErrors(['file' => 'The file upload was interrupted.']);
|
||||
default:
|
||||
return $this->respondWithErrors(['file' => 'An error occurred uploading the file to the server.']);
|
||||
}
|
||||
}
|
||||
|
||||
$title = $file->getClientOriginalName();
|
||||
$mime = $file->getMimeType();
|
||||
$fileInfo = Media::store($file, empty($request->input('permission')));
|
||||
if ($fileInfo === null) {
|
||||
return $this->respondError(
|
||||
['file' => 'The file could not be stored on the server'],
|
||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$request->merge([
|
||||
'title' => $title,
|
||||
'mime' => $mime,
|
||||
'name' => $fileInfo['name'],
|
||||
'size' => filesize($fileInfo['path'])
|
||||
]);
|
||||
|
||||
$media = $request->user()->media()->create($request->all());
|
||||
return $this->respondAsResource((new MediaFilter($request))->filter($media));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the media resource in storage.
|
||||
*
|
||||
* @param MediaUpdateRequest $request The update request.
|
||||
* @param \App\Models\Media $medium The specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(MediaUpdateRequest $request, Media $medium)
|
||||
{
|
||||
if ((new MediaFilter($request))->filter($medium) === null) {
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
|
||||
$file = $request->file('file');
|
||||
if ($file !== null) {
|
||||
if ($file->getSize() > Media::maxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
|
||||
$oldPath = $medium->path();
|
||||
$title = $file->getClientOriginalName();
|
||||
$mime = $file->getMimeType();
|
||||
$fileInfo = Media::store($file, empty($request->input('permission')));
|
||||
if ($fileInfo === null) {
|
||||
return $this->respondError(
|
||||
return $this->respondWithErrors(
|
||||
['file' => 'The file could not be stored on the server'],
|
||||
HttpResponseCodes::HTTP_INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if (file_exists($oldPath) === true) {
|
||||
unlink($oldPath);
|
||||
}
|
||||
|
||||
$request->merge([
|
||||
'title' => $file->getClientOriginalName(),
|
||||
'mime' => $file->getMimeType(),
|
||||
'title' => $title,
|
||||
'mime' => $mime,
|
||||
'name' => $fileInfo['name'],
|
||||
'size' => filesize($fileInfo['path'])
|
||||
]);
|
||||
|
||||
$media = $request->user()->media()->create($request->all());
|
||||
return $this->respondAsResource(
|
||||
MediaConductor::model($request, $media),
|
||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||
);
|
||||
}//end if
|
||||
|
||||
$medium->update($request->all());
|
||||
return $this->respondWithTransformer($file);
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the media resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\MediaRequest $request The update request.
|
||||
* @param \App\Models\Media $medium The specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(MediaRequest $request, Media $medium)
|
||||
{
|
||||
if (MediaConductor::updatable($medium) === true) {
|
||||
$file = $request->file('file');
|
||||
if ($file !== null) {
|
||||
if ($file->getSize() > Media::maxUploadSize()) {
|
||||
return $this->respondTooLarge();
|
||||
}
|
||||
|
||||
$oldPath = $medium->path();
|
||||
$fileInfo = Media::store($file, empty($request->input('permission')));
|
||||
if ($fileInfo === null) {
|
||||
return $this->respondWithErrors(
|
||||
['file' => 'The file could not be stored on the server'],
|
||||
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));
|
||||
}//end if
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param Request $request Request instance.
|
||||
* @param \App\Models\Media $medium Specified media file.
|
||||
* @param \App\Models\Media $medium Specified media file.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Request $request, Media $medium)
|
||||
public function destroy(Media $medium)
|
||||
{
|
||||
if ((new MediaFilter($request))->filter($medium) !== null) {
|
||||
if (MediaConductor::destroyable($medium) === true) {
|
||||
if (file_exists($medium->path()) === true) {
|
||||
unlink($medium->path());
|
||||
}
|
||||
@@ -163,14 +172,14 @@ class MediaController extends ApiController
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondNotFound();
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param Request $request Request instance.
|
||||
* @param \App\Models\Media $medium Specified media.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Media $medium Specified media.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function download(Request $request, Media $medium)
|
||||
@@ -218,6 +227,7 @@ class MediaController extends ApiController
|
||||
$headerExpires = $updated_at->addMonth()->toRfc2822String();
|
||||
}//end if
|
||||
|
||||
// deepcode ignore InsecureHash: Browsers expect Etag to be a md5 hash
|
||||
$headerEtag = md5($updated_at->format('U'));
|
||||
$headerLastModified = $updated_at->toRfc2822String();
|
||||
|
||||
|
||||
229
app/Http/Controllers/Api/OCRController.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use thiagoalessio\TesseractOCR\TesseractOCR;
|
||||
use FFMpeg;
|
||||
use App\Enum\CurlErrorCodes;
|
||||
|
||||
class OCRController extends ApiController
|
||||
{
|
||||
/**
|
||||
* ApplicationController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// $this->middleware('auth:sanctum')
|
||||
// ->only(['show']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param Request $request The log request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
// if ($request->user()?->hasPermission('logs/' . $name) === true) {
|
||||
$url = $request->get('url');
|
||||
if ($url !== null) {
|
||||
$data = ['ocr' => []];
|
||||
|
||||
$filters = $request->get('filters', ['tesseract']);
|
||||
if(is_array($filters) === false) {
|
||||
$filters = explode(',', $filters);
|
||||
}
|
||||
|
||||
$tesseractOEM = $request->get('tesseract.oem');
|
||||
$tesseractDigits = $request->get('tesseract.digits');
|
||||
$tesseractAllowlist = $request->get('tesseract.allowlist');
|
||||
|
||||
// Download URL
|
||||
$urlDownloadFilePath = tempnam(sys_get_temp_dir(), 'download');
|
||||
$maxDownloadSize = (1024 * 1024); // 1MB
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
// We need progress updates to break the connection mid-way
|
||||
curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // more progress info
|
||||
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function(
|
||||
$downloadSize, $downloaded, $uploadSize, $uploaded
|
||||
) use($maxDownloadSize) {
|
||||
return ($downloaded > $maxDownloadSize) ? 1 : 0;
|
||||
});
|
||||
|
||||
$curlResult = curl_exec($ch);
|
||||
$curlError = curl_errno($ch);
|
||||
$curlSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
|
||||
curl_close($ch);
|
||||
if($curlError !== 0) {
|
||||
$error = 'File size is larger then allowed';
|
||||
if($curlError !== CurlErrorCodes::CURLE_ABORTED_BY_CALLBACK) {
|
||||
$error = CurlErrorCodes::getMessage($curlError);
|
||||
}
|
||||
|
||||
return $this->respondWithErrors(['url' => $error]);
|
||||
}
|
||||
|
||||
// Save url file
|
||||
file_put_contents($urlDownloadFilePath, $curlResult);
|
||||
$urlDownloadFilePathBase = preg_replace('/\\.[^.\\s]{3,4}$/', '', $urlDownloadFilePath);
|
||||
|
||||
// tesseract (overall)
|
||||
$ocr = null;
|
||||
foreach($filters as $filterItem) {
|
||||
if(str_starts_with($filterItem, 'tesseract') === true) {
|
||||
$ocr = new TesseractOCR();
|
||||
$ocr->image($urlDownloadFilePath);
|
||||
if ($tesseractOEM !== null) {
|
||||
$ocr->oem($tesseractOEM);
|
||||
}
|
||||
if ($tesseractDigits !== null) {
|
||||
$ocr->digits();
|
||||
}
|
||||
if ($tesseractAllowlist !== null) {
|
||||
$ocr->allowlist($tesseractAllowlist);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Image Filter Function
|
||||
$tesseractImageFilterFunc = function($filter, $options = null) use($curlResult, $curlSize, $ocr) {
|
||||
$result = '';
|
||||
$img = imagecreatefromstring($curlResult);
|
||||
if ($img !== false && (($options !== null && imagefilter($img, $filter, $options) === true) || ($options === null && imagefilter($img, $filter) === true))) {
|
||||
|
||||
ob_start();
|
||||
imagepng($img);
|
||||
$imgData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$imgDataSize = strlen($imgData);
|
||||
|
||||
$ocr->imageData($imgData, $imgDataSize);
|
||||
imagedestroy($img);
|
||||
|
||||
$result = $ocr->run(500);
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
// Image Scale Function
|
||||
$tesseractImageScaleFunc = function($scaleFunc) use ($curlResult, $ocr) {
|
||||
$result = '';
|
||||
$srcImage = imagecreatefromstring($curlResult);
|
||||
$srcWidth = imagesx($srcImage);
|
||||
$srcHeight = imagesy($srcImage);
|
||||
|
||||
$dstWidth = $scaleFunc($srcWidth);
|
||||
$dstHeight = $scaleFunc($srcHeight);
|
||||
$dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
|
||||
|
||||
imagecopyresampled($dstImage, $srcImage, 0, 0, 0, 0, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
|
||||
|
||||
ob_start();
|
||||
imagepng($dstImage);
|
||||
$imgData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$imgDataSize = strlen($imgData);
|
||||
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($dstImage);
|
||||
|
||||
$ocr->imageData($imgData, $imgDataSize);
|
||||
$result = $ocr->run(500);
|
||||
return $result;
|
||||
};
|
||||
|
||||
// filter: tesseract
|
||||
if(in_array('tesseract', $filters) === true) {
|
||||
$data['ocr']['tesseract'] = $ocr->run(500);
|
||||
}
|
||||
|
||||
// filter: tesseract.grayscale
|
||||
if (in_array('tesseract.grayscale', $filters) === true) {
|
||||
$data['ocr']['tesseract.grayscale'] = $tesseractImageFilterFunc(IMG_FILTER_GRAYSCALE);
|
||||
}
|
||||
|
||||
// filter: tesseract.double_scale
|
||||
if (in_array('tesseract.double_scale', $filters) === true) {
|
||||
$data['ocr']['tesseract.double_scale'] = $tesseractImageScaleFunc(function($size) {
|
||||
return $size * 2;
|
||||
});
|
||||
}
|
||||
|
||||
// filter: tesseract.half_scale
|
||||
if (in_array('tesseract.half_scale', $filters) === true) {
|
||||
$data['ocr']['tesseract.half_scale'] = $tesseractImageScaleFunc(function($size) {
|
||||
return $size / 2;
|
||||
});
|
||||
}
|
||||
|
||||
// filter: tesseract.edgedetect
|
||||
if (in_array('tesseract.edgedetect', $filters) === true) {
|
||||
$data['ocr']['tesseract.edgedetect'] = $tesseractImageFilterFunc(IMG_FILTER_EDGEDETECT);
|
||||
}
|
||||
|
||||
// filter: tesseract.mean_removal
|
||||
if (in_array('tesseract.mean_removal', $filters) === true) {
|
||||
$data['ocr']['tesseract.mean_removal'] = $tesseractImageFilterFunc(IMG_FILTER_MEAN_REMOVAL);
|
||||
}
|
||||
|
||||
// filter: tesseract.negate
|
||||
if (in_array('tesseract.negate', $filters) === true) {
|
||||
$data['ocr']['tesseract.negate'] = $tesseractImageFilterFunc(IMG_FILTER_NEGATE);
|
||||
}
|
||||
|
||||
// filter: tesseract.pixelate
|
||||
if (in_array('tesseract.pixelate', $filters) === true) {
|
||||
$data['ocr']['tesseract.pixelate'] = $tesseractImageFilterFunc(IMG_FILTER_PIXELATE, 3);
|
||||
}
|
||||
|
||||
// filter: keras
|
||||
if(in_array('keras', $filters) === true) {
|
||||
$cmd = '/usr/bin/python3 ' . base_path() . '/scripts/keras_oc.py ' . urlencode($url);
|
||||
$command = escapeshellcmd($cmd);
|
||||
$output = shell_exec($cmd);
|
||||
if ($output !== null && strlen($output) > 0) {
|
||||
$output = substr($output, strpos($output, '----------START----------') + 25);
|
||||
} else {
|
||||
$output = '';
|
||||
}
|
||||
$data['ocr']['keras'] = $output;
|
||||
}
|
||||
|
||||
unlink($urlDownloadFilePath);
|
||||
return $this->respondJson($data);
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors(['url' => 'url is missing']);
|
||||
}
|
||||
|
||||
// $ffmpeg = FFMpeg\FFMpeg::create();
|
||||
|
||||
// // Load the input video
|
||||
// $inputFile = $ffmpeg->open('input.mp4');
|
||||
|
||||
// // Split the video into individual frames
|
||||
// $videoFrames = $inputFile->frames();
|
||||
// foreach ($videoFrames as $frame) {
|
||||
// // Save the frame as a PNG
|
||||
// $frame->save(new FFMpeg\Format\Video\PNG(), 'frame-' . $frame->getMetadata('pts') . '.png');
|
||||
|
||||
// // Pass the PNG to Tesseract for processing
|
||||
// exec("tesseract frame-" . $frame->getMetadata('pts') . ".png output");
|
||||
// }
|
||||
|
||||
// // Read the output from Tesseract
|
||||
// $text = file_get_contents("output.txt");
|
||||
|
||||
// // Do something with the text from Tesseract
|
||||
// echo $text;
|
||||
}
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\MediaConductor;
|
||||
use App\Conductors\PostConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Filters\PostFilter;
|
||||
use App\Http\Requests\PostStoreRequest;
|
||||
use App\Http\Requests\PostUpdateRequest;
|
||||
use App\Http\Requests\PostRequest;
|
||||
use App\Models\Media;
|
||||
use App\Models\Post;
|
||||
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 PostController extends ApiController
|
||||
@@ -27,56 +33,70 @@ class PostController extends ApiController
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \App\Filters\PostFilter $filter Post filter request.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(PostFilter $filter)
|
||||
public function index(Request $request)
|
||||
{
|
||||
list($collection, $total) = PostConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$filter->filter(),
|
||||
['total' => $filter->foundTotal()]
|
||||
$collection,
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param PostFilter $filter The filter request.
|
||||
* @param \App\Models\Post $post The post model.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Post $post The post model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(PostFilter $filter, Post $post)
|
||||
public function show(Request $request, Post $post)
|
||||
{
|
||||
return $this->respondAsResource($filter->filter($post));
|
||||
if (PostConductor::viewable($post) === true) {
|
||||
return $this->respondAsResource(PostConductor::model($request, $post));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param PostStoreRequest $request The post store request.
|
||||
* @param \App\Http\Requests\PostRequest $request The user request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(PostStoreRequest $request)
|
||||
public function store(PostRequest $request)
|
||||
{
|
||||
$post = Post::create($request->all());
|
||||
return $this->respondAsResource(
|
||||
(new PostFilter($request))->filter($post),
|
||||
null,
|
||||
HttpResponseCodes::HTTP_CREATED
|
||||
);
|
||||
if (PostConductor::creatable() === true) {
|
||||
$post = Post::create($request->all());
|
||||
return $this->respondAsResource(
|
||||
PostConductor::model($request, $post),
|
||||
['respondCode' => HttpResponseCodes::HTTP_CREATED]
|
||||
);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param PostUpdateRequest $request The post update request.
|
||||
* @param \App\Models\Post $post The specified post.
|
||||
* @param \App\Http\Requests\PostRequest $request The post update request.
|
||||
* @param \App\Models\Post $post The specified post.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(PostUpdateRequest $request, Post $post)
|
||||
public function update(PostRequest $request, Post $post)
|
||||
{
|
||||
$post->update($request->all());
|
||||
return $this->respondAsResource((new PostFilter($request))->filter($post));
|
||||
if (PostConductor::updatable($post) === true) {
|
||||
$post->update($request->all());
|
||||
return $this->respondAsResource(PostConductor::model($request, $post));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +107,140 @@ class PostController extends ApiController
|
||||
*/
|
||||
public function destroy(Post $post)
|
||||
{
|
||||
$post->delete();
|
||||
return $this->respondNoContent();
|
||||
if (PostConductor::destroyable($post) === true) {
|
||||
$post->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The post model.
|
||||
* @return JsonResponse Returns the post attachments.
|
||||
* @throws InvalidFormatException
|
||||
* @throws BindingResolutionException
|
||||
* @throws InvalidCastException
|
||||
*/
|
||||
public function getAttachments(Request $request, Post $post)
|
||||
{
|
||||
if (PostConductor::viewable($post) === true) {
|
||||
$medium = $post->attachments->map(function ($attachment) {
|
||||
return $attachment->media;
|
||||
});
|
||||
|
||||
return $this->respondAsResource(MediaConductor::collection($request, $medium), ['isCollection' => true, 'resourceName' => 'attachment']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an attachment related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The post model.
|
||||
* @return JsonResponse The response.
|
||||
* @throws BindingResolutionException
|
||||
* @throws MassAssignmentException
|
||||
*/
|
||||
public function storeAttachment(Request $request, Post $post)
|
||||
{
|
||||
if (PostConductor::updatable($post) === true) {
|
||||
if($request->has("medium") && Media::find($request->medium)) {
|
||||
$post->attachments()->create(['media_id' => $request->medium]);
|
||||
return $this->respondCreated();
|
||||
}
|
||||
|
||||
return $this->respondWithErrors(['media' => 'The media ID was not found']);
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/replace attachments related to this model.
|
||||
*
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The related model.
|
||||
* @return JsonResponse
|
||||
* @throws BindingResolutionException
|
||||
* @throws MassAssignmentException
|
||||
*/
|
||||
public function updateAttachments(Request $request, Post $post)
|
||||
{
|
||||
if (PostConductor::updatable($post) === true) {
|
||||
$mediaIds = $request->attachments;
|
||||
if(is_array($mediaIds) === false) {
|
||||
$mediaIds = explode(',', $request->attachments);
|
||||
}
|
||||
|
||||
$mediaIds = array_map('trim', $mediaIds); // trim each media ID
|
||||
$attachments = $post->attachments;
|
||||
|
||||
// Delete attachments that are not in $mediaIds
|
||||
foreach ($attachments as $attachment) {
|
||||
if (!in_array($attachment->media_id, $mediaIds)) {
|
||||
$attachment->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Create new attachments for media IDs that are not already in $post->attachments()
|
||||
foreach ($mediaIds as $mediaId) {
|
||||
$found = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id == $mediaId) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$post->attachments()->create(['media_id' => $mediaId]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific related attachment.
|
||||
* @param Request $request The user request.
|
||||
* @param Post $post The model.
|
||||
* @param Media $medium The attachment medium.
|
||||
* @return JsonResponse
|
||||
* @throws BindingResolutionException
|
||||
*/
|
||||
public function deleteAttachment(Request $request, Post $post, Media $medium)
|
||||
{
|
||||
if (PostConductor::updatable($post) === true) {
|
||||
$attachments = $post->attachments;
|
||||
$deleted = false;
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->media_id === $medium->id) {
|
||||
$attachment->delete();
|
||||
$deleted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deleted) {
|
||||
// Attachment was deleted successfully
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
// Attachment with matching media ID was not found
|
||||
return $this->respondNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Conductors\SubscriptionConductor;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Models\Subscription;
|
||||
use App\Filters\SubscriptionFilter;
|
||||
use App\Http\Requests\SubscriptionRequest;
|
||||
use App\Jobs\SendEmailJob;
|
||||
use App\Mail\SubscriptionConfirm;
|
||||
use App\Mail\SubscriptionUnsubscribed;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SubscriptionController extends ApiController
|
||||
{
|
||||
@@ -23,58 +25,71 @@ class SubscriptionController extends ApiController
|
||||
/**
|
||||
* Display a listing of subscribers.
|
||||
*
|
||||
* @param \App\Filters\SubscriptionFilter $filter Filter object.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(SubscriptionFilter $filter)
|
||||
public function index(Request $request)
|
||||
{
|
||||
$collection = $filter->filter();
|
||||
list($collection, $total) = SubscriptionConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['total' => $filter->foundTotal()]
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\Subscription $subscription The subscription model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Request $request, Subscription $subscription)
|
||||
{
|
||||
if (SubscriptionConductor::viewable($subscription) === true) {
|
||||
return $this->respondAsResource(SubscriptionConductor::model($request, $subscription));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a subscriber email in the database.
|
||||
*
|
||||
* @param SubscriptionRequest $request The subscriber update request.
|
||||
* @param \App\Http\Requests\SubscriptionRequest $request The subscriber update request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(SubscriptionRequest $request)
|
||||
{
|
||||
if (Subscription::where('email', $request->email)->first() !== null) {
|
||||
return $this->respondWithErrors(['email' => 'This email address has already subscribed']);
|
||||
if (SubscriptionConductor::creatable() === true) {
|
||||
Subscription::create($request->all());
|
||||
dispatch((new SendEmailJob($request->email, new SubscriptionConfirm($request->email))))->onQueue('mail');
|
||||
|
||||
return $this->respondCreated();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
Subscription::create($request->all());
|
||||
dispatch((new SendEmailJob($request->email, new SubscriptionConfirm($request->email))))->onQueue('mail');
|
||||
|
||||
return $this->respondCreated();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the specified user.
|
||||
*
|
||||
* @param SubscriptionFilter $filter The subscription filter.
|
||||
* @param Subscription $subscription The subscription model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(SubscriptionFilter $filter, Subscription $subscription)
|
||||
{
|
||||
return $this->respondAsResource($filter->filter($subscription));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param SubscriptionRequest $request The subscription update request.
|
||||
* @param Subscription $subscription The specified subscription.
|
||||
* @param \App\Http\Requests\SubscriptionRequest $request The subscription update request.
|
||||
* @param \App\Models\Subscription $subscription The specified subscription.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(SubscriptionRequest $request, Subscription $subscription)
|
||||
{
|
||||
// if (EventConductor::updatable($event) === true) {
|
||||
// $event->update($request->all());
|
||||
// return $this->respondAsResource(EventConductor::model($request, $event));
|
||||
// }
|
||||
|
||||
// return $this->respondForbidden();
|
||||
|
||||
|
||||
// $input = [];
|
||||
// $updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password'];
|
||||
|
||||
@@ -103,14 +118,12 @@ class SubscriptionController extends ApiController
|
||||
*/
|
||||
public function destroy(Subscription $subscription)
|
||||
{
|
||||
// if ($user->hasPermission('admin/user') === false) {
|
||||
// return $this->respondForbidden();
|
||||
// }
|
||||
|
||||
$email = $subscription->email;
|
||||
|
||||
$subscription->delete();
|
||||
return $this->respondNoContent();
|
||||
if (SubscriptionConductor::destroyable($subscription) === true) {
|
||||
$subscription->delete();
|
||||
return $this->respondNoContent();
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Filters\UserFilter;
|
||||
use App\Http\Requests\UserUpdateRequest;
|
||||
use App\Http\Requests\UserStoreRequest;
|
||||
use App\Http\Requests\UserRequest;
|
||||
use App\Http\Requests\UserForgotPasswordRequest;
|
||||
use App\Http\Requests\UserForgotUsernameRequest;
|
||||
use App\Http\Requests\UserRegisterRequest;
|
||||
@@ -23,6 +21,7 @@ use App\Models\User;
|
||||
use App\Models\UserCode;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Conductors\UserConductor;
|
||||
|
||||
class UserController extends ApiController
|
||||
{
|
||||
@@ -48,96 +47,102 @@ class UserController extends ApiController
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \App\Filters\UserFilter $filter Filter object.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(UserFilter $filter)
|
||||
public function index(Request $request)
|
||||
{
|
||||
$collection = $filter->filter();
|
||||
list($collection, $total) = UserConductor::request($request);
|
||||
|
||||
return $this->respondAsResource(
|
||||
$collection,
|
||||
['total' => $filter->foundTotal()]
|
||||
['isCollection' => true,
|
||||
'appendData' => ['total' => $total]]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created user in the database.
|
||||
*
|
||||
* @param UserStoreRequest $request The user update request.
|
||||
* @param \App\Http\Requests\UserRequest $request The endpoint request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(UserStoreRequest $request)
|
||||
public function store(UserRequest $request)
|
||||
{
|
||||
if ($request->user()->hasPermission('admin/user') !== true) {
|
||||
if (UserConductor::creatable() === true) {
|
||||
$user = User::create($request->all());
|
||||
return $this->respondAsResource(UserConductor::model($request, $user), ['respondCode' => HttpResponseCodes::HTTP_CREATED]);
|
||||
} else {
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
$user = User::create($request->all());
|
||||
return $this->respondAsResource((new UserFilter($request))->filter($user), [], HttpResponseCodes::HTTP_CREATED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the specified user.
|
||||
*
|
||||
* @param UserFilter $filter The user filter.
|
||||
* @param User $user The user model.
|
||||
* @param \Illuminate\Http\Request $request The endpoint request.
|
||||
* @param \App\Models\User $user The user model.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(UserFilter $filter, User $user)
|
||||
public function show(Request $request, User $user)
|
||||
{
|
||||
return $this->respondAsResource($filter->filter($user));
|
||||
if (UserConductor::viewable($user) === true) {
|
||||
return $this->respondAsResource(UserConductor::model($request, $user));
|
||||
}
|
||||
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UserUpdateRequest $request The user update request.
|
||||
* @param User $user The specified user.
|
||||
* @param \App\Http\Requests\UserRequest $request The user update request.
|
||||
* @param \App\Models\User $user The specified user.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(UserUpdateRequest $request, User $user)
|
||||
public function update(UserRequest $request, User $user)
|
||||
{
|
||||
$input = [];
|
||||
$updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password'];
|
||||
if (UserConductor::updatable($user) === true) {
|
||||
$input = [];
|
||||
$updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password'];
|
||||
|
||||
if ($request->user()->hasPermission('admin/user') === true) {
|
||||
$updatable = array_merge($updatable, ['email_verified_at']);
|
||||
} elseif ($request->user()->is($user) !== true) {
|
||||
return $this->respondForbidden();
|
||||
if ($request->user()->hasPermission('admin/user') === true) {
|
||||
$updatable = array_merge($updatable, ['email_verified_at']);
|
||||
}
|
||||
|
||||
$input = $request->only($updatable);
|
||||
if (array_key_exists('password', $input) === true) {
|
||||
$input['password'] = Hash::make($request->input('password'));
|
||||
}
|
||||
|
||||
$user->update($input);
|
||||
|
||||
return $this->respondAsResource(UserConductor::model($request, $user));
|
||||
}
|
||||
|
||||
$input = $request->only($updatable);
|
||||
if (array_key_exists('password', $input) === true) {
|
||||
$input['password'] = Hash::make($request->input('password'));
|
||||
}
|
||||
|
||||
$user->update($input);
|
||||
|
||||
return $this->respondAsResource((new UserFilter($request))->filter($user));
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the user from the database.
|
||||
*
|
||||
* @param User $user The specified user.
|
||||
* @param \App\Models\User $user The specified user.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
if ($user->hasPermission('admin/user') === false) {
|
||||
return $this->respondForbidden();
|
||||
if (UserConductor::destroyable($user) === true) {
|
||||
$user->delete();
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
return $this->respondNoContent();
|
||||
return $this->respondForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user
|
||||
*
|
||||
* @param 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)
|
||||
@@ -171,7 +176,7 @@ class UserController extends ApiController
|
||||
/**
|
||||
* Sends an email with all the usernames registered at that address
|
||||
*
|
||||
* @param UserForgotUsernameRequest $request The forgot username request.
|
||||
* @param \App\Http\Requests\UserForgotUsernameRequest $request The forgot username request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function forgotUsername(UserForgotUsernameRequest $request)
|
||||
@@ -191,7 +196,7 @@ class UserController extends ApiController
|
||||
/**
|
||||
* Generates a new reset password code
|
||||
*
|
||||
* @param UserForgotPasswordRequest $request The reset password request.
|
||||
* @param \App\Http\Requests\UserForgotPasswordRequest $request The reset password request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function forgotPassword(UserForgotPasswordRequest $request)
|
||||
@@ -213,7 +218,7 @@ class UserController extends ApiController
|
||||
/**
|
||||
* Resets a user password
|
||||
*
|
||||
* @param UserResetPasswordRequest $request The reset password request.
|
||||
* @param \App\Http\Requests\UserResetPasswordRequest $request The reset password request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function resetPassword(UserResetPasswordRequest $request)
|
||||
@@ -240,14 +245,14 @@ class UserController extends ApiController
|
||||
}
|
||||
|
||||
return $this->respondError([
|
||||
'code' => 'The code was not found or has expired'
|
||||
'code' => 'The code was not found or has expired.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify an email code
|
||||
*
|
||||
* @param UserVerifyEmailRequest $request The verify email request.
|
||||
* @param \App\Http\Requests\UserVerifyEmailRequest $request The verify email request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function verifyEmail(UserVerifyEmailRequest $request)
|
||||
@@ -278,14 +283,14 @@ class UserController extends ApiController
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors([
|
||||
'code' => 'The code was not found or has expired'
|
||||
'code' => 'The code was not found or has expired.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend a new verify email
|
||||
*
|
||||
* @param 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)
|
||||
@@ -312,7 +317,7 @@ class UserController extends ApiController
|
||||
/**
|
||||
* Resend verification email
|
||||
*
|
||||
* @param UserResendVerifyEmailRequest $request The resend user request.
|
||||
* @param \App\Http\Requests\UserResendVerifyEmailRequest $request The resend user request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function resendVerifyEmailCode(UserResendVerifyEmailRequest $request)
|
||||
|
||||
@@ -14,10 +14,12 @@ class BaseRequest extends FormRequest
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
if (method_exists($this, 'postAuthorize') === true && request()->isMethod('post') === true) {
|
||||
if (request()->isMethod('post') === true && method_exists($this, 'postAuthorize') === true) {
|
||||
return $this->postAuthorize();
|
||||
} elseif (method_exists($this, 'putAuthorize') === true && request()->isMethod('put') === true) {
|
||||
} elseif ((request()->isMethod('put') === true || request()->isMethod('patch') === true) && method_exists($this, 'putAuthorize') === true) {
|
||||
return $this->putAuthorize();
|
||||
} elseif (request()->isMethod('delete') === true && method_exists($this, 'destroyAuthorize') === true) {
|
||||
return $this->deleteAuthorize();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -38,8 +40,8 @@ class BaseRequest extends FormRequest
|
||||
|
||||
if (method_exists($this, 'postRules') === true && request()->isMethod('post') === true) {
|
||||
$rules = $this->mergeRules($rules, $this->postRules());
|
||||
} elseif (method_exists($this, 'putRules') === true && request()->isMethod('put') === true) {
|
||||
$rules = $this->mergeRules($rules, $this->postRules());
|
||||
} elseif (method_exists($this, 'putRules') === true && (request()->isMethod('put') === true || request()->isMethod('patch') === true)) {
|
||||
$rules = $this->mergeRules($rules, $this->putRules());
|
||||
} elseif (method_exists($this, 'destroyRules') === true && request()->isMethod('delete') === true) {
|
||||
$rules = $this->mergeRules($rules, $this->destroyRules());
|
||||
}
|
||||
@@ -73,8 +75,8 @@ class BaseRequest extends FormRequest
|
||||
|
||||
if (is_array($collection2[$key]) === true) {
|
||||
$key_ruleset = array_merge($key_ruleset, $collection2[$key]);
|
||||
} elseif (is_string($collection1[$key]) === true) {
|
||||
$key_ruleset = array_merge($key_ruleset, explode('|', $collection1[$key]));
|
||||
} elseif (is_string($collection2[$key]) === true) {
|
||||
$key_ruleset = array_merge($key_ruleset, explode('|', $collection2[$key]));
|
||||
}
|
||||
|
||||
if (count($key_ruleset) > 0) {
|
||||
|
||||
@@ -2,31 +2,10 @@
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class EventRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function postAuthorize()
|
||||
{
|
||||
return $this->user()?->hasPermission('admin/events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function putAuthorize()
|
||||
{
|
||||
return $this->user()?->hasPermission('admin/events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the base rules to this request
|
||||
*
|
||||
@@ -44,14 +23,15 @@ class EventRequest extends BaseRequest
|
||||
'end_at' => 'date|after:start_date',
|
||||
'publish_at' => 'date|nullable',
|
||||
'status' => [
|
||||
Rule::in(['draft', 'open', 'closed', 'cancelled']),
|
||||
Rule::in(['draft', 'soon', 'open', 'closed', 'cancelled']),
|
||||
],
|
||||
'registration_type' => [
|
||||
Rule::in(['none', 'email', 'link']),
|
||||
Rule::in(['none', 'email', 'link', 'message']),
|
||||
],
|
||||
'registration_data' => [
|
||||
Rule::when(strcasecmp('email', $this->attributes->get('registration_type')) == 0, 'required|email'),
|
||||
Rule::when(strcasecmp('link', $this->attributes->get('registration_type')) == 0, 'required|url')
|
||||
Rule::when(strcasecmp('link', $this->attributes->get('registration_type')) == 0, 'required|url'),
|
||||
Rule::when(strcasecmp('message', $this->attributes->get('registration_type')) == 0, 'required|message'),
|
||||
],
|
||||
'hero' => 'uuid|exists:media,id',
|
||||
];
|
||||
|
||||
8
app/Http/Requests/MediaRequest.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
class MediaRequest extends BaseRequest
|
||||
{
|
||||
/* empty */
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MediaStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MediaUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
46
app/Http/Requests/PostRequest.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PostRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to POST requests.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'slug' => 'required|string|min:6|unique:posts',
|
||||
'title' => 'required|string|min:6|max:255',
|
||||
'publish_at' => 'required|date',
|
||||
'user_id' => 'required|uuid|exists:users,id',
|
||||
'content' => 'required|string|min:6',
|
||||
'hero' => 'required|uuid|exists:media,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to PUT request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function putRules()
|
||||
{
|
||||
return [
|
||||
'slug' => [
|
||||
'string',
|
||||
'min:6',
|
||||
Rule::unique('posts')->ignoreModel($this->post),
|
||||
],
|
||||
'title' => 'string|min:6|max:255',
|
||||
'publish_at' => 'date',
|
||||
'user_id' => 'uuid|exists:users,id',
|
||||
'content' => 'string|min:6',
|
||||
'hero' => 'uuid|exists:media,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class PostStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'slug' => 'string|min:6|unique:posts',
|
||||
'title' => 'string|min:6|max:255',
|
||||
'publish_at' => 'date',
|
||||
'user_id' => 'uuid|exists:users,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PostUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'slug' => [
|
||||
'string',
|
||||
'min:6',
|
||||
Rule::unique('posts')->ignoreModel($this->post),
|
||||
],
|
||||
'title' => 'string|min:6|max:255',
|
||||
'publish_at' => 'date',
|
||||
'user_id' => 'uuid|exists:users,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ class SubscriptionRequest extends BaseRequest
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'email' => 'required|email|unique:subscriptions',
|
||||
'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
@@ -31,4 +31,16 @@ class SubscriptionRequest extends BaseRequest
|
||||
'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the custom error messages.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'email.unique' => 'This email address has already subscribed',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
54
app/Http/Requests/UserRequest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UserRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Apply the additional POST base rules to this request
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'username' => 'required|string|max:255|min:4|unique:users',
|
||||
'first_name' => 'required|string|max:255|min:2',
|
||||
'last_name' => 'required|string|max:255|min:2',
|
||||
'email' => 'required|string|email|max:255',
|
||||
'phone' => ['string', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'email_verified_at' => 'date'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to PUT request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function putRules()
|
||||
{
|
||||
$user = $this->route('user');
|
||||
|
||||
return [
|
||||
'username' => [
|
||||
'string',
|
||||
'max:255',
|
||||
'min:4',
|
||||
Rule::unique('users')->ignore($user->id)->when(
|
||||
$this->username !== $user->username,
|
||||
function ($query) {
|
||||
return $query->where('username', $this->username);
|
||||
}
|
||||
),
|
||||
],
|
||||
'first_name' => 'string|max:255|min:2',
|
||||
'last_name' => 'string|max:255|min:2',
|
||||
'email' => 'string|email|max:255',
|
||||
'phone' => ['nullable','regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'password' => 'string|min:8'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'username' => 'required|string|max:255|min:4|unique:users',
|
||||
'first_name' => 'required|string|max:255|min:2',
|
||||
'last_name' => 'required|string|max:255|min:2',
|
||||
'email' => 'required|string|email|max:255',
|
||||
'phone' => ['string', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'email_verified_at' => 'date'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'username' => 'string|max:255|min:6|unique:users',
|
||||
'first_name' => 'string|max:255|min:2',
|
||||
'last_name' => 'string|max:255|min:2',
|
||||
'email' => 'string|email|max:255',
|
||||
'phone' => ['nullable','regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'password' => 'string|min:8'
|
||||
];
|
||||
}
|
||||
}
|
||||
37
app/Models/Attachment.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Attachment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'media_id',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get attachments attachable
|
||||
*/
|
||||
public function attachable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the media for this attachment.
|
||||
*/
|
||||
public function media()
|
||||
{
|
||||
return $this->belongsTo(Media::class);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,17 @@ class Event extends Model
|
||||
'registration_type',
|
||||
'registration_data',
|
||||
'hero',
|
||||
'content'
|
||||
'content',
|
||||
'price',
|
||||
'ages',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Get all of the post's attachments.
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
return $this->morphMany('App\Models\Attachment', 'attachable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
namespace App\Models;
|
||||
|
||||
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\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use Spatie\ImageOptimizer\OptimizerChainFactory;
|
||||
|
||||
class Media extends Model
|
||||
{
|
||||
@@ -168,6 +169,35 @@ class Media extends Model
|
||||
}
|
||||
|
||||
$path = Storage::disk($storage)->path($name);
|
||||
if (in_array($file->getClientOriginalExtension(), ['jpg', 'jpeg', 'png', 'gif']) === true) {
|
||||
// Generate additional image sizes
|
||||
$sizes = [
|
||||
'thumb' => [150, 150],
|
||||
'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
|
||||
$optimizerChain = OptimizerChainFactory::create();
|
||||
foreach ($images as $imagePath) {
|
||||
$optimizerChain->optimize($imagePath);
|
||||
}
|
||||
}//end if
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'path' => $path
|
||||
|
||||
@@ -27,7 +27,7 @@ class Post extends Model
|
||||
|
||||
|
||||
/**
|
||||
* Get the file user
|
||||
* Get the post user
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
@@ -35,4 +35,12 @@ class Post extends Model
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the post's attachments.
|
||||
*/
|
||||
public function attachments()
|
||||
{
|
||||
return $this->morphMany('App\Models\Attachment', 'attachable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,48 @@ class User extends Authenticatable implements Auditable
|
||||
return ($this->permissions()->where('permission', $permission)->first() !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Give permissions to the user
|
||||
*
|
||||
* @param string|array $permissions The permission(s) to give.
|
||||
* @return Collection
|
||||
*/
|
||||
public function givePermission($permissions)
|
||||
{
|
||||
if (!is_array($permissions)) {
|
||||
$permissions = [$permissions];
|
||||
}
|
||||
|
||||
$permissions = collect($permissions)->map(function ($permission) {
|
||||
return ['permission' => $permission];
|
||||
});
|
||||
|
||||
$existingPermissions = $this->permissions()->whereIn('permission', $permissions->pluck('permission'))->get();
|
||||
$newPermissions = $permissions->reject(function ($permission) use ($existingPermissions) {
|
||||
return $existingPermissions->contains('permission', $permission['permission']);
|
||||
});
|
||||
|
||||
return $this->permissions()->createMany($newPermissions->toArray());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Revoke permissions from the user
|
||||
*
|
||||
* @param string|array $permissions The permission(s) to revoke.
|
||||
* @return int
|
||||
*/
|
||||
public function revokePermission($permissions)
|
||||
{
|
||||
if (!is_array($permissions)) {
|
||||
$permissions = [$permissions];
|
||||
}
|
||||
|
||||
return $this->permissions()
|
||||
->whereIn('permission', $permissions)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of files of the user
|
||||
*
|
||||
|
||||
@@ -46,8 +46,28 @@ class RouteServiceProvider extends ServiceProvider
|
||||
*/
|
||||
protected function configureRateLimiting()
|
||||
{
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60)->by($request->user()?->id !== null ?: $request->ip());
|
||||
});
|
||||
// RateLimiter::for('api', function (Request $request) {
|
||||
// return Limit::perMinute(60)->by($request->user()?->id !== null ?: $request->ip());
|
||||
// });
|
||||
|
||||
$rateLimitEnabled = true;
|
||||
$user = auth()->user();
|
||||
|
||||
if (app()->environment('testing')) {
|
||||
$rateLimitEnabled = false;
|
||||
} elseif ($user !== null && $user->hasPermission('admin/ratelimit') === true) {
|
||||
// Admin users with the "admin/ratelimit" permission are not rate limited
|
||||
$rateLimitEnabled = false;
|
||||
}
|
||||
|
||||
if ($rateLimitEnabled === true) {
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(180)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
} else {
|
||||
RateLimiter::for('api', function () {
|
||||
return Limit::none();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
698
app/Services/AnimatedGifService.php
Normal file
@@ -0,0 +1,698 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class AnimatedGifService
|
||||
{
|
||||
/**
|
||||
* Check if a GIF file at a path is animated or not
|
||||
*
|
||||
* @param string $filenameOrBlob GIF file path or data blob if dataSize > 0.
|
||||
* @param integer $dataSize GIF blob size.
|
||||
* @return boolean GIF file/blob is animated.
|
||||
*/
|
||||
public static function isAnimatedGif(string $filenameOrBlob, int $dataSize = 0)
|
||||
{
|
||||
$regex = '#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s';
|
||||
$count = 0;
|
||||
|
||||
if ($dataSize > 0) {
|
||||
if (($fh = @fopen($filenameOrBlob, 'rb')) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$chunk = false;
|
||||
while (feof($fh) === false && $count < 2) {
|
||||
$chunk = ($chunk !== '' ? substr($chunk, -20) : "") . fread($fh, (1024 * 100)); //read 100kb at a time
|
||||
$count += preg_match_all($regex, $chunk, $matches);
|
||||
}
|
||||
|
||||
fclose($fh);
|
||||
} else {
|
||||
$count = preg_match_all($regex, $filenameOrBlob, $matches);
|
||||
}
|
||||
|
||||
return $count > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract frames of a GIF
|
||||
*
|
||||
* @param string $filenameOrBlob GIF filename path
|
||||
* @param integer $dataSize GIF blob size.
|
||||
* @param boolean $originalFrames Get original frames (with transparent background)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function extract(string $filenameOrBlob, int $dataSize = 0, $originalFrames = false)
|
||||
{
|
||||
if (self::isAnimatedGif($filenameOrBlob) === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->reset();
|
||||
$this->parseFramesInfo($filename);
|
||||
$prevImg = null;
|
||||
|
||||
for ($i = 0; $i < count($this->frameSources); $i++) {
|
||||
$this->frames[$i] = [];
|
||||
$this->frameDurations[$i] = $this->frames[$i]['duration'] = $this->frameSources[$i]['delay_time'];
|
||||
|
||||
$img = imagecreatefromstring($this->fileHeader["gifheader"] . $this->frameSources[$i]["graphicsextension"] . $this->frameSources[$i]["imagedata"] . chr(0x3b));
|
||||
|
||||
if (!$originalFrames) {
|
||||
if ($i > 0) {
|
||||
$prevImg = $this->frames[($i - 1)]['image'];
|
||||
} else {
|
||||
$prevImg = $img;
|
||||
}
|
||||
|
||||
$sprite = imagecreate($this->gifMaxWidth, $this->gifMaxHeight);
|
||||
imagesavealpha($sprite, true);
|
||||
|
||||
$transparent = imagecolortransparent($prevImg);
|
||||
|
||||
if ($transparent > -1 && imagecolorstotal($prevImg) > $transparent) {
|
||||
$actualTrans = imagecolorsforindex($prevImg, $transparent);
|
||||
imagecolortransparent($sprite, imagecolorallocate($sprite, $actualTrans['red'], $actualTrans['green'], $actualTrans['blue']));
|
||||
}
|
||||
|
||||
if ((int) $this->frameSources[$i]['disposal_method'] == 1 && $i > 0) {
|
||||
imagecopy($sprite, $prevImg, 0, 0, 0, 0, $this->gifMaxWidth, $this->gifMaxHeight);
|
||||
}
|
||||
|
||||
imagecopyresampled($sprite, $img, $this->frameSources[$i]["offset_left"], $this->frameSources[$i]["offset_top"], 0, 0, $this->gifMaxWidth, $this->gifMaxHeight, $this->gifMaxWidth, $this->gifMaxHeight);
|
||||
$img = $sprite;
|
||||
}//end if
|
||||
|
||||
$this->frameImages[$i] = $this->frames[$i]['image'] = $img;
|
||||
}//end for
|
||||
|
||||
return $this->frames;
|
||||
}
|
||||
}
|
||||
|
||||
class GifFrameExtractor
|
||||
{
|
||||
// Properties
|
||||
// ===================================================================================
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $gif;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $frames;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $frameDurations;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $frameImages;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $framePositions;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $frameDimensions;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*
|
||||
* (old: $this->index)
|
||||
*/
|
||||
private $frameNumber;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* (old: $this->imagedata)
|
||||
*/
|
||||
private $frameSources;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* (old: $this->fileHeader)
|
||||
*/
|
||||
private $fileHeader;
|
||||
|
||||
/**
|
||||
* @var integer The reader pointer in the file source
|
||||
*
|
||||
* (old: $this->pointer)
|
||||
*/
|
||||
private $pointer;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $gifMaxWidth;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $gifMaxHeight;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $totalDuration;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* (old: globaldata)
|
||||
*/
|
||||
private $globaldata;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* (old: orgvars)
|
||||
*/
|
||||
private $orgvars;
|
||||
|
||||
// Methods
|
||||
// ===================================================================================
|
||||
|
||||
|
||||
/**
|
||||
* Parse the frame informations contained in the GIF file
|
||||
*
|
||||
* @param string $filename GIF filename path
|
||||
*/
|
||||
private function parseFramesInfo($filename)
|
||||
{
|
||||
$this->openFile($filename);
|
||||
$this->parseGifHeader();
|
||||
$this->parseGraphicsExtension(0);
|
||||
$this->getApplicationData();
|
||||
$this->getApplicationData();
|
||||
$this->getFrameString(0);
|
||||
$this->parseGraphicsExtension(1);
|
||||
$this->getCommentData();
|
||||
$this->getApplicationData();
|
||||
$this->getFrameString(1);
|
||||
|
||||
while (!$this->checkByte(0x3b) && !$this->checkEOF()) {
|
||||
$this->getCommentData(1);
|
||||
$this->parseGraphicsExtension(2);
|
||||
$this->getFrameString(2);
|
||||
$this->getApplicationData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the gif header (old: get_gif_header)
|
||||
*/
|
||||
private function parseGifHeader()
|
||||
{
|
||||
$this->pointerForward(10);
|
||||
|
||||
if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) {
|
||||
$this->pointerForward(2);
|
||||
$this->pointerForward(pow(2, ($this->readBits($mybyte, 5, 3) + 1)) * 3);
|
||||
} else {
|
||||
$this->pointerForward(2);
|
||||
}
|
||||
|
||||
$this->fileHeader["gifheader"] = $this->dataPart(0, $this->pointer);
|
||||
|
||||
// Decoding
|
||||
$this->orgvars["gifheader"] = $this->fileHeader["gifheader"];
|
||||
$this->orgvars["background_color"] = $this->orgvars["gifheader"][11];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the application data of the frames (old: get_application_data)
|
||||
*/
|
||||
private function getApplicationData()
|
||||
{
|
||||
$startdata = $this->readByte(2);
|
||||
|
||||
if ($startdata == chr(0x21) . chr(0xff)) {
|
||||
$start = ($this->pointer - 2);
|
||||
$this->pointerForward($this->readByteInt());
|
||||
$this->readDataStream($this->readByteInt());
|
||||
$this->fileHeader["applicationdata"] = $this->dataPart($start, ($this->pointer - $start));
|
||||
} else {
|
||||
$this->pointerRewind(2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the comment data of the frames (old: get_comment_data)
|
||||
*/
|
||||
private function getCommentData()
|
||||
{
|
||||
$startdata = $this->readByte(2);
|
||||
|
||||
if ($startdata == chr(0x21) . chr(0xfe)) {
|
||||
$start = ($this->pointer - 2);
|
||||
$this->readDataStream($this->readByteInt());
|
||||
$this->fileHeader["commentdata"] = $this->dataPart($start, ($this->pointer - $start));
|
||||
} else {
|
||||
$this->pointerRewind(2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the graphic extension of the frames (old: get_graphics_extension)
|
||||
*
|
||||
* @param integer $type
|
||||
*/
|
||||
private function parseGraphicsExtension($type)
|
||||
{
|
||||
$startdata = $this->readByte(2);
|
||||
|
||||
if ($startdata == chr(0x21) . chr(0xf9)) {
|
||||
$start = ($this->pointer - 2);
|
||||
$this->pointerForward($this->readByteInt());
|
||||
$this->pointerForward(1);
|
||||
|
||||
if ($type == 2) {
|
||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->dataPart($start, ($this->pointer - $start));
|
||||
} elseif ($type == 1) {
|
||||
$this->orgvars["hasgx_type_1"] = 1;
|
||||
$this->globaldata["graphicsextension"] = $this->dataPart($start, ($this->pointer - $start));
|
||||
} elseif ($type == 0) {
|
||||
$this->orgvars["hasgx_type_0"] = 1;
|
||||
$this->globaldata["graphicsextension_0"] = $this->dataPart($start, ($this->pointer - $start));
|
||||
}
|
||||
} else {
|
||||
$this->pointerRewind(2);
|
||||
}//end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full frame string block (old: get_image_block)
|
||||
*
|
||||
* @param integer $type
|
||||
*/
|
||||
private function getFrameString($type)
|
||||
{
|
||||
if ($this->checkByte(0x2c)) {
|
||||
$start = $this->pointer;
|
||||
$this->pointerForward(9);
|
||||
|
||||
if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) {
|
||||
$this->pointerForward(pow(2, ($this->readBits($mybyte, 5, 3) + 1)) * 3);
|
||||
}
|
||||
|
||||
$this->pointerForward(1);
|
||||
$this->readDataStream($this->readByteInt());
|
||||
$this->frameSources[$this->frameNumber]["imagedata"] = $this->dataPart($start, ($this->pointer - $start));
|
||||
|
||||
if ($type == 0) {
|
||||
$this->orgvars["hasgx_type_0"] = 0;
|
||||
|
||||
if (isset($this->globaldata["graphicsextension_0"])) {
|
||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"];
|
||||
} else {
|
||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = null;
|
||||
}
|
||||
|
||||
unset($this->globaldata["graphicsextension_0"]);
|
||||
} elseif ($type == 1) {
|
||||
if (isset($this->orgvars["hasgx_type_1"]) && $this->orgvars["hasgx_type_1"] == 1) {
|
||||
$this->orgvars["hasgx_type_1"] = 0;
|
||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension"];
|
||||
unset($this->globaldata["graphicsextension"]);
|
||||
} else {
|
||||
$this->orgvars["hasgx_type_0"] = 0;
|
||||
$this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"];
|
||||
unset($this->globaldata["graphicsextension_0"]);
|
||||
}
|
||||
}//end if
|
||||
|
||||
$this->parseFrameData();
|
||||
$this->frameNumber++;
|
||||
}//end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse frame data string into an array (old: parse_image_data)
|
||||
*/
|
||||
private function parseFrameData()
|
||||
{
|
||||
$this->frameSources[$this->frameNumber]["disposal_method"] = $this->getImageDataBit("ext", 3, 3, 3);
|
||||
$this->frameSources[$this->frameNumber]["user_input_flag"] = $this->getImageDataBit("ext", 3, 6, 1);
|
||||
$this->frameSources[$this->frameNumber]["transparent_color_flag"] = $this->getImageDataBit("ext", 3, 7, 1);
|
||||
$this->frameSources[$this->frameNumber]["delay_time"] = $this->dualByteVal($this->getImageDataByte("ext", 4, 2));
|
||||
$this->totalDuration += (int) $this->frameSources[$this->frameNumber]["delay_time"];
|
||||
$this->frameSources[$this->frameNumber]["transparent_color_index"] = ord($this->getImageDataByte("ext", 6, 1));
|
||||
$this->frameSources[$this->frameNumber]["offset_left"] = $this->dualByteVal($this->getImageDataByte("dat", 1, 2));
|
||||
$this->frameSources[$this->frameNumber]["offset_top"] = $this->dualByteVal($this->getImageDataByte("dat", 3, 2));
|
||||
$this->frameSources[$this->frameNumber]["width"] = $this->dualByteVal($this->getImageDataByte("dat", 5, 2));
|
||||
$this->frameSources[$this->frameNumber]["height"] = $this->dualByteVal($this->getImageDataByte("dat", 7, 2));
|
||||
$this->frameSources[$this->frameNumber]["local_color_table_flag"] = $this->getImageDataBit("dat", 9, 0, 1);
|
||||
$this->frameSources[$this->frameNumber]["interlace_flag"] = $this->getImageDataBit("dat", 9, 1, 1);
|
||||
$this->frameSources[$this->frameNumber]["sort_flag"] = $this->getImageDataBit("dat", 9, 2, 1);
|
||||
$this->frameSources[$this->frameNumber]["color_table_size"] = (pow(2, ($this->getImageDataBit("dat", 9, 5, 3) + 1)) * 3);
|
||||
$this->frameSources[$this->frameNumber]["color_table"] = substr($this->frameSources[$this->frameNumber]["imagedata"], 10, $this->frameSources[$this->frameNumber]["color_table_size"]);
|
||||
$this->frameSources[$this->frameNumber]["lzw_code_size"] = ord($this->getImageDataByte("dat", 10, 1));
|
||||
|
||||
$this->framePositions[$this->frameNumber] = [
|
||||
'x' => $this->frameSources[$this->frameNumber]["offset_left"],
|
||||
'y' => $this->frameSources[$this->frameNumber]["offset_top"],
|
||||
];
|
||||
|
||||
$this->frameDimensions[$this->frameNumber] = [
|
||||
'width' => $this->frameSources[$this->frameNumber]["width"],
|
||||
'height' => $this->frameSources[$this->frameNumber]["height"],
|
||||
];
|
||||
|
||||
// Decoding
|
||||
$this->orgvars[$this->frameNumber]["transparent_color_flag"] = $this->frameSources[$this->frameNumber]["transparent_color_flag"];
|
||||
$this->orgvars[$this->frameNumber]["transparent_color_index"] = $this->frameSources[$this->frameNumber]["transparent_color_index"];
|
||||
$this->orgvars[$this->frameNumber]["delay_time"] = $this->frameSources[$this->frameNumber]["delay_time"];
|
||||
$this->orgvars[$this->frameNumber]["disposal_method"] = $this->frameSources[$this->frameNumber]["disposal_method"];
|
||||
$this->orgvars[$this->frameNumber]["offset_left"] = $this->frameSources[$this->frameNumber]["offset_left"];
|
||||
$this->orgvars[$this->frameNumber]["offset_top"] = $this->frameSources[$this->frameNumber]["offset_top"];
|
||||
|
||||
// Updating the max width
|
||||
if ($this->gifMaxWidth < $this->frameSources[$this->frameNumber]["width"]) {
|
||||
$this->gifMaxWidth = $this->frameSources[$this->frameNumber]["width"];
|
||||
}
|
||||
|
||||
// Updating the max height
|
||||
if ($this->gifMaxHeight < $this->frameSources[$this->frameNumber]["height"]) {
|
||||
$this->gifMaxHeight = $this->frameSources[$this->frameNumber]["height"];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image data byte (old: get_imagedata_byte)
|
||||
*
|
||||
* @param string $type
|
||||
* @param integer $start
|
||||
* @param integer $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getImageDataByte($type, $start, $length)
|
||||
{
|
||||
if ($type == "ext") {
|
||||
return substr($this->frameSources[$this->frameNumber]["graphicsextension"], $start, $length);
|
||||
}
|
||||
|
||||
// "dat"
|
||||
return substr($this->frameSources[$this->frameNumber]["imagedata"], $start, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image data bit (old: get_imagedata_bit)
|
||||
*
|
||||
* @param string $type
|
||||
* @param integer $byteIndex
|
||||
* @param integer $bitStart
|
||||
* @param integer $bitLength
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
private function getImageDataBit($type, $byteIndex, $bitStart, $bitLength)
|
||||
{
|
||||
if ($type == "ext") {
|
||||
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["graphicsextension"], $byteIndex, 1)), $bitStart, $bitLength);
|
||||
}
|
||||
|
||||
// "dat"
|
||||
return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["imagedata"], $byteIndex, 1)), $bitStart, $bitLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value of 2 ASCII chars (old: dualbyteval)
|
||||
*
|
||||
* @param string $s
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function dualByteVal($s)
|
||||
{
|
||||
$i = (ord($s[1]) * 256 + ord($s[0]));
|
||||
|
||||
return $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data stream (old: read_data_stream)
|
||||
*
|
||||
* @param integer $firstLength
|
||||
*/
|
||||
private function readDataStream($firstLength)
|
||||
{
|
||||
$this->pointerForward($firstLength);
|
||||
$length = $this->readByteInt();
|
||||
|
||||
if ($length != 0) {
|
||||
while ($length != 0) {
|
||||
$this->pointerForward($length);
|
||||
$length = $this->readByteInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the gif file (old: loadfile)
|
||||
*
|
||||
* @param string $filename
|
||||
*/
|
||||
private function openFile($filename)
|
||||
{
|
||||
$this->handle = fopen($filename, "rb");
|
||||
$this->pointer = 0;
|
||||
|
||||
$imageSize = getimagesize($filename);
|
||||
$this->gifWidth = $imageSize[0];
|
||||
$this->gifHeight = $imageSize[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the read gif file (old: closefile)
|
||||
*/
|
||||
private function closeFile()
|
||||
{
|
||||
fclose($this->handle);
|
||||
$this->handle = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the file from the beginning to $byteCount in binary (old: readbyte)
|
||||
*
|
||||
* @param integer $byteCount
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function readByte($byteCount)
|
||||
{
|
||||
$data = fread($this->handle, $byteCount);
|
||||
$this->pointer += $byteCount;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a byte and return ASCII value (old: readbyte_int)
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function readByteInt()
|
||||
{
|
||||
$data = fread($this->handle, 1);
|
||||
$this->pointer++;
|
||||
|
||||
return ord($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a $byte to decimal (old: readbits)
|
||||
*
|
||||
* @param string $byte
|
||||
* @param integer $start
|
||||
* @param integer $length
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
private function readBits($byte, $start, $length)
|
||||
{
|
||||
$bin = str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
|
||||
$data = substr($bin, $start, $length);
|
||||
|
||||
return bindec($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind the file pointer reader (old: p_rewind)
|
||||
*
|
||||
* @param integer $length
|
||||
*/
|
||||
private function pointerRewind($length)
|
||||
{
|
||||
$this->pointer -= $length;
|
||||
fseek($this->handle, $this->pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the file pointer reader (old: p_forward)
|
||||
*
|
||||
* @param integer $length
|
||||
*/
|
||||
private function pointerForward($length)
|
||||
{
|
||||
$this->pointer += $length;
|
||||
fseek($this->handle, $this->pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
fseek($this->handle, $start);
|
||||
$data = fread($this->handle, $length);
|
||||
fseek($this->handle, $this->pointer);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character if a byte (old: checkbyte)
|
||||
*
|
||||
* @param integer $byte
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function checkByte($byte)
|
||||
{
|
||||
if (fgetc($this->handle) == chr($byte)) {
|
||||
fseek($this->handle, $this->pointer);
|
||||
return true;
|
||||
}
|
||||
|
||||
fseek($this->handle, $this->pointer);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the end of the file (old: checkEOF)
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function checkEOF()
|
||||
{
|
||||
if (fgetc($this->handle) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fseek($this->handle, $this->pointer);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset and clear this current object
|
||||
*/
|
||||
private function reset()
|
||||
{
|
||||
$this->gif = null;
|
||||
$this->totalDuration = $this->gifMaxHeight = $this->gifMaxWidth = $this->handle = $this->pointer = $this->frameNumber = 0;
|
||||
$this->frameDimensions = $this->framePositions = $this->frameImages = $this->frameDurations = $this->globaldata = $this->orgvars = $this->frames = $this->fileHeader = $this->frameSources = [];
|
||||
}
|
||||
|
||||
// Getter / Setter
|
||||
// ===================================================================================
|
||||
|
||||
|
||||
/**
|
||||
* Get the total of all added frame duration
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getTotalDuration()
|
||||
{
|
||||
return $this->totalDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of extracted frames
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getFrameNumber()
|
||||
{
|
||||
return $this->frameNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extracted frames (images and durations)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFrames()
|
||||
{
|
||||
return $this->frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extracted frame positions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFramePositions()
|
||||
{
|
||||
return $this->framePositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extracted frame dimensions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFrameDimensions()
|
||||
{
|
||||
return $this->frameDimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extracted frame images
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFrameImages()
|
||||
{
|
||||
return $this->frameImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extracted frame durations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFrameDurations()
|
||||
{
|
||||
return $this->frameDurations;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use ImageIntervention;
|
||||
|
||||
class ImageService
|
||||
{
|
||||
}
|
||||
@@ -2,7 +2,10 @@
|
||||
"name": "laravel/laravel",
|
||||
"type": "project",
|
||||
"description": "The Laravel Framework.",
|
||||
"keywords": ["framework", "laravel"],
|
||||
"keywords": [
|
||||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0.2",
|
||||
@@ -12,7 +15,10 @@
|
||||
"laravel/framework": "^9.19",
|
||||
"laravel/sanctum": "^3.0",
|
||||
"laravel/tinker": "^2.7",
|
||||
"owen-it/laravel-auditing": "^13.0"
|
||||
"owen-it/laravel-auditing": "^13.0",
|
||||
"php-ffmpeg/php-ffmpeg": "^1.1",
|
||||
"spatie/image-optimizer": "^1.6",
|
||||
"thiagoalessio/tesseract_ocr": "^2.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
@@ -24,15 +30,20 @@
|
||||
"spatie/laravel-ignition": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"app/Helpers/Array.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
"Database\\Seeders\\": "database/seeders/",
|
||||
"Faker\\Provider\\": "faker/provider/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
"Tests\\": "tests/",
|
||||
"Faker\\Provider\\": "faker/provider/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
1081
composer.lock
generated
@@ -60,6 +60,7 @@ return [
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') === true ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
PDO::ATTR_TIMEOUT => env('DB_TIMEOUT', 30),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
|
||||
40
database/factories/EventFactory.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
||||
*/
|
||||
class EventFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$startDate = Carbon::parse($this->faker->dateTimeBetween('now', '+1 year'));
|
||||
$endDate = Carbon::parse($this->faker->dateTimeBetween($startDate, '+1 year'));
|
||||
$publishDate = Carbon::parse($this->faker->dateTimeBetween('-1 month', '+1 month'));
|
||||
|
||||
return [
|
||||
'title' => $this->faker->sentence(),
|
||||
'location' => $this->faker->randomElement(['online', 'physical']),
|
||||
'address' => $this->faker->address,
|
||||
'start_at' => $startDate,
|
||||
'end_at' => $endDate,
|
||||
'publish_at' => $publishDate,
|
||||
'status' => $this->faker->randomElement(['draft', 'soon', 'open', 'closed', 'cancelled']),
|
||||
'registration_type' => $this->faker->randomElement(['none', 'email', 'link', 'message']),
|
||||
'registration_data' => $this->faker->sentence(),
|
||||
'hero' => $this->faker->uuid,
|
||||
'content' => $this->faker->paragraphs(3, true),
|
||||
'price' => $this->faker->numberBetween(0, 150),
|
||||
'ages' => $this->faker->regexify('\d+(\+|\-\d+)?'),
|
||||
];
|
||||
}
|
||||
}
|
||||
29
database/factories/MediaFactory.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
||||
*/
|
||||
class MediaFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'title' => $this->faker->sentence(),
|
||||
'name' => storage_path('app/public/') . $this->faker->slug() . '.' . $this->faker->fileExtension,
|
||||
'mime' => $this->faker->mimeType,
|
||||
'user_id' => $this->faker->uuid,
|
||||
'size' => $this->faker->numberBetween(1000, 1000000),
|
||||
'permission' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
31
database/factories/PostFactory.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Event>
|
||||
*/
|
||||
class PostFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$publishDate = Carbon::parse($this->faker->dateTimeBetween('-1 month', '+1 month'));
|
||||
|
||||
return [
|
||||
'title' => $this->faker->sentence(),
|
||||
'slug' => $this->faker->slug(),
|
||||
'publish_at' => $publishDate,
|
||||
'content' => $this->faker->paragraphs(3, true),
|
||||
'user_id' => $this->faker->uuid,
|
||||
'hero' => $this->faker->uuid,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,16 @@ class UserFactory extends Factory
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
$faker->addProvider(new \Faker\Provider\CustomInternetProvider($faker));
|
||||
|
||||
return [
|
||||
'username' => fake()->unique()->userName(),
|
||||
'first_name' => fake()->firstName(),
|
||||
'last_name' => fake()->lastName(),
|
||||
'email' => fake()->safeEmail(),
|
||||
'username' => $faker->unique()->userNameWithMinLength(6),
|
||||
'first_name' => $faker->firstName(),
|
||||
'last_name' => $faker->lastName(),
|
||||
'email' => $faker->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'phone' => fake()->phoneNumber(),
|
||||
'phone' => $faker->phoneNumber(),
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('attachments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('media_id');
|
||||
$table->uuidMorphs('attachable');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('media_id')->references('id')->on('media')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('media_attachments');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('events', function (Blueprint $table) {
|
||||
$table->string('price')->default("");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('events', function (Blueprint $table) {
|
||||
$table->dropColumn('price');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('events', function (Blueprint $table) {
|
||||
$table->string('ages')->default("");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('events', function (Blueprint $table) {
|
||||
$table->dropColumn('ages');
|
||||
});
|
||||
}
|
||||
};
|
||||
16
faker/provider/CustomInternetProvider.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
namespace Faker\Provider;
|
||||
|
||||
use Faker\Provider\Internet as BaseInternet;
|
||||
|
||||
class CustomInternetProvider extends BaseInternet
|
||||
{
|
||||
public function userNameWithMinLength($minLength = 6)
|
||||
{
|
||||
$username = $this->userName();
|
||||
while (strlen($username) < $minLength) {
|
||||
$username .= $this->randomNumber();
|
||||
}
|
||||
return $username;
|
||||
}
|
||||
}
|
||||
6
import-meta.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface ImportMetaExtras extends ImportMeta {
|
||||
env: {
|
||||
APP_URL: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
1813
package-lock.json
generated
27
package.json
@@ -4,12 +4,13 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint \"**/*.{js,jsx,.vue}\" --ignore-path .gitignore",
|
||||
"format": "prettier . --write"
|
||||
"format": "prettier . --write",
|
||||
"test": "vitest",
|
||||
"prepare": "mkdir -p public/tinymce/skins && cp -r node_modules/tinymce/skins public/tinymce && mkdir -p public/tinymce/plugins/emoticons/js && cp node_modules/tinymce/plugins/emoticons/js/emojis.min.js public/tinymce/plugins/emoticons/js/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"axios": "^1.1.2",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-jsdoc": "^39.6.4",
|
||||
@@ -18,32 +19,30 @@
|
||||
"lodash": "^4.17.19",
|
||||
"postcss": "^8.1.14",
|
||||
"prettier": "2.8.2",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^4.0.0"
|
||||
"rollup-plugin-analyzer": "^4.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-compression2": "^0.8.2",
|
||||
"vitest": "^0.28.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.2",
|
||||
"@tinymce/tinymce-vue": "^4.0.7",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vuepic/vue-datepicker": "^3.6.4",
|
||||
"date-fns": "^2.29.3",
|
||||
"dompurify": "^3.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"element-plus": "^2.2.27",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^2.0.28",
|
||||
"pinia-plugin-persistedstate": "^3.0.1",
|
||||
"sass": "^1.57.1",
|
||||
"trix": "^2.0.4",
|
||||
"tinymce": "^6.3.1",
|
||||
"vue": "^3.2.36",
|
||||
"vue-dompurify-html": "^3.1.2",
|
||||
"vue-final-modal": "^3.4.11",
|
||||
"vue-loader": "^17.0.1",
|
||||
"vue-recaptcha-v3": "^2.0.1",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-easy-data-table": "^1.5.24",
|
||||
"vue3-promise-dialog": "^0.3.4"
|
||||
"vue3-easy-data-table": "^1.5.24"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<!-- <env name="DB_CONNECTION" value="sqlite"/> -->
|
||||
<!-- <env name="DB_DATABASE" value=":memory:"/> -->
|
||||
<env name="DB_CONNECTION" value="sqlite"/>
|
||||
<env name="DB_DATABASE" value=":memory:"/>
|
||||
<env name="MAIL_MAILER" value="array"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
<env name="SESSION_DRIVER" value="array"/>
|
||||
<env name="TELESCOPE_ENABLED" value="false"/>
|
||||
<env name="FAKER_PROVIDER_PATH" value="./faker/provider/CustomInternetProvider.php"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
||||
@@ -3,8 +3,20 @@
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "^(uploads|img)/.+">
|
||||
<If "%{QUERY_STRING} =~ /(^|&)download=1($|&)/">
|
||||
Header set Content-Disposition "attachment"
|
||||
</If>
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Add www subdomain if missing
|
||||
RewriteCond %{HTTP_HOST} ^stemmechanics.com.au$ [NC]
|
||||
RewriteRule (.*) https://www.stemmechanics.com.au/$1 [R=301,L]
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
@@ -14,6 +26,28 @@
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Pass to media handler if the media request has query
|
||||
RewriteCond %{REQUEST_FILENAME} -f
|
||||
RewriteCond %{QUERY_STRING} .
|
||||
RewriteRule ^uploads/(.+)\.(jpe?g|png)$ media.php?url=uploads/$1.$2 [NC,QSA,L]
|
||||
|
||||
# AddEncoding allows you to have certain browsers uncompress information on the fly.
|
||||
AddEncoding gzip .gz
|
||||
|
||||
#Serve gzip compressed CSS files if they exist and the client accepts gzip.
|
||||
RewriteCond %{HTTP:Accept-encoding} gzip
|
||||
RewriteCond %{REQUEST_FILENAME}\.gz -s
|
||||
RewriteRule ^(.*)\.css $1\.css\.gz [QSA]
|
||||
|
||||
# Serve gzip compressed JS files if they exist and the client accepts gzip.
|
||||
RewriteCond %{HTTP:Accept-encoding} gzip
|
||||
RewriteCond %{REQUEST_FILENAME}\.gz -s
|
||||
RewriteRule ^(.*)\.js $1\.js\.gz [QSA]
|
||||
|
||||
# Serve correct content types, and prevent mod_deflate double gzip.
|
||||
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1]
|
||||
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
|
||||
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 45 KiB |
BIN
public/img/background.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/img/dashboard-bg.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
3
public/img/fileicons/.htaccess
Normal file
@@ -0,0 +1,3 @@
|
||||
RewriteEngine on
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule \.png$ unknown.png [L]
|
||||
BIN
public/img/fileicons/doc.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/img/fileicons/docx.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/img/fileicons/file-icons.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/img/fileicons/jpeg.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/img/fileicons/jpg.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/fileicons/mov.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/img/fileicons/mp4.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/img/fileicons/pdf.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/fileicons/png.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/fileicons/svg.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/img/fileicons/unknown.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
public/img/fileicons/xls.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/img/fileicons/xml.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/fileicons/zip.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/img/minecraft-grass-block.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
public/img/sad-monster.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
83
public/media.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
// file deepcode ignore PT: Input is sanitized using realpath which is ignored by Snyk
|
||||
// file deepcode ignore Ssrf: Input is sanitized using realpath which is ignored by Snyk
|
||||
|
||||
$filepath = "";
|
||||
if (isset($_GET['url'])) {
|
||||
$filepath = realpath($_GET['url']);
|
||||
}
|
||||
|
||||
if ($filepath !== false && strlen($filepath) > 0 && strpos($_GET['url'], 'uploads/') === 0 && is_file($filepath)) {
|
||||
if(isset($_GET['size'])) {
|
||||
$availableSizes = ['thumb', 'small', 'medium', 'large', 'xlarge']; // we ignore full as its the original file
|
||||
$requestedSize = strtolower($_GET['size']);
|
||||
$requestedSizeIndex = array_search($requestedSize, $availableSizes);
|
||||
|
||||
// Loop through the array from the requested size index
|
||||
if($requestedSizeIndex !== false) {
|
||||
for ($i = $requestedSizeIndex; $i < count($availableSizes); $i++) {
|
||||
$sizePath = pathinfo($filepath, PATHINFO_DIRNAME) . '/' . pathinfo($filepath, PATHINFO_FILENAME) . "-$availableSizes[$i]." . pathinfo($filepath, PATHINFO_EXTENSION);
|
||||
if (file_exists($sizePath)) {
|
||||
$filepath = $sizePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output the original image to the browser
|
||||
header('Content-Type: '. mime_content_type($filepath));
|
||||
header('Content-Disposition: inline; filename=' . pathinfo($filepath, PATHINFO_FILENAME) . '.' . pathinfo($filepath, PATHINFO_EXTENSION));
|
||||
readfile($filepath);
|
||||
} else {
|
||||
$newWidth = (isset($_GET['w']) ? intval($_GET['w']) : -1);
|
||||
$newHeight = (isset($_GET['h']) ? intval($_GET['h']) : -1);
|
||||
|
||||
if($newWidth != -1 || $newHeight != -1) {
|
||||
$image = imagecreatefromstring(file_get_contents($filepath));
|
||||
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
|
||||
$aspectRatio = $width / $height;
|
||||
|
||||
if($newWidth == -1) {
|
||||
$newWidth = intval($newHeight * $aspectRatio);
|
||||
}
|
||||
|
||||
if($newHeight == -1) {
|
||||
$newHeight = intval($newWidth / $aspectRatio);
|
||||
}
|
||||
|
||||
$newImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
imagecopyresampled($newImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||
|
||||
// Output the resized image to the browser
|
||||
$mime_type = mime_content_type($_GET['url']);
|
||||
header('Content-Type: ' . $mime_type);
|
||||
switch($mime_type) {
|
||||
case "image/jpeg":
|
||||
imagejpeg($newImage);
|
||||
break;
|
||||
case "image/gif":
|
||||
imagegif($newImage);
|
||||
break;
|
||||
case "image/png":
|
||||
imagepng($newImage);
|
||||
break;
|
||||
}
|
||||
imagedestroy($newImage);
|
||||
|
||||
// Clean up the image resources
|
||||
imagedestroy($image);
|
||||
} else {
|
||||
// Output the original image to the browser
|
||||
header('Content-Type: '. mime_content_type($filepath));
|
||||
header('Content-Disposition: inline; filename=' . pathinfo($filepath, PATHINFO_FILENAME) . '.' . pathinfo($filepath, PATHINFO_EXTENSION));
|
||||
readfile($filepath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Return a 404 error
|
||||
header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found");
|
||||
exit;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
@import "variables.scss";
|
||||
@import "utils.scss";
|
||||
@import "data-table.scss";
|
||||
@import "datepicker.scss";
|
||||
@import "tinymce.scss";
|
||||
@import "prism.css";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -20,6 +21,11 @@ body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// Who knows why ion-icon randomally sets this to hidden.....
|
||||
ion-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -43,107 +49,55 @@ h1 {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: map-get($spacer, 1);
|
||||
|
||||
&.required:after {
|
||||
content: " *";
|
||||
color: $danger-color;
|
||||
}
|
||||
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
margin-right: map-get($spacer, 3);
|
||||
|
||||
&:after {
|
||||
content: ":";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 1.5rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
code {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 12px;
|
||||
padding: map-get($spacer, 2) map-get($spacer, 3);
|
||||
color: $font-color;
|
||||
margin-bottom: map-get($spacer, 4);
|
||||
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
font-size: 0.8rem;
|
||||
background-color: #eee;
|
||||
padding: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
white-space: pre-wrap;
|
||||
overflow-x: auto;
|
||||
max-height: 30rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
select {
|
||||
padding-right: 2.5rem;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.75rem center;
|
||||
background-size: 24px 18px;
|
||||
}
|
||||
|
||||
.input-file-group {
|
||||
/* Page Errors */
|
||||
.sm-page-error {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 12px;
|
||||
flex-direction: column;
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
position: absolute;
|
||||
margin-left: -9999px;
|
||||
.sm-error-number {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 30vw;
|
||||
font-weight: 600;
|
||||
color: #295b7e;
|
||||
|
||||
img {
|
||||
height: 25vw;
|
||||
margin: 0 0.5rem 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
label.button {
|
||||
margin-right: map-get($spacer, 4);
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
margin: 0;
|
||||
height: 3rem;
|
||||
width: auto;
|
||||
}
|
||||
.sm-error-content {
|
||||
text-align: center;
|
||||
font-size: 120%;
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
border: 1px solid $border-color;
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
flex: 1;
|
||||
height: 3rem;
|
||||
background-color: #fff;
|
||||
line-height: 3rem;
|
||||
padding: 0 1rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg,
|
||||
button {
|
||||
@extend .prevent-select;
|
||||
}
|
||||
|
||||
/* Dialog */
|
||||
.modal {
|
||||
/* SM Dialog */
|
||||
.sm-dialog-outer {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
top: 0;
|
||||
@@ -153,239 +107,12 @@ button {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.sm-dialog-outer:last-of-type {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(2px);
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
z-index: 1000;
|
||||
padding: 1rem;
|
||||
|
||||
.dialog {
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
box-shadow: 4px 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Loader */
|
||||
.loader-cover {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: map-get($spacer, 5) calc(map-get($spacer, 5) * 2);
|
||||
|
||||
border: 1px solid transparent;
|
||||
border-radius: 24px;
|
||||
|
||||
svg {
|
||||
font-size: calc(map-get($spacer, 5) * 1.5);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: map-get($spacer, 4);
|
||||
padding-top: map-get($spacer, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Button */
|
||||
button.button,
|
||||
a.button,
|
||||
label.button {
|
||||
padding: map-get($spacer, 2) map-get($spacer, 4);
|
||||
color: white;
|
||||
font-weight: 800;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-radius: 24px;
|
||||
transition: background-color 0.1s, color 0.1s;
|
||||
cursor: pointer;
|
||||
background-color: $secondary-color;
|
||||
border-color: $secondary-color;
|
||||
min-width: 7rem;
|
||||
text-align: center;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: $secondary-color !important;
|
||||
border-color: $secondary-color !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
text-decoration: none;
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background-color: $primary-color;
|
||||
border-color: $primary-color;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background-color: $secondary-color;
|
||||
border-color: $secondary-color;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: $secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: $danger-color;
|
||||
border-color: $danger-color;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: $danger-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
border-color: $outline-color;
|
||||
color: $outline-color;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: $outline-color;
|
||||
border-color: $outline-color;
|
||||
color: $outline-hover-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
svg {
|
||||
padding-left: 0.5rem;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
}
|
||||
|
||||
.button + .button {
|
||||
margin: 0 map-get($spacer, 2);
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Group */
|
||||
.form-group {
|
||||
margin-bottom: map-get($spacer, 3);
|
||||
padding: 0 4px;
|
||||
flex: 1;
|
||||
|
||||
input,
|
||||
textarea {
|
||||
margin-bottom: map-get($spacer, 1);
|
||||
}
|
||||
|
||||
.form-group-info {
|
||||
font-size: 85%;
|
||||
margin-bottom: map-get($spacer, 1);
|
||||
}
|
||||
|
||||
.form-group-error {
|
||||
// display: none;
|
||||
font-size: 85%;
|
||||
margin-bottom: map-get($spacer, 1);
|
||||
color: $danger-color;
|
||||
}
|
||||
|
||||
.form-group-help {
|
||||
font-size: 85%;
|
||||
margin-bottom: map-get($spacer, 1);
|
||||
color: $secondary-color;
|
||||
|
||||
svg {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
input,
|
||||
textarea,
|
||||
.input-file-group,
|
||||
.input-media-group .input-media-display {
|
||||
border: 2px solid $danger-color;
|
||||
}
|
||||
|
||||
.form-group-error {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Page Errors */
|
||||
.page-error {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
margin-top: map-get($spacer, 5);
|
||||
|
||||
.image {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
margin-left: map-get($spacer, 2);
|
||||
margin-right: map-get($spacer, 2);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
margin-left: map-get($spacer, 2);
|
||||
margin-right: map-get($spacer, 2);
|
||||
margin-bottom: 0;
|
||||
|
||||
h1 {
|
||||
text-align: left;
|
||||
margin-bottom: map-get($spacer, 1);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.not-found {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
.content {
|
||||
flex: 0;
|
||||
margin-bottom: map-get($spacer, 5);
|
||||
|
||||
align-items: center;
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.vue3-easy-data-table__body td {
|
||||
white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.vue3-easy-data-table__main {
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
@@ -32,16 +38,6 @@
|
||||
a {
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
svg {
|
||||
margin: 0 0.2rem;
|
||||
transition: color 0.1s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
resources/css/prism.css
Normal file
@@ -0,0 +1,3 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+clike+javascript+bash+c+javadoclike+js-extras+json+json5+log+markup-templating+objectivec+perl+php+phpdoc+php-extras+python+regex+sql+swift+typoscript+yaml */
|
||||
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
13
resources/css/tinymce.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
// @import "../../public/skins/ui/oxide/skin.min.css";
|
||||
// @import "../../public/skins/ui/oxide/content.min.css";
|
||||
// @import "../../public/skins/content/default/content.min.css";
|
||||
|
||||
.tox .tox-dialog.tox-dialog--width-lg {
|
||||
height: 650px;
|
||||
max-height: 650px;
|
||||
|
||||
.tox-dialog__body-content {
|
||||
height: auto !important;
|
||||
flex-basis: auto !important;
|
||||
}
|
||||
}
|
||||
@@ -125,33 +125,33 @@
|
||||
/* Margin */
|
||||
@each $index, $size in $spacer {
|
||||
.m-#{$index} {
|
||||
margin: #{$size};
|
||||
margin: #{$size} !important;
|
||||
}
|
||||
|
||||
.mt-#{$index} {
|
||||
margin-top: #{$size};
|
||||
margin-top: #{$size} !important;
|
||||
}
|
||||
|
||||
.mb-#{$index} {
|
||||
margin-bottom: #{$size};
|
||||
margin-bottom: #{$size} !important;
|
||||
}
|
||||
|
||||
.ml-#{$index} {
|
||||
margin-left: #{$size};
|
||||
margin-left: #{$size} !important;
|
||||
}
|
||||
|
||||
.mr-#{$index} {
|
||||
margin-right: #{$size};
|
||||
margin-right: #{$size} !important;
|
||||
}
|
||||
|
||||
.mx-#{$index} {
|
||||
margin-left: #{$size};
|
||||
margin-right: #{$size};
|
||||
margin-left: #{$size} !important;
|
||||
margin-right: #{$size} !important;
|
||||
}
|
||||
|
||||
.my-#{$index} {
|
||||
margin-top: #{$size};
|
||||
margin-bottom: #{$size};
|
||||
margin-top: #{$size} !important;
|
||||
margin-bottom: #{$size} !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@
|
||||
/* Utility */
|
||||
.prevent-select {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ $success-color: #198754;
|
||||
$success-color-dark: #12653e;
|
||||
$success-color-darker: #0c4329;
|
||||
|
||||
$warning-color-lighter: #fff8e2;
|
||||
$warning-color-light: #fff6d9;
|
||||
$warning-color: #fff3cd;
|
||||
$warning-color-dark: #ffd75a;
|
||||
$warning-color-darker: #ffc203;
|
||||
|
||||
$border-color: #dddddd;
|
||||
$secondary-background-color: #efefef;
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { useUserStore } from "./store/UserStore";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
axios.defaults.baseURL = import.meta.env.APP_URL_API;
|
||||
axios.defaults.withCredentials = true;
|
||||
axios.defaults.headers.common["Accept"] = "application/json";
|
||||
|
||||
axios.interceptors.request.use((request) => {
|
||||
const userStore = useUserStore();
|
||||
if (userStore.id) {
|
||||
request.headers["Authorization"] = `Bearer ${userStore.token}`;
|
||||
}
|
||||
|
||||
return request;
|
||||
});
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (error.config.redirect !== false && error.response.status === 401) {
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
userStore.clearUser();
|
||||
|
||||
const url = new URL(error.request.responseURL);
|
||||
router.push({ name: "login", query: { redirect: url.pathname } });
|
||||
}
|
||||
|
||||
// if(error.config.redirect === true) {
|
||||
// if(error.response.status === 403) {
|
||||
// router.push({ name: 'error-forbidden' })
|
||||
// } else if(error.response.status === 404) {
|
||||
// router.push({ name: 'error-notfound' })
|
||||
// } else if(error.response.status >= 500) {
|
||||
// router.push({name: 'error-internal'})
|
||||
// }
|
||||
// }
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
34
resources/js/bootstrap.js
vendored
@@ -1,34 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
window._ = _;
|
||||
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Echo exposes an expressive API for subscribing to channels and listening
|
||||
* for events that are broadcast by Laravel. Echo and event broadcasting
|
||||
* allows your team to easily build robust real-time web applications.
|
||||
*/
|
||||
|
||||
// import Echo from 'laravel-echo';
|
||||
|
||||
// import Pusher from 'pusher-js';
|
||||
// window.Pusher = Pusher;
|
||||
|
||||
// window.Echo = new Echo({
|
||||
// broadcaster: 'pusher',
|
||||
// key: import.meta.env.VITE_PUSHER_APP_KEY,
|
||||
// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
|
||||
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
|
||||
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
|
||||
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
|
||||
// enabledTransports: ['ws', 'wss'],
|
||||
// });
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<a :href="computedHref" :target="props.target" rel="noopener"
|
||||
><slot></slot
|
||||
></a>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import axios from 'axios'
|
||||
import { computed } from "vue";
|
||||
import { useUserStore } from "../store/UserStore";
|
||||
|
||||
const props = defineProps({
|
||||
href: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const computedHref = computed(() => {
|
||||
const url = new URL(props.href);
|
||||
if (url.pathname.startsWith("/api/") && userStore.token) {
|
||||
return props.href + "?token=" + encodeURIComponent(userStore.token);
|
||||
}
|
||||
|
||||
return props.href;
|
||||
});
|
||||
|
||||
// const handleClick = async (event) => {
|
||||
// const url = new URL(props.href)
|
||||
// if(url.pathname.startsWith('/api/')) {
|
||||
// console.log('api')
|
||||
// event.preventDefault()
|
||||
|
||||
// axios.get(props.href, {responseType: 'blob'})
|
||||
// .then(response => {
|
||||
// const blob = new Blob([response.data], { type: response.data.type })
|
||||
// const href = URL.createObjectURL(blob)
|
||||
// const link = document.createElement('a')
|
||||
// link.setAttribute('href', href)
|
||||
// link.setAttribute('target', props.target)
|
||||
// document.body.appendChild(link)
|
||||
// link.click()
|
||||
// document.body.removeChild(link)
|
||||
// URL.revokeObjectURL(href)
|
||||
// }).catch(e => {
|
||||
// console.log(e)
|
||||
// })
|
||||
// }
|
||||
|
||||
// console.log('finish')
|
||||
// }
|
||||
</script>
|
||||
@@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div :class="['mdialog-mask', classes]">
|
||||
<div class="mdialog">
|
||||
<h3 v-if="title">{{ title }}</h3>
|
||||
<component :is="component" v-if="component" />
|
||||
<template v-else-if="content">
|
||||
<div v-html="content" />
|
||||
</template>
|
||||
<button
|
||||
@click="
|
||||
show = false;
|
||||
onClose();
|
||||
">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, defineExpose, shallowRef } from "vue";
|
||||
|
||||
const show = ref(false);
|
||||
const title = ref("");
|
||||
const content = ref("");
|
||||
const onClose = ref(() => {});
|
||||
const classes = ref("");
|
||||
const component = shallowRef("");
|
||||
|
||||
defineExpose({
|
||||
title,
|
||||
content,
|
||||
component,
|
||||
show,
|
||||
onClose,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mdialog-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mdialog {
|
||||
padding: 1rem 2rem;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||