Compare commits

..

6 commits

Author SHA1 Message Date
Vincent BRIDIER
0af4cd3528 Merge branch 'update-dockerfile' into 'main'
chore: add PHP_8_5_TAG build arg to Dockerfile

See merge request ldc-web/projets/ovoteam!44
2026-10-26 14:07:32 +01:00
Corentin Huet
c8541bf062 chore: add PHP_8_5_TAG build arg to Dockerfile 2026-10-26 14:06:04 +01:00
Noix
696fd96bd2 Merge branch 'fixUrlMain' into 'main'
fix: 🐛 url social network

See merge request ldc-web/projets/ovoteam!35
2026-09-17 14:31:25 +00:00
Abdel LAMRANI
16c6e78ee2 fix: 🐛 url social network 2026-09-15 13:01:14 +02:00
Noix
03b979537f Merge branch '26-new-recipes' into 'main'
feat: ✨ updated recipes pdf

See merge request ldc-web/projets/ovoteam!24
2026-07-09 07:26:58 +00:00
Noah Lancien
b999224f1f feat: ✨ updated recipes pdf 2026-07-09 09:23:36 +02:00
672 changed files with 2012 additions and 114009 deletions

View file

@ -1,24 +0,0 @@
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
[*.scss]
indent_size = 2
[*.{tsx,ts,cjs}]
indent_size = 2

View file

@ -1,75 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=
SAML2_DEFAULT_TENANT_UUID=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=
MEILISEARCH_KEY=

View file

@ -1,4 +0,0 @@
node_modules
public
vendor
resources/ts/components/ui/

View file

@ -1,80 +0,0 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
project: "./tsconfig.json",
},
settings: {
react: {
version: "detect",
},
"import/resolver": {
typescript: {
project: "./tsconfig.json",
},
alias: {
map: [
["@", "./resources/ts"],
["&", "./resources/assets"],
],
extensions: [".ts", ".tsx", ".js", ".jsx"],
},
},
},
env: {
browser: true,
es2021: true,
node: true,
},
ignorePatterns: ["resources/ts/components/ui/**"],
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
plugins: ["react", "react-hooks", "@typescript-eslint", "import", "prettier"],
rules: {
"no-undef": "off",
"react/display-name": "off",
"react/jsx-key": "off",
"react/jsx-filename-extension": ["error", { extensions: [".tsx", ".jsx"] }],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/require-default-props": "off",
"import/extensions": [
"error",
"ignorePackages",
{
ts: "never",
tsx: "never",
js: "never",
jsx: "never",
},
],
"import/order": "off",
"prettier/prettier": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"no-console": [
"warn",
{
allow: ["warn", "error"],
},
],
},
};

11
.gitattributes vendored
View file

@ -1,11 +0,0 @@
* 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

28
.gitignore vendored
View file

@ -1,28 +0,0 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.fleet
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
Homestead.json
Homestead.yaml
Thumbs.db
.npmrc
public/favicon-back
/docker-compose.yml

View file

@ -1,221 +1,35 @@
stages:
- install
- lint
- test
- security
- build
- deploy
.docker-build:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.23.2-debug
entrypoint: [ "" ]
script:
- /kaniko/executor --context "${CI_PROJECT_DIR}" --build-arg "GITLAB_USER=${CI_DEPLOY_USER}" --build-arg "GITLAB_PASSWORD=${CI_DEPLOY_PASSWORD}" --dockerfile "${CI_PROJECT_DIR}/Dockerfile" --destination "${IMAGE_TAG}" --cache=true --cache-ttl 24h
allow_failure: false
docker-build:staging:
extends: .docker-build
variables:
IMAGE_TAG: "${CI_REGISTRY_IMAGE}:s-${CI_COMMIT_SHA}"
rules:
- if: $CI_COMMIT_BRANCH == "trunk"
when: manual
allow_failure: true
docker-build:latest:
extends: .docker-build
variables:
IMAGE_TAG: "${CI_REGISTRY_IMAGE}:p-${CI_COMMIT_SHA}"
rules:
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "master"
when: manual
allow_failure: true
include:
- project: "ldc-web/shared-ci"
ref: "main"
file: "security/composer.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "build/docker-multistage.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "test/test.yml"
- project: "ldc-web/shared-ci"
ref: "main"
file: "deploy/deploy.yml"
- project: 'ldc-web/shared-ci'
ref: 'main'
file: 'deploy/deploy.yml'
# * LINT --------------------------------------------------------------------
.php-lint-base: &php-lint-base
image: $CI_REGISTRY/ldc-web/projets/ldc-web-images-docker/uwil-ci-8.5:latest
variables:
COMPOSER_CACHE_DIR: ".composer-cache"
CACHE_STORE: "array"
DB_CONNECTION: "sqlite"
DB_DATABASE: ":memory:"
cache:
key:
files:
- composer.lock
prefix: "composer"
policy: pull
paths:
- .composer-cache/
- vendor/
before_script:
- composer config gitlab-token.lab.s-web.fr $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
- composer install --no-scripts --no-interaction --no-ansi --no-progress --prefer-dist
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "develop"
parallel-lint:
<<: *php-lint-base
stage: lint
script:
- vendor/bin/parallel-lint --exclude vendor --exclude storage --exclude bootstrap --exclude node_modules .
allow_failure: false
pint:
<<: *php-lint-base
stage: lint
script:
- vendor/bin/pint --test
allow_failure: false
phpcs:
<<: *php-lint-base
stage: lint
script:
- vendor/bin/phpcs --standard=phpcs.xml --extensions=php --ignore=vendor/*,storage/*,bootstrap/*,node_modules/* --report=junit --report-file=phpcs-report.xml
artifacts:
when: on_failure
expose_as: 'phpcs'
paths:
- phpcs-report.xml
reports:
junit: phpcs-report.xml
allow_failure: false
phpstan:
<<: *php-lint-base
stage: lint
variables:
PHP_MEMORY_LIMIT: "512M"
COMPOSER_CACHE_DIR: ".composer-cache"
CACHE_STORE: "array"
DB_CONNECTION: "sqlite"
DB_DATABASE: ":memory:"
script:
- php -d memory_limit=512M vendor/bin/phpstan --error-format=junit > phpstan-report.xml
artifacts:
when: on_failure
expose_as: 'phpstan'
paths:
- phpstan-report.xml
reports:
junit: phpstan-report.xml
allow_failure: false
eslint:
stage: lint
image: oven/bun:1
variables:
BUN_INSTALL_CACHE_DIR: ".bun-cache"
cache:
key:
files:
- bun.lock
prefix: "bun"
policy: pull
paths:
- .bun-cache/
before_script:
- echo "@ldc-web:registry=https://${CI_SERVER_HOST}/api/v4/packages/npm/" >> .npmrc
- echo "//${CI_SERVER_HOST}/api/v4/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- echo "@uwil:registry=https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/" >> .npmrc
- echo "//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- bun install --no-save
script:
- bun run eslint-ci --quiet --output-file=eslint-junit.xml
artifacts:
when: on_failure
expose_as: 'eslint'
paths:
- eslint-junit.xml
reports:
junit: eslint-junit.xml
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "develop"
stylelint:
stage: lint
image: oven/bun:1
variables:
BUN_INSTALL_CACHE_DIR: ".bun-cache"
cache:
key:
files:
- bun.lock
prefix: "bun"
policy: pull
paths:
- .bun-cache/
before_script:
- echo "@ldc-web:registry=https://${CI_SERVER_HOST}/api/v4/packages/npm/" >> .npmrc
- echo "//${CI_SERVER_HOST}/api/v4/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- echo "@uwil:registry=https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/" >> .npmrc
- echo "//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- bun install --no-save
script:
- bun run stylelint-ci --quiet --output-file=stylelint-junit.xml
artifacts:
when: on_failure
expose_as: 'stylelint'
paths:
- stylelint-junit.xml
reports:
junit: stylelint-junit.xml
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "develop"
# * TESTS -------------------------------------------------------------------
bun-build-test:
stage: test
image: oven/bun:1
variables:
BUN_INSTALL_CACHE_DIR: ".bun-cache"
cache:
key:
files:
- bun.lock
prefix: "bun"
policy: pull
paths:
- .bun-cache/
before_script:
- echo "@ldc-web:registry=https://${CI_SERVER_HOST}/api/v4/packages/npm/" >> .npmrc
- echo "//${CI_SERVER_HOST}/api/v4/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- echo "@uwil:registry=https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/" >> .npmrc
- echo "//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- bun install --no-save
script:
- timeout 300 bun run prod
artifacts:
expose_as: 'front-build-test'
paths:
- public/build/manifest.json
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "trunk" || $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG != null
# * SECURITY ----------------------------------------------------------------
npm-security:
stage: security
image: oven/bun:1
before_script:
- echo "@ldc-web:registry=https://${CI_SERVER_HOST}/api/v4/packages/npm/" >> .npmrc
- echo "//${CI_SERVER_HOST}/api/v4/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
- echo "@uwil:registry=https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/" >> .npmrc
- echo "//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken=${CI_DEPLOY_PASSWORD}" >> .npmrc
script:
- bun audit --json > bun-security-checker-report.json
artifacts:
when: on_failure
expose_as: 'bun-security-checker'
paths:
- bun-security-checker-report.json
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "trunk" || $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG != null
# * BUILD -------------------------------------------------------------------
.docker-build:
dependencies: []
stages:
- build
- deploy

View file

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

View file

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

View file

@ -1,2 +0,0 @@
#!/bin/sh
npx commitlint --config node_modules/@uwil/husky-config/commitlint.config.js --edit $1

View file

@ -1,56 +0,0 @@
{
"extends": [
"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",
"**/packages/*.css",
"**/packages/**/*.css",
"**/packages/*.scss",
"**/packages/**/*.scss",
"**/packages/*.sass",
"**/packages/**/*.sass"
],
"customSyntax": "postcss-scss",
"rules": {
"declaration-property-value-no-unknown": true,
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"scss/double-slash-comment-empty-line-before": [
"always",
{
"ignore": [
"between-comments",
"stylelint-commands",
"inside-block"
]
}
],
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": [
true,
{
"ignoreAtRules": [
"tailwind",
"apply",
"variants",
"responsive",
"screen",
"layer"
]
}
]
}
}

View file

@ -1,5 +0,0 @@
{
"recommendations": [
"emeraldwalk.runonsave"
]
}

109
.vscode/settings.json vendored
View file

@ -1,109 +0,0 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#b0981c",
"activityBar.activeBorder": "#15202b",
"activityBar.background": "#b0981c",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#91edde",
"activityBarBadge.foreground": "#15202b",
"sash.hoverBorder": "#b0981c",
"statusBar.background": "#847215",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#b0981c",
"statusBarItem.remoteBackground": "#1fc0a6",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#847215",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#84721599",
"titleBar.inactiveForeground": "#e7e7e799",
"commandCenter.border": "#e7e7e799",
"activityBarTop.activeBackground": "#b0981c",
"activityBarTop.activeBorder": "#15202b",
"activityBarTop.background": "#b0981c",
"activityBarTop.foreground": "#15202b",
"activityBarTop.inactiveForeground": "#15202b99",
"commandCenter.foreground": "#e7e7e7",
"statusBar.debuggingBackground": "#847215",
"statusBar.debuggingForeground": "#e7e7e7",
"statusBar.inactiveBackground": "#847215"
},
"peacock.color": "#847215",
"peacock.remoteColor": "#847215",
"intelephense.environment.phpVersion": "8.5.0",
"intelephense.environment.includePaths": [
"./.intellipest",
"./vendor/fakerphp/faker",
"./vendor/pestphp",
"./vendor/phpunit/phpunit",
"./vendor/laravel"
],
"phpstan.binCommand": [
"${workspaceFolder}/vendor/bin/phpstan"
],
"laravel-pint.enable": true,
"editor.formatOnSave": true,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "html", "vue"],
"[html]": {
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
}
},
"[blade]": {
"editor.formatOnSave": true,
"editor.formatOnType": false
},
"[javascript]": {
"editor.autoIndent": "full",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.detectIndentation": false,
"editor.formatOnSave": true
},
"[scss]": {
"editor.defaultFormatter": "stylelint.vscode-stylelint",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": "always"
}
},
"[typescript]": {
"editor.autoIndent": "full",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.detectIndentation": false,
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"[php]": {
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"[vue]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
}
},
"conventionalCommits.scopes": [
"migration"
],
"emeraldwalk.runonsave": {
"autoClearConsole": false,
"commands": [
{
"match": "\\.php$",
"isAsync": true,
"cmd": "${workspaceFolder}/vendor/bin/pint ${file}"
}
]
},
"LaravelExtraIntellisense.phpCommand": "docker exec -w /app/srcs -t ovoteam-$(whoami)-apache-php-1 php -r \"{code}\"",
"LaravelExtraIntellisense.basePathForCode": "/app/srcs"
}

View file

@ -1,12 +0,0 @@
# Changelog
All notable changes to `laravel-project` will be documented in this file.
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
- Commits respect [Conventionnal commits](https://www.conventionalcommits.org/en/v1.0.0/) & use [Gitmoji](https://gitmoji.dev/).
## **[v1.0.0] - 01/01/2026**
### Added
- feat: 🎉 working project

View file

@ -1,43 +0,0 @@
## Domaine Métier et Contraintes : Les Produits
Le catalogue de produits repose sur une segmentation précise nécessitant obligatoirement 5 critères d'identification[cite: 1, 2]. Les produits se déclinent sous de très nombreux formats et conditionnements (colis, barquettes, bidons, poches, sachets, seaux, verrines, etc.)[cite: 2].
### Caractéristiques et filtres des produits
| Attribut | Valeurs métier définies | Contraintes de sélection |
| :--- | :--- | :--- |
| **Catégories / Gammes** | Œufs liquides petit conditionnement, Œufs liquides industrie, Œufs liquides pour la grande distribution, Œufs durs, Œufs brouillés, Œufs pochés, Omelettes et Gammes traiteur et desserts[cite: 1, 2]. | 1 seule par produit[cite: 1, 2]. |
| **Technologies** | Frais, Surgelé, Ambiant[cite: 2]. | 1 seule par produit[cite: 2]. |
| **Modes d'élevage** | Plein air, Bio, Sol, Conventionnel et Label Rouge[cite: 1, 2]. | Minimum 1, maximum illimité (X)[cite: 1]. |
| **Métiers (Cibles)** | Hôtellerie, Restauration rapide et snacking, Boulangerie pâtisserie, Restauration collective, Industrie agro-alimentaire, Grande distribution et Restauration à table[cite: 1, 2]. | Minimum 1, maximum illimité (X)[cite: 1]. |
| **Égalim** | Oui, Non[cite: 2]. | Obligatoire ("Oui" ou "Non")[cite: 2]. |
### Contraintes d'affichage (Fiche Produit)
* Les champs dédiés aux valeurs nutritionnelles et le fichier PDF ne doivent pas s'afficher en front-office si aucune information n'est complétée[cite: 1].
* Une section « Consignes de conservation avant ouverture » doit être ajoutée en dessous de la section détails/avantages[cite: 1].
* Une section « Consignes de conservation après ouverture » doit être ajoutée sous la section "avant ouverture", mais ne doit pas s'afficher si elle n'est pas complétée[cite: 1].
* Un slider de recettes doit être implémenté en bas de page avec 9 slides[cite: 1].
* Ce slider doit afficher en premier les recettes associées au produit, puis compléter avec d'autres recettes de manière aléatoire[cite: 1].
* Aucune liaison avec INCO n'est à prévoir (pas de code INCO)[cite: 1].
---
## Domaine Métier et Contraintes : Les Recettes
Les recettes interagissent avec les produits et sont soumises à 4 filtres spécifiques pour faciliter la navigation des utilisateurs[cite: 2].
### Caractéristiques et filtres des recettes
| Attribut | Valeurs métier définies | Contraintes de sélection |
| :--- | :--- | :--- |
| **Gammes de produits** | Identiques aux gammes de produits : Œufs liquides (petit conditionnement, industrie, grande distribution), Œufs durs, brouillés, pochés, Omelettes, traiteur et desserts[cite: 1, 2]. | 1 seule gamme par recette[cite: 1]. |
| **Moments de consommation** | Petit déjeuner / brunch, Déjeuner / dîner, Snacking, Dessert / pâtisserie[cite: 1, 2]. | 1 seul moment par recette[cite: 1]. |
| **Inspirations** | Cuisine du monde, cuisine française, cuisine asiatique, cuisine protéinée, Snacking, Sucré, Brunch[cite: 1, 2]. | 1 seule inspiration par recette[cite: 1]. |
| **Saisons** | Printemps/été, Automne/hiver, Toute l'année[cite: 1, 2]. | 1 seule saison par recette[cite: 1]. |
### Contraintes d'affichage (Fiche Recette)
* Le champ « astuces et conseils » ne doit pas s'afficher en front-office si aucune information n'est complétée[cite: 1].
* Un slider des produits doit être implémenté en bas de page avec 6 slides[cite: 1].
* Ce slider doit afficher en premier les produits associés à la recette, puis afficher d'autres produits en aléatoire pour compléter[cite: 1].

View file

@ -1,54 +1,5 @@
ARG PHP_8_5_TAG=latest
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-ci-8.5:latest AS vendor
WORKDIR /app
ARG GITLAB_USER
ARG GITLAB_PASSWORD
COPY database/ database/
COPY composer.json composer.json
COPY composer.lock composer.lock
RUN composer config gitlab-token.lab.s-web.fr "${GITLAB_USER}" "${GITLAB_PASSWORD}"
RUN composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--no-dev \
--prefer-dist
FROM node:24-alpine AS frontend
ARG GITLAB_PASSWORD
RUN mkdir -p /app/public/build
COPY package*.json postcss.config.js tailwind.config.js tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
COPY resources/ /app/resources/
COPY --from=vendor /app/vendor/ /app/vendor/
WORKDIR /app
RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm config set @uwil:registry https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm install --no-audit --no-fund --loglevel=error && npm run prod
ARG PHP_8_5_TAG
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
ARG USER=www-data
COPY --chown=${USER}:${USER} . /app/srcs
COPY --chown=${USER}:${USER} ./resources/favicons/front/ /app/srcs/public/
COPY --chown=${USER}:${USER} ./resources/favicons/back/ /app/srcs/public/back/
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
USER root
RUN chmod -R 755 /app/srcs/storage
USER ${USER}
USER ${USER}

122
README.md
View file

@ -1,33 +1,111 @@
# Table of contents
# OVO TEAM
- [Table of contents](#table-of-contents)
- [Frameworks, Platforms and Libraries](#frameworks-platforms-and-libraries)
- [Documentation](#documentation)
- [Changelog](#changelog)
- [Copyright and License](#copyright-and-license)
## Instalation
# Frameworks, Platforms and Libraries
Copier `.env` vers `.env.example` et remplir les informations.
<!-- https://github.com/Ileriayo/markdown-badges -->
## Déployer
[![SASS](https://img.shields.io/badge/SASS-hotpink.svg?style=for-the-badge&logo=SASS&logoColor=white)](https://sass-lang.com/)
[![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white)](https://tailwindcss.com/)
[![Vite](https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white)](https://vite.dev/)
[![PHP](https://img.shields.io/badge/php-%23777BB4.svg?style=for-the-badge&logo=php&logoColor=white)](https://www.php.net/)
[![Laravel](https://img.shields.io/badge/laravel-%23FF2D20.svg?style=for-the-badge&logo=laravel&logoColor=white)](https://laravel.com/docs/master)
- Apres git pull sur l'envirronnement désiré
- Envoyer les éléments uploads si nécéssaire (Ne devrais pas étre le cas)
- Executer la partie `Compiler le front`
# Documentation
## Faire une mise à jour
```php
composer i/npm i
```
Commenter dans `public/.htaccess` la partie `# Hide update.php`, puis accéder à ce fichier via la navigateur
# Changelog
## Fichier uploads
Latest version v1.0.0.
Au sein de `public/sites/default/files`
See the [CHANGELOG.md](CHANGELOG.md) file for details.
Les dossiers :
# Copyright and License
- `inline-images` Pour les contenus insérés dans les WYSIWYG
- les dossiers `20*` comme `2023-05` par exemple pour les media uploadés via la création de pages
- `styles` qui contienent des images uploadés de dimension spécifiques
Copyright (c) 2025 and beyond UWIL.
## Compiler le front
`node_modules/gulp/bin/gulp.js -f public/themes/custom/custtheme/gulpfile.js`
Deactiver `Aggreger les fichiers CSS et javascript` au sein de `/fr/admin/config/development/performance`
Cliquer sur `Effacer tous les caches` si vous reactivez l'option
---
## Original README
Drupal is an open source content management platform supporting a variety of
websites ranging from personal weblogs to large community-driven websites. For
more information, visit the Drupal website, [Drupal.org][Drupal.org], and join
the [Drupal community][Drupal community].
## Contributing
Drupal is developed on [Drupal.org][Drupal.org], the home of the international
Drupal community since 2001!
[Drupal.org][Drupal.org] hosts Drupal's [GitLab repository][GitLab repository],
its [issue queue][issue queue], and its [documentation][documentation]. Before
you start working on code, be sure to search the [issue queue][issue queue] and
create an issue if your aren't able to find an existing issue.
Every issue on Drupal.org automatically creates a new community-accessible fork
that you can contribute to. Learn more about the code contribution process on
the [Issue forks & merge requests page][issue forks].
## Usage
For a brief introduction, see [USAGE.txt](/core/USAGE.txt). You can also find
guides, API references, and more by visiting Drupal's [documentation
page][documentation].
You can quickly extend Drupal's core feature set by installing any of its
[thousands of free and open source modules][modules]. With Drupal and its
module ecosystem, you can often build most or all of what your project needs
before writing a single line of code.
## Changelog
Drupal keeps detailed [change records][changelog]. You can search Drupal's
changes for a record of every notable breaking change and new feature since
2011.
## Security
For a list of security announcements, see the [Security advisories
page][Security advisories] (available as [an RSS feed][security RSS]). This
page also describes how to subscribe to these announcements via email.
For information about the Drupal security process, or to find out how to report
a potential security issue to the Drupal security team, see the [Security team
page][security team].
## Need a helping hand?
Visit the [Support page][support] or browse [over a thousand Drupal
providers][service providers] offering design, strategy, development, and
hosting services.
## Legal matters
Know your rights when using Drupal by reading Drupal core's
[license](/core/LICENSE.txt).
Learn about the [Drupal trademark and logo policy here][trademark].
[Drupal.org]: https://www.drupal.org
[Drupal community]: https://www.drupal.org/community
[GitLab repository]: https://git.drupalcode.org/project/drupal
[issue queue]: https://www.drupal.org/project/issues/drupal
[issue forks]: https://www.drupal.org/drupalorg/docs/gitlab-integration/issue-forks-merge-requests
[documentation]: https://www.drupal.org/documentation
[changelog]: https://www.drupal.org/list-changes/drupal
[modules]: https://www.drupal.org/project/project_module
[security advisories]: https://www.drupal.org/security
[security RSS]: https://www.drupal.org/security/rss.xml
[security team]: https://www.drupal.org/drupal-security-team
[service providers]: https://www.drupal.org/drupal-services
[support]: https://www.drupal.org/support
[trademark]: https://www.drupal.com/trademark

File diff suppressed because it is too large Load diff

View file

@ -1,122 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Casts;
use Domain\Models\ValueObjects\Products\NutritionalValue;
use Domain\Models\ValueObjects\Products\NutritionalValues;
use Domain\Models\ValueObjects\Products\NutritionnalUnit;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use InvalidArgumentException;
/** @implements CastsAttributes<NutritionalValues|null, NutritionalValues|array<string, mixed>|null> */
class NutritionalValuesCast implements CastsAttributes
{
/** @param array<string, mixed> $attributes */
public function get(Model $model, string $key, mixed $value, array $attributes): ?NutritionalValues
{
if ($value === null) {
return null;
}
$data = is_string($value) ? json_decode($value, true) : $value;
if (! is_array($data)) {
return null;
}
return $this->fromArray($data);
}
/**
* @param array<string, mixed> $attributes
* @return array<string, mixed>
*/
public function set(Model $model, string $key, mixed $value, array $attributes): array
{
if ($value === null) {
return [$key => null];
}
if (is_string($value)) {
$value = json_decode($value, true);
}
if (is_array($value)) {
$value = $this->fromArray($value);
}
if (! $value instanceof NutritionalValues) {
throw new InvalidArgumentException('The given value is not a NutritionalValues instance.');
}
return [$key => json_encode($value->toArray())];
}
/**
* @param array<array-key, mixed> $data
*/
private function fromArray(array $data): ?NutritionalValues
{
$refWeightData = Arr::array($data, 'refWeightG');
$rawVal = Arr::get($refWeightData, 'value');
$rawUnit = Arr::get($refWeightData, 'unit');
if ((! is_string($rawVal) && ! is_numeric($rawVal)) || ! is_string($rawUnit)) {
return null;
}
$refWeightUnit = NutritionnalUnit::tryFrom($rawUnit);
if ($refWeightUnit === null) {
return null;
}
/** @param array<string|int, mixed> $data */
$getProperty = function (array $data, string $key): ?NutritionalValue {
return when(
is_array(Arr::get($data, $key)),
fn () => $this->toNutritionalValues(Arr::array($data, $key)),
null
);
};
return new NutritionalValues(
refWeightG: new NutritionalValue((string) $rawVal, $refWeightUnit),
kj: $getProperty($data, 'kj'),
kcal: $getProperty($data, 'kcal'),
fatG: $getProperty($data, 'fatG'),
satFatG: $getProperty($data, 'satFatG'),
carbG: $getProperty($data, 'carbG'),
sugarG: $getProperty($data, 'sugarG'),
fiberG: $getProperty($data, 'fiberG'),
proteinG: $getProperty($data, 'proteinG'),
saltG: $getProperty($data, 'saltG'),
);
}
/**
* @param array<string|int, mixed> $data
*/
private function toNutritionalValues(array $data): ?NutritionalValue
{
$rawItemVal = Arr::get($data, 'value');
$rawItemUnit = Arr::get($data, 'unit');
if ((! is_string($rawItemVal) && ! is_numeric($rawItemVal)) || ! is_string($rawItemUnit)) {
return null;
}
$value = mb_trim((string) $rawItemVal);
$unit = NutritionnalUnit::tryFrom($rawItemUnit);
if ($value === '' || $unit === null) {
return null;
}
return new NutritionalValue($value, $unit);
}
}

View file

@ -1,53 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Contracts\Directories;
use App\Contracts\Directory;
use App\Dtos\CategoryProducts;
use Domain\Models\Entities\Product;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Domain\Models\ValueObjects\Pagination;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Illuminate\Pagination\LengthAwarePaginator;
/** @extends Directory<int, \App\Models\Product> */
interface ProductDirectory extends Directory
{
public function find(ProductId $id): ?Product;
public function findBySlug(string $slug): ?Product;
/**
* @return \App\Dtos\ProductSummary[]
*/
public function getAllProductsSummary(): array;
/**
* @return LengthAwarePaginator<array-key,Product>
*/
public function getPaginatedProducts(
Pagination $pagination,
string $search = '',
): LengthAwarePaginator;
/**
* @param \Domain\Models\ValueObjects\Products\BreedingMethod[] $breedingMethods
* @param \Domain\Models\ValueObjects\Products\Technology[] $technologies
* @param \Domain\Models\ValueObjects\Products\Business[] $businesses
*/
public function getCategoryProducts(
ProductCategory $category,
array $breedingMethods = [],
array $technologies = [],
array $businesses = [],
?bool $isEgalim = null,
string $search = '',
int $limitPerCategory = 6
): ?CategoryProducts;
/** @return \Domain\Models\Entities\Product[] */
public function getRelatedProducts(RecipeId $recipeId, int $limit = 4): array;
}

View file

@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Contracts\Directories;
use App\Contracts\Directory;
use Domain\Models\Entities\Recipe;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Domain\Models\ValueObjects\Pagination;
use Illuminate\Pagination\LengthAwarePaginator;
/** @extends Directory<int, \App\Models\Recipe> */
interface RecipeDirectory extends Directory
{
public function find(RecipeId $id): ?Recipe;
public function findBySlug(string $slug): ?Recipe;
/**
* @param \Domain\Models\ValueObjects\Products\ProductCategory[] $productRanges
* @param \Domain\Models\ValueObjects\Recipes\ConsumptionTime[] $consumptionTimes
* @param \Domain\Models\ValueObjects\Recipes\Inspiration[] $inspirations
* @param \Domain\Models\ValueObjects\Recipes\Season[] $seasons
* @return LengthAwarePaginator<array-key,Recipe>
*/
public function getPaginatedRecipes(
Pagination $pagination,
array $productRanges = [],
array $consumptionTimes = [],
array $inspirations = [],
array $seasons = [],
string $search = ''
): LengthAwarePaginator;
/** @return \Domain\Models\Entities\Recipe[] */
public function getRelatedRecipes(ProductId $productId, int $limit = 4): array;
}

View file

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Contracts;
/**
* @template TEntity
* @template TId
*
* @method TEntity|null find(TId $id)
*/
interface Directory
{
}

View file

@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Contracts;
/**
* @template TModel of \Illuminate\Database\Eloquent\Model
* @template TId of \Ramsey\Uuid\UuidInterface|int
*
* @method TModel|null find(TId $id)
* @method void persist(TModel $model)
* @method void delete(TModel $model)
*/
interface Repository
{
}

View file

@ -1,224 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Directories;
use App\Contracts\Directories\ProductDirectory;
use App\Dtos\CategoryProducts;
use App\Dtos\ProductSummary;
use App\Models\Product;
use App\Models\Recipe;
use Domain\Models\Entities\Product as DomainProduct;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Domain\Models\ValueObjects\Pagination;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
class EloquentProductDirectory implements ProductDirectory
{
public function find(ProductId $id): ?DomainProduct
{
return Product::query()
->with(['recipes'])
->where('id', (string) $id->value)
->limit(1)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->first();
}
public function findBySlug(string $slug): ?DomainProduct
{
return Product::query()
->with(['recipes'])
->where('slug', $slug)
->limit(1)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->first();
}
public function getAllProductsSummary(): array
{
return Product::query()
->select(['id', 'name', 'picture'])
->orderBy('name', 'asc')
->get()
->map(fn (Product $product) => $this->mapToSummary($product))
->all();
}
public function getPaginatedProducts(
Pagination $pagination,
string $search = '',
): LengthAwarePaginator {
$dbQueryCallback = function (Builder $query) {
/** @var \Illuminate\Database\Eloquent\Builder<Product> $query */
$query->with(['recipes']);
};
if (! empty($search)) {
$scout = Product::search($search)->query($dbQueryCallback);
/** @var \Illuminate\Pagination\LengthAwarePaginator<array-key,Product> $paginated */
$paginated = $scout->paginate(
$pagination->pageSize(),
'page',
$pagination->pageNumber()
);
return $paginated->through(fn ($product) => $this->mapToEntity($product));
}
/** @var \Illuminate\Database\Eloquent\Builder<Product> $query */
$query = Product::query();
$dbQueryCallback($query);
return $query->paginate(
perPage: $pagination->pageSize(),
page: $pagination->pageNumber()
)->through(fn (Product $product) => $this->mapToEntity($product));
}
/**
* @param \Domain\Models\ValueObjects\Products\BreedingMethod[] $breedingMethods
* @param \Domain\Models\ValueObjects\Products\Technology[] $technologies
* @param \Domain\Models\ValueObjects\Products\Business[] $businesses
*/
public function getCategoryProducts(
ProductCategory $category,
array $breedingMethods = [],
array $technologies = [],
array $businesses = [],
?bool $isEgalim = null,
string $search = '',
int $limitPerCategory = 6
): ?CategoryProducts {
$dbQueryCallback = function (Builder $query) use ($category, $breedingMethods, $technologies, $businesses, $isEgalim) {
/** @var \Illuminate\Database\Eloquent\Builder<Product> $query */
$query->where('category', $category->value)
->with(['recipes']);
if (! empty($breedingMethods)) {
$query->where(function ($q) use ($breedingMethods) {
foreach ($breedingMethods as $method) {
$q->orWhereJsonContains('breeding_methods', $method->value);
}
});
}
if (! empty($technologies)) {
$techValues = array_map(fn ($t) => $t->value, $technologies);
$query->whereIn('technology', $techValues);
}
if (! empty($businesses)) {
$query->where(function ($q) use ($businesses) {
foreach ($businesses as $business) {
$q->orWhereJsonContains('businesses', $business->value);
}
});
}
if ($isEgalim === true) {
$query->where('is_egalim', true);
}
};
$query = Product::query();
if (! empty($search)) {
$scout = Product::search($search);
// Get all matching IDs from Meilisearch
$matchingIds = $scout->keys();
if ($matchingIds->isEmpty()) {
return null;
}
$query->whereIn('id', $matchingIds);
}
$dbQueryCallback($query);
$count = $query->count();
if ($count === 0) {
return null;
}
$products = (clone $query)->limit($limitPerCategory)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->all();
return new CategoryProducts(
category: $category,
total: $count,
products: $products,
);
}
private function mapToSummary(Product $product): ProductSummary
{
return new ProductSummary(
id: (string) $product->id,
name: $product->name,
picture: $product->picture,
);
}
private function mapToEntity(Product $product): DomainProduct
{
return new DomainProduct(
id: new ProductId(Uuid::fromString($product->id)),
name: $product->name,
slug: $product->slug,
category: $product->category,
technology: $product->technology,
isEgalim: $product->is_egalim,
breedingMethods: $product->breeding_methods->all(),
businesses: $product->businesses->all(),
detailsAndAdvantages: $product->details_and_advantages,
formats: $product->formats->all(),
handlingAdvices: $product->handling_advices,
ingredients: $product->ingredients,
conservationBeforeOpening: $product->conservation_before_opening,
description: $product->description,
seoTitle: $product->seo_title,
seoDescription: $product->seo_description,
conservationAfterOpening: $product->conservation_after_opening,
nutritionalValues: $product->nutritional_values,
picture: $product->picture,
pictureAlt: $product->picture_alt,
pictureTitle: $product->picture_title,
pdfFilePath: $product->pdf,
associatedRecipes: $product->recipes->map(
fn (Recipe $recipe): UuidInterface => $recipe->getId()->value
)->all()
);
}
public function getRelatedProducts(RecipeId $recipeId, int $limit = 4): array
{
$recipe = Recipe::query()
->where('id', $recipeId->value)
->first();
if (! $recipe) {
return [];
}
return $recipe->products()
->with(['recipes'])
->limit($limit)
->get()
->map(fn (Product $product) => $this->mapToEntity($product))
->all();
}
}

View file

@ -1,141 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Directories;
use App\Contracts\Directories\RecipeDirectory;
use App\Models\Product;
use App\Models\Recipe;
use Domain\Models\Entities\Recipe as DomainRecipe;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Domain\Models\ValueObjects\Pagination;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\LengthAwarePaginator;
use Ramsey\Uuid\Uuid;
class EloquentRecipeDirectory implements RecipeDirectory
{
public function find(RecipeId $id): ?DomainRecipe
{
return Recipe::query()
->with(['products'])
->where('id', (string) $id->value)
->limit(1)
->get()
->map(fn (Recipe $recipe) => $this->mapToEntity($recipe))
->first();
}
public function findBySlug(string $slug): ?DomainRecipe
{
return Recipe::query()
->with(['products'])
->where('slug', $slug)
->limit(1)
->get()
->map(fn (Recipe $recipe) => $this->mapToEntity($recipe))
->first();
}
public function getPaginatedRecipes(
Pagination $pagination,
array $productRanges = [],
array $consumptionTimes = [],
array $inspirations = [],
array $seasons = [],
string $search = ''
): LengthAwarePaginator {
$dbQueryCallback = function (Builder $query) use ($productRanges, $consumptionTimes, $inspirations, $seasons) {
/** @var \Illuminate\Database\Eloquent\Builder<Recipe> $query */
$query->with(['products']);
if (! empty($productRanges)) {
$rangesValues = array_map(fn ($r) => $r->value, $productRanges);
$query->whereIn('product_category', $rangesValues);
}
if (! empty($consumptionTimes)) {
$timesValues = array_map(fn ($t) => $t->value, $consumptionTimes);
$query->whereIn('consomption_time', $timesValues);
}
if (! empty($inspirations)) {
$inspValues = array_map(fn ($i) => $i->value, $inspirations);
$query->whereIn('inspiration', $inspValues);
}
if (! empty($seasons)) {
$seasonValues = array_map(fn ($s) => $s->value, $seasons);
$query->whereIn('season', $seasonValues);
}
};
$query = Recipe::query();
if (! empty($search)) {
$scout = Recipe::search($search);
$matchingIds = $scout->keys();
if ($matchingIds->isEmpty()) {
/** @var \Illuminate\Pagination\LengthAwarePaginator<array-key,DomainRecipe> */
return $query->whereIn('id', [])->paginate(
perPage: $pagination->pageSize(),
page: $pagination->pageNumber()
);
}
$query->whereIn('id', $matchingIds);
}
$dbQueryCallback($query);
return $query->paginate(
perPage: $pagination->pageSize(),
page: $pagination->pageNumber()
)->through(fn (Recipe $recipe) => $this->mapToEntity($recipe));
}
private function mapToEntity(Recipe $recipe): DomainRecipe
{
return new DomainRecipe(
id: new RecipeId(Uuid::fromString($recipe->id)),
name: $recipe->name,
slug: $recipe->slug,
picture: $recipe->picture,
seoTitle: $recipe->seo_title,
seoDescription: $recipe->seo_description,
productRange: $recipe->product_category,
consomptionTime: $recipe->consomption_time,
inspiration: $recipe->inspiration,
season: $recipe->season,
description: $recipe->description,
details: $recipe->details,
preparationTime: '15 min de préparation',
guestsCount: 4,
ingredientsList: $recipe->ingredients_list,
preparationSteps: $recipe->preparation_steps,
tipsAndAdvices: $recipe->tips_and_advices,
associatedProducts: []
);
}
public function getRelatedRecipes(ProductId $productId, int $limit = 4): array
{
$product = Product::query()
->where('id', (string) $productId->value)
->first();
if (! $product) {
return [];
}
return $product->recipes()
->with(['products'])
->limit($limit)
->get()
->map(fn (Recipe $recipe) => $this->mapToEntity($recipe))
->all();
}
}

View file

@ -1,49 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Dtos;
use Domain\Models\Entities\Product;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Illuminate\Support\Arr;
use Override;
final readonly class CategoryProducts implements \JsonSerializable
{
public function __construct(
public ProductCategory $category,
public int $total,
/** @var \Domain\Models\Entities\Product[] */
public array $products,
) {
}
#[Override]
public function jsonSerialize(): mixed
{
return [
'category' => [
'name' => $this->category->name(),
'label' => $this->category->label(),
'value' => $this->category->value(),
],
'total' => $this->total,
'products' => array_map(
[$this, 'productsToArray'],
$this->products
),
];
}
/** @return array<array-key,mixed> */
private function productsToArray(Product $product): array
{
return array_merge(
Arr::wrap(json_decode(json_encode($product) ?: '', true)),
[
'id' => (string) $product->id->value,
]
);
}
}

View file

@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Dtos;
final readonly class ProductSummary
{
public function __construct(
public string $id,
public string $name,
public ?string $picture,
) {
}
}

View file

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Enums\Contact;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ActivitySectorEnum: int
{
use BaseEnum;
case commercial_catering = 1;
case collective_catering = 2;
case bakery_pastry = 3;
case food_industry = 4;
case supermarkets = 5;
case others = 6;
/**
* Optionnal labels definition.
*
* @phpstan-ignore classConstant.unused
*/
private const LABELS = [
self::commercial_catering->name => 'Restauration Commerciale',
self::collective_catering->name => 'Restauration Collective',
self::bakery_pastry->name => 'Boulangerie-Pâtisserie',
self::food_industry->name => 'Industrie Agro-Alimentaire',
self::supermarkets->name => 'Grandes et Moyennes Surfaces',
self::others->name => 'Autres',
];
}

View file

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Enums\Contact;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ObjectEnum: int
{
use BaseEnum;
case product_questions = 1;
case quote_request = 2;
case order_tracking = 3;
case partnership_request = 4;
case product_complaint = 5;
case other = 6;
/**
* Optionnal labels definition.
*
* @phpstan-ignore classConstant.unused
*/
private const LABELS = [
self::product_questions->name => 'Questions sur nos produits',
self::quote_request->name => 'Demande de devis',
self::order_tracking->name => 'Suivi de commande',
self::partnership_request->name => 'Demande de partenariat',
self::product_complaint->name => 'Réclamation produit',
self::other->name => 'Autre',
];
}

View file

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Exceptions;
class AuthServiceException extends \RuntimeException
{
public static function of(?string $message = null, ?\Throwable $cause = null): self
{
return new self($message ?? 'An authentication error occurred.', previous: $cause);
}
}

View file

@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\Helpers;
class FileStorageOperationException extends \RuntimeException
{
public static function copyFailed(string $sourcePath, string $destinationPath): self
{
return new self(sprintf('Failed to copy file %s to %s', $sourcePath, $destinationPath));
}
public static function cannotGenerateUniqueFilename(string $folderPath, string $filename): self
{
return new self("Failed to have a unique filename for {$folderPath}/{$filename}");
}
}

View file

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\Helpers;
class UnsupportedStructureConversionException extends \RuntimeException
{
public static function fromType(mixed $input): self
{
return new self('Cannot handle $input of type ' . \gettype($input));
}
}

View file

@ -1,87 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Back;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Contracts\Session\Session;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Str;
use Uwil\LaravelBackend\Controllers\BackendController as UwilBackendController;
use Uwil\LaravelBackend\Enums\ItemsPerPaginationEnum;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class BackendController extends UwilBackendController
{
use AuthorizesRequests;
use ValidatesRequests;
public function __construct(
protected readonly Request $request,
protected readonly Session $session,
) {
}
/**
* Customize pagination with cache or config (default).
*
* @template T of \Illuminate\Database\Eloquent\Model
*
* @param \Illuminate\Database\Eloquent\Builder<T> $query
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<array-key,T>
*/
protected function paginate(
Builder $query,
?ItemsPerPaginationEnum $pagination = null
): LengthAwarePaginator {
$route = $this->request->route();
throw_if(
$route === null,
\UnexpectedValueException::class,
'Route is required in order to paginate'
);
$cacheKey = $this->getCacheKey($route);
$perPage = $this->getPerPage($cacheKey);
$this->persistPerPage($cacheKey, $perPage);
return $query->paginate(perPage: $perPage)->withQueryString();
}
private function persistPerPage(string $cacheKey, int $perPage): void
{
if ($this->session->get($cacheKey) !== $perPage) {
$this->session->put($cacheKey, $perPage);
}
}
private function getPerPage(string $cacheKey): int
{
$perPage = $this->session->get($cacheKey);
$perPage = $perPage ?? ItemsPerPaginationEnum::twelve->value();
if (! is_scalar($perPage)) {
throw new \UnexpectedValueException('perPage shall be a scalar valuye');
}
return ToolboxHelper::getValidatedEnum(
intval($perPage),
'pagination',
'\Uwil\LaravelBackend\Enums\ItemsPerPaginationEnum',
);
}
private function getCacheKey(Route $route): string
{
$currentRoutePath = Str::slug($route->getName() ?? 'unknown');
return "pagination.{$currentRoutePath}";
}
}

View file

@ -1,240 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\Products\IndexProductRequest;
use App\Http\Requests\Back\Products\StoreProductRequest;
use App\Http\Requests\Back\Products\UpdateProductRequest;
use App\Http\Resources\CommonResource;
use App\Models\Product;
use Domain\Models\ValueObjects\Products\BreedingMethod;
use Domain\Models\ValueObjects\Products\Business;
use Domain\Models\ValueObjects\Products\NutritionnalUnit;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Products\ProductFormat;
use Domain\Models\ValueObjects\Products\Technology;
use Illuminate\Contracts\Session\Session;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Response;
class ProductController extends BackendController
{
public function __construct(
Request $request,
Session $session,
) {
parent::__construct(
$request,
$session
);
$this->modelName = Product::modelName();
$this->modelPage = Product::modelPage();
}
public function index(IndexProductRequest $request): Response
{
$this->authorize('viewAny', Product::class);
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Product> $query */
$query = Product::query();
if ($request->routeIs('back.products.trash')) {
$query = $query->onlyTrashed();
}
$searchValue = $request->getSearchValue();
if ($searchValue) {
$this->searchQuery(
$query,
$searchValue,
null,
'name'
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.name'),
]);
$this->sortQuery($query);
$paginatedModels = $this->paginate($query);
return $this->inertiaRender('back/Pages/Products/Index', [
'paginatedModels' => CommonResource::collection($paginatedModels),
'search' => $searchValue,
'searchFields' => $searchFields,
'enumProductCategory' => ProductCategory::toArray(),
'enumTechnology' => Technology::toArray(),
]);
}
public function show(Product $product): RedirectResponse
{
$this->authorize('viewAny', Product::class);
return redirect()->route('back.products.edit', $product);
}
public function create(Product $product): Response
{
$this->authorize('create', Product::class);
return $this->inertiaRender('back/Pages/Products/Create', [
'data' => CommonResource::make($product),
'enumProductCategory' => ProductCategory::toArray(),
'enumTechnology' => Technology::toArray(),
'enumBreedingMethod' => BreedingMethod::toArray(),
'enumBusiness' => Business::toArray(),
'enumProductFormat' => ProductFormat::toArray(),
'enumNutritionnalUnit' => NutritionnalUnit::toArray(),
]);
}
public function store(StoreProductRequest $request): RedirectResponse
{
$this->authorize('create', Product::class);
return DB::transaction(function () use ($request) {
$product = new Product();
/** @var array<string, mixed> $data */
$data = $request->only($product->getFillable());
$product->fill($data);
if ($product->saveOrFail()) {
return redirect()->route('back.products.edit', $product)
->with('success', trans('uwil-backend::crud.messages.has_been_created', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
});
}
public function edit(Product $product): Response
{
$this->authorize('update', $product);
return $this->inertiaRender('back/Pages/Products/Edit', [
'data' => CommonResource::make($product),
'enumProductCategory' => ProductCategory::toArray(),
'enumTechnology' => Technology::toArray(),
'enumBreedingMethod' => BreedingMethod::toArray(),
'enumBusiness' => Business::toArray(),
'enumProductFormat' => ProductFormat::toArray(),
'enumNutritionnalUnit' => NutritionnalUnit::toArray(),
]);
}
public function update(
UpdateProductRequest $request,
Product $product
): RedirectResponse {
$this->authorize('update', $product);
return DB::transaction(function () use ($request, $product) {
/** @var array<string, mixed> $data */
$data = $request->only($product->getFillable());
$product->fill($data);
if ($product->saveOrFail()) {
return redirect()->route('back.products.edit', $product)
->with('success', trans('uwil-backend::crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
return redirect()->route('back.products.edit', $product)
->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
});
}
public function duplicate(Product $product): Response
{
return $this->create($product->replicate());
}
public function restore(string $id): RedirectResponse
{
$product = Product::withTrashed()->findOrFail($id);
$this->authorize('restore', $product);
$previousQueries = $this->getPreviousQueries();
if ($product->restore()) {
return redirect()->route('back.products.trash', $previousQueries)
->with('success', trans('uwil-backend::crud.messages.has_been_restored', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
public function destroy(Product $product): RedirectResponse
{
$this->authorize('delete', $product);
$previousQueries = $this->getPreviousQueries();
if ($product->delete()) {
return redirect()->route('back.products.index', $previousQueries)
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
public function forceDestroy(string $id): RedirectResponse
{
$product = Product::withTrashed()->findOrFail($id);
$this->authorize('forceDelete', $product);
$previousQueries = $this->getPreviousQueries();
if ($product->forceDelete()) {
return redirect()->route('back.products.trash', $previousQueries)
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.product', 1)),
]));
}
/** @return array<array-key,string|array<mixed>> */
private function getPreviousQueries(): array
{
$previousQueries = [];
$previousQueryString = \parse_url($this->getPreviousUrl(), \PHP_URL_QUERY) ?? '' ?: '';
\parse_str($previousQueryString, $previousQueries);
return $previousQueries;
}
private function getPreviousUrl(): string
{
return url()->previous(route('back.dashboard.index'));
}
}

View file

@ -1,251 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Back;
use App\Contracts\Directories\ProductDirectory;
use App\Http\Requests\Back\Recipes\IndexRecipeRequest;
use App\Http\Requests\Back\Recipes\StoreRecipeRequest;
use App\Http\Requests\Back\Recipes\UpdateRecipeRequest;
use App\Http\Resources\CommonResource;
use App\Models\Recipe;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Recipes\ConsumptionTime;
use Domain\Models\ValueObjects\Recipes\Inspiration;
use Domain\Models\ValueObjects\Recipes\Season;
use Illuminate\Contracts\Session\Session;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Response;
class RecipeController extends BackendController
{
public function __construct(
Request $request,
Session $session,
private readonly ProductDirectory $productDirectory,
) {
parent::__construct(
$request,
$session
);
$this->modelName = Recipe::modelName();
$this->modelPage = Recipe::modelPage();
}
public function index(IndexRecipeRequest $request): Response
{
$this->authorize('viewAny', Recipe::class);
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Recipe> $query */
$query = Recipe::query();
if ($request->routeIs('back.recipes.trash')) {
$query = $query->onlyTrashed();
}
$searchValue = $request->getSearchValue();
if ($searchValue) {
$this->searchQuery(
$query,
$searchValue,
null,
'name'
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.name'),
]);
$this->sortQuery($query);
$paginatedModels = $this->paginate($query);
return $this->inertiaRender('back/Pages/Recipes/Index', [
'paginatedModels' => CommonResource::collection($paginatedModels),
'search' => $searchValue,
'searchFields' => $searchFields,
'enumProductCategory' => ProductCategory::toArray(),
'enumConsumptionTime' => ConsumptionTime::toArray(),
'enumInspiration' => Inspiration::toArray(),
'enumSeason' => Season::toArray(),
]);
}
public function show(Recipe $recipe): RedirectResponse
{
$this->authorize('viewAny', Recipe::class);
return redirect()->route('back.recipes.edit', $recipe);
}
public function create(Recipe $recipe): Response
{
$this->authorize('create', Recipe::class);
return $this->inertiaRender('back/Pages/Recipes/Create', [
'data' => CommonResource::make($recipe),
'enumProductCategory' => ProductCategory::toArray(),
'enumConsumptionTime' => ConsumptionTime::toArray(),
'enumInspiration' => Inspiration::toArray(),
'enumSeason' => Season::toArray(),
'allProducts' => $this->productDirectory->getAllProductsSummary(),
]);
}
public function store(StoreRecipeRequest $request): RedirectResponse
{
$this->authorize('create', Recipe::class);
return DB::transaction(function () use ($request) {
$recipe = new Recipe();
/** @var array<string, mixed> $data */
$data = $request->only($recipe->getFillable());
$recipe->fill($data);
if ($recipe->saveOrFail()) {
$recipe->products()
->sync($request->array('products'));
return redirect()->route('back.recipes.edit', $recipe)
->with('success', trans('uwil-backend::crud.messages.has_been_created', [
'model' => Str::ucfirst(trans_choice('models.classes.recipes', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
});
}
public function edit(Recipe $recipe): Response
{
$this->authorize('update', $recipe);
$recipe->load('products');
return $this->inertiaRender('back/Pages/Recipes/Edit', [
'data' => CommonResource::make($recipe),
'enumProductCategory' => ProductCategory::toArray(),
'enumConsumptionTime' => ConsumptionTime::toArray(),
'enumInspiration' => Inspiration::toArray(),
'enumSeason' => Season::toArray(),
'allProducts' => $this->productDirectory->getAllProductsSummary(),
]);
}
public function update(
UpdateRecipeRequest $request,
Recipe $recipe
): RedirectResponse {
$this->authorize('update', $recipe);
return DB::transaction(function () use ($request, $recipe) {
/** @var array<string, mixed> $data */
$data = $request->only($recipe->getFillable());
$recipe->fill($data);
if ($recipe->saveOrFail()) {
$recipe->products()
->sync($request->array('products'));
return redirect()->route('back.recipes.edit', $recipe)
->with('success', trans('uwil-backend::crud.messages.has_been_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
return redirect()->route('back.recipes.edit', $recipe)
->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
});
}
public function duplicate(Recipe $recipe): Response
{
$this->authorize('duplicate', $recipe);
return $this->create($recipe->replicate());
}
public function restore(string $id): RedirectResponse
{
$recipe = Recipe::withTrashed()->findOrFail($id);
$this->authorize('restore', $recipe);
$previousQueries = $this->getPreviousQueries();
if ($recipe->restore()) {
return redirect()->route('back.recipes.trash', $previousQueries)
->with('success', trans('uwil-backend::crud.messages.has_been_restored', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
public function destroy(Recipe $recipe): RedirectResponse
{
$this->authorize('delete', $recipe);
$previousQueries = $this->getPreviousQueries();
if ($recipe->delete()) {
return redirect()->route('back.recipes.index', $previousQueries)
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
public function forceDestroy(string $id): RedirectResponse
{
$recipe = Recipe::withTrashed()->findOrFail($id);
$this->authorize('forceDelete', $recipe);
$previousQueries = $this->getPreviousQueries();
if ($recipe->forceDelete()) {
return redirect()->route('back.recipes.trash', $previousQueries)
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
return redirect()->back()
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)),
]));
}
/** @return array<array-key,string|array<mixed>> */
private function getPreviousQueries(): array
{
$previousQueries = [];
$previousQueryString = \parse_url($this->getPreviousUrl(), \PHP_URL_QUERY) ?? '' ?: '';
\parse_str($previousQueryString, $previousQueries);
return $previousQueries;
}
private function getPreviousUrl(): string
{
return url()->previous(route('back.dashboard.index'));
}
}

View file

@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
}

View file

@ -1,75 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Front;
use App\Contracts\Directories\ProductDirectory;
use App\Contracts\Directories\RecipeDirectory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Front\Products\IndexProductRequest;
use Domain\Models\Entities\Product;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Inertia\Inertia;
use Inertia\Response;
class ProductController extends Controller
{
public function __construct(
private readonly ProductDirectory $productDirectory,
private readonly RecipeDirectory $recipeDirectory,
) {
}
public function index(IndexProductRequest $request): Response
{
$categories = $request->getCategories();
$breedingMethods = $request->getBreedingMethods();
$technologies = $request->getTechnologies();
$businesses = $request->getBusinesses();
$isEgalim = $request->getIsEgalim();
$search = $request->getSearchValue() ?? '';
$categoriesProducts = [];
if (count($categories) === 0) {
$categories = ! $request->hasFilter() ? [
ProductCategory::OMELETS_AND_DELI_AND_DESSERT_LINES,
ProductCategory::HARD_BOILED_EGGS,
] : ProductCategory::cases();
}
foreach ($categories as $category) {
$result = $this->productDirectory->getCategoryProducts(
category: $category,
breedingMethods: $breedingMethods,
technologies: $technologies,
businesses: $businesses,
isEgalim: $isEgalim,
search: $search,
// * No limit on search is volontary, limit is done in front, the creative team did not mind the pagination work.
limitPerCategory: -1
);
if ($result !== null) {
$categoriesProducts[] = $result;
}
}
return Inertia::render('front/Pages/Products/Index', [
'categoriesProducts' => $categoriesProducts,
]);
}
public function show(Product $product): Response
{
$relatedRecipes = $this->recipeDirectory
->getRelatedRecipes($product->id(), 10);
return Inertia::render('front/Pages/Products/Show', [
'product' => $product,
'relatedRecipes' => $relatedRecipes,
]);
}
}

View file

@ -1,79 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Front;
use App\Contracts\Directories\ProductDirectory;
use App\Contracts\Directories\RecipeDirectory;
use App\Http\Controllers\Controller;
use App\Http\Requests\Front\Recipes\IndexRecipeRequest;
use Domain\Models\Entities\Recipe;
use Domain\Models\ValueObjects\Pagination;
use Illuminate\Pagination\LengthAwarePaginator;
use Inertia\Inertia;
use Inertia\Response;
class RecipeController extends Controller
{
public function __construct(
private readonly RecipeDirectory $recipeDirectory,
private readonly ProductDirectory $productDirectory,
) {
}
public function index(IndexRecipeRequest $request): Response
{
$categories = $request->getCategories();
$consumptionTimes = $request->getConsumptionTimes();
$inspirations = $request->getInspirations();
$seasons = $request->getSeasons();
$search = $request->getSearchValue() ?? '';
// * Load all recipes for client-side display
$allRecipes = [];
$page = 1;
$perPage = 100;
do {
$pagination = Pagination::ofPage($page, $perPage);
$paginatedRecipes = $this->recipeDirectory->getPaginatedRecipes(
pagination: $pagination,
productRanges: $categories,
consumptionTimes: $consumptionTimes,
inspirations: $inspirations,
seasons: $seasons,
search: $search
);
foreach ($paginatedRecipes->items() as $recipe) {
$allRecipes[] = $recipe;
}
$page++;
} while ($page <= $paginatedRecipes->lastPage());
$mockedPaginatedRecipes = new LengthAwarePaginator(
items: $allRecipes,
total: count($allRecipes),
perPage: max(1, count($allRecipes)),
currentPage: 1,
);
return Inertia::render('front/Pages/Recipes/Index', [
'paginatedRecipes' => $mockedPaginatedRecipes,
]);
}
public function show(Recipe $recipe): Response
{
$relatedProducts = $this->productDirectory
->getRelatedProducts($recipe->id(), 10);
return Inertia::render('front/Pages/Recipes/Show', [
'recipe' => $recipe,
'relatedProducts' => $relatedProducts,
]);
}
}

View file

@ -1,96 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Middlewares;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
use Mews\Purifier\Purifier;
use Symfony\Component\HttpFoundation\Response;
class HtmlSanitizer
{
public function __construct(
private readonly Purifier $purifierService
) {
}
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next
* @param string ...$params List of input names that should be sanitized with rich sanitization (allowing some HTML)
*/
public function handle(Request $request, \Closure $next, string ...$params): Response
{
$inputs = $request->all();
\array_walk($inputs, function (mixed &$input, int | string $name) use ($params): void {
$this->cleanInput($input, (string) $name, null, $params);
});
$request->merge($inputs);
return $next($request);
}
/**
* @param string[] $params
*/
private function cleanInput(mixed &$input, string $name, ?string $rootName, array $params): void
{
if (is_array($input)) {
array_walk_recursive($input, function (mixed &$nestedInput, int | string $nestedName) use ($name, $params): void {
$this->cleanInput($nestedInput, (string) $nestedName, $name, $params);
});
}
if (! is_string($input)) {
return;
}
if ($this->shouldApplyRichSanitization($name, $rootName, $params)) {
$this->sanitizeRichInput($input);
return;
}
$input = strip_tags($input);
}
/**
* @param string[] $params
*/
private function shouldApplyRichSanitization(string $name, ?string $rootName, array $params): bool
{
return in_array($name, $params) || in_array($rootName ?? $name, $params);
}
private function sanitizeRichInput(string &$input): void
{
$isJson = \json_decode($input, true);
if ($isJson && is_array($isJson)) {
\array_walk_recursive($isJson, function (string | array &$item): void {
if (\is_string($item)) {
$item = $this->purifyString($item);
}
});
$input = json_encode($isJson, \JSON_THROW_ON_ERROR) ?: throw new \UnexpectedValueException('Failed to encode JSON after sanitization.');
return;
}
$input = $this->purifyString($input);
}
private function purifyString(string $input): string
{
$cleaned = $this->purifierService->clean($input);
if (! is_string($cleaned)) {
throw new \UnexpectedValueException('Purifier should return a string when input is a string.');
}
return ToolboxHelper::stripCkEditorSchemesOnMediaInputs($cleaned);
}
}

View file

@ -1,173 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Middlewares;
use Closure;
use Illuminate\Contracts\Container\Container;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
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 string[]
*/
private $unwantedHeaderList = [
'X-Powered-By',
'Server',
];
public function __construct(
private readonly Container $container
) {
}
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$appUrl = Config::string('app.url');
$appDebug = Config::boolean('app.debug');
$telescopeEnabled = Config::boolean('telescope.enabled', false);
// * For Vite DEV
$host = \parse_url($appUrl, \PHP_URL_HOST);
$viteDevOrigin = $appDebug && \is_string($host) && $host !== ''
? "https://{$host}:4443"
: '';
$viteDevSocket = $appDebug && \is_string($host) && $host !== ''
? "wss://{$host}:4443"
: '';
$nonce = Vite::cspNonce();
$jsBo = $request->routeIs('bo*') ? "'unsafe-eval'" : '';
$jsdelivrUrl = 'https://cdn.jsdelivr.net';
$openStreetMapTiles = 'https://*.tile.openstreetmap.org';
$this->declareNonce($nonce);
$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; preload');
// * Ignore CSP on some conditions.
if (
$appDebug && (
$response->isClientError() || // Ignition
$response->isServerError() || // Ignition
Vite::isRunningHot() || // Vite
($telescopeEnabled && str_starts_with($request->path(), 'telescope')) // Telescope
)
) {
return $response;
}
$cspPolicies = [
"default-src 'none';",
"base-uri 'self';",
"manifest-src 'self';",
"media-src 'self' blob: {$appUrl};",
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
"connect-src 'self' blob: " . implode(' ', [
'https://yl.s-web.fr/',
// * BAN address autocomplete (api-adresse.data.gouv.fr)
'https://api-adresse.data.gouv.fr',
'https://maps.googleapis.com',
// * Google reCAPTCHA
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com',
'https://www.recaptcha.net/',
// * VITE DEV
$viteDevSocket,
$viteDevOrigin,
]) . ';',
"img-src 'self' data: blob: " . implode(' ', [
'https://maps.googleapis.com',
'https://maps.gstatic.com',
$viteDevOrigin,
$openStreetMapTiles,
]) . ' data:;',
'child-src ' . implode(' ', []) . ';',
"frame-src 'self' " . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/',
'https://www.youtube.com/embed/',
'https://player.vimeo.com/',
'https://e.issuu.com/embed.html?',
]) . ';',
"object-src 'none';",
"font-src 'self' data: " . \implode(' ', [
'https://fonts.gstatic.com',
// * VITE DEV
$viteDevOrigin,
]) . ';',
// ! Using CKEditor will must have unsafe-inline style
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
$jsdelivrUrl,
'https://cdnjs.cloudflare.com',
'https://fonts.googleapis.com',
'https://use.fontawesome.com',
// * File Manager
// * VITE DEV
$viteDevOrigin,
]) . ';',
// ! If you dont use ckeditor use this
// "style-src 'self' 'nonce-$nonce';",
"script-src 'self' 'nonce-$nonce' 'strict-dynamic' " . \implode(' ', [
'https: blob:',
$jsBo,
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
'https://www.google-analytics.com',
// * VITE DEV
$viteDevOrigin,
]) . ';',
];
$response->headers->set(
'Content-Security-Policy',
implode(' ', $cspPolicies)
);
$response->headers->set('Feature-Policy', 'fullscreen \'self\'; geolocation \'self\';');
return $response;
}
/**
* Remove unwanted headers
*
* @param string[] $headerList List of headers to remove from the response
* @return void
*/
private function removeUnwantedHeaders(array $headerList)
{
foreach ($headerList as $header) {
header_remove($header);
}
}
private function declareNonce(?string $value): void
{
Vite::useCspNonce($value);
$this->container->singleton('csp_nonce', $value);
}
}

View file

@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Back\Products;
use App\Services\AdminAuthService;
use Illuminate\Foundation\Http\FormRequest;
class IndexProductRequest extends FormRequest
{
public function __construct(
private readonly AdminAuthService $authService
) {
}
public function authorize(): bool
{
return $this->authService->check();
}
public function getSearchValue(): ?string
{
return $this->has('search') ?
$this->string('search')->value() : null;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return [
'search' => ['sometimes', 'string'],
];
}
}

View file

@ -1,133 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Back\Products;
use App\Services\AdminAuthService;
use Domain\Models\ValueObjects\Products\BreedingMethod;
use Domain\Models\ValueObjects\Products\Business;
use Domain\Models\ValueObjects\Products\NutritionnalUnit;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Products\ProductFormat;
use Domain\Models\ValueObjects\Products\Technology;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreProductRequest extends FormRequest
{
protected const int MAX_FILE_SIZE_KB = 5120;
protected const int PICTURE_WIDTH = 800;
protected const int PICTURE_HEIGHT = 800;
public function __construct(
private readonly AdminAuthService $authService
) {
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->authService->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'seo_title' => ['required', 'string', 'between:45,70'],
'seo_description' => ['required', 'string', 'between:50,160'],
'category' => ['required', new Enum(ProductCategory::class)],
'technology' => ['required', new Enum(Technology::class)],
'is_egalim' => ['boolean'],
'breeding_methods' => ['required', 'array'],
'breeding_methods.*' => ['required', new Enum(BreedingMethod::class)],
'businesses' => ['required', 'array'],
'businesses.*' => ['required', new Enum(Business::class)],
'formats' => ['required', 'array'],
'formats.*' => ['required', new Enum(ProductFormat::class)],
'description' => ['required', 'string', 'min:15', 'max:500'],
'details_and_advantages' => ['required', 'string'],
'handling_advices' => ['required', 'string'],
'ingredients' => ['nullable', 'string'],
'conservation_before_opening' => ['required', 'string', 'max:255'],
'conservation_after_opening' => ['nullable', 'string', 'max:255'],
'picture' => [
'required',
Rule::imageFile()
->max(self::MAX_FILE_SIZE_KB)
->dimensions(
Rule::dimensions()
->width(self::PICTURE_WIDTH)
->height(self::PICTURE_HEIGHT)
),
],
'picture_alt' => ['required', 'string', 'min:4', 'max:75'],
'picture_title' => ['required', 'string', 'min:4', 'max:75'],
'pdf' => [
'nullable',
Rule::file()
->max(self::MAX_FILE_SIZE_KB)
->types('pdf'),
],
'nutritional_values' => ['nullable', 'array'],
'nutritional_values.*.value' => ['required_with:nutritional_values.*.unit', 'string', 'min:1', 'max:50'],
'nutritional_values.*.unit' => ['required_with:nutritional_values.*.value', new Enum(NutritionnalUnit::class)],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string, string>
*/
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
'seo_title' => trans('validation.attributes.seo_title'),
'seo_description' => trans('validation.attributes.seo_description'),
'category' => trans('validation.attributes.category'),
'technology' => trans('validation.attributes.technology'),
'is_egalim' => trans('validation.attributes.is_egalim'),
'breeding_methods' => trans('validation.attributes.breeding_methods'),
'breeding_methods.*' => trans('validation.attributes.breeding_methods'),
'businesses' => trans('validation.attributes.businesses'),
'businesses.*' => trans('validation.attributes.businesses'),
'formats' => trans('validation.attributes.formats'),
'formats.*' => trans('validation.attributes.formats'),
'description' => trans('validation.attributes.description'),
'details_and_advantages' => trans('validation.attributes.details_and_advantages'),
'handling_advices' => trans('validation.attributes.handling_advices'),
'ingredients' => trans('validation.attributes.ingredients'),
'conservation_before_opening' => trans('validation.attributes.conservation_before_opening'),
'conservation_after_opening' => trans('validation.attributes.conservation_after_opening'),
'picture' => trans('validation.attributes.picture'),
'picture_alt' => trans('validation.attributes.picture_alt'),
'picture_title' => trans('validation.attributes.picture_title'),
'pdf' => trans('validation.attributes.pdf'),
'nutritional_values' => trans('validation.attributes.nutritional_values'),
'nutritional_values.refWeightG.value' => trans('validation.attributes.nutritional_refWeightG'),
'nutritional_values.kj.value' => trans('validation.attributes.nutritional_kj'),
'nutritional_values.kcal.value' => trans('validation.attributes.nutritional_kcal'),
'nutritional_values.fatG.value' => trans('validation.attributes.nutritional_fatG'),
'nutritional_values.satFatG.value' => trans('validation.attributes.nutritional_satFatG'),
'nutritional_values.carbG.value' => trans('validation.attributes.nutritional_carbG'),
'nutritional_values.sugarG.value' => trans('validation.attributes.nutritional_sugarG'),
'nutritional_values.fiberG.value' => trans('validation.attributes.nutritional_fiberG'),
'nutritional_values.proteinG.value' => trans('validation.attributes.nutritional_proteinG'),
'nutritional_values.saltG.value' => trans('validation.attributes.nutritional_saltG'),
'nutritional_values.*.unit' => trans('validation.attributes.nutritional_values_unit'),
];
}
}

View file

@ -1,91 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Back\Products;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
class UpdateProductRequest extends StoreProductRequest
{
public function rules(): array
{
return array_merge(parent::rules(), [
'pdf' => [
'nullable',
$this->validatePdf(...),
],
'picture' => [
'required',
$this->validatePicture(...),
],
]);
}
private function validatePdf(string $attribute, mixed $value, \Closure $fail): void
{
$valuePath = match (true) {
is_string($value) => $value,
$value instanceof UploadedFile => $value->getPathname(),
default => null
};
if (is_string($valuePath)) {
$this->validateMaxFileSize($attribute, $valuePath, $fail);
ToolboxHelper::validatePdf(
$attribute,
$valuePath,
$fail
);
} elseif ($value !== null) {
$fail('Unexpected value for pdf');
}
}
private function validatePicture(string $attribute, mixed $value, \Closure $fail): void
{
$valuePath = match (true) {
is_string($value) => $value,
$value instanceof UploadedFile => $value->getPathname(),
default => null
};
if (is_string($valuePath)) {
$this->validateMaxFileSize($attribute, $valuePath, $fail);
ToolboxHelper::validatePicture(
$attribute,
$valuePath,
$fail,
(object) [
'minWidth' => self::PICTURE_WIDTH,
'maxWidth' => self::PICTURE_WIDTH,
'minHeight' => self::PICTURE_HEIGHT,
'maxHeight' => self::PICTURE_HEIGHT,
]
);
} elseif ($value !== null) {
$fail('Unexpected value for picture');
}
}
private function validateMaxFileSize(string $attribute, string $valuePath, \Closure $fail): void
{
$sizeInBytes = match (true) {
Storage::exists($valuePath) => Storage::size($valuePath),
File::exists($valuePath) => File::size($valuePath),
default => 0,
};
if (($sizeInBytes / 1024) > self::MAX_FILE_SIZE_KB) {
$fail(trans('validation.max.file', [
'attribute' => $this->attributes()[$attribute] ?? $attribute,
'max' => self::MAX_FILE_SIZE_KB,
]));
}
}
}

View file

@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Back\Recipes;
use App\Services\AdminAuthService;
use Illuminate\Foundation\Http\FormRequest;
class IndexRecipeRequest extends FormRequest
{
public function __construct(
private readonly AdminAuthService $authService
) {
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->authService->check();
}
public function getSearchValue(): ?string
{
return $this->has('search') ?
$this->string('search')->value() : null;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return [
'search' => ['sometimes', 'string'],
];
}
}

View file

@ -1,100 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Back\Recipes;
use App\Services\AdminAuthService;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Recipes\ConsumptionTime;
use Domain\Models\ValueObjects\Recipes\Inspiration;
use Domain\Models\ValueObjects\Recipes\Season;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreRecipeRequest extends FormRequest
{
protected const int MAX_FILE_SIZE_KB = 5120;
protected const int PICTURE_WIDTH = 800;
protected const int PICTURE_HEIGHT = 800;
public function __construct(
private readonly AdminAuthService $authService
) {
}
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return $this->authService->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'seo_title' => ['required', 'string', 'between:45,70'],
'seo_description' => ['required', 'string', 'between:50,160'],
'product_category' => ['required', new Enum(ProductCategory::class)],
'consomption_time' => ['required', new Enum(ConsumptionTime::class)],
'inspiration' => ['required', new Enum(Inspiration::class)],
'season' => ['required', new Enum(Season::class)],
'description' => ['nullable', 'string'],
'details' => ['nullable', 'string'],
'ingredients_list' => ['required', 'string'],
'preparation_steps' => ['required', 'string'],
'tips_and_advices' => ['nullable', 'string'],
'picture' => [
'required',
Rule::imageFile()
->max(self::MAX_FILE_SIZE_KB)
->dimensions(
Rule::dimensions()
->width(static::PICTURE_WIDTH)
->height(static::PICTURE_HEIGHT)
),
],
'picture_alt' => ['required', 'string', 'min:4', 'max:75'],
'picture_title' => ['required', 'string', 'min:4', 'max:75'],
'products' => ['nullable', 'array'],
'products.*' => ['string', 'uuid', 'exists:products,id'],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string, string>
*/
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
'seo_title' => trans('validation.attributes.seo_title'),
'seo_description' => trans('validation.attributes.seo_description'),
'product_category' => trans('validation.attributes.product_category'),
'consomption_time' => trans('validation.attributes.consomption_time'),
'inspiration' => trans('validation.attributes.inspiration'),
'season' => trans('validation.attributes.season'),
'description' => trans('validation.attributes.description'),
'details' => trans('validation.attributes.details'),
'ingredients_list' => trans('validation.attributes.ingredients_list'),
'preparation_steps' => trans('validation.attributes.preparation_steps'),
'tips_and_advices' => trans('validation.attributes.tips_and_advices'),
'picture' => trans('validation.attributes.picture'),
'picture_alt' => trans('validation.attributes.picture_alt'),
'picture_title' => trans('validation.attributes.picture_title'),
'products' => trans('validation.attributes.associated_products'),
];
}
}

View file

@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Back\Recipes;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
class UpdateRecipeRequest extends StoreRecipeRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return array_merge(parent::rules(), [
'picture' => [
'required',
$this->validatePicture(...),
],
]);
}
private function validatePicture(string $attribute, mixed $value, \Closure $fail): void
{
$valuePath = match (true) {
is_string($value) => $value,
$value instanceof UploadedFile => $value->getPathname(),
default => null
};
if (is_string($valuePath)) {
$this->validateMaxFileSize($attribute, $valuePath, $fail);
ToolboxHelper::validatePicture(
$attribute,
$valuePath,
$fail,
(object) [
'minWidth' => self::PICTURE_WIDTH,
'maxWidth' => self::PICTURE_WIDTH,
'minHeight' => self::PICTURE_HEIGHT,
'maxHeight' => self::PICTURE_HEIGHT,
]
);
} elseif ($value !== null) {
$fail('Unexpected value for picture');
}
}
private function validateMaxFileSize(string $attribute, string $valuePath, \Closure $fail): void
{
$sizeInBytes = match (true) {
Storage::exists($valuePath) => Storage::size($valuePath),
File::exists($valuePath) => File::size($valuePath),
default => 0,
};
if (($sizeInBytes / 1024) > self::MAX_FILE_SIZE_KB) {
$fail(trans('validation.max.file', [
'attribute' => $this->attributes()[$attribute] ?? $attribute,
'max' => self::MAX_FILE_SIZE_KB,
]));
}
}
}

View file

@ -1,124 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Front\Products;
use Domain\Models\ValueObjects\Products\BreedingMethod;
use Domain\Models\ValueObjects\Products\Business;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Products\Technology;
use Illuminate\Foundation\Http\FormRequest;
class IndexProductRequest extends FormRequest
{
/**
* @return \Domain\Models\ValueObjects\Products\ProductCategory[]
*/
public function getCategories(): array
{
return array_map(
function ($category) {
if (! is_string($category)) {
throw new \InvalidArgumentException('Category must be a string');
}
return ProductCategory::from($category);
},
$this->array('category'),
);
}
/** @return \Domain\Models\ValueObjects\Products\BreedingMethod[] */
public function getBreedingMethods(): array
{
return array_map(
function ($breeding) {
if (! is_string($breeding)) {
throw new \InvalidArgumentException('Breeding method must be a string');
}
return BreedingMethod::from($breeding);
},
$this->array('breeding'),
);
}
/** @return \Domain\Models\ValueObjects\Products\Technology[] */
public function getTechnologies(): array
{
return array_map(
function ($technology) {
if (! is_string($technology)) {
throw new \InvalidArgumentException('Technology must be a string');
}
return Technology::from($technology);
},
$this->array('technology'),
);
}
/** @return \Domain\Models\ValueObjects\Products\Business[] */
public function getBusinesses(): array
{
return array_map(
function ($business) {
if (! is_string($business)) {
throw new \InvalidArgumentException('Business must be a string');
}
return Business::from($business);
},
$this->array('business'),
);
}
public function getIsEgalim(): ?bool
{
$egalim = $this->input('egalim');
if (is_array($egalim) && (in_array(1, $egalim, true) || in_array('1', $egalim, true))) {
return true;
}
return null;
}
public function getSearchValue(): ?string
{
return $this->has('search') && ! empty($this->string('search')->value()) ?
$this->string('search')->value() : null;
}
public function hasFilter(): bool
{
return count($this->getCategories()) > 0
|| count($this->getBreedingMethods()) > 0
|| count($this->getTechnologies()) > 0
|| count($this->getBusinesses()) > 0
|| $this->getIsEgalim() !== null
|| $this->getSearchValue() !== null;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return [
'search' => ['nullable', 'string'],
'category' => ['nullable', 'array'],
'category.*' => ['string'],
'breeding' => ['nullable', 'array'],
'breeding.*' => ['string'],
'technology' => ['nullable', 'array'],
'technology.*' => ['string'],
'business' => ['nullable', 'array'],
'business.*' => ['string'],
'egalim' => ['nullable', 'array'],
'egalim.*' => ['numeric'],
];
}
}

View file

@ -1,100 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Front\Recipes;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Recipes\ConsumptionTime;
use Domain\Models\ValueObjects\Recipes\Inspiration;
use Domain\Models\ValueObjects\Recipes\Season;
use Illuminate\Foundation\Http\FormRequest;
class IndexRecipeRequest extends FormRequest
{
/** @return \Domain\Models\ValueObjects\Products\ProductCategory[] */
public function getCategories(): array
{
return array_map(
function ($category) {
if (! is_string($category)) {
throw new \InvalidArgumentException('Category must be a string');
}
return ProductCategory::from($category);
},
$this->array('category'),
);
}
/** @return \Domain\Models\ValueObjects\Recipes\ConsumptionTime[] */
public function getConsumptionTimes(): array
{
return array_map(
function ($time) {
if (! is_string($time)) {
throw new \InvalidArgumentException('Consumption time must be a string');
}
return ConsumptionTime::from($time);
},
$this->array('consumption_time'),
);
}
/** @return \Domain\Models\ValueObjects\Recipes\Inspiration[] */
public function getInspirations(): array
{
return array_map(
function ($inspiration) {
if (! is_string($inspiration)) {
throw new \InvalidArgumentException('Inspiration must be a string');
}
return Inspiration::from($inspiration);
},
$this->array('inspiration'),
);
}
/** @return \Domain\Models\ValueObjects\Recipes\Season[] */
public function getSeasons(): array
{
return array_map(
function ($season) {
if (! is_string($season)) {
throw new \InvalidArgumentException('Season must be a string');
}
return Season::from($season);
},
$this->array('season'),
);
}
public function getSearchValue(): ?string
{
return $this->has('search') && ! empty($this->string('search')->value()) ?
$this->string('search')->value() : null;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Validation\Rule|array<mixed>|string>
*/
public function rules(): array
{
return [
'search' => ['nullable', 'string'],
'category' => ['nullable', 'array'],
'category.*' => ['string'],
'consumption_time' => ['nullable', 'array'],
'consumption_time.*' => ['string'],
'inspiration' => ['nullable', 'array'],
'inspiration.*' => ['string'],
'season' => ['nullable', 'array'],
'season.*' => ['string'],
];
}
}

View file

@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
use Uwil\LaravelBackend\Resources\CommonResource as BaseCommonResource;
class CommonResource extends BaseCommonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
$data = parent::toArray($request);
$resource = $this->resource;
if (! is_object($resource) || ! ($resource instanceof Model)) {
throw new \InvalidArgumentException('Resource must be an instance of ' . Model::class);
}
$modelClass = $resource::class;
$data['modelClass'] = $modelClass;
$data['policies'] = Arr::wrap($data['policies'] ?? []);
$data['policies']['create'] = Gate::allows('create', $modelClass);
$data['policies']['viewAny'] = Gate::allows('viewAny', $modelClass);
return $data;
}
}

View file

@ -1,529 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use App\Exceptions\Helpers\FileStorageOperationException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
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 sha256 file hash
* 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.
*/
class FileStorageHelper
{
private const string STORAGE_PREFIX = 'storage/';
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $publicStorageFilePath The file path from public directory.
* @return string The local storage path.
*/
public static function getFileFullPath(string $publicStorageFilePath): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('public');
$path = Str::replace(self::STORAGE_PREFIX, '', \ltrim(\urldecode($publicStorageFilePath), '/'));
return $disk->path($path);
}
/**
* Destroy the file associate with the attribute.
*/
public static function removeFile(Model $model, string $attribute): void
{
$attributeValue = $model->getAttribute($attribute);
if (! is_scalar($attributeValue)) {
return;
}
$path = \ltrim(\urldecode(strval($attributeValue)), '/');
if (! str_starts_with($path, 'storage')) {
return;
}
$path = Str::replace(self::STORAGE_PREFIX, '', $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).
*/
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
{
$originalValue = ToolboxHelper::getOriginal($model, $attribute);
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
if ($onlyIfUpdated && $model->{$attribute} === $originalValue) {
return;
}
if (! is_string($originalValue)) {
return;
}
$originalPath = \ltrim(\urldecode($originalValue), '/');
if (! str_starts_with($originalPath, 'storage')) {
return;
}
$originalPath = Str::replace(self::STORAGE_PREFIX, '', $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 Model $model The model that will hold the picture.
* @param UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
* @param bool $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param bool $private To store the file in a private space.
*
* @throws FileStorageOperationException If file copy fails.
*
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public static function storeFile(
Model $model,
UploadedFile | \SplFileInfo | string | null $file,
bool $slugify = false,
?string $filename = null,
bool $private = false
): string {
// phpcs:enable
$tableName = $model->getTable();
if ($file instanceof UploadedFile) {
return self::storeUploadedFile($file, $tableName, $slugify, $filename, $private);
}
if ($file instanceof \SplFileInfo) {
return self::storeSplFileInfo($file, $tableName, $slugify, $filename, $private);
}
return (string) $file;
}
private static function storeUploadedFile(
UploadedFile $file,
string $tableName,
bool $slugify,
?string $filename,
bool $private
): string {
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$disk = $private ? 'private' : 'public';
$newPath = str_replace(
storage_path("app/{$disk}"),
'',
self::prepareMoveFile($tableName, $filename, $private)
);
$directory = pathinfo($newPath, \PATHINFO_DIRNAME);
$basename = pathinfo($newPath, \PATHINFO_BASENAME);
if (! $private) {
$file->storePubliclyAs(
$directory,
$basename,
['disk' => $disk]
);
} else {
$file->storeAs(
$directory,
$basename,
['disk' => $disk]
);
}
return "storage{$newPath}";
}
private static function storeSplFileInfo(
\SplFileInfo $file,
string $tableName,
bool $slugify,
?string $filename,
bool $private
): string {
$filename = $filename ?: $file->getBaseName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
if (! File::copy($file->getRealPath(), $newPath)) {
throw FileStorageOperationException::copyFailed($file->getRealPath(), $newPath);
}
if ($private) {
return \ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/');
}
return \ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
/**
* 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 Model $model The model that will hold the picture.
* @param string|null $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
* files using storeModelFilesFromHtml.
* @return string|null Html with files pathes replaced using storage strategy.
*/
public static function storeModelFilesFromHtml(
Model $model,
?string $htmlString,
?string $oldHtmlString = null
): ?string {
if (is_null($htmlString)) {
return null;
}
Collection::make(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString): void {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('private');
$fileInfo = new \SplFileInfo($disk->path(
ToolboxHelper::mbReplaceSingle('/uploads/', '', \urldecode($path))
));
$htmlString = ToolboxHelper::mbReplaceSingle(
$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.
*/
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.
*/
public static function removeOldFiles(array $oldFiles, array $newFiles): void
{
$oldFiles = Collection::make(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
$filesToKeep = Collection::make(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
$filesToRemove = $oldFiles->diff($filesToKeep);
$filesToRemove->each(function (string $publicStorageFilePath): void {
$path = Str::replace(self::STORAGE_PREFIX, '', \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.
*/
public static function removeModelFilesOfHtml(string $htmlString): void
{
$filesToRemove = Collection::make(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
$filesToRemove->each(function (string $publicStorageFilePath): void {
$path = Str::replace(self::STORAGE_PREFIX, '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
});
}
/**
* Find all occurence of files in html
*
* @param string $html The html string to parse and find files.
* @return string[] The array of files found in html.
*/
public static function findFilesInHtml(string $html): array
{
preg_match_all(
'/(?:href|src|url)="([^"]*)"/Ui',
$html,
$matches
);
return Collection::make($matches[1])->unique()->filter()->values()->all();
}
/**
* Filter array to get only uploaded files that exists
* on disk 'public' (using helper storage strategy)
*
* @param string[] $urls The array of urls to filter
* @return string[] The filtered array with only uploaded files that exists on disk 'public
*/
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
{
return Collection::make($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
$path = (string) (\parse_url($url, \PHP_URL_PATH) ?? '');
return str_starts_with(\ltrim($path, '/'), 'storage/modelfiles');
})->filter(function (string $url) {
$path = (string) (\parse_url($url, \PHP_URL_PATH) ?? '');
return Storage::disk('public')
->exists(Str::replace(self::STORAGE_PREFIX, '', \ltrim(\urldecode($path), '/')));
})->all();
}
/**
* Filter array to get only uploaded files that exist on disk 'private'
*
* @param string[] $urls The array of urls to filter
* @return string[] The filtered array
*/
private static function onlyUploadedFilesWithFileManager(array $urls): array
{
$appUrl = Config::string('app.url');
if (empty($appUrl)) {
throw new \UnexpectedValueException('App URL must be set in config to filter uploaded files with file manager');
}
$appHost = parse_url((string) $appUrl, PHP_URL_HOST);
return Collection::make($urls)->unique()->filter(function (string $url) use ($appHost) {
$host = parse_url($url, PHP_URL_HOST);
// 1. Validation du domaine (Doit être identique à l'app ou être un chemin relatif)
if ($host !== null && $host !== $appHost) {
return false;
}
// 2. Extraction et nettoyage du chemin
$path = ltrim(urldecode((string) parse_url($url, PHP_URL_PATH)), '/');
// 3. Vérification du préfixe 'uploads/' et existence sur le disque
if (! str_starts_with($path, 'uploads/')) {
return false;
}
// On retire 'uploads/' pour tester l'existence sur le disque 'private'
$diskPath = substr($path, strlen('uploads/'));
return Storage::disk('private')->exists($diskPath);
})->values()->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 bool $private May the file be stored in a private dir.
*/
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
);
$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/sha256 first 2 letters
*/
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
{
$filenameHash = \hash('sha256', $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
*
* @throws FileStorageOperationException 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 FileStorageOperationException::cannotGenerateUniqueFilename($folderPath, $filename);
}
$filename = Str::of(pathinfo($filename, \PATHINFO_FILENAME))
->append(Str::random(8) . '-')
->append('.')
->append(pathinfo($filename, \PATHINFO_EXTENSION))
->toString();
}
return $filename;
}
/**
* Slugify the filename
*/
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 bool $beautify Beautify the filename.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $filename, bool $beautify = true): string
{
$filename = preg_replace('/[<>:"\/\\|?*\x00-\x1F\x7F\xA0\xAD#\[\]@!$&\'()+,;={}^~`]/u', '-', $filename) ?? $filename;
// * Avoids '.' '..' or '.hiddenFiles' .
$filename = (string) Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = self::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 ?: null;
return \mb_strcut(
$filename,
0,
255 - (! empty($extension) ? strlen($extension) + 1 : 0),
$encoding
) . (! empty($extension) ? ".$extension" : '');
}
/**
* Beautify a filename
*/
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;
$filename = preg_replace([
// * "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// * "file...name..zip" becomes "file.name.zip"
'/\.{2,}/',
], '.', $filename) ?? $filename;
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
$encoding = mb_detect_encoding($filename);
$filename = mb_strtolower($filename, $encoding ?: 'UTF-8');
// * ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
}
/**
* Remove all model files for the given Model.
*/
public static function removeModelFiles(Model $model): void
{
$tableName = $model::query()->getModel()->getTable();
if (! empty(Storage::disk('public')->allFiles("modelfiles/{$tableName}"))) {
Storage::disk('public')->deleteDirectory("modelfiles/{$tableName}");
}
if (! empty(Storage::disk('private')->allFiles("modelfiles/{$tableName}"))) {
Storage::disk('private')->deleteDirectory("modelfiles/{$tableName}");
}
}
public static function isMimeType(string $mimeType, \SplFileInfo $file): bool
{
if (! $file->getRealPath()) {
return false;
}
$actualMime = File::mimeType($file->getRealPath());
return $actualMime ? Str::is($mimeType, $actualMime) : false;
}
}

View file

@ -1,408 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class ToolboxHelper extends ToolboxHelperCore
{
/**
* @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2
* @return array<int|string, mixed>
*/
public static function arrayMergeRecursiveDistinct(array &$array1, array &$array2): array
{
$merged = $array1;
foreach ($array2 as $key => &$value) {
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = self::arrayMergeRecursiveDistinct($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
/**
* 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..)
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
return Str::of($input)
->replaceMatches('/(?<=<oembed url=")(http:\/\/)(?=.*?">)/m', '')
->toString();
}
/**
* Asserts that the field is unique.
*
* @param array<mixed>|string|int|float|bool|null $value
*
* @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,
null | int | string $id = null,
\Closure | string | null $query = null
): void {
if (! $query) {
Validator::make([$field => $value], [
(string) str_replace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
(string) str_replace('.', '\.', "{$field}.unique") => trans(
'crud.libs.toolbox.assertFieldIsUnique',
['attribute' => $field, 'value' => is_array($value) ? json_encode($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' => is_array($value) ? json_encode($value) : $value]
),
])->validate();
}
/**
* Assert That no Back Office url is present on txt
*
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
return self::replaceUrlsInText($text, function (string $url) use ($routes, $field): void {
$request = Request::create($url);
if (! self::isInternalHost($url)) {
return;
}
if (trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo') {
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalBoUrls',
['attribute' => $field]
),
]);
}
});
}
/**
* Assert That only external url is present on txt
*
* @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();
return self::replaceUrlsInText($text, function (string $url) use ($routes, $field): void {
$request = Request::create($url);
$routes->match($request);
if (! self::isInternalHost($url)) {
return;
}
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalUrls',
['attribute' => $field, 'appname' => Config::string('app.name')]
),
]);
});
}
private static function isInternalHost(string $url): bool
{
return \parse_url((string) Config::string('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST);
}
private static function replaceUrlsInText(string $text, callable $onMatchedUrl): string
{
$result = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@%._+~#=]{2,256}\.[a-z]{2,6}\b[-a-zA-Z0-9@:%_.+~#?&\/=]*)/',
function (array $matches) use ($onMatchedUrl): string {
$match = collect($matches)->first() ?? '';
$url = (string) $match;
try {
$onMatchedUrl($url);
} catch (NotFoundHttpException) {
return (string) $match;
}
return (string) $match;
},
$text
);
return is_string($result) ? $result : $text;
}
/**
* Get original polyfill with autotranslate behavior
*/
public static function getOriginal(Model $model, string $attribute): mixed
{
if (
\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model)) && // @phpstan-ignore-next-line
$model->isTranslatableAttribute($attribute)
) {
// @phpstan-ignore-next-line
return $model->getTranslation(
$attribute,
// @phpstan-ignore-next-line
$model->getLocale(),
// @phpstan-ignore-next-line
$model->useFallbackLocale()
);
}
return $model->getOriginal($attribute);
}
/**
* Generates an array from a dot separated string
*
* @param string $path The dot separated string to convert to an array.
* @param mixed $value The value to set at the end of the array.
* @param string $separator The separator used in the path (default is '.').
* @return array<string,mixed> The generated array.
*/
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
{
throw_if($separator === '', \InvalidArgumentException::class, 'Separator cannot be an empty string.');
$arr = $value;
$keys = explode($separator, $path);
$keys = array_reverse($keys);
foreach ($keys as $key) {
$arr = [$key => $arr];
}
return $arr;
}
/**
* Validate pdf
*
* @param callable(string):void $fail A callback that should be called if the validation fails.
*
* @throws \Illuminate\Validation\ValidationException If the pdf is invalid.
*/
public static function validatePdf(string $attribute, ?string $value, callable $fail): void
{
if ($value === null) {
return;
}
if (! ($useStorage = Storage::exists($value)) && ! File::exists($value)) {
$fail(trans('validation.custom.has_picture.file_does_not_exist', [
'attribute' => $attribute,
]));
return;
}
if (self::getUploadedFileMimeType($value, $useStorage) !== 'application/pdf') {
$fail(trans('validation.mimetypes', [
'attribute' => $attribute,
'values' => 'application/pdf',
]));
}
}
/**
* Validate picture
*
* @param callable(string):void $fail A callback that should be called if the validation fails.
* @param object{
* minWidth: int,
* maxWidth: int,
* minHeight: int,
* maxHeight: int
* }&\stdClass $sizes An object containing the minimum and maximum width and height for the picture.
*
* @throws \Illuminate\Validation\ValidationException If the picture is invalid.
*/
public static function validatePicture(string $attribute, ?string $value, callable $fail, object $sizes): void
{
if ($value === null) {
return;
}
if (! ($useStorage = Storage::exists($value)) && ! File::exists($value)) {
$fail(trans('validation.custom.has_picture.file_does_not_exist', [
'attribute' => $attribute,
]));
return;
}
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
if (
! \in_array(self::getUploadedFileMimeType($value, $useStorage), [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
])
) {
$fail(trans('validation.mimetypes', [
'attribute' => $attribute,
'values' => 'image/jpeg, image/jpg, image/png, image/gif',
]));
}
if (! ($dimensions = \getimagesize($fileFullPath))) {
$fail(trans('validation.dimensions', [
'attribute' => $attribute,
]));
return;
}
$width = intval($dimensions[0]);
$height = intval($dimensions[1]);
$xMessage = trans('validation.custom.has_picture.width_between', [
'attribute' => $attribute,
'min' => "{$sizes->minWidth}px",
'max' => "{$sizes->maxWidth}px",
]);
if ($sizes->minWidth === $sizes->maxWidth) {
$xMessage = trans('validation.custom.has_picture.width_equal', [
'attribute' => $attribute,
'value' => "{$sizes->minWidth}px",
]);
}
$yMessage = trans('validation.custom.has_picture.height_between', [
'attribute' => $attribute,
'min' => "{$sizes->minHeight}px",
'max' => "{$sizes->maxHeight}px",
]);
if ($sizes->minHeight === $sizes->maxHeight) {
$yMessage = trans('validation.custom.has_picture.height_equal', [
'attribute' => $attribute,
'value' => "{$sizes->minHeight}px",
]);
}
if (($width < $sizes->minWidth) || ($width > $sizes->maxWidth)) {
$fail($xMessage);
}
if (($height < $sizes->minHeight) || ($height > $sizes->maxHeight)) {
$fail($yMessage);
}
}
/**
* Fetch class list from a specific namespace
*
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingNamespace(string $namespace): array
{
return collect(File::files(\app_path((string) Str::of($namespace)->replaceFirst('\App', ''))))
->filter(fn (SplFileInfo $splFileInfo) => $splFileInfo->getExtension() === 'php')
->map(fn (SplFileInfo $splFileInfo) => (string) Str::of($splFileInfo->getBasename())
->replace('.' . $splFileInfo->getExtension(), '')->prepend("{$namespace}\\"))->all();
}
/**
* Get all classes from a specific namespace that implement a specific interface
*
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingInterface(string $namespace, string $interface): array
{
return Arr::where(
static::getClassesUsingNamespace($namespace),
fn (string $class_namespace) => \collect(class_implements($class_namespace, true) ?: [])->filter()
->contains(fn (string $class_interface) => $class_interface === $interface)
);
}
/**
* Convert Hex color to RGB
*
* @return int[]
*/
public static function hexToRgb(string $hexcolor): array
{
$hexcolor = (string) str($hexcolor)->replaceStart('#', '');
$r = (int) hexdec(substr($hexcolor, 0, 2));
$g = (int) hexdec(substr($hexcolor, 2, 2));
$b = (int) hexdec(substr($hexcolor, 4, 2));
return [$r, $g, $b];
}
/**
* Get text color contrast for a hex background color
*
* @return string "black" or "white"
*/
public static function getContrastYiq(string $hexcolor): string
{
[$r, $g, $b] = self::hexToRgb($hexcolor);
$yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
return ($yiq >= 128) ? 'black' : 'white';
}
/**
* Convert an HTML string to plain text with proper word spacing.
*
* Replaces HTML tags with spaces, strips remaining tags,
* normalises whitespace and trims the result.
*
* @param string|null $html The HTML content to convert.
* @param int|null $limit Optional character limit (uses Str::limit).
*/
public static function htmlToText(?string $html, ?int $limit = null): string
{
if ($html === null || $html === '') {
return '';
}
$text = strip_tags((string) preg_replace('/<[^>]*>/', ' ', $html));
$text = trim((string) preg_replace('/\s+/', ' ', $text));
if ($limit !== null) {
return Str::limit($text, $limit);
}
return $text;
}
}

View file

@ -1,191 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use App\Exceptions\Helpers\UnsupportedStructureConversionException;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
abstract class ToolboxHelperCore
{
/**
* Multibyte-safe str_replace with unified dispatch.
*
* Returns false when $replace is an array but $search is a scalar string.
*
* @param array<string>|string $search
* @param array<string>|string $replace
* @param array<int|string, mixed>|string $subject
* @return array<int|string, mixed>|string|false
*/
public static function mbReplace(
array | string $search,
array | string $replace,
array | string $subject,
int &$count = 0,
): array | string | false {
if (is_array($subject)) {
return self::mbReplaceInArraySubject($search, $replace, $subject, $count);
}
return self::mbReplaceScalar($search, $replace, $subject, $count);
}
/**
* Dispatch for scalar subject.
* Returns false when $replace is array but $search is a scalar string.
*
* @param array<string>|string $search
* @param array<string>|string $replace
*/
private static function mbReplaceScalar(
array | string $search,
array | string $replace,
string $subject,
int &$count = 0,
): string | false {
if (! is_array($search) && is_array($replace)) {
return false;
}
if (is_array($search)) {
return self::mbReplaceWithSearchArray($search, $replace, $subject, $count);
}
if (! is_string($replace)) {
throw new \InvalidArgumentException('Replace must be a string when search is a string.');
}
return self::mbReplaceSingle($search, $replace, $subject, $count);
}
/**
* @param string[]|string $search
* @param string[]|string $replace
* @param array<int|string, mixed> $subject
* @return array<int|string, mixed>
*/
public static function mbReplaceInArraySubject(array | string $search, array | string $replace, array $subject, int &$count = 0): array
{
foreach ($subject as &$value) {
if (is_array($value)) {
$value = self::mbReplaceInArraySubject($search, $replace, $value, $count);
} elseif (is_array($search) && is_string($value)) {
$value = self::mbReplaceWithSearchArray($search, $replace, $value, $count);
} elseif (! is_array($replace) && is_string($value)) {
$value = self::mbReplaceSingle($search, $replace, $value, $count);
}
}
unset($value);
return $subject;
}
/**
* @param string[] $search
* @param array<string>|string $replace
*/
public static function mbReplaceWithSearchArray(array $search, array | string $replace, string $subject, int &$count = 0): string
{
if (! is_array($replace)) {
foreach ($search as $searchItem) {
$subject = (string) self::mbReplaceSingle($searchItem, $replace, $subject, $count);
}
return $subject;
}
$iterations = max(count($search), count($replace));
$searchItem = reset($search);
$replaceItem = reset($replace);
while ($iterations--) {
$subject = (string) self::mbReplaceSingle((string) $searchItem, (string) $replaceItem, $subject, $count);
$searchItem = next($search);
$replaceItem = next($replace);
}
return $subject;
}
public static function mbReplaceSingle(string $search, string $replace, string $subject, int &$count = 0): string
{
$parts = mb_split(preg_quote($search), $subject) ?: [];
$count = count($parts) - 1;
return implode($replace, $parts);
}
/**
* Convert deeply an object to array
*
* @return array<int|string,mixed>
*
* @throws \App\Exceptions\Helpers\UnsupportedStructureConversionException If $input var is a scalar value.
*/
public static function objectToArray(mixed $input): array
{
if (! (is_object($input) || is_array($input))) {
throw UnsupportedStructureConversionException::fromType($input);
}
$ret = (array) $input;
foreach ($ret as &$item) {
$item = (is_object($item) || is_array($item)) ? self::objectToArray($item) : $item;
}
return $ret;
}
/**
* Convert deeply an array to object
*
* @throws \App\Exceptions\Helpers\UnsupportedStructureConversionException If $input var is a scalar value.
*/
public static function arrayToObject(mixed $input): object
{
if (! (is_object($input) || is_array($input))) {
throw UnsupportedStructureConversionException::fromType($input);
}
$ret = (object) $input;
$retArray = (array) $ret;
foreach ($retArray as &$item) {
$item = (is_object($item) || is_array($item)) ? self::arrayToObject($item) : $item;
}
unset($item);
return (object) $retArray;
}
/**
* Get the file full path
*/
protected static function getUploadedFileFullPath(string $value, bool $useStorage): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk();
if ($useStorage) {
return $disk->path($value);
}
return realpath($value) ?: $value;
}
/**
* Get the file mimetype
*
* @return string|false
*/
protected static function getUploadedFileMimeType(string $value, bool $useStorage)
{
if ($useStorage) {
return Storage::mimeType($value);
}
return File::mimeType($value);
}
}

View file

@ -1,195 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use App\Casts\NutritionalValuesCast;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Products\BreedingMethod;
use Domain\Models\ValueObjects\Products\Business;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Products\ProductFormat;
use Domain\Models\ValueObjects\Products\Technology;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use Laravel\Scout\Searchable;
use Ramsey\Uuid\Uuid;
use Spatie\Translatable\HasTranslations;
use Uwil\LaravelBackend\Interfaces\TranslatableModel;
use Uwil\LaravelBackend\Traits\Models\HasActivityLog;
use Uwil\LaravelBackend\Traits\Models\HasCache;
use Uwil\LaravelBackend\Traits\Models\HasModelNaming;
use Uwil\LaravelBackend\Traits\Models\HasUwilImages;
// phpcs:disable Generic.Files.LineLength.TooLong
/**
* @property string $id
* @property string $seo_title
* @property string $seo_description
* @property string $picture
* @property string $picture_alt
* @property string $picture_title
* @property string $name
* @property string $slug
* @property ProductCategory $category Domain\Models\ValueObjects\Products\ProductRange
* @property Technology $technology Domain\Models\ValueObjects\Products\Technology
* @property bool $is_egalim
* @property \Illuminate\Support\Collection<int, BreedingMethod> $breeding_methods Array of Domain\Models\ValueObjects\Products\BreedingMethod
* @property \Illuminate\Support\Collection<int, Business> $businesses Array of Domain\Models\ValueObjects\Products\Business
* @property \Illuminate\Support\Collection<int, ProductFormat> $formats Array of Domain\Models\ValueObjects\Products\ProductFormat
* @property string $description
* @property string $details_and_advantages
* @property string $handling_advices
* @property string|null $ingredients
* @property string $conservation_before_opening
* @property string|null $conservation_after_opening
* @property \Domain\Models\ValueObjects\Products\NutritionalValues|null $nutritional_values Domain\Models\ValueObjects\NutritionalValues
* @property string|null $pdf
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Uwil\LaravelBackend\Models\ActivityLog> $activityLogs
* @property-read array $translatable_columns_from
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Recipe> $recipes
* @property-read mixed $translations
*
* @method static \Database\Factories\ProductFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereJsonContainsLocale(string $column, string $locale, ?mixed $value, string $operand = '=')
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereJsonContainsLocales(string $column, array $locales, ?mixed $value, string $operand = '=')
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereLocale(string $column, string $locale)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product whereLocales(string $column, array $locales)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Product withoutTrashed()
*
* @mixin \Eloquent
*/
// phpcs:enable Generic.Files.LineLength.TooLong
class Product extends Model implements TranslatableModel
{
use HasActivityLog;
use HasCache;
/** @use HasFactory<\Database\Factories\ProductFactory> */
use HasFactory;
use HasModelNaming;
use HasTranslations;
use HasUuids;
use HasUwilImages;
use Searchable;
use SoftDeletes;
protected $fillable = [
'seo_title',
'seo_description',
'picture',
'picture_alt',
'picture_title',
'name',
'category',
'technology',
'is_egalim',
'breeding_methods',
'businesses',
'formats',
'description',
'details_and_advantages',
'handling_advices',
'ingredients',
'conservation_before_opening',
'conservation_after_opening',
'nutritional_values',
'pdf',
];
protected $casts = [
'is_egalim' => 'boolean',
'category' => ProductCategory::class,
'technology' => Technology::class,
'breeding_methods' => AsEnumCollection::class . ':' . BreedingMethod::class,
'businesses' => AsEnumCollection::class . ':' . Business::class,
'formats' => AsEnumCollection::class . ':' . ProductFormat::class,
'nutritional_values' => NutritionalValuesCast::class,
];
public function newUniqueId(): string
{
return Uuid::uuid7()->toString();
}
public function getId(): ProductId
{
return new ProductId(Uuid::fromString(strval($this->id)));
}
#[\Override]
protected static function booted(): void
{
static::saving(function (self $model): void {
self::manageFields($model);
});
static::deleting(function (self $model): void {
FileStorageHelper::removeFile($model, 'picture');
FileStorageHelper::removeFile($model, 'pdf');
});
}
/** @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\App\Models\Recipe,$this> */
public function recipes(): BelongsToMany
{
return $this->belongsToMany(Recipe::class);
}
/**
* Relations should be share to views.
*
* @return array<int,string>
*/
public static function getPropsRelations(): array
{
return self::getCacheRelations();
}
private static function manageFields(self $model): void
{
self::assertNameIsUnique($model->name, $model->id);
$model->slug = Str::slug($model->name);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
if ($model->pdf) {
$model->pdf = FileStorageHelper::storeFile($model, $model->pdf, true);
}
}
private static function assertNameIsUnique(?string $name, string | int | null $id = null): void
{
$table = new self()->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($name ?? ''), $id);
}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'category' => $this->category?->value,
];
}
}

View file

@ -1,180 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Recipes\ConsumptionTime;
use Domain\Models\ValueObjects\Recipes\Inspiration;
use Domain\Models\ValueObjects\Recipes\Season;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;
use Laravel\Scout\Searchable;
use Ramsey\Uuid\Uuid;
use Spatie\Translatable\HasTranslations;
use Uwil\LaravelBackend\Interfaces\TranslatableModel;
use Uwil\LaravelBackend\Traits\Models\HasActivityLog;
use Uwil\LaravelBackend\Traits\Models\HasCache;
use Uwil\LaravelBackend\Traits\Models\HasModelNaming;
use Uwil\LaravelBackend\Traits\Models\HasUwilImages;
// phpcs:disable Generic.Files.LineLength.TooLong
/**
* @phpcs :disable Generic.Files.LineLength.TooLong
*
* @property string $id
* @property string $seo_title
* @property string $seo_description
* @property string $picture
* @property string $picture_alt
* @property string $picture_title
* @property string $name
* @property string $slug
* @property ProductCategory $product_category Domain\Models\ValueObjects\Products\ProductRange
* @property ConsumptionTime $consomption_time Domain\Models\ValueObjects\Recipes\ConsumptionTime
* @property Inspiration $inspiration Domain\Models\ValueObjects\Recipes\Inspiration
* @property Season $season Domain\Models\ValueObjects\Recipes\Season
* @property string|null $description
* @property string|null $details
* @property string $ingredients_list
* @property string $preparation_steps
* @property string|null $tips_and_advices
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Uwil\LaravelBackend\Models\ActivityLog> $activityLogs
* @property-read array $translatable_columns_from
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Product> $products
* @property-read mixed $translations
*
* @method static \Database\Factories\RecipeFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe whereJsonContainsLocale(string $column, string $locale, ?mixed $value, string $operand = '=')
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe whereJsonContainsLocales(string $column, array $locales, ?mixed $value, string $operand = '=')
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe whereLocale(string $column, string $locale)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe whereLocales(string $column, array $locales)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Recipe withoutTrashed()
*
* @mixin \Eloquent
*/
// phpcs:enable Generic.Files.LineLength.TooLong
class Recipe extends Model implements TranslatableModel
{
use HasActivityLog;
use HasCache;
/** @use HasFactory<\Database\Factories\RecipeFactory> */
use HasFactory;
use HasModelNaming;
use HasTranslations;
use HasUuids;
use HasUwilImages;
use Searchable;
use SoftDeletes;
protected $fillable = [
'seo_title',
'seo_description',
'picture',
'picture_alt',
'picture_title',
'name',
'product_category',
'consomption_time',
'inspiration',
'season',
'description',
'details',
'ingredients_list',
'preparation_steps',
'tips_and_advices',
];
protected $casts = [
'product_category' => ProductCategory::class,
'consomption_time' => ConsumptionTime::class,
'inspiration' => Inspiration::class,
'season' => Season::class,
];
public function newUniqueId(): string
{
return Uuid::uuid7()->toString();
}
public function getId(): RecipeId
{
return new RecipeId(Uuid::fromString($this->id));
}
#[\Override]
protected static function booted(): void
{
static::saving(function (self $model): void {
self::manageFields($model);
});
static::deleting(function (self $model): void {
FileStorageHelper::removeFile($model, 'picture');
});
}
/** @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\App\Models\Product,$this> */
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class);
}
/**
* Relations should be share to views.
*
* @return array<int,string>
*/
public static function getPropsRelations(): array
{
return self::getCacheRelations();
}
private static function manageFields(self $model): void
{
self::assertNameIsUnique($model->name, $model->id);
$model->slug = Str::slug($model->name);
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
}
private static function assertNameIsUnique(?string $name, string | int | null $id = null): void
{
$table = new self()->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($name ?? ''), $id);
}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'product_category' => $this->product_category?->value,
'consomption_time' => $this->consomption_time?->value,
'inspiration' => $this->inspiration?->value,
'season' => $this->season?->value,
];
}
}

View file

@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Product;
use Uwil\LaravelBackend\Enums\RoleEnum;
use Uwil\LaravelBackend\Models\User;
use Uwil\LaravelBackend\Policies\Rules\UserStaticRules;
class ProductPolicy
{
public function viewAny(User $authModel): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function viewTrash(User $authModel): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function view(User $authModel, Product $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function create(User $authModel): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function duplicate(User $authModel, Product $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function update(User $authModel, Product $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function delete(User $authModel, Product $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function restore(User $authModel, Product $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function forceDelete(User $authModel, Product $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
}

View file

@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\Recipe;
use Uwil\LaravelBackend\Enums\RoleEnum;
use Uwil\LaravelBackend\Models\User;
use Uwil\LaravelBackend\Policies\Rules\UserStaticRules;
class RecipePolicy
{
public function viewAny(User $authModel): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function viewTrash(User $authModel): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function view(User $authModel, Recipe $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function create(User $authModel): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function duplicate(User $authModel, Recipe $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function update(User $authModel, Recipe $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function delete(User $authModel, Recipe $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function restore(User $authModel, Recipe $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
public function forceDelete(User $authModel, Recipe $_): bool
{
return UserStaticRules::atLeastRole($authModel, RoleEnum::admin);
}
}

View file

@ -1,162 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Contracts\Directories\ProductDirectory;
use App\Contracts\Directories\RecipeDirectory;
use App\Directories\EloquentProductDirectory;
use App\Directories\EloquentRecipeDirectory;
use App\Enums\Contact\ActivitySectorEnum;
use App\Enums\Contact\ObjectEnum;
use App\Services\AdminAuthService;
use Domain\Models\ValueObjects\Pagination;
use Domain\Models\ValueObjects\Products\BreedingMethod;
use Domain\Models\ValueObjects\Products\Business;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Products\ProductFormat;
use Domain\Models\ValueObjects\Products\Technology;
use Domain\Models\ValueObjects\Recipes\ConsumptionTime;
use Domain\Models\ValueObjects\Recipes\Inspiration;
use Domain\Models\ValueObjects\Recipes\Season;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;
use Uwil\LaravelBackend\Support\ModelListRegistry;
use Uwil\LaravelCommon\Support\InertiaSharedRegistry;
use Uwil\LaravelDynamicPages\Enums\PageStatusEnum;
use Uwil\LaravelDynamicPages\Models\DynamicPage;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(
AdminAuthService::class,
function () {
$guard = Auth::guard('backend');
return new AdminAuthService($guard);
}
);
$this->app->singleton(
ProductDirectory::class,
fn () => new EloquentProductDirectory()
);
$this->app->singleton(
RecipeDirectory::class,
fn () => new EloquentRecipeDirectory()
);
$this->requestMacro();
}
public function boot(): void
{
$this->dateSetup();
$this->laravelGlobalSetup();
$this->registerSharedInertiaProps();
$this->registerModelsInBackendRegistry();
}
private function laravelGlobalSetup(): void
{
Model::shouldBeStrict(app()->environment('local'));
Vite::useCspNonce();
URL::forceScheme('https');
}
private function registerSharedInertiaProps(): void
{
InertiaSharedRegistry::add('dynamicPages', function (): array {
if (! Schema::hasTable('dynamic_pages')) {
return [];
}
$dynamicPageModels = DynamicPage::getModelsCached()
->where('status', PageStatusEnum::published);
$dynamicPages = [];
foreach ($dynamicPageModels as $dynamicPageModel) {
$pascalCase = str_replace(' ', '', ucwords(str_replace('-', ' ', $dynamicPageModel->id_key)));
$dynamicPages["page{$pascalCase}"] = $dynamicPageModel;
}
return $dynamicPages;
});
InertiaSharedRegistry::add('activitySectorEnum', fn () => ActivitySectorEnum::toArray());
InertiaSharedRegistry::add('objectEnum', fn () => ObjectEnum::toArray());
InertiaSharedRegistry::add('breedingMethodEnum', fn () => BreedingMethod::toArray());
InertiaSharedRegistry::add('businessEnum', fn () => Business::toArray());
InertiaSharedRegistry::add('productCategoryEnum', fn () => ProductCategory::toArray());
InertiaSharedRegistry::add('productFormatEnum', fn () => ProductFormat::toArray());
InertiaSharedRegistry::add('technologyEnum', fn () => Technology::toArray());
InertiaSharedRegistry::add('consumptionTimeEnum', fn () => ConsumptionTime::toArray());
InertiaSharedRegistry::add('inspirationEnum', fn () => Inspiration::toArray());
InertiaSharedRegistry::add('seasonEnum', fn () => Season::toArray());
}
private function registerModelsInBackendRegistry(): void
{
ModelListRegistry::add(
2,
'products',
trans_choice('models.classes.product', 1),
'name',
);
ModelListRegistry::add(
3,
'recipes',
trans_choice('models.classes.recipe', 1),
'name',
);
}
private function dateSetup(): void
{
foreach ([\LC_TIME, \LC_MESSAGES, \LC_COLLATE, \LC_CTYPE, \LC_MONETARY] as $constant) {
setlocale(
$constant,
sprintf(
'%s_%s.UTF-8',
Config::string('app.locale'),
strtoupper(Config::string('app.locale'))
)
);
}
Date::setLocale(Config::string('app.locale'));
Date::setFallbackLocale(Config::string('app.fallback_locale'));
}
private function requestMacro(): void
{
Request::macro('pagination', function (int $defaultPageSize = 25): Pagination {
/** @var \Illuminate\Http\Request $this */
if ($this->has('offset') || $this->has('limit')) {
return Pagination::ofOffsetAndLimit(
$this->integer('offset', 0),
$this->integer('limit', $defaultPageSize)
);
}
return Pagination::ofPage(
$this->integer('page', 1),
$this->integer(
'per_page',
// * Default laravel paginator name
$this->integer('pagination', $defaultPageSize)
)
);
});
}
}

View file

@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Models\Product;
use App\Models\Recipe;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Support\InertiaSharedRegistry;
class AuthServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
InertiaSharedRegistry::add('auth.policies', function () {
$localModels = [
Product::class,
Recipe::class,
];
$localPolicies = [];
foreach ($localModels as $model) {
$key = Str::camel(Str::plural(class_basename($model)));
$localPolicies[$key] = [
'viewAny' => Gate::allows('viewAny', $model),
'create' => Gate::allows('create', $model),
'viewTrash' => Gate::allows('viewTrash', $model),
];
}
return $localPolicies;
});
}
}

View file

@ -1,89 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Contracts\Directories\ProductDirectory;
use App\Contracts\Directories\RecipeDirectory;
use App\Models\Product;
use App\Models\Recipe;
use Domain\Models\Entities\Product as DomainProduct;
use Domain\Models\Entities\Recipe as DomainRecipe;
use Domain\Models\ValueObjects\Entities\ProductId;
use Domain\Models\ValueObjects\Entities\RecipeId;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Ramsey\Uuid\Uuid;
use Shared\Helpers\UuidHelper;
class RouteServiceProvider extends ServiceProvider
{
/**
* Define your route model bindings, pattern filters, and other route configuration.
*/
public function boot(): void
{
Route::bind('product', function (string $value) {
if (request()->is('back/*') || request()->is('back') || request()->routeIs('back.*')) {
return Product::withTrashed()
->where('id', $value)
->orWhere('slug', $value)
->firstOrFail();
}
return $this->resolveProduct($value);
});
Route::bind('recipe', function (string $value) {
if (request()->is('back/*') || request()->is('back') || request()->routeIs('back.*')) {
return Recipe::withTrashed()
->where('id', $value)
->orWhere('slug', $value)
->firstOrFail();
}
return $this->resolveRecipe($value);
});
}
private function resolveProduct(string $value): DomainProduct
{
$productDirectory = app(ProductDirectory::class);
if (Str::isUuid($value) && UuidHelper::isV7(Uuid::fromString($value))) {
$product = $productDirectory->find(new ProductId(Uuid::fromString($value)));
}
if (! isset($product)) {
$product = $productDirectory->findBySlug($value);
}
if (! isset($product)) {
throw new ModelNotFoundException('Product not found');
}
return $product;
}
private function resolveRecipe(string $value): DomainRecipe
{
$recipeDirectory = app(RecipeDirectory::class);
if (Str::isUuid($value) && UuidHelper::isV7(Uuid::fromString($value))) {
$recipe = $recipeDirectory->find(new RecipeId(Uuid::fromString($value)));
}
if (! isset($recipe)) {
$recipe = $recipeDirectory->findBySlug($value);
}
if (! isset($recipe)) {
throw new ModelNotFoundException('Recipe not found');
}
return $recipe;
}
}

View file

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Contracts\View\View as ContractsView;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
public function boot(): void
{
if ($this->app->runningInConsole()) {
return;
}
View::composer('*', function (ContractsView $view) {
$view->with('csp_nonce', Vite::cspNonce());
});
}
}

View file

@ -1,59 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Exceptions\AuthServiceException;
use Illuminate\Contracts\Auth\StatefulGuard;
use Uwil\LaravelBackend\Models\User;
class AdminAuthService
{
public function __construct(
private readonly StatefulGuard $guard
) {
}
public function canWithUser(\UnitEnum | string $abilities, mixed $arguments = []): bool
{
return $this->getUser()?->can($abilities, $arguments) ?? false;
}
public function check(): bool
{
return $this->guard->check();
}
public function guest(): bool
{
return ! $this->check();
}
public function getUser(): ?User
{
$user = $this->guard->user();
if ($user && ! ($user instanceof User)) {
throw new \InvalidArgumentException('Auth service needs a backend guard');
}
return $user ? clone $user : null;
}
public function getUserAsMandatory(): User
{
$user = $this->getUser();
if ($user === null) {
throw new AuthServiceException('Authenticated user is required from here');
}
return clone $user;
}
public function getMandatoryUserId(): string
{
return (string) $this->getUserAsMandatory()->id;
}
}

18
artisan
View file

@ -1,18 +0,0 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);

View file

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Foundation\Application;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
health: '/up',
)
->withProviders(require __DIR__ . '/providers.php')
->withMiddleware(require __DIR__ . '/middlewares.php')
->withExceptions(require __DIR__ . '/exceptions.php')->create();

View file

@ -1,2 +0,0 @@
*
!.gitignore

View file

@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Uwil\LaravelCommon\Helpers\InertiaHelper;
return function (Exceptions $exceptions) {
$exceptions->render(function (HttpException $e, Request $request) {
$status = $e->getStatusCode();
if ($request->isAttemptingPrecognition() && $status === 204) {
return response('', 204, $e->getHeaders());
}
return InertiaHelper::errorRender($request, $status);
});
$exceptions->report(function (\Throwable $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
$exceptions->render(function (ValidationException $e, Request $request) {
if ($request->wantsJson() || $request->isAttemptingPrecognition()) {
return response()->json([
'message' => $e->getMessage(),
'errors' => $e->errors(),
], $e->status);
}
$safeInput = Collection::make($request->all())
->except(['password', 'password_confirmation', 'current_password'])
->toArray();
return back()
->withInput($safeInput)
->withErrors($e->errors());
});
};

View file

@ -1,89 +0,0 @@
<?php
declare(strict_types=1);
use App\Http\Middlewares\HtmlSanitizer;
use App\Http\Middlewares\SecureHeaders;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets;
use Illuminate\Http\Request;
use Illuminate\Session\Middleware\StartSession;
use Uwil\AdminSecure\Laravel\Http\Middleware\AdminSecureMiddleware;
use Uwil\LaravelBackend\Middlewares\Authenticate;
use Uwil\LaravelBackend\Middlewares\EncryptCookies;
use Uwil\LaravelBackend\Middlewares\LogoutUnpublishedUsers;
use Uwil\LaravelBackend\Middlewares\RedirectIfAuthenticated;
use Uwil\LaravelCommon\Middlewares\DisableSsr;
use Uwil\LaravelCommon\Middlewares\ForceLocale;
use Uwil\LaravelCommon\Middlewares\HandleInertiaRequests;
use Uwil\LaravelCommon\Middlewares\TrimStrings;
use Uwil\LaravelCommon\Middlewares\TrustHosts;
use Uwil\LaravelCommon\Middlewares\TrustProxies;
$redirectGuests = function (Request $request) {
$logins = [
'backend' => route('back.login'),
'frontend' => '/',
];
foreach ($request->route()?->gatherMiddleware() ?? [] as $middleware) {
if (\is_string($middleware) && str_contains($middleware, 'auth:')) {
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
if ($route) {
return $route;
}
}
}
return $logins['frontend'];
};
$redirectUsers = function (Request $request) {
$logins = [
'backend' => route('back.dashboard.index'),
'frontend' => '/',
];
foreach ($request->route()?->gatherMiddleware() ?? [] as $middleware) {
if (\is_string($middleware) && str_contains($middleware, 'auth:')) {
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
if ($route) {
return $route;
}
}
}
return $logins['frontend'];
};
return function (Middleware $middleware) use ($redirectGuests, $redirectUsers): void {
$middleware->trustProxies(at: '*');
$middleware->redirectGuestsTo($redirectGuests);
$middleware->redirectUsersTo($redirectUsers);
$middleware->web(append: [
ForceLocale::class,
HandleInertiaRequests::class,
AddLinkHeadersForPreloadedAssets::class,
SecureHeaders::class,
TrimStrings::class,
TrustHosts::class,
TrustProxies::class,
LogoutUnpublishedUsers::class,
]);
$middleware->preventRequestForgery(except: [
'/csp-report',
]);
$middleware->group('saml', [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
]);
$middleware->alias([
'auth' => Authenticate::class,
'guest' => RedirectIfAuthenticated::class,
'htmlsanitize' => HtmlSanitizer::class,
'admin-secure' => AdminSecureMiddleware::class,
'disable-ssr' => DisableSsr::class,
]);
};

View file

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
use App\Providers\AppServiceProvider;
use App\Providers\AuthServiceProvider;
use App\Providers\RouteServiceProvider;
use App\Providers\ViewServiceProvider;
use Uwil\AdminSecure\Laravel\AdminSecureServiceProvider;
use Uwil\LaravelBackend\BackendServiceProvider;
use Uwil\LaravelCommon\CommonServiceProvider;
use Uwil\LaravelDynamicPages\DynamicPagesServiceProvider;
use Uwil\LaravelK8S\K8SServiceProvider;
use Uwil\LaravelSchemaOrg\SchemaOrgServiceProvider;
return [
AppServiceProvider::class,
AuthServiceProvider::class,
RouteServiceProvider::class,
ViewServiceProvider::class,
AdminSecureServiceProvider::class,
BackendServiceProvider::class,
CommonServiceProvider::class,
DynamicPagesServiceProvider::class,
K8SServiceProvider::class,
SchemaOrgServiceProvider::class,
];

View file

@ -1 +0,0 @@
export default { extends: ["@uwil/husky-config/commitlint.config.js"] };

View file

@ -1,21 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "resources/sass/common.scss",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View file

@ -1,124 +0,0 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "uwil/laravel-project",
"type": "project",
"description": "UWIL Laravel project",
"keywords": [
"laravel",
"framework"
],
"license": "MIT",
"require": {
"php": "^8.5",
"scaler-tech/laravel-saml2": "^2.5",
"diglactic/laravel-breadcrumbs": "^10.0",
"inertiajs/inertia-laravel": "^3.0",
"laravel/framework": "^13.0",
"laravel/scout": "^11.3",
"laravel/telescope": "^5.7",
"laravel/tinker": "^3.0",
"meilisearch/meilisearch-php": "^1.16",
"monolog/monolog": "^3.0",
"psr/log": "^3.0",
"sentry/sentry-laravel": "^4.25",
"tightenco/ziggy": "^2.5",
"uwil/k8s-laravel": "^3.0",
"uwil/admin-secure-laravel": "^6.0",
"uwil/laravel-dynamic-pages": "^5.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.7",
"fakerphp/faker": "^1.23",
"larastan/larastan": "^3.4",
"laravel-lang/common": "^6.8",
"laravel/breeze": "^2.3.0",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.29",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.9",
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpunit/phpunit": "^12.0",
"slevomat/coding-standard": "^8.22"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"Domain\\": "domain/",
"Infrastructure\\": "infra/",
"Shared\\": "shared/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"@php artisan lang:update",
"@php artisan ide-helper:generate",
"@php artisan ide-helper:models -RW",
"./vendor/bin/pint app/Models"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"platform-check": false
},
"minimum-stability": "stable",
"prefer-stable": true,
"repositories": [
{
"name": "lab.s-web.fr",
"type": "composer",
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
}
]
}

13414
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,112 +0,0 @@
<?php
declare(strict_types=1);
return [
/**
*--------------------------------------------------------------------------
* Application Name
*--------------------------------------------------------------------------
*
* This value is the name of your application, which will be used when the
* framework needs to place the application's name in a notification or
* other UI elements where an application name needs to be displayed.
*/
'name' => env('APP_NAME', 'Laravel'),
/**
*--------------------------------------------------------------------------
* Application Environment
*--------------------------------------------------------------------------
*
* This value determines the "environment" your application is currently
* running in. This may determine how you prefer to configure various
* services the application utilizes. Set this in your ".env" file.
*/
'env' => env('APP_ENV', 'production'),
/**
*--------------------------------------------------------------------------
* Application Debug Mode
*--------------------------------------------------------------------------
*
* When your application is in debug mode, detailed error messages with
* stack traces will be shown on every error that occurs within your
* application. If disabled, a simple generic error page is shown.
*/
'debug' => (bool) env('APP_DEBUG', false),
/**
*--------------------------------------------------------------------------
* Application URL
*--------------------------------------------------------------------------
*
* This URL is used by the console to properly generate URLs when using
* the Artisan command line tool. You should set this to the root of
* the application so that it's available within Artisan commands.
*/
'url' => env('APP_URL', 'http://localhost'),
/**
*--------------------------------------------------------------------------
* Application Timezone
*--------------------------------------------------------------------------
*
* Here you may specify the default timezone for your application, which
* will be used by the PHP date and date-time functions. The timezone
* is set to "UTC" by default as it is suitable for most use cases.
*/
'timezone' => env('APP_TIMEZONE', 'Europe/Paris'),
/**
*--------------------------------------------------------------------------
* Application Locale Configuration
*--------------------------------------------------------------------------
*
* The application locale determines the default locale that will be used
* by Laravel's translation / localization methods. This option can be
* set to any locale for which you plan to have translation strings.
*/
'locale' => env('APP_LOCALE', 'fr'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'fr'),
'faker_locale' => env('APP_FAKER_LOCALE', 'fr_FR'),
/**
*--------------------------------------------------------------------------
* Encryption Key
*--------------------------------------------------------------------------
*
* This key is utilized by Laravel's encryption services and should be set
* to a random, 32 character string to ensure that all encrypted values
* are secure. You should do this prior to deploying the application.
*/
'cipher' => 'AES-256-CBC',
'key' => env('APP_KEY'),
'previous_keys' => [
...array_filter(
explode(',', env('APP_PREVIOUS_KEYS', ''))
),
],
/**
*--------------------------------------------------------------------------
* Maintenance Mode Driver
*--------------------------------------------------------------------------
*
* These configuration options determine the driver used to determine and
* manage Laravel's "maintenance mode" status. The "cache" driver will
* allow maintenance mode to be controlled across multiple machines.
*
* Supported drivers: "file", "cache"
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'database'),
],
];

View file

@ -1,109 +0,0 @@
<?php
declare(strict_types=1);
use Uwil\LaravelBackend\Models\User;
return [
/**
*--------------------------------------------------------------------------
* Authentication Defaults
*--------------------------------------------------------------------------
*
* This option defines the default authentication "guard" and password
* reset "broker" for your application. You may change these values
* as required, but they're a perfect start for most applications.
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/**
*--------------------------------------------------------------------------
* Authentication Guards
*--------------------------------------------------------------------------
*
* Next, you may define every authentication guard for your application.
* Of course, a great default configuration has been defined for you
* which utilizes session storage plus the Eloquent user provider.
*
* All authentication guards have a user provider, which defines how the
* users are actually retrieved out of your database or other storage
* system used by the application. Typically, Eloquent is utilized.
*
* Supported: "session"
*/
'guards' => [
'backend' => [
'driver' => 'session',
'provider' => 'users',
],
'frontend' => [
'driver' => 'session',
'provider' => 'customers',
],
],
/**
*--------------------------------------------------------------------------
* User Providers
*--------------------------------------------------------------------------
*
* All authentication guards have a user provider, which defines how the
* users are actually retrieved out of your database or other storage
* system used by the application. Typically, Eloquent is utilized.
*
* If you have multiple user tables or models you may configure multiple
* providers to represent the model / table. These providers may then
* be assigned to any extra authentication guards you have defined.
*
* Supported: "database", "eloquent"
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', User::class),
],
],
/**
*--------------------------------------------------------------------------
* Resetting Passwords
*--------------------------------------------------------------------------
*
* These configuration options specify the behavior of Laravel's password
* reset functionality, including the table utilized for token storage
* and the user provider that is invoked to actually retrieve users.
*
* The expiry time is the number of minutes that each reset token will be
* considered valid. This security feature keeps tokens short-lived so
* they have less time to be guessed. You may change this as needed.
*
* The throttle setting is the number of seconds a user must wait before
* generating more password reset tokens. This prevents the user from
* quickly generating a very large amount of password reset tokens.
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
/**
*--------------------------------------------------------------------------
* Password Confirmation Timeout
*--------------------------------------------------------------------------
*
* Here you may define the amount of seconds before a password confirmation
* window expires and users are asked to re-enter their password via the
* confirmation screen. By default, the timeout lasts for three hours.
*/
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
];

View file

@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
use Diglactic\Breadcrumbs\Generator;
use Diglactic\Breadcrumbs\Manager;
return [
/**
*--------------------------------------------------------------------------
* View Name
*--------------------------------------------------------------------------
*
* Choose a view to display when Breadcrumbs::render() is called.
* Built in templates are:
*
* - 'breadcrumbs::bootstrap5' - Bootstrap 5
* - 'breadcrumbs::bootstrap4' - Bootstrap 4
* - 'breadcrumbs::bulma' - Bulma
* - 'breadcrumbs::foundation6' - Foundation 6
* - 'breadcrumbs::json-ld' - JSON-LD Structured Data
* - 'breadcrumbs::materialize' - Materialize
* - 'breadcrumbs::tailwind' - Tailwind CSS
* - 'breadcrumbs::uikit' - UIkit
*
* Or a custom view, e.g. '_partials/breadcrumbs'.
*/
'view' => null,
/**
*--------------------------------------------------------------------------
* Breadcrumbs File(s)
*--------------------------------------------------------------------------
*
* The file(s) where breadcrumbs are defined. e.g.
*
* - base_path('routes/breadcrumbs.php')
* - glob(base_path('breadcrumbs/*.php'))
*/
'files' => array_filter([
...glob(base_path('routes/breadcrumbs/*.php')) ?: [],
]),
/**
*--------------------------------------------------------------------------
* Exceptions
*--------------------------------------------------------------------------
*
* Determine when to throw an exception.
*/
// * When route-bound breadcrumbs are used but the current route doesn't have a name (UnnamedRouteException)
'unnamed-route-exception' => true,
// * When route-bound breadcrumbs are used and the matching breadcrumb doesn't exist (InvalidBreadcrumbException)
'missing-route-bound-breadcrumb-exception' => true,
// * When a named breadcrumb is used but doesn't exist (InvalidBreadcrumbException)
'invalid-named-breadcrumb-exception' => true,
/**
*--------------------------------------------------------------------------
* Classes
*--------------------------------------------------------------------------
*
* Subclass the default classes for more advanced customisations.
*/
// * Manager
'manager-class' => Manager::class,
// * Generator
'generator-class' => Generator::class,
];

View file

@ -1,117 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Support\Str;
return [
/*
*--------------------------------------------------------------------------
* Default Cache Store
*--------------------------------------------------------------------------
*
* This option controls the default cache store that will be used by the
* framework. This connection is utilized if another isn't explicitly
* specified when running a cache operation inside the application.
*
*/
'default' => env('CACHE_STORE', 'database'),
/*
*--------------------------------------------------------------------------
* Cache Stores
*--------------------------------------------------------------------------
*
* Here you may define all of the cache "stores" for your application as
* well as their drivers. You may even define multiple stores for the
* same cache driver to group types of items stored in your caches.
*
* Supported drivers: "array", "database", "file", "memcached",
* "redis", "dynamodb", "octane",
* "failover", "null"
*
*/
'stores' => [
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
'lock_path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],
],
/*
*--------------------------------------------------------------------------
* Cache Key Prefix
*--------------------------------------------------------------------------
*
* When utilizing the APC, database, memcached, Redis, and DynamoDB cache
* stores, there might be other applications using the same cache. For
* that reason, you may prefix every cache key to avoid collisions.
*
*/
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')) . '-cache-'),
];

View file

@ -1,21 +0,0 @@
<?php
declare(strict_types=1);
return [
'google' => [
'recaptcha' => [
'public' => env('GOOGLE_RECAPTCHA_PUBLIC'),
'private' => env('GOOGLE_RECAPTCHA_PRIVATE'),
'version' => env('GOOGLE_RECAPTCHA_VERSION', '3') === '3' ? true : false,
'bypassKey' => 'bypass',
],
'analytics' => [
'id' => env('GOOGLE_ANALYTICS_ID'),
],
],
'matomo' => [
'site_id' => env('MATOMO_SITE_ID'),
],
];

View file

@ -1,190 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Support\Str;
use Pdo\Mysql;
return [
/*
*--------------------------------------------------------------------------
* Default Database Connection Name
*--------------------------------------------------------------------------
*
* Here you may specify which of the database connections below you wish
* to use as your default connection for database operations. This is
* the connection which will be utilized unless another connection
* is explicitly specified when you execute a query / statement.
*
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
*--------------------------------------------------------------------------
* Database Connections
*--------------------------------------------------------------------------
*
* Below are all of the database connections defined for your application.
* An example configuration is provided for each database system which
* is supported by Laravel. You're free to add / remove connections.
*
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500
? Mysql::ATTR_SSL_CA
: \PDO::MYSQL_ATTR_SSL_CA
) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
(PHP_VERSION_ID >= 80500
? Mysql::ATTR_SSL_CA
: \PDO::MYSQL_ATTR_SSL_CA
) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSLMODE', 'prefer'),
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
],
],
/*
*--------------------------------------------------------------------------
* Migration Repository Table
*--------------------------------------------------------------------------
*
* This table keeps track of all the migrations that have already run for
* your application. Using this information, we can determine which of
* the migrations on disk haven't actually been run on the database.
*
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
*--------------------------------------------------------------------------
* Redis Databases
*--------------------------------------------------------------------------
*
* Redis is an open source, fast, and advanced key-value store that also
* provides a richer body of commands than a typical key-value system
* such as Memcached. You may define your connection settings here.
*
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')) . '-database-'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
'max_retries' => env('REDIS_MAX_RETRIES', 3),
'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
],
];

View file

@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
return [
/**
*--------------------------------------------------------------------------
* Default Filesystem Disk
*--------------------------------------------------------------------------
*
* Here you may specify the default filesystem disk that should be used
* by the framework. The "local" disk, as well as a variety of cloud
* based disks are available to your application for file storage.
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/**
*--------------------------------------------------------------------------
* Filesystem Disks
*--------------------------------------------------------------------------
*
* Below you may configure as many filesystem disks as necessary, and you
* may even configure multiple disks for the same driver. Examples for
* most supported storage drivers are configured here for reference.
*
* Supported drivers: "local", "ftp", "sftp", "s3"
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'serve' => true,
'throw' => false,
'report' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
],
/**
*--------------------------------------------------------------------------
* Symbolic Links
*--------------------------------------------------------------------------
*
* Here you may configure the symbolic links that will be created when the
* `storage:link` Artisan command is executed. The array keys should be
* the locations of the links and the values should be their targets.
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

View file

@ -1,379 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Session\Store;
return [
/*
|--------------------------------------------------------------------------
| Filename
|--------------------------------------------------------------------------
|
| The default filename.
|
*/
'filename' => '_ide_helper.php',
/*
|--------------------------------------------------------------------------
| Models filename
|--------------------------------------------------------------------------
|
| The default filename for the models helper file.
|
*/
'models_filename' => '_ide_helper_models.php',
/*
|--------------------------------------------------------------------------
| PhpStorm meta filename
|--------------------------------------------------------------------------
|
| PhpStorm also supports the directory `.phpstorm.meta.php/` with arbitrary
| files in it, should you need additional files for your project; e.g.
| `.phpstorm.meta.php/laravel_ide_Helper.php'.
|
*/
'meta_filename' => '.phpstorm.meta.php',
/*
|--------------------------------------------------------------------------
| Fluent helpers
|--------------------------------------------------------------------------
|
| Set to true to generate commonly used Fluent methods.
|
*/
'include_fluent' => false,
/*
|--------------------------------------------------------------------------
| Write model query methods
|--------------------------------------------------------------------------
|
| Set to false to disable generated docs for the 'query()', 'newQuery()' and 'newModelQuery()' methods.
|
*/
'write_query_methods' => false,
/*
|--------------------------------------------------------------------------
| Write model magic methods
|--------------------------------------------------------------------------
|
| Set to false to disable write magic methods of model.
|
*/
'write_model_magic_where' => false,
/*
|--------------------------------------------------------------------------
| Write model external Eloquent builder methods
|--------------------------------------------------------------------------
|
| Set to false to disable write external Eloquent builder methods.
|
*/
'write_model_external_builder_methods' => false,
/*
|--------------------------------------------------------------------------
| Write model relation count and exists properties
|--------------------------------------------------------------------------
|
| Set to false to disable writing of relation count and exists properties
| to model DocBlocks.
|
*/
'write_model_relation_count_properties' => false,
'write_model_relation_exists_properties' => false,
/*
|--------------------------------------------------------------------------
| Write Eloquent model mixins
|--------------------------------------------------------------------------
|
| This will add the necessary DocBlock mixins to the model class
| contained in the Laravel framework. This helps the IDE with
| auto-completion.
|
| Please be aware that this setting changes a file within the /vendor directory.
|
*/
'write_eloquent_model_mixins' => false,
/*
|--------------------------------------------------------------------------
| Helper files to include
|--------------------------------------------------------------------------
|
| Include helper files. By default not included, but can be toggled with the
| -- helpers (-H) option. Extra helper files can be included.
|
*/
'include_helpers' => false,
'helper_files' => [
base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php',
base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php',
],
/*
|--------------------------------------------------------------------------
| Model locations to include
|--------------------------------------------------------------------------
|
| Define in which directories the ide-helper:models command should look
| for models.
|
| glob patterns are supported to easier reach models in sub-directories,
| e.g. `app/Services/* /Models` (without the space).
|
*/
'model_locations' => [
'app',
],
/*
|--------------------------------------------------------------------------
| Models to ignore
|--------------------------------------------------------------------------
|
| Define which models should be ignored.
|
*/
'ignored_models' => [],
/*
|--------------------------------------------------------------------------
| Models hooks
|--------------------------------------------------------------------------
|
| Define which hook classes you want to run for models to add custom information.
|
| Hooks should implement Barryvdh\LaravelIdeHelper\Contracts\ModelHookInterface.
|
*/
'model_hooks' => [
// App\Support\IdeHelper\MyModelHook::class
],
/*
|--------------------------------------------------------------------------
| Extra classes
|--------------------------------------------------------------------------
|
| These implementations are not really extended, but called with magic functions.
|
*/
'extra' => [
'Eloquent' => [Builder::class, \Illuminate\Database\Query\Builder::class],
'Session' => [Store::class],
],
'magic' => [],
/*
|--------------------------------------------------------------------------
| Interface implementations
|--------------------------------------------------------------------------
|
| These interfaces will be replaced with the implementing class. Some interfaces
| are detected by the helpers, others can be listed below.
|
*/
'interfaces' => [
// App\MyInterface::class => App\MyImplementation::class,
],
/*
|--------------------------------------------------------------------------
| Support for camel cased models
|--------------------------------------------------------------------------
|
| There are some Laravel packages (such as Eloquence) that allow for accessing
| Eloquent model properties via camel case, instead of snake case.
|
| Enabling this option will support these packages by saving all model
| properties as camel case, instead of snake case.
|
| For example, normally you would see this:
|
| * @property \Illuminate\Support\Carbon $created_at
| * @property \Illuminate\Support\Carbon $updated_at
|
| With this enabled, the properties will be this:
|
| * @property \Illuminate\Support\Carbon $createdAt
| * @property \Illuminate\Support\Carbon $updatedAt
|
| Note, it is currently an all-or-nothing option.
|
*/
'model_camel_case_properties' => false,
/*
|--------------------------------------------------------------------------
| Property casts
|--------------------------------------------------------------------------
|
| Cast the given "real type" to the given "type".
|
*/
'type_overrides' => [
'integer' => 'int',
'boolean' => 'bool',
],
/*
|--------------------------------------------------------------------------
| Include DocBlocks from classes
|--------------------------------------------------------------------------
|
| Include DocBlocks from classes to allow additional code inspection for
| magic methods and properties.
|
*/
'include_class_docblocks' => false,
/*
|--------------------------------------------------------------------------
| Force FQN usage
|--------------------------------------------------------------------------
|
| Use the fully qualified (class) name in DocBlocks,
| even if the class exists in the same namespace
| or there is an import (use className) of the class.
|
*/
'force_fqn' => false,
/*
|--------------------------------------------------------------------------
| Use generics syntax
|--------------------------------------------------------------------------
|
| Use generics syntax within DocBlocks,
| e.g. `Collection<User>` instead of `Collection|User[]`.
|
*/
'use_generics_annotations' => true,
/*
|--------------------------------------------------------------------------
| Default return types for macros
|--------------------------------------------------------------------------
|
| Define default return types for macros without explicit return types.
| e.g. `\Illuminate\Database\Query\Builder::class => 'static'`,
| `\Illuminate\Support\Str::class => 'string'`
|
*/
'macro_default_return_types' => [
Factory::class => PendingRequest::class,
],
/*
|--------------------------------------------------------------------------
| Additional relation types
|--------------------------------------------------------------------------
|
| Sometimes it's needed to create custom relation types. The key of the array
| is the relationship method name. The value of the array is the fully-qualified
| class name of the relationship, e.g. `'relationName' => RelationShipClass::class`.
|
*/
'additional_relation_types' => [],
/*
|--------------------------------------------------------------------------
| Additional relation return types
|--------------------------------------------------------------------------
|
| When using custom relation types its possible for the class name to not contain
| the proper return type of the relation. The key of the array is the relationship
| method name. The value of the array is the return type of the relation ('many'
| or 'morphTo').
| e.g. `'relationName' => 'many'`.
|
*/
'additional_relation_return_types' => [],
/*
|--------------------------------------------------------------------------
| Enforce nullable Eloquent relationships on not null columns
|--------------------------------------------------------------------------
|
| When set to true (default), this option enforces nullable Eloquent relationships.
| However, in cases where the application logic ensures the presence of related
| records it may be desirable to set this option to false to avoid unwanted null warnings.
|
| Default: true
| A not null column with no foreign key constraint will have a "nullable" relationship.
| * @property int $not_null_column_with_no_foreign_key_constraint
| * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint
|
| Option: false
| A not null column with no foreign key constraint will have a "not nullable" relationship.
| * @property int $not_null_column_with_no_foreign_key_constraint
| * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint
|
*/
'enforce_nullable_relationships' => true,
/*
|--------------------------------------------------------------------------
| Make soft deletable relations nullable
|--------------------------------------------------------------------------
|
| When set to true (default), relationships to models using SoftDeletes trait
| will be marked as nullable. This is because soft-deleted records are excluded
| from queries by default, meaning even non-nullable foreign keys can return
| null when the related model is soft-deleted.
|
| Default: true
| A relationship to a soft-deletable model will include |null in the type:
| * @property-read Team|null $team
|
| Option: false
| A relationship to a soft-deletable model will NOT include |null (unless
| nullable for other reasons such as nullable foreign key column):
| * @property-read Team $team
|
*/
'soft_deletes_force_nullable' => true,
/*
|--------------------------------------------------------------------------
| Run artisan commands after migrations to generate model helpers
|--------------------------------------------------------------------------
|
| The specified commands should run after migrations are finished running.
|
*/
'post_migrate' => [
// 'ide-helper:models --nowrite',
],
];

View file

@ -1,62 +0,0 @@
<?php
declare(strict_types=1);
return [
/**
*--------------------------------------------------------------------------
* Server Side Rendering
*--------------------------------------------------------------------------
*
* These options configures if and how Inertia uses Server Side Rendering
* to pre-render the initial visits made to your application's pages.
*
* You can specify a custom SSR bundle path, or omit it to let Inertia
* try and automatically detect it for you.
*
* Do note that enabling these options will NOT automatically make SSR work,
* as a separate rendering service needs to be available. To learn more,
* please visit https://inertiajs.com/server-side-rendering
*/
'ssr' => [
'enabled' => false,
'url' => env('APP_URL', 'https://localhost'),
],
/**
*--------------------------------------------------------------------------
* Testing
*--------------------------------------------------------------------------
*
* The values described here are used to locate Inertia components on the
* filesystem. For instance, when using `assertInertia`, the assertion
* attempts to locate the component as a file relative to any of the
* paths AND with any of the extensions specified here.
*/
'testing' => [
'ensure_pages_exist' => true,
'page_paths' => [
resource_path('ts/front/Pages'),
],
'page_extensions' => [
'js',
'jsx',
'svelte',
'ts',
'tsx',
'vue',
],
],
'history' => [
'encrypt' => false,
],
];

View file

@ -1,356 +0,0 @@
<?php
declare(strict_types=1);
return [
// Uncomment the languages that your site supports - or add new ones.
// These are sorted by the native name, which is the order you might show them in a language selector.
// Regional languages are sorted by their base language, so "British English" sorts as "English, British".
// Original config here: https://github.com/mcamara/laravel-localization/blob/master/src/config/config.php.
// @phpcs:disable Squiz.Commenting.InlineComment.NoSpaceBefore, Generic.Files.LineLength.TooLong, Squiz.PHP.CommentedOutCode.Found
'supportedLocales' => [
// 'ace' => ['name' => 'Achinese', 'script' => 'Latn', 'native' => 'Aceh', 'regional' => ''],
// 'af' => ['name' => 'Afrikaans', 'script' => 'Latn', 'native' => 'Afrikaans', 'regional' => 'af_ZA'],
// 'agq' => ['name' => 'Aghem', 'script' => 'Latn', 'native' => 'Aghem', 'regional' => ''],
// 'ak' => ['name' => 'Akan', 'script' => 'Latn', 'native' => 'Akan', 'regional' => 'ak_GH'],
// 'an' => ['name' => 'Aragonese', 'script' => 'Latn', 'native' => 'aragonés', 'regional' => 'an_ES'],
// 'cch' => ['name' => 'Atsam', 'script' => 'Latn', 'native' => 'Atsam', 'regional' => ''],
// 'gn' => ['name' => 'Guaraní', 'script' => 'Latn', 'native' => 'Avañe’ẽ', 'regional' => ''],
// 'ae' => ['name' => 'Avestan', 'script' => 'Latn', 'native' => 'avesta', 'regional' => ''],
// 'ay' => ['name' => 'Aymara', 'script' => 'Latn', 'native' => 'aymar aru', 'regional' => 'ay_PE'],
// 'az' => ['name' => 'Azerbaijani (Latin)', 'script' => 'Latn', 'native' => 'azərbaycanca', 'regional' => 'az_AZ'],
// 'id' => ['name' => 'Indonesian', 'script' => 'Latn', 'native' => 'Bahasa Indonesia', 'regional' => 'id_ID'],
// 'ms' => ['name' => 'Malay', 'script' => 'Latn', 'native' => 'Bahasa Melayu', 'regional' => 'ms_MY'],
// 'bm' => ['name' => 'Bambara', 'script' => 'Latn', 'native' => 'bamanakan', 'regional' => ''],
// 'jv' => ['name' => 'Javanese (Latin)', 'script' => 'Latn', 'native' => 'Basa Jawa', 'regional' => ''],
// 'su' => ['name' => 'Sundanese', 'script' => 'Latn', 'native' => 'Basa Sunda', 'regional' => ''],
// 'bh' => ['name' => 'Bihari', 'script' => 'Latn', 'native' => 'Bihari', 'regional' => ''],
// 'bi' => ['name' => 'Bislama', 'script' => 'Latn', 'native' => 'Bislama', 'regional' => ''],
// 'nb' => ['name' => 'Norwegian Bokmål', 'script' => 'Latn', 'native' => 'Bokmål', 'regional' => 'nb_NO'],
// 'bs' => ['name' => 'Bosnian', 'script' => 'Latn', 'native' => 'bosanski', 'regional' => 'bs_BA'],
// 'br' => ['name' => 'Breton', 'script' => 'Latn', 'native' => 'brezhoneg', 'regional' => 'br_FR'],
// 'ca' => ['name' => 'Catalan', 'script' => 'Latn', 'native' => 'català', 'regional' => 'ca_ES'],
// 'ch' => ['name' => 'Chamorro', 'script' => 'Latn', 'native' => 'Chamoru', 'regional' => ''],
// 'ny' => ['name' => 'Chewa', 'script' => 'Latn', 'native' => 'chiCheŵa', 'regional' => ''],
// 'kde' => ['name' => 'Makonde', 'script' => 'Latn', 'native' => 'Chimakonde', 'regional' => ''],
// 'sn' => ['name' => 'Shona', 'script' => 'Latn', 'native' => 'chiShona', 'regional' => ''],
// 'co' => ['name' => 'Corsican', 'script' => 'Latn', 'native' => 'corsu', 'regional' => ''],
// 'cy' => ['name' => 'Welsh', 'script' => 'Latn', 'native' => 'Cymraeg', 'regional' => 'cy_GB'],
// 'da' => ['name' => 'Danish', 'script' => 'Latn', 'native' => 'dansk', 'regional' => 'da_DK'],
// 'se' => ['name' => 'Northern Sami', 'script' => 'Latn', 'native' => 'davvisámegiella', 'regional' => 'se_NO'],
// 'de' => ['name' => 'German', 'script' => 'Latn', 'native' => 'Deutsch', 'regional' => 'de_DE'],
// 'luo' => ['name' => 'Luo', 'script' => 'Latn', 'native' => 'Dholuo', 'regional' => ''],
// 'nv' => ['name' => 'Navajo', 'script' => 'Latn', 'native' => 'Diné bizaad', 'regional' => ''],
// 'dua' => ['name' => 'Duala', 'script' => 'Latn', 'native' => 'duálá', 'regional' => ''],
// 'et' => ['name' => 'Estonian', 'script' => 'Latn', 'native' => 'eesti', 'regional' => 'et_EE'],
// 'na' => ['name' => 'Nauru', 'script' => 'Latn', 'native' => 'Ekakairũ Naoero', 'regional' => ''],
// 'guz' => ['name' => 'Ekegusii', 'script' => 'Latn', 'native' => 'Ekegusii', 'regional' => ''],
'en' => ['name' => 'English', 'script' => 'Latn', 'native' => 'English', 'regional' => 'en_GB'],
// 'en-AU' => ['name' => 'Australian English', 'script' => 'Latn', 'native' => 'Australian English', 'regional' => 'en_AU'],
// 'en-GB' => ['name' => 'British English', 'script' => 'Latn', 'native' => 'British English', 'regional' => 'en_GB'],
// 'en-CA' => ['name' => 'Canadian English', 'script' => 'Latn', 'native' => 'Canadian English', 'regional' => 'en_CA'],
// 'en-US' => ['name' => 'U.S. English', 'script' => 'Latn', 'native' => 'U.S. English', 'regional' => 'en_US'],
// 'es' => ['name' => 'Spanish', 'script' => 'Latn', 'native' => 'español', 'regional' => 'es_ES'],
// 'eo' => ['name' => 'Esperanto', 'script' => 'Latn', 'native' => 'esperanto', 'regional' => ''],
// 'eu' => ['name' => 'Basque', 'script' => 'Latn', 'native' => 'euskara', 'regional' => 'eu_ES'],
// 'ewo' => ['name' => 'Ewondo', 'script' => 'Latn', 'native' => 'ewondo', 'regional' => ''],
// 'ee' => ['name' => 'Ewe', 'script' => 'Latn', 'native' => 'eʋegbe', 'regional' => ''],
// 'fil' => ['name' => 'Filipino', 'script' => 'Latn', 'native' => 'Filipino', 'regional' => 'fil_PH'],
'fr' => ['name' => 'French', 'script' => 'Latn', 'native' => 'français', 'regional' => 'fr_FR'],
// 'fr-CA' => ['name' => 'Canadian French', 'script' => 'Latn', 'native' => 'français canadien', 'regional' => 'fr_CA'],
// 'fy' => ['name' => 'Western Frisian', 'script' => 'Latn', 'native' => 'frysk', 'regional' => 'fy_DE'],
// 'fur' => ['name' => 'Friulian', 'script' => 'Latn', 'native' => 'furlan', 'regional' => 'fur_IT'],
// 'fo' => ['name' => 'Faroese', 'script' => 'Latn', 'native' => 'føroyskt', 'regional' => 'fo_FO'],
// 'gaa' => ['name' => 'Ga', 'script' => 'Latn', 'native' => 'Ga', 'regional' => ''],
// 'ga' => ['name' => 'Irish', 'script' => 'Latn', 'native' => 'Gaeilge', 'regional' => 'ga_IE'],
// 'gv' => ['name' => 'Manx', 'script' => 'Latn', 'native' => 'Gaelg', 'regional' => 'gv_GB'],
// 'sm' => ['name' => 'Samoan', 'script' => 'Latn', 'native' => 'Gagana fa’a Sāmoa', 'regional' => ''],
// 'gl' => ['name' => 'Galician', 'script' => 'Latn', 'native' => 'galego', 'regional' => 'gl_ES'],
// 'ki' => ['name' => 'Kikuyu', 'script' => 'Latn', 'native' => 'Gikuyu', 'regional' => ''],
// 'gd' => ['name' => 'Scottish Gaelic', 'script' => 'Latn', 'native' => 'Gàidhlig', 'regional' => 'gd_GB'],
// 'ha' => ['name' => 'Hausa', 'script' => 'Latn', 'native' => 'Hausa', 'regional' => 'ha_NG'],
// 'bez' => ['name' => 'Bena', 'script' => 'Latn', 'native' => 'Hibena', 'regional' => ''],
// 'ho' => ['name' => 'Hiri Motu', 'script' => 'Latn', 'native' => 'Hiri Motu', 'regional' => ''],
// 'hr' => ['name' => 'Croatian', 'script' => 'Latn', 'native' => 'hrvatski', 'regional' => 'hr_HR'],
// 'bem' => ['name' => 'Bemba', 'script' => 'Latn', 'native' => 'Ichibemba', 'regional' => 'bem_ZM'],
// 'io' => ['name' => 'Ido', 'script' => 'Latn', 'native' => 'Ido', 'regional' => ''],
// 'ig' => ['name' => 'Igbo', 'script' => 'Latn', 'native' => 'Igbo', 'regional' => 'ig_NG'],
// 'rn' => ['name' => 'Rundi', 'script' => 'Latn', 'native' => 'Ikirundi', 'regional' => ''],
// 'ia' => ['name' => 'Interlingua', 'script' => 'Latn', 'native' => 'interlingua', 'regional' => 'ia_FR'],
// 'iu-Latn' => ['name' => 'Inuktitut (Latin)', 'script' => 'Latn', 'native' => 'Inuktitut', 'regional' => 'iu_CA'],
// 'sbp' => ['name' => 'Sileibi', 'script' => 'Latn', 'native' => 'Ishisangu', 'regional' => ''],
// 'nd' => ['name' => 'North Ndebele', 'script' => 'Latn', 'native' => 'isiNdebele', 'regional' => ''],
// 'nr' => ['name' => 'South Ndebele', 'script' => 'Latn', 'native' => 'isiNdebele', 'regional' => 'nr_ZA'],
// 'xh' => ['name' => 'Xhosa', 'script' => 'Latn', 'native' => 'isiXhosa', 'regional' => 'xh_ZA'],
// 'zu' => ['name' => 'Zulu', 'script' => 'Latn', 'native' => 'isiZulu', 'regional' => 'zu_ZA'],
// 'it' => ['name' => 'Italian', 'script' => 'Latn', 'native' => 'italiano', 'regional' => 'it_IT'],
// 'ik' => ['name' => 'Inupiaq', 'script' => 'Latn', 'native' => 'Iñupiaq', 'regional' => 'ik_CA'],
// 'dyo' => ['name' => 'Jola-Fonyi', 'script' => 'Latn', 'native' => 'joola', 'regional' => ''],
// 'kea' => ['name' => 'Kabuverdianu', 'script' => 'Latn', 'native' => 'kabuverdianu', 'regional' => ''],
// 'kaj' => ['name' => 'Jju', 'script' => 'Latn', 'native' => 'Kaje', 'regional' => ''],
// 'mh' => ['name' => 'Marshallese', 'script' => 'Latn', 'native' => 'Kajin M̧ajeļ', 'regional' => 'mh_MH'],
// 'kl' => ['name' => 'Kalaallisut', 'script' => 'Latn', 'native' => 'kalaallisut', 'regional' => 'kl_GL'],
// 'kln' => ['name' => 'Kalenjin', 'script' => 'Latn', 'native' => 'Kalenjin', 'regional' => ''],
// 'kr' => ['name' => 'Kanuri', 'script' => 'Latn', 'native' => 'Kanuri', 'regional' => ''],
// 'kcg' => ['name' => 'Tyap', 'script' => 'Latn', 'native' => 'Katab', 'regional' => ''],
// 'kw' => ['name' => 'Cornish', 'script' => 'Latn', 'native' => 'kernewek', 'regional' => 'kw_GB'],
// 'naq' => ['name' => 'Nama', 'script' => 'Latn', 'native' => 'Khoekhoegowab', 'regional' => ''],
// 'rof' => ['name' => 'Rombo', 'script' => 'Latn', 'native' => 'Kihorombo', 'regional' => ''],
// 'kam' => ['name' => 'Kamba', 'script' => 'Latn', 'native' => 'Kikamba', 'regional' => ''],
// 'kg' => ['name' => 'Kongo', 'script' => 'Latn', 'native' => 'Kikongo', 'regional' => ''],
// 'jmc' => ['name' => 'Machame', 'script' => 'Latn', 'native' => 'Kimachame', 'regional' => ''],
// 'rw' => ['name' => 'Kinyarwanda', 'script' => 'Latn', 'native' => 'Kinyarwanda', 'regional' => 'rw_RW'],
// 'asa' => ['name' => 'Kipare', 'script' => 'Latn', 'native' => 'Kipare', 'regional' => ''],
// 'rwk' => ['name' => 'Rwa', 'script' => 'Latn', 'native' => 'Kiruwa', 'regional' => ''],
// 'saq' => ['name' => 'Samburu', 'script' => 'Latn', 'native' => 'Kisampur', 'regional' => ''],
// 'ksb' => ['name' => 'Shambala', 'script' => 'Latn', 'native' => 'Kishambaa', 'regional' => ''],
// 'swc' => ['name' => 'Congo Swahili', 'script' => 'Latn', 'native' => 'Kiswahili ya Kongo', 'regional' => ''],
// 'sw' => ['name' => 'Swahili', 'script' => 'Latn', 'native' => 'Kiswahili', 'regional' => 'sw_KE'],
// 'dav' => ['name' => 'Dawida', 'script' => 'Latn', 'native' => 'Kitaita', 'regional' => ''],
// 'teo' => ['name' => 'Teso', 'script' => 'Latn', 'native' => 'Kiteso', 'regional' => ''],
// 'khq' => ['name' => 'Koyra Chiini', 'script' => 'Latn', 'native' => 'Koyra ciini', 'regional' => ''],
// 'ses' => ['name' => 'Songhay', 'script' => 'Latn', 'native' => 'Koyraboro senni', 'regional' => ''],
// 'mfe' => ['name' => 'Morisyen', 'script' => 'Latn', 'native' => 'kreol morisien', 'regional' => ''],
// 'ht' => ['name' => 'Haitian', 'script' => 'Latn', 'native' => 'Kreyòl ayisyen', 'regional' => 'ht_HT'],
// 'kj' => ['name' => 'Kuanyama', 'script' => 'Latn', 'native' => 'Kwanyama', 'regional' => ''],
// 'ksh' => ['name' => 'Kölsch', 'script' => 'Latn', 'native' => 'Kölsch', 'regional' => ''],
// 'ebu' => ['name' => 'Kiembu', 'script' => 'Latn', 'native' => 'Kĩembu', 'regional' => ''],
// 'mer' => ['name' => 'Kimîîru', 'script' => 'Latn', 'native' => 'Kĩmĩrũ', 'regional' => ''],
// 'lag' => ['name' => 'Langi', 'script' => 'Latn', 'native' => 'Kɨlaangi', 'regional' => ''],
// 'lah' => ['name' => 'Lahnda', 'script' => 'Latn', 'native' => 'Lahnda', 'regional' => ''],
// 'la' => ['name' => 'Latin', 'script' => 'Latn', 'native' => 'latine', 'regional' => ''],
// 'lv' => ['name' => 'Latvian', 'script' => 'Latn', 'native' => 'latviešu', 'regional' => 'lv_LV'],
// 'to' => ['name' => 'Tongan', 'script' => 'Latn', 'native' => 'lea fakatonga', 'regional' => ''],
// 'lt' => ['name' => 'Lithuanian', 'script' => 'Latn', 'native' => 'lietuvių', 'regional' => 'lt_LT'],
// 'li' => ['name' => 'Limburgish', 'script' => 'Latn', 'native' => 'Limburgs', 'regional' => 'li_BE'],
// 'ln' => ['name' => 'Lingala', 'script' => 'Latn', 'native' => 'lingála', 'regional' => ''],
// 'lg' => ['name' => 'Ganda', 'script' => 'Latn', 'native' => 'Luganda', 'regional' => 'lg_UG'],
// 'luy' => ['name' => 'Oluluyia', 'script' => 'Latn', 'native' => 'Luluhia', 'regional' => ''],
// 'lb' => ['name' => 'Luxembourgish', 'script' => 'Latn', 'native' => 'Lëtzebuergesch', 'regional' => 'lb_LU'],
// 'hu' => ['name' => 'Hungarian', 'script' => 'Latn', 'native' => 'magyar', 'regional' => 'hu_HU'],
// 'mgh' => ['name' => 'Makhuwa-Meetto', 'script' => 'Latn', 'native' => 'Makua', 'regional' => ''],
// 'mg' => ['name' => 'Malagasy', 'script' => 'Latn', 'native' => 'Malagasy', 'regional' => 'mg_MG'],
// 'mt' => ['name' => 'Maltese', 'script' => 'Latn', 'native' => 'Malti', 'regional' => 'mt_MT'],
// 'mtr' => ['name' => 'Mewari', 'script' => 'Latn', 'native' => 'Mewari', 'regional' => ''],
// 'mua' => ['name' => 'Mundang', 'script' => 'Latn', 'native' => 'Mundang', 'regional' => ''],
// 'mi' => ['name' => 'Māori', 'script' => 'Latn', 'native' => 'Māori', 'regional' => 'mi_NZ'],
// 'nl' => ['name' => 'Dutch', 'script' => 'Latn', 'native' => 'Nederlands', 'regional' => 'nl_NL'],
// 'nmg' => ['name' => 'Kwasio', 'script' => 'Latn', 'native' => 'ngumba', 'regional' => ''],
// 'yav' => ['name' => 'Yangben', 'script' => 'Latn', 'native' => 'nuasue', 'regional' => ''],
// 'nn' => ['name' => 'Norwegian Nynorsk', 'script' => 'Latn', 'native' => 'nynorsk', 'regional' => 'nn_NO'],
// 'oc' => ['name' => 'Occitan', 'script' => 'Latn', 'native' => 'occitan', 'regional' => 'oc_FR'],
// 'ang' => ['name' => 'Old English', 'script' => 'Runr', 'native' => 'Old English', 'regional' => ''],
// 'xog' => ['name' => 'Soga', 'script' => 'Latn', 'native' => 'Olusoga', 'regional' => ''],
// 'om' => ['name' => 'Oromo', 'script' => 'Latn', 'native' => 'Oromoo', 'regional' => 'om_ET'],
// 'ng' => ['name' => 'Ndonga', 'script' => 'Latn', 'native' => 'OshiNdonga', 'regional' => ''],
// 'hz' => ['name' => 'Herero', 'script' => 'Latn', 'native' => 'Otjiherero', 'regional' => ''],
// 'uz-Latn' => ['name' => 'Uzbek (Latin)', 'script' => 'Latn', 'native' => 'oʼzbekcha', 'regional' => 'uz_UZ'],
// 'nds' => ['name' => 'Low German', 'script' => 'Latn', 'native' => 'Plattdüütsch', 'regional' => 'nds_DE'],
// 'pl' => ['name' => 'Polish', 'script' => 'Latn', 'native' => 'polski', 'regional' => 'pl_PL'],
// 'pt' => ['name' => 'Portuguese', 'script' => 'Latn', 'native' => 'português', 'regional' => 'pt_PT'],
// 'pt-BR' => ['name' => 'Brazilian Portuguese', 'script' => 'Latn', 'native' => 'português do Brasil', 'regional' => 'pt_BR'],
// 'ff' => ['name' => 'Fulah', 'script' => 'Latn', 'native' => 'Pulaar', 'regional' => 'ff_SN'],
// 'pi' => ['name' => 'Pahari-Potwari', 'script' => 'Latn', 'native' => 'Pāli', 'regional' => ''],
// 'aa' => ['name' => 'Afar', 'script' => 'Latn', 'native' => 'Qafar', 'regional' => 'aa_ER'],
// 'ty' => ['name' => 'Tahitian', 'script' => 'Latn', 'native' => 'Reo Māohi', 'regional' => ''],
// 'ksf' => ['name' => 'Bafia', 'script' => 'Latn', 'native' => 'rikpa', 'regional' => ''],
// 'ro' => ['name' => 'Romanian', 'script' => 'Latn', 'native' => 'română', 'regional' => 'ro_RO'],
// 'cgg' => ['name' => 'Chiga', 'script' => 'Latn', 'native' => 'Rukiga', 'regional' => ''],
// 'rm' => ['name' => 'Romansh', 'script' => 'Latn', 'native' => 'rumantsch', 'regional' => ''],
// 'qu' => ['name' => 'Quechua', 'script' => 'Latn', 'native' => 'Runa Simi', 'regional' => ''],
// 'nyn' => ['name' => 'Nyankole', 'script' => 'Latn', 'native' => 'Runyankore', 'regional' => ''],
// 'ssy' => ['name' => 'Saho', 'script' => 'Latn', 'native' => 'Saho', 'regional' => ''],
// 'sc' => ['name' => 'Sardinian', 'script' => 'Latn', 'native' => 'sardu', 'regional' => 'sc_IT'],
// 'de-CH' => ['name' => 'Swiss High German', 'script' => 'Latn', 'native' => 'Schweizer Hochdeutsch', 'regional' => 'de_CH'],
// 'gsw' => ['name' => 'Swiss German', 'script' => 'Latn', 'native' => 'Schwiizertüütsch', 'regional' => ''],
// 'trv' => ['name' => 'Taroko', 'script' => 'Latn', 'native' => 'Seediq', 'regional' => ''],
// 'seh' => ['name' => 'Sena', 'script' => 'Latn', 'native' => 'sena', 'regional' => ''],
// 'nso' => ['name' => 'Northern Sotho', 'script' => 'Latn', 'native' => 'Sesotho sa Leboa', 'regional' => 'nso_ZA'],
// 'st' => ['name' => 'Southern Sotho', 'script' => 'Latn', 'native' => 'Sesotho', 'regional' => 'st_ZA'],
// 'tn' => ['name' => 'Tswana', 'script' => 'Latn', 'native' => 'Setswana', 'regional' => 'tn_ZA'],
// 'sq' => ['name' => 'Albanian', 'script' => 'Latn', 'native' => 'shqip', 'regional' => 'sq_AL'],
// 'sid' => ['name' => 'Sidamo', 'script' => 'Latn', 'native' => 'Sidaamu Afo', 'regional' => 'sid_ET'],
// 'ss' => ['name' => 'Swati', 'script' => 'Latn', 'native' => 'Siswati', 'regional' => 'ss_ZA'],
// 'sk' => ['name' => 'Slovak', 'script' => 'Latn', 'native' => 'slovenčina', 'regional' => 'sk_SK'],
// 'sl' => ['name' => 'Slovene', 'script' => 'Latn', 'native' => 'slovenščina', 'regional' => 'sl_SI'],
// 'so' => ['name' => 'Somali', 'script' => 'Latn', 'native' => 'Soomaali', 'regional' => 'so_SO'],
// 'sr-Latn' => ['name' => 'Serbian (Latin)', 'script' => 'Latn', 'native' => 'Srpski', 'regional' => 'sr_RS'],
// 'sh' => ['name' => 'Serbo-Croatian', 'script' => 'Latn', 'native' => 'srpskohrvatski', 'regional' => ''],
// 'fi' => ['name' => 'Finnish', 'script' => 'Latn', 'native' => 'suomi', 'regional' => 'fi_FI'],
// 'sv' => ['name' => 'Swedish', 'script' => 'Latn', 'native' => 'svenska', 'regional' => 'sv_SE'],
// 'sg' => ['name' => 'Sango', 'script' => 'Latn', 'native' => 'Sängö', 'regional' => ''],
// 'tl' => ['name' => 'Tagalog', 'script' => 'Latn', 'native' => 'Tagalog', 'regional' => 'tl_PH'],
// 'tzm-Latn' => ['name' => 'Central Atlas Tamazight (Latin)', 'script' => 'Latn', 'native' => 'Tamazight', 'regional' => ''],
// 'kab' => ['name' => 'Kabyle', 'script' => 'Latn', 'native' => 'Taqbaylit', 'regional' => 'kab_DZ'],
// 'twq' => ['name' => 'Tasawaq', 'script' => 'Latn', 'native' => 'Tasawaq senni', 'regional' => ''],
// 'shi' => ['name' => 'Tachelhit (Latin)', 'script' => 'Latn', 'native' => 'Tashelhit', 'regional' => ''],
// 'nus' => ['name' => 'Nuer', 'script' => 'Latn', 'native' => 'Thok Nath', 'regional' => ''],
// 'vi' => ['name' => 'Vietnamese', 'script' => 'Latn', 'native' => 'Tiếng Việt', 'regional' => 'vi_VN'],
// 'tg-Latn' => ['name' => 'Tajik (Latin)', 'script' => 'Latn', 'native' => 'tojikī', 'regional' => 'tg_TJ'],
// 'lu' => ['name' => 'Luba-Katanga', 'script' => 'Latn', 'native' => 'Tshiluba', 'regional' => 've_ZA'],
// 've' => ['name' => 'Venda', 'script' => 'Latn', 'native' => 'Tshivenḓa', 'regional' => ''],
// 'tw' => ['name' => 'Twi', 'script' => 'Latn', 'native' => 'Twi', 'regional' => ''],
// 'tr' => ['name' => 'Turkish', 'script' => 'Latn', 'native' => 'Türkçe', 'regional' => 'tr_TR'],
// 'ale' => ['name' => 'Aleut', 'script' => 'Latn', 'native' => 'Unangax tunuu', 'regional' => ''],
// 'ca-valencia' => ['name' => 'Valencian', 'script' => 'Latn', 'native' => 'valencià', 'regional' => ''],
// 'vai-Latn' => ['name' => 'Vai (Latin)', 'script' => 'Latn', 'native' => 'Viyamíĩ', 'regional' => ''],
// 'vo' => ['name' => 'Volapük', 'script' => 'Latn', 'native' => 'Volapük', 'regional' => ''],
// 'fj' => ['name' => 'Fijian', 'script' => 'Latn', 'native' => 'vosa Vakaviti', 'regional' => ''],
// 'wa' => ['name' => 'Walloon', 'script' => 'Latn', 'native' => 'Walon', 'regional' => 'wa_BE'],
// 'wae' => ['name' => 'Walser', 'script' => 'Latn', 'native' => 'Walser', 'regional' => 'wae_CH'],
// 'wen' => ['name' => 'Sorbian', 'script' => 'Latn', 'native' => 'Wendic', 'regional' => ''],
// 'wo' => ['name' => 'Wolof', 'script' => 'Latn', 'native' => 'Wolof', 'regional' => 'wo_SN'],
// 'ts' => ['name' => 'Tsonga', 'script' => 'Latn', 'native' => 'Xitsonga', 'regional' => 'ts_ZA'],
// 'dje' => ['name' => 'Zarma', 'script' => 'Latn', 'native' => 'Zarmaciine', 'regional' => ''],
// 'yo' => ['name' => 'Yoruba', 'script' => 'Latn', 'native' => 'Èdè Yorùbá', 'regional' => 'yo_NG'],
// 'de-AT' => ['name' => 'Austrian German', 'script' => 'Latn', 'native' => 'Österreichisches Deutsch', 'regional' => 'de_AT'],
// 'is' => ['name' => 'Icelandic', 'script' => 'Latn', 'native' => 'íslenska', 'regional' => 'is_IS'],
// 'cs' => ['name' => 'Czech', 'script' => 'Latn', 'native' => 'čeština', 'regional' => 'cs_CZ'],
// 'bas' => ['name' => 'Basa', 'script' => 'Latn', 'native' => 'Ɓàsàa', 'regional' => ''],
// 'mas' => ['name' => 'Masai', 'script' => 'Latn', 'native' => 'ɔl-Maa', 'regional' => ''],
// 'haw' => ['name' => 'Hawaiian', 'script' => 'Latn', 'native' => 'ʻŌlelo Hawaiʻi', 'regional' => ''],
// 'el' => ['name' => 'Greek', 'script' => 'Grek', 'native' => 'Ελληνικά', 'regional' => 'el_GR'],
// 'uz' => ['name' => 'Uzbek (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Ўзбек', 'regional' => 'uz_UZ'],
// 'az-Cyrl' => ['name' => 'Azerbaijani (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Азәрбајҹан', 'regional' => 'uz_UZ'],
// 'ab' => ['name' => 'Abkhazian', 'script' => 'Cyrl', 'native' => 'Аҧсуа', 'regional' => ''],
// 'os' => ['name' => 'Ossetic', 'script' => 'Cyrl', 'native' => 'Ирон', 'regional' => 'os_RU'],
// 'ky' => ['name' => 'Kyrgyz', 'script' => 'Cyrl', 'native' => 'Кыргыз', 'regional' => 'ky_KG'],
// 'sr' => ['name' => 'Serbian (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Српски', 'regional' => 'sr_RS'],
// 'av' => ['name' => 'Avaric', 'script' => 'Cyrl', 'native' => 'авар мацӀ', 'regional' => ''],
// 'ady' => ['name' => 'Adyghe', 'script' => 'Cyrl', 'native' => 'адыгэбзэ', 'regional' => ''],
// 'ba' => ['name' => 'Bashkir', 'script' => 'Cyrl', 'native' => 'башҡорт теле', 'regional' => ''],
// 'be' => ['name' => 'Belarusian', 'script' => 'Cyrl', 'native' => 'беларуская', 'regional' => 'be_BY'],
// 'bg' => ['name' => 'Bulgarian', 'script' => 'Cyrl', 'native' => 'български', 'regional' => 'bg_BG'],
// 'kv' => ['name' => 'Komi', 'script' => 'Cyrl', 'native' => 'коми кыв', 'regional' => ''],
// 'mk' => ['name' => 'Macedonian', 'script' => 'Cyrl', 'native' => 'македонски', 'regional' => 'mk_MK'],
// 'mn' => ['name' => 'Mongolian (Cyrillic)', 'script' => 'Cyrl', 'native' => 'монгол', 'regional' => 'mn_MN'],
// 'ce' => ['name' => 'Chechen', 'script' => 'Cyrl', 'native' => 'нохчийн мотт', 'regional' => 'ce_RU'],
// 'ru' => ['name' => 'Russian', 'script' => 'Cyrl', 'native' => 'русский', 'regional' => 'ru_RU'],
// 'sah' => ['name' => 'Yakut', 'script' => 'Cyrl', 'native' => 'саха тыла', 'regional' => ''],
// 'tt' => ['name' => 'Tatar', 'script' => 'Cyrl', 'native' => 'татар теле', 'regional' => 'tt_RU'],
// 'tg' => ['name' => 'Tajik (Cyrillic)', 'script' => 'Cyrl', 'native' => 'тоҷикӣ', 'regional' => 'tg_TJ'],
// 'tk' => ['name' => 'Turkmen', 'script' => 'Cyrl', 'native' => 'түркменче', 'regional' => 'tk_TM'],
// 'uk' => ['name' => 'Ukrainian', 'script' => 'Cyrl', 'native' => 'українська', 'regional' => 'uk_UA'],
// 'cv' => ['name' => 'Chuvash', 'script' => 'Cyrl', 'native' => 'чӑваш чӗлхи', 'regional' => 'cv_RU'],
// 'cu' => ['name' => 'Church Slavic', 'script' => 'Cyrl', 'native' => 'ѩзыкъ словѣньскъ', 'regional' => ''],
// 'kk' => ['name' => 'Kazakh', 'script' => 'Cyrl', 'native' => 'қазақ тілі', 'regional' => 'kk_KZ'],
// 'hy' => ['name' => 'Armenian', 'script' => 'Armn', 'native' => 'Հայերեն', 'regional' => 'hy_AM'],
// 'yi' => ['name' => 'Yiddish', 'script' => 'Hebr', 'native' => 'ייִדיש', 'regional' => 'yi_US'],
// 'he' => ['name' => 'Hebrew', 'script' => 'Hebr', 'native' => 'עברית', 'regional' => 'he_IL'],
// 'ug' => ['name' => 'Uyghur', 'script' => 'Arab', 'native' => 'ئۇيغۇرچە', 'regional' => 'ug_CN'],
// 'ur' => ['name' => 'Urdu', 'script' => 'Arab', 'native' => 'اردو', 'regional' => 'ur_PK'],
// 'ar' => ['name' => 'Arabic', 'script' => 'Arab', 'native' => 'العربية', 'regional' => 'ar_AE'],
// 'uz-Arab' => ['name' => 'Uzbek (Arabic)', 'script' => 'Arab', 'native' => 'اۉزبېک', 'regional' => ''],
// 'tg-Arab' => ['name' => 'Tajik (Arabic)', 'script' => 'Arab', 'native' => 'تاجیکی', 'regional' => 'tg_TJ'],
// 'sd' => ['name' => 'Sindhi', 'script' => 'Arab', 'native' => 'سنڌي', 'regional' => 'sd_IN'],
// 'fa' => ['name' => 'Persian', 'script' => 'Arab', 'native' => 'فارسی', 'regional' => 'fa_IR'],
// 'pa-Arab' => ['name' => 'Punjabi (Arabic)', 'script' => 'Arab', 'native' => 'پنجاب', 'regional' => 'pa_IN'],
// 'ps' => ['name' => 'Pashto', 'script' => 'Arab', 'native' => 'پښتو', 'regional' => 'ps_AF'],
// 'ks' => ['name' => 'Kashmiri (Arabic)', 'script' => 'Arab', 'native' => 'کأشُر', 'regional' => 'ks_IN'],
// 'ku' => ['name' => 'Kurdish', 'script' => 'Arab', 'native' => 'کوردی', 'regional' => 'ku_TR'],
// 'dv' => ['name' => 'Divehi', 'script' => 'Thaa', 'native' => 'ދިވެހިބަސް', 'regional' => 'dv_MV'],
// 'ks-Deva' => ['name' => 'Kashmiri (Devaganari)', 'script' => 'Deva', 'native' => 'कॉशुर', 'regional' => 'ks_IN'],
// 'kok' => ['name' => 'Konkani', 'script' => 'Deva', 'native' => 'कोंकणी', 'regional' => 'kok_IN'],
// 'doi' => ['name' => 'Dogri', 'script' => 'Deva', 'native' => 'डोगरी', 'regional' => 'doi_IN'],
// 'ne' => ['name' => 'Nepali', 'script' => 'Deva', 'native' => 'नेपाली', 'regional' => ''],
// 'pra' => ['name' => 'Prakrit', 'script' => 'Deva', 'native' => 'प्राकृत', 'regional' => ''],
// 'brx' => ['name' => 'Bodo', 'script' => 'Deva', 'native' => 'बड़ो', 'regional' => 'brx_IN'],
// 'bra' => ['name' => 'Braj', 'script' => 'Deva', 'native' => 'ब्रज भाषा', 'regional' => ''],
// 'mr' => ['name' => 'Marathi', 'script' => 'Deva', 'native' => 'मराठी', 'regional' => 'mr_IN'],
// 'mai' => ['name' => 'Maithili', 'script' => 'Tirh', 'native' => 'मैथिली', 'regional' => 'mai_IN'],
// 'raj' => ['name' => 'Rajasthani', 'script' => 'Deva', 'native' => 'राजस्थानी', 'regional' => ''],
// 'sa' => ['name' => 'Sanskrit', 'script' => 'Deva', 'native' => 'संस्कृतम्', 'regional' => 'sa_IN'],
// 'hi' => ['name' => 'Hindi', 'script' => 'Deva', 'native' => 'हिन्दी', 'regional' => 'hi_IN'],
// 'as' => ['name' => 'Assamese', 'script' => 'Beng', 'native' => 'অসমীয়া', 'regional' => 'as_IN'],
// 'bn' => ['name' => 'Bengali', 'script' => 'Beng', 'native' => 'বাংলা', 'regional' => 'bn_BD'],
// 'mni' => ['name' => 'Manipuri', 'script' => 'Beng', 'native' => 'মৈতৈ', 'regional' => 'mni_IN'],
// 'pa' => ['name' => 'Punjabi (Gurmukhi)', 'script' => 'Guru', 'native' => 'ਪੰਜਾਬੀ', 'regional' => 'pa_IN'],
// 'gu' => ['name' => 'Gujarati', 'script' => 'Gujr', 'native' => 'ગુજરાતી', 'regional' => 'gu_IN'],
// 'or' => ['name' => 'Oriya', 'script' => 'Orya', 'native' => 'ଓଡ଼ିଆ', 'regional' => 'or_IN'],
// 'ta' => ['name' => 'Tamil', 'script' => 'Taml', 'native' => 'தமிழ்', 'regional' => 'ta_IN'],
// 'te' => ['name' => 'Telugu', 'script' => 'Telu', 'native' => 'తెలుగు', 'regional' => 'te_IN'],
// 'kn' => ['name' => 'Kannada', 'script' => 'Knda', 'native' => 'ಕನ್ನಡ', 'regional' => 'kn_IN'],
// 'ml' => ['name' => 'Malayalam', 'script' => 'Mlym', 'native' => 'മലയാളം', 'regional' => 'ml_IN'],
// 'si' => ['name' => 'Sinhala', 'script' => 'Sinh', 'native' => 'සිංහල', 'regional' => 'si_LK'],
// 'th' => ['name' => 'Thai', 'script' => 'Thai', 'native' => 'ไทย', 'regional' => 'th_TH'],
// 'lo' => ['name' => 'Lao', 'script' => 'Laoo', 'native' => 'ລາວ', 'regional' => 'lo_LA'],
// 'bo' => ['name' => 'Tibetan', 'script' => 'Tibt', 'native' => 'པོད་སྐད་', 'regional' => 'bo_IN'],
// 'dz' => ['name' => 'Dzongkha', 'script' => 'Tibt', 'native' => 'རྫོང་ཁ', 'regional' => 'dz_BT'],
// 'my' => ['name' => 'Burmese', 'script' => 'Mymr', 'native' => 'မြန်မာဘာသာ', 'regional' => 'my_MM'],
// 'ka' => ['name' => 'Georgian', 'script' => 'Geor', 'native' => 'ქართული', 'regional' => 'ka_GE'],
// 'byn' => ['name' => 'Blin', 'script' => 'Ethi', 'native' => 'ብሊን', 'regional' => 'byn_ER'],
// 'tig' => ['name' => 'Tigre', 'script' => 'Ethi', 'native' => 'ትግረ', 'regional' => 'tig_ER'],
// 'ti' => ['name' => 'Tigrinya', 'script' => 'Ethi', 'native' => 'ትግርኛ', 'regional' => 'ti_ET'],
// 'am' => ['name' => 'Amharic', 'script' => 'Ethi', 'native' => 'አማርኛ', 'regional' => 'am_ET'],
// 'wal' => ['name' => 'Wolaytta', 'script' => 'Ethi', 'native' => 'ወላይታቱ', 'regional' => 'wal_ET'],
// 'chr' => ['name' => 'Cherokee', 'script' => 'Cher', 'native' => 'ᏣᎳᎩ', 'regional' => ''],
// 'iu' => ['name' => 'Inuktitut (Canadian Aboriginal Syllabics)', 'script' => 'Cans', 'native' => 'ᐃᓄᒃᑎᑐᑦ', 'regional' => 'iu_CA'],
// 'oj' => ['name' => 'Ojibwa', 'script' => 'Cans', 'native' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ', 'regional' => ''],
// 'cr' => ['name' => 'Cree', 'script' => 'Cans', 'native' => 'ᓀᐦᐃᔭᐍᐏᐣ', 'regional' => ''],
// 'km' => ['name' => 'Khmer', 'script' => 'Khmr', 'native' => 'ភាសាខ្មែរ', 'regional' => 'km_KH'],
// 'mn-Mong' => ['name' => 'Mongolian (Mongolian)', 'script' => 'Mong', 'native' => 'ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ', 'regional' => 'mn_MN'],
// 'shi-Tfng' => ['name' => 'Tachelhit (Tifinagh)', 'script' => 'Tfng', 'native' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', 'regional' => ''],
// 'tzm' => ['name' => 'Central Atlas Tamazight (Tifinagh)','script' => 'Tfng', 'native' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', 'regional' => ''],
// 'yue' => ['name' => 'Yue', 'script' => 'Hant', 'native' => '廣州話', 'regional' => 'yue_HK'],
// 'ja' => ['name' => 'Japanese', 'script' => 'Jpan', 'native' => '日本語', 'regional' => 'ja_JP'],
// 'zh' => ['name' => 'Chinese (Simplified)', 'script' => 'Hans', 'native' => '简体中文', 'regional' => 'zh_CN'],
// 'zh-Hant' => ['name' => 'Chinese (Traditional)', 'script' => 'Hant', 'native' => '繁體中文', 'regional' => 'zh_CN'],
// 'ii' => ['name' => 'Sichuan Yi', 'script' => 'Yiii', 'native' => 'ꆈꌠꉙ', 'regional' => ''],
// 'vai' => ['name' => 'Vai (Vai)', 'script' => 'Vaii', 'native' => 'ꕙꔤ', 'regional' => ''],
// 'jv-Java' => ['name' => 'Javanese (Javanese)', 'script' => 'Java', 'native' => 'ꦧꦱꦗꦮ', 'regional' => ''],
// 'ko' => ['name' => 'Korean', 'script' => 'Hang', 'native' => '한국어', 'regional' => 'ko_KR'],
],
// @phpcs:enable
// Requires middleware `LaravelSessionRedirect.php`.
//
// Automatically determine locale from browser
// (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)
// on first call if it's not defined in the URL. Redirect user to computed localized url.
// For example, if users browser language is `de`, and `de` is active in the array `supportedLocales`,
// the `/about` would be redirected to `/de/about`.
//
// The locale will be stored in session and only be computed from browser
// again if the session expires.
//
// If false, system will take app.php locale attribute.
'useAcceptLanguageHeader' => false,
// If `hideDefaultLocaleInURL` is true, then a url without locale
// is identical with the same url with default locale.
// For example, if `en` is default locale, then `/en/about` and `/about`
// would be identical.
//
// If in addition the middleware `LaravelLocalizationRedirectFilter` is active, then
// every url with default locale is redirected to url without locale.
// For example, `/en/about` would be redirected to `/about`.
// It is recommended to use `hideDefaultLocaleInURL` only in
// combination with the middleware `LaravelLocalizationRedirectFilter`
// to avoid duplicate content (SEO).
//
// If `useAcceptLanguageHeader` is true, then the first time
// the locale will be determined from browser and redirect to that language.
// After that, `hideDefaultLocaleInURL` behaves as usual.
'hideDefaultLocaleInURL' => true,
// If you want to display the locales in particular order in the language selector you should write the order here.
// CAUTION: Please consider using the appropriate locale code otherwise it will not work
// Example: 'localesOrder' => ['es','en'].
'localesOrder' => ['fr', 'en'],
// If you want to use custom language URL segments like 'at' instead of 'de-AT', you can map them to allow the
// LanguageNegotiator to assign the desired locales based on HTTP Accept Language Header. For example, if you want
// to use 'at' instead of 'de-AT', you would map 'de-AT' to 'at' (ie. ['de-AT' => 'at']).
'localesMapping' => [],
// Locale suffix for LC_TIME and LC_MONETARY
// Defaults to most common ".UTF-8". Set to blank on Windows systems, change to ".utf8" on CentOS and similar.
'utf8suffix' => env('LARAVELLOCALIZATION_UTF8SUFFIX', '.UTF-8'),
// URLs which should not be processed, e.g. '/nova', '/nova/*', '/nova-api/*' or specific application URLs
// Defaults to [].
'urlsIgnored' => ['/skipped'],
'httpMethodsIgnored' => ['POST', 'PUT', 'PATCH', 'DELETE'],
];

View file

@ -1,150 +0,0 @@
<?php
declare(strict_types=1);
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Processor\PsrLogMessageProcessor;
return [
/*
*--------------------------------------------------------------------------
* Default Log Channel
*--------------------------------------------------------------------------
*
* This option defines the default log channel that is utilized to write
* messages to your logs. The value provided here should match one of
* the channels present in the list of "channels" configured below.
*
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
*--------------------------------------------------------------------------
* Deprecations Log Channel
*--------------------------------------------------------------------------
*
* This option controls the log channel that should be used to log warnings
* regarding deprecated PHP and library features. This allows you to get
* your application ready for upcoming major versions of dependencies.
*
*/
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
/*
*--------------------------------------------------------------------------
* Log Channels
*--------------------------------------------------------------------------
*
* Here you may configure the log channels for your application. Laravel
* utilizes the Monolog PHP logging library, which includes a variety
* of powerful log handlers and formatters that you're free to use.
*
* Available drivers: "single", "daily", "slack", "syslog",
* "errorlog", "monolog", "custom", "stack"
*
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'handler_with' => [
'stream' => 'php://stderr',
],
'formatter' => env('LOG_STDERR_FORMATTER'),
'processors' => [PsrLogMessageProcessor::class],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
'stdout' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDOUT_FORMATTER'),
'with' => [
'stream' => 'php://stdout',
],
],
'k8s' => [
'driver' => 'stack',
'channels' => ['daily', 'stdout'],
'ignore_exceptions' => false,
],
],
];

View file

@ -1,119 +0,0 @@
<?php
declare(strict_types=1);
return [
/*
*--------------------------------------------------------------------------
* Default Mailer
*--------------------------------------------------------------------------
*
* This option controls the default mailer that is used to send all email
* messages unless another mailer is explicitly specified when sending
* the message. All additional mailers can be configured within the
* "mailers" array. Examples of each type of mailer are provided.
*
*/
'default' => env('MAIL_MAILER', 'smtp'),
/*
*--------------------------------------------------------------------------
* Mailer Configurations
*--------------------------------------------------------------------------
*
* Here you may configure all of the mailers used by your application plus
* their respective settings. Several examples have been configured for
* you and you are free to add your own as your application requires.
*
* Laravel supports a variety of mail "transport" drivers that can be used
* when delivering an email. You may specify which one you're using for
* your mailers below. You may also add additional mailers if needed.
*
* Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
* "postmark", "resend", "log", "array",
* "failover", "roundrobin"
*
*/
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'scheme' => env('MAIL_SCHEME'),
'url' => env('MAIL_URL'),
'host' => env('MAIL_HOST', '127.0.0.1'),
'port' => env('MAIL_PORT', 2525),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
'local_domain' => env(
'MAIL_EHLO_DOMAIN',
parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)
),
],
'ses' => [
'transport' => 'ses',
],
'postmark' => [
'transport' => 'postmark',
],
'resend' => [
'transport' => 'resend',
],
'sendmail' => [
'transport' => 'sendmail',
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
],
'log' => [
'transport' => 'log',
'channel' => env('MAIL_LOG_CHANNEL'),
],
'array' => [
'transport' => 'array',
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'log',
],
'retry_after' => 60,
],
'roundrobin' => [
'transport' => 'roundrobin',
'mailers' => [
'ses',
'postmark',
],
'retry_after' => 60,
],
],
/*
*--------------------------------------------------------------------------
* Global "From" Address
*--------------------------------------------------------------------------
*
* You may wish for all emails sent by your application to be sent from
* the same address. Here you may specify a name and address that is
* used globally for all emails that are sent by your application.
*
*/
'from' => [
'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
'name' => env('MAIL_FROM_NAME', 'Example'),
],
];

View file

@ -1,133 +0,0 @@
<?php
/**
* Ok, glad you are here
* first we get a config instance, and set the settings
* $config = HTMLPurifier_Config::createDefault();
* $config->set('Core.Encoding', $this->config->get('purifier.encoding'));
* $config->set('Cache.SerializerPath', $this->config->get('purifier.cachePath'));
* if ( ! $this->config->get('purifier.finalize')) {
* $config->autoFinalize = false;
* }
* $config->load [$this->getConfig());
*
* You must NOT delete the default settings
* anything in settings should be compacted with params that needed to instance HTMLPurifier_Config.
*
* @link http://htmlpurifier.org/live/configdoc/plain.html
*/
declare(strict_types=1);
// phpcs:disable Generic.Files.LineLength.TooLong
return [
'encoding' => 'UTF-8',
'finalize' => true,
'cachePath' => storage_path('app/purifier'),
'cacheFileMode' => 0755,
'settings' => [
'default' => [
'HTML.SafeIframe' => true,
// * For iframes
'URI.SafeIframeRegexp' => '%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/|vimeo.com/)%',
'HTML.Doctype' => 'HTML 4.01 Transitional',
// * Legacy filter en full avec safe legacy.
// phpcs:ignore Generic.Files.LineLength.MaxExceeded
'HTML.Allowed' => 'h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],figure[class|style],figcaption,oembed[url],div[style|class],blockquote,b,strong,i,em,u,s,a[href|title|target|rel|data-model],ul[style|data-type],ol[style],dl[style],li[style|data-type],label,input[type|checked|disabled],p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style|allowfullscreen],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt,sub,sup',
// * Restricted basics.
// 'HTML.Allowed' => 'br,p[style],b[style],u[style],i[style],a[href|title|target|rel],strong[style],span[class],ul[style],ol[style],li[style],h2[style],h3[style],h4[style],hr,blockquote[style]',
// * Legacy filter en full avec safe legacy.
// phpcs:ignore Generic.Files.LineLength.MaxExceeded
'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,margin,margin-left,margin-right,color,background-color,text-align,list-style-type,float,width,height,vertical-align,border-color',
// * Restricted basics.
// 'CSS.AllowedProperties' => 'text-align,margin-left',
'AutoFormat.AutoParagraph' => false,
'AutoFormat.RemoveEmpty' => false,
'AutoFormat.RemoveEmpty.RemoveNbsp' => false,
],
'custom_definition' => [
'id' => 'html5-definitions',
'rev' => 2,
'debug' => true,
'elements' => [
// * http://developers.whatwg.org/sections.html
['section', 'Block', 'Flow', 'Common'],
['nav', 'Block', 'Flow', 'Common'],
['article', 'Block', 'Flow', 'Common'],
['aside', 'Block', 'Flow', 'Common'],
['header', 'Block', 'Flow', 'Common'],
['footer', 'Block', 'Flow', 'Common'],
// * Content model actually excludes several tags, not modelled here
['address', 'Block', 'Flow', 'Common'],
['hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common'],
// * http://developers.whatwg.org/grouping-content.html
['figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'],
['figcaption', 'Inline', 'Flow', 'Common'],
['oembed', 'Inline', 'Flow', 'Common'],
// * http://developers.whatwg.org/the-video-element.html#the-video-element
['video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [
'src' => 'URI',
'type' => 'Text',
'width' => 'Length',
'height' => 'Length',
'poster' => 'URI',
'preload' => 'Enum#auto,metadata,none',
'controls' => 'Bool',
]],
['source', 'Block', 'Flow', 'Common', [
'src' => 'URI',
'type' => 'Text',
]],
// * http://developers.whatwg.org/text-level-semantics.html
['s', 'Inline', 'Inline', 'Common'],
['var', 'Inline', 'Inline', 'Common'],
['sub', 'Inline', 'Inline', 'Common'],
['sup', 'Inline', 'Inline', 'Common'],
['mark', 'Inline', 'Inline', 'Common'],
['wbr', 'Inline', 'Empty', 'Core'],
// * http://developers.whatwg.org/edits.html
['ins', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']],
['del', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']],
],
'attributes' => [
['iframe', 'allowfullscreen', 'Bool'],
['table', 'height', 'Text'],
['td', 'border', 'Text'],
['th', 'border', 'Text'],
['tr', 'width', 'Text'],
['tr', 'height', 'Text'],
['tr', 'border', 'Text'],
['figure', 'class', 'Text'],
['figure', 'style', 'Text'],
['oembed', 'url', 'Text'],
['span', 'style', 'Text'],
['img', 'style', 'Text'],
],
],
'custom_attributes' => [
['a', 'target', 'Enum#_blank,_self,_target,_top'],
['a', 'data-model', 'Text'],
['input', 'type', 'Text'],
['input', 'checked', 'Bool'],
['input', 'disabled', 'Bool'],
['ol', 'style', 'Text'],
['ul', 'data-type', 'Text'],
['li', 'data-type', 'Text'],
['figure', 'style', 'Text'],
['img', 'style', 'Text'],
['span', 'style', 'Text'],
['oembed', 'url', 'Text'],
],
'custom_elements' => [
['u', 'Inline', 'Inline', 'Common'],
['oembed', 'Inline', 'Inline', 'Common'],
['label', 'Inline', 'Flow', 'Common'],
['input', 'Inline', 'Empty', 'Common'],
],
],
];

View file

@ -1,131 +0,0 @@
<?php
declare(strict_types=1);
return [
/*
*--------------------------------------------------------------------------
* Default Queue Connection Name
*--------------------------------------------------------------------------
*
* Laravel's queue supports a variety of backends via a single, unified
* API, giving you convenient access to each backend using identical
* syntax for each. The default queue connection is defined below.
*
*/
'default' => env('QUEUE_CONNECTION', 'database'),
/*
*--------------------------------------------------------------------------
* Queue Connections
*--------------------------------------------------------------------------
*
* Here you may configure the connection options for every queue backend
* used by your application. An example configuration is provided for
* each backend supported by Laravel. You're also free to add more.
*
* Drivers: "sync", "database", "beanstalkd", "sqs", "redis",
* "deferred", "background", "failover", "null"
*
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
'deferred' => [
'driver' => 'deferred',
],
'background' => [
'driver' => 'background',
],
'failover' => [
'driver' => 'failover',
'connections' => [
'database',
'deferred',
],
],
],
/*
*--------------------------------------------------------------------------
* Job Batching
*--------------------------------------------------------------------------
*
* The following options configure the database and table that store job
* batching information. These options can be updated to any database
* connection and table which has been defined by your application.
*
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
*--------------------------------------------------------------------------
* Failed Queue Jobs
*--------------------------------------------------------------------------
*
* These options configure the behavior of failed queue job logging so you
* can control how and where failed jobs are stored. Laravel ships with
* support for storing failed jobs in a simple file or in a database.
*
* Supported drivers: "database-uuids", "dynamodb", "file", "null"
*
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

View file

@ -1,343 +0,0 @@
<?php
declare(strict_types=1);
use Slides\Saml2\Models\Tenant;
return [
/**
*--------------------------------------------------------------------------
* Tenant Model
*--------------------------------------------------------------------------
*
* This will allow you to override the tenant model with your own.
*/
'tenantModel' => Tenant::class,
/**
*--------------------------------------------------------------------------
* Use built-in routes
*--------------------------------------------------------------------------
*
* If "useRoutes" set to true, the package defines five new routes:
*
* Method | URI | Name
* -------|---------------------------------|------------------
* POST | {routesPrefix}/{uuid}/acs | saml.acs
* GET | {routesPrefix}/{uuid}/login | saml.login
* GET | {routesPrefix}/{uuid}/logout | saml.logout
* GET | {routesPrefix}/{uuid}/metadata | saml.metadata
* GET | {routesPrefix}/{uuid}/sls | saml.sls
*/
'useRoutes' => true,
/**
*--------------------------------------------------------------------------
* Built-in routes prefix
*--------------------------------------------------------------------------
*
* Here you may define the prefix for built-in routes.
*/
'routesPrefix' => '/saml2',
/**
*--------------------------------------------------------------------------
* Middle groups to use for the SAML routes
*--------------------------------------------------------------------------
*
* Note, Laravel 5.2 requires a group which includes StartSession
*/
'routesMiddleware' => ['saml'],
/**
*--------------------------------------------------------------------------
* Signature validation
*--------------------------------------------------------------------------
*
* Set to true if you want to use parameters from $_SERVER to validate the signature.
*/
'retrieveParametersFromServer' => false,
/**
*--------------------------------------------------------------------------
* Login redirection URL.
*--------------------------------------------------------------------------
*
* The redirection URL after successful login.
*/
'loginRoute' => '/admin/login',
/**
*--------------------------------------------------------------------------
* Logout redirection URL.
*--------------------------------------------------------------------------
*
* The redirection URL after successful logout.
*/
'logoutRoute' => '/admin/login',
/**
*--------------------------------------------------------------------------
* Login error redirection URL.
*--------------------------------------------------------------------------
*
* The redirection URL after login failing.
*/
'errorRoute' => '/admin/login',
/**
*--------------------------------------------------------------------------
* Strict mode.
*--------------------------------------------------------------------------
*
* If 'strict' is True, then the PHP Toolkit will reject unsigned
* or unencrypted messages if it expects them signed or encrypted
* Also will reject the messages if not strictly follow the SAML
* standard: Destination, NameId, Conditions... are validated too.
*/
'strict' => true,
/**
*--------------------------------------------------------------------------
* Debug mode.
*--------------------------------------------------------------------------
*
* When enabled, errors must be printed.
*/
'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)),
/**
*--------------------------------------------------------------------------
* Whether to use `X-Forwarded-*` headers to determine port/domain/protocol.
*--------------------------------------------------------------------------
*
* If 'proxyVars' is True, then the Saml lib will trust proxy headers
* e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if
* your application is running behind a load balancer which terminates SSL.
*/
'proxyVars' => true,
/**
*--------------------------------------------------------------------------
* Service Provider configuration.
*--------------------------------------------------------------------------
*
* General setting of the service provider.
*/
'sp' => [
/**
*--------------------------------------------------------------------------
* NameID format.
*--------------------------------------------------------------------------
*
* Specifies constraints on the name identifier to be used to
* represent the requested subject.
*/
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
/**
*--------------------------------------------------------------------------
* SP Certificates.
*--------------------------------------------------------------------------
*
* Usually x509cert and privateKey of the SP are provided by files placed at
* the certs folder. But we can also provide them with the following parameters.
*/
'x509cert' => env('SAML2_SP_CERT_x509', ''),
'privateKey' => env('SAML2_SP_CERT_PRIVATEKEY', ''),
/**
*--------------------------------------------------------------------------
* Identifier (URI) of the SP entity.
*--------------------------------------------------------------------------
*
* Leave blank to use the 'saml.metadata' route.
*/
'entityId' => env('SAML2_SP_ENTITYID', ''),
/**
*--------------------------------------------------------------------------
* The Assertion Consumer Service (ACS) URL.
*--------------------------------------------------------------------------
*
* URL Location where the <Response> from the IdP will be returned, using HTTP-POST binding.
* Leave blank to use the 'saml.acs' route.
*/
'assertionConsumerService' => [
'url' => '',
],
/**
*--------------------------------------------------------------------------
* The Single Logout Service URL.
*--------------------------------------------------------------------------
*
* Specifies info about where and how the <Logout Response> message MUST be
* returned to the requester, in this case our SP.
*
* URL Location where the <Response> from the IdP will be returned, using HTTP-Redirect binding.
* Leave blank to use the 'saml.sls' route.
*/
'singleLogoutService' => [
'url' => '',
],
],
/**
*--------------------------------------------------------------------------
* OneLogin security settings.
*--------------------------------------------------------------------------
*/
'security' => [
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
/**
*--------------------------------------------------------------------------
* NameId encryption
*--------------------------------------------------------------------------
*
* Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
* will be encrypted.
*/
'nameIdEncrypted' => true,
/**
*--------------------------------------------------------------------------
* AuthnRequest signage
*--------------------------------------------------------------------------
*
* Indicates whether the <samlp:AuthnRequest> messages sent by
* this SP will be signed. The Metadata of the SP will offer this info
*/
'authnRequestsSigned' => true,
/**
*--------------------------------------------------------------------------
* Logout request signage
*--------------------------------------------------------------------------
*
* Indicates whether the <samlp:logoutRequest> messages sent by this SP
* will be signed.
*/
'logoutRequestSigned' => true,
/**
*--------------------------------------------------------------------------
* Logout response signage
*--------------------------------------------------------------------------
*
* Indicates whether the <samlp:logoutResponse> messages sent by this SP
* will be signed.
*/
'logoutResponseSigned' => true,
/**
*--------------------------------------------------------------------------
* Whether need to sign metadata.
*--------------------------------------------------------------------------
*
* The possible values:
* - false
* - true (use certs)
* - array:
* ```
* [
* 'keyFileName' => 'metadata.key',
* 'certFileName' => 'metadata.crt'
* ]
* ```
*/
'signMetadata' => true,
/**
*--------------------------------------------------------------------------
* Requirement to sign messages.
*--------------------------------------------------------------------------
*
* Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
* <samlp:LogoutResponse> elements received by this SP to be signed.
*/
'wantMessagesSigned' => true,
/**
*--------------------------------------------------------------------------
* Requirement to sign assertion elements.
*--------------------------------------------------------------------------
*
* Indicates a requirement for the <saml:Assertion> elements received by
* this SP to be signed.
*/
'wantAssertionsSigned' => true,
/**
*--------------------------------------------------------------------------
* Requirement to encrypt NameID.
*--------------------------------------------------------------------------
*
* Indicates a requirement for the NameID received by this SP to be encrypted.
*/
'wantNameIdEncrypted' => false,
/**
*--------------------------------------------------------------------------
* Authentication context.
*--------------------------------------------------------------------------
*
* Set to false and no AuthContext will be sent in the AuthNRequest,
*
* Set true or don't present this parameter and you will get an
* AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
*
* Set an array with the possible auth context values:
* ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509']
*/
'requestedAuthnContext' => true,
],
/**
*--------------------------------------------------------------------------
* Contact information.
*--------------------------------------------------------------------------
*
* It is recommended to supply a technical and support contacts.
*/
'contactPerson' => [
'technical' => [
'givenName' => env('SAML2_CONTACT_TECHNICAL_NAME', env('APP_NAME', 'Technical')),
'emailAddress' => env('SAML2_CONTACT_TECHNICAL_EMAIL', 'no@reply.com'),
],
'support' => [
'givenName' => env('SAML2_CONTACT_SUPPORT_NAME', env('APP_NAME', 'Support')),
'emailAddress' => env('SAML2_CONTACT_SUPPORT_EMAIL', 'no@reply.com'),
],
],
/**
*--------------------------------------------------------------------------
* Organization information.
*--------------------------------------------------------------------------
*
* The info in en_US lang is recommended, add more if required.
*/
'organization' => [
'en-US' => [
'name' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
'displayname' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
'url' => env('SAML2_ORGANIZATION_URL', env('APP_URL', 'http://url')),
],
],
/**
* Default tenant UUID.
*/
'default_tenant_uuid' => env('SAML2_DEFAULT_TENANT_UUID', null),
/**
* Target domain.
*/
'domain' => env('SAML2_DOMAIN', ''),
];

View file

@ -1,212 +0,0 @@
<?php
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Default Search Engine
|--------------------------------------------------------------------------
|
| This option controls the default search connection that gets used while
| using Laravel Scout. This connection is used when syncing all models
| to the search service. You should adjust this based on your needs.
|
| Supported: "algolia", "meilisearch", "typesense",
| "database", "collection", "null"
|
*/
'driver' => env('SCOUT_DRIVER', 'collection'),
/*
|--------------------------------------------------------------------------
| Index Prefix
|--------------------------------------------------------------------------
|
| Here you may specify a prefix that will be applied to all search index
| names used by Scout. This prefix may be useful if you have multiple
| "tenants" or applications sharing the same search infrastructure.
|
*/
'prefix' => env('SCOUT_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Queue Data Syncing
|--------------------------------------------------------------------------
|
| This option allows you to control if the operations that sync your data
| with your search engines are queued. When this is set to "true" then
| all automatic data syncing will get queued for better performance.
|
*/
'queue' => env('SCOUT_QUEUE', false),
/*
|--------------------------------------------------------------------------
| Database Transactions
|--------------------------------------------------------------------------
|
| This configuration option determines if your data will only be synced
| with your search indexes after every open database transaction has
| been committed, thus preventing any discarded data from syncing.
|
*/
'after_commit' => false,
/*
|--------------------------------------------------------------------------
| Chunk Sizes
|--------------------------------------------------------------------------
|
| These options allow you to control the maximum chunk size when you are
| mass importing data into the search engine. This allows you to fine
| tune each of these chunk sizes based on the power of the servers.
|
*/
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
/*
|--------------------------------------------------------------------------
| Soft Deletes
|--------------------------------------------------------------------------
|
| This option allows to control whether to keep soft deleted records in
| the search indexes. Maintaining soft deleted records can be useful
| if your application still needs to search for the records later.
|
*/
'soft_delete' => false,
/*
|--------------------------------------------------------------------------
| Identify User
|--------------------------------------------------------------------------
|
| This option allows you to control whether to notify the search engine
| of the user performing the search. This is sometimes useful if the
| engine supports any analytics based on this application's users.
|
| Supported engines: "algolia"
|
*/
'identify' => env('SCOUT_IDENTIFY', false),
/*
|--------------------------------------------------------------------------
| Algolia Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Algolia settings. Algolia is a cloud hosted
| search engine which works great with Scout out of the box. Just plug
| in your application ID and admin API key to get started searching.
|
*/
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
'index-settings' => [
// 'users' => [
// 'searchableAttributes' => ['id', 'name', 'email'],
// 'attributesForFaceting'=> ['filterOnly(email)'],
// ],
],
],
/*
|--------------------------------------------------------------------------
| Meilisearch Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Meilisearch settings. Meilisearch is an open
| source search engine with minimal configuration. Below, you can state
| the host and key information for your own Meilisearch installation.
|
| See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options
|
*/
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY'),
'index-settings' => [
// 'users' => [
// 'filterableAttributes'=> ['id', 'name', 'email'],
// ],
],
],
/*
|--------------------------------------------------------------------------
| Typesense Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Typesense settings. Typesense is an open
| source search engine using minimal configuration. Below, you will
| state the host, key, and schema configuration for the instance.
|
*/
'typesense' => [
'client-settings' => [
'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
'nodes' => [
[
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
],
'nearest_node' => [
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),
],
// 'max_total_results' => env('TYPESENSE_MAX_TOTAL_RESULTS', 1000),
'model-settings' => [
// User::class => [
// 'collection-schema' => [
// 'fields' => [
// [
// 'name' => 'id',
// 'type' => 'string',
// ],
// [
// 'name' => 'name',
// 'type' => 'string',
// ],
// [
// 'name' => 'created_at',
// 'type' => 'int64',
// ],
// ],
// 'default_sorting_field' => 'created_at',
// ],
// 'search-parameters' => [
// 'query_by' => 'name'
// ],
// ],
],
'import_action' => env('TYPESENSE_IMPORT_ACTION', 'upsert'),
],
];

View file

@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
use Uwil\LaravelCommon\Helpers\ExceptionsHelper;
return [
'dsn' => env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')),
// The release version of your application.
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')).
'release' => env('SENTRY_RELEASE'),
// When left empty or `null` the Laravel environment will be used.
'environment' => env('SENTRY_ENVIRONMENT'),
'breadcrumbs' => [
// Capture Laravel logs in breadcrumbs.
'logs' => true,
// Capture SQL queries in breadcrumbs.
'sql_queries' => true,
// Capture bindings on SQL queries logged in breadcrumbs.
'sql_bindings' => false,
// Capture queue job information in breadcrumbs.
'queue_info' => true,
// Capture command information in breadcrumbs.
'command_info' => true,
],
'tracing' => [
// Trace queue jobs as their own transactions.
'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false),
// Capture queue jobs as spans when executed on the sync driver.
'queue_jobs' => true,
// Capture SQL queries as spans.
'sql_queries' => true,
// Try to find out where the SQL query originated from and add it to the query spans.
'sql_origin' => true,
// Capture views as spans.
'views' => true,
// Indicates if the tracing integrations supplied by Sentry should be loaded.
'default_integrations' => true,
// Indicates that requests without a matching route should be traced.
'missing_routes' => false,
],
// See: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send-default-pii.
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
// See: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate.
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_TRACES_SAMPLE_RATE'),
'before_send' => [ExceptionsHelper::class, 'sentryEventHandler'],
];

View file

@ -1,47 +0,0 @@
<?php
declare(strict_types=1);
return [
/*
*--------------------------------------------------------------------------
* Third Party Services
*--------------------------------------------------------------------------
*
* This file is for storing the credentials for third party services such
* as Mailgun, Postmark, AWS and more. This file provides the de facto
* location for this type of information, allowing packages to have
* a conventional file to locate the various service credentials.
*
*/
'postmark' => [
'key' => env('POSTMARK_API_KEY'),
],
'resend' => [
'key' => env('RESEND_API_KEY'),
],
'ses' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'slack' => [
'notifications' => [
'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
],
],
'google' => [
'recaptcha' => [
'public' => env('GOOGLE_RECAPTCHA_PUBLIC'),
'private' => env('GOOGLE_RECAPTCHA_PRIVATE'),
'version' => env('GOOGLE_RECAPTCHA_VERSION', '3') === '3' ? true : false,
],
],
];

View file

@ -1,219 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Support\Str;
return [
/*
*--------------------------------------------------------------------------
* Default Session Driver
*--------------------------------------------------------------------------
*
* This option determines the default session driver that is utilized for
* incoming requests. Laravel supports a variety of storage options to
* persist session data. Database storage is a great default choice.
*
* Supported: "file", "cookie", "database", "memcached",
* "redis", "dynamodb", "array"
*
*/
'driver' => env('SESSION_DRIVER', 'database'),
/*
*--------------------------------------------------------------------------
* Session Lifetime
*--------------------------------------------------------------------------
*
* Here you may specify the number of minutes that you wish the session
* to be allowed to remain idle before it expires. If you want them
* to expire immediately when the browser is closed then you may
* indicate that via the expire_on_close configuration option.
*
*/
'lifetime' => (int) env('SESSION_LIFETIME', 120),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
*--------------------------------------------------------------------------
* Session Encryption
*--------------------------------------------------------------------------
*
* This option allows you to easily specify that all of your session data
* should be encrypted before it's stored. All encryption is performed
* automatically by Laravel and you may use the session like normal.
*
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
*--------------------------------------------------------------------------
* Session File Location
*--------------------------------------------------------------------------
*
* When utilizing the "file" session driver, the session files are placed
* on disk. The default storage location is defined here; however, you
* are free to provide another location where they should be stored.
*
*/
'files' => storage_path('framework/sessions'),
/*
*--------------------------------------------------------------------------
* Session Database Connection
*--------------------------------------------------------------------------
*
* When using the "database" or "redis" session drivers, you may specify a
* connection that should be used to manage these sessions. This should
* correspond to a connection in your database configuration options.
*
*/
'connection' => env('SESSION_CONNECTION'),
/*
*--------------------------------------------------------------------------
* Session Database Table
*--------------------------------------------------------------------------
*
* When using the "database" session driver, you may specify the table to
* be used to store sessions. Of course, a sensible default is defined
* for you; however, you're welcome to change this to another table.
*
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
*--------------------------------------------------------------------------
* Session Cache Store
*--------------------------------------------------------------------------
*
* When using one of the framework's cache driven session backends, you may
* define the cache store which should be used to store the session data
* between requests. This must match one of your defined cache stores.
*
* Affects: "dynamodb", "memcached", "redis"
*
*/
'store' => env('SESSION_STORE'),
/*
*--------------------------------------------------------------------------
* Session Sweeping Lottery
*--------------------------------------------------------------------------
*
* Some session drivers must manually sweep their storage location to get
* rid of old sessions from storage. Here are the chances that it will
* happen on a given request. By default, the odds are 2 out of 100.
*
*/
'lottery' => [2, 100],
/*
*--------------------------------------------------------------------------
* Session Cookie Name
*--------------------------------------------------------------------------
*
* Here you may change the name of the session cookie that is created by
* the framework. Typically, you should not need to change this value
* since doing so does not grant a meaningful security improvement.
*
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug((string) env('APP_NAME', 'laravel')) . '-session'
),
/*
*--------------------------------------------------------------------------
* Session Cookie Path
*--------------------------------------------------------------------------
*
* The session cookie path determines the path for which the cookie will
* be regarded as available. Typically, this will be the root path of
* your application, but you're free to change this when necessary.
*
*/
'path' => env('SESSION_PATH', '/'),
/*
*--------------------------------------------------------------------------
* Session Cookie Domain
*--------------------------------------------------------------------------
*
* This value determines the domain and subdomains the session cookie is
* available to. By default, the cookie will be available to the root
* domain without subdomains. Typically, this shouldn't be changed.
*
*/
'domain' => env('SESSION_DOMAIN'),
/*
*--------------------------------------------------------------------------
* HTTPS Only Cookies
*--------------------------------------------------------------------------
*
* By setting this option to true, session cookies will only be sent back
* to the server if the browser has a HTTPS connection. This will keep
* the cookie from being sent to you when it can't be done securely.
*
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
*--------------------------------------------------------------------------
* HTTP Access Only
*--------------------------------------------------------------------------
*
* Setting this value to true will prevent JavaScript from accessing the
* value of the cookie and the cookie will only be accessible through
* the HTTP protocol. It's unlikely you should disable this option.
*
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
*--------------------------------------------------------------------------
* Same-Site Cookies
*--------------------------------------------------------------------------
*
* This option determines how your cookies behave when cross-site requests
* take place, and can be used to mitigate CSRF attacks. By default, we
* will set this value to "lax" to permit secure cross-site requests.
*
* See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
*
* Supported: "lax", "strict", "none", null
*
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
*--------------------------------------------------------------------------
* Partitioned Cookies
*--------------------------------------------------------------------------
*
* Setting this value to true will tie the cookie to the top-level site for
* a cross-site context. Partitioned cookies are accepted by the browser
* when flagged "secure" and the Same-Site attribute is set to "none".
*
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

View file

@ -1,170 +0,0 @@
<?php
declare(strict_types=1);
use Laravel\Telescope\Http\Middleware\Authorize;
use Laravel\Telescope\Watchers;
return [
/**
*--------------------------------------------------------------------------
* Telescope Master Switch
*--------------------------------------------------------------------------
*
* This option may be used to disable all Telescope watchers regardless
* of their individual configuration, which simply provides a single
* and convenient way to enable or disable Telescope data storage.
*/
'enabled' => env('TELESCOPE_ENABLED', false),
/**
*--------------------------------------------------------------------------
* Telescope Domain
*--------------------------------------------------------------------------
*
* This is the subdomain where Telescope will be accessible from. If the
* setting is null, Telescope will reside under the same domain as the
* application. Otherwise, this value will be used as the subdomain.
*/
'domain' => env('TELESCOPE_DOMAIN'),
/**
*--------------------------------------------------------------------------
* Telescope Path
*--------------------------------------------------------------------------
*
* This is the URI path where Telescope will be accessible from. Feel free
* to change this path to anything you like. Note that the URI will not
* affect the paths of its internal API that aren't exposed to users.
*/
'path' => env('TELESCOPE_PATH', 'admin/telescope'),
/**
*--------------------------------------------------------------------------
* Telescope Storage Driver
*--------------------------------------------------------------------------
*
* This configuration options determines the storage driver that will
* be used to store Telescope's data. In addition, you may set any
* custom options as needed by the particular driver you choose.
*/
'driver' => env('TELESCOPE_DRIVER', 'database'),
'storage' => [
'database' => [
'connection' => env('DB_CONNECTION', 'mysql'),
'chunk' => 1000,
],
],
/**
*--------------------------------------------------------------------------
* Telescope Queue
*--------------------------------------------------------------------------
*
* This configuration options determines the queue connection and queue
* which will be used to process ProcessPendingUpdate jobs. This can
* be changed if you would prefer to use a non-default connection.
*/
'queue' => [
'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
'queue' => env('TELESCOPE_QUEUE', null),
],
/**
*--------------------------------------------------------------------------
* Telescope Route Middleware
*--------------------------------------------------------------------------
*
* These middleware will be assigned to every Telescope route, giving you
* the chance to add your own middleware to this list or change any of
* the existing middleware. Or, you can simply stick with this list.
*/
'middleware' => [
Authorize::class,
],
/**
*--------------------------------------------------------------------------
* Allowed / Ignored Paths & Commands
*--------------------------------------------------------------------------
*
* The following array lists the URI paths and Artisan commands that will
* not be watched by Telescope. In addition to this list, some Laravel
* commands, like migrations and queue commands, are always ignored.
*/
'only_paths' => [],
'ignore_paths' => [
'livewire*',
'nova-api*',
'pulse*',
],
'ignore_commands' => [],
/**
*--------------------------------------------------------------------------
* Telescope Watchers
*--------------------------------------------------------------------------
*
* The following array lists the "watchers" that will be registered with
* Telescope. The watchers gather the application's profile data when
* a request or task is executed. Feel free to customize this list.
*/
'watchers' => [
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
Watchers\CacheWatcher::class => [
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
'hidden' => [],
],
Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
Watchers\CommandWatcher::class => [
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
'ignore' => [],
],
Watchers\DumpWatcher::class => [
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
],
Watchers\EventWatcher::class => [
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
'ignore' => [],
],
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
Watchers\GateWatcher::class => [
'enabled' => env('TELESCOPE_GATE_WATCHER', true),
'ignore_abilities' => [],
'ignore_packages' => true,
'ignore_paths' => [],
],
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', true),
Watchers\LogWatcher::class => [
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
'level' => 'error',
],
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', true),
Watchers\ModelWatcher::class => [
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
'events' => ['eloquent.*'],
'hydrations' => true,
],
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', true),
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'ignore_packages' => true,
'ignore_paths' => [],
'slow' => 100,
],
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
Watchers\RequestWatcher::class => [
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
'ignore_http_methods' => [],
'ignore_status_codes' => [],
],
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', true),
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
],
];

View file

@ -1,64 +0,0 @@
<?php
declare(strict_types=1);
use Uwil\LaravelBackend\Models\User;
use Uwil\LaravelBackend\Requests\Users\StoreUserRequest;
use Uwil\LaravelBackend\Requests\Users\UpdateUserRequest;
return [
/**
*--------------------------------------------------------------------------
* Routes
*--------------------------------------------------------------------------
*
* This option may be used to edit routes informations.
* - prefix: string.
* - name: string.
*/
'routes' => [
'prefix' => 'admin',
'name' => 'back.',
],
/**
*--------------------------------------------------------------------------
* Redirects
*--------------------------------------------------------------------------
*
* This option may be used to edit redirects.
* - redirect_logout: string.
*/
'redirect_logout' => 'front.dynamic-pages.accueil.fr',
/**
*--------------------------------------------------------------------------
* Saml
*--------------------------------------------------------------------------
*
* This option may be used to active or not the saml connection.
* - enabled: boolean.
*/
'saml' => [
'enabled' => true,
],
/**
*--------------------------------------------------------------------------
* User
*--------------------------------------------------------------------------
*
* This options may be used when overriding the User model.
* - model: class.
* - store: class.
* - update: class.
*/
'user' => [
'model' => User::class,
'requests' => [
'store' => StoreUserRequest::class,
'update' => UpdateUserRequest::class,
],
],
];

View file

@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
return [
/**
*--------------------------------------------------------------------------
* Path of the default view.
*--------------------------------------------------------------------------
*
* This option may be used to edit routes informations.
* - default-view: string.
*/
'default-view' => 'front/Pages/DynamicPageDefault',
];

View file

@ -1,76 +0,0 @@
<?php
declare(strict_types=1);
namespace Database\Factories;
use App\Models\Product;
use Domain\Models\ValueObjects\Products\BreedingMethod;
use Domain\Models\ValueObjects\Products\Business;
use Domain\Models\ValueObjects\Products\NutritionalValues;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Products\ProductFormat;
use Domain\Models\ValueObjects\Products\Technology;
use Illuminate\Database\Eloquent\Factories\Factory;
/** @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Product> */
class ProductFactory extends Factory
{
protected $model = Product::class;
public function definition(): array
{
return [
'seo_title' => $this->faker->sentence(4),
'seo_description' => $this->faker->text(150),
'picture' => $this->faker->imageUrl(800, 600, 'food'),
'picture_alt' => $this->faker->words(3, true),
'picture_title' => $this->faker->words(3, true),
'name' => ucfirst($this->faker->words(3, true)),
'category' => $this->faker->randomElement(ProductCategory::cases()),
'technology' => $this->faker->randomElement(Technology::cases()),
'breeding_methods' => $this->faker->randomElements(
BreedingMethod::cases(),
$this->faker->numberBetween(1, 3)
),
'businesses' => $this->faker->randomElements(
Business::cases(),
$this->faker->numberBetween(1, count(Business::cases()))
),
'is_egalim' => $this->faker->boolean(30),
'formats' => $this->faker->randomElements(
ProductFormat::cases(),
$this->faker->numberBetween(1, 3)
),
'description' => $this->faker->paragraph(),
'details_and_advantages' => $this->faker->paragraphs(2, true),
'handling_advices' => $this->faker->paragraph(),
'ingredients' => $this->faker->optional(0.8)->paragraph(),
'conservation_before_opening' => 'À conserver entre 0°C et 4°C.',
'conservation_after_opening' => $this->faker->optional(0.7)->sentence(),
'nutritional_values' => $this->faker->boolean(80) ? new NutritionalValues(
refWeightG: 100.0,
kj: $this->faker->randomFloat(2, 0, 3000),
kcal: $this->faker->randomFloat(2, 0, 800),
fatG: $this->faker->randomFloat(2, 0, 100),
satFatG: $this->faker->randomFloat(2, 0, 50),
carbG: $this->faker->randomFloat(2, 0, 100),
sugarG: $this->faker->randomFloat(2, 0, 100),
fiberG: $this->faker->randomFloat(2, 0, 50),
proteinG: $this->faker->randomFloat(2, 0, 100),
saltG: $this->faker->randomFloat(2, 0, 10),
) : null,
'pdf' => $this->faker->optional(0.5)->filePath(),
'inco_code' => $this->faker->optional(0.5)->bothify('INCO-####'),
];
}
}

View file

@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace Database\Factories;
use App\Models\Recipe;
use Domain\Models\ValueObjects\Products\ProductCategory;
use Domain\Models\ValueObjects\Recipes\ConsumptionTime;
use Domain\Models\ValueObjects\Recipes\Inspiration;
use Domain\Models\ValueObjects\Recipes\Season;
use Illuminate\Database\Eloquent\Factories\Factory;
class RecipeFactory extends Factory
{
protected $model = Recipe::class;
public function definition(): array
{
return [
'seo_title' => $this->faker->sentence(4),
'seo_description' => $this->faker->text(150),
'picture' => $this->faker->imageUrl(),
'picture_alt' => $this->faker->words(3, true),
'picture_title' => $this->faker->words(3, true),
'name' => $this->faker->words(3, true),
'product_category' => $this->faker->randomElement(ProductCategory::cases()),
'consomption_time' => $this->faker->randomElement(ConsumptionTime::cases()),
'inspiration' => $this->faker->randomElement(Inspiration::cases()),
'season' => $this->faker->randomElement(Season::cases()),
'description' => $this->faker->paragraph(),
'details' => 'Pour ' . $this->faker->numberBetween(1, 10) . " personnes\n"
. 'À réaliser en ' . $this->faker->numberBetween(15, 60) . ' minutes',
'ingredients_list' => $this->faker->paragraph(),
'preparation_steps' => $this->faker->text(),
'tips_and_advices' => $this->faker->optional()->paragraph(),
];
}
}

View file

@ -1,56 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration
{
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('seo_title');
$table->string('seo_description');
$table->string('picture');
$table->string('picture_alt');
$table->string('picture_title');
$table->string('name');
$table->string('slug')->unique();
$table->string('category')->comment('Domain\Models\ValueObjects\Products\ProductRange');
$table->string('technology')->comment('Domain\Models\ValueObjects\Products\Technology');
$table->boolean('is_egalim')->default(false);
$table->json('breeding_methods')->comment('Array of Domain\Models\ValueObjects\Products\BreedingMethod');
$table->json('businesses')->comment('Array of Domain\Models\ValueObjects\Products\Business');
$table->json('formats')->comment('Array of Domain\Models\ValueObjects\Products\ProductFormat');
$table->text('description');
$table->text('details_and_advantages');
$table->text('handling_advices');
$table->text('ingredients')->nullable();
$table->text('conservation_before_opening');
$table->text('conservation_after_opening')->nullable();
$table->json('nutritional_values')->nullable()->comment('Domain\Models\ValueObjects\NutritionalValues');
$table->string('pdf')->nullable();
$table->softDeletes();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('products');
}
};

View file

@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration
{
public function up(): void
{
Schema::create('recipes', function (Blueprint $table) {
$table->uuid('id')->primary();
// SEO & Medias
$table->string('seo_title');
$table->string('seo_description');
$table->string('picture');
$table->string('picture_alt');
$table->string('picture_title');
$table->string('name');
$table->string('slug')->unique();
$table->string('product_category')->comment('Domain\Models\ValueObjects\Products\ProductRange');
$table->string('consomption_time')->comment('Domain\Models\ValueObjects\Recipes\ConsumptionTime');
$table->string('inspiration')->comment('Domain\Models\ValueObjects\Recipes\Inspiration');
$table->string('season')->comment('Domain\Models\ValueObjects\Recipes\Season');
$table->text('description')->nullable();
$table->text('details')->nullable();
$table->text('ingredients_list');
$table->text('preparation_steps');
$table->text('tips_and_advices')->nullable();
$table->softDeletes();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('recipes');
}
};

View file

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration
{
public function up(): void
{
Schema::create('product_recipe', function (Blueprint $table) {
$table->foreignUuid('product_id')->constrained()->cascadeOnDelete();
$table->foreignUuid('recipe_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('product_recipe');
}
};

View file

@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('activity_logs', function (Blueprint $table) {
$table->string('model_id')->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View file

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table): void {
if (! Schema::hasColumn('users', 'deleted_at')) {
$table->softDeletes()->nullable()->comment('Date the user was deleted.');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
if (Schema::hasColumn('users', 'deleted_at')) {
$table->dropSoftDeletes();
}
});
}
};

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