Merge branch 'develop' into trunk

This commit is contained in:
Vincent BRIDIER 2023-05-10 12:37:21 +02:00
commit 79ddb51a74
No known key found for this signature in database
GPG key ID: 8571BFA20420D512
8568 changed files with 60023 additions and 1771105 deletions

21
.editorconfig Normal file
View file

@ -0,0 +1,21 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4
[*.vue]
indent_size = 2

63
.env.example Normal file
View file

@ -0,0 +1,63 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
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=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
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}"
# Sentry
SENTRY_LARAVEL_DSN=
GOOGLE_MAPS_KEY=

55
.eslintrc.json Normal file
View file

@ -0,0 +1,55 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:vue/vue3-strongly-recommended",
"@vue/eslint-config-typescript"
],
"overrides": [
{
"files": ["*.ts", "*.vue"],
"rules": {
"no-undef": "off"
}
},
{
"files": ["*.vue"],
"rules": {
"indent": "off"
}
}
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".vue", ".ts", ".d.ts"]
},
"alias": {
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
"map": [
["~", "./resources/assets"],
["@", "./node_modules"]
]
}
}
}
}

11
.gitattributes vendored Normal file
View file

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

50
.gitignore vendored
View file

@ -1,24 +1,26 @@
#fichiers systemes
.bash_history
.bash_profile
.selected_editor
.ssh/
.upload/
.viminfo
# ignore log file
*.log
log.txt
# ignore nodejs files
node_modules
# ignore fichiers de preuve de gain
wp-content/uploads/PDFgain/*
!wp-content/uploads/PDFgain/.gitkeep
# ignore upload file
content/uploads/*
!content/uploads/.htaccess
version-checker/*
.env
.env.backup
.env.production
.phpunit.result.cache
.stylelintcache
/.fleet
/.idea
/.phpunit.cache
/.vscode/launch.json
/node_modules
/public/build
/public/cache
/public/hot
/public/medias
/public/sitemap.xml
/public/storage
/public/uploads
/storage/*.key
/vendor
auth.json
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.vscode/settings.json

11
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,11 @@
include:
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'lint/lint.yml'
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'security/security.yml'
stages:
- lint
- security

View file

@ -0,0 +1,11 @@
## Comportement constaté
## Comportement attendu
## Solution envisagée
## Étapes pour la reproduction du bug
*

View file

@ -0,0 +1,7 @@
## Présentation du besoin
## Implémentation
## Maquettes, documentations...

415
.htaccess
View file

@ -1,415 +0,0 @@
# protection contre la lecture des répertoires
Options All -Indexes
# Masquer les informations du serveur
ServerSignature Off
RewriteEngine on
# Protect wp-config.php
<FilesMatch ^wp-config.php$>
deny from all
</FilesMatch>
# Protect .htaccess et .htpasswds
<Files ~ "^.*\.([Hh][Tt][AaPp])">
order allow,deny
deny from all
</Files>
# Protect debug.log
<Files debug.log>
order allow,deny
deny from all
</Files>
# Protect sql files
<FilesMatch "\.sql">
Order allow,deny
Deny from all
</FilesMatch>
# protection des fichiers readme et licence wordpress
<files readme.html>
Order allow,deny
Deny from all
</files>
<files license.txt>
Order allow,deny
Deny from all
</files>
<files readme.txt>
Order allow,deny
Deny from all
</files>
# protection des fichiers sys .git
RedirectMatch 404 /\.
#Bloquer l'indexation des fichiers PDF
<files ~ "\.(doc|docx|pdf)$">
Header set X-Robots-Tag "noindex, nofollow"
</Files>
# Rules to disable XML-RPC
<files xmlrpc.php>
Order allow,deny
Deny from all
</files>
# Cacher l'identifiant d'un auteur
<IfModule mod_rewrite.c>
RewriteCond %{QUERY_STRING} ^author=([0-9]*)
RewriteRule .* - [F]
</IfModule>
### FILTRE CONTRE XSS, REDIRECTIONS HTTP, base64_encode, VARIABLE PHP GLOBALS VIA URL, MODIFIER VARIABLE _REQUEST VIA URL, TEST DE FAILLE PHP, INJECTION SQL SIMPLE
RewriteCond %{REQUEST_METHOD} (GET|POST) [NC]
RewriteCond %{QUERY_STRING} ^(.*)(%3C|<)/?script(.*)$ [NC,OR]
RewriteCond %{QUERY_STRING} ^(.*)(%3D|=)?javascript(%3A|:)(.*)$ [NC,OR]
RewriteCond %{QUERY_STRING} ^(.*)document\.location\.href(.*)$ [OR]
RewriteCond %{QUERY_STRING} ^.*(%24&x).* [NC,OR]
RewriteCond %{QUERY_STRING} ^.*(127\.0).* [NC,OR]
RewriteCond %{QUERY_STRING} ^(.*)(%3D|=)(https?|ftp|mosConfig)(%3A|:)//(.*)$ [NC,OR] ## ATTENTION A CETTE REGLE. ELLE PEUT CASSER CERTAINES REDIRECTIONS RESSEMBLANT A: http://www.truc.fr/index.php?r=http://www.google.fr ##
RewriteCond %{QUERY_STRING} ^.*(_encode|localhost|loopback).* [NC,OR]
RewriteCond %{QUERY_STRING} ^(.*)GLOBALS(=|[|%[0-9A-Z]{0,2})(.*)$ [OR]
RewriteCond %{QUERY_STRING} ^(.*)_REQUEST(=|[|%[0-9A-Z]{0,2})(.*)$ [OR]
RewriteCond %{QUERY_STRING} ^(.*)(SELECT(%20|\+)|UNION(%20|\+)ALL|INSERT(%20|\+)|DELETE(%20|\+)|CHAR\(|UPDATE(%20|\+)|REPLACE(%20|\+)|LIMIT(%20|\+)|CONCAT(%20|\+)|DECLARE(%20|\+))(.*)$ [NC]
RewriteRule (.*) - [F]
<IfModule mod_headers.c>
# A TESTER la page n'accepte que les scripts provenant du même nom de domaine et de google.com (par exemple)
#Header set Content-Security-Policy "script-src 'self' https://www.google.com"
# Protection contre le clickjacking. Interdit que le site soit intégré en iframe
Header always set X-FRAME-OPTIONS "DENY"
# Bloque la totalité du contenu lorsqu'une attaque XSS est suspectée
Header always set X-XSS-Protection "1; mode=block"
#Désactive la détection automatique du type des ressources
Header always set X-Content-Type-Options "nosniff"
# protege les cookies d'être modifiés par le client
#Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
</IfModule>
# BEGIN DEFLATE COMPRESSION
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font
AddOutputFilterByType DEFLATE application/x-font-opentype
AddOutputFilterByType DEFLATE application/x-font-otf
AddOutputFilterByType DEFLATE application/x-font-truetype
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/otf
AddOutputFilterByType DEFLATE font/ttf
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
</IfModule>
# END DEFLATE COMPRESSION
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
ExpiresByType text/html "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/pdf "access plus 0 seconds"
ExpiresByType application/rss+xml "access plus 1 hour"
ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/x-font-ttf "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType application/x-font-woff "access plus 1 month"
ExpiresByType application/x-font-woff2 "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType video/ogg "access plus 1 month"
ExpiresByType audio/ogg "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 month"
ExpiresByType video/webm "access plus 1 month"
ExpiresByType text/css "access plus 6 month"
ExpiresByType application/javascript "access plus 6 month"
ExpiresByType application/x-shockwave-flash "access plus 1 week"
ExpiresByType image/x-icon "access plus 1 week"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\\.(ico|jpe?g|png|gif|swf|svg)$">
Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
<FilesMatch "\\.(css)$">
Header set Cache-Control "max-age=604800, public"
</FilesMatch>
<FilesMatch "\\.(js)$">
Header set Cache-Control "max-age=604800, private"
</FilesMatch>
<filesmatch "\\.(html|htm)$">
Header set Cache-Control "max-age=5, public"
</filesmatch>
<filesmatch "\\.(woff)$">
Header set Cache-Control "max-age=604800, public"
</filesmatch>
</IfModule>
<ifModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file .(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</ifModule>
Header unset Pragma
FileETag None
Header unset ETag
# Enable HackRepair.com's blacklist
# Start Abuse Agent Blocking
RewriteCond %{HTTP_USER_AGENT} "^Mozilla.*Indy" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Mozilla.*NEWT" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^$" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Maxthon$" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^SeaMonkey$" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Acunetix" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^binlar" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^BlackWidow" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Bolt 0" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^BOT for JCE" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Bot mailto\:craftbot@yahoo\.com" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^casper" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^checkprivacy" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^ChinaClaw" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^clshttp" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^cmsworldmap" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Custo" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Default Browser 0" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^diavol" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^DIIbot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^DISCo" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^dotbot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Download Demon" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^eCatch" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^EirGrabber" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^EmailCollector" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^EmailSiphon" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^EmailWolf" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Express WebPictures" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^extract" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^ExtractorPro" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^EyeNetIE" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^feedfinder" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^FHscan" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^FlashGet" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^flicky" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^g00g1e" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^GetRight" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^GetWeb\!" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Go\!Zilla" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Go\-Ahead\-Got\-It" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^grab" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^GrabNet" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Grafula" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^harvest" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^HMView" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Image Stripper" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Image Sucker" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^InterGET" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Internet Ninja" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^InternetSeer\.com" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^jakarta" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Java" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^JetCar" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^JOC Web Spider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^kanagawa" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^kmccrew" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^larbin" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^LeechFTP" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^libwww" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Mass Downloader" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^microsoft\.url" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^MIDown tool" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^miner" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Mister PiX" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^MSFrontPage" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Navroad" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^NearSite" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Net Vampire" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^NetAnts" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^NetSpider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^NetZIP" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^nutch" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Octopus" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Offline Explorer" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Offline Navigator" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^PageGrabber" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Papa Foto" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^pavuk" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^pcBrowser" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^PeoplePal" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^planetwork" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^psbot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^purebot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^pycurl" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^RealDownload" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^ReGet" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Rippers 0" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^sitecheck\.internetseer\.com" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^SiteSnagger" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^skygrid" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^SmartDownload" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^sucker" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^SuperBot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^SuperHTTP" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Surfbot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^tAkeOut" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Teleport Pro" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Toata dragostea mea pentru diavola" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^turnit" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^vikspider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^VoidEYE" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Web Image Collector" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebAuto" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebBandit" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebCopier" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebFetch" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebGo IS" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebLeacher" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebReaper" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebSauger" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Website eXtractor" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Website Quester" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebStripper" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebWhacker" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WebZIP" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Widow" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WPScan" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WWW\-Mechanize" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^WWWOFFLE" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Xaldon WebSpider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^Zeus" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "^zmeu" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "360Spider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "CazoodleBot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "discobot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "EasouSpider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "ecxi" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "GT\:\:WWW" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "heritrix" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "HTTP\:\:Lite" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "HTTrack" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "ia_archiver" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "id\-search" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "IDBot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Indy Library" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "IRLbot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "ISC Systems iRc Search 2\.1" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "LinksCrawler" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "LinksManager\.com_bot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "linkwalker" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "lwp\-trivial" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "MFC_Tear_Sample" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Microsoft URL Control" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Missigua Locator" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "MJ12bot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "panscient\.com" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "PECL\:\:HTTP" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "PHPCrawl" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "PleaseCrawl" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "SBIder" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "SearchmetricsBot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "SeznamBot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Snoopy" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Steeler" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "URI\:\:Fetch" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "urllib" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Web Sucker" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "webalta" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "WebCollage" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "Wells Search II" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "WEP Search" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "XoviBot" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "YisouSpider" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "zermelo" [NC,OR]
RewriteCond %{HTTP_USER_AGENT} "ZyBorg" [NC,OR]
# End Abuse Agent Blocking
# Start Abuse HTTP Referrer Blocking
RewriteCond %{HTTP_REFERER} "^https?://(?:[^/]+\.)?semalt\.com" [NC,OR]
RewriteCond %{HTTP_REFERER} "^https?://(?:[^/]+\.)?kambasoft\.com" [NC,OR]
RewriteCond %{HTTP_REFERER} "^https?://(?:[^/]+\.)?savetubevideo\.com" [NC]
# End Abuse HTTP Referrer Blocking
RewriteRule ^.* - [F,L]
# End HackRepair.com Blacklist, http://pastebin.com/u/hackrepair
# mode maintenance (commentez le bloque BEGIN WordPress)
#<IfModule mod_rewrite.c>
# Options +FollowSymlinks
# RewriteEngine on
# RewriteCond %{REQUEST_URI} !/maintenance.html$
# # use X-FORWARDED-FOR whith haproxy
# RewriteCond %{HTTP:X-FORWARDED-FOR} !86.65.205.242
# RewriteRule $ /maintenance.html [R=302,L]
#</IfModule>
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
## REDIRECTIONS PERMANENTES ##
RedirectPermanent /de/accueil /de/
RedirectPermanent /home /
RedirectPermanent /de/home /de/
RedirectPermanent /de/our-products /products/
RedirectPermanent /de/our-values /de/konzept/
RedirectPermanent /de/our-breeding-farm-in-pictures /de/konzept/
RedirectPermanent /our-breeding-farm-in-pictures /concept/
RedirectPermanent /testimonies-of-our-breeders /concept/
RedirectPermanent /sharebutton.to /
RedirectPermanent /our-values /concept/
RedirectPermanent /our-products /products/
RedirectPermanent /de/nos-valeurs /de/konzept/
RedirectPermanent /de/contact /de/kontakt/
RedirectPermanent /contact /contact-us/
RedirectPermanent /de/testimonies-of-our-breeders /de/konzept/
RedirectPermanent /de/our-products/?categ=oie /de/produkte/
RedirectPermanent /de/our-breeding-farm-in-pictures/?categ=oie /de/produkte/
RedirectPermanent /de/our-products/?categ=canard /de/produkte/
RedirectPermanent /de/our-breeding-farm-in-pictures/?categ=canard /de/produkte/
RedirectPermanent /our-breeding-farm-in-pictures/?categ=canard /products/
RedirectPermanent /our-breeding-farm-in-pictures/?categ=poulet /products/
RedirectPermanent /de/our-breeding-farm-in-pictures/?categ=poulet /de/produkte/
RedirectPermanent /de/our-products/?categ=poulet /de/produkte/
RedirectPermanent /our-products/?categ=poulet /products/
RedirectPermanent /our-products/?categ=canard /products/
RedirectPermanent /de/legal-notice /de/impressum/

43
.stylelintrc.json Normal file
View file

@ -0,0 +1,43 @@
{
"extends": [
"stylelint-config-prettier",
"stylelint-config-standard-scss",
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"plugins": ["stylelint-scss"],
"files": ["**/*.css", "**/*.scss"],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
"**/vendor/*.scss",
"**/vendor/**/*.scss",
"**/vendor/*.sass",
"**/vendor/**/*.sass",
"**/vendor/*.less",
"**/vendor/**/*.less"
],
"customSyntax": "postcss-scss",
"rules": {
"indentation": 4,
"string-quotes": "double",
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"declaration-colon-space-after": "always-single-line",
"function-parentheses-space-inside": "never-single-line",
"declaration-colon-newline-after": null,
"scss/double-slash-comment-empty-line-before": [
"always",
{
"ignore": [
"between-comments",
"stylelint-commands",
"inside-block"
]
}
]
}
}

25
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,25 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#d6de61",
"activityBar.background": "#d6de61",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#21969e",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#15202b99",
"sash.hoverBorder": "#d6de61",
"statusBar.background": "#cbd537",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#aab326",
"statusBarItem.remoteBackground": "#cbd537",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#cbd537",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#cbd53799",
"titleBar.inactiveForeground": "#15202b99"
},
"peacock.remoteColor": "#CBD537",
"intelephense.environment.phpVersion": "8.2.0",
"intelephense.completion.fullyQualifyGlobalConstantsAndFunctions": true,
"intelephense.phpdoc.useFullyQualifiedNames": true
}

6
README.md Normal file
View file

@ -0,0 +1,6 @@
# TODO
- Creer https://console.cloud.google.com wenn mit truc pour ajouter la clé google map
- Api pour storefinder cassée rules.wo-gilt-was.de
- passer le script en TS et le style pas en inline

View file

@ -0,0 +1,177 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateAdmin extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a backend admin user';
/**
* The firstname of the new user.
*
* @var string
*/
protected $firstname;
/**
* The lastname of the new user.
*
* @var string
*/
protected $lastname;
/**
* The email of the new user.
*
* @var string
*/
protected $email;
/**
* The password of the new user.
*
* @var string
*/
protected $password;
/**
* Execute the console command.
*
* @return integer
*/
public function handle(): int
{
$this->firstname = null;
$this->addFirstname();
$this->lastname = null;
$this->addLastname();
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$user = new User([
'firstname' => $this->firstname,
'lastname' => $this->lastname,
'email' => $this->email,
'password' => $this->password,
]);
$user->saveOrFail();
$this->info('User created 👌');
return 0;
}
/**
* Add a firstname to the user.
*
* @return void
*/
private function addFirstname(): void
{
while (is_null($this->firstname)) {
try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->firstname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid firstname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a lastname to the user.
*
* @return void
*/
private function addLastname(): void
{
while (is_null($this->lastname)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->lastname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
while (is_null($this->email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$this->email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
}

33
app/Console/Kernel.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace App\Console;
use App\Jobs\SlidersStatusUpdate;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->job(SlidersStatusUpdate::class)->everyMinute();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Enums\Concepts;
use App\Traits\Enums\BaseEnum;
enum ColorsEnum: int
{
use BaseEnum;
case redwine = 1;
case orange = 2;
case blue = 3;
case lightgreen = 4;
case brown = 5;
/**
* Custom added colors definition
*/
private const COLORS = [
self::redwine->name => 'rgb(165 25 60)',
self::orange->name => 'rgb(240 165 0)',
self::blue->name => 'rgb(95 155 210)',
self::lightgreen->name => 'rgb(180 190 0)',
self::brown->name => 'rgb(95 40 45)',
];
/**
* Get Color
*
* @return string
*/
public function color(): string
{
return self::COLORS[$this->name];
}
/**
* Get enum colors
*
* @return array<string>
*/
public static function toColors(): array
{
return self::COLORS;
}
}

25
app/Enums/InputType.php Normal file
View file

@ -0,0 +1,25 @@
<?php
namespace App\Enums;
use App\Traits\Enums\BaseEnum;
enum InputType: int
{
use BaseEnum;
case boolean = 1;
case dropdownArray = 2;
case email = 3;
case image = 4;
case imageArray = 5;
case labelArray = 6;
case miniblocArray = 7;
case number = 8;
case selectEnum = 9;
case password = 10;
case string = 11;
case text = 12;
case url = 13;
case videoVimeo = 14;
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Enums\Products;
use App\Traits\Enums\BaseEnum;
enum CertificationsEnum : int
{
use BaseEnum;
case haltungsform1 = 1;
case haltungsform2 = 2;
case haltungsform3 = 3;
case haltungsform4 = 4;
case ecc = 5;
/**
* Custom added images definition
*/
private const IMAGES = [
self::haltungsform1->name => "haltungsform1.png",
self::haltungsform2->name => "haltungsform2.png",
self::haltungsform3->name => "haltungsform3.png",
self::haltungsform4->name => "haltungsform4.png",
self::ecc->name => "ecc.png",
];
/**
* Get image
*
* @return string
*/
public function image(): string
{
return self::IMAGES[$this->name];
}
/**
* Get enum images
*
* @return array<string>
*/
public static function toImages(): array
{
return self::IMAGES;
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Enums\Ranges;
use App\Traits\Enums\BaseEnum;
enum PictoEnum : int
{
use BaseEnum;
case chicken = 1;
case duck = 2;
case turkey = 3;
case soupchicken = 4;
case guineafowl = 5;
case goose = 6;
/**
* Custom added images definition
*/
private const IMAGES = [
self::chicken->name => "chicken.png",
self::duck->name => "duck.png",
self::turkey->name => "turkey.png",
self::soupchicken->name => "soupchicken.png",
self::guineafowl->name => "guineafowl.png",
self::goose->name => "goose.png",
];
/**
* Get image
*
* @return string
*/
public function image(): string
{
return self::IMAGES[$this->name];
}
/**
* Get enum images
*
* @return array<string>
*/
public static function toImages(): array
{
return self::IMAGES;
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Enums\Recipes;
use App\Traits\Enums\BaseEnum;
enum DifficultyEnum : int
{
use BaseEnum;
case easy = 1;
case medium = 2;
case hard = 3;
}

View file

@ -0,0 +1,12 @@
<?php
namespace App\Enums\Sliders;
use App\Traits\Enums\BaseEnum;
enum TypeEnum : int
{
use BaseEnum;
case home = 1;
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:backend')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('backend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\View\View
*/
public function showLoginForm()
{
return view('bo.auth.login');
}
/**
* Where to redirect users after login.
*
* @return string
*/
protected function redirectTo(): string
{
return response()->redirectToIntended()->getTargetUrl();
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Auth;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
return Auth::guard('backend');
}
/**
* Where to redirect users after login.
*
* @return string
*/
protected function redirectTo(): string
{
return \route('bo.login');
}
}

View file

@ -0,0 +1,176 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Concepts\StoreConceptRequest;
use App\Http\Requests\Bo\Concepts\UpdateConceptRequest;
use App\Models\Concept;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Models\MiniCrudModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ConceptController extends Controller
{
use ChangesModelOrder;
use MiniCrudModel;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $concepts */
$concepts = Concept::query();
if ($request->search) {
$this->searchQuery(
$concepts,
$request->search,
null,
'seo_title',
'name',
'title',
'subtitle',
);
}
$this->sortQuery($concepts);
$searchFields = trans('Name');
$conceptModels = $concepts->paginate(10);
return view('bo.pages.concepts.index', compact('conceptModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
if (Concept::count() === 8) {
return back()
->with(
'error',
trans(
'You already created 8 :models. Please delete or edit an existing one.',
['models' => trans('models.classes.concepts')]
)
);
}
$conceptData = Concept::getEmptyData();
return view('bo.pages.concepts.create', compact('conceptData'));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Concepts\StoreConceptRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreConceptRequest $request)
{
return DB::transaction(function () use ($request) {
$concept = new Concept();
$concept->data = $concept->getEmptyData();
$concept->fill(array_replace_recursive(
\json_decode(\json_encode($concept->toArray()), true),
$request->validated()
));
if ($concept->saveOrFail()) {
return redirect()
->route('bo.concepts.edit', $concept)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.concepts'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.concepts'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Concept $concept
* @return \Illuminate\Http\Response
*/
public function edit(Concept $concept)
{
return view('bo.pages.concepts.edit', [
'conceptModel' => $concept
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Concepts\UpdateConceptRequest $request
* @param \App\Models\Concept $concept
* @return \Illuminate\Http\Response
*/
public function update(UpdateConceptRequest $request, Concept $concept)
{
return DB::transaction(function () use ($request, $concept) {
$concept->fill(array_replace_recursive(
\json_decode(\json_encode($concept->toArray()), true),
$request->validated()
));
if ($concept->saveOrFail()) {
return redirect()
->route('bo.concepts.edit', $concept)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.concepts'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.concepts'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Concept $concept
* @return \Illuminate\Http\Response
*/
public function destroy(Concept $concept)
{
if ($concept->deleteOrFail()) {
return redirect()
->route('bo.concepts.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.concepts'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.concepts'))]
));
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
class DashboardController extends Controller
{
/**
* Dashboard Page
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
return view('bo.pages.dashboard');
}
/**
* Chagne current locale on BO.
*
* @param string $lang
* @return \Illuminate\Http\RedirectResponse
*/
public function changeLang(string $lang): RedirectResponse
{
if (!\in_array($lang, config('app.locales'))) {
$lang = config('app.fallback_locale');
}
session()->put('lang', $lang);
return redirect()->back();
}
}

View file

@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Homes\UpdateHomeRequest;
use App\Models\Home;
use App\Models\Product;
use App\Traits\Requests\HasPicture;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class HomeController extends Controller
{
use HasPicture;
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit()
{
$homeModel = Home::query()->firstOrFail();
$products = Product::query()->where('status', true)->orderBy('label', 'ASC')->get();
return view('bo.pages.homes.edit', compact('homeModel', 'products'));
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Homes\UpdateHomeRequest $request
* @param \App\Models\Home $home
* @return \Illuminate\Http\Response
*/
public function update(UpdateHomeRequest $request, Home $home)
{
return DB::transaction(function () use ($request, $home) {
$data = $request->validated();
$home->fill($data);
if ($home->saveOrFail()) {
return redirect()
->route('bo.homes.edit', $home)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.homes'))]
));
} //end if
return back()->with('success', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.homes'))]
));
});
}
}

View file

@ -0,0 +1,176 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Http\Requests\ProductIndexFilterRequest;
use App\Models\Product;
use App\Models\Range;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Requests\HasPicture;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ProductController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \App\Http\Requests\ProductIndexFilterRequest $request
* @return \Illuminate\Http\Response
*/
public function index(ProductIndexFilterRequest $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $product */
$product = Product::query();
if ($request->search) {
$this->searchQuery(
$product,
$request->search,
null,
'label',
);
}
$this->sortQuery($product);
$searchFields = trans('Label');
$noOrder = false;
if ($request->range_id) {
$product->whereHas('range', fn (Builder $q) => $q->where('id', $request->range_id));
$noOrder = true;
}
$productModels = $product->paginate(10);
$ranges = Range::query()->get();
return view('bo.pages.products.index', compact('ranges', 'productModels', 'searchFields', 'noOrder'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$rangeModels = Range::orderBy('label', 'ASC')->get();
return view('bo.pages.products.create', compact('rangeModels'));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreProductRequest $request)
{
return DB::transaction(function () use ($request) {
$product = new Product();
$tmpNutrionnalValues = \collect($request->validated()['nutritionnal_values']);
$tmpList = \collect($request->validated()['list']);
$product->fill($request->validated());
$product->nutritionnal_values = $tmpNutrionnalValues;
$product->list = $tmpList;
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.products'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.products'))]
));
});
}
/**
* Display a listing of the resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit(Product $product)
{
$rangeModels = Range::orderBy('label', 'ASC')->get();
return view('bo.pages.products.edit', [
'productModel' => $product,
'rangeModels' => $rangeModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function update(UpdateProductRequest $request, Product $product)
{
return DB::transaction(function () use ($request, $product) {
$tmpNutrionnalValues = \collect($request->validated()['nutritionnal_values']);
$tmpList = \collect($request->validated()['list']);
$product->fill($request->validated());
$product->nutritionnal_values = $tmpNutrionnalValues;
$product->list = $tmpList;
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.products'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.products'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
if ($product->deleteOrFail()) {
return redirect()
->route('bo.products.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.products'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.products'))]
));
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ranges\StoreRangeRequest;
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
use App\Models\Range;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Requests\HasPicture;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RangeController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $range */
$range = Range::query();
if ($request->search) {
$this->searchQuery(
$range,
$request->search,
null,
'label',
);
}
$this->sortQuery($range);
$searchFields = trans('Label');
$rangeModels = $range->paginate(10);
return view('bo.pages.ranges.index', compact('rangeModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.pages.ranges.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\StoreRangeRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreRangeRequest $request)
{
return DB::transaction(function () use ($request) {
$range = new Range();
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.ranges'))]
));
});
}
/**
* Display a listing of the resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit(Range $range)
{
return view('bo.pages.ranges.edit', ['rangeModel' => $range]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
*/
public function update(UpdateRangeRequest $request, Range $range)
{
return DB::transaction(function () use ($request, $range) {
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.ranges'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
*/
public function destroy(Range $range)
{
if ($range->deleteOrFail()) {
return redirect()
->route('bo.ranges.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
}

View file

@ -0,0 +1,164 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Recipes\StoreRecipeRequest;
use App\Http\Requests\Bo\Recipes\UpdateRecipeRequest;
use App\Models\Range;
use App\Models\Recipe;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Requests\HasPicture;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RecipeController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $recipe */
$recipe = Recipe::query();
if ($request->search) {
$this->searchQuery(
$recipe,
$request->search,
null,
'name',
);
}
$this->sortQuery($recipe);
$searchFields = trans('Nom');
$recipeModels = $recipe->paginate(10);
return view('bo.pages.recipes.index', compact('recipeModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$rangeModels = Range::orderBy('order', 'ASC')->get();
return view('bo.pages.recipes.create', compact('rangeModels'));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Recipes\StoreRecipeRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreRecipeRequest $request)
{
return DB::transaction(function () use ($request) {
$recipe = new Recipe();
$tmpIngredients = \collect($request->validated()['ingredients'])->sortKeys();
$recipe->fill($request->validated());
$recipe->ingredients = $tmpIngredients;
if ($recipe->saveOrFail()) {
return redirect()
->route('bo.recipes.edit', $recipe)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.recipes'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.recipes'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Recipe $recipe
* @return \Illuminate\Http\Response
*/
public function edit(Recipe $recipe)
{
$rangeModels = Range::orderBy('order', 'ASC')->get();
return view('bo.pages.recipes.edit', [
'recipeModel' => $recipe,
'rangeModels' => $rangeModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Recipes\UpdateRecipeRequest $request
* @param \App\Models\Recipe $recipe
* @return \Illuminate\Http\Response
*/
public function update(UpdateRecipeRequest $request, Recipe $recipe)
{
return DB::transaction(function () use ($request, $recipe) {
$tmpIngredients = \collect($request->validated()['ingredients'])->sortKeys();
$recipe->fill($request->validated());
$recipe->ingredients = $tmpIngredients;
if ($recipe->saveOrFail()) {
return redirect()
->route('bo.recipes.edit', $recipe)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.recipes'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.recipes'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Recipe $recipe
* @return \Illuminate\Http\Response
*/
public function destroy(Recipe $recipe)
{
if ($recipe->deleteOrFail()) {
return redirect()
->route('bo.recipes.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.recipes'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.recipes'))]
));
}
}

View file

@ -0,0 +1,153 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Sliders\StoreSliderRequest;
use App\Http\Requests\Bo\Sliders\UpdateSliderRequest;
use App\Models\Slider;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SliderController extends Controller
{
use ChangesModelOrder;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $sliders */
$sliders = Slider::query();
if ($request->search) {
$this->searchQuery(
$sliders,
$request->search,
null,
'title',
'subtitle',
);
}
$this->sortQuery($sliders);
$searchFields = trans('Title, Subtitle');
$sliderModels = $sliders->paginate(10);
return view('bo.pages.sliders.index', compact('sliderModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.pages.sliders.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Sliders\StoreSliderRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreSliderRequest $request)
{
return DB::transaction(function () use ($request) {
$slider = new Slider();
$slider->fill($request->validated());
if ($slider->saveOrFail()) {
return redirect()
->route('bo.sliders.edit', $slider)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.sliders'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.sliders'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Slider $slider
* @return \Illuminate\Http\Response
*/
public function edit(Slider $slider)
{
return view('bo.pages.sliders.edit', [
'sliderModel' => $slider
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Sliders\UpdateSliderRequest $request
* @param \App\Models\Slider $slider
* @return \Illuminate\Http\Response
*/
public function update(UpdateSliderRequest $request, Slider $slider)
{
return DB::transaction(function () use ($request, $slider) {
$slider->fill($request->validated());
if ($slider->saveOrFail()) {
return redirect()
->route('bo.sliders.edit', $slider)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.sliders'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.sliders'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Slider $slider
* @return \Illuminate\Http\Response
*/
public function destroy(Slider $slider)
{
if ($slider->deleteOrFail()) {
return redirect()
->route('bo.sliders.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.sliders'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.sliders'))]
));
}
}

View file

@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Stores\StoreStoreRequest;
use App\Http\Requests\Bo\Stores\UpdateStoreRequest;
use App\Models\Store;
use App\Models\StoreType;
use App\Models\StoreRegion;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Requests\HasPicture;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StoreController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $store */
$store = Store::query();
if ($request->search) {
$this->searchQuery(
$store,
$request->search,
null,
'state',
);
}
$this->sortQuery($store);
$searchFields = trans('State');
$storeModels = $store->paginate(10);
return view('bo.pages.stores.index', compact('storeModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$typeModels = StoreType::orderBy('name', 'ASC')->get();
$regionModels = StoreRegion::orderBy('name', 'ASC')->get();
return view('bo.pages.stores.create', compact('typeModels', 'regionModels'));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Stores\StoreStoreRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreStoreRequest $request)
{
return DB::transaction(function () use ($request) {
$store = new Store();
$store->fill($request->validated());
if ($store->saveOrFail()) {
return redirect()
->route('bo.stores.edit', $store)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.stores'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.stores'))]
));
});
}
/**
* Display a listing of the resource.
*
* @param \App\Models\Store $store
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit(Store $store)
{
$typeModels = StoreType::orderBy('name', 'ASC')->get();
$regionModels = StoreRegion::orderBy('name', 'ASC')->get();
return view('bo.pages.stores.edit', [
'storeModel' => $store,
'typeModels' => $typeModels,
'regionModels' => $regionModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Stores\UpdateStoreRequest $request
* @param \App\Models\Store $store
* @return \Illuminate\Http\Response
*/
public function update(UpdateStoreRequest $request, Store $store)
{
return DB::transaction(function () use ($request, $store) {
$store->fill($request->validated());
if ($store->saveOrFail()) {
return redirect()
->route('bo.stores.edit', $store)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.stores'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.stores'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Store $store
* @return \Illuminate\Http\Response
*/
public function destroy(Store $store)
{
if ($store->deleteOrFail()) {
return redirect()
->route('bo.stores.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.stores'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.stores'))]
));
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StoreGroups\StoreStoreGroupRequest;
use App\Http\Requests\Bo\StoreGroups\UpdateStoreGroupRequest;
use App\Models\StoreGroup;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Requests\HasPicture;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StoreGroupController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $storeGroup */
$storeGroup = StoreGroup::query();
if ($request->search) {
$this->searchQuery(
$storeGroup,
$request->search,
null,
'name',
);
}
$this->sortQuery($storeGroup);
$searchFields = trans('Name');
$storeGroupModels = $storeGroup->paginate(10);
return view('bo.pages.storegroups.index', compact('storeGroupModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.pages.storegroups.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\StoreGroups\StoreStoreGroupRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreStoreGroupRequest $request)
{
return DB::transaction(function () use ($request) {
$storeGroup = new StoreGroup();
$storeGroup->fill($request->validated());
if ($storeGroup->saveOrFail()) {
return redirect()
->route('bo.storegroups.edit', $storeGroup)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.storegroups'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.storegroups'))]
));
});
}
/**
* Display a listing of the resource.
*
* @param \App\Models\StoreGroup $storegroup
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function edit(StoreGroup $storegroup)
{
return view('bo.pages.storegroups.edit', ['storegroupModel' => $storegroup]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StoreGroups\UpdateStoreGroupRequest $request
* @param \App\Models\StoreGroup $storegroup
* @return \Illuminate\Http\Response
*/
public function update(UpdateStoreGroupRequest $request, StoreGroup $storegroup)
{
return DB::transaction(function () use ($request, $storegroup) {
$storegroup->fill($request->validated());
if ($storegroup->saveOrFail()) {
return redirect()
->route('bo.storegroups.edit', $storegroup)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.storegroups'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.storegroups'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\StoreGroup $storegroup
* @return \Illuminate\Http\Response
*/
public function destroy(StoreGroup $storegroup)
{
foreach ($storegroup->storeRegions as $region) {
foreach ($region->stores as $store) {
$store->deleteOrFail();
}
$region->deleteOrFail();
}
if ($storegroup->deleteOrFail()) {
return redirect()
->route('bo.storegroups.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.storegroups'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.storegroups'))]
));
}
}

View file

@ -0,0 +1,163 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StoreRegions\StoreStoreRegionRequest;
use App\Http\Requests\Bo\StoreRegions\UpdateStoreRegionRequest;
use App\Models\StoreRegion;
use App\Models\StoreGroup;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Requests\HasPicture;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StoreRegionController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $storeregion */
$storeregion = StoreRegion::query();
if ($request->search) {
$this->searchQuery(
$storeregion,
$request->search,
null,
'name',
);
}
$this->sortQuery($storeregion);
$searchFields = trans('Name');
$storeRegionModels = $storeregion->paginate(10);
return view('bo.pages.storeregions.index', compact('storeRegionModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$storegroupModels = StoreGroup::orderBy('order', 'ASC')->get();
return view('bo.pages.storeregions.create', compact('storegroupModels'));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\StoreRegions\StoreStoreRegionRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreStoreRegionRequest $request)
{
return DB::transaction(function () use ($request) {
$storeregion = new StoreRegion();
$storeregion->fill($request->validated());
if ($storeregion->saveOrFail()) {
return redirect()
->route('bo.storeregions.edit', $storeregion)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.storeregions'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.storeregions'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\StoreRegion $storeregion
* @return \Illuminate\Http\Response
*/
public function edit(StoreRegion $storeregion)
{
$storegroupModels = StoreGroup::orderBy('order', 'ASC')->get();
return view('bo.pages.storeregions.edit', [
'storeregionModel' => $storeregion,
'storegroupModels' => $storegroupModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StoreRegions\UpdateStoreRegionRequest $request
* @param \App\Models\StoreRegion $storeregion
* @return \Illuminate\Http\Response
*/
public function update(UpdateStoreRegionRequest $request, StoreRegion $storeregion)
{
return DB::transaction(function () use ($request, $storeregion) {
$storeregion->fill($request->validated());
if ($storeregion->saveOrFail()) {
return redirect()
->route('bo.storeregions.edit', $storeregion)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.storeregions'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.storeregions'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\StoreRegion $storeregion
* @return \Illuminate\Http\Response
*/
public function destroy(StoreRegion $storeregion)
{
foreach ($storeregion->stores as $store) {
$store->deleteOrFail();
}
if ($storeregion->deleteOrFail()) {
return redirect()
->route('bo.storeregions.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.storeregions'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.storeregions'))]
));
}
}

View file

@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StoreTypes\StoreStoreTypeRequest;
use App\Http\Requests\Bo\StoreTypes\UpdateStoreTypeRequest;
use App\Models\StoreType;
use App\Traits\Controllers\UpdateModelStatus;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Requests\HasPicture;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StoreTypeController extends Controller
{
use ChangesModelOrder;
use HasPicture;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $storetype */
$storetype = StoreType::query();
if ($request->search) {
$this->searchQuery(
$storetype,
$request->search,
null,
'name',
);
}
$this->sortQuery($storetype);
$searchFields = trans('Name');
$storeTypeModels = $storetype->paginate(10);
return view('bo.pages.storetypes.index', compact('storeTypeModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.pages.storetypes.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\StoreTypes\StoreStoreTypeRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreStoreTypeRequest $request)
{
return DB::transaction(function () use ($request) {
$storetype = new StoreType();
$storetype->fill($request->validated());
if ($storetype->saveOrFail()) {
return redirect()
->route('bo.storetypes.edit', $storetype)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.storetypes'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.storetypes'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\StoreType $storetype
* @return \Illuminate\Http\Response
*/
public function edit(StoreType $storetype)
{
return view('bo.pages.storetypes.edit', [
'storetypeModel' => $storetype,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StoreTypes\UpdateStoreTypeRequest $request
* @param \App\Models\StoreType $storetype
* @return \Illuminate\Http\Response
*/
public function update(UpdateStoreTypeRequest $request, StoreType $storetype)
{
return DB::transaction(function () use ($request, $storetype) {
$storetype->fill($request->validated());
if ($storetype->saveOrFail()) {
return redirect()
->route('bo.storetypes.edit', $storetype)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.storetypes'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.storetypes'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\StoreType $storetype
* @return \Illuminate\Http\Response
*/
public function destroy(StoreType $storetype)
{
foreach ($storetype->stores as $store) {
$store->deleteOrFail();
}
if ($storetype->deleteOrFail()) {
return redirect()
->route('bo.storetypes.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.storetypes'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.storetypes'))]
));
}
}

View file

@ -0,0 +1,125 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
string ...$fields
): Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$this->searchOnFields($query, $word, $fields);
} //end foreach
// Search whole using whole sentence.
$this->searchOnFields($query, $search, $fields);
if ($searchQuery) {
$searchQuery($query);
}
});
} //end if
return $query;
}
/**
* Build query search on all fields
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param array $fields
* @return \Illuminate\Database\Eloquent\Builder
*/
private function searchOnFields(Builder $query, string $search, array $fields): Builder
{
$table = $query->getModel()->getTable();
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
} else {
$query->orWhereRaw(
"UPPER(`$table`.`$f`) LIKE UPPER(?)",
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
}
}
return $query;
}
/**
* Sort columns with a query
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function sortQuery(Builder $query, array $ignore = []): Builder
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
if (request()->sort_col) {
Session::put("$rName.sort_col", request()->sort_col);
Session::put("$rName.sorted", true);
}
if (request()->sort_way) {
Session::put("$rName.sort_way", request()->sort_way);
Session::put("$rName.sorted", true);
}
if (request()->rst) {
Session::remove("$rName.sorted");
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
}
return $query;
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Concept;
class ConceptController extends Controller
{
/**
* Page show
*
* @param \App\Models\Concept $concept
* @return \Illuminate\Contracts\View\View
*/
public function show(Concept $concept)
{
return view('fo.pages.concepts.concept', ['conceptModel' => $concept]);
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\ContactRequest;
use App\Mail\ContactMessageReceived;
use Illuminate\Support\Facades\Mail;
class ContactController extends Controller
{
/**
* Show contact page
*
* @return \Illuminate\Contracts\View\View
*/
public function contactForm()
{
return view('fo.pages.contact');
}
/**
* Submit contact form
*
* @param \App\Http\Requests\Fo\ContactRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function sendContactForm(ContactRequest $request)
{
Mail::queue(new ContactMessageReceived(
first_name: $request->first_name,
last_name: $request->last_name,
email: $request->email,
message: $request->message
));
return redirect()->back()->with(['success' => \trans('Thank you for your message.')]);
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\StoreGroup;
class GroupController extends Controller
{
/**
* RetailGroup Show
*
* @param integer $id
* @return \App\Models\StoreGroup
*/
public function show(int $id)
{
return StoreGroup::query()->find($id);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Home;
use App\Models\Slider;
class HomeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$home = Home::query()->firstOrFail();
$sliders = Slider::query()->where('status', true)->orderBy('order')->get();
return view('fo.pages.home', compact('home', 'sliders'));
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\Range;
use Illuminate\Contracts\Database\Eloquent\Builder;
class ProductController extends Controller
{
/**
* Products index
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$ranges = Range::query()->where('status', true)
->whereHas('products', fn (Builder $q) => $q->where('status', true))
->with('products', fn (Builder $q) => $q->where('status', true)->orderBy('order'))
->orderBy('order')
->get();
return view('fo.pages.products.index', compact(['ranges']));
}
/**
* Product show
*
* @param \App\Models\Product $publishedProduct
* @return \Illuminate\Contracts\View\View
*/
public function show(Product $publishedProduct)
{
return view('fo.pages.products.show', ['productModel' => $publishedProduct]);
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Recipe;
use Illuminate\Contracts\Database\Eloquent\Builder;
class RecipeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function index()
{
$recipes = Recipe::query()->where('status', true)
->whereHas('range', fn (Builder $q) => $q->where('status', true))
->orderBy('order')
->get();
return view('fo.pages.recipes.index', compact('recipes'));
}
/**
* Recipe show
*
* @param \App\Models\Recipe $publishedRecipe
* @return \Illuminate\Contracts\View\View
*/
public function show(Recipe $publishedRecipe)
{
return view('fo.pages.recipes.show', ['recipeModel' => $publishedRecipe]);
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\StoreRegion;
class RegionController extends Controller
{
/**
* Show retail region
*
* @param integer $id
* @return \App\Models\StoreRegion;
*/
public function show(int $id)
{
return StoreRegion::find($id);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\StoreFinderRequest;
use App\Http\Resources\StoreFinderResource;
use App\Models\Store;
use Illuminate\Http\JsonResponse;
class StoreController extends Controller
{
/**
* Store by city
*
* @param \App\Http\Requests\Api\StoreFinderRequest $request
* @return \Illuminate\Http\JsonResponse;
*/
public function city(StoreFinderRequest $request): JsonResponse
{
return \response()->json(new StoreFinderResource(Store::query()
->where('city', 'LIKE', '%' . $request->input('city') . '%')
->orWhere('zipcode', 'LIKE', '%' . $request->input('city') . '%')
->orderBy('order')
->get()));
}
}

93
app/Http/Kernel.php Normal file
View file

@ -0,0 +1,93 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
'htmlsanitize',
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\CodeZero\LocalizedRoutes\Middleware\SetLocale::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'lang' => \App\Http\Middleware\Lang::class,
];
/**
* The priority-sorted list of middleware.
*
* Forces non-global middleware to always be in the given order.
*
* @var string[]
*/
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\CodeZero\LocalizedRoutes\Middleware\SetLocale::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo(Request $request): ?string
{
$logins = [
'backend' => route('bo.login')
];
if (!$request->expectsJson()) {
// * Notify must login
Session::flash('warning', trans('Veuillez vous connecter pour continuer'));
foreach ($request->route()->gatherMiddleware() as $middleware) {
if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) {
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
if ($route) {
return $route;
}
}
}
return route('login');
}
abort(401);
return null;
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [];
}

View file

@ -0,0 +1,61 @@
<?php
namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class HtmlSanitizer
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|array ...$params
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, \Closure $next, string|array ...$params): Response
{
$inputs = $request->all();
/** @var callable|null */
$cleanInput = null;
$cleanInput = function (
null|string|array &$input,
string $name,
?string $rootName = null
) use (
$params,
&$cleanInput
): void {
// * Nested element array
if (is_array($input)) {
array_walk_recursive($input, $cleanInput, $name);
}
if (!is_string($input)) {
return;
}
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
// ? Is a json input ?
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
// ! Decode each json field recursive.
\array_walk_recursive($isJson, function (string|array &$item) {
if (\is_string($item)) {
$item = clean($item);
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
}
});
} else {
$input = clean($input);
$input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input);
}
} else {
$input = strip_tags($input);
}
};
\array_walk($inputs, $cleanInput);
$request->merge($inputs);
return $next($request);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Symfony\Component\HttpFoundation\Response;
class Lang
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next): Response
{
// phpcs:enable
$prefix = Route::current()->action['prefix'];
if ($prefix === '/bo') {
app()->setLocale(session()->get('lang', config('app.locale')));
return $next($request);
}
return $next($request);
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [];
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string|null ...$guards
* @return \Symfony\Component\HttpFoundation\Response
* @throws \RuntimeException If guard is unhandled.
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, \Closure $next, ...$guards): Response
{
// phpcs:enable
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
switch ($guard) {
case 'frontend':
return \redirect()->route('home');
case 'backend':
if (!$request->routeIs('bo.*')) {
continue;
}
return \redirect()->route('bo.dashboard');
default:
throw new \RuntimeException("Unhandled guard `{$guard}` .");
}
}
}
return $next($request);
}
}

View file

@ -0,0 +1,115 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
/**
* Enumerate headers which you do not want in your application's responses.
* Great starting point would be to go check out @Scott_Helme's:
* https://securityheaders.com/
*
* @var array
*/
private $unwantedHeaderList = [
'X-Powered-By',
'Server',
];
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next): Response
{
// phpcs:enable
View::share('nonce', $nonce = Vite::useCspNonce());
// * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ?
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList);
$response = $next($request);
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
$cspPolicies = [
"default-src 'none';",
"manifest-src 'self';",
"prefetch-src 'self' https://cloud.ccm19.de;",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob: " . \implode(' ', [
'https://rules.wo-gilt-was.de',
'https://cloud.ccm19.de',
'https://region1.google-analytics.com',
'https://maps.googleapis.com',
]) . ';',
"img-src * data: blob:;",
'frame-src ' . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/'
]) . ';',
"font-src 'self' data: " . \implode(' ', [
'https://fonts.gstatic.com',
'https://use.typekit.net',
]) . ';',
// ! Using CKEditor will must have unsafe-inline style
// phpcs:ignore Generic.Files.LineLength.TooLong
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
'https://cdnjs.cloudflare.com',
'https://use.typekit.net',
'https://p.typekit.net',
'https://cloud.ccm19.de',
// * File Manager
'https://fonts.googleapis.com',
'https://cdn.jsdelivr.net',
'https://use.fontawesome.com',
'https://fonts.googleapis.com'
]) . ';',
// ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';",
// phpcs:ignore Generic.Files.LineLength.TooLong
"script-src 'self' {$jsDev} " . \implode(' ', [
'https: blob:',
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
"$scheme://www.google-analytics.com",
]) . ';'
];
$response->headers->set(
'Content-Security-Policy',
implode(' ', $cspPolicies) . $reportUri
);
$response->headers->set('Feature-Policy', 'fullscreen \'self\';');
return $response;
}
/**
* Remove unwanted headers
*
* @param array $headerList
* @return void
*/
private function removeUnwantedHeaders(array $headerList)
{
foreach ($headerList as $header) {
header_remove($header);
}
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts()
{
return [
$this->allSubdomainsOfApplicationUrl()
];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var integer
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View file

@ -0,0 +1,15 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [];
}

View file

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
'/csp-report', '/*'
];
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
class StoreFinderRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'city' => 'required|string|min:1'
];
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class ChangeOrderRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'direction' => $this->action === 'up' ? true : false
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'action' => 'required|string|in:up,down',
'direction' => 'required|boolean',
];
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace App\Http\Requests\Bo\Concepts;
use App\Http\Requests\Bo\UpdateMiniCrudRequest;
use App\Models\Concept;
use App\Traits\Requests\HasPicture;
use Illuminate\Support\Str;
class StoreConceptRequest extends UpdateMiniCrudRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return (auth('backend')->check() && Concept::count() < 9);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug(strip_tags($this->name))
]);
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'name' => 'required|min:3|max:70',
'slug' => 'unique_translation:concepts',
'title' => 'required|min:3|max:70',
'subtitle' => 'required|min:3|max:70',
'description' => 'required|string|min:15',
'data' => 'present|array',
'data.*' => 'required|array',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
];
return \array_merge(
$rules,
$this->pictureRules('picture', 1512, 354, 1512, 354, false),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'seo_title' => trans('titre') . ' seo',
'seo_description' => trans('description') . ' seo',
'name' => trans('name of the concept'),
'slug' => trans('slug of the concept'),
'title' => trans('title of the concept'),
'subtitle' => trans('subtitle of the concept'),
'description' => trans('description of the concept'),
'data' => trans('values of the data'),
'data.*.value' => trans('value of the data'),
'picture' => trans('illustration'),
'picture_alt' => trans('alt attribute of the image'),
'picture_title' => trans('title attribute of the image'),
];
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Requests\Bo\Concepts;
use App\Http\Requests\Bo\Concepts\StoreConceptRequest;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
class UpdateConceptRequest extends StoreConceptRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'slug' => UniqueTranslationRule::for('concepts')->ignore($this->concept->id),
];
return \array_merge(
parent::rules(),
$rules,
$this->pictureRules('picture', 1512, 354, 1512, 354, true),
);
}
}

View file

@ -0,0 +1,97 @@
<?php
namespace App\Http\Requests\Bo\Homes;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
class UpdateHomeRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->mergePicture('part3_picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'part1_title' => 'required|min:3|max:70',
'part1_subtitle' => 'required|min:3|max:70',
'part1_text_1' => 'required|string|min:15',
'part1_text_2' => 'required|string|min:15',
'part1_text_3' => 'required|string|min:15',
'part2_title' => 'required|min:3|max:70',
'part2_subtitle' => 'required|min:3|max:70',
'part2_text' => 'required|string|min:15',
'part2_list' => 'present|array',
'part2_list.*' => 'required|array',
'part2_list.*.value' => 'required|string|min:3|max:255',
'part2_product1_id' => 'required|exists:products,id',
'part2_product2_id' => 'required|exists:products,id',
'part3_picture_alt' => 'required|min:3|max:255',
'part3_picture_title' => 'required|min:3|max:255',
];
return \array_merge(
$rules,
$this->pictureRules('part3_picture', 2048, 1366, 2048, 1366, true),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'seo_title' => trans('titre') . ' seo',
'seo_description' => trans('description') . ' seo',
'part1_title' => trans('title part.1'),
'part1_subtitle' => trans('subtitle part.1'),
'part1_text_1' => trans('text 1 part.1'),
'part1_text_2' => trans('text 2 part.1'),
'part1_text_3' => trans('text 3 part.1'),
'part2_title' => trans('title part.2'),
'part2_subtitle' => trans('subtitle part.2'),
'part2_text' => trans('text part.2'),
'part2_list' => trans('values of the list part.2'),
'part2_list.*.value' => trans('value of the list part.2'),
'part2_product1_id' => trans('value of the first product part.2'),
'part2_product2_id' => trans('value of the second product part.2'),
'part3_picture' => trans('illustration'),
'part3_picture_alt' => trans('alt attribute of the image'),
'part3_picture_title' => trans('title attribute of the image'),
];
}
}

View file

@ -0,0 +1,112 @@
<?php
namespace App\Http\Requests\Bo\Products;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
class StoreProductRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug(strip_tags($this->label)),
'status' => $this->status ? true : false,
'list' => $this->list ?? [],
'nutritionnal_values' => $this->nutritionnal_values ?? [],
'certifications' => \collect($this->certifications)->all(),
]);
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'label' => 'required|min:3|max:255',
'slug' => 'unique_translation:products',
'description' => 'required|string|min:15',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'certifications' => 'present|array',
'certifications.*' => 'required|integer',
'list' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (1 > count($value)) {
$fail(\trans('You must fill in at least 2 lines in the list.'));
}
}
],
'list.*' => 'required|array',
'list.*.value' => 'nullable|string|min:3|max:255',
'nutritionnal_values' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (1 > count($value)) {
$fail(\trans('You must fill in at least 2 lines of nutritional values.'));
}
}
],
'nutritionnal_values.*' => 'required|array',
'nutritionnal_values.*.label' => 'nullable|string|min:3|max:255',
'nutritionnal_values.*.value' => 'nullable|string|min:3|max:255',
'range_id' => 'required|exists:ranges,id',
'status' => 'required|boolean'
];
return \array_merge(
$rules,
$this->pictureRules('picture', 600, 600, 600, 600, false),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'label' => trans('label'),
'slug' => trans('slug'),
'description' => trans('description'),
'picture_alt' => trans('picture_alt'),
'picture_title' => trans('picture_title'),
'certifications' => trans('certifications'),
'certifications.*' => trans('value(s) of certifications'),
'list' => trans('list'),
'nutritionnal_values' => trans('nutritionnal_values'),
'nutritionnal_values.*.label' => trans('label(s) of nutritionnal_values'),
'nutritionnal_values.*.value' => trans('value(s) of nutritionnal_values'),
'range_id' => trans('range'),
'status' => trans('publish'),
];
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace App\Http\Requests\Bo\Products;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
class UpdateProductRequest extends StoreProductRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'label' => 'required|min:3|max:255',
'slug' => UniqueTranslationRule::for('products')->ignore($this->product->id),
'description' => 'required|string|min:15',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'certifications' => 'present|array',
'certifications.*' => 'required|integer',
'list' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (1 > count($value)) {
$fail(\trans('You must fill in at least 2 lines in the list.'));
}
}
],
'list.*' => 'required|array',
'list.*.value' => 'nullable|string|min:3|max:255',
'nutritionnal_values' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (1 > count($value)) {
$fail(\trans('You must fill in at least 2 lines of nutritional values.'));
}
}
],
'nutritionnal_values.*' => 'required|array',
'nutritionnal_values.*.label' => 'nullable|string|min:3|max:255',
'nutritionnal_values.*.value' => 'nullable|string|min:3|max:255',
'range_id' => 'required|exists:ranges,id',
'status' => 'required|boolean'
];
return \array_merge(
$rules,
$this->pictureRules('picture', 600, 600, 600, 600, true),
);
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Requests\Bo\Ranges;
use App\Enums\Ranges\PictoEnum;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Enum;
class StoreRangeRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug(strip_tags($this->label)),
'status' => $this->status ? true : false,
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'label' => 'required|min:3|max:255',
'slug' => 'unique_translation:ranges',
'picto' => [new Enum(PictoEnum::class)],
'status' => 'required|boolean'
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'label' => trans('label'),
'picto' => trans('picto'),
'slug' => trans('slug'),
'status' => trans('publish')
];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Http\Requests\Bo\Ranges;
use App\Enums\Ranges\PictoEnum;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
use Illuminate\Validation\Rules\Enum;
class UpdateRangeRequest extends StoreRangeRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'label' => 'required|min:3|max:255',
'slug' => UniqueTranslationRule::for('ranges')->ignore($this->range->id),
'picto' => [new Enum(PictoEnum::class)],
'status' => 'required|boolean'
];
}
}

View file

@ -0,0 +1,107 @@
<?php
namespace App\Http\Requests\Bo\Recipes;
use App\Enums\Recipes\DifficultyEnum;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Enum;
class StoreRecipeRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'slug' => Str::slug(strip_tags($this->name)),
'status' => $this->status ? true : false,
]);
$this->mergePicture('picture');
$this->mergePicture('picture_thumbnail');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'seo_title' => 'required|min:3|max:70',
'seo_description' => 'required|min:50|max:160',
'name' => 'required|min:3|max:255',
'slug' => 'unique_translation:recipes',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'picture_thumbnail_alt' => 'required|min:3|max:255',
'picture_thumbnail_title' => 'required|min:3|max:255',
'ingredients' => [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
'present', 'array', function ($attribute, array $value, $fail) {
if (1 > count($value)) {
$fail(\trans('You must fill in at least 1 line of ingredients.'));
}
}
],
'ingredients.*' => 'nullable|array',
'ingredients.*.header' => 'nullable|string|min:3|max:255',
'ingredients.*.value' => 'string|min:3|max:255',
'teaser' => 'required|min:50|max:160',
'steps' => 'required|string|min:15',
'difficulty' => [new Enum(DifficultyEnum::class)],
'duration' => 'required|string|min:1|max:150',
'tips' => 'required|string|min:15',
'range_id' => 'required|exists:ranges,id',
'status' => 'required|boolean'
];
return \array_merge(
$rules,
$this->pictureRules('picture', 1600, 900, 1600, 900, false),
$this->pictureRules('picture_thumbnail', 430, 242, 430, 242, false),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'seo_title' => trans('title') . ' seo',
'seo_description' => trans('description') . ' seo',
'name' => trans('name'),
'slug' => trans('slug'),
'ingredients' => trans('ingredients'),
'ingredients.*.header' => trans('header in the list of ingredients'),
'ingredients.*.label' => trans('value of ingredients'),
'ingredients.*.value' => trans('value of ingredients'),
'teaser' => trans('teaser'),
'steps' => trans('steps'),
'difficulty' => trans('difficulty'),
'duration' => trans('duration'),
'tips' => trans('tips'),
'range_id' => trans('range'),
'status' => trans('publish')
];
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Requests\Bo\Recipes;
use CodeZero\UniqueTranslation\UniqueTranslationRule;
class UpdateRecipeRequest extends StoreRecipeRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'slug' => UniqueTranslationRule::for('recipes')->ignore($this->recipe->id)
];
return \array_merge(
parent::rules(),
$rules,
$this->pictureRules('picture', 1600, 900, 1600, 900, true),
$this->pictureRules('picture_thumbnail', 430, 242, 430, 242, true),
);
}
}

View file

@ -0,0 +1,88 @@
<?php
namespace App\Http\Requests\Bo\Sliders;
use App\Enums\Sliders\TypeEnum;
use App\Http\Requests\Bo\UpdateMiniCrudRequest;
use App\Traits\Requests\HasPicture;
use Illuminate\Validation\Rules\Enum;
class StoreSliderRequest extends UpdateMiniCrudRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'link_new_tab' => $this->link_new_tab ? true : false,
'status' => $this->status ? true : false,
'publish_at' => $this->publish_at ? \str_replace('T', ' ', $this->publish_at) : null,
'unpublish_at' => $this->unpublish_at ? \str_replace('T', ' ', $this->unpublish_at) : null,
]);
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'type' => [new Enum(TypeEnum::class)],
'title' => 'required|min:3|max:70',
'subtitle' => 'required|min:3|max:70',
'description' => 'required|string|min:15',
'link' => 'required|url',
'link_title' => 'required|min:3|max:255',
'link_new_tab' => 'required|boolean',
'picture_alt' => 'required|min:3|max:255',
'picture_title' => 'required|min:3|max:255',
'status' => 'required|boolean',
'publish_at' => 'sometimes|nullable|date',
'unpublish_at' => 'sometimes|nullable|date'
];
return \array_merge(
$rules,
$this->pictureRules('picture', 1270, 930, 1270, 930, false),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'type' => trans('type of slider'),
'title' => trans('title of the slide'),
'subtitle' => trans('subtitle of the slide'),
'description' => trans('description of the slide'),
'link' => trans('link of the slide'),
'link_title' => trans('title of link'),
'link_new_tab' => trans('open the link in the new tab'),
'picture' => trans('illustration'),
'picture_alt' => trans('alt attribute of the image'),
'picture_title' => trans('title attribute of the image'),
'status' => trans('status of the slide'),
];
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace App\Http\Requests\Bo\Sliders;
use App\Http\Requests\Bo\Sliders\StoreSliderRequest;
class UpdateSliderRequest extends StoreSliderRequest
{
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Http\Requests\Bo\StoreGroups;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
class StoreStoreGroupRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'name' => 'required|min:3|max:255',
];
return \array_merge(
$rules,
$this->pictureRules('picture', 200, 200, 600, 600, false),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'name' => trans('name'),
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests\Bo\StoreGroups;
use App\Http\Requests\Bo\StoreGroups\StoreStoreGroupRequest;
use Illuminate\Validation\Rule;
class UpdateStoreGroupRequest extends StoreStoreGroupRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'name' => [
'required',
'min:3',
'max:255',
Rule::unique('store_groups', 'name')->ignore($this->storegroup)
],
];
return \array_merge(
$rules,
$this->pictureRules('picture', 200, 200, 600, 600, true),
);
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace App\Http\Requests\Bo\StoreRegions;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
class StoreStoreRegionRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->mergePicture('picture');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'name' => 'required|min:3|max:255',
'group_id' => 'required|exists:store_groups,id',
];
return \array_merge(
$rules,
$this->pictureRules('picture', 200, 200, 600, 600, true),
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'name' => trans('name'),
'group_id' => trans('group'),
];
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Bo\StoreRegions;
use App\Http\Requests\Bo\StoreRegions\StoreStoreRegionRequest;
use Illuminate\Validation\Rule;
class UpdateStoreRegionRequest extends StoreStoreRegionRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'name' => [
'required',
'min:3',
'max:255',
Rule::unique('store_regions', 'name')->ignore($this->storeregion)
],
'group_id' => 'required|exists:store_groups,id',
];
return \array_merge(
$rules,
$this->pictureRules('picture', 200, 200, 600, 600, false),
);
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Http\Requests\Bo\StoreTypes;
use Illuminate\Foundation\Http\FormRequest;
class StoreStoreTypeRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'name' => 'required|min:3|max:255',
];
return \array_merge(
$rules,
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'name' => trans('name'),
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Bo\StoreTypes;
use App\Http\Requests\Bo\StoreTypes\StoreStoreTypeRequest;
use Illuminate\Validation\Rule;
class UpdateStoreTypeRequest extends StoreStoreTypeRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'name' => [
'required',
'min:3',
'max:255',
Rule::unique('store_types', 'name')->ignore($this->storetype)
],
];
return \array_merge(
$rules,
);
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Http\Requests\Bo\Stores;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
class StoreStoreRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'state' => 'required|min:3|max:255',
'type_id' => 'required|exists:store_types,id',
'street' => 'required|min:3|max:255',
'zipcode' => 'required|min:0|max:5',
'city' => 'required|min:3|max:255',
'latitude' => 'required|between:-90,90|decimal:0,8',
'longitude' => 'required|between:-180,180|decimal:0,8',
'region_id' => 'required|exists:store_regions,id',
];
return \array_merge(
$rules,
);
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'state' => trans('State'),
'type_id' => trans('Type'),
'street' => trans('Street'),
'zipcode' => trans('Zipcode'),
'city' => trans('City'),
'latitude' => trans('Latitude'),
'longitude' => trans('Longitude'),
'region_id' => trans('Region'),
];
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace App\Http\Requests\Bo\Stores;
use App\Http\Requests\Bo\Stores\StoreStoreRequest;
class UpdateStoreRequest extends StoreStoreRequest
{
}

View file

@ -0,0 +1,156 @@
<?php
namespace App\Http\Requests\Bo;
use App\Enums\InputType;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
abstract class UpdateMiniCrudRequest extends FormRequest
{
/**
* The model parameter name
*
* @var string
*/
protected $modelName = null;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
public function rules()
{
// phpcs:enable
$rules = [];
foreach ($this->{$this->modelName}->data as $iptName => $input) {
$input = \json_decode(\json_encode($input), true);
switch ($input['inputType']) {
case InputType::number->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('required|numeric', $input)
]);
break;
case InputType::string->value:
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate('required|string|min:3', $input)
]);
break;
case InputType::text->value:
$mergeRules = $this->compileValidate('required|string|min:15', $input);
$mergeRules = collect(\explode('|', $mergeRules))
->map(function ($rule) {
if (\strpos($rule, 'max:') !== false) {
$max = \intval(str_replace('max:', '', $rule));
return function ($attribute, $value, $fail) use ($max) {
if (\strlen(\strip_tags($value)) > $max) {
$fail("$attribute ne doit pas excéder $max caractères");
}
};
}
return $rule;
});
$rules = array_merge($rules, [
"data.$iptName.text" => $mergeRules
]);
break;
case InputType::image->value:
$ipt = $this->data[$iptName];
$max = isset($ipt['dimensionsMax']);
$dim = collect(explode(
',',
$ipt['dimensionsMax'] ?? $input['dimensions']
))->mapWithKeys(function ($v) {
$v = explode(' ', $v);
return [$v[0] => $v[1]];
});
$rules = array_merge($rules, [
"data.$iptName.value" => $this->compileValidate(
implode('|', [
'required',
'image',
'mimes:png,jpg,jpeg',
sprintf(
'dimensions:%s=%d,%s=%d',
$max ? 'max_height' : 'height',
$dim['hauteur'],
$max ? 'max_width' : 'width',
$dim['largeur']
)
]),
$input
),
"data.$iptName.alt" => $this->compileValidate('required|string|min:5', $input),
"data.$iptName.title" => $this->compileValidate('required|string|min:5', $input)
]);
break;
case InputType::url->value:
$rules = array_merge($rules, [
"data.$iptName.value" => [
'required', 'url',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
try {
ToolboxHelper::assertNoInternalUrls($attribute, $value);
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
} catch (ValidationException $e) {
$fail(collect($e->errors())->first()[0]);
}
}
]
]);
break;
case InputType::boolean->value:
$all = $this->all();
$nested = ToolboxHelper::dotStringToNestedArray(
"data.$iptName.value",
$this->input("data.$iptName.value") ? true : false
);
$all = array_merge_recursive_distinct($all, $nested);
$this->merge($all);
$rules = array_merge($rules, [
"data.$iptName.value" => 'boolean'
]);
break;
} //end switch
} //end foreach
return $rules;
}
/**
* Undocumented function
*
* @param string $inputTypeValidate
* @param array $input
* @return string
*/
public function compileValidate(string $inputTypeValidate, array $input): string
{
if (!isset($input['validate']) or !\is_string($input['validate'])) {
return $inputTypeValidate;
}
/** @var array */
$inputValidate = explode('|', $input['validate']);
$out = explode('|', $inputTypeValidate);
foreach ($inputValidate as $validate) {
if (!\in_array($validate, $out)) {
$out[] = $validate;
}
}
return \implode('|', $out);
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests\Fo;
use Illuminate\Foundation\Http\FormRequest;
class ContactRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'terms' => $this->terms ? true : false
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'first_name' => 'required|min:1',
'last_name' => 'required|min:1',
'email' => 'required|email:rfc,spoof,dns,strict',
'message' => 'required|string|min:10',
'terms' => 'accepted',
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProductIndexFilterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'range_id' => 'sometimes|nullable|exists:ranges,id'
];
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources;
use App\Models\Store;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class StoreFinderResource extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array<int|string, mixed>
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function toArray(Request $request): array
{
// phpcs:enable
return $this->collection->map(fn (Store $store) => [
'name' => $store->storeRegion->storeGroup->name,
'type' => $store->storeType->name,
'street' => $store->street,
'zipcode' => $store->zipcode,
'city' => $store->city,
'latitude' => $store->latitude,
'longitude' => $store->longitude,
'picture' => $store->storeRegion->storeGroup->picture,
])->all();
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Jobs;
use App\Models\Slider;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
/**
* Update published and visibility attributes reading their
* publish_at and unpublish_at attributes.
*/
class SlidersStatusUpdate
{
/**
* Called when the job will be executed.
*
* @return void
*/
public function handle()
{
$slidersToPublish = Slider::where('publish_at', '<=', Carbon::now()->toDateTime())
->whereNotNull('publish_at')
->where('status', false)->get();
Log::debug(__CLASS__ . " sliders to publish " . $slidersToPublish->count());
foreach ($slidersToPublish as $slider) {
$slider->publish_at = null;
$slider->status = true;
$slider->saveOrFail();
}
$slidersToUnPublish = Slider::where('unpublish_at', '<=', Carbon::now()->toDateTime())
->whereNotNull('unpublish_at')
->where('status', true)->get();
Log::debug(__CLASS__ . " sliders to unpublish " . $slidersToPublish->count());
foreach ($slidersToUnPublish as $slider) {
$slider->unpublish_at = null;
$slider->status = false;
$slider->saveOrFail();
}
}
}

View file

@ -0,0 +1,190 @@
<?php
namespace App\Lib\Helpers;
class ExceptionsHelper
{
/**
* Handle sentry event
*
* @param \Sentry\Event $event
* @return null|\Sentry\Event
*/
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
{
// Remove unwanted reports.
$event->setModules([]);
$event->setOsContext(null);
/** @var \Sentry\ExceptionDataBag[] */
$exceptionBag = [];
foreach ($event->getExceptions() as $exceptionDataBag) {
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
}
$event->setExceptions($exceptionBag);
return $event;
}
/**
* Anonimize Sentry Exception bag
*
* @param \Sentry\ExceptionDataBag $exceptionDataBag
* @return \Sentry\ExceptionDataBag
*/
private static function getAnonymizedSentryException(
\Sentry\ExceptionDataBag $exceptionDataBag
): \Sentry\ExceptionDataBag {
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
}
return $exceptionDataBag;
}
/**
* Anonymise sentry stacktrace
*
* @param \Sentry\Stacktrace $stackTrace
* @return \Sentry\Stacktrace
*/
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
{
/** @var \Sentry\Frame[] */
$frames = \array_reverse($stackTrace->getFrames(), true);
$i = \count($frames) - 1;
// * Remove all but keep first (index.php).
while ($i-- > 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
foreach ($frames as $index => $frame) {
$frame->setPostContext([]);
$frame->setPreContext([]);
$frame->setContextLine('');
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
$stackTrace->addFrame($frame);
if ($index === 0) {
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
}
return $stackTrace;
}
/**
* Get anonymized sentry params
*
* @param mixed[] $params
* @return string[]
*/
private static function getAnonymizedSentryParams(array $params): array
{
foreach ($params as &$param) {
$type = \strtolower(\gettype($param));
if (\is_string($param)) {
$type = \explode(' ', \trim(strtolower($param)))[0] ?? null;
}
switch (true) {
case $type === 'lambda':
case $type === 'object':
continue 2;
case $type === 'array' and \is_array($param):
$param = sprintf('Array(%d)', \count($param));
continue 2;
default:
if (\is_numeric($param)) {
$param = \ucfirst($type);
continue 2;
}
if (\is_string($param)) {
$param = sprintf('String(%d)', \strlen($param));
continue 2;
}
} //end switch
} //end foreach
return $params;
}
/**
* Anonymize at least Sql queries
*
* @param string $message
* @return string
*/
public static function getAnonymizedMessage(string $message): string
{
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
if ($sqlPos = \strpos($message, 'to use near')) {
$message = \mb_strcut($message, 0, $sqlPos);
}
return $message;
}
/**
* Anonymize stack trace
*
* @param \Throwable $e
* @return string
*/
public static function getAnonymizedStackTrace(\Throwable $e): string
{
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
$traceStack = $e->getTrace();
$traceStr = '';
$maxStackStage = 0;
foreach ($traceStack as $stackStage => $stack) {
$maxStackStage = $stackStage;
$traceStr .= \sprintf(
'#%s %s(%d): ',
$stackStage,
str_replace($projectPath, '', $stack['file'] ?? ''),
$stack['line'] ?? ''
);
if (isset($stack['class'])) {
$traceStr .= "{$stack['class']}->{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
} elseif (isset($stack['function'])) {
$traceStr .= "{$stack['function']}(";
if (isset($stack['args'])) {
$traceStr .= self::handleArgs($stack);
}
$traceStr .= ")\n";
}
} //end foreach
$maxStackStage++;
$traceStr .= "#$maxStackStage {main}";
return $traceStr;
}
/**
* Handle Args
*
* @param array $stack
* @return string
*/
public static function handleArgs(array $stack): string
{
$traceArgs = '';
$argsCount = count($stack['args']);
for ($i = 0; $i < $argsCount; $i++) {
$type = \gettype($stack['args'][$i]);
switch ($type) {
case 'object':
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
break;
case 'array':
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
break;
case 'string':
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
break;
default:
$traceArgs .= \gettype($stack['args'][$i]);
}
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
}
return $traceArgs;
}
}

View file

@ -0,0 +1,454 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* This class is used to handle files in LDC Laravel stack
*
* * The storage strategy will be using date.
* The file name may contain sufix timestamp highresolution time (hrtime or microtime) if is duplicate.
* (function() { return time() . ' ' . Str::slug(microtime(true)) . ' ' . hrtime(true); })();
*
* posts/ - The model table name
* 10/ - Year
* 04/ - Month
* 19/ - Day
* 5f/ - First two letters of md5 file
* black-cat-under-red-car.png - Slugified file name
* black-cat-under-red-car-286071690833400.png - Slugified file name that is duplicated
*
* The final path will be storage/modelfiles/posts/10/04/19/5f/black-cat-under-red-car-286071690833400.png
*
* ! No limits of file per folder will be handled.
*
* @author Munsch Jérémy <jeremy.munsch@ldc.fr>
*/
class FileStorageHelper
{
/**
* Destroy the file associate with the attribute.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeFile(Model $model, string $attribute): void
{
$path = \ltrim(\urldecode($model->{$attribute}), '/');
if (\strpos($path, 'storage') !== 0) {
return;
}
$path = ToolboxHelper::mbReplace('storage/', '', $path);
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
}
/**
* Destroy the file associate with the attribute old value (before updated to DB).
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @param boolean $onlyIfUpdated
* @return void
*/
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
{
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
if ($onlyIfUpdated and $model->{$attribute} === ToolboxHelper::getOriginal($model, $attribute)) {
return;
}
$originalPath = \ltrim(\urldecode(ToolboxHelper::getOriginal($model, $attribute)), '/');
if (\strpos($originalPath, 'storage') !== 0) {
return;
}
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
if (Storage::disk('public')->exists($originalPath)) {
Storage::disk('public')->delete($originalPath);
}
}
/**
* Store a file using optimized storage strategy
*
* You can use filename param to overload file name.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
* @param boolean $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param boolean $private To store the file in a private space.
* @return string
* @throws \RuntimeException If file copy fails.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public static function storeFile(
Model $model,
$file,
bool $slugify = false,
string $filename = null,
bool $private = false
): string {
// phpcs:enable
$tableName = $model->getTable();
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
/** @var \Illuminate\Http\UploadedFile $file */
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
$newPath = str_replace(storage_path('app'), '', $newPath);
if (!$private) {
// * Store public file.
$file->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
$file->storeAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return str_replace('/private/', '', $newPath);
}//end if
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
if (!File::copy($file->getRealPath(), $newPath)) {
throw new \RuntimeException(sprintf(
'Failed to copy file %s to %s',
$file->getRealPath(),
$newPath
));
}
return $private ?
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
return strval($file);
}
/**
* Store all files from string attribute html
* If oldHtmlString is passed, a diff is done with new and old files
* to keep only files that are present in the new html string.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param string $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
* files using storeModelFilesFromHtml.
* @return string Html with files pathes replaced using storage strategy.
*/
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
$fileInfo = new \SplFileInfo($disk->path(
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
));
$htmlString = ToolboxHelper::mbReplace(
$path,
'/' . self::storeFile($model, $fileInfo, true),
$htmlString
);
});
if ($oldHtmlString) {
self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString);
}
return $htmlString;
}
/**
* Remove all old files from string attribute html that are stored in public that
* are nove present anymore on new html string
*
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
{
self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString));
}
/**
* Remove all old files that are stored in public that
* are not present anymore on new array files
*
* @param string[] $oldFiles The old array files.
* @param string[] $newFiles The new array files.
* @return void
*/
public static function removeOldFiles(array $oldFiles, array $newFiles): void
{
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
$filesToRemove = $oldFiles->diff($filesToKeep);
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
*/
public static function removeModelFilesOfHtml(string $htmlString): void
{
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
$filesToRemove->each(function (string $publicStorageFilePath) {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Find all occurence of files in html
*
* @param string $html
* @return array
*/
public static function findFilesInHtml(string $html): array
{
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
return collect($m)->pluck('href')->filter()
->concat(collect($m)->pluck('url')->filter())
->concat(collect($m)->pluck('src')->filter())
->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'public' (using helper storage strategy)
*
* @param array $urls
* @return array
*/
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('public')
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'uploads'
*
* @param array $urls
* @return array
*/
private static function onlyUploadedFilesWithFileManager(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$host = \parse_url($url, \PHP_URL_HOST);
$path = \parse_url($url, \PHP_URL_PATH);
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
\strpos(\ltrim($path, '/'), 'uploads') === 0;
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('uploads')
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Prepare storage path and return
* the file full path
*
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param boolean $private May the file be stored in a private dir.
* @return string
*/
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
{
// * Security filter
$filename = \htmlspecialchars($filename);
// * Security filter + beautifier
$filename = self::sanitizeFileName($filename);
// * Slugify filename
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
(\strlen($ext) ? ".{$ext}" : '');
$folderPath = self::getStoragePath(
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
$tableName,
$filename,
$private
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
}
/**
* Create sub folders to store files using pattern
* storage/modelfiles/posts/10/04/19/5f
* year/month/day/md5 first 2 letters
*
* @param string $storage_dir
* @param string $table
* @param string $filename
* @return string
*/
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
{
$filenameHash = \md5($filename);
$pathFolder = \storage_path(sprintf(
'%s/%s/%s/%s/%s/%s',
$storage_dir,
$table,
gmdate('y'),
gmdate('m'),
gmdate('d'),
substr($filenameHash, 0, 2)
));
if (!File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
return $pathFolder;
}
/**
* Get unique filename if the path is already occupied
*
* @param string $folderPath
* @param string $filename
* @return string
* @throws \RuntimeException If a unique filename cannot be obtained.
*/
private static function getFileUniqueName(string $folderPath, string $filename): string
{
$attempts = 0;
$maxAttempts = 20;
while (File::exists("{$folderPath}/{$filename}")) {
if ($attempts++ >= $maxAttempts) {
throw new \RuntimeException("Failed to have a unique filename for {$folderPath}/{$filename}");
}
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
\uniqid('-') . '.' .
pathinfo($filename, \PATHINFO_EXTENSION);
}
return $filename;
}
/**
* Slugify the filename
*
* @param string $filename
* @return string
*/
public static function slugifyFileName(string $filename): string
{
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
}
/**
* Sanitize File name for uploaded files
*
* @param string $filename The original uploaded client file name.
* @param boolean $beautify Beautify the filename.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $filename, bool $beautify = true): string
{
// * Sanitize filename
// phpcs:disable Generic.Files.LineLength.TooLong
$filename = preg_replace(
'~
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
~x',
'-',
$filename
);
// phpcs:enable
// * Avoids '.' '..' or '.hiddenFiles' .
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = static::beautifyFilename($filename);
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
/** @var string */
$filename = pathinfo($filename, \PATHINFO_FILENAME);
$encoding = mb_detect_encoding($filename);
$encoding = $encoding ? $encoding : null;
return \mb_strcut(
$filename,
0,
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
$encoding
) . (!empty($extension) ? ".$extension" : '');
}
/**
* Beautify a filename
*
* @param string $filename
* @return string
*/
private static function beautifyFilename(string $filename): string
{
// Reduce consecutive characters.
$filename = preg_replace([
// * "file name.zip" becomes 'file-name.zip'
'/ +/',
// * "file___name.zip" becomes 'file-name.zip'
'/_+/',
// * "file---name.zip" becomes 'file-name.zip'
'/-+/'
], '-', $filename);
$filename = preg_replace([
// * "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// * "file...name..zip" becomes "file.name.zip"
'/\.{2,}/'
], '.', $filename);
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// * ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
}
}

View file

@ -0,0 +1,324 @@
<?php
namespace App\Lib\Helpers;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ToolboxHelper
{
/**
* Strip scheme (https) on oembed urls
* Only embed allowed are for MP4 self hosted videos right now
*
* https is force by ckeditor MediaElements which natively supports
* only cloud hosted medias (youtube, spotify etc..)
*
* @param string $input
* @return string
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
}
/**
* Assert That no Back Office url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalBoUrls',
['attribute' => $field]
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Assert That only external url is present on txt
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalUrls(string $field, string $text): string
{
self::assertNoInternalBoUrls($field, $text);
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
$url = $match;
try {
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
$routes->match($request)
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalUrls',
['attribute' => $field, 'appname' => config('app.name')]
)
]);
}
return $match;
} catch (NotFoundHttpException $e) {
return $match;
}
},
$text
);
return $text;
}
/**
* Mutltibyte string replace
*
* @param array|string $search
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string
*/
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
int &$count = 0
): array|string {
if (!is_array($search) && is_array($replace)) {
return false;
}
if (is_array($subject)) {
// Call mb_replace for each single string in $subject .
foreach ($subject as &$string) {
$string = &self::mbReplace($search, $replace, $string, $count);
}
} elseif (is_array($search)) {
if (!is_array($replace)) {
foreach ($search as &$string) {
$subject = self::mbReplace($string, $replace, $subject, $count);
}
} else {
$n = max(count($search), count($replace));
while ($n--) {
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
next($search);
next($replace);
}
}
} else {
$parts = mb_split(preg_quote($search), $subject);
$count = count($parts) - 1;
$subject = implode($replace, $parts);
} //end if
return $subject;
}
/**
* Asserts that the field is unique
*
* @param string $table
* @param string $field
* @param string|integer|float|boolean|null|array $value
* @param integer|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
*/
public static function assertFieldIsUnique(
string $table,
string $field,
string|int|float|bool|null|array $value,
?int $id = null,
?callable $query = null
) {
if (!$query) {
Validator::make([$field => $value], [
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
self::mbReplace('.', '\.', "{$field}.unique")
=> trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value])
])->validate();
return;
}
Validator::make([$field => $value], [
$field => Rule::unique($table)->where($query)->ignore($id),
], [
$field . '.unique' => trans(
'crud.libs.toolbox.assertFieldIsUnique',
['attribute' => $field, 'value' => $value]
)
])->validate();
}
/**
* Get original polyfill with autotranslate behavior
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return mixed
*/
public static function getOriginal(Model $model, string $attribute): mixed
{
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
return $model->getTranslation(
$attribute,
$model->getLocale(),
$model->useFallbackLocale()
);
}
return $model->getOriginal($attribute);
}
/**
* Dynamic model query
*
* @param string $defaultLocale
* @param \Illuminate\Contracts\Database\Eloquent\Builder $q
* @param string $column
* @param string $value
* @return \Illuminate\Contracts\Database\Eloquent\Builder
*/
public static function queryColumnWithLocales(
string $defaultLocale,
Builder $q,
string $column,
string $value
): Builder {
$currentLocale = app()->currentLocale();
$locales = collect(config('app.locales'))
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
if (!$locales->has($defaultLocale)) {
// * Remove default locale.
$locales->pull($defaultLocale);
}
if (!$locales->has($currentLocale)) {
// * Remove current locale.
$locales->pull($currentLocale);
}
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value) {
// * Query default locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$defaultLocale}') = '{$value}'");
if ($defaultLocale !== $currentLocale) {
// * Query current locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$currentLocale}') = '{$value}'");
}
// * Query any other locale
$locales->each(fn ($locale) => $q->orWhereRaw(
"JSON_EXTRACT({$column}, '$.{$locale}') = '{$value}'"
));
});
}
/**
* Generates an array from a dot separated string
*
* @param string $path
* @param mixed $value
* @param string $separator
* @return array
*/
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
{
$arr = $value;
$keys = array_reverse(explode($separator, $path));
foreach ($keys as $key) {
$arr = [$key => $arr];
}
return $arr;
}
/**
* Convert deeply an object to array
*
* @param array|object $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class ContactMessageReceived extends Mailable
{
use Queueable;
use SerializesModels;
/**
* Create a new message instance.
*
* @param string $first_name
* @param string $last_name
* @param string $email
* @param string $message
* @return void
*/
public function __construct(
private string $first_name,
private string $last_name,
private string $email,
private string $message,
) {
}
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): Envelope
{
return new Envelope(
subject: \trans('Kontaktformular :appname', [
'appname' => \config('app.name')
]),
from: new Address(
'info@wenndannmitrespekt.de',
'Kontaktformular wenndannmitrespekt.de'
),
to: new Address(
'kontakt@wenndannmitrespekt.de',
'kontakt@wenndannmitrespekt.de'
)
);
}
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): Content
{
return new Content(
markdown: 'emails.contact-message-received',
with: [
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'message' => $this->message,
]
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

313
app/Models/Concept.php Normal file
View file

@ -0,0 +1,313 @@
<?php
namespace App\Models;
use App\Enums\Concepts\ColorsEnum;
use App\Enums\InputType;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\HasTranslations;
use App\Traits\Models\IsCachedAsCollection;
use App\Traits\Models\MiniCrudModel;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Enum;
/**
* @property string $seo_title
* @property string $seo_description
* @property string $name
* @property string $slug
* @property string $title
* @property string $subtitle
* @property string $description
* @property object $data
* @property string $picture
* @property string $picture_alt
* @property string $picture_title
* @property string $created_at
* @property string $updated_at
*/
class Concept extends Model
{
use HasFactory;
use HasTranslations;
use IsCachedAsCollection;
use MiniCrudModel;
/**
* @var array
*/
protected $fillable = [
'seo_title',
'seo_description',
'name',
'slug',
'title',
'subtitle',
'description',
'data',
'picture',
'picture_alt',
'picture_title',
'order',
'created_at',
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'data' => 'object',
];
/**
* The translatable columns.
*
* @var array
*/
public $translatable = [
'data',
'seo_title',
'seo_description',
'name',
'slug',
'title',
'subtitle',
'description',
'picture',
'picture_alt',
'picture_title',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::assertFieldIsUnique('name', $model->name);
self::manageFields($model);
self::setOrder($model);
});
static::created(function () {
self::removeCached();
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
self::removeCached();
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
});
}
// * - Cache
/**
* Change query for cached elements
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected static function queryCached(Builder $query): Builder
{
return $query->where('id', '<>', 1)->orderBy('order');
}
// * METHODS
/**
* Common methods function.
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
self::setSlug($model);
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
$model->description = clean($model->description);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**
* Set the slug.
*
* @param \Illuminate\Database\Eloquent\Model $model
*
* @return void
*/
private static function setSlug(Model $model)
{
$model->slug = Str::slug($model->name);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
/**
* Return the structure necessary of data.
*
* @param string $title
* @param integer $color
* @param string $text
* @param string $img
* @param string $imgAlt
* @param string $imgTitle
* @return ((string|\App\Enums\InputType)[]|(string|int[]|\App\Enums\InputType)[]|(string|\App\Enums\InputType|int|(string|\Illuminate\Validation\Rules\Enum)[])[])[]
*/
public static function getEmptyData(
string $title = '',
int $color = 0,
string $text = '',
string $img = '',
string $imgAlt = '',
string $imgTitle = ''
) {
$color = $color ?: \collect(ColorsEnum::cases())->random()->value;
return [
// * BLOC 1
"bloc_1_title" => [
"label" => "Bloc 1 - Title",
"inputType" => InputType::string->value,
"value" => $title,
"validate" => "required|min:4|max:100"
],
"bloc_1_color" => [
"label" => "Bloc 1 - Color",
"inputType" => InputType::selectEnum->value,
"value" => $color,
"validate" => ["required", "integer", new Enum(ColorsEnum::class)]
],
"bloc_1_text" => [
"label" => "Bloc 1 - Text",
"inputType" => InputType::text->value,
"value" => $text,
"validate" => "string|min:10"
],
"bloc_1_image" => [
"label" => "Bloc 1 - Image",
"dimensions" => ["width" => 1140, "height" => 795],
"inputType" => InputType::image->value,
"alt" => $imgAlt,
"title" => $imgTitle,
"value" => $img,
],
// * BLOC 2
"bloc_2_title" => [
"label" => "Bloc 2 - Title",
"inputType" => InputType::string->value,
"value" => $title,
"validate" => "required|min:4|max:100"
],
"bloc_2_color" => [
"label" => "Bloc 2 - Color",
"inputType" => InputType::selectEnum->value,
"value" => $color,
"validate" => ["required", "integer", new Enum(ColorsEnum::class)]
],
"bloc_2_text" => [
"label" => "Bloc 2 - Text",
"inputType" => InputType::text->value,
"value" => $text,
"validate" => "string|min:10"
],
"bloc_2_image" => [
"label" => "Bloc 2 - Image",
"dimensions" => ["width" => 1140, "height" => 795],
"inputType" => InputType::image->value,
"alt" => $imgAlt,
"title" => $imgTitle,
"value" => $img,
],
// * BLOC 3
"bloc_3_title" => [
"label" => "Bloc 3 - Title",
"inputType" => InputType::string->value,
"value" => $title,
"validate" => "required|min:4|max:100"
],
"bloc_3_color" => [
"label" => "Bloc 3 - Color",
"inputType" => InputType::selectEnum->value,
"value" => $color,
"validate" => ["required", "integer", new Enum(ColorsEnum::class)]
],
"bloc_3_text" => [
"label" => "Bloc 3 - Text",
"inputType" => InputType::text->value,
"value" => $text,
"validate" => "string|min:10"
],
"bloc_3_image" => [
"label" => "Bloc 3 - Image",
"dimensions" => ["width" => 1140, "height" => 795],
"inputType" => InputType::image->value,
"alt" => $imgAlt,
"title" => $imgTitle,
"value" => $img,
],
// * BLOC 4
"bloc_4_title" => [
"label" => "Bloc 4 - Title",
"inputType" => InputType::string->value,
"value" => $title,
"validate" => "required|min:4|max:100"
],
"bloc_4_color" => [
"label" => "Bloc 4 - Color",
"inputType" => InputType::selectEnum->value,
"value" => $color,
"validate" => ["required", "integer", new Enum(ColorsEnum::class)]
],
"bloc_4_text" => [
"label" => "Bloc 4 - Text",
"inputType" => InputType::text->value,
"value" => $text,
"validate" => "string|min:10"
],
"bloc_4_image" => [
"label" => "Bloc 4 - Image",
"dimensions" => ["width" => 1140, "height" => 795],
"inputType" => InputType::image->value,
"alt" => $imgAlt,
"title" => $imgTitle,
"value" => $img,
],
];
}
}

155
app/Models/Home.php Normal file
View file

@ -0,0 +1,155 @@
<?php
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property integer $part2_product1_id
* @property integer $part2_product2_id
* @property string $seo_title
* @property string $seo_description
* @property string $part1_title
* @property string $part1_subtitle
* @property string $part1_text_1
* @property string $part1_text_2
* @property string $part1_text_3
* @property string $part2_title
* @property string $part2_subtitle
* @property string $part2_text
* @property array $part2_list
* @property string $part3_picture
* @property string $created_at
* @property string $updated_at
*/
class Home extends Model
{
use HasFactory;
use HasTranslations;
/**
* @var array
*/
protected $fillable = [
'part2_product1_id',
'part2_product2_id',
'seo_title',
'seo_description',
'part1_title',
'part1_subtitle',
'part1_text_1',
'part1_text_2',
'part1_text_3',
'part2_title',
'part2_subtitle',
'part2_text',
'part2_list',
'part3_picture',
'part3_picture_alt',
'part3_picture_title',
'created_at',
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'part2_list' => 'array',
];
/**
* The translatable columns.
*
* @var array
*/
public $translatable = [
'seo_title',
'seo_description',
'part1_title',
'part1_subtitle',
'part1_text_1',
'part1_text_2',
'part1_text_3',
'part2_title',
'part2_subtitle',
'part2_text',
'part2_list',
'part3_picture',
'part3_picture_alt',
'part3_picture_title',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::manageFields($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'part3_picture');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'part3_picture');
});
}
// * METHODS
/**
* Undocumented function
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
ToolboxHelper::assertNoInternalBoUrls('part1_text_1', $model->part1_text_1);
ToolboxHelper::assertNoInternalBoUrls('part1_text_2', $model->part1_text_2);
ToolboxHelper::assertNoInternalBoUrls('part1_text_3', $model->part1_text_3);
ToolboxHelper::assertNoInternalBoUrls('part2_text', $model->part2_text);
if (is_array($model->part2_list)) {
foreach ($model->part2_list as $item) {
ToolboxHelper::assertNoInternalBoUrls('part2_list', $item['value']);
}
}
$model->part1_text_1 = clean($model->part1_text_1);
$model->part1_text_2 = clean($model->part1_text_2);
$model->part1_text_3 = clean($model->part1_text_3);
$model->part2_text = clean($model->part2_text);
$model->part2_list = clean($model->part2_list);
$model->part3_picture = FileStorageHelper::storeFile($model, $model->part3_picture, true);
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function product1()
{
return $this->belongsTo(Product::class, 'part2_product1_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function product2()
{
return $this->belongsTo(Product::class, 'part2_product2_id');
}
}

227
app/Models/Product.php Normal file
View file

@ -0,0 +1,227 @@
<?php
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/**
* @property integer $range_id
* @property string $seo_title
* @property string $seo_description
* @property string $label
* @property string $slug
* @property string $description
* @property string $picture
* @property array $certifications
* @property array $nutritionnal_values
* @property array $list
* @property boolean $status
* @property integer $order
* @property string $created_at
* @property string $updated_at
*/
class Product extends Model
{
use HasFactory;
use HasTranslations;
/**
* @var array
*/
protected $fillable = [
'range_id',
'seo_title',
'seo_description',
'label',
'slug',
'description',
'picture',
'picture_alt',
'picture_title',
'certifications',
'nutritionnal_values',
'list',
'status',
'order',
'created_at',
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'certifications' => 'array',
'nutritionnal_values' => 'array',
'list' => 'array',
];
/**
* The translatable columns.
*
* @var array
*/
public $translatable = [
'seo_title',
'seo_description',
'label',
'slug',
'description',
'picture',
'picture_alt',
'picture_title',
'nutritionnal_values',
'list',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::assertFieldIsUnique('label', $model->label);
self::manageFields($model);
self::cleanTranslationJsonFields($model, 'nutritionnal_values', 'list');
self::setOrder($model);
});
static::updating(function (self $model) {
self::manageFields($model);
self::cleanTranslationJsonFields($model, 'nutritionnal_values', 'list');
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
});
}
// * METHODS
/**
* Undocumented function
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
self::setSlug($model);
if (is_array($model->list)) {
foreach ($model->list as $itemList) {
ToolboxHelper::assertNoInternalBoUrls('list', $itemList['value'] ?? '');
}
}
if (is_array($model->nutritionnal_values)) {
foreach ($model->nutritionnal_values as $itemNutritionnalValues) {
ToolboxHelper::assertNoInternalBoUrls('nutritionnal_values', $itemNutritionnalValues['label'] ?? '');
ToolboxHelper::assertNoInternalBoUrls('nutritionnal_values', $itemNutritionnalValues['value'] ?? '');
}
}
$model->nutritionnal_values = clean($model->nutritionnal_values);
$model->list = clean($model->list);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**
* Remove the original array from json field
*
* @param Model $model
* @param string ...$fields The names of the json fields.
* @return void
*/
private static function cleanTranslationJsonFields(Model $model, string ...$fields): void
{
foreach ($fields as $field) {
$translations = $model->getTranslations($field);
foreach ($translations as $key => $trans) {
if (!in_array($key, config('app.locales'))) {
$model->forgetTranslation($field, $key);
}
}
}
}
/**
* Set the slug.
*
* @param \Illuminate\Database\Eloquent\Model $model
*
* @return void
*/
private static function setSlug(Model $model)
{
$model->slug = Str::slug($model->label);
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function recipes()
{
return $this->belongsToMany('App\Models\Recipe', 'recipes_products');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function range()
{
return $this->belongsTo('App\Models\Range');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function homes1()
{
return $this->hasMany('App\Models\Home', 'part2_product1_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function homes2()
{
return $this->hasMany('App\Models\Home', 'part2_product2_id');
}
}

150
app/Models/Range.php Normal file
View file

@ -0,0 +1,150 @@
<?php
namespace App\Models;
use App\Enums\Ranges\PictoEnum;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/**
* @property string $seo_title
* @property string $seo_description
* @property string $label
* @property string $slug
* @property string $picto
* @property integer $order
* @property boolean $status
* @property string $created_at
* @property string $updated_at
*/
class Range extends Model
{
use HasFactory;
use HasTranslations;
/**
* @var array
*/
protected $fillable = [
'seo_title',
'seo_description',
'label',
'slug',
'picto',
'order',
'status',
'created_at',
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'picto' => PictoEnum::class,
];
/**
* The translatable columns.
*
* @var array
*/
public $translatable = [
'seo_title',
'seo_description',
'label',
'slug',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::manageFields($model);
self::assertFieldIsUnique('label', $model->label);
self::setOrder($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
}
// * METHODS
/**
* Set the slug.
*
* @param \Illuminate\Database\Eloquent\Model $model
*
* @return void
*/
private static function setSlug(Model $model)
{
$model->slug = Str::slug($model->label);
}
/**
* Common methods function
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
self::setSlug($model);
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function recipes()
{
return $this->hasMany('App\Models\Recipe');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function products()
{
return $this->hasMany('App\Models\Product');
}
}

227
app/Models/Recipe.php Normal file
View file

@ -0,0 +1,227 @@
<?php
namespace App\Models;
use App\Enums\Recipes\DifficultyEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
/**
* @property integer $range_id
* @property string $seo_title
* @property string $seo_description
* @property string $name
* @property string $slug
* @property string $picture
* @property string $picture_thumbnail
* @property string $ingredients
* @property string $teaser
* @property string $steps
* @property \App\Enums\Recipes\DifficultyEnum $difficulty
* @property string $duration
* @property string $tips
* @property boolean $status
* @property integer $order
* @property string $created_at
* @property string $updated_at
*/
class Recipe extends Model
{
use HasFactory;
use HasTranslations;
/**
* @var array
*/
protected $fillable = [
'range_id',
'seo_title',
'seo_description',
'name',
'slug',
'picture',
'picture_alt',
'picture_title',
'picture_thumbnail',
'picture_thumbnail_alt',
'picture_thumbnail_title',
'ingredients',
'teaser',
'steps',
'difficulty',
'duration',
'tips',
'status',
'order',
'created_at',
'updated_at'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'ingredients' => 'json',
'difficulty' => DifficultyEnum::class
];
/**
* The translatable columns.
*
* @var array
*/
public $translatable = [
'seo_title',
'seo_description',
'name',
'slug',
'picture',
'picture_alt',
'picture_title',
'picture_thumbnail',
'picture_thumbnail_alt',
'picture_thumbnail_title',
'ingredients',
'duration',
'teaser',
'steps',
'tips',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::assertFieldIsUnique('name', $model->name);
self::setSlug($model);
self::manageFields($model);
self::cleanTranslationJsonFields($model, 'ingredients');
self::setOrder($model);
});
static::updating(function (self $model) {
self::manageFields($model);
self::cleanTranslationJsonFields($model, 'ingredients');
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
FileStorageHelper::removeOldFile($model, 'picture_thumbnail');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
FileStorageHelper::removeFile($model, 'picture_thumbnail');
});
}
// * METHODS
/**
* Common methods function.
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
if (is_array($model->ingredients)) {
foreach ($model->ingredients as $itemIngredients) {
if (isset($itemIngredients['header'])) {
ToolboxHelper::assertNoInternalBoUrls('ingredients', $itemIngredients['header']);
}
ToolboxHelper::assertNoInternalBoUrls('ingredients', \strval($itemIngredients['value']));
}
}
ToolboxHelper::assertNoInternalBoUrls('steps', $model->steps);
ToolboxHelper::assertNoInternalBoUrls('tips', $model->tips);
$model->ingredients = clean($model->ingredients);
$model->steps = clean($model->steps);
$model->tips = clean($model->tips);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
$model->picture_thumbnail = FileStorageHelper::storeFile($model, $model->picture_thumbnail, true);
}
/**
* Remove the original array from json field
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string ...$fields The names of the json fields.
* @return void
*/
private static function cleanTranslationJsonFields(Model $model, string ...$fields): void
{
foreach ($fields as $field) {
$translations = $model->getTranslations($field);
foreach ($translations as $key => $trans) {
if (!in_array($key, config('app.locales'))) {
$model->forgetTranslation($field, $key);
}
}
}
}
/**
* Set the slug.
*
* @param \Illuminate\Database\Eloquent\Model $model
*
* @return void
*/
private static function setSlug(Model $model)
{
$model->slug = Str::slug($model->name);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function products()
{
return $this->belongsToMany(Product::class, 'recipes_products');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function range()
{
return $this->belongsTo(Range::class);
}
}

154
app/Models/Slider.php Normal file
View file

@ -0,0 +1,154 @@
<?php
namespace App\Models;
use App\Enums\Sliders\TypeEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property boolean $type
* @property string $title
* @property string $subtitle
* @property string $description
* @property string $link
* @property string $link_title
* @property boolean $link_new_tab
* @property string $picture
* @property boolean $status
* @property integer $order
* @property string $unpublish_at
* @property string $publish_at
* @property string $created_at
* @property string $updated_at
*/
class Slider extends Model
{
use HasFactory;
use HasTranslations;
/**
* @var array
*/
protected $fillable = [
'type',
'title',
'subtitle',
'description',
'link',
'link_title',
'link_new_tab',
'picture',
'picture_alt',
'picture_title',
'status',
'order',
'unpublish_at',
'publish_at',
'created_at',
'updated_at'
];
/**
* The translatable columns.
*
* @var array
*/
public $translatable = [
'title',
'subtitle',
'description',
'link',
'link_title',
'picture',
'picture_alt',
'picture_title'
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'type' => TypeEnum::class,
'status' => 'bool',
'publish_at' => 'datetime',
'unpublish_at' => 'datetime'
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::assertFieldIsUnique('title', $model->title);
self::manageFields($model);
self::setOrder($model);
if (!$model->status) {
$model->publish_at = null;
} else {
$model->unpublish_at = null;
}
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
});
}
// * METHODS
/**
* Common methods function.
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
if ($model->link) {
ToolboxHelper::assertNoInternalBoUrls('link', $model->link);
}
ToolboxHelper::assertNoInternalBoUrls('description', $model->description);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
}

86
app/Models/Store.php Normal file
View file

@ -0,0 +1,86 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property integer $type_id
* @property integer $region_id
* @property string $state
* @property string $street
* @property string $zipcode
* @property string $city
* @property float $latitude
* @property float $longitude
* @property integer $order
* @property string $created_at
* @property string $updated_at
*/
class Store extends Model
{
use HasFactory;
/**
* @var array
*/
protected $fillable = [
'type_id',
'region_id',
'state',
'street',
'zipcode',
'city',
'latitude',
'longitude',
'order',
'created_at',
'updated_at'
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::setOrder($model);
});
}
// * METHODS
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function storeType()
{
return $this->belongsTo(StoreType::class, 'type_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function storeRegion()
{
return $this->belongsTo(StoreRegion::class, 'region_id');
}
}

104
app/Models/StoreGroup.php Normal file
View file

@ -0,0 +1,104 @@
<?php
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $name
* @property string $picture
* @property integer $order
* @property string $created_at
* @property string $updated_at
*/
class StoreGroup extends Model
{
use HasFactory;
/**
* @var array
*/
protected $fillable = [
'name',
'picture',
'order',
'created_at',
'updated_at'
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::assertFieldIsUnique('name', $model->name);
self::manageFields($model);
self::setOrder($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
});
}
// * METHODS
/**
* Undocumented function
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function storeRegions()
{
return $this->hasMany(StoreRegion::class, 'group_id');
}
}

113
app/Models/StoreRegion.php Normal file
View file

@ -0,0 +1,113 @@
<?php
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property integer $group_id
* @property string $name
* @property string $picture
* @property integer $order
* @property string $created_at
* @property string $updated_at
*/
class StoreRegion extends Model
{
use HasFactory;
/**
* @var array
*/
protected $fillable = [
'group_id',
'name',
'picture',
'created_at',
'updated_at'
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::manageFields($model);
self::assertFieldIsUnique('name', $model->name);
self::setOrder($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'picture');
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'picture');
});
}
// * METHODS
/**
* Undocumented function
*
* @param Model $model
* @return void
*/
private static function manageFields(Model $model)
{
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function stores()
{
return $this->hasMany(Store::class, 'region_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function storeGroup()
{
return $this->belongsTo(StoreGroup::class, 'group_id');
}
}

78
app/Models/StoreType.php Normal file
View file

@ -0,0 +1,78 @@
<?php
namespace App\Models;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $name
* @property string $created_at
* @property string $updated_at
*/
class StoreType extends Model
{
use HasFactory;
/**
* @var array
*/
protected $fillable = [
'name',
'created_at',
'updated_at'
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::creating(function (self $model) {
self::assertFieldIsUnique('name', $model->name);
self::setOrder($model);
});
}
// * METHODS
/**
* Asserts using validation that the field is unique.
*
* @param string $nameField
* @param string $valueField
* @param integer|null $id
* @return void
*/
private static function assertFieldIsUnique(string $nameField, string $valueField, ?int $id = null)
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, $nameField, $valueField, $id);
}
/**
* Set order after the last element of the list.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
private static function setOrder(Model $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
// * RELATIONSHIPS
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function stores()
{
return $this->hasMany(Store::class, 'type_id');
}
}

65
app/Models/User.php Normal file
View file

@ -0,0 +1,65 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use HasFactory;
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'firstname',
'lastname',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
// * EVENT CLOSURES.
static::creating(function (self $model) {
if (Hash::needsRehash($model->password)) {
$model->password = Hash::make($model->password);
}
});
static::updating(function (self $model) {
if (Hash::needsRehash($model->password)) {
$model->password = Hash::make($model->password);
}
});
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace App\Providers;
use App\Models\Concept;
use App\Models\Home;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Spatie\Translatable\Facades\Translatable;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
// * HTTPS FIX
URL::forceScheme('https');
// * HA PROXY FIX
App::singleton('clientIp', function () {
if (config('app.env') === 'local') {
return request()->getClientIp();
} else {
return request()->server('HTTP_X_FORWARDED_FOR') ??
request()->getClientIp();
}
});
// * LOCAL FORCE eg: for Carbon
setlocale(LC_ALL, \sprintf(
'%s_%s.UTF-8',
config('app.locale'),
\strtoupper(config('app.locale'))
));
Carbon::setLocale(config('app.locale'));
Translatable::fallback(
fallbackAny: true,
);
$this->sharetoView();
}
/**
* Share Vars to view
*
* @return void
*/
private function sharetoView()
{
if (app()->runningInConsole()) {
return;
}
// * FORCE BOOTSTRAP PAGINATOR (or custom if in front)
// Early boot, request wont be filled as expected.
if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') {
Paginator::useBootstrap();
} else {
Paginator::useBootstrap();
}
// * FORCE BOOTSTRAP PAGINATOR (or custom if in front)
// Early boot, request wont be filled as expected.
if (collect(explode('/', \request()->getPathInfo()))->get(1) === 'bo') {
Paginator::defaultview('bo.modules.pagination');
} else {
Paginator::defaultview('fo.modules.pagination');
}
$appInfos = Cache::remember('composer', 360, function () {
return json_decode(File::get(\app_path('../composer.json')));
});
// Share composer.json infos.
if (
isset($appInfos->name) and isset($appInfos->version) and
isset($appInfos->license) and isset($appInfos->time)
) {
View::share('cpsr_name', $appInfos->name);
View::share('cpsr_version', $appInfos->version);
View::share('cpsr_license', $appInfos->license);
View::share('cpsr_time', now()->parse($appInfos->time)->isoFormat('Y'));
}
View::share('globalHome', Home::query()->first());
View::share('conceptListGlobal', Concept::getCached());
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot(): void
{
}
}

Some files were not shown because too many files have changed in this diff Show more