Compare commits
25 commits
develop
...
flucollect
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24604f5c9e | ||
|
|
077ca889b3 | ||
|
|
0ee73c16c9 | ||
|
|
caa9c17b8a | ||
|
|
280acef9d1 | ||
|
|
e6a65cf4a6 | ||
|
|
94f1634791 | ||
|
|
97062e8d91 | ||
|
|
c0c1608ad5 | ||
|
|
50d304c6ce | ||
|
|
1d07b4fb8f | ||
|
|
70e55f6732 | ||
|
|
dd7bbc3b49 | ||
|
|
5c11fa8468 | ||
|
|
82ca7da7db | ||
|
|
8659827b93 | ||
|
|
749aff1401 | ||
|
|
c3595ad6c0 | ||
|
|
c66a67c889 | ||
|
|
f694850e0d | ||
|
|
37e12669e5 | ||
|
|
a2cae01503 | ||
|
|
8addafcefd | ||
|
|
96d2e3960b | ||
|
|
46ea9e24f5 |
443 changed files with 177341 additions and 5189 deletions
5
.browserslistrc
Normal file
5
.browserslistrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Browsers that we support
|
||||
|
||||
last 3 version
|
||||
> 0.3%
|
||||
not dead # no browsers without security updates
|
||||
|
|
@ -3,9 +3,9 @@ root = true
|
|||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
|
|
@ -16,3 +16,6 @@ indent_size = 2
|
|||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
||||
|
||||
[*.vue]
|
||||
indent_size = 2
|
||||
|
|
|
|||
22
.env.example
22
.env.example
|
|
@ -17,7 +17,7 @@ DB_PASSWORD=
|
|||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
FILESYSTEM_DRIVER=local
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
|
@ -29,17 +29,14 @@ REDIS_PASSWORD=null
|
|||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailhog
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
MAIL_LIST_SERVICEWEB=
|
||||
MAIL_LIST_INVALIDINCO=
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
|
|
@ -49,7 +46,16 @@ AWS_USE_PATH_STYLE_ENDPOINT=false
|
|||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
# Sentry
|
||||
SENTRY_LARAVEL_DSN=
|
||||
|
|
|
|||
49
.env.testing
Normal file
49
.env.testing
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
APP_NAME=Laravel
|
||||
APP_ENV=testing
|
||||
APP_KEY=base64:UypKzhOMAs2aDu3ovgw/lINvWZQWicbz9nfUXDpDzeQ=
|
||||
APP_DEBUG=true
|
||||
APP_URL="http://127.0.0.25:8888"
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3308
|
||||
DB_DATABASE=testing_flucollector
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=:Mysql.Docker
|
||||
|
||||
MIGRATIONPAGINATION=20000
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=database
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_FROM_ADDRESS=crm@ldc.fr
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
MAIL_HOST=mailhog
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_ENCRYPTION=
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
55
.eslintrc.json
Normal file
55
.eslintrc.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-strongly-recommended",
|
||||
"@vue/eslint-config-typescript"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.vue"],
|
||||
"rules": {
|
||||
"no-undef": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.vue"],
|
||||
"rules": {
|
||||
"indent": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["vue", "@typescript-eslint"],
|
||||
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
|
||||
"rules": {
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double"],
|
||||
"semi": ["error", "always"],
|
||||
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
|
||||
"vue/one-component-per-file": "off"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".vue", ".ts", ".d.ts"]
|
||||
},
|
||||
"alias": {
|
||||
"extensions": [".vue", ".js", ".ts", ".scss", ".d.ts"],
|
||||
"map": [
|
||||
["~", "./resources/assets"],
|
||||
["@", "./node_modules"]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
.gitattributes
vendored
14
.gitattributes
vendored
|
|
@ -1,5 +1,11 @@
|
|||
* text=auto
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
*.js linguist-vendored
|
||||
* 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
|
||||
|
|
|
|||
20
.gitignore
vendored
20
.gitignore
vendored
|
|
@ -1,14 +1,26 @@
|
|||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpunit.result.cache
|
||||
.stylelintcache
|
||||
/.fleet
|
||||
/.idea
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/cache
|
||||
/public/hot
|
||||
/public/sitemap.xml
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.phpunit.result.cache
|
||||
auth.json
|
||||
docker-compose.override.yml
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
/.idea
|
||||
.npmrc
|
||||
storage/inco
|
||||
storage/marque
|
||||
|
|
|
|||
24
.gitlab-ci.yml
Normal file
24
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
include:
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'lint/lint.yml'
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'security/security.yml'
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'build/build.yml'
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'test/test.yml'
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: 'deploy/deploy.yml'
|
||||
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- security
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
11
.gitlab/issue_templates/bug.md
Normal file
11
.gitlab/issue_templates/bug.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
## Comportement constaté
|
||||
|
||||
|
||||
## Comportement attendu
|
||||
|
||||
|
||||
## Solution envisagée
|
||||
|
||||
|
||||
## Étapes pour la reproduction du bug
|
||||
*
|
||||
7
.gitlab/issue_templates/feature.md
Normal file
7
.gitlab/issue_templates/feature.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
## Présentation du besoin
|
||||
|
||||
|
||||
## Implémentation
|
||||
|
||||
|
||||
## Maquettes, documentations...
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "client"]
|
||||
path = client
|
||||
url = git@lab.s-web.fr:ldc-web/projets/flucollector/flucollector-client.git
|
||||
14
.styleci.yml
14
.styleci.yml
|
|
@ -1,14 +0,0 @@
|
|||
php:
|
||||
preset: laravel
|
||||
version: 8
|
||||
disabled:
|
||||
- no_unused_imports
|
||||
finder:
|
||||
not-name:
|
||||
- index.php
|
||||
- server.php
|
||||
js:
|
||||
finder:
|
||||
not-name:
|
||||
- webpack.mix.js
|
||||
css: true
|
||||
48
.stylelintrc.json
Normal file
48
.stylelintrc.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"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",
|
||||
"**/vendor/*.less",
|
||||
"**/vendor/**/*.less"
|
||||
],
|
||||
"customSyntax": "postcss-scss",
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"string-quotes": "double",
|
||||
"selector-max-id": null,
|
||||
"color-no-invalid-hex": true,
|
||||
"order/properties-alphabetical-order": null,
|
||||
"max-nesting-depth": 5,
|
||||
"selector-max-compound-selectors": 5,
|
||||
"declaration-colon-space-after": "always-single-line",
|
||||
"function-parentheses-space-inside": "never-single-line",
|
||||
"declaration-colon-newline-after": null,
|
||||
"value-list-comma-newline-after": null,
|
||||
"scss/double-slash-comment-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
"ignore": [
|
||||
"between-comments",
|
||||
"stylelint-commands",
|
||||
"inside-block"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
20
.vscode/extensions.json
vendored
Normal file
20
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"rvest.vs-code-prettier-eslint",
|
||||
"onecentlin.laravel-extension-pack",
|
||||
"johnpapa.vscode-peacock",
|
||||
"xdebug.php-debug",
|
||||
"neilbrayfield.php-docblocker",
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"valeryanm.vscode-phpsab",
|
||||
"syler.sass-indented",
|
||||
"sibiraj-s.vscode-scss-formatter",
|
||||
"mrmlnc.vscode-scss",
|
||||
"stylelint.vscode-stylelint",
|
||||
"vue.vscode-typescript-vue-plugin",
|
||||
"wscats.vue",
|
||||
"vue.volar",
|
||||
"misterj.vue-volar-extention-pack"
|
||||
]
|
||||
}
|
||||
49
.vscode/launch.json
vendored
49
.vscode/launch.json
vendored
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Listen for Xdebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"hostname": "xdebug-incollector.s-web.test",
|
||||
"port": 9003
|
||||
},
|
||||
{
|
||||
"name": "Launch currently open script",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"cwd": "${fileDirname}",
|
||||
"port": 0,
|
||||
"runtimeArgs": [
|
||||
"-dxdebug.start_with_request=yes"
|
||||
],
|
||||
"env": {
|
||||
"XDEBUG_MODE": "debug,develop",
|
||||
"XDEBUG_CONFIG": "client_port=${port}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch Built-in web server",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"-dxdebug.mode=debug",
|
||||
"-dxdebug.start_with_request=yes",
|
||||
"-S",
|
||||
"localhost:0"
|
||||
],
|
||||
"program": "",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"port": 9003,
|
||||
"serverReadyAction": {
|
||||
"pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
|
|
@ -1,23 +1,24 @@
|
|||
{
|
||||
"phpcs.executablePath": "vendor/bin/phpcs",
|
||||
"workbench.colorCustomizations": {
|
||||
"activityBar.activeBackground": "#a714ac",
|
||||
"activityBar.activeBorder": "#eb549a",
|
||||
"activityBar.background": "#a714ac",
|
||||
"activityBar.activeBackground": "#bf106b",
|
||||
"activityBar.background": "#bf106b",
|
||||
"activityBar.foreground": "#e7e7e7",
|
||||
"activityBar.inactiveForeground": "#e7e7e799",
|
||||
"activityBarBadge.background": "#eb549a",
|
||||
"activityBarBadge.foreground": "#15202b",
|
||||
"sash.hoverBorder": "#a714ac",
|
||||
"statusBar.background": "#0f437e",
|
||||
"activityBarBadge.background": "#213b05",
|
||||
"activityBarBadge.foreground": "#e7e7e7",
|
||||
"commandCenter.border": "#e7e7e799",
|
||||
"sash.hoverBorder": "#bf106b",
|
||||
"statusBar.background": "#900c51",
|
||||
"statusBar.foreground": "#e7e7e7",
|
||||
"statusBarItem.hoverBackground": "#a714ac",
|
||||
"statusBarItem.remoteBackground": "#0f437e",
|
||||
"statusBarItem.hoverBackground": "#bf106b",
|
||||
"statusBarItem.remoteBackground": "#900c51",
|
||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
||||
"titleBar.activeBackground": "#0f437e",
|
||||
"titleBar.activeBackground": "#900c51",
|
||||
"titleBar.activeForeground": "#e7e7e7",
|
||||
"titleBar.inactiveBackground": "#0f437e99",
|
||||
"titleBar.inactiveBackground": "#900c5199",
|
||||
"titleBar.inactiveForeground": "#e7e7e799"
|
||||
},
|
||||
"peacock.remoteColor": "#0f437e"
|
||||
"peacock.remoteColor": "#900c51"
|
||||
}
|
||||
|
|
|
|||
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
### All notable changes to `Flucollector` will be documented in this file.
|
||||
|
||||
# version 1.0.0
|
||||
Init release
|
||||
21
LICENCE.md
Normal file
21
LICENCE.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License (!! includes CC BY-NC 3.0 parts please read Readme.md)
|
||||
|
||||
Copyright (c) 2023
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
58
README.md
58
README.md
|
|
@ -1,16 +1,52 @@
|
|||
# Incollector
|
||||
# Comment lancer les tests unitaires ?
|
||||
|
||||
## .env
|
||||
mettre :
|
||||
```
|
||||
### Attention, si vous utilisez juste un .env à la place d'un .env.testing votre base de donnée de developpement sera effacée à chaque lancement des tests unitaires
|
||||
|
||||
```
|
||||
Etapes :
|
||||
- cp .env.testing.example .env.testing (spécial tests unitaires)
|
||||
- php artisan key:generate
|
||||
- parameter les BDD dans le .env.testing
|
||||
- php artisan config:cache
|
||||
- git submodule init
|
||||
- git submodule update (récupérer le client)
|
||||
- composer install
|
||||
- service start mysql (votre base de donnée accessible sur 127.0.0.1:3306)
|
||||
- chmod u+x ./test.sh
|
||||
- php artisan optimize
|
||||
- composer run test
|
||||
|
||||
## Flux
|
||||
Flux INCO dans :
|
||||
```
|
||||
storage/inco
|
||||
```
|
||||
Config Supervisor 2 Fichiers :
|
||||
|
||||
## Scheduled Jobs
|
||||
`uwilflucollector-worker.conf`
|
||||
**Pour les jobs en général**
|
||||
|
||||
[program:uwilflucollector-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /srcs/artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
umask=077
|
||||
numprocs=8
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/srcs/storage/logs/worker.log
|
||||
stopwaitsecs=3600
|
||||
|
||||
`uwilflucollector-stats-worker.conf`
|
||||
**Pour les jobs de stats uniquement**
|
||||
|
||||
[program:uwilflucollector-stats-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /srcs/artisan queue:work --queue=stats --sleep=3 --tries=3 --max-time=3600
|
||||
autostart=true
|
||||
autorestart=true
|
||||
startasgroup=true
|
||||
killasgroup=true
|
||||
user=www-data
|
||||
umask=077
|
||||
numprocs=1
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/srcs/storage/logs/worker.log
|
||||
stopwaitsecs=3600
|
||||
|
|
|
|||
61
app/Casts/AllegationCast.php
Normal file
61
app/Casts/AllegationCast.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\Lib\XmlInco\Allegations;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AllegationCast implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* Cast the given value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array<string,mixed> $attributes
|
||||
* @return mixed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
*/
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (!\is_string($value)) {
|
||||
$value = \strval($value);
|
||||
}
|
||||
$value = \json_decode($value, true);
|
||||
/** @var array<string[]> $value */
|
||||
return new Allegations(...$value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array<string,mixed> $attributes
|
||||
* @return mixed
|
||||
* @throws \RuntimeException If $value is not an intance of PortionAmount.
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
*/
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_object($value) and $value instanceof Allegations) {
|
||||
return $value->toJson();
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Cannot cast {$key}, need instance of " . Allegations::class);
|
||||
}
|
||||
}
|
||||
61
app/Casts/NutrionalValuesCast.php
Normal file
61
app/Casts/NutrionalValuesCast.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\Lib\XmlInco\NutritionalValues;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class NutrionalValuesCast implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* Cast the given value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array<string,mixed> $attributes
|
||||
* @return mixed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
*/
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (!\is_string($value)) {
|
||||
$value = \strval($value);
|
||||
}
|
||||
$value = \json_decode($value, true);
|
||||
/** @var array<string,float|integer|null|string> $value */
|
||||
return new NutritionalValues($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array<string,mixed> $attributes
|
||||
* @return mixed
|
||||
* @throws \RuntimeException If $value is not an intance of PortionAmount.
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
*/
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_object($value) and $value instanceof NutritionalValues) {
|
||||
return $value->toJson();
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Cannot cast {$key}, need instance of " . NutritionalValues::class);
|
||||
}
|
||||
}
|
||||
59
app/Casts/PortionAmountCast.php
Normal file
59
app/Casts/PortionAmountCast.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\Lib\XmlInco\PortionAmount;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PortionAmountCast implements CastsAttributes
|
||||
{
|
||||
/**
|
||||
* Cast the given value.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array<string,mixed> $attributes
|
||||
* @return mixed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
*/
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (\is_numeric($value)) {
|
||||
$value = \intval($value);
|
||||
}
|
||||
return PortionAmount::fromDatabase($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the given value for storage.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array<string,mixed> $attributes
|
||||
* @return mixed
|
||||
* @throws \RuntimeException If $value is not an intance of PortionAmount.
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
|
||||
*/
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
|
||||
{
|
||||
// phpcs:enable
|
||||
if (\is_null($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (\is_object($value) and $value instanceof PortionAmount) {
|
||||
return $value->toDatabaseValue();
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Cannot cast {$key}, need instance of " . PortionAmount::class);
|
||||
}
|
||||
}
|
||||
238
app/Console/Commands/GenerateClient.php
Normal file
238
app/Console/Commands/GenerateClient.php
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\Api\ApiError;
|
||||
use App\Enums\Api\ApiStatus;
|
||||
use App\Enums\XmlInco\PortionType;
|
||||
use App\Exceptions\Api\ApiErrorException;
|
||||
use App\Exceptions\Api\ApplicationException;
|
||||
use App\Lib\XmlInco\Allegations;
|
||||
use App\Lib\XmlInco\NutritionalValues;
|
||||
use App\Lib\XmlInco\PortionAmount;
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class GenerateClient extends Command
|
||||
{
|
||||
private const TARGET = 'client/src';
|
||||
private const CLIENTNAMESPACE = 'UWIL\\Flucollector';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'client:generate';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate the flucollector client in `client` folder';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->copyLibXmlIncoClasses();
|
||||
$this->copyTraits();
|
||||
$this->copyEnums();
|
||||
$this->copyExceptions();
|
||||
$this->copyRoutesList();
|
||||
$this->copyApiConfigInfo();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy config files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function copyApiConfigInfo(): void
|
||||
{
|
||||
$this->info('Copy Api Config');
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
if (File::exists("$rootTarget/../apiConfig.readonly.json")) {
|
||||
File::delete("$rootTarget/../apiConfig.readonly.json");
|
||||
}
|
||||
File::put(
|
||||
"$rootTarget/../apiConfig.readonly.json",
|
||||
\json_encode(config('api'), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy routes into a json file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function copyRoutesList(): void
|
||||
{
|
||||
$this->info('Copy Routes');
|
||||
$routes = [];
|
||||
/** @var \Illuminate\Routing\Route */
|
||||
foreach (Route::getRoutes() as $route) {
|
||||
if (\strpos($route->getPrefix(), 'api') !== false) {
|
||||
$routes[$route->getName()] = [
|
||||
'uri' => $route->uri(),
|
||||
'methods' => $route->methods()
|
||||
];
|
||||
}
|
||||
}
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
if (File::exists("$rootTarget/routes.json")) {
|
||||
File::delete("$rootTarget/routes.json");
|
||||
}
|
||||
File::put(
|
||||
"$rootTarget/routes.json",
|
||||
\json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy Exeption classes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function copyExceptions(): void
|
||||
{
|
||||
$this->info('Copy Exceptions');
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
$classes = [
|
||||
ApplicationException::class,
|
||||
ApiErrorException::class
|
||||
];
|
||||
$this->copyToFolder($classes, 'Exceptions');
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$classBasename = class_basename($class);
|
||||
$dest = "$rootTarget/Exceptions/$classBasename.php";
|
||||
$this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy base enums classes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function copyTraits(): void
|
||||
{
|
||||
$this->info('Copy Traits');
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
$classes = [
|
||||
BaseEnum::class
|
||||
];
|
||||
$this->copyToFolder($classes, 'Traits');
|
||||
foreach ($classes as $class) {
|
||||
$classBasename = class_basename($class);
|
||||
$dest = "$rootTarget/Traits/$classBasename.php";
|
||||
$this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy enums classes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function copyEnums(): void
|
||||
{
|
||||
$this->info('Copy Enums');
|
||||
$classes = [
|
||||
ApiError::class,
|
||||
ApiStatus::class,
|
||||
PortionType::class,
|
||||
];
|
||||
$this->copyToFolder($classes, 'Enums');
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
foreach ($classes as $class) {
|
||||
$classBasename = class_basename($class);
|
||||
$dest = "{$rootTarget}/Enums/{$classBasename}.php";
|
||||
$this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy lib XmlInco classes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function copyLibXmlIncoClasses(): void
|
||||
{
|
||||
$this->info('Copy Lib XmlInco classes');
|
||||
$classes = [
|
||||
Allegations::class,
|
||||
NutritionalValues::class,
|
||||
PortionAmount::class,
|
||||
];
|
||||
$this->copyToFolder($classes, 'Lib');
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
foreach ($classes as $class) {
|
||||
$classBasename = class_basename($class);
|
||||
$dest = "{$rootTarget}/Lib/{$classBasename}.php";
|
||||
$this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy Classes to specified folder
|
||||
*
|
||||
* @param array $classes
|
||||
* @param string $folder
|
||||
* @return void
|
||||
*/
|
||||
private function copyToFolder(array $classes, string $folder): void
|
||||
{
|
||||
$rootTarget = app_path('../' . self::TARGET);
|
||||
$folderNamespace = \str_replace('/', '\\', $folder);
|
||||
foreach ($classes as $class) {
|
||||
$classFileName = \str_replace("App\\$folderNamespace\\", '', sprintf('%s.php', $class));
|
||||
$classFileName = \str_replace('\\', '/', $classFileName);
|
||||
$classBasename = class_basename($class);
|
||||
$source = \app_path("$folder/$classFileName");
|
||||
$dest = "$rootTarget/$folder/$classBasename.php";
|
||||
if (File::exists($dest)) {
|
||||
File::delete($dest);
|
||||
}
|
||||
File::copy($source, $dest);
|
||||
$this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a specific string with another on a file
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $search
|
||||
* @param string $replace
|
||||
* @return void
|
||||
*/
|
||||
private function replaceInPhpFile(string $source, string $search, string $replace): void
|
||||
{
|
||||
$fContent = File::get($source);
|
||||
|
||||
// Remove trailing \Api .
|
||||
$fContent = Str::of($fContent)->replaceMatches("/\\\(Api)([\s;\\\])/", '$2');
|
||||
// Remove trailing \XmlInco .
|
||||
$fContent = Str::of($fContent)->replaceMatches("/\\\(XmlInco)([\s;\\\])/", '$2');
|
||||
$fContent = Str::of($fContent)->replaceMatches("/\\\Traits\\\Enums/", '\Traits');
|
||||
|
||||
// Replace namespace top level.
|
||||
$pos = strpos($fContent, $search);
|
||||
if ($pos !== false) {
|
||||
File::put($source, \str_replace($search, $replace, $fContent));
|
||||
}
|
||||
}
|
||||
}
|
||||
35
app/Console/Commands/TestMail.php
Normal file
35
app/Console/Commands/TestMail.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class TestMail extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'uwil:mail:test';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send test email to equipe.web@ldc.fr';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
Mail::raw(sprintf('UWIL mail-test from %s %s', \config('app.name'), \config('app.url')), function ($msg) {
|
||||
$msg->to('equipe.web@ldc.fr')->subject('Test Email');
|
||||
});
|
||||
}
|
||||
}
|
||||
231
app/Console/Commands/UserCreate.php
Normal file
231
app/Console/Commands/UserCreate.php
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class UserCreate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'uwil:user:create';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create a backend admin user';
|
||||
|
||||
/**
|
||||
* The first name of the new user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $first_name;
|
||||
|
||||
/**
|
||||
* The last name of the new user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $last_name;
|
||||
|
||||
/**
|
||||
* The email of the new user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $email;
|
||||
|
||||
/**
|
||||
* The password of the new user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* The role of the new user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $role;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->first_name = null;
|
||||
$this->addFirstName();
|
||||
|
||||
$this->last_name = null;
|
||||
$this->addLastName();
|
||||
|
||||
$this->email = null;
|
||||
$this->addEmail();
|
||||
|
||||
$this->password = null;
|
||||
$this->addPassword();
|
||||
|
||||
$this->role = null;
|
||||
$this->addRole();
|
||||
|
||||
$user = new User([
|
||||
'first_name' => $this->first_name,
|
||||
'last_name' => $this->last_name,
|
||||
'email' => $this->email,
|
||||
'password' => $this->password,
|
||||
'role' => $this->role,
|
||||
'published' => true,
|
||||
'published_at' => Carbon::now(),
|
||||
]);
|
||||
$user->saveOrFail();
|
||||
|
||||
$this->info('User created 👌');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a first name to the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addFirstName(): void
|
||||
{
|
||||
while (is_null($this->first_name)) {
|
||||
try {
|
||||
$tmp = $this->ask('Type the wanted user first name', 'Admin');
|
||||
$validator = Validator::make(['first_name' => $tmp], [
|
||||
'first_name' => 'required|min:3|max:255'
|
||||
]);
|
||||
$validator->validate();
|
||||
$this->first_name = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please provide a valid first name %s',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
} //end try
|
||||
}; //end while
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a last name to the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addLastName(): void
|
||||
{
|
||||
while (is_null($this->last_name)) {
|
||||
try {
|
||||
$tmp = $this->ask('Type the wanted user last name', 'Saïtama');
|
||||
$validator = Validator::make(['last_name' => $tmp], [
|
||||
'last_name' => 'required|min:3|max:255'
|
||||
]);
|
||||
$validator->validate();
|
||||
$this->last_name = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please provide a valid last_name %s',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
} //end try
|
||||
}; //end while
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an email to the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addEmail(): void
|
||||
{
|
||||
while (is_null($this->email)) {
|
||||
try {
|
||||
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
|
||||
$validator = Validator::make(['email' => $tmp], [
|
||||
'email' => 'required|unique:users,email|email:rfc,dns'
|
||||
]);
|
||||
$validator->validate();
|
||||
$this->email = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please provide a valid email %s',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
} //end try
|
||||
}; //end while
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a password to the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addPassword(): void
|
||||
{
|
||||
while (
|
||||
($this->password = $this->secret(
|
||||
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
|
||||
)) !== $this->secret('Confirm the password')
|
||||
) {
|
||||
continue;
|
||||
}; //end while
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a role to the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addRole(): void
|
||||
{
|
||||
while (is_null($this->role)) {
|
||||
try {
|
||||
$tmp = $this->choice(
|
||||
'Select his role',
|
||||
[RoleEnum::conceptor->name, RoleEnum::admin->name, RoleEnum::visitor->name],
|
||||
RoleEnum::visitor->name,
|
||||
$maxAttempts = null,
|
||||
$allowMultipleSelections = false
|
||||
);
|
||||
if ($tmp === RoleEnum::conceptor->name) {
|
||||
$tmp = RoleEnum::conceptor;
|
||||
}
|
||||
if ($tmp === RoleEnum::admin->name) {
|
||||
$tmp = RoleEnum::admin;
|
||||
}
|
||||
if ($tmp === RoleEnum::visitor->name) {
|
||||
$tmp = RoleEnum::visitor;
|
||||
}
|
||||
$validator = Validator::make(['role' => $tmp], [
|
||||
'role' => 'required'
|
||||
]);
|
||||
$validator->validate();
|
||||
$this->role = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please choose from the selection',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
} //end try
|
||||
}; //end while
|
||||
}
|
||||
}
|
||||
37
app/Console/Commands/XmlBrandReader.php
Normal file
37
app/Console/Commands/XmlBrandReader.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\Arcadia\XmlBrandReaderJob;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class XmlBrandReader extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'xml:brand';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run the XmlBrandReaderJob manually';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Running the Job, this could take some time.');
|
||||
$job = new XmlBrandReaderJob();
|
||||
$job->console = $this;
|
||||
$job->handle();
|
||||
$this->info(' Job completed');
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\XmlIncoReader as JobsXmlIncoReader;
|
||||
use App\Jobs\Arcadia\XmlIncoReaderJob;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class XmlIncoReader extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
|
|
@ -30,9 +29,9 @@ class XmlIncoReader extends Command
|
|||
public function handle()
|
||||
{
|
||||
$this->info('Running the Job, this could take some time.');
|
||||
$job = new JobsXmlIncoReader();
|
||||
$job = new XmlIncoReaderJob();
|
||||
$job->console = $this;
|
||||
$job->handle();
|
||||
$this->info('Job completed');
|
||||
$this->info(' Job completed');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,25 +7,14 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
|||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// $schedule->command('inspire')->hourly();
|
||||
//$schedule->job(new XmlIncoReader)->weeklyOn(1, '8:00');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -35,7 +24,7 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
|
|
|||
46
app/Enums/ActivityLogs/ActivityLogsEventEnum.php
Normal file
46
app/Enums/ActivityLogs/ActivityLogsEventEnum.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\ActivityLogs;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum ActivityLogsEventEnum: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case created = 1;
|
||||
case updated = 2;
|
||||
case duplicated = 3;
|
||||
case deleted = 4;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::created->name => 'Création',
|
||||
self::updated->name => 'Mise à jour',
|
||||
self::duplicated->name => 'Duplication',
|
||||
self::deleted->name => 'Suppression',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Custom added classes definition
|
||||
*/
|
||||
private const BOOTSTRAPCLASS = [
|
||||
self::created->name => 'success',
|
||||
self::updated->name => 'warning',
|
||||
self::duplicated->name => 'secondary',
|
||||
self::deleted->name => 'danger',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get Class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function bootstrapClass(): string
|
||||
{
|
||||
return self::BOOTSTRAPCLASS[$this->name];
|
||||
}
|
||||
}
|
||||
36
app/Enums/Api/ApiError.php
Normal file
36
app/Enums/Api/ApiError.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Api;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum ApiError: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case modelNotFound = 101;
|
||||
case validationFailed = 102;
|
||||
case authFailed = 117;
|
||||
case apiAuthFailed = 119;
|
||||
case methodNotAllowed = 150;
|
||||
case wrongTimeStamp = 151;
|
||||
case missingParameter = 400;
|
||||
case authorizationException = 403;
|
||||
case throttleRequestsException = 429;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::modelNotFound->name => '%s introuvable.',
|
||||
self::validationFailed->name => 'Impossible de valider les données',
|
||||
self::authFailed->name => 'Echec de l\'authentification du compte',
|
||||
self::apiAuthFailed->name => 'Echec de l\'authentification client à l\'Api pour ce service',
|
||||
self::methodNotAllowed->name => 'This method is not allowed for this request',
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
self::wrongTimeStamp->name => 'Your request is malformed, please check timestamp header and parameter',
|
||||
self::missingParameter->name => 'The request is missing some parameters',
|
||||
self::authorizationException->name => 'You are not autorized to access this',
|
||||
self::throttleRequestsException->name => 'Throttle: too many request have been attempted, please retry later'
|
||||
];
|
||||
}
|
||||
13
app/Enums/Api/ApiStatus.php
Normal file
13
app/Enums/Api/ApiStatus.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Api;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum ApiStatus: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case OK = 1;
|
||||
case KO = 2;
|
||||
}
|
||||
|
|
@ -2,20 +2,16 @@
|
|||
|
||||
namespace App\Enums;
|
||||
|
||||
use Kwaadpepper\Enum\BaseEnumRoutable;
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* This is an enumeration class,
|
||||
* the following methods are its possible values
|
||||
*
|
||||
* @method static self debug()
|
||||
* @method static self log()
|
||||
* @method static self warning()
|
||||
* @method static self info()
|
||||
* @method static self error()
|
||||
* @method static self critical()
|
||||
*
|
||||
*/
|
||||
class LogType extends BaseEnumRoutable
|
||||
enum LogType: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case debug = 1;
|
||||
case log = 2;
|
||||
case warning = 3;
|
||||
case info = 4;
|
||||
case error = 5;
|
||||
case critical = 6;
|
||||
}
|
||||
|
|
|
|||
16
app/Enums/Pagination/ItemsPerPaginationEnum.php
Normal file
16
app/Enums/Pagination/ItemsPerPaginationEnum.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Pagination;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum ItemsPerPaginationEnum: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case five = 5;
|
||||
case twelve = 12;
|
||||
case twentyfive = 25;
|
||||
case fifty = 50;
|
||||
case onehundred = 100;
|
||||
}
|
||||
21
app/Enums/Theme/BootstrapThemeEnum.php
Normal file
21
app/Enums/Theme/BootstrapThemeEnum.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Theme;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum BootstrapThemeEnum: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case light = 1;
|
||||
case dark = 2;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::light->name => 'Light',
|
||||
self::dark->name => 'Dark',
|
||||
];
|
||||
}
|
||||
23
app/Enums/Users/RoleEnum.php
Normal file
23
app/Enums/Users/RoleEnum.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\Users;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum RoleEnum: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case conceptor = 1;
|
||||
case admin = 2;
|
||||
case visitor = 3;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::conceptor->name => 'Concepteur',
|
||||
self::admin->name => 'Administrateur',
|
||||
self::visitor->name => 'Visiteur',
|
||||
];
|
||||
}
|
||||
21
app/Enums/XmlInco/PortionType.php
Normal file
21
app/Enums/XmlInco/PortionType.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums\XmlInco;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum PortionType: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case approximative = 0;
|
||||
case exact = 1;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::approximative->name => 'approximativement',
|
||||
self::exact->name => 'exactement'
|
||||
];
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AbortXmlImportException extends Exception
|
||||
class AbortXmlImportException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
|
|
|||
99
app/Exceptions/Api/ApiErrorException.php
Normal file
99
app/Exceptions/Api/ApiErrorException.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions\Api;
|
||||
|
||||
use App\Enums\Api\ApiError;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* This would be thrown if any of the case
|
||||
* referenced in \App\Enums\ApiError would
|
||||
* occur within flucollector
|
||||
*
|
||||
* On the client an ApiErrorException would
|
||||
* also be throw with the ApiError corresponding to
|
||||
* the situation
|
||||
*/
|
||||
class ApiErrorException extends \RuntimeException
|
||||
{
|
||||
/**
|
||||
* Api Error
|
||||
* This value makes the error more explicit.
|
||||
* Handling these errors are normal as inputs or
|
||||
* request may be undesired or refused by flucollector.
|
||||
*
|
||||
* @var \App\Enums\Api\ApiError
|
||||
*/
|
||||
protected $error;
|
||||
|
||||
/**
|
||||
* This is an explicit error message,
|
||||
* ou may print this to the front or not depending
|
||||
* on $error value
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* This is the http code returned by flucollector.
|
||||
* This value could be used to handle errors.
|
||||
* But please take in consideration $error first
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $httpCode;
|
||||
|
||||
/**
|
||||
* Create a new exception base on ApiError enum.
|
||||
*
|
||||
* @param \App\Enums\Api\ApiError $error
|
||||
* @param integer $httpCode
|
||||
* @param string $message
|
||||
* @param \Throwable|null $previous
|
||||
*/
|
||||
public function __construct(ApiError $error, int $httpCode = 400, string $message = '', \Throwable $previous = null)
|
||||
{
|
||||
$this->error = $error;
|
||||
$this->message = $message;
|
||||
$this->httpCode = $httpCode;
|
||||
parent::__construct(
|
||||
strlen($this->message) ? $this->message : $error->label(),
|
||||
$error->value(),
|
||||
$previous
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Api Error
|
||||
*
|
||||
* @return \App\Enums\Api\ApiError
|
||||
*/
|
||||
public function getApiError(): ApiError
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the http code
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->httpCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used by laravel only.
|
||||
* It uses Log class to print the error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function report()
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
Log::debug(sprintf('FluxCollector API: %s', $this->getMessage()));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
24
app/Exceptions/Api/ApplicationException.php
Normal file
24
app/Exceptions/Api/ApplicationException.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions\Api;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* This is a critical error.
|
||||
* The request could not be handled. You may dump any value you can
|
||||
* get before submitting a ticket to https://support.uwil.fr
|
||||
*/
|
||||
class ApplicationException extends \RuntimeException
|
||||
{
|
||||
/**
|
||||
* This is used by laravel only.
|
||||
* It uses Log class to print the error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function report()
|
||||
{
|
||||
Log::critical(sprintf('API: %s', $this->getMessage()));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,27 +2,39 @@
|
|||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
|
||||
use Throwable;
|
||||
use App\Enums\Api\ApiError;
|
||||
use App\Enums\Api\ApiStatus;
|
||||
use App\Exceptions\Api\ApiErrorException;
|
||||
use App\Http\Resources\Api\ApiResponse;
|
||||
use App\Lib\ApiHelper;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Http\Exceptions\ThrottleRequestsException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var string[]
|
||||
* @var array
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
ApiErrorException::class
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed for validation exceptions.
|
||||
*
|
||||
* @var string[]
|
||||
* @var array
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
|
@ -32,10 +44,132 @@ class Handler extends ExceptionHandler
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
$this->reportable(function (\Throwable $e) {
|
||||
if (app()->bound('sentry')) {
|
||||
app('sentry')->captureException($e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @param \Throwable $e
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function render(mixed $request, \Throwable $e): Response
|
||||
{
|
||||
if (ApiHelper::isRequestForApi($request) and $request->isJson()) {
|
||||
return $this->handleApiException($request, $e);
|
||||
} //end if
|
||||
// @codeCoverageIgnoreStart
|
||||
return parent::render($request, $e);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a json response for any apiV4 error case.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Throwable $e
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
private function handleApiException(Request $request, \Throwable $e): JsonResponse
|
||||
{
|
||||
switch (get_class($e)) {
|
||||
case AuthenticationException::class:
|
||||
/** @var \Illuminate\Auth\AuthenticationException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
401,
|
||||
ApiError::apiAuthFailed->value(),
|
||||
sprintf('%s, %s', ApiError::apiAuthFailed->label(), $e->getMessage())
|
||||
);
|
||||
case MethodNotAllowedHttpException::class:
|
||||
/** @var \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
$e->getStatusCode(),
|
||||
ApiError::methodNotAllowed->value(),
|
||||
sprintf('%s, %s', ApiError::methodNotAllowed->label(), $e->getMessage())
|
||||
);
|
||||
case ValidationException::class:
|
||||
/** @var \Illuminate\Validation\ValidationException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
$e->status,
|
||||
ApiError::validationFailed->value(),
|
||||
sprintf(ApiError::validationFailed->label(), $e->getMessage()),
|
||||
['errors' => $e->errors()]
|
||||
);
|
||||
case ModelNotFoundException::class:
|
||||
/** @var \Illuminate\Database\Eloquent\ModelNotFoundException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
404,
|
||||
ApiError::modelNotFound->value(),
|
||||
sprintf(
|
||||
ApiError::modelNotFound->label(),
|
||||
\array_slice(explode('\\', $e->getModel()), -1)[0]
|
||||
)
|
||||
);
|
||||
case ApiErrorException::class:
|
||||
/** @var \App\Exceptions\Api\ApiErrorException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
$e->getStatusCode(),
|
||||
$e->getCode(),
|
||||
$e->getMessage()
|
||||
);
|
||||
case ThrottleRequestsException::class:
|
||||
/** @var \Illuminate\Http\Exceptions\ThrottleRequestsException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
429,
|
||||
ApiError::throttleRequestsException->value(),
|
||||
ApiError::throttleRequestsException->label()
|
||||
);
|
||||
case AuthorizationException::class:
|
||||
/** @var \Illuminate\Auth\Access\AuthorizationException $e */
|
||||
return $this->getReponse(
|
||||
$request,
|
||||
403,
|
||||
ApiError::authorizationException->value(),
|
||||
ApiError::authorizationException->label()
|
||||
);
|
||||
} //end switch
|
||||
// @codeCoverageIgnoreStart
|
||||
if (config('app.env') === 'testing') {
|
||||
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
|
||||
print_r((string)$e);
|
||||
}
|
||||
return $this->getReponse($request, 500, \intval($e->getCode()), $e->getMessage());
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an Api json response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param integer $httpCode
|
||||
* @param integer $apiCode
|
||||
* @param string $message
|
||||
* @param array $data
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
private static function getReponse(
|
||||
Request $request,
|
||||
int $httpCode,
|
||||
int $apiCode,
|
||||
string $message,
|
||||
array $data = []
|
||||
): JsonResponse {
|
||||
return (new ApiResponse(
|
||||
(object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
|
||||
ApiStatus::KO
|
||||
))->toResponse($request)->setStatusCode($httpCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidXmlException extends Exception
|
||||
class InvalidXmlException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
|
|
|||
19
app/Http/Controllers/Api/ApiController.php
Normal file
19
app/Http/Controllers/Api/ApiController.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Just returns HTTP 200 response.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function ping(): JsonResponse
|
||||
{
|
||||
return response()->json();
|
||||
}
|
||||
}
|
||||
63
app/Http/Controllers/Api/XmlIncoController.php
Normal file
63
app/Http/Controllers/Api/XmlIncoController.php
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\XmlInco\SearchRequest;
|
||||
use App\Http\Resources\Api\XmlListResource;
|
||||
use App\Http\Resources\Api\XmlIncoResource;
|
||||
use App\Models\Service;
|
||||
use App\Models\XmlInco;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class XmlIncoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Returns all service questions.
|
||||
*
|
||||
* @param \App\Http\Requests\Api\XmlInco\SearchRequest $request
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Http\Resources\Json\ResourceCollection
|
||||
*/
|
||||
public function search(SearchRequest $request, Service $service): ResourceCollection
|
||||
{
|
||||
return new XmlListResource($service->arcadiaBrand->xmlIncos()
|
||||
->where(fn (Builder $q) => $this->searchQuery(
|
||||
$q,
|
||||
$request->search,
|
||||
null,
|
||||
'id',
|
||||
'name'
|
||||
))->limit(20)->get(['id', 'name']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ids list of updated resources since a specific date
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @param \Illuminate\Support\Carbon $api_since
|
||||
* @return \Illuminate\Http\Resources\Json\ResourceCollection
|
||||
*/
|
||||
public function getIdListSince(Service $service, Carbon $api_since): ResourceCollection
|
||||
{
|
||||
return new XmlListResource(
|
||||
$service->arcadiaBrand->xmlIncos()
|
||||
->where('xml_incos.updated_at', '>=', $api_since)->get(['id', 'name']),
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Xml Inco resource
|
||||
*
|
||||
* @param \App\Models\XmlInco $api_xml_inco
|
||||
* @return \App\Http\Resources\Api\XmlIncoResource
|
||||
*/
|
||||
public function show(XmlInco $api_xml_inco): XmlIncoResource
|
||||
{
|
||||
return new XmlIncoResource($api_xml_inco);
|
||||
}
|
||||
}
|
||||
71
app/Http/Controllers/Bo/ActivityLogsController.php
Normal file
71
app/Http/Controllers/Bo/ActivityLogsController.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Traits\Controllers\ChangesModelOrder;
|
||||
use App\Traits\Controllers\UpdateModelPublished;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ActivityLogsController extends Controller
|
||||
{
|
||||
use ChangesModelOrder;
|
||||
use UpdateModelPublished;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$this->authorize('isConceptor');
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = ActivityLog::query()->with('user');
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->search;
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$search,
|
||||
null,
|
||||
'model_class',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
trans('validation.custom.target'),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
/** Custom pagination */
|
||||
$activitylogModels = $this->paginate($query);
|
||||
|
||||
return view('bo.pages.activity_logs.index', compact('activitylogModels', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param App\Models\ActivityLog $activity_log
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function show(ActivityLog $activity_log): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$this->authorize('isConceptor');
|
||||
$targetModelClass = $activity_log->model_class;
|
||||
$targetModel = $activity_log->model_class::where(
|
||||
(new $targetModelClass())->getRouteKeyName(),
|
||||
$activity_log->model_id
|
||||
)->first();
|
||||
|
||||
return view('bo.pages.activity_logs.show', [
|
||||
'activitylogModel' => $activity_log,
|
||||
'targetModel' => $targetModel,
|
||||
]);
|
||||
}
|
||||
}
|
||||
54
app/Http/Controllers/Bo/Auth/LoginController.php
Normal file
54
app/Http/Controllers/Bo/Auth/LoginController.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
use AuthenticatesUsers;
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest:backend')->except('logout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the guard to be used during authentication.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||
*/
|
||||
protected function guard(): StatefulGuard
|
||||
{
|
||||
return Auth::guard('backend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application's login form.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function showLoginForm(): View
|
||||
{
|
||||
return view('bo.auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function redirectTo(): string
|
||||
{
|
||||
return response()->redirectToIntended()->getTargetUrl();
|
||||
}
|
||||
}
|
||||
33
app/Http/Controllers/Bo/Auth/ResetPasswordController.php
Normal file
33
app/Http/Controllers/Bo/Auth/ResetPasswordController.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\Auth\StatefulGuard;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ResetPasswordController extends Controller
|
||||
{
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Get the guard to be used during authentication.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Auth\StatefulGuard
|
||||
*/
|
||||
protected function guard(): StatefulGuard
|
||||
{
|
||||
return Auth::guard('backend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Where to redirect users after login.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function redirectTo(): string
|
||||
{
|
||||
return \route('bo.login');
|
||||
}
|
||||
}
|
||||
59
app/Http/Controllers/Bo/BrandController.php
Normal file
59
app/Http/Controllers/Bo/BrandController.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Enums\Pagination\ItemsPerPaginationEnum;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ArcadiaBrand;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BrandController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(ArcadiaBrand::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = ArcadiaBrand::query();
|
||||
|
||||
if ($request->search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$request->search,
|
||||
null,
|
||||
'id',
|
||||
'label',
|
||||
);
|
||||
}
|
||||
|
||||
$this->sortQuery($query);
|
||||
$searchFields = trans('Arcadia id, Label');
|
||||
|
||||
$brandModels = $this->paginate($query, ItemsPerPaginationEnum::twentyfive);
|
||||
|
||||
return view('bo.pages.brands.index', compact('brandModels', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \App\Models\ArcadiaBrand $arcadiaBrand
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function show(ArcadiaBrand $arcadiaBrand)
|
||||
{
|
||||
return view('bo.pages.brands.show', ['brandModel' => $arcadiaBrand]);
|
||||
}
|
||||
}
|
||||
19
app/Http/Controllers/Bo/DashboardController.php
Normal file
19
app/Http/Controllers/Bo/DashboardController.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* Dashboard Page
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('bo.pages.dashboard');
|
||||
}
|
||||
}
|
||||
204
app/Http/Controllers/Bo/ServiceController.php
Normal file
204
app/Http/Controllers/Bo/ServiceController.php
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Services\StoreServiceRequest;
|
||||
use App\Http\Requests\Bo\Services\UpdateServiceRequest;
|
||||
use App\Mail\ServiceCredentialRenewed;
|
||||
use App\Models\ArcadiaBrand;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class ServiceController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): \Illuminate\Contracts\View\View
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = Service::query();
|
||||
|
||||
if ($request->brand) {
|
||||
$query = $query->whereHas('arcadiaBrand', $request->brand);
|
||||
}
|
||||
if ($request->search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$request->search,
|
||||
null,
|
||||
'name',
|
||||
);
|
||||
}
|
||||
|
||||
$this->sortQuery($query);
|
||||
$searchFields = trans('Nom');
|
||||
|
||||
$serviceModels = $query->paginate(12);
|
||||
|
||||
return view('bo.pages.services.index', compact('serviceModels', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(Service $service): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$brandModels = ArcadiaBrand::all();
|
||||
|
||||
return view('bo.pages.services.create', [
|
||||
'serviceModel' => $service,
|
||||
'brandModels' => $brandModels
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Services\StoreServiceRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreServiceRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$service = new Service();
|
||||
$service->fill($request->validated());
|
||||
$service->generateBearerToken();
|
||||
$service->saveOrFail();
|
||||
$this->generateServicePassword($service);
|
||||
if ($service->saveOrFail()) {
|
||||
return redirect()->route('bo.services.edit', $service)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(__('models.classes.service'))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_created', [
|
||||
'model' => __('models.classes.service')
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Service $service): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$brandModels = ArcadiaBrand::all();
|
||||
|
||||
return view('bo.pages.services.edit', [
|
||||
'serviceModel' => $service,
|
||||
'brandModels' => $brandModels
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a spacific resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Services\UpdateServiceRequest $request
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(UpdateServiceRequest $request, Service $service): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $service) {
|
||||
$service->fill($request->validated());
|
||||
if ($service->saveOrFail()) {
|
||||
return redirect()->route('bo.services.edit', $service->id)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(__('models.classes.service'))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.services.edit', $service->id)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(__('models.classes.service'))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the specified resource.
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function show(Service $service): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('bo.pages.services.show', [
|
||||
'serviceModel' => $service
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Service $service): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if ($service->deleteOrFail()) {
|
||||
return redirect()->route('bo.services.index')
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => __('models.classes.service')
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_deleted', [
|
||||
'model' => __('models.classes.service')
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Regen service credentials.
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed, Squiz.Commenting.FunctionComment.Missing
|
||||
public function generateServicePassword(Service $service): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
// * Special Condition for migration from v2 to v3.
|
||||
$service->bearer_token = !Hash::needsRehash($service->bearer_token) ?
|
||||
$service->generateBearerToken() : $service->bearer_token;
|
||||
|
||||
$bearerPwd = $service->generateBearerPassword();
|
||||
$service->saveOrFail();
|
||||
Mail::send(new ServiceCredentialRenewed($service, $user));
|
||||
return back()
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
->with('error', trans("ATTENTION : METTEZ À JOUR LE SITE ! \nNouveau token d'API de :service : ':token' et mot de passe ':password'", [
|
||||
'service' => $service->name,
|
||||
'token' => $service->bearer_token,
|
||||
'password' => $bearerPwd,
|
||||
]));
|
||||
}
|
||||
}
|
||||
90
app/Http/Controllers/Bo/StatsController.php
Normal file
90
app/Http/Controllers/Bo/StatsController.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\StatsByServiceRequest;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceStat;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use League\Csv\Writer;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class StatsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Shows stats by service.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\StatsByServiceRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function viewStatsByService(StatsByServiceRequest $request)
|
||||
{
|
||||
$stats = null;
|
||||
if ($request->has('service')) {
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\ServiceStat> */
|
||||
$stats = ServiceStat::query()
|
||||
->with('service')
|
||||
->whereDate('date', '>=', $request->start)
|
||||
->whereDate('date', '<=', $request->end)
|
||||
->whereHas('service', fn (Builder $q) => $q->where('id', $request->service))
|
||||
->get();
|
||||
}
|
||||
return view('bo.pages.stats.view-stats-by-services', \compact('stats'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports stats by service as a csv file.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\StatsByServiceRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function exportByBrandCsv(StatsByServiceRequest $request): Response
|
||||
{
|
||||
/** @var \App\Models\Service */
|
||||
$service = Service::query()->findOrFail($request->service);
|
||||
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\ServiceStat> */
|
||||
$stats = ServiceStat::query()
|
||||
->with('service.arcadiaBrand')
|
||||
->whereDate('date', '>=', $request->start)
|
||||
->whereDate('date', '<=', $request->end)
|
||||
->whereHas('service', fn (Builder $q) => $q->where('id', $request->service))
|
||||
->get();
|
||||
|
||||
$columns = Schema::getColumnListing((new ServiceStat())->getTable());
|
||||
$columns = Arr::where($columns, fn ($value) => !in_array($value, ['date', 'service_id']));
|
||||
/** @var array<string> $columns */
|
||||
|
||||
$header = '"date";"service";' . implode(';', $columns);
|
||||
|
||||
$w = Writer::createFromString($header . PHP_EOL);
|
||||
$w->setDelimiter(';');
|
||||
$w->setEnclosure('"');
|
||||
$w->setOutputBOM(Writer::BOM_UTF8);
|
||||
|
||||
/** @var \App\Models\ServiceStat $stat */
|
||||
foreach ($stats as $stat) {
|
||||
/** @var array<string,string> $cols */
|
||||
$cols = [
|
||||
$stat->date->isoFormat('YYYY-MM-DD'),
|
||||
$service->label
|
||||
];
|
||||
foreach ($columns as $colname) {
|
||||
$cols[] = $stat->{$colname};
|
||||
}
|
||||
$w->insertOne($cols);
|
||||
}
|
||||
return response((string)$w, 200, [
|
||||
'Content-Encoding' => 'none',
|
||||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'Content-Disposition' => "attachment; filename=\"Statistiques du {$request->start} au {$request->end} pour {$service->name}.csv\"",
|
||||
'Content-Description' => 'File Transfer',
|
||||
]);
|
||||
}
|
||||
}
|
||||
169
app/Http/Controllers/Bo/UsersController.php
Normal file
169
app/Http/Controllers/Bo/UsersController.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Users\StoreUserRequest;
|
||||
use App\Http\Requests\Bo\Users\UpdateUserRequest;
|
||||
use App\Models\User;
|
||||
use App\Traits\Controllers\UpdateModelPublished;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
use UpdateModelPublished;
|
||||
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): \Illuminate\Contracts\View\View
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $userModels */
|
||||
$userModels = User::query();
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->search;
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$userModels,
|
||||
$search,
|
||||
null,
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'role',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
trans('validation.attributes.first_name'),
|
||||
trans('validation.attributes.last_name'),
|
||||
trans('validation.attributes.email'),
|
||||
trans('validation.attributes.role'),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($userModels);
|
||||
|
||||
/** Custom pagination */
|
||||
$userModels = $this->paginate($userModels);
|
||||
|
||||
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(User $user): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('bo.pages.users.create', ['userModel' => $user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Users\StoreUserRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$user = new User();
|
||||
$user->fill($request->validated());
|
||||
|
||||
if ($user->saveOrFail()) {
|
||||
return redirect()->route('bo.users.edit', $user)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param App\Models\User $user
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(User $user): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('bo.pages.users.edit', ['userModel' => $user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Users\UpdateUserRequest $request
|
||||
* @param \App\Models\User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $user) {
|
||||
$user->fill($request->validated());
|
||||
|
||||
if ($user->saveOrFail()) {
|
||||
return redirect()->route('bo.users.edit', $user)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.users.edit', $user)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(User $user): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if ($user->deleteOrFail()) {
|
||||
return redirect()->route('bo.users.index')
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the specified resource.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function duplicate(User $user): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return $this->create($user->replicate());
|
||||
}
|
||||
}
|
||||
59
app/Http/Controllers/Bo/XmlIncoController.php
Normal file
59
app/Http/Controllers/Bo/XmlIncoController.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Enums\Pagination\ItemsPerPaginationEnum;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\XmlInco;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class XmlIncoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(XmlInco::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = XmlInco::query();
|
||||
|
||||
if ($request->search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$request->search,
|
||||
null,
|
||||
'id',
|
||||
'name',
|
||||
);
|
||||
}
|
||||
|
||||
$this->sortQuery($query);
|
||||
$searchFields = trans('Arcadia id, Nom');
|
||||
|
||||
$xmlIncoModels = $this->paginate($query, ItemsPerPaginationEnum::twentyfive);
|
||||
|
||||
return view('bo.pages.xml_incos.index', compact('xmlIncoModels', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \App\Models\XmlInco $xml_inco
|
||||
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
|
||||
*/
|
||||
public function show(XmlInco $xml_inco)
|
||||
{
|
||||
return view('bo.pages.xml_incos.show', ['xmlIncoModel' => $xml_inco]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,230 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\Pagination\ItemsPerPaginationEnum;
|
||||
use App\Enums\Theme\BootstrapThemeEnum;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
use AuthorizesRequests;
|
||||
use ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Build Search query on specified fields
|
||||
* splitting search into words or using whole sentence
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
|
||||
* @param string $search The query string.
|
||||
* @param callable $searchQuery The query string.
|
||||
* @param string ...$fields Either a simple array or a string
|
||||
* in case of a string the two fields
|
||||
* would be concatenate to make the search.
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function searchQuery(
|
||||
Builder $query,
|
||||
string $search,
|
||||
callable $searchQuery = null,
|
||||
string ...$fields
|
||||
): \Illuminate\Database\Eloquent\Builder {
|
||||
if (count($fields)) {
|
||||
// Search using words.
|
||||
$words = explode(' ', $search);
|
||||
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
|
||||
foreach ($words as $word) {
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
$this->searchOnFields($query, $word, $fields);
|
||||
} //end foreach
|
||||
// Search whole using whole sentence.
|
||||
$this->searchOnFields($query, $search, $fields);
|
||||
if ($searchQuery) {
|
||||
$searchQuery($query);
|
||||
}
|
||||
});
|
||||
} //end if
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query search on all fields
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $search
|
||||
* @param array $fields
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
private function searchOnFields(
|
||||
Builder $query,
|
||||
string $search,
|
||||
array $fields
|
||||
): \Illuminate\Database\Eloquent\Builder {
|
||||
$table = $query->getModel()->getTable();
|
||||
foreach ($fields as $field) {
|
||||
$field = str_replace('?', '\?', $field);
|
||||
if (is_array($field)) {
|
||||
$query->orWhereRaw(
|
||||
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($field)->map(function ($q) use ($table) {
|
||||
return "`$table`.`$q`";
|
||||
})->implode(', \' \', ')),
|
||||
['%' . htmlspecialchars($search) . '%']
|
||||
);
|
||||
return $query;
|
||||
}
|
||||
// Get models woth enum's label.
|
||||
if (is_array($this->getSearchValue($query, $search, $field))) {
|
||||
foreach ($this->getSearchValue($query, $search, $field) as $array) {
|
||||
$query->orWhereRaw(
|
||||
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
|
||||
['%' . htmlspecialchars($array->value) . '%']
|
||||
);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
// Get models with relation's name/label.
|
||||
if (Str::endsWith($field, '_id') && $query->getModel()->has(Str::remove('_id', $field))) {
|
||||
$query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) {
|
||||
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
|
||||
$queryRelation->where('name', 'like', '%' . $search . '%');
|
||||
} elseif (Schema::hasColumn(Str::remove('_id', $field) . 's', 'label')) {
|
||||
$queryRelation->where('label', 'like', '%' . $search . '%');
|
||||
}
|
||||
});
|
||||
return $query;
|
||||
}
|
||||
$query->orWhereRaw(
|
||||
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
|
||||
['%' . htmlspecialchars($this->getSearchValue($query, $search, $field)) . '%']
|
||||
);
|
||||
} //end foreach
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search value (on string or Enum).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param string $search
|
||||
* @param string $field
|
||||
* @return string|array
|
||||
*/
|
||||
protected function getSearchValue(Builder $query, string $search, string $field): string|array
|
||||
{
|
||||
if (
|
||||
count($query->getModel()->getCasts()) and // Check if the model has casts.
|
||||
isset($query->getModel()->getCasts()[$field]) and // Check if there is cast about specific field.
|
||||
enum_exists($query->getModel()->getCasts()[$field]) // Check if the cast is an enum.
|
||||
) {
|
||||
$cases = collect($query->getModel()->getCasts()[$field]::toArray());
|
||||
$enum = $cases->filter(function ($item) use ($search) {
|
||||
return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label));
|
||||
});
|
||||
return $enum->toArray();
|
||||
} else {
|
||||
return $search;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort columns with a query.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $ignore
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder
|
||||
{
|
||||
$rName = request()->route()->getName();
|
||||
$table = $query->getModel()->getTable();
|
||||
if (request()->sort_col) {
|
||||
Session::put("$rName.sort_col", request()->sort_col);
|
||||
Session::put("$rName.sorted", true);
|
||||
}
|
||||
if (request()->sort_way) {
|
||||
Session::put("$rName.sort_way", request()->sort_way);
|
||||
Session::put("$rName.sorted", true);
|
||||
}
|
||||
if (request()->rst) {
|
||||
Session::remove("$rName.sorted");
|
||||
Session::remove("$rName.sort_col");
|
||||
Session::remove("$rName.sort_way");
|
||||
}
|
||||
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
|
||||
if (
|
||||
!isset($ignore) or
|
||||
(isset($ignore) and !in_array(request()->sort_col, $ignore))
|
||||
) {
|
||||
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
|
||||
}
|
||||
} elseif (Schema::hasColumn($table, 'order')) {
|
||||
$query = $query->orderBy('order', 'ASC');
|
||||
Session::put("$rName.sort_col", 'order');
|
||||
Session::put("$rName.sort_way", 'asc');
|
||||
} elseif (Schema::hasColumn($table, 'updated_at')) {
|
||||
$query = $query->orderBy('updated_at', 'DESC');
|
||||
Session::put("$rName.sort_col", 'updated_at');
|
||||
Session::put("$rName.sort_way", 'desc');
|
||||
} elseif (Schema::hasColumn($table, 'created_at')) {
|
||||
$query = $query->orderBy('created_at', 'DESC');
|
||||
Session::put("$rName.sort_col", 'created_at');
|
||||
Session::put("$rName.sort_way", 'desc');
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize pagination with cache or config (default).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
protected function paginate(
|
||||
Builder $query,
|
||||
ItemsPerPaginationEnum $pagination = null
|
||||
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
|
||||
$currentRoutePath = Str::slug(request()->route()->getName());
|
||||
$cacheKey = "pagination.{$currentRoutePath}";
|
||||
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve;
|
||||
$pagination = ToolboxHelper::getValidatedEnum(
|
||||
Cache::get($cacheKey, $pagination->value()),
|
||||
'pagination',
|
||||
'\App\Enums\Pagination\ItemsPerPaginationEnum',
|
||||
);
|
||||
if (Cache::get($cacheKey) !== $pagination) {
|
||||
Cache::put($cacheKey, $pagination);
|
||||
}
|
||||
$query = $query->paginate($pagination);
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bootstrap theme or light (default).
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
protected function setTheme(Request $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$theme = ToolboxHelper::getValidatedEnum(
|
||||
$request->theme ?: Cache::get('theme', BootstrapThemeEnum::light->value()),
|
||||
'theme',
|
||||
'\App\Http\Controllers\BootstrapThemeEnum',
|
||||
);
|
||||
if (Cache::get("theme") !== $theme) {
|
||||
Cache::put("theme", $theme);
|
||||
}
|
||||
return redirect()->back()->with('success', __('crud.messages.theme_updated'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,37 +11,40 @@ class Kernel extends HttpKernel
|
|||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\HtmlSanitizer::class
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\SecureHeaders::class
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
'throttle:api',
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
|
||||
\App\Http\Middleware\JsonOnly::class,
|
||||
\App\Http\Middleware\ApiResponse::class,
|
||||
\App\Http\Middleware\ApiCheckTimeStamps::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
|
@ -51,16 +54,17 @@ class Kernel extends HttpKernel
|
|||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
|
|
|
|||
43
app/Http/Middleware/ApiCheckTimeStamps.php
Normal file
43
app/Http/Middleware/ApiCheckTimeStamps.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\Api\ApiError;
|
||||
use App\Exceptions\Api\ApiErrorException;
|
||||
use App\Lib\ApiHelper;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* Check Http Header Timestamp with request timestamp
|
||||
* to prevent replay attacks
|
||||
* https://restfulapi.net/security-essentials/
|
||||
*/
|
||||
class ApiCheckTimeStamps
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
* @throws \App\Exceptions\Api\V4\ApiErrorException With ApiError::wrongTimeStamp
|
||||
* if timestamp has an incorrect
|
||||
* format or is timed out.
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
if (ApiHelper::isRequestForApi($request)) {
|
||||
if (!\is_string($request->timestamp) or !Carbon::hasFormat($request->timestamp, 'Y-m-d H:i:s')) {
|
||||
throw new ApiErrorException(ApiError::wrongTimeStamp);
|
||||
}
|
||||
if (
|
||||
Carbon::parse($request->timestamp, 'utc')
|
||||
->isBefore(Carbon::now('UTC')->subSeconds(config('api.timestampTimeout', 10)))
|
||||
) {
|
||||
throw new ApiErrorException(ApiError::wrongTimeStamp);
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
59
app/Http/Middleware/ApiResponse.php
Normal file
59
app/Http/Middleware/ApiResponse.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\Api\ApiStatus;
|
||||
use App\Http\Resources\Api\ApiResponse as ApiResponseResource;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ApiResponse
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
* @throws \ErrorException If the received response is not a JsonResponse.
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
/** @var \Illuminate\Http\JsonResponse $response */
|
||||
$response = $next($request);
|
||||
|
||||
if (\get_class($response) !== JsonResponse::class) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \ErrorException('All api responses should be json.');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// Set no cache header.
|
||||
$response->header('Cache-Control', 'No-Store');
|
||||
|
||||
/** @var \App\Models\Service|null */
|
||||
if (\optional($service = app(Service::class))->id) {
|
||||
$service->incrementStat();
|
||||
}
|
||||
|
||||
/** @var object $data */
|
||||
$data = $response->getData();
|
||||
|
||||
/** Check if it's and Api Response. */
|
||||
if (
|
||||
gettype($data) === "object" and
|
||||
isset($data->status) and
|
||||
isset($data->data) and
|
||||
isset($data->version) and
|
||||
in_array($data->status, [ApiStatus::OK->value(), ApiStatus::KO->value()])
|
||||
) {
|
||||
/** Return direct ApiResponse */
|
||||
return $response;
|
||||
}
|
||||
|
||||
$status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK : ApiStatus::KO;
|
||||
|
||||
return $response->setData(new ApiResponseResource((object)$data, $status));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,19 +3,37 @@
|
|||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
protected function redirectTo($request)
|
||||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
if (! $request->expectsJson()) {
|
||||
$logins = [
|
||||
'backend' => route('bo.login')
|
||||
];
|
||||
if (!$request->expectsJson()) {
|
||||
// * Notify must login
|
||||
Session::flash('warning', trans('Veuillez vous connecter pour continuer'));
|
||||
|
||||
foreach ($request->route()->gatherMiddleware() as $middleware) {
|
||||
if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) {
|
||||
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
|
||||
if ($route) {
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
return route('login');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ class EncryptCookies extends Middleware
|
|||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
protected $except = [];
|
||||
}
|
||||
|
|
|
|||
61
app/Http/Middleware/HtmlSanitizer.php
Normal file
61
app/Http/Middleware/HtmlSanitizer.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class HtmlSanitizer
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|array ...$params
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next, string|array ...$params): Response
|
||||
{
|
||||
$inputs = $request->all();
|
||||
/** @var callable|null */
|
||||
$cleanInput = null;
|
||||
$cleanInput = function (
|
||||
null|string|array &$input,
|
||||
string $name,
|
||||
?string $rootName = null
|
||||
) use (
|
||||
$params,
|
||||
&$cleanInput
|
||||
): void {
|
||||
// * Nested element array
|
||||
if (is_array($input)) {
|
||||
array_walk_recursive($input, $cleanInput, $name);
|
||||
}
|
||||
if (!is_string($input)) {
|
||||
return;
|
||||
}
|
||||
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
|
||||
// ? Is a json input ?
|
||||
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
|
||||
// ! Decode each json field recursive.
|
||||
\array_walk_recursive($isJson, function (string|array &$item) {
|
||||
if (\is_string($item)) {
|
||||
$item = clean($item);
|
||||
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$input = clean($input);
|
||||
$input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input);
|
||||
}
|
||||
} else {
|
||||
$input = strip_tags($input);
|
||||
}
|
||||
};
|
||||
\array_walk($inputs, $cleanInput);
|
||||
$request->merge($inputs);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
27
app/Http/Middleware/JsonOnly.php
Normal file
27
app/Http/Middleware/JsonOnly.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class JsonOnly
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, \Closure $next): mixed
|
||||
{
|
||||
if (
|
||||
(!$request->isJson() or
|
||||
!$request->expectsJson() or
|
||||
is_null(json_decode($request->getContent())))
|
||||
) {
|
||||
abort(400, 'Invalid JSON');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,7 @@ class PreventRequestsDuringMaintenance extends Middleware
|
|||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
protected $except = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,39 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null ...$guards
|
||||
* @return mixed
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
* @param string|null ...$guards
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \RuntimeException If guard is unhandled.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, ...$guards)
|
||||
public function handle(Request $request, \Closure $next, ...$guards): Response
|
||||
{
|
||||
// phpcs:enable phpcs:disable
|
||||
$guards = empty($guards) ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
if (auth($guard)->check()) {
|
||||
switch ($guard) {
|
||||
case 'frontend':
|
||||
return \redirect()->route('fo.homepage');
|
||||
case 'backend':
|
||||
if (!$request->routeIs('bo.*')) {
|
||||
continue;
|
||||
}
|
||||
return \redirect()->route('bo.dashboard');
|
||||
default:
|
||||
throw new \RuntimeException("Unhandled guard `{$guard}` .");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
103
app/Http/Middleware/SecureHeaders.php
Normal file
103
app/Http/Middleware/SecureHeaders.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Facades\Vite;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SecureHeaders
|
||||
{
|
||||
/**
|
||||
* Enumerate headers which you do not want in your application's responses.
|
||||
* Great starting point would be to go check out @Scott_Helme's:
|
||||
* https://securityheaders.com/
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $unwantedHeaderList = [
|
||||
'X-Powered-By',
|
||||
'Server',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// phpcs:enable
|
||||
View::share('nonce', $nonce = Vite::useCspNonce());
|
||||
// * For VueJS dev tool to execute
|
||||
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
|
||||
$scheme = config('app.env') === 'local' ? 'http' : 'https';
|
||||
$reportUri = Route::has('cspReportUri') ?
|
||||
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
|
||||
$this->removeUnwantedHeaders($this->unwantedHeaderList);
|
||||
$response = $next($request);
|
||||
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
||||
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
|
||||
$cspPolicies = [
|
||||
"default-src 'none';",
|
||||
"manifest-src 'self';",
|
||||
// * CKEDITOR Specific https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/csp.html
|
||||
"connect-src 'self' blob:;",
|
||||
"img-src * data: blob:;",
|
||||
'frame-src ' . implode(' ', [
|
||||
'https://www.google.com/recaptcha/',
|
||||
'https://recaptcha.google.com/recaptcha/',
|
||||
'https://www.google.com/maps/'
|
||||
]) . ';',
|
||||
"font-src 'self' data: https://fonts.gstatic.com;",
|
||||
// ! Using CKEditor will must have unsafe-inline style
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
|
||||
'https://cdnjs.cloudflare.com',
|
||||
// * File Manager
|
||||
'https://fonts.googleapis.com',
|
||||
'https://cdn.jsdelivr.net',
|
||||
'https://use.fontawesome.com',
|
||||
'https://fonts.googleapis.com'
|
||||
]) . ';',
|
||||
// ! If you dont use ckeditor use this
|
||||
// "style-src 'self' 'nonce-$nonce';",
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"script-src 'self' {$jsDev} " . \implode(' ', [
|
||||
'https: blob:',
|
||||
'https://www.googletagmanager.com',
|
||||
'https://cdnjs.cloudflare.com',
|
||||
"$scheme://www.google-analytics.com",
|
||||
]) . ';'
|
||||
];
|
||||
|
||||
$response->headers->set(
|
||||
'Content-Security-Policy',
|
||||
implode(' ', $cspPolicies) . $reportUri
|
||||
);
|
||||
$response->headers->set('Feature-Policy', 'fullscreen \'self\';');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unwanted headers
|
||||
*
|
||||
* @param array $headerList
|
||||
* @return void
|
||||
*/
|
||||
private function removeUnwantedHeaders(array $headerList): void
|
||||
{
|
||||
foreach ($headerList as $header) {
|
||||
header_remove($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ class TrimStrings extends Middleware
|
|||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class TrustHosts extends Middleware
|
|||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,17 +10,17 @@ class TrustProxies extends Middleware
|
|||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array|string|null
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
* @var integer
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
|
|
|
|||
15
app/Http/Middleware/ValidateSignature.php
Normal file
15
app/Http/Middleware/ValidateSignature.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [];
|
||||
}
|
||||
|
|
@ -9,9 +9,9 @@ class VerifyCsrfToken extends Middleware
|
|||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
'/csp-report'
|
||||
];
|
||||
}
|
||||
|
|
|
|||
42
app/Http/Requests/Api/XmlInco/SearchRequest.php
Normal file
42
app/Http/Requests/Api/XmlInco/SearchRequest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\XmlInco;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SearchRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth('api')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'search' => is_string($this->search) ? strip_tags($this->search) : null
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string,\Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'search' => 'required|string|min:1'
|
||||
];
|
||||
}
|
||||
}
|
||||
57
app/Http/Requests/Bo/Services/StoreServiceRequest.php
Normal file
57
app/Http/Requests/Bo/Services/StoreServiceRequest.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Services;
|
||||
|
||||
use DragonCode\Support\Facades\Helpers\Arr;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreServiceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'arcadia_brand_id' => Arr::get(Arr::first(Arr::wrap($this->arcadiaBrand ?? [])), 'id')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|min:5|max:255',
|
||||
'arcadia_brand_id' => 'required|integer|min:0|exists:arcadia_brands,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => trans('Nom du service'),
|
||||
'arcadia_brand_id' => trans('Id de la marque'),
|
||||
];
|
||||
}
|
||||
}
|
||||
7
app/Http/Requests/Bo/Services/UpdateServiceRequest.php
Normal file
7
app/Http/Requests/Bo/Services/UpdateServiceRequest.php
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Services;
|
||||
|
||||
class UpdateServiceRequest extends StoreServiceRequest
|
||||
{
|
||||
}
|
||||
55
app/Http/Requests/Bo/StatsByServiceRequest.php
Normal file
55
app/Http/Requests/Bo/StatsByServiceRequest.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class StatsByServiceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Illuminate\Validation\ValidationException If given dates are unparsable of period
|
||||
* is bigger than 1 year.
|
||||
* @throws \Exception If carbon date processing failed.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
try {
|
||||
if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)) + 1) > 365) {
|
||||
throw ValidationException::withMessages([
|
||||
'Vous devez sélectionner une période de maximum 1 ans'
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if (\get_class($e) !== ValidationException::class) {
|
||||
throw ValidationException::withMessages([
|
||||
'Les dates sélectionnées ne sembles pas être valides'
|
||||
]);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
return [
|
||||
'brand' => 'sometimes|exists:arcadia_brands,id',
|
||||
'start' => 'required_with:brand|before_or_equal:end|date|date_format:Y-m-d',
|
||||
'end' => 'required_with:brand|after_or_equal:start|date|date_format:Y-m-d|after_or_equal:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
42
app/Http/Requests/Bo/UpdateChangeOrderRequest.php
Normal file
42
app/Http/Requests/Bo/UpdateChangeOrderRequest.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateChangeOrderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'direction' => $this->direction === 'up' ? true : false
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'direction' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
229
app/Http/Requests/Bo/UpdateMiniCrudRequest.php
Normal file
229
app/Http/Requests/Bo/UpdateMiniCrudRequest.php
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use App\Enums\InputType;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
abstract class UpdateMiniCrudRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* The model parameter name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $modelName = null;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
* @phpcs:disable Generic.Metrics.NestingLevel.TooHigh
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
// phpcs:enable
|
||||
$rules = [];
|
||||
foreach ($this->{$this->modelName}->data as $iptName => $input) {
|
||||
$input = \json_decode(\json_encode($input), true);
|
||||
switch ($input['inputType']) {
|
||||
case InputType::number->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate('required|numeric', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::videoYoutube->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate('regex:%([^"&?\/\s]{11})%i', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::videoVimeo->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate('regex:%([0-9]{8,10})%i', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::video->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate([
|
||||
'regex:%([0-9]{8,10}|[^"&?\/\s]{11})%i'
|
||||
], $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::string->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $this->compileValidate('required|string|min:3', $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::text->value:
|
||||
$mergeRules = $this->compileValidate('required|string|min:15', $input);
|
||||
$mergeRules = collect($mergeRules)
|
||||
->map(function ($rule) {
|
||||
if (\strpos($rule, 'max:') !== false) {
|
||||
$max = \intval(str_replace('max:', '', $rule));
|
||||
return function ($attribute, $value, $fail) use ($max) {
|
||||
if (\strlen(\strip_tags($value)) > $max) {
|
||||
$fail("$attribute ne doit pas excéder $max caractères");
|
||||
}
|
||||
};
|
||||
}
|
||||
return $rule;
|
||||
});
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => $mergeRules
|
||||
]);
|
||||
break;
|
||||
case InputType::image->value:
|
||||
$required = $this->isUpdate() ? 'sometimes' : 'required';
|
||||
|
||||
$ipt = $this->data[$iptName];
|
||||
$max = isset($ipt['dimensionsMax']);
|
||||
$dim = \is_string($input['dimensions']) ? collect(explode(
|
||||
',',
|
||||
(isset($ipt['dimensionsMax']) and is_string($ipt['dimensionsMax'])) ?
|
||||
$ipt['dimensionsMax'] : $input['dimensions']
|
||||
))->mapWithKeys(function ($v) {
|
||||
$v = explode(' ', $v);
|
||||
return [$v[0] => $v[1]];
|
||||
}) : $input['dimensions'];
|
||||
|
||||
// * Adapt dimensions
|
||||
$sizes = (object)[
|
||||
'minHeight' => $dim['height'],
|
||||
'maxHeight' => $dim['height'],
|
||||
'minWith' => $dim['width'],
|
||||
'maxWith' => $dim['width'],
|
||||
];
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
function (string $attribute, $value, callable $fail) use ($sizes) {
|
||||
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
|
||||
}
|
||||
],
|
||||
$this->compileValidate(
|
||||
implode('|', [
|
||||
$required,
|
||||
'image',
|
||||
'mimes:png,jpg,jpeg',
|
||||
sprintf(
|
||||
'dimensions:%s=%d,%s=%d',
|
||||
$max ? 'max_height' : 'height',
|
||||
$dim['height'],
|
||||
$max ? 'max_width' : 'width',
|
||||
$dim['width']
|
||||
)
|
||||
]),
|
||||
$input
|
||||
),
|
||||
"data.$iptName.alt" => $this->compileValidate("$required|string|min:5", $input),
|
||||
"data.$iptName.title" => $this->compileValidate("$required|string|min:5", $input)
|
||||
]);
|
||||
break;
|
||||
case InputType::url->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
'required', 'url',
|
||||
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
try {
|
||||
ToolboxHelper::assertNoInternalUrls($attribute, $value);
|
||||
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
|
||||
} catch (ValidationException $e) {
|
||||
$fail(collect($e->errors())->first()[0]);
|
||||
}
|
||||
}
|
||||
]
|
||||
]);
|
||||
break;
|
||||
case InputType::boolean->value:
|
||||
$all = $this->all();
|
||||
$nested = ToolboxHelper::dotStringToNestedArray(
|
||||
"data.$iptName.value",
|
||||
$this->input("data.$iptName.value") ? true : false
|
||||
);
|
||||
$all = ToolboxHelper::arrayMergeRecursiveDistinct($all, $nested);
|
||||
$this->merge($all);
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => 'boolean'
|
||||
]);
|
||||
break;
|
||||
} //end switch
|
||||
} //end foreach
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string|array $inputTypeValidate
|
||||
* @param array $input
|
||||
* @return array
|
||||
*/
|
||||
public function compileValidate(string|array $inputTypeValidate, array $input): array
|
||||
{
|
||||
if (\is_string($inputTypeValidate)) {
|
||||
$inputTypeValidate = explode('|', $inputTypeValidate);
|
||||
}
|
||||
/** @var array|string */
|
||||
$inputValidate = \collect($input)->get('validate', $input);
|
||||
$inputValidate = \is_string($inputValidate) ? explode('|', $inputValidate) : $inputValidate;
|
||||
foreach ($inputValidate as $validate) {
|
||||
if (!\in_array($validate, $inputTypeValidate)) {
|
||||
$inputTypeValidate[] = $validate;
|
||||
}
|
||||
}
|
||||
|
||||
$test = \collect($inputTypeValidate)->all();
|
||||
$test = \collect($inputTypeValidate)->sort(function ($valueA, $valueB) {
|
||||
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
|
||||
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
|
||||
return $valueA - $valueB;
|
||||
})->unique()->all();
|
||||
|
||||
return \collect($inputTypeValidate)->unique()->sort(function ($valueA, $valueB) {
|
||||
$valueA = $this->ruleHasToComeFirst($valueA) ? 0 : 2;
|
||||
$valueB = $this->ruleHasToComeFirst($valueB) ? 1 : 2;
|
||||
return $valueA - $valueB;
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the rulle need to come first ?
|
||||
*
|
||||
* @param string $rule
|
||||
* @return boolean
|
||||
*/
|
||||
private function ruleHasToComeFirst(string $rule): bool
|
||||
{
|
||||
$rule = \collect(\explode(':', $rule, 1))->filter()->first(null, '');
|
||||
return \collect([
|
||||
'nullable', 'present', 'required', 'required_if',
|
||||
'required_unless', 'required_with', 'required_with_all',
|
||||
'required_without', 'required_without_all', 'required_with_all',
|
||||
'missing', 'missing_if', 'missing_unless',
|
||||
'missing_with', 'missing_with_all',
|
||||
])->first(fn (string $firstRule) => \strpos($rule, $firstRule) === 0) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this request an update request ?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function isUpdate(): bool
|
||||
{
|
||||
return $this->isMethod('PUT');
|
||||
}
|
||||
}
|
||||
74
app/Http/Requests/Bo/Users/StoreUserRequest.php
Normal file
74
app/Http/Requests/Bo/Users/StoreUserRequest.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Users;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class StoreUserRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth('backend')->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'published' => $this->published ? true : false
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'first_name' => 'required|string|min:3|max:255',
|
||||
'last_name' => 'required|string|min:3|max:255',
|
||||
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
|
||||
'password' => [
|
||||
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
|
||||
->letters()
|
||||
->mixedCase()
|
||||
->numbers()
|
||||
->symbols()
|
||||
->uncompromised()
|
||||
],
|
||||
'role' => ['required', new Enum(RoleEnum::class)],
|
||||
'published' => 'required|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'first_name' => trans('validation.attributes.first_name'),
|
||||
'last_name' => trans('validation.attributes.last_name'),
|
||||
'email' => trans('validation.attributes.email'),
|
||||
'password' => trans('validation.attributes.password'),
|
||||
'role' => trans('validation.attributes.role'),
|
||||
'published' => trans('validation.custom.publication')
|
||||
];
|
||||
}
|
||||
}
|
||||
48
app/Http/Requests/Bo/Users/UpdateUserRequest.php
Normal file
48
app/Http/Requests/Bo/Users/UpdateUserRequest.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Users;
|
||||
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class UpdateUserRequest extends StoreUserRequest
|
||||
{
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'published' => $this->published ? true : false,
|
||||
'password' => $this->password ? $this->password : $this->route('user')->password,
|
||||
'password_confirmation' => $this->password_confirmation ?
|
||||
$this->password_confirmation :
|
||||
$this->route('user')->password
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'email' => [
|
||||
'required',
|
||||
'email:rfc,strict,dns,spoof,filter',
|
||||
'unique:users,email,' . $this->route('user')->id . ',id',
|
||||
],
|
||||
'password' => [
|
||||
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
|
||||
->letters()
|
||||
->mixedCase()
|
||||
->numbers()
|
||||
->symbols()
|
||||
->uncompromised()
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
43
app/Http/Resources/Api/ApiResponse.php
Normal file
43
app/Http/Resources/Api/ApiResponse.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api;
|
||||
|
||||
use App\Enums\Api\ApiStatus;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class ApiResponse extends JsonResource
|
||||
{
|
||||
/** @var \stdClass|null */
|
||||
protected $data;
|
||||
|
||||
/** @var \App\Enums\ApiStatus */
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* @param \stdClass|null $data
|
||||
* @param \App\Enums\Api\ApiStatus $status
|
||||
*/
|
||||
public function __construct(\stdClass|null $data = null, ApiStatus $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
//phpcs:enable
|
||||
return [
|
||||
'status' => $this->status->value(),
|
||||
'data' => (array)($this->data ?? []),
|
||||
'version' => config('api.version')
|
||||
];
|
||||
}
|
||||
}
|
||||
47
app/Http/Resources/Api/XmlIncoResource.php
Normal file
47
app/Http/Resources/Api/XmlIncoResource.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class XmlIncoResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* The resource instance.
|
||||
*
|
||||
* @var \App\Models\XmlInco
|
||||
*/
|
||||
public $resource;
|
||||
|
||||
/**
|
||||
* XmlInco columns to keep
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $skel = [
|
||||
'id', 'name', 'description', 'tips',
|
||||
'preparation_advices', 'sell_arguments',
|
||||
'allegations', 'ingredient_list', 'allergen_list',
|
||||
'nutritional_values', 'portion_amount', 'portion_type',
|
||||
'is_seasonal', 'updated_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return array<string, mixed>
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
|
||||
// phpcs:enable
|
||||
return \array_merge(
|
||||
Arr::only($this->resource->toArray(), $this->skel),
|
||||
['updated_at' => $this->resource->updated_at->toIso8601String()]
|
||||
);
|
||||
}
|
||||
}
|
||||
54
app/Http/Resources/Api/XmlListResource.php
Normal file
54
app/Http/Resources/Api/XmlListResource.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api;
|
||||
|
||||
use App\Models\XmlInco;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class XmlListResource extends JsonResourceCollection
|
||||
{
|
||||
/** @var \Illuminate\Support\Collection<\App\Models\XmlInco> */
|
||||
protected $xmlIncoList = null;
|
||||
|
||||
/**
|
||||
* Send only ids
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $onlyIds = false;
|
||||
|
||||
/**
|
||||
* Create a new resource instance.
|
||||
*
|
||||
* @param mixed $resource
|
||||
* @param \Illuminate\Support\Collection<\App\Models\XmlInco> $xmlIncoList
|
||||
* @return void
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function __construct(mixed $resource, Collection $xmlIncoList = null)
|
||||
{
|
||||
//phpcs:enable
|
||||
$this->xmlIncoList = $xmlIncoList;
|
||||
parent::__construct($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Support\Collection
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
*/
|
||||
public function toArray(Request $request): \Illuminate\Support\Collection
|
||||
{
|
||||
//phpcs:enable
|
||||
return $this->collection->map(function (XmlInco $xml_inco) {
|
||||
return [
|
||||
'id' => $xml_inco->id,
|
||||
'name' => $xml_inco->name
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
372
app/Jobs/Arcadia/XArcadiaFlux.php
Normal file
372
app/Jobs/Arcadia/XArcadiaFlux.php
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\Arcadia;
|
||||
|
||||
use App\Enums\LogType;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
abstract class XArcadiaFlux implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The Laravel disk name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $disk = null;
|
||||
|
||||
/**
|
||||
* This is for Laravel Console usage
|
||||
*
|
||||
* @var \Illuminate\Console\Command|null
|
||||
*/
|
||||
public $console = null;
|
||||
|
||||
/**
|
||||
* This is for Laravel Console usage
|
||||
*
|
||||
* @var \Symfony\Component\Console\Helper\ProgressBar|null
|
||||
*/
|
||||
public $progressBar = null;
|
||||
|
||||
/**
|
||||
* invalid xml folder
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $invalidFolderName = 'invalid';
|
||||
|
||||
/**
|
||||
* The relative folder where to archive data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $archiveFolderName = 'archives';
|
||||
|
||||
/**
|
||||
* The files to ignore
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoreFiles = [
|
||||
'.gitignore', '.gitkeep'
|
||||
];
|
||||
|
||||
/**
|
||||
* Check if the file extension
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $extension
|
||||
* @return boolean
|
||||
*/
|
||||
protected function checkExtension(string $filename, string $extension): bool
|
||||
{
|
||||
$isExtension = strtolower(pathinfo($filename, \PATHINFO_EXTENSION)) === $extension;
|
||||
if (!$isExtension) {
|
||||
Log::warning(sprintf(
|
||||
'%s : unwanted file type in %s wanted %s',
|
||||
get_class($this),
|
||||
$filename,
|
||||
$extension
|
||||
));
|
||||
}
|
||||
return $isExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the file to invalid folder
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void
|
||||
*/
|
||||
protected function moveToInvalid(string $filename): void
|
||||
{
|
||||
$newFileName = $filename;
|
||||
if (!$this->getDisk()->exists($filename)) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to invalid (not found)',
|
||||
$filename
|
||||
),
|
||||
LogType::warning
|
||||
);
|
||||
return;
|
||||
}
|
||||
if ($this->getDisk()->exists(sprintf('%s/%s', $this->invalidFolderName, $filename))) {
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$newFileName = \sprintf(
|
||||
'%s_%d.%s',
|
||||
\substr($filename, 0, 0 - (strlen($ext) + 1)),
|
||||
\time(),
|
||||
$ext
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->invalidFolderName, $newFileName))) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to invalid',
|
||||
$filename
|
||||
),
|
||||
LogType::warning
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to invalid',
|
||||
$filename
|
||||
),
|
||||
LogType::warning
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppression des fichiers dans
|
||||
* invalid qui ont plus de 6 mois
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearOldInvalids(): void
|
||||
{
|
||||
$disk = $this->getDisk();
|
||||
$invalids = $disk->files($this->invalidFolderName);
|
||||
|
||||
// /** @var File */
|
||||
foreach ($invalids as $invalid) {
|
||||
$pi = \pathinfo($invalid, \PATHINFO_BASENAME);
|
||||
if (in_array($pi, $this->ignoreFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($disk->lastModified($invalid) < strtotime(Carbon::now()->subMonth(6))) {
|
||||
if (!$disk->delete($invalid)) {
|
||||
Log::critical(sprintf('%s : Could not delete file %s', get_class($this), $invalid));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the file to archives
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void
|
||||
*/
|
||||
protected function moveToArchives(string $filename): void
|
||||
{
|
||||
$newFileName = $filename;
|
||||
if (!$this->getDisk()->exists($filename)) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to archives (not found)',
|
||||
$filename
|
||||
),
|
||||
LogType::warning
|
||||
);
|
||||
return;
|
||||
}
|
||||
if ($this->getDisk()->exists(sprintf('%s/%s', $this->archiveFolderName, $filename))) {
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$newFileName = \sprintf(
|
||||
'%s_%d.%s',
|
||||
\substr($filename, 0, 0 - (strlen($ext) + 1)),
|
||||
\time(),
|
||||
$ext
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->archiveFolderName, $newFileName))) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to archives',
|
||||
$filename
|
||||
),
|
||||
LogType::warning
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to archives',
|
||||
$filename
|
||||
),
|
||||
LogType::warning
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppression des fichiers dans
|
||||
* archives qui ont plus de 2j
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearOldArchives(): void
|
||||
{
|
||||
$disk = $this->getDisk();
|
||||
$archives = $disk->files($this->archiveFolderName);
|
||||
|
||||
// /** @var File */
|
||||
foreach ($archives as $archive) {
|
||||
$pi = \pathinfo($archive, \PATHINFO_BASENAME);
|
||||
if (in_array($pi, $this->ignoreFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($disk->lastModified($archive) < strtotime(Carbon::now()->subDays(2))) {
|
||||
if (!$disk->delete($archive)) {
|
||||
Log::critical(sprintf('%s : Could not delete file %s', get_class($this), $archive));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concerned disk
|
||||
*
|
||||
* @return \Illuminate\Contracts\Filesystem\Filesystem
|
||||
*/
|
||||
protected function getDisk(): Filesystem
|
||||
{
|
||||
return Storage::disk($this->disk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs data
|
||||
*
|
||||
* @param string $message
|
||||
* @param LogType $logtype
|
||||
* @return void
|
||||
* @throws \RuntimeException If logtype is not handled.
|
||||
*/
|
||||
protected function log(string $message, LogType $logtype = null): void
|
||||
{
|
||||
if (!$logtype) {
|
||||
$logtype = LogType::debug;
|
||||
}
|
||||
$message = sprintf('%s : %s', get_class($this), $message);
|
||||
switch ($logtype) {
|
||||
case LogType::debug:
|
||||
Log::debug($message);
|
||||
break;
|
||||
case LogType::info:
|
||||
Log::info($message);
|
||||
break;
|
||||
case LogType::log:
|
||||
Log::log($message);
|
||||
break;
|
||||
case LogType::warning:
|
||||
Log::warning($message);
|
||||
break;
|
||||
case LogType::error:
|
||||
Log::error($message);
|
||||
break;
|
||||
case LogType::critical:
|
||||
Log::critical($message);
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
|
||||
} //end switch
|
||||
$this->consolePrint($message, $logtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints to the console
|
||||
*
|
||||
* @param string $message
|
||||
* @param \App\Enums\LogType $logtype
|
||||
* @return void
|
||||
* @throws \RuntimeException If logtype is not handled.
|
||||
*/
|
||||
protected function consolePrint(string $message, LogType $logtype): void
|
||||
{
|
||||
$this->clearProgressBarFromLine();
|
||||
if (!$this->console) {
|
||||
return;
|
||||
}
|
||||
switch ($logtype) {
|
||||
case LogType::debug:
|
||||
case LogType::info:
|
||||
case LogType::log:
|
||||
$this->console->info($message);
|
||||
break;
|
||||
case LogType::warning:
|
||||
$this->console->comment($message);
|
||||
break;
|
||||
case LogType::error:
|
||||
case LogType::critical:
|
||||
$this->console->error($message);
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException(sprintf(
|
||||
'%s : unhandled log type %s',
|
||||
get_class($this),
|
||||
$logtype->label()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start ProgressBar
|
||||
*
|
||||
* @param integer $total
|
||||
* @return void
|
||||
*/
|
||||
protected function startProgressBar(int $total)
|
||||
{
|
||||
if ($this->console) {
|
||||
$this->progressBar = $this->console->getOutput()->createProgressBar($total);
|
||||
$this->progressBar->start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the line to write someting
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearProgressBarFromLine()
|
||||
{
|
||||
if ($this->progressBar) {
|
||||
$this->progressBar->clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment 1 on progress bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function incrementProgressBar()
|
||||
{
|
||||
if ($this->progressBar) {
|
||||
$this->progressBar->advance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* End the progress bar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function endProgressBar()
|
||||
{
|
||||
if ($this->progressBar) {
|
||||
$this->progressBar->finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/Jobs/Arcadia/XmlBrandReaderJob.php
Normal file
62
app/Jobs/Arcadia/XmlBrandReaderJob.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\Arcadia;
|
||||
|
||||
use App\Models\ArcadiaBrand;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class XmlBrandReaderJob extends XmlJobs
|
||||
{
|
||||
/**
|
||||
* The xsd file path used to validate files
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $xsdSchemaPath = 'documents/arcadia/brand.xsd';
|
||||
|
||||
/**
|
||||
* Disk name from config/filesystems.php
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $disk = 'arcadia_brand';
|
||||
|
||||
/**
|
||||
* @var array Columns to take from the xml.
|
||||
*/
|
||||
protected $takeColumns = [
|
||||
'codMarq' => 'id',
|
||||
'libMarq' => 'label',
|
||||
];
|
||||
|
||||
/**
|
||||
* Fields that are allowed to be null
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $nullableFields = [];
|
||||
|
||||
/**
|
||||
* Unique keys for updating rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $uniqueKeyField = ['id'];
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
ArcadiaBrand::flushQueryCache();
|
||||
$this->xsdSchemaPath = \resource_path($this->xsdSchemaPath);
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
$this->parseData('dam', ArcadiaBrand::class, function (\SimpleXMLElement $xmlContent, array &$line) {
|
||||
Arr::set($line, 'id', intval(Arr::get($line, 'id')));
|
||||
Arr::set($line, 'label', (string)Str::of(Arr::get($line, 'label'))->squish()->title());
|
||||
});
|
||||
}
|
||||
}
|
||||
1226
app/Jobs/Arcadia/XmlIncoReaderJob.php
Normal file
1226
app/Jobs/Arcadia/XmlIncoReaderJob.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,38 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
namespace App\Jobs\Arcadia;
|
||||
|
||||
use App\Enums\LogType;
|
||||
use App\Exceptions\AbortXmlImportException;
|
||||
use App\Exceptions\InvalidXmlException;
|
||||
use App\Lib\Utils;
|
||||
use ErrorException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
use RecursiveArrayIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SimpleXMLElement;
|
||||
use Throwable;
|
||||
use TypeError;
|
||||
|
||||
abstract class XmlJobs extends XArcadiaFlux
|
||||
{
|
||||
/**
|
||||
* invalid xml folder
|
||||
* The xsd file path used to validate files
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
protected $invalidFolderName = 'invalid';
|
||||
protected $xsdSchemaPath = null;
|
||||
|
||||
/**
|
||||
* xsd file location
|
||||
* The xsd file content used to validate files
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
protected $xsdFile = '';
|
||||
protected $xsdSchema = null;
|
||||
|
||||
/**
|
||||
* The columns to take from the XML
|
||||
|
|
@ -57,83 +50,117 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
*/
|
||||
protected $encapsulate = false;
|
||||
|
||||
/**
|
||||
* Unique keys for updating rows
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $uniqueKeyField = [];
|
||||
|
||||
/**
|
||||
* Parse Data from the XML and gives it to the callback
|
||||
*
|
||||
* @param string $entityName
|
||||
* @param string $modelClass
|
||||
* @param string $entityName
|
||||
* @param string $modelClass
|
||||
* @param callable $afterParseCallback After parse is done.
|
||||
* @param callable $saveCallback Instead of updateOrCreate, execute $saveCallback.
|
||||
* @param callable $afterSaveCallback After save is done.
|
||||
* @return void
|
||||
* @throws \App\Exceptions\InvalidXmlException If xml file is not valid.
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded
|
||||
*/
|
||||
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
protected function parseData(string $entityName, string $modelClass, callable $callback = null): void
|
||||
{
|
||||
/** @var Filesystem */
|
||||
protected function parseData(
|
||||
string $entityName,
|
||||
string $modelClass,
|
||||
callable $afterParseCallback = null,
|
||||
callable $saveCallback = null,
|
||||
callable $afterSaveCallback = null
|
||||
): void {
|
||||
$disk = $this->getDisk();
|
||||
$this->log('Démarrage du traitement');
|
||||
|
||||
$this->assertXsdIsHereIfAny();
|
||||
|
||||
$files = $disk->files();
|
||||
|
||||
$progress = 1;
|
||||
$total = count(array_filter($files, function ($filename) {
|
||||
$total = count(array_filter($files, function ($filename) {
|
||||
if ($this->fileHasToBeIgnored($filename)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
|
||||
if (count($files)) {
|
||||
$this->startProgressBar($total);
|
||||
}
|
||||
|
||||
foreach ($files as $filename) {
|
||||
if ($this->fileHasToBeIgnored($filename)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->log(sprintf(' traitement %d sur %s ', $progress++, $total));
|
||||
$this->incrementProgressBar();
|
||||
|
||||
try {
|
||||
/** @var string $data */
|
||||
$data = $disk->get($filename);
|
||||
|
||||
$this->validateXmlFile($filename, $data);
|
||||
|
||||
try {
|
||||
$this->validateSchema($data);
|
||||
if ($this->encapsulate) {
|
||||
$data = sprintf('<Root>%s</Root>', $data);
|
||||
/** @var SimpleXMLElement|false */
|
||||
/** @var \SimpleXMLElement|false */
|
||||
$xmlContent = simplexml_load_string($data);
|
||||
} else {
|
||||
/** @var SimpleXMLElement|false */
|
||||
/** @var \SimpleXMLElement|false */
|
||||
$xmlContent = simplexml_load_string($data, null, LIBXML_NOCDATA | LIBXML_PARSEHUGE);
|
||||
}
|
||||
if ($xmlContent === false) {
|
||||
throw new InvalidXmlException('XML is Invalid (simplexml_load_string returned false)');
|
||||
}
|
||||
} catch (ErrorException $e) {
|
||||
} catch (\ErrorException $e) {
|
||||
throw new InvalidXmlException('XML is Invalid', 0, $e);
|
||||
}
|
||||
|
||||
$data = $this->extract($xmlContent);
|
||||
|
||||
/** @var array<array<string,string>> */
|
||||
$lines = [];
|
||||
foreach ($data[$entityName] as $traca) {
|
||||
|
||||
// * Unwrap if neceessary.
|
||||
$data = isset($data[$entityName]) ? $data[$entityName] : $data;
|
||||
|
||||
foreach ($data as $traca) {
|
||||
$line = $this->processFields($traca);
|
||||
if ($callback) {
|
||||
$callback($xmlContent, $line);
|
||||
if ($afterParseCallback) {
|
||||
$afterParseCallback($xmlContent, $line);
|
||||
}
|
||||
$lines[] = $line;
|
||||
}
|
||||
|
||||
// Mise en base des données
|
||||
// * Mise en base des données
|
||||
foreach ($lines as $line) {
|
||||
/** @var array Unique Key columns */
|
||||
$uniqueColumnsKey = [];
|
||||
foreach ($this->uniqueKeyField as $uField) {
|
||||
$uniqueColumnsKey[$uField] = $line[$uField];
|
||||
}
|
||||
forward_static_call_array(
|
||||
[$modelClass, 'updateOrCreate'],
|
||||
[$uniqueColumnsKey, $line]
|
||||
);
|
||||
}
|
||||
if (count($uniqueColumnsKey)) {
|
||||
if ($saveCallback) {
|
||||
$saveCallback($uniqueColumnsKey, $line);
|
||||
} else {
|
||||
forward_static_call_array(
|
||||
[$modelClass, 'updateOrCreate'],
|
||||
[$uniqueColumnsKey, $line]
|
||||
);
|
||||
}
|
||||
if ($afterSaveCallback) {
|
||||
$afterSaveCallback($xmlContent, $line);
|
||||
}
|
||||
}
|
||||
} //end foreach
|
||||
} catch (InvalidXmlException $e) {
|
||||
$message = $e->getMessage();
|
||||
if (get_class($e) === QueryException::class) {
|
||||
$message = implode(' ', $e->errorInfo);
|
||||
}
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be parsed => %s on line %d',
|
||||
|
|
@ -141,24 +168,11 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
$message,
|
||||
$e->getLine()
|
||||
),
|
||||
LogType::critical()
|
||||
LogType::critical
|
||||
);
|
||||
$this->sendInvalidXmlEmail($filename, $e);
|
||||
$this->reportInvalidXmlSentryAlert($filename, $e);
|
||||
$this->moveToInvalid($filename);
|
||||
continue;
|
||||
} catch (ErrorException | TypeError $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'CRITICAL: File %s could not be parsed => %s on line %d',
|
||||
$filename,
|
||||
$e->getMessage(),
|
||||
$e->getLine()
|
||||
),
|
||||
LogType::critical()
|
||||
);
|
||||
$this->sendErrorParseEmailToServiceWeb($e);
|
||||
continue;
|
||||
// ignore the file
|
||||
} catch (AbortXmlImportException $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
|
|
@ -167,17 +181,18 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
$e->getMessage(),
|
||||
$e->getLine()
|
||||
),
|
||||
LogType::error()
|
||||
LogType::error
|
||||
);
|
||||
$this->sendErrorParseEmailToServiceWeb($e);
|
||||
report($e);
|
||||
continue;
|
||||
// ignore the file
|
||||
} catch (FileNotFoundException | QueryException $e) {
|
||||
// This happens because of concurent xml jobs read
|
||||
// * ignore the file
|
||||
} catch (QueryException $e) {
|
||||
$this->log(implode(' ', $e->errorInfo), LogType::critical);
|
||||
report($e);
|
||||
continue;
|
||||
} catch (FileNotFoundException $e) {
|
||||
// This happens because of concurent xml jobs read.
|
||||
$message = $e->getMessage();
|
||||
if (get_class($e) === QueryException::class) {
|
||||
$message = implode(' ', $e->errorInfo);
|
||||
}
|
||||
$this->log(
|
||||
sprintf(
|
||||
'ERROR: File %s could not be read (not found) => %s on line %d',
|
||||
|
|
@ -185,16 +200,34 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
$message,
|
||||
$e->getLine()
|
||||
),
|
||||
LogType::debug()
|
||||
LogType::debug
|
||||
);
|
||||
continue;
|
||||
// ignore the file
|
||||
}//end try
|
||||
// * ignore the file
|
||||
} catch (\Exception | \ErrorException | \TypeError $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be parsed => %s on line %d',
|
||||
$filename,
|
||||
$e->getMessage(),
|
||||
$e->getLine()
|
||||
),
|
||||
LogType::critical
|
||||
);
|
||||
report($e);
|
||||
continue;
|
||||
// * ignore the file
|
||||
} //end try
|
||||
$this->moveToArchives($filename);
|
||||
}//end foreach
|
||||
} //end foreach
|
||||
|
||||
// Clear old archives
|
||||
// Clear old invalids.
|
||||
$this->clearOldInvalids();
|
||||
|
||||
// Clear old archives.
|
||||
$this->clearOldArchives();
|
||||
|
||||
$this->endProgressBar();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -205,7 +238,7 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
*/
|
||||
protected function extract($sxe = null): array
|
||||
{
|
||||
if (!$sxe instanceof SimpleXMLElement) {
|
||||
if (!$sxe instanceof \SimpleXMLElement) {
|
||||
return array();
|
||||
}
|
||||
$extract = array();
|
||||
|
|
@ -229,7 +262,7 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
*
|
||||
* @param array $fields
|
||||
* @return array
|
||||
* @throws AbortXmlImportException
|
||||
* @throws AbortXmlImportException If a field could not be validated.
|
||||
*/
|
||||
protected function processFields(array $fields): array
|
||||
{
|
||||
|
|
@ -242,8 +275,8 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
throw new AbortXmlImportException(sprintf('The field %s is not allowed to be null', $key));
|
||||
}
|
||||
if (is_array($field)) {
|
||||
// Flatten data
|
||||
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($field));
|
||||
// * Flatten data
|
||||
$it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($field));
|
||||
foreach ($it as $k => $f) {
|
||||
$ko = strtolower(sprintf('%s.%s', $key, $k));
|
||||
if (!$this->isFieldNullable($ko) && is_null($f)) {
|
||||
|
|
@ -261,7 +294,7 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
} else {
|
||||
$result[$key] = $field;
|
||||
}
|
||||
}//end foreach
|
||||
} //end foreach
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
@ -306,24 +339,8 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
*/
|
||||
protected function fileHasToBeIgnored(string $filename): bool
|
||||
{
|
||||
return in_array($filename, $this->ignoreFiles) or !$this->checkExtension($filename, 'xml');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate xml data against xsd file if provided
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function validateSchema($data) {
|
||||
if($this->xsdFile) {
|
||||
//check against XSD
|
||||
$xsd = storage_path($this->disk."/".$this->xsdFile);
|
||||
$domDocument= new \DOMDocument();
|
||||
$domDocument->loadXML($data);
|
||||
//free some memroy to avoid overload
|
||||
unset($data);
|
||||
$domDocument->schemaValidate($xsd);
|
||||
}
|
||||
return in_array($filename, $this->ignoreFiles) or
|
||||
!$this->checkExtension($filename, 'xml');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -341,77 +358,78 @@ abstract class XmlJobs extends XArcadiaFlux
|
|||
return $isXml;
|
||||
}
|
||||
|
||||
protected function sendErrorParseEmailToServiceWeb(Throwable $e)
|
||||
/**
|
||||
* Report Invalid Xml using sentry
|
||||
*
|
||||
* @param string $filename
|
||||
* @param \Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
protected function reportInvalidXmlSentryAlert(string $filename, \Throwable $e)
|
||||
{
|
||||
Utils::sendEmailErrorManually($e);
|
||||
}
|
||||
|
||||
protected function sendInvalidXmlEmail(string $filename, Throwable $e)
|
||||
{
|
||||
$message = $e->getPrevious()->getMessage();
|
||||
Mail::send('emails.invalidxml', [
|
||||
'strError' => sprintf(
|
||||
\report(new \RuntimeException(
|
||||
sprintf(
|
||||
'File %s could not be parsed => %s on line %d',
|
||||
$filename,
|
||||
$message,
|
||||
$e->getPrevious()->getMessage(),
|
||||
$e->getLine()
|
||||
),
|
||||
'appName' => config('app.name'),
|
||||
'filename' => $filename,
|
||||
'exception' => $e->getPrevious()
|
||||
], function ($m) {
|
||||
$m->to(
|
||||
config('mail.contactsList.invalidInco'),
|
||||
sprintf('%s Exception Handler', config('app.name'))
|
||||
)->subject('You sent me an invalid XML file, please check its validity before resend ASAP.');
|
||||
});
|
||||
$e->getCode(),
|
||||
$e
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the file to invalid folder
|
||||
* Assert XSD is readable if configured
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException If xsd file cant be read.
|
||||
* @throws \RuntimeException If schame is empty.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
*/
|
||||
protected function moveToInvalid(string $filename): void
|
||||
private function assertXsdIsHereIfAny()
|
||||
{
|
||||
$newFileName = $filename;
|
||||
if (!$this->getDisk()->exists($filename)) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to invalid (not found)',
|
||||
$filename
|
||||
),
|
||||
LogType::warning()
|
||||
);
|
||||
// phpcs:enable
|
||||
if (!$this->xsdSchemaPath) {
|
||||
return;
|
||||
}
|
||||
if ($this->getDisk()->exists(sprintf('%s/%s', $this->invalidFolderName, $filename))) {
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$newFileName = \sprintf(
|
||||
'%s_%d.%s',
|
||||
\substr($filename, 0, 0 - (strlen($ext) + 1)),
|
||||
\time(),
|
||||
$ext
|
||||
);
|
||||
if (empty($this->xsdSchema = File::get($this->xsdSchemaPath))) {
|
||||
throw new \RuntimeException('xsdSchema is empty');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Xml file against XSD if loaded
|
||||
*
|
||||
* @param string $fileName
|
||||
* @param string $fileContent
|
||||
* @return void
|
||||
* @throws \App\Exceptions\InvalidXmlException If XML could not be parsed or validated against XSD.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
*/
|
||||
private function validateXmlFile(string $fileName, string $fileContent)
|
||||
{
|
||||
if (!$this->xsdSchema) {
|
||||
return;
|
||||
}
|
||||
$domDocument = new \DOMDocument('1.0', 'UTF-8');
|
||||
try {
|
||||
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->invalidFolderName, $newFileName))) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to invalid',
|
||||
$filename
|
||||
),
|
||||
LogType::warning()
|
||||
if (!$domDocument->loadXML($fileContent)) {
|
||||
throw new InvalidXmlException("XML Validation {$fileName} content could not be loaded to validate.");
|
||||
}
|
||||
if (!$domDocument->schemaValidateSource($this->xsdSchema, \LIBXML_SCHEMA_CREATE)) {
|
||||
throw new InvalidXmlException(
|
||||
"XML Validation $fileName content could not be validated against xsd."
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to invalid',
|
||||
$filename
|
||||
),
|
||||
LogType::warning()
|
||||
} catch (\Exception $e) {
|
||||
if (\get_class($e) === InvalidXmlException::class) {
|
||||
throw $e;
|
||||
}
|
||||
throw new InvalidXmlException(
|
||||
"XML Validation $fileName content could not be validated against xsd.",
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
81
app/Jobs/SetServiceStats.php
Normal file
81
app/Jobs/SetServiceStats.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceStat;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class SetServiceStats implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
private const KEY = '2202020';
|
||||
|
||||
/** @var \App\Models\Service */
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Service $service
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Service $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var \App\Models\ServiceStat */
|
||||
$serviceStat = ServiceStat::firstOrNew([
|
||||
'date' => date('Y-m-d'),
|
||||
'service_id' => $this->service->id,
|
||||
]);
|
||||
if (!$serviceStat->exists) {
|
||||
$serviceStat->service()->associate($this->service);
|
||||
}
|
||||
$serviceStat->nb_calls += 1;
|
||||
$serviceStat->saveOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the time at which the job should timeout.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function retryUntil()
|
||||
{
|
||||
return now()->addMinutes(rand(0, 360));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the middleware the job should pass through.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function middleware()
|
||||
{
|
||||
return [
|
||||
(new WithoutOverlapping(self::KEY))
|
||||
->releaseAfter(rand(90, 180))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\LogType;
|
||||
use Exception;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
abstract class XArcadiaFlux implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The Laravel disk name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $disk = null;
|
||||
|
||||
/**
|
||||
* This is for Laravel Console usage
|
||||
*/
|
||||
public $console = null;
|
||||
|
||||
/**
|
||||
* The relative folder where to archive data
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $archiveFolderName = 'archives';
|
||||
|
||||
/**
|
||||
* The files to ignore
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoreFiles = [
|
||||
'.gitignore', '.gitkeep'
|
||||
];
|
||||
|
||||
/**
|
||||
* Check if the file extension
|
||||
*
|
||||
* @param string $filename
|
||||
* @return boolean
|
||||
*/
|
||||
protected function checkExtension(string $filename, string $extension): bool
|
||||
{
|
||||
$isExtension = strtolower(pathinfo($filename, \PATHINFO_EXTENSION)) === $extension;
|
||||
if (!$isExtension) {
|
||||
Log::warning(sprintf(
|
||||
'%s : unwanted file type in %s wanted %s',
|
||||
get_class($this),
|
||||
$filename,
|
||||
$extension
|
||||
));
|
||||
}
|
||||
return $isExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the file to archives
|
||||
*
|
||||
* @param string $filename
|
||||
* @return void
|
||||
*/
|
||||
protected function moveToArchives(string $filename): void
|
||||
{
|
||||
$newFileName = $filename;
|
||||
if (!$this->getDisk()->exists($filename)) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to archives (not found)',
|
||||
$filename
|
||||
),
|
||||
LogType::warning()
|
||||
);
|
||||
return;
|
||||
}
|
||||
if ($this->getDisk()->exists(sprintf('%s/%s', $this->archiveFolderName, $filename))) {
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$newFileName = \sprintf(
|
||||
'%s_%d.%s',
|
||||
\substr($filename, 0, 0 - (strlen($ext) + 1)),
|
||||
\time(),
|
||||
$ext
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (!$this->getDisk()->move($filename, sprintf('%s/%s', $this->archiveFolderName, $newFileName))) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to archives',
|
||||
$filename
|
||||
),
|
||||
LogType::warning()
|
||||
);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->log(
|
||||
sprintf(
|
||||
'File %s could not be moved to archives',
|
||||
$filename
|
||||
),
|
||||
LogType::warning()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppression des fichiers dans
|
||||
* archives qui ont plus de 2j
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function clearOldArchives(): void
|
||||
{
|
||||
$disk = $this->getDisk();
|
||||
$archives = $disk->files($this->archiveFolderName);
|
||||
|
||||
// /** @var File */
|
||||
foreach ($archives as $archive) {
|
||||
$pi = \pathinfo($archive, \PATHINFO_BASENAME);
|
||||
if (in_array($pi, $this->ignoreFiles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($disk->lastModified($archive) < strtotime(Carbon::now()->subDays(2))) {
|
||||
if (!$disk->delete($archive)) {
|
||||
Log::critical(sprintf('%s : Could not delete file %s', get_class($this), $archive));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concerned disk
|
||||
*
|
||||
* @return Filesystem
|
||||
*/
|
||||
protected function getDisk(): Filesystem
|
||||
{
|
||||
return Storage::disk($this->disk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs data
|
||||
*
|
||||
* @param string $message
|
||||
* @param LogType $logtype
|
||||
* @return void
|
||||
*/
|
||||
protected function log(string $message, LogType $logtype = null): void
|
||||
{
|
||||
if (!$logtype) {
|
||||
$logtype = LogType::debug();
|
||||
}
|
||||
$message = sprintf('%s : %s', get_class($this), $message);
|
||||
switch ($logtype->value) {
|
||||
case LogType::debug()->value:
|
||||
Log::debug($message);
|
||||
break;
|
||||
case LogType::info()->value:
|
||||
Log::info($message);
|
||||
break;
|
||||
case LogType::log()->value:
|
||||
Log::log($message);
|
||||
break;
|
||||
case LogType::warning()->value:
|
||||
Log::warning($message);
|
||||
break;
|
||||
case LogType::error()->value:
|
||||
Log::error($message);
|
||||
break;
|
||||
case LogType::critical()->value:
|
||||
Log::critical($message);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
|
||||
}//end switch
|
||||
$this->consolePrint($message, $logtype);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints to the console
|
||||
*
|
||||
* @param string $message
|
||||
* @param LogType $logtype
|
||||
* @return void
|
||||
*/
|
||||
protected function consolePrint(string $message, LogType $logtype): void
|
||||
{
|
||||
if ($this->console) {
|
||||
switch ($logtype->value) {
|
||||
case LogType::debug()->value:
|
||||
case LogType::info()->value:
|
||||
case LogType::log()->value:
|
||||
$this->console->info($message);
|
||||
break;
|
||||
case LogType::warning()->value:
|
||||
$this->console->comment($message);
|
||||
break;
|
||||
case LogType::error()->value:
|
||||
case LogType::critical()->value:
|
||||
$this->console->error($message);
|
||||
break;
|
||||
default:
|
||||
throw new Exception(sprintf('%s : unhandled log type %s', get_class($this), $logtype->label));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,523 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Exceptions\AbortXmlImportException;
|
||||
use App\Models\XmlInco;
|
||||
use SimpleXMLElement;
|
||||
|
||||
class XmlIncoReader extends XmlJobs
|
||||
{
|
||||
|
||||
protected $disk = 'inco';
|
||||
|
||||
protected $imagesPath = 'images';
|
||||
|
||||
protected $xsdFile = 'xsd/incollector.xsd';
|
||||
|
||||
/**
|
||||
* éléments à la racine : nom du noeud
|
||||
* sous éléments d'un noeud PARENT :
|
||||
* d'abord:
|
||||
* 'PARENT' => 'parent', // Consider this sub array
|
||||
* puis:
|
||||
* "PARENT.LEAF" => 'leaf', // extract this data in the line
|
||||
* Note : Xmljobs iterates the PARENT with RecursiveIteratorIterator::LEAVES_ONLY
|
||||
* intermediate nodes are therefore ignored
|
||||
* example : nodes : <EXPLOITANT><SOCIETE><ID_SOCIETE /><NOM_SOCIETE /></SOCIETE></EXPLOITANT>
|
||||
* how to extract leaves :
|
||||
* 'EXPLOITANT' => 'exploitant', // Consider this sub array
|
||||
* 'EXPLOITANT.ID_SOCIETE' => 'id_societe',
|
||||
* 'EXPLOITANT.NOM_SOCIETE' => 'nom_societe',
|
||||
*/
|
||||
protected $takeColumns = [
|
||||
'NO_ART' => 'id', // primary key on model
|
||||
|
||||
//this wil not return anything cf doc above
|
||||
'ID_SOCIETE' => 'id_societe',
|
||||
'NOM_SOCIETE' => 'nom_societe',
|
||||
|
||||
'PROD_SAISON' => 'prod_saison',
|
||||
'NOM' => 'nom',
|
||||
'NUMERO' => 'numero',
|
||||
'TAILLE_REF' => 'taille_ref', //$poidsRef censé être dans INCO_COMPO (et généralement 100g)
|
||||
'ALLEGATIONS' => 'allegations',
|
||||
'CONSEIL_PREPA' => 'conseil_prepa',
|
||||
'ASTUCES' => 'astuces',
|
||||
'DESCRIPTIF' => 'descriptif', //$descriptif
|
||||
'ARG_VENTE' => 'arg_vente',
|
||||
'NB_PORTION' => 'nb_portion',
|
||||
'TYP_PORTION' => 'typ_portion',
|
||||
'PDS_MOY_THEO' => 'pds_moy_theo',
|
||||
'LIB_CONSO' => 'lib_conso',
|
||||
'LST_ING' => 'lst_ing', //$ingredients
|
||||
|
||||
// list alergene
|
||||
'LST_ALLERGENE' => 'lst_allergene', // Consider this sub array
|
||||
'LST_ALLERGENE.LIB_ALLERGENE' => 'lib_allergene',
|
||||
|
||||
// Allegations
|
||||
'ALLEGAT_SANTE' => 'allegat_sante',
|
||||
'ALLEGAT_NUTRIT' => 'allegat_nutrit',
|
||||
'ALLEGAT_AUTRE' => 'allegat_autre',
|
||||
|
||||
|
||||
'INCO_COMPO' => 'inco_compo', // Consider this sub array
|
||||
'INCO_COMPO.NOM' => 'nom',
|
||||
'INCO_COMPO.TAILLE_REF' => 'taille_ref',
|
||||
'INCO_COMPO.VAL_ENERG_KJ_NUT' => 'val_energ_kj_nut',
|
||||
'INCO_COMPO.VAL_ENERG_KJ_R' => 'val_energ_kj_r', //_R apports de la taille_ref
|
||||
'INCO_COMPO.VAL_ENERG_KCAL_R' => 'val_energ_kcal_r',
|
||||
'INCO_COMPO.VAL_ENERG_KCAL_NUT' => 'val_energ_kcal_nut',
|
||||
'INCO_COMPO.MAT_GRASS_R' => 'mat_grass_r',
|
||||
'INCO_COMPO.MAT_GRASS_NUT' => 'mat_grass_nut',
|
||||
'INCO_COMPO.AGS_R' => 'ags_r',
|
||||
'INCO_COMPO.AGS_NUT' => 'ags_nut',
|
||||
'INCO_COMPO.GLUCIDES_R' => 'glucides_r',
|
||||
'INCO_COMPO.GLUCIDES_NUT' => 'glucides_nut',
|
||||
'INCO_COMPO.SUCRE_NUT' => 'sucre_nut',
|
||||
'INCO_COMPO.SUCRE_R' => 'sucre_r',
|
||||
'INCO_COMPO.FIB_ALIM_NUT' => 'fib_alim_nut',
|
||||
'INCO_COMPO.FIB_ALIM_R' => 'fib_alim_r',
|
||||
'INCO_COMPO.PROTEINE_R' => 'proteine_r',
|
||||
'INCO_COMPO.PROTEINE_NUT' => 'proteine_nut',
|
||||
'INCO_COMPO.SEL_R' => 'sel_r',
|
||||
'INCO_COMPO.SEL_NUT' => 'sel_nut',
|
||||
'INCO_COMPO.SODIUM_R' => 'sodium_r',
|
||||
'INCO_COMPO.ENER_E14_R' => 'ener_e14_r',
|
||||
'INCO_COMPO.FRUITS_R' => 'fruits_r',
|
||||
'INCO_COMPO.FRUITS_COQUE_SECH_R' => 'fruits_coque_sech_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_E14_R' => 'pourcent_apport_ref_e14_r', //POURCENT_*_P apports journaliers par portion
|
||||
'INCO_COMPO.ENER_KJO_R' => 'ener_kjo_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_KJO_R' => 'pourcent_apport_ref_kjo_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_FAT_R' => 'pourcent_apport_ref_fat_r',
|
||||
'INCO_COMPO.FATSAT_R' => 'fatsat_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_FATSAT_R' => 'pourcent_apport_ref_fatsat_r',
|
||||
'INCO_COMPO.CHOVAL_R' => 'choval_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_CHOVAL_R' => 'pourcent_apport_ref_choval_r',
|
||||
'INCO_COMPO.SUGAR_R' => 'sugar_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_SUGAR_R' => 'pourcent_apport_ref_sugar_r',
|
||||
'INCO_COMPO.FIBTG_R' => 'fibtg_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_FIBTG_R' => 'pourcent_apport_ref_fibtg_r',
|
||||
'INCO_COMPO.PRO_R' => 'pro_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_PRO_R' => 'pourcent_apport_ref_pro_r',
|
||||
'INCO_COMPO.SALTEQ_R' => 'salteq_r',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_SALTEQ_R' => 'pourcent_apport_ref_salteq_r',
|
||||
'INCO_COMPO.APPORT_REF' => 'apport_ref',
|
||||
'INCO_COMPO.TAILLE_PORT' => 'taille_port', //$poidsPortion
|
||||
'INCO_COMPO.VAL_ENERG_KJ_P' => 'val_energ_kj_p', //_P apports par portion
|
||||
'INCO_COMPO.VAL_ENERG_KCAL_P' => 'val_energ_kcal_p',
|
||||
'INCO_COMPO.MAT_GRASS_P' => 'mat_grass_p',
|
||||
'INCO_COMPO.AGS_P' => 'ags_p',
|
||||
'INCO_COMPO.GLUCIDES_P' => 'glucides_p',
|
||||
'INCO_COMPO.SUCRE_P' => 'sucre_p',
|
||||
'INCO_COMPO.FIB_ALIM_P' => 'fib_alim_p',
|
||||
'INCO_COMPO.PROTEINE_P' => 'proteine_p',
|
||||
'INCO_COMPO.SODIUM_P' => 'sodium_p',
|
||||
'INCO_COMPO.FRUITS_P' => 'fruits_p',
|
||||
'INCO_COMPO.SEL_P' => 'sel_p',
|
||||
'INCO_COMPO.FRUITS_COQUE_SECH_P' => 'fruits_coque_sech_p',
|
||||
'INCO_COMPO.ENER_E14_P' => 'ener_e14_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_E14_P' => 'pourcent_apport_ref_e14_p',
|
||||
'INCO_COMPO.ENER_KJO_P' => 'ener_kjo_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_KJO_P' => 'pourcent_apport_ref_kjo_p',
|
||||
'INCO_COMPO.FAT_P' => 'fat_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_FAT_P' => 'pourcent_apport_ref_fat_p',
|
||||
'INCO_COMPO.FATSAT_P' => 'fatsat_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_FATSAT_P' => 'pourcent_apport_ref_fatsat_p',
|
||||
'INCO_COMPO.CHOVAL_P' => 'CHOVAL_P',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_CHOVAL_P' => 'pourcent_apport_ref_choval_p',
|
||||
'INCO_COMPO.SUGAR_P' => 'SUGAR_P',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_SUGAR_P' => 'pourcent_apport_ref_sugar_p',
|
||||
'INCO_COMPO.FIBTG_P' => 'fibtg_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_FIBTG_P' => 'pourcent_apport_ref_fibtg_p',
|
||||
'INCO_COMPO.PRO_P' => 'pro_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_PRO_P' => 'pourcent_apport_ref_pro_p',
|
||||
'INCO_COMPO.SALTEQ_P' => 'salteq_p',
|
||||
'INCO_COMPO.POURCENT_APPORT_REF_SALTEQ_P' => 'pourcent_apport_ref_salteq_p',
|
||||
'INCO_COMPO.APPORT_PORT' => 'apport_port',
|
||||
'INCO_COMPO.PART_P' => 'part_p',
|
||||
'INCO_COMPO.PART_G' => 'part_g',
|
||||
'INCO_COMPO.PART_L' => 'part_l',
|
||||
'INCO_COMPO.P_SUR_L' => 'p_sur_l',
|
||||
'INCO_COMPO.POURCENT_AGS' => 'pourcent_ags',
|
||||
'INCO_COMPO.POURCENT_AGI' => 'pourcent_agi',
|
||||
'INCO_COMPO.POURCENT_SS' => 'pourcent_ss',
|
||||
'INCO_COMPO.POURCENT_AMIDON' => 'pourcent_amidon',
|
||||
'INCO_COMPO.COMPOSANTE_N' => 'composante_n',
|
||||
'INCO_COMPO.COMPOSANTE_P' => 'composante_p',
|
||||
'INCO_COMPO.POURCENT_FRUITS' => 'pourcent_fruits',
|
||||
'INCO_COMPO.SCORE_NUTRI' => 'score_nutri', //du coup nutriscore ?
|
||||
'INCO_COMPO.CLASSEMENT' => 'classement', //$nutriscore mais pas censé être dans INCO_COMPO
|
||||
'INCO_COMPO.MAT_GRASS_R_PREC' => 'mat_grass_r_prec',
|
||||
'INCO_COMPO.AGS_R_PREC' => 'ags_r_prec',
|
||||
'INCO_COMPO.GLUCIDES_R_PREC' => 'glucides_r_prec',
|
||||
'INCO_COMPO.SUCRE_R_PREC' => 'sucre_r_prec',
|
||||
'INCO_COMPO.FIB_ALIM_R_PREC' => 'fib_alim_r_prec',
|
||||
'INCO_COMPO.PROTEINE_R_PREC' => 'proteine_r_prec',
|
||||
'INCO_COMPO.SEL_R_PREC' => 'sel_r_prec',
|
||||
'INCO_COMPO.SODIUM_R_PREC' => 'sodium_r_prec',
|
||||
'INCO_COMPO.FRUITS_R_PREC' => 'fruits_r_prec',
|
||||
'INCO_COMPO.FRUITS_COQUE_SECH_R_PREC' => 'fruits_coque_sech_r_prec',
|
||||
];
|
||||
|
||||
protected $nullableFields = [
|
||||
'taille_ref',
|
||||
'conseil_prepa',
|
||||
'typ_portion',
|
||||
'nb_portion',
|
||||
'descriptif',
|
||||
'prod_saison',
|
||||
'allegations',
|
||||
'astuces', 'arg_vente',
|
||||
'pds_moy_theo',
|
||||
'lib_conso', 'lst_ing', 'lst_allergene',
|
||||
'allegat_sante', 'allegat_nutrit', 'allegat_autre',
|
||||
'inco_compo',
|
||||
'inco_compo.nom',
|
||||
'inco_compo.numero',
|
||||
'inco_compo.taille',
|
||||
'inco_compo.taille_ref',
|
||||
'inco_compo.val_energ_kj_nut',
|
||||
'inco_compo.val_energ_kj_r',
|
||||
'inco_compo.val_energ_kcal_r',
|
||||
'inco_compo.val_energ_kcal_nut',
|
||||
'inco_compo.mat_grass_r',
|
||||
'inco_compo.mat_grass_nut',
|
||||
'inco_compo.ags_r',
|
||||
'inco_compo.ags_nut',
|
||||
'inco_compo.glucides_r',
|
||||
'inco_compo.glucides_nut',
|
||||
'inco_compo.sucre_nut',
|
||||
'inco_compo.sucre_r',
|
||||
'inco_compo.fib_alim_nut',
|
||||
'inco_compo.fib_alim_r',
|
||||
'inco_compo.proteine_r',
|
||||
'inco_compo.proteine_nut',
|
||||
'inco_compo.sel_r',
|
||||
'inco_compo.sel_nut',
|
||||
'inco_compo.sodium_r',
|
||||
'inco_compo.ener_e14_r',
|
||||
'inco_compo.fruits_r',
|
||||
'inco_compo.fruits_coque_sech_r',
|
||||
'inco_compo.pourcent_apport_ref_e14_r',
|
||||
'inco_compo.ener_kjo_r',
|
||||
'inco_compo.pourcent_apport_ref_kjo_r',
|
||||
'inco_compo.pourcent_apport_ref_fat_r',
|
||||
'inco_compo.fatsat_r',
|
||||
'inco_compo.pourcent_apport_ref_fatsat_r',
|
||||
'inco_compo.choval_r',
|
||||
'inco_compo.pourcent_apport_ref_choval_r',
|
||||
'inco_compo.sugar_r',
|
||||
'inco_compo.pourcent_apport_ref_sugar_r',
|
||||
'inco_compo.fibtg_r',
|
||||
'inco_compo.pourcent_apport_ref_fibtg_r',
|
||||
'inco_compo.pro_r',
|
||||
'inco_compo.pourcent_apport_ref_pro_r',
|
||||
'inco_compo.salteq_r',
|
||||
'inco_compo.pourcent_apport_ref_salteq_r',
|
||||
'inco_compo.apport_ref',
|
||||
'inco_compo.taille_port',
|
||||
'inco_compo.val_energ_kj_p',
|
||||
'inco_compo.val_energ_kcal_p',
|
||||
'inco_compo.mat_grass_p',
|
||||
'inco_compo.ags_p',
|
||||
'inco_compo.glucides_p',
|
||||
'inco_compo.sucre_p',
|
||||
'inco_compo.fib_alim_p',
|
||||
'inco_compo.proteine_p',
|
||||
'inco_compo.sodium_p',
|
||||
'inco_compo.fruits_p',
|
||||
'inco_compo.sel_p',
|
||||
'inco_compo.fruits_coque_sech_p',
|
||||
'inco_compo.ener_e14_p',
|
||||
'inco_compo.pourcent_apport_ref_e14_p',
|
||||
'inco_compo.ener_kjo_p',
|
||||
'inco_compo.pourcent_apport_ref_kjo_p',
|
||||
'inco_compo.fat_p',
|
||||
'inco_compo.pourcent_apport_ref_fat_p',
|
||||
'inco_compo.fatsat_p',
|
||||
'inco_compo.pourcent_apport_ref_fatsat_p',
|
||||
'inco_compo.choval_p',
|
||||
'inco_compo.pourcent_apport_ref_choval_p',
|
||||
'inco_compo.sugar_p',
|
||||
'inco_compo.pourcent_apport_ref_sugar_p',
|
||||
'inco_compo.fibtg_p',
|
||||
'inco_compo.pourcent_apport_ref_fibtg_p',
|
||||
'inco_compo.pro_p',
|
||||
'inco_compo.pourcent_apport_ref_pro_p',
|
||||
'inco_compo.salteq_p',
|
||||
'inco_compo.pourcent_apport_ref_salteq_p',
|
||||
'inco_compo.apport_port',
|
||||
'inco_compo.part_p',
|
||||
'inco_compo.part_g',
|
||||
'inco_compo.part_l',
|
||||
'inco_compo.p_sur_l',
|
||||
'inco_compo.pourcent_ags',
|
||||
'inco_compo.pourcent_agi',
|
||||
'inco_compo.pourcent_ss',
|
||||
'inco_compo.pourcent_amidon',
|
||||
'inco_compo.composante_n',
|
||||
'inco_compo.composante_p',
|
||||
'inco_compo.pourcent_fruits',
|
||||
'inco_compo.score_nutri',
|
||||
'inco_compo.classement',
|
||||
'inco_compo.mat_grass_r_prec',
|
||||
'inco_compo.ags_r_prec',
|
||||
'inco_compo.glucides_r_prec',
|
||||
'inco_compo.sucre_r_prec',
|
||||
'inco_compo.fib_alim_r_prec',
|
||||
'inco_compo.proteine_r_prec',
|
||||
'inco_compo.sel_r_prec',
|
||||
'inco_compo.sodium_r_prec',
|
||||
'inco_compo.fruits_r_prec',
|
||||
'inco_compo.fruits_coque_sech_r_prec'
|
||||
];
|
||||
|
||||
protected $uniqueKeyField = ['id'];
|
||||
|
||||
protected $mergeColumns = [
|
||||
'allegations' => [
|
||||
'allegat_sante',
|
||||
'allegat_nutrit',
|
||||
'allegat_autre'
|
||||
],
|
||||
'nutritional_values' => [
|
||||
'taille_ref',
|
||||
'val_energ_kj_nut',
|
||||
'val_energ_kj_r',
|
||||
'val_energ_kcal_r',
|
||||
'val_energ_kcal_nut',
|
||||
'mat_grass_r',
|
||||
'mat_grass_nut',
|
||||
'ags_r',
|
||||
'ags_nut',
|
||||
'glucides_r',
|
||||
'glucides_nut',
|
||||
'sucre_nut',
|
||||
'sucre_r',
|
||||
'fib_alim_nut',
|
||||
'fib_alim_r',
|
||||
'proteine_r',
|
||||
'proteine_nut',
|
||||
'sel_r',
|
||||
'sel_nut',
|
||||
'sodium_r',
|
||||
'ener_e14_r',
|
||||
'fruits_r',
|
||||
'fruits_coque_sech_r',
|
||||
'pourcent_apport_ref_e14_r',
|
||||
'ener_kjo_r',
|
||||
'pourcent_apport_ref_kjo_r',
|
||||
'pourcent_apport_ref_fat_r',
|
||||
'fatsat_r',
|
||||
'pourcent_apport_ref_fatsat_r',
|
||||
'choval_r',
|
||||
'pourcent_apport_ref_choval_r',
|
||||
'sugar_r',
|
||||
'pourcent_apport_ref_sugar_r',
|
||||
'fibtg_r',
|
||||
'pourcent_apport_ref_fibtg_r',
|
||||
'pro_r',
|
||||
'pourcent_apport_ref_pro_r',
|
||||
'salteq_r',
|
||||
'pourcent_apport_ref_salteq_r',
|
||||
'apport_ref',
|
||||
'taille_port',
|
||||
'val_energ_kj_p',
|
||||
'val_energ_kcal_p',
|
||||
'mat_grass_p',
|
||||
'ags_p',
|
||||
'glucides_p',
|
||||
'sucre_p',
|
||||
'fib_alim_p',
|
||||
'proteine_p',
|
||||
'sodium_p',
|
||||
'fruits_p',
|
||||
'sel_p',
|
||||
'fruits_coque_sech_p',
|
||||
'ener_e14_p',
|
||||
'pourcent_apport_ref_e14_p',
|
||||
'ener_kjo_p',
|
||||
'pourcent_apport_ref_kjo_p',
|
||||
'fat_p',
|
||||
'pourcent_apport_ref_fat_p',
|
||||
'fatsat_p',
|
||||
'pourcent_apport_ref_fatsat_p',
|
||||
'choval_p',
|
||||
'pourcent_apport_ref_choval_p',
|
||||
'sugar_p',
|
||||
'pourcent_apport_ref_sugar_p',
|
||||
'fibtg_p',
|
||||
'pourcent_apport_ref_fibtg_p',
|
||||
'pro_p',
|
||||
'pourcent_apport_ref_pro_p',
|
||||
'salteq_p',
|
||||
'pourcent_apport_ref_salteq_p',
|
||||
'apport_port',
|
||||
'part_p',
|
||||
'part_g',
|
||||
'part_l',
|
||||
'p_sur_l',
|
||||
'pourcent_ags',
|
||||
'pourcent_agi',
|
||||
'pourcent_ss',
|
||||
'pourcent_amidon',
|
||||
'composante_n',
|
||||
'composante_p',
|
||||
'pourcent_fruits',
|
||||
'score_nutri',
|
||||
'classement',
|
||||
'mat_grass_r_prec',
|
||||
'ags_r_prec',
|
||||
'glucides_r_prec',
|
||||
'sucre_r_prec',
|
||||
'fib_alim_r_prec',
|
||||
'proteine_r_prec',
|
||||
'sel_r_prec',
|
||||
'sodium_r_prec',
|
||||
'fruits_r_prec',
|
||||
'fruits_coque_sech_r_prec',
|
||||
]
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
$this->parseData('INCO', XmlInco::class, function (SimpleXMLElement $xmlData, array &$line) {
|
||||
|
||||
// merge columns
|
||||
foreach ($this->mergeColumns as $key => $values) {
|
||||
$line[$key] = [];
|
||||
// merge columns and remove merged columns from line
|
||||
array_map(function ($kValue) use (&$line, $key) {
|
||||
if (array_key_exists($kValue, $line)) {
|
||||
$line[$key][$kValue] = $line[$kValue];
|
||||
unset($line[$kValue]);
|
||||
}
|
||||
}, $values);
|
||||
if ($key == 'allegations') {
|
||||
$line[$key] = implode(',', array_filter($line[$key]));
|
||||
$line[$key] = strlen($line[$key]) ? $line[$key] : null;
|
||||
}
|
||||
//json column cast into array in model => no need for encode
|
||||
// if ($key == 'nutritional_values') {
|
||||
// $line[$key] = json_encode($line[$key], \JSON_FORCE_OBJECT);
|
||||
// }
|
||||
}
|
||||
|
||||
// Boolean cast prod_saison
|
||||
$line['prod_saison'] = isset($line['prod_saison']) ?
|
||||
(strtolower($line['prod_saison']) == 'non' ? false : true) : false;
|
||||
|
||||
$arImgPaths = $this->parseImage($xmlData);
|
||||
$line['picture'] = $arImgPaths;
|
||||
});
|
||||
}
|
||||
|
||||
private function parseImage(SimpleXMLElement $xmlData)
|
||||
{
|
||||
$images = $xmlData->INCO->MEDIA_VISUEL_EAI->MEDIA_VISUEL_EAI;
|
||||
$arImgPaths = array();
|
||||
$index = 1;
|
||||
|
||||
foreach ($images as $image) {
|
||||
$imageData = trim((string)$image);
|
||||
if (strlen($imageData)) {
|
||||
$imageData = base64_decode($imageData);
|
||||
$imgName = strval($xmlData->INCO->INCO_COMPO->TBL_INCO_COMPO->ID_INCO);
|
||||
if(empty($imgName)) {
|
||||
$imgName = strval($xmlData->INCO->NO_ART);
|
||||
}
|
||||
$imgName = $imgName."_".$index;
|
||||
$imgNamePath = sprintf('%s/%s', rtrim($this->imagesPath, '/'), $imgName);
|
||||
|
||||
$disk = $this->getDisk();
|
||||
|
||||
$info = getimagesizefromstring($imageData);
|
||||
if($info !== false) {
|
||||
$mime = $info['mime'];
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
$imgExtension = 'jpg';
|
||||
break;
|
||||
case 'image/png':
|
||||
$imgExtension = 'png';
|
||||
break;
|
||||
case 'image/gif':
|
||||
$imgExtension = 'gif';
|
||||
break;
|
||||
default:
|
||||
throw new AbortXmlImportException(sprintf('Unknown image type %s', $imgName));
|
||||
}
|
||||
|
||||
$imgNamePath = sprintf('%s.%s', $imgNamePath, $imgExtension);
|
||||
|
||||
if (!$disk->put($imgNamePath, $imageData)) {
|
||||
throw new AbortXmlImportException(sprintf('The image %s could not be saved', $imgNamePath));
|
||||
}
|
||||
|
||||
$arImgPaths[$index] = sprintf('%s/%s', $this->disk, $imgNamePath);
|
||||
$index++;
|
||||
} else {
|
||||
//probably not an image could be a PDF, PPT, AI anything is possible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $arImgPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive extract to array
|
||||
* introduce sort of Hook to transform
|
||||
* <TYP_PORTION>
|
||||
* <code><![CDATA[2]]></code>
|
||||
* <libelle><![CDATA[Approximatif]]></libelle>
|
||||
* </TYP_PORTION>
|
||||
* extracted as "TYP_PORTION" => array:1 [
|
||||
* 0 => array:2 [
|
||||
* "code" => "2"
|
||||
* "libelle" => "Approximatif"
|
||||
* ]
|
||||
* ]
|
||||
* into this
|
||||
* <TYP_PORTION><![CDATA[Approximatif]]></TYP_PORTION>
|
||||
* extracted as "TYP_PORTION" => "Approximatif"
|
||||
*
|
||||
*
|
||||
* @param \SimpleXMLElement|false $sxe
|
||||
* @return array
|
||||
*/
|
||||
protected function extract($sxe = null): array
|
||||
{
|
||||
|
||||
if (!$sxe instanceof SimpleXMLElement) {
|
||||
return array();
|
||||
}
|
||||
$extract = array();
|
||||
foreach ($sxe->children() as $key => $value) {
|
||||
if ($value->count()) {
|
||||
$extraction = $this->extract($value);
|
||||
|
||||
if(count($extraction)
|
||||
&& array_key_exists("code",$extraction)
|
||||
&& array_key_exists("libelle",$extraction)) {
|
||||
$val = strval($extraction['libelle']);
|
||||
$extract[$key] = strlen($val) ? $val : null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$extract[$key][] = $extraction; //$this->extract($value);
|
||||
continue;
|
||||
}
|
||||
$value = strval($value);
|
||||
if (array_key_exists($key, $extract) and is_array($extract[$key])) {
|
||||
$extract[$key][] = strlen($value) ? $value : null;
|
||||
} else {
|
||||
$extract[$key] = strlen($value) ? $value : null;
|
||||
}
|
||||
}
|
||||
return $extract;
|
||||
}
|
||||
}
|
||||
184
app/Lib/ApiHelper.php
Normal file
184
app/Lib/ApiHelper.php
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib;
|
||||
|
||||
use App\Exceptions\Api\ApplicationException;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Api Helper Class
|
||||
*/
|
||||
class ApiHelper
|
||||
{
|
||||
/**
|
||||
* Package infos
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $packageInfos;
|
||||
|
||||
/**
|
||||
* Tells if the request is within api routes.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isRequestForApi(Request $request): bool
|
||||
{
|
||||
// * Small hack to ensure to know if route is From APIv2.
|
||||
$routeName = static::getRouteName($request) ??
|
||||
(strpos($request->path(), 'api') === 0 ? 'api.' : null) ?? 'none';
|
||||
return strpos($routeName, 'api.') === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get current route name
|
||||
*
|
||||
* @param Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getRouteName(Request $request)
|
||||
{
|
||||
return Route::currentRouteName() ?? ($request->route() ? $request->route()->getName() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bearer token from request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getBearerTokenFromRequest(Request $request): ?string
|
||||
{
|
||||
$header = $request->header('Authorization', '');
|
||||
|
||||
if (Str::contains($header, 'Bearer ')) {
|
||||
$auths = \explode(',', $header);
|
||||
foreach ($auths as $auth) {
|
||||
if (Str::contains($auth, 'Bearer ')) {
|
||||
return Str::substr($auth, 7);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get current route prefix
|
||||
*
|
||||
* @param Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getRoutePrefix(Request $request)
|
||||
{
|
||||
return (Route::getCurrentRoute() ? Route::getCurrentRoute()->getPrefix() : null) ??
|
||||
($request->route() ? $request->route()->getPrefix() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get route from request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Routing\Route|null
|
||||
*/
|
||||
public static function getRoute(Request $request)
|
||||
{
|
||||
return Route::getRoutes()->getByName(static::getRouteName($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get composer.json infos
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getPackageInfo(): array
|
||||
{
|
||||
if (!static::$packageInfos) {
|
||||
static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../../composer.json'), true);
|
||||
}
|
||||
return static::$packageInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the request signature
|
||||
*
|
||||
* @param string $secret
|
||||
* @param string $uri
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return string request signature
|
||||
* @throws ApplicationException If an error occurs.
|
||||
*/
|
||||
public static function signRequest(
|
||||
#[\SensitiveParameter] string $secret,
|
||||
string $uri,
|
||||
string $method,
|
||||
array $parameters
|
||||
): string {
|
||||
try {
|
||||
$uri = collect(\parse_url($uri));
|
||||
$method = \strtolower($method);
|
||||
|
||||
// * Remove signature from parameters to calculate the hashmac.
|
||||
$signature = collect([
|
||||
'uri' => sprintf(
|
||||
'%s://%s%s',
|
||||
$uri->get('scheme'),
|
||||
$uri->get('host'),
|
||||
$uri->get('path')
|
||||
),
|
||||
'method' => $method
|
||||
]);
|
||||
|
||||
$flattenRequestArray = function (array $array) use (&$flattenRequestArray) {
|
||||
$out = [];
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$subArray = $flattenRequestArray($value);
|
||||
foreach ($subArray as $subKey => $subValue) {
|
||||
$out["$key.$subKey"] = $subValue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$out[$key] = $value;
|
||||
}
|
||||
return $out;
|
||||
};
|
||||
|
||||
$parameters = $flattenRequestArray($parameters);
|
||||
$parameters = collect($parameters);
|
||||
|
||||
// * Remove signature from parameters.
|
||||
$parameters->pull('signature');
|
||||
|
||||
// * Natural sort all parameters.
|
||||
$parameters = $parameters->sortKeys(\SORT_NATURAL)
|
||||
->map(function ($value, $key) {
|
||||
return sprintf('%s=%s', \rawurlencode($key), \rawurlencode($value));
|
||||
});
|
||||
|
||||
$signature->push($parameters->implode('&'));
|
||||
|
||||
$calculatedHash = base64_encode(hash_hmac(
|
||||
'sha256',
|
||||
$signature->implode('\n'),
|
||||
Crypt::decryptString($secret),
|
||||
true
|
||||
));
|
||||
|
||||
if (!$calculatedHash) {
|
||||
throw new ApplicationException('Failed to calculate requests hash');
|
||||
}
|
||||
|
||||
return $calculatedHash;
|
||||
} catch (DecryptException $e) {
|
||||
throw new ApplicationException('Failed to decrypt service credentials', 0, $e);
|
||||
} catch (\Exception $e) {
|
||||
throw new ApplicationException('An error occured when authenticating service', 0, $e);
|
||||
} //end try
|
||||
}
|
||||
}
|
||||
64
app/Lib/Helpers/CollectionHelper.php
Normal file
64
app/Lib/Helpers/CollectionHelper.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CollectionHelper
|
||||
{
|
||||
/**
|
||||
* Paginate a collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @param integer $pageSize
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
|
||||
{
|
||||
$page = Paginator::resolveCurrentPage('page');
|
||||
|
||||
return self::paginateManually($results, $pageSize, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate a collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @param integer $pageSize
|
||||
* @param integer $page
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
$total = $results->count();
|
||||
|
||||
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => 'page',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new length-aware paginator instance.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param integer $total
|
||||
* @param integer $perPage
|
||||
* @param integer $currentPage
|
||||
* @param array $options
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
|
||||
{
|
||||
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'perPage' => $perPage,
|
||||
'currentPage' => $currentPage,
|
||||
'options' => $options
|
||||
]);
|
||||
}
|
||||
}
|
||||
190
app/Lib/Helpers/ExceptionsHelper.php
Normal file
190
app/Lib/Helpers/ExceptionsHelper.php
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
class ExceptionsHelper
|
||||
{
|
||||
/**
|
||||
* Handle sentry event
|
||||
*
|
||||
* @param \Sentry\Event $event
|
||||
* @return null|\Sentry\Event
|
||||
*/
|
||||
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
|
||||
{
|
||||
// Remove unwanted reports.
|
||||
$event->setModules([]);
|
||||
$event->setOsContext(null);
|
||||
|
||||
/** @var \Sentry\ExceptionDataBag[] */
|
||||
$exceptionBag = [];
|
||||
foreach ($event->getExceptions() as $exceptionDataBag) {
|
||||
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
|
||||
}
|
||||
$event->setExceptions($exceptionBag);
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonimize Sentry Exception bag
|
||||
*
|
||||
* @param \Sentry\ExceptionDataBag $exceptionDataBag
|
||||
* @return \Sentry\ExceptionDataBag
|
||||
*/
|
||||
private static function getAnonymizedSentryException(
|
||||
\Sentry\ExceptionDataBag $exceptionDataBag
|
||||
): \Sentry\ExceptionDataBag {
|
||||
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
|
||||
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
|
||||
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
|
||||
}
|
||||
return $exceptionDataBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymise sentry stacktrace
|
||||
*
|
||||
* @param \Sentry\Stacktrace $stackTrace
|
||||
* @return \Sentry\Stacktrace
|
||||
*/
|
||||
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
|
||||
{
|
||||
/** @var \Sentry\Frame[] */
|
||||
$frames = \array_reverse($stackTrace->getFrames(), true);
|
||||
$i = \count($frames) - 1;
|
||||
// * Remove all but keep first (index.php).
|
||||
while ($i-- > 0) {
|
||||
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
|
||||
}
|
||||
foreach ($frames as $index => $frame) {
|
||||
$frame->setPostContext([]);
|
||||
$frame->setPreContext([]);
|
||||
$frame->setContextLine('');
|
||||
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
|
||||
$stackTrace->addFrame($frame);
|
||||
if ($index === 0) {
|
||||
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
|
||||
}
|
||||
}
|
||||
return $stackTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get anonymized sentry params
|
||||
*
|
||||
* @param mixed[] $params
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getAnonymizedSentryParams(array $params): array
|
||||
{
|
||||
foreach ($params as &$param) {
|
||||
$type = \strtolower(\gettype($param));
|
||||
if (\is_string($param)) {
|
||||
$type = \explode(' ', \trim(strtolower($param)))[0] ?? null;
|
||||
}
|
||||
switch (true) {
|
||||
case $type === 'lambda':
|
||||
case $type === 'object':
|
||||
continue 2;
|
||||
case $type === 'array' and \is_array($param):
|
||||
$param = sprintf('Array(%d)', \count($param));
|
||||
continue 2;
|
||||
default:
|
||||
if (\is_numeric($param)) {
|
||||
$param = \ucfirst($type);
|
||||
continue 2;
|
||||
}
|
||||
if (\is_string($param)) {
|
||||
$param = sprintf('String(%d)', \strlen($param));
|
||||
continue 2;
|
||||
}
|
||||
} //end switch
|
||||
} //end foreach
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Anonymize at least Sql queries
|
||||
*
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnonymizedMessage(string $message): string
|
||||
{
|
||||
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
|
||||
if ($sqlPos = \strpos($message, 'to use near')) {
|
||||
$message = \mb_strcut($message, 0, $sqlPos);
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymize stack trace
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnonymizedStackTrace(\Throwable $e): string
|
||||
{
|
||||
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
|
||||
$traceStack = $e->getTrace();
|
||||
$traceStr = '';
|
||||
$maxStackStage = 0;
|
||||
foreach ($traceStack as $stackStage => $stack) {
|
||||
$maxStackStage = $stackStage;
|
||||
$traceStr .= \sprintf(
|
||||
'#%s %s(%d): ',
|
||||
$stackStage,
|
||||
str_replace($projectPath, '', $stack['file'] ?? ''),
|
||||
$stack['line'] ?? ''
|
||||
);
|
||||
if (isset($stack['class'])) {
|
||||
$traceStr .= "{$stack['class']}->{$stack['function']}(";
|
||||
if (isset($stack['args'])) {
|
||||
$traceStr .= self::handleArgs($stack);
|
||||
}
|
||||
$traceStr .= ")\n";
|
||||
} elseif (isset($stack['function'])) {
|
||||
$traceStr .= "{$stack['function']}(";
|
||||
if (isset($stack['args'])) {
|
||||
$traceStr .= self::handleArgs($stack);
|
||||
}
|
||||
$traceStr .= ")\n";
|
||||
}
|
||||
} //end foreach
|
||||
$maxStackStage++;
|
||||
$traceStr .= "#$maxStackStage {main}";
|
||||
return $traceStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Args
|
||||
*
|
||||
* @param array $stack
|
||||
* @return string
|
||||
*/
|
||||
public static function handleArgs(array $stack): string
|
||||
{
|
||||
$traceArgs = '';
|
||||
$argsCount = count($stack['args']);
|
||||
for ($i = 0; $i < $argsCount; $i++) {
|
||||
$type = \gettype($stack['args'][$i]);
|
||||
switch ($type) {
|
||||
case 'object':
|
||||
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
|
||||
break;
|
||||
case 'array':
|
||||
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
|
||||
break;
|
||||
case 'string':
|
||||
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
|
||||
break;
|
||||
default:
|
||||
$traceArgs .= \gettype($stack['args'][$i]);
|
||||
}
|
||||
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
|
||||
}
|
||||
return $traceArgs;
|
||||
}
|
||||
}
|
||||
454
app/Lib/Helpers/FileStorageHelper.php
Normal file
454
app/Lib/Helpers/FileStorageHelper.php
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* This class is used to handle files in LDC Laravel stack
|
||||
*
|
||||
* * The storage strategy will be using date.
|
||||
* The file name may contain sufix timestamp highresolution time (hrtime or microtime) if is duplicate.
|
||||
* (function() { return time() . ' ' . Str::slug(microtime(true)) . ' ' . hrtime(true); })();
|
||||
*
|
||||
* posts/ - The model table name
|
||||
* 10/ - Year
|
||||
* 04/ - Month
|
||||
* 19/ - Day
|
||||
* 5f/ - First two letters of md5 file
|
||||
* black-cat-under-red-car.png - Slugified file name
|
||||
* black-cat-under-red-car-286071690833400.png - Slugified file name that is duplicated
|
||||
*
|
||||
* The final path will be storage/modelfiles/posts/10/04/19/5f/black-cat-under-red-car-286071690833400.png
|
||||
*
|
||||
* ! No limits of file per folder will be handled.
|
||||
*
|
||||
* @author Munsch Jérémy <jeremy.munsch@ldc.fr>
|
||||
*/
|
||||
class FileStorageHelper
|
||||
{
|
||||
/**
|
||||
* Destroy the file associate with the attribute.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return void
|
||||
*/
|
||||
public static function removeFile(Model $model, string $attribute): void
|
||||
{
|
||||
$path = \ltrim(\urldecode($model->{$attribute}), '/');
|
||||
if (\strpos($path, 'storage') !== 0) {
|
||||
return;
|
||||
}
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', $path);
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the file associate with the attribute old value (before updated to DB).
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @param boolean $onlyIfUpdated
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
|
||||
{
|
||||
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
|
||||
if ($onlyIfUpdated and $model->{$attribute} === ToolboxHelper::getOriginal($model, $attribute)) {
|
||||
return;
|
||||
}
|
||||
$originalPath = \ltrim(\urldecode(ToolboxHelper::getOriginal($model, $attribute)), '/');
|
||||
if (\strpos($originalPath, 'storage') !== 0) {
|
||||
return;
|
||||
}
|
||||
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
|
||||
if (Storage::disk('public')->exists($originalPath)) {
|
||||
Storage::disk('public')->delete($originalPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a file using optimized storage strategy
|
||||
*
|
||||
* You can use filename param to overload file name.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
|
||||
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
|
||||
* @param boolean $slugify Force filename slugification.
|
||||
* @param string|null $filename This string may be used to store the file.
|
||||
* @param boolean $private To store the file in a private space.
|
||||
* @return string
|
||||
* @throws \RuntimeException If file copy fails.
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
public static function storeFile(
|
||||
Model $model,
|
||||
$file,
|
||||
bool $slugify = false,
|
||||
string $filename = null,
|
||||
bool $private = false
|
||||
): string {
|
||||
// phpcs:enable
|
||||
$tableName = $model->getTable();
|
||||
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
|
||||
/** @var \Illuminate\Http\UploadedFile $file */
|
||||
$filename = $filename ?: $file->getClientOriginalName();
|
||||
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
|
||||
$newPath = self::prepareMoveFile($tableName, $filename, $private);
|
||||
$newPath = str_replace(storage_path('app'), '', $newPath);
|
||||
|
||||
if (!$private) {
|
||||
// * Store public file.
|
||||
$file->storePubliclyAs(
|
||||
pathinfo($newPath, \PATHINFO_DIRNAME),
|
||||
pathinfo($newPath, \PATHINFO_BASENAME)
|
||||
);
|
||||
return 'storage' . str_replace('/public', '', $newPath);
|
||||
}
|
||||
|
||||
$file->storeAs(
|
||||
pathinfo($newPath, \PATHINFO_DIRNAME),
|
||||
pathinfo($newPath, \PATHINFO_BASENAME)
|
||||
);
|
||||
|
||||
return str_replace('/private/', '', $newPath);
|
||||
} //end if
|
||||
if (\is_object($file) and $file instanceof \SplFileInfo) {
|
||||
/** @var \SplFileInfo $file */
|
||||
$filename = $filename ?: $file->getBaseName();
|
||||
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
|
||||
$newPath = self::prepareMoveFile($tableName, $filename, $private);
|
||||
if (!File::copy($file->getRealPath(), $newPath)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Failed to copy file %s to %s',
|
||||
$file->getRealPath(),
|
||||
$newPath
|
||||
));
|
||||
}
|
||||
return $private ?
|
||||
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
|
||||
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
|
||||
}
|
||||
return strval($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store all files from string attribute html
|
||||
* If oldHtmlString is passed, a diff is done with new and old files
|
||||
* to keep only files that are present in the new html string.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
|
||||
* @param string $htmlString The html string to parse and modify files.
|
||||
* @param string|null $oldHtmlString The old html string with alreadyParsed
|
||||
* files using storeModelFilesFromHtml.
|
||||
* @return string Html with files pathes replaced using storage strategy.
|
||||
*/
|
||||
public static function storeModelFilesFromHtml(
|
||||
Model $model,
|
||||
string $htmlString,
|
||||
string $oldHtmlString = null
|
||||
): string {
|
||||
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
|
||||
->each(function (string $path) use ($model, &$htmlString) {
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter */
|
||||
$disk = Storage::disk('uploads');
|
||||
$fileInfo = new \SplFileInfo($disk->path(
|
||||
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
|
||||
));
|
||||
$htmlString = ToolboxHelper::mbReplace(
|
||||
$path,
|
||||
'/' . self::storeFile($model, $fileInfo, true),
|
||||
$htmlString
|
||||
);
|
||||
});
|
||||
|
||||
if ($oldHtmlString) {
|
||||
self::removeOldModelFilesFromHtml($htmlString, $oldHtmlString);
|
||||
}
|
||||
|
||||
return $htmlString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all old files from string attribute html that are stored in public that
|
||||
* are nove present anymore on new html string
|
||||
*
|
||||
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
|
||||
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
|
||||
{
|
||||
self::removeOldFiles(self::findFilesInHtml($oldHtmlString), self::findFilesInHtml($newHtmlString));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all old files that are stored in public that
|
||||
* are not present anymore on new array files
|
||||
*
|
||||
* @param string[] $oldFiles The old array files.
|
||||
* @param string[] $newFiles The new array files.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeOldFiles(array $oldFiles, array $newFiles): void
|
||||
{
|
||||
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
|
||||
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
|
||||
$filesToRemove = $oldFiles->diff($filesToKeep);
|
||||
$filesToRemove->each(function (string $publicStorageFilePath) {
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all files from string attribute html that are stored in public
|
||||
*
|
||||
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeModelFilesOfHtml(string $htmlString): void
|
||||
{
|
||||
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
|
||||
$filesToRemove->each(function (string $publicStorageFilePath) {
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
|
||||
if (Storage::disk('public')->exists($path)) {
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all occurence of files in html
|
||||
*
|
||||
* @param string $html
|
||||
* @return array
|
||||
*/
|
||||
public static function findFilesInHtml(string $html): array
|
||||
{
|
||||
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
|
||||
return collect($m)->pluck('href')->filter()
|
||||
->concat(collect($m)->pluck('url')->filter())
|
||||
->concat(collect($m)->pluck('src')->filter())
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter array to get only uploaded files that exists
|
||||
* on disk 'public' (using helper storage strategy)
|
||||
*
|
||||
* @param array $urls
|
||||
* @return array
|
||||
*/
|
||||
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
|
||||
{
|
||||
return \collect($urls)->unique()
|
||||
// * Filter to get only /uploads and using app url or empty
|
||||
->filter(function (string $url) {
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
|
||||
})->filter(function (string $url) {
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return Storage::disk('public')
|
||||
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter array to get only uploaded files that exists
|
||||
* on disk 'uploads'
|
||||
*
|
||||
* @param array $urls
|
||||
* @return array
|
||||
*/
|
||||
private static function onlyUploadedFilesWithFileManager(array $urls): array
|
||||
{
|
||||
return \collect($urls)->unique()
|
||||
// * Filter to get only /uploads and using app url or empty
|
||||
->filter(function (string $url) {
|
||||
$host = \parse_url($url, \PHP_URL_HOST);
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
|
||||
\strpos(\ltrim($path, '/'), 'uploads') === 0;
|
||||
})->filter(function (string $url) {
|
||||
$path = \parse_url($url, \PHP_URL_PATH);
|
||||
return Storage::disk('uploads')
|
||||
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare storage path and return
|
||||
* the file full path
|
||||
*
|
||||
* @param string $tableName Model table name.
|
||||
* @param string $filename The basename for the file.
|
||||
* @param boolean $private May the file be stored in a private dir.
|
||||
* @return string
|
||||
*/
|
||||
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
|
||||
{
|
||||
// * Security filter
|
||||
$filename = \htmlspecialchars($filename);
|
||||
// * Security filter + beautifier
|
||||
$filename = self::sanitizeFileName($filename);
|
||||
// * Slugify filename
|
||||
$ext = \pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
$filename = Str::slug(\pathinfo($filename, \PATHINFO_FILENAME)) .
|
||||
(\strlen($ext) ? ".{$ext}" : '');
|
||||
|
||||
$folderPath = self::getStoragePath(
|
||||
$private ? 'app/private/modelfiles' : 'app/public/modelfiles',
|
||||
$tableName,
|
||||
$filename,
|
||||
$private
|
||||
);
|
||||
$filename = self::getFileUniqueName($folderPath, $filename);
|
||||
return "{$folderPath}/{$filename}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sub folders to store files using pattern
|
||||
* storage/modelfiles/posts/10/04/19/5f
|
||||
* year/month/day/md5 first 2 letters
|
||||
*
|
||||
* @param string $storage_dir
|
||||
* @param string $table
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
|
||||
{
|
||||
$filenameHash = \md5($filename);
|
||||
$pathFolder = \storage_path(sprintf(
|
||||
'%s/%s/%s/%s/%s/%s',
|
||||
$storage_dir,
|
||||
$table,
|
||||
gmdate('y'),
|
||||
gmdate('m'),
|
||||
gmdate('d'),
|
||||
substr($filenameHash, 0, 2)
|
||||
));
|
||||
if (!File::exists($pathFolder)) {
|
||||
File::makeDirectory($pathFolder, 0755, true);
|
||||
}
|
||||
return $pathFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique filename if the path is already occupied
|
||||
*
|
||||
* @param string $folderPath
|
||||
* @param string $filename
|
||||
* @return string
|
||||
* @throws \RuntimeException If a unique filename cannot be obtained.
|
||||
*/
|
||||
private static function getFileUniqueName(string $folderPath, string $filename): string
|
||||
{
|
||||
$attempts = 0;
|
||||
$maxAttempts = 20;
|
||||
while (File::exists("{$folderPath}/{$filename}")) {
|
||||
if ($attempts++ >= $maxAttempts) {
|
||||
throw new \RuntimeException("Failed to have a unique filename for {$folderPath}/{$filename}");
|
||||
}
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
|
||||
\uniqid('-') . '.' .
|
||||
pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slugify the filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public static function slugifyFileName(string $filename): string
|
||||
{
|
||||
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize File name for uploaded files
|
||||
*
|
||||
* @param string $filename The original uploaded client file name.
|
||||
* @param boolean $beautify Beautify the filename.
|
||||
* @return string The sanitized file name.
|
||||
*/
|
||||
public static function sanitizeFileName(string $filename, bool $beautify = true): string
|
||||
{
|
||||
// * Sanitize filename
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$filename = preg_replace(
|
||||
'~
|
||||
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
||||
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||
~x',
|
||||
'-',
|
||||
$filename
|
||||
);
|
||||
// phpcs:enable
|
||||
// * Avoids '.' '..' or '.hiddenFiles' .
|
||||
$filename = Str::of($filename)->ltrim('.-');
|
||||
// * Optional beautification
|
||||
if ($beautify) {
|
||||
$filename = static::beautifyFilename($filename);
|
||||
}
|
||||
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
|
||||
/** @var string */
|
||||
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
/** @var string */
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME);
|
||||
|
||||
$encoding = mb_detect_encoding($filename);
|
||||
$encoding = $encoding ? $encoding : null;
|
||||
return \mb_strcut(
|
||||
$filename,
|
||||
0,
|
||||
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
|
||||
$encoding
|
||||
) . (!empty($extension) ? ".$extension" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautify a filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function beautifyFilename(string $filename): string
|
||||
{
|
||||
// Reduce consecutive characters.
|
||||
$filename = preg_replace([
|
||||
// * "file name.zip" becomes 'file-name.zip'
|
||||
'/ +/',
|
||||
// * "file___name.zip" becomes 'file-name.zip'
|
||||
'/_+/',
|
||||
// * "file---name.zip" becomes 'file-name.zip'
|
||||
'/-+/'
|
||||
], '-', $filename);
|
||||
$filename = preg_replace([
|
||||
// * "file--.--.-.--name.zip" becomes "file.name.zip"
|
||||
'/-*\.-*/',
|
||||
// * "file...name..zip" becomes "file.name.zip"
|
||||
'/\.{2,}/'
|
||||
], '.', $filename);
|
||||
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
|
||||
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
|
||||
// * ".file-name.-" becomes "file-name"
|
||||
$filename = trim($filename, '.-');
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
601
app/Lib/Helpers/ToolboxHelper.php
Normal file
601
app/Lib/Helpers/ToolboxHelper.php
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
class ToolboxHelper
|
||||
{
|
||||
/**
|
||||
* Multibyte strtok
|
||||
*
|
||||
* @param string $delimiters
|
||||
* @param string $str
|
||||
* @return string|false
|
||||
*/
|
||||
public static function mbStrtok(string $delimiters, string $str = null): string|false
|
||||
{
|
||||
// * Keep track of the position on the string for each subsequent call.
|
||||
static $pos = 0;
|
||||
static $string = "";
|
||||
|
||||
// * If a new string is passed, reset the static parameters.
|
||||
if ($str != null) {
|
||||
$pos = 0;
|
||||
$string = $str;
|
||||
}
|
||||
|
||||
// * Initialize the token.
|
||||
$token = "";
|
||||
|
||||
while ($pos < mb_strlen($string)) {
|
||||
$char = mb_substr($string, $pos, 1);
|
||||
$pos++;
|
||||
|
||||
if (mb_strpos($delimiters, $char) === false) {
|
||||
$token .= $char;
|
||||
} else {
|
||||
// * Don't return empty strings.
|
||||
if ($token != "") {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// * Check whether there is a last token to return.
|
||||
if ($token != "") {
|
||||
return $token;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multibyte sub str replace
|
||||
*
|
||||
* @param string $original
|
||||
* @param string $replacement
|
||||
* @param integer $position
|
||||
* @param integer $length
|
||||
* @return string
|
||||
*/
|
||||
public static function mbSubstrReplace(string $original, string $replacement, int $position, int $length): string
|
||||
{
|
||||
$startString = mb_substr($original, 0, $position, "UTF-8");
|
||||
$endString = mb_substr($original, $position + $length, mb_strlen($original), "UTF-8");
|
||||
|
||||
return $startString . $replacement . $endString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate recaptcha
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param boolean $isV3
|
||||
* @return void
|
||||
*/
|
||||
public static function validateRecaptcha(Request $request, bool $isV3 = true)
|
||||
{
|
||||
Validator::validate($request->all(), [
|
||||
'g-recaptcha-response' => [
|
||||
'required', 'string',
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
function ($attribute, $value, $fail) use ($isV3) {
|
||||
try {
|
||||
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
|
||||
'secret' => config('services.google.recaptcha.private'),
|
||||
'response' => \strval($value),
|
||||
'remoteip' => app('clientIp')
|
||||
]);
|
||||
if ($e = $response->toException()) {
|
||||
throw $e;
|
||||
}
|
||||
if ($isV3) {
|
||||
if (
|
||||
!$response->json('success') or
|
||||
!$response->json('challenge_ts') or
|
||||
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
|
||||
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
|
||||
!$response->json('score')
|
||||
) {
|
||||
throw new \RuntimeException();
|
||||
}
|
||||
if (\floatval($response->json('score')) < 0.8) {
|
||||
$fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!$response->json('success') or
|
||||
!$response->json('challenge_ts') or
|
||||
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
|
||||
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
|
||||
) {
|
||||
throw new \RuntimeException();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$fail('Erreur lors de la validation du formulaire veuillez réssayer');
|
||||
} //end try
|
||||
}
|
||||
]
|
||||
], [], ['g-recaptcha-response' => 'reCaptcha']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip scheme (https) on oembed urls
|
||||
* Only embed allowed are for MP4 self hosted videos right now
|
||||
*
|
||||
* https is force by ckeditor MediaElements which natively supports
|
||||
* only cloud hosted medias (youtube, spotify etc..)
|
||||
*
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
|
||||
{
|
||||
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That no Back Office url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalBoUrls(string $field, string $text): string
|
||||
{
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => trans(
|
||||
'crud.libs.toolbox.assertNoInternalBoUrls',
|
||||
['attribute' => $field]
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert That only external url is present on txt
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
*/
|
||||
public static function assertNoInternalUrls(string $field, string $text): string
|
||||
{
|
||||
self::assertNoInternalBoUrls($field, $text);
|
||||
$routes = Route::getRoutes();
|
||||
// * Url replace and email replace.
|
||||
$text = preg_replace_callback(
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
|
||||
function ($matches) use ($routes, $field) {
|
||||
$match = collect($matches)->first() ?? '';
|
||||
$url = $match;
|
||||
try {
|
||||
$request = Request::create($url);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
$routes->match($request)
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => trans(
|
||||
'crud.libs.toolbox.assertNoInternalUrls',
|
||||
['attribute' => $field, 'appname' => config('app.name')]
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutltibyte string replace
|
||||
*
|
||||
* @param array|string $search
|
||||
* @param array|string $replace
|
||||
* @param array|string $subject
|
||||
* @param integer $count
|
||||
* @return array|string
|
||||
*/
|
||||
public static function mbReplace(
|
||||
array|string $search,
|
||||
array|string $replace,
|
||||
array|string $subject,
|
||||
int &$count = 0
|
||||
): array|string {
|
||||
if (!is_array($search) && is_array($replace)) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($subject)) {
|
||||
// Call mb_replace for each single string in $subject .
|
||||
foreach ($subject as &$string) {
|
||||
$string = &self::mbReplace($search, $replace, $string, $count);
|
||||
}
|
||||
} elseif (is_array($search)) {
|
||||
if (!is_array($replace)) {
|
||||
foreach ($search as &$string) {
|
||||
$subject = self::mbReplace($string, $replace, $subject, $count);
|
||||
}
|
||||
} else {
|
||||
$n = max(count($search), count($replace));
|
||||
while ($n--) {
|
||||
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
|
||||
next($search);
|
||||
next($replace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$parts = mb_split(preg_quote($search), $subject);
|
||||
$count = count($parts) - 1;
|
||||
$subject = implode($replace, $parts);
|
||||
} //end if
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field is unique
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @param string|integer|float|boolean|null|array $value
|
||||
* @param integer|null $id
|
||||
* @param callable|null $query
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
|
||||
*/
|
||||
public static function assertFieldIsUnique(
|
||||
string $table,
|
||||
string $field,
|
||||
string|int|float|bool|null|array $value,
|
||||
?int $id = null,
|
||||
?callable $query = null
|
||||
) {
|
||||
if (!$query) {
|
||||
Validator::make([$field => $value], [
|
||||
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
|
||||
], [
|
||||
self::mbReplace('.', '\.', "{$field}.unique")
|
||||
=> trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value])
|
||||
])->validate();
|
||||
return;
|
||||
}
|
||||
Validator::make([$field => $value], [
|
||||
$field => Rule::unique($table)->where($query)->ignore($id),
|
||||
], [
|
||||
$field . '.unique' => trans(
|
||||
'crud.libs.toolbox.assertFieldIsUnique',
|
||||
['attribute' => $field, 'value' => $value]
|
||||
)
|
||||
])->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get original polyfill with autotranslate behavior
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getOriginal(Model $model, string $attribute): mixed
|
||||
{
|
||||
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
|
||||
return $model->getTranslation(
|
||||
$attribute,
|
||||
$model->getLocale(),
|
||||
$model->useFallbackLocale()
|
||||
);
|
||||
}
|
||||
return $model->getOriginal($attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic model query
|
||||
*
|
||||
* @param string $defaultLocale
|
||||
* @param \Illuminate\Contracts\Database\Eloquent\Builder $q
|
||||
* @param string $column
|
||||
* @param string $value
|
||||
* @return \Illuminate\Contracts\Database\Eloquent\Builder
|
||||
*/
|
||||
public static function queryColumnWithLocales(
|
||||
string $defaultLocale,
|
||||
Builder $q,
|
||||
string $column,
|
||||
string $value
|
||||
): Builder {
|
||||
$currentLocale = app()->currentLocale();
|
||||
$locales = collect(config('app.locales'))
|
||||
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
|
||||
if (!$locales->has($defaultLocale)) {
|
||||
// * Remove default locale.
|
||||
$locales->pull($defaultLocale);
|
||||
}
|
||||
if (!$locales->has($currentLocale)) {
|
||||
// * Remove current locale.
|
||||
$locales->pull($currentLocale);
|
||||
}
|
||||
|
||||
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value) {
|
||||
// * Query default locale
|
||||
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$defaultLocale}') = '{$value}'");
|
||||
if ($defaultLocale !== $currentLocale) {
|
||||
// * Query current locale
|
||||
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$currentLocale}') = '{$value}'");
|
||||
}
|
||||
// * Query any other locale
|
||||
$locales->each(fn ($locale) => $q->orWhereRaw(
|
||||
"JSON_EXTRACT({$column}, '$.{$locale}') = '{$value}'"
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array from a dot separated string
|
||||
*
|
||||
* @param string $path
|
||||
* @param mixed $value
|
||||
* @param string $separator
|
||||
* @return array
|
||||
*/
|
||||
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
|
||||
{
|
||||
$arr = $value;
|
||||
$keys = array_reverse(explode($separator, $path));
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$arr = [$key => $arr];
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an object to array
|
||||
*
|
||||
* @param array|object $input
|
||||
* @return array
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function objectToArray($input): array
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (is_object($input) or is_array($input)) {
|
||||
$ret = (array)$input;
|
||||
foreach ($ret as &$item) {
|
||||
// Recursively process EACH element only if not scalar.
|
||||
$item = (is_object($item) or is_array($item)) ?
|
||||
self::objectToArray($item) : $item;
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an array to object
|
||||
*
|
||||
* @param array|object $input
|
||||
* @return object
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function arrayToObject($input): object
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (is_object($input) or is_array($input)) {
|
||||
$ret = (object)$input;
|
||||
foreach ($ret as &$item) {
|
||||
// Recursively process EACH element only if not scalar.
|
||||
$item = (is_object($item) or is_array($item)) ?
|
||||
self::arrayToObject($item) : $item;
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate picture
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param string|null $value
|
||||
* @param callback $fail
|
||||
* @param object $sizes
|
||||
* @return void
|
||||
*/
|
||||
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes)
|
||||
{
|
||||
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
|
||||
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
return;
|
||||
}
|
||||
/** @var string */
|
||||
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
|
||||
if (
|
||||
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
|
||||
'image/jpeg', 'image/jpg', 'image/png', 'image/gif'
|
||||
])
|
||||
) {
|
||||
$fail(trans(':attribute doit être une image de type \'image/jpeg,image/jpg,image/png,image/gif\'', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
}
|
||||
if (!($dimensions = \getimagesize($fileFullPath)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
|
||||
$fail(trans(':attribute impossible de valider les dimensions', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
} else {
|
||||
$width = intval($dimensions[0]);
|
||||
$height = intval($dimensions[1]);
|
||||
|
||||
$xMessage = trans(
|
||||
":attribute la largeur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
|
||||
);
|
||||
if ($sizes->minWith === $sizes->maxWith) {
|
||||
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWith}px");
|
||||
}
|
||||
$yMessage = trans(
|
||||
":attribute la hauteur doit être comprise entre {$sizes->minWith}px et {$sizes->maxWith}px"
|
||||
);
|
||||
if ($sizes->minHeight === $sizes->maxHeight) {
|
||||
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minWith}px");
|
||||
}
|
||||
|
||||
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) {
|
||||
$fail($xMessage);
|
||||
}
|
||||
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
|
||||
$fail($yMessage);
|
||||
}
|
||||
} //end if
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file full path
|
||||
*
|
||||
* @param string $value
|
||||
* @param boolean $useStorage
|
||||
* @return string
|
||||
*/
|
||||
private static function getUploadedFileFullPath(string $value, bool $useStorage): string
|
||||
{
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter */
|
||||
$disk = Storage::disk();
|
||||
return !$useStorage ? (realpath($value) ?: $value) : $disk->path($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file mimetype
|
||||
*
|
||||
* @param string $value
|
||||
* @param boolean $useStorage
|
||||
* @return string|false
|
||||
*/
|
||||
private static function getUploadedFileMimeType(string $value, bool $useStorage)
|
||||
{
|
||||
return !$useStorage ? File::mimeType($value) : Storage::mimeType($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch class list from a specific namespace
|
||||
*
|
||||
* @param string $namespace
|
||||
* @return array<string> An array of classe namespaces
|
||||
*/
|
||||
public static function getClassesUsingNamespace(string $namespace): array
|
||||
{
|
||||
return collect(File::files(\app_path(Str::of($namespace)->replaceFirst('\App', ''))))
|
||||
->filter(fn (SplFileInfo $splFileInfo) => $splFileInfo->getExtension() === 'php')
|
||||
->map(fn (SplFileInfo $splFileInfo) => Str::of($splFileInfo->getBasename())
|
||||
->replace('.' . $splFileInfo->getExtension(), '')->prepend("{$namespace}\\"))->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all classes from a specific namespace that implement a specific interface
|
||||
*
|
||||
* @param string $namespace
|
||||
* @param string $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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validated pagination from request.
|
||||
*
|
||||
* @param integer $default
|
||||
* @param string $field
|
||||
* @param string $enumPath
|
||||
* @return integer
|
||||
*/
|
||||
public static function getValidatedEnum(int $default, string $field, string $enumPath): int
|
||||
{
|
||||
try {
|
||||
return \intval(Validator::make(
|
||||
[$field => \request()->get($field)],
|
||||
[$field => new Enum($enumPath)]
|
||||
)->validated()[$field]);
|
||||
} catch (ValidationException $e) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Some utilities methods
|
||||
*
|
||||
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
|
||||
*/
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
class Utils
|
||||
{
|
||||
/**
|
||||
* Replace stange accents
|
||||
*
|
||||
* https://stackoverflow.com/questions/3371697/replacing-accented-characters-php
|
||||
* @author Jérémy Munsch <jeremy.munsch@jeremydev.ovh>
|
||||
* @param string $subject
|
||||
* @return string
|
||||
*/
|
||||
public static function replaceSpecChar(string $subject): string
|
||||
{
|
||||
$char_map = array(
|
||||
"ъ" => "-", "ь" => "-", "Ъ" => "-", "Ь" => "-",
|
||||
"А" => "A", "Ă" => "A", "Ǎ" => "A", "Ą" => "A", "À" => "A", "Ã" => "A", "Á" => "A", "Æ" => "A", "Â" => "A", "Å" => "A", "Ǻ" => "A", "Ā" => "A", "א" => "A",
|
||||
"Б" => "B", "ב" => "B", "Þ" => "B",
|
||||
"Ĉ" => "C", "Ć" => "C", "Ç" => "C", "Ц" => "C", "צ" => "C", "Ċ" => "C", "Č" => "C", "©" => "C", "ץ" => "C",
|
||||
"Д" => "D", "Ď" => "D", "Đ" => "D", "ד" => "D", "Ð" => "D",
|
||||
"È" => "E", "Ę" => "E", "É" => "E", "Ë" => "E", "Ê" => "E", "Е" => "E", "Ē" => "E", "Ė" => "E", "Ě" => "E", "Ĕ" => "E", "Є" => "E", "Ə" => "E", "ע" => "E",
|
||||
"Ф" => "F", "Ƒ" => "F",
|
||||
"Ğ" => "G", "Ġ" => "G", "Ģ" => "G", "Ĝ" => "G", "Г" => "G", "ג" => "G", "Ґ" => "G",
|
||||
"ח" => "H", "Ħ" => "H", "Х" => "H", "Ĥ" => "H", "ה" => "H",
|
||||
"I" => "I", "Ï" => "I", "Î" => "I", "Í" => "I", "Ì" => "I", "Į" => "I", "Ĭ" => "I", "I" => "I", "И" => "I", "Ĩ" => "I", "Ǐ" => "I", "י" => "I", "Ї" => "I", "Ī" => "I", "І" => "I",
|
||||
"Й" => "J", "Ĵ" => "J",
|
||||
"ĸ" => "K", "כ" => "K", "Ķ" => "K", "К" => "K", "ך" => "K",
|
||||
"Ł" => "L", "Ŀ" => "L", "Л" => "L", "Ļ" => "L", "Ĺ" => "L", "Ľ" => "L", "ל" => "L",
|
||||
"מ" => "M", "М" => "M", "ם" => "M",
|
||||
"Ñ" => "N", "Ń" => "N", "Н" => "N", "Ņ" => "N", "ן" => "N", "Ŋ" => "N", "נ" => "N", "ʼn" => "N", "Ň" => "N",
|
||||
"Ø" => "O", "Ó" => "O", "Ò" => "O", "Ô" => "O", "Õ" => "O", "О" => "O", "Ő" => "O", "Ŏ" => "O", "Ō" => "O", "Ǿ" => "O", "Ǒ" => "O", "Ơ" => "O",
|
||||
"פ" => "P", "ף" => "P", "П" => "P",
|
||||
"ק" => "Q",
|
||||
"Ŕ" => "R", "Ř" => "R", "Ŗ" => "R", "ר" => "R", "Р" => "R", "®" => "R",
|
||||
"Ş" => "S", "Ś" => "S", "Ș" => "S", "Š" => "S", "С" => "S", "Ŝ" => "S", "ס" => "S",
|
||||
"Т" => "T", "Ț" => "T", "ט" => "T", "Ŧ" => "T", "ת" => "T", "Ť" => "T", "Ţ" => "T",
|
||||
"Ù" => "U", "Û" => "U", "Ú" => "U", "Ū" => "U", "У" => "U", "Ũ" => "U", "Ư" => "U", "Ǔ" => "U", "Ų" => "U", "Ŭ" => "U", "Ů" => "U", "Ű" => "U", "Ǖ" => "U", "Ǜ" => "U", "Ǚ" => "U", "Ǘ" => "U",
|
||||
"В" => "V", "ו" => "V",
|
||||
"Ý" => "Y", "Ы" => "Y", "Ŷ" => "Y", "Ÿ" => "Y",
|
||||
"Ź" => "Z", "Ž" => "Z", "Ż" => "Z", "З" => "Z", "ז" => "Z",
|
||||
"а" => "a", "ă" => "a", "ǎ" => "a", "ą" => "a", "à" => "a", "ã" => "a", "á" => "a", "æ" => "a", "â" => "a", "å" => "a", "ǻ" => "a", "ā" => "a", "א" => "a",
|
||||
"б" => "b", "ב" => "b", "þ" => "b",
|
||||
"ĉ" => "c", "ć" => "c", "ç" => "c", "ц" => "c", "צ" => "c", "ċ" => "c", "č" => "c", "©" => "c", "ץ" => "c",
|
||||
"Ч" => "ch", "ч" => "ch",
|
||||
"д" => "d", "ď" => "d", "đ" => "d", "ד" => "d", "ð" => "d",
|
||||
"è" => "e", "ę" => "e", "é" => "e", "ë" => "e", "ê" => "e", "е" => "e", "ē" => "e", "ė" => "e", "ě" => "e", "ĕ" => "e", "є" => "e", "ə" => "e", "ע" => "e",
|
||||
"ф" => "f", "ƒ" => "f",
|
||||
"ğ" => "g", "ġ" => "g", "ģ" => "g", "ĝ" => "g", "г" => "g", "ג" => "g", "ґ" => "g",
|
||||
"ח" => "h", "ħ" => "h", "х" => "h", "ĥ" => "h", "ה" => "h",
|
||||
"i" => "i", "ï" => "i", "î" => "i", "í" => "i", "ì" => "i", "į" => "i", "ĭ" => "i", "ı" => "i", "и" => "i", "ĩ" => "i", "ǐ" => "i", "י" => "i", "ї" => "i", "ī" => "i", "і" => "i",
|
||||
"й" => "j", "Й" => "j", "Ĵ" => "j", "ĵ" => "j",
|
||||
"ĸ" => "k", "כ" => "k", "ķ" => "k", "к" => "k", "ך" => "k",
|
||||
"ł" => "l", "ŀ" => "l", "л" => "l", "ļ" => "l", "ĺ" => "l", "ľ" => "l", "ל" => "l",
|
||||
"מ" => "m", "м" => "m", "ם" => "m",
|
||||
"ñ" => "n", "ń" => "n", "н" => "n", "ņ" => "n", "ן" => "n", "ŋ" => "n", "נ" => "n", "ʼn" => "n", "ň" => "n",
|
||||
"ø" => "o", "ó" => "o", "ò" => "o", "ô" => "o", "õ" => "o", "о" => "o", "ő" => "o", "ŏ" => "o", "ō" => "o", "ǿ" => "o", "ǒ" => "o", "ơ" => "o",
|
||||
"פ" => "p", "ף" => "p", "п" => "p",
|
||||
"ק" => "q",
|
||||
"ŕ" => "r", "ř" => "r", "ŗ" => "r", "ר" => "r", "р" => "r", "®" => "r",
|
||||
"ş" => "s", "ś" => "s", "ș" => "s", "š" => "s", "с" => "s", "ŝ" => "s", "ס" => "s",
|
||||
"т" => "t", "ț" => "t", "ט" => "t", "ŧ" => "t", "ת" => "t", "ť" => "t", "ţ" => "t",
|
||||
"ù" => "u", "û" => "u", "ú" => "u", "ū" => "u", "у" => "u", "ũ" => "u", "ư" => "u", "ǔ" => "u", "ų" => "u", "ŭ" => "u", "ů" => "u", "ű" => "u", "ǖ" => "u", "ǜ" => "u", "ǚ" => "u", "ǘ" => "u",
|
||||
"в" => "v", "ו" => "v",
|
||||
"ý" => "y", "ы" => "y", "ŷ" => "y", "ÿ" => "y",
|
||||
"ź" => "z", "ž" => "z", "ż" => "z", "з" => "z", "ז" => "z", "ſ" => "z",
|
||||
"™" => "tm",
|
||||
"@" => "at",
|
||||
"Ä" => "ae", "Ǽ" => "ae", "ä" => "ae", "æ" => "ae", "ǽ" => "ae",
|
||||
"ij" => "ij", "IJ" => "ij",
|
||||
"я" => "ja", "Я" => "ja",
|
||||
"Э" => "je", "э" => "je",
|
||||
"ё" => "jo", "Ё" => "jo",
|
||||
"ю" => "ju", "Ю" => "ju",
|
||||
"œ" => "oe", "Œ" => "oe", "ö" => "oe", "Ö" => "oe",
|
||||
"щ" => "sch", "Щ" => "sch",
|
||||
"ш" => "sh", "Ш" => "sh",
|
||||
"ß" => "ss",
|
||||
"Ü" => "ue",
|
||||
"Ж" => "zh", "ж" => "zh",
|
||||
);
|
||||
return self::mbStrtr($subject, $char_map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multibyte strtr
|
||||
*
|
||||
* @param string $str
|
||||
* @param string|array $from
|
||||
* @param string|null $to
|
||||
* @return string
|
||||
*/
|
||||
public static function mbStrtr($str, $from, $to = null): string
|
||||
{
|
||||
if (is_array($from)) {
|
||||
$from = array_map('utf8_decode', $from);
|
||||
$from = array_map('utf8_decode', array_flip($from));
|
||||
return utf8_encode(strtr(utf8_decode($str), array_flip($from)));
|
||||
}
|
||||
return utf8_encode(strtr(utf8_decode($str), utf8_decode($from), utf8_decode($to)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually send an exception to team instead of going trough the error handler
|
||||
* (requires package kwaadpepper/exception-handler)
|
||||
*
|
||||
* @param Throwable $e
|
||||
* @return void
|
||||
*/
|
||||
public static function sendEmailErrorManually(Throwable $e)
|
||||
{
|
||||
Mail::send([
|
||||
'html' => 'exception-handler::emails.exception',
|
||||
'text' => 'exception-handler::emails.exception-plain'
|
||||
], [
|
||||
'appName' => config('app.name'),
|
||||
'code' => $e->getCode(),
|
||||
'line' => $e->getLine(),
|
||||
'ename' => \get_class($e),
|
||||
'emessage' => self::getAnonymizedMessage($e->getMessage()),
|
||||
'file' => str_replace(\realpath(\sprintf('%s/../', \app_path())), '', $e->getFile()),
|
||||
'stackTrace' => self::getAnonymizedStackTrace($e),
|
||||
'uri' => app()->runningInConsole() ?
|
||||
'CONSOLE' : (
|
||||
(request() and request()->fullUrl()) ?
|
||||
request()->fullUrl() : 'none')
|
||||
], function ($m) {
|
||||
/** @var \Illuminate\Mail\Message $m */
|
||||
$m->to(
|
||||
config('mail.contactsList.serviceweb'),
|
||||
sprintf('%s Exception Handler', config('app.name'))
|
||||
)->subject('Need help !, an error occured in your application, please check Logs ASAP');
|
||||
});
|
||||
Log::alert(sprintf('Envoie d\'une erreur par mail : %s', json_encode($e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymize at least Sql queries
|
||||
*
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
private static function getAnonymizedMessage(string $message): string
|
||||
{
|
||||
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymize stack trace
|
||||
*
|
||||
* @param \Exception $e
|
||||
* @return string
|
||||
*/
|
||||
private static function getAnonymizedStackTrace(Throwable $e): string
|
||||
{
|
||||
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
|
||||
$traceStack = $e->getTrace();
|
||||
$traceStr = '';
|
||||
$maxStackStage = 0;
|
||||
foreach ($traceStack as $stackStage => $stack) {
|
||||
$maxStackStage = $stackStage;
|
||||
$traceStr .= \sprintf(
|
||||
'#%s %s(%d): ',
|
||||
$stackStage,
|
||||
str_replace($projectPath, '', $stack['file']),
|
||||
$stack['line']
|
||||
);
|
||||
$traceStr .= "{$stack['class']}->{$stack['function']}(";
|
||||
$traceArgs = '';
|
||||
if (isset($stack['args'])) {
|
||||
$argsCount = count($stack['args']);
|
||||
for ($i = 0; $i < $argsCount; $i++) {
|
||||
$type = \gettype($stack['args'][$i]);
|
||||
switch ($type) {
|
||||
case 'object':
|
||||
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
|
||||
break;
|
||||
case 'array':
|
||||
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
|
||||
break;
|
||||
case 'string':
|
||||
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
|
||||
break;
|
||||
default:
|
||||
$traceArgs .= \gettype($stack['args'][$i]);
|
||||
}
|
||||
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
|
||||
}
|
||||
$traceStr .= $traceArgs;
|
||||
}//end if
|
||||
$traceStr .= ")\n";
|
||||
}//end foreach
|
||||
$maxStackStage++;
|
||||
$traceStr .= "#$maxStackStage {main}";
|
||||
return $traceStr;
|
||||
}
|
||||
}
|
||||
132
app/Lib/XmlInco/Allegations.php
Normal file
132
app/Lib/XmlInco/Allegations.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\XmlInco;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Jsonable;
|
||||
|
||||
/**
|
||||
* Des listes d'allégations
|
||||
*
|
||||
* @property-read string[] $health
|
||||
* @property-read string[] $nutrition
|
||||
* @property-read string[] $other
|
||||
*
|
||||
* @package App\Lib
|
||||
*/
|
||||
class Allegations implements \Stringable, \JsonSerializable, Jsonable, Arrayable
|
||||
{
|
||||
/**
|
||||
* Valeur minimale, ou exacte
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $health;
|
||||
|
||||
/**
|
||||
* Valeur minimale, ou exacte
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $nutrition;
|
||||
|
||||
/**
|
||||
* Valeur minimale, ou exacte
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $other;
|
||||
|
||||
/**
|
||||
* Allegation
|
||||
*
|
||||
* ! All array are filtered keeping only strings.
|
||||
*
|
||||
* @param string[] $health Health allegations.
|
||||
* @param string[] $nutrition Nutrition allegations.
|
||||
* @param string[] $other Other allegations.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $health = [], array $nutrition = [], array $other = [])
|
||||
{
|
||||
$this->health = \array_filter($health, fn ($el) => \is_string($el));
|
||||
$this->nutrition = \array_filter($nutrition, fn ($el) => \is_string($el));
|
||||
$this->other = \array_filter($other, fn ($el) => \is_string($el));
|
||||
}
|
||||
|
||||
// * Methods
|
||||
|
||||
/**
|
||||
* Getter
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
* @throws \Error If property access is unhandled.
|
||||
*/
|
||||
public function __get(string $name): array
|
||||
{
|
||||
if (\in_array($name, ['health', 'nutrition', 'other'])) {
|
||||
return $this->{$name};
|
||||
}
|
||||
$class = __CLASS__;
|
||||
throw new \Error("Undefined property: {$class}::{$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all
|
||||
*
|
||||
* @return array<string,string[]>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
// * Implements Interfaces
|
||||
|
||||
/**
|
||||
* Convert the object to its JSON representation.
|
||||
*
|
||||
* @param integer $options
|
||||
* @return string
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
||||
*/
|
||||
public function toJson($options = 0)
|
||||
{
|
||||
// phpcs:enable
|
||||
return \json_encode($this->jsonSerialize(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object into something JSON serializable.
|
||||
*
|
||||
* @return array<string,string[]>
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array<string,string[]>
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'health' => $this->health,
|
||||
'nutrition' => $this->nutrition,
|
||||
'other' => $this->other
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return \implode("\n", \array_map(fn (array $arr) => \implode(', ', $arr), $this->toArray()));
|
||||
}
|
||||
}
|
||||
281
app/Lib/XmlInco/NutritionalValues.php
Normal file
281
app/Lib/XmlInco/NutritionalValues.php
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\XmlInco;
|
||||
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Jsonable;
|
||||
|
||||
/**
|
||||
* Les valeurs nutritionelles
|
||||
*
|
||||
* @property-read array<string,float|string|null> $values
|
||||
*
|
||||
* @package App\Lib
|
||||
*/
|
||||
class NutritionalValues implements \Stringable, \JsonSerializable, Jsonable, Arrayable
|
||||
{
|
||||
/**
|
||||
* Liste des valeurs nutritionelles
|
||||
*
|
||||
* @var array<string,float|integer|null|string>
|
||||
*/
|
||||
private array $values;
|
||||
|
||||
/**
|
||||
* Nutritional values properties
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $properties = [
|
||||
'contribution_for',
|
||||
'nutritional_score',
|
||||
'ranking',
|
||||
'energy',
|
||||
'energy_kj',
|
||||
'protein',
|
||||
'carbohydrates',
|
||||
'sugar',
|
||||
'fat',
|
||||
'saturated_fatty_acids',
|
||||
'dietary_fiber',
|
||||
'salt',
|
||||
'energy_percent',
|
||||
'energy_kj_percent',
|
||||
'protein_percent',
|
||||
'carbohydrates_percent',
|
||||
'sugar_percent',
|
||||
'fat_percent',
|
||||
'saturated_fatty_acids_percent',
|
||||
'dietary_fiber_percent',
|
||||
'salt_percent'
|
||||
];
|
||||
|
||||
/**
|
||||
* Nutritional values properties units mesure
|
||||
*
|
||||
* @var array<string,string|null>
|
||||
*/
|
||||
private static $propertiesUnits = [
|
||||
'contribution_for' => null,
|
||||
'nutritional_score' => null,
|
||||
'ranking' => null,
|
||||
'energy' => 'Kcal',
|
||||
'energy_kj' => 'Kj',
|
||||
'protein' => 'g',
|
||||
'carbohydrates' => 'g',
|
||||
'sugar' => 'g',
|
||||
'fat' => 'g',
|
||||
'saturated_fatty_acids' => 'g',
|
||||
'dietary_fiber' => 'g',
|
||||
'salt' => 'g',
|
||||
'energy_percent' => '%',
|
||||
'energy_kj_percent' => '%',
|
||||
'protein_percent' => '%',
|
||||
'carbohydrates_percent' => '%',
|
||||
'sugar_percent' => '%',
|
||||
'fat_percent' => '%',
|
||||
'saturated_fatty_acids_percent' => '%',
|
||||
'dietary_fiber_percent' => '%',
|
||||
'salt_percent' => '%'
|
||||
];
|
||||
|
||||
/**
|
||||
* Translation values of inco unreadable keys
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $cleanValues = [
|
||||
'apport_port' => 'contribution_for',
|
||||
'score_nutri' => 'nutritional_score',
|
||||
'classement' => 'ranking',
|
||||
'val_energ_kcal_r' => 'energy',
|
||||
'val_energ_kcal_p' => 'energy_percent',
|
||||
'val_energ_kj_r' => 'energy_kj',
|
||||
'pourcent_apport_ref_kjo_r' => 'energy_kj_percent',
|
||||
'proteine_r' => 'protein',
|
||||
'pourcent_apport_ref_pro_r' => 'protein_percent',
|
||||
'sucre_r' => 'sugar',
|
||||
'pourcent_apport_ref_sugar_r' => 'sugar_percent',
|
||||
'mat_grass_r' => 'fat',
|
||||
'pourcent_apport_ref_fat_r' => 'fat_percent',
|
||||
'fib_alim_r' => 'dietary_fiber',
|
||||
'pourcent_apport_ref_fibtg_r' => 'dietary_fiber_percent',
|
||||
'glucides_r' => 'carbohydrates',
|
||||
'glucides_p' => 'carbohydrates_percent',
|
||||
'ags_r' => 'saturated_fatty_acids',
|
||||
'pourcent_ags' => 'saturated_fatty_acids_percent',
|
||||
'sel_r' => 'salt',
|
||||
'pourcent_apport_ref_salteq_r' => 'salt_percent'
|
||||
];
|
||||
|
||||
/**
|
||||
* Human readable translations for inco translation
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $humanReadable = [
|
||||
'Pour' => 'contribution_for',
|
||||
'Classement' => 'ranking',
|
||||
'Nutriscore' => 'nutritional_score',
|
||||
'Énergie' => 'energy',
|
||||
'Énergie kJ' => 'energy_kj',
|
||||
'Protéines' => 'protein',
|
||||
'Glucides' => 'carbohydrates',
|
||||
'Sucres' => 'sugar',
|
||||
'Matière grasse' => 'fat',
|
||||
'Acides Gras Saturés' => 'saturated_fatty_acids',
|
||||
'Fibres alimentaires' => 'dietary_fiber',
|
||||
'Sel' => 'salt'
|
||||
];
|
||||
|
||||
/**
|
||||
* Allegation
|
||||
*
|
||||
* ! All array are filtered keeping only strings.
|
||||
*
|
||||
* @param string[] $values Nutritional values.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $values = [])
|
||||
{
|
||||
$emptySkel = \array_map(fn () => null, \array_flip(self::$properties));
|
||||
$values = \array_map(fn ($value) => \is_null($value) ?
|
||||
$value : (\is_numeric($value) ? \floatval($value) : \strval($value)), $values);
|
||||
$this->values = \array_merge($emptySkel, $values);
|
||||
}
|
||||
|
||||
// * Methods
|
||||
|
||||
/**
|
||||
* Getter
|
||||
*
|
||||
* @param string $name
|
||||
* @return array|float|string|null
|
||||
* @throws \Error If property access is unhandled.
|
||||
*/
|
||||
public function __get(string $name): array|float|string|null
|
||||
{
|
||||
if (\in_array($name, self::$properties)) {
|
||||
return $this->values[$name];
|
||||
}
|
||||
if ($name === 'values') {
|
||||
return $this->values;
|
||||
}
|
||||
$class = __CLASS__;
|
||||
throw new \Error("Undefined property: {$class}::{$name}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable values
|
||||
*
|
||||
* @return array<string,float|string|null>
|
||||
*/
|
||||
public function humanReadableValues(): array
|
||||
{
|
||||
$o = [];
|
||||
foreach (self::$humanReadable as $k => $t) {
|
||||
$o[$k] = $this->values[$t];
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get units
|
||||
*
|
||||
* @param boolean $forHumanReadable
|
||||
* @return array
|
||||
*/
|
||||
public static function getUnits(bool $forHumanReadable = false): array
|
||||
{
|
||||
if ($forHumanReadable) {
|
||||
$out = [];
|
||||
$humanReadable = array_flip(self::$humanReadable);
|
||||
foreach (self::$propertiesUnits as $propertyIndex => $unit) {
|
||||
if (\array_key_exists($propertyIndex, $humanReadable)) {
|
||||
$out[$humanReadable[$propertyIndex]] = $unit;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
return self::$propertiesUnits;
|
||||
}
|
||||
|
||||
// * Static Methods
|
||||
|
||||
/**
|
||||
* Convert Xml Inco values into proper Nutritional values
|
||||
*
|
||||
* @param array $incoNutritionalValues
|
||||
* @return self
|
||||
*/
|
||||
public static function makeNutritionalValues(array $incoNutritionalValues): self
|
||||
{
|
||||
$incoNutritionalValues = \array_map(fn ($value) => \is_null($value) ?
|
||||
$value : (\is_numeric($value) ? \floatval($value) : \strval($value)), $incoNutritionalValues);
|
||||
|
||||
$o = [];
|
||||
foreach (self::$cleanValues as $k => $t) {
|
||||
$o[$t] = $incoNutritionalValues[$k] ?? null;
|
||||
}
|
||||
|
||||
return new self($o);
|
||||
}
|
||||
|
||||
// * Implements Interfaces
|
||||
|
||||
/**
|
||||
* Convert the object to its JSON representation.
|
||||
*
|
||||
* @param integer $options Json_encode php options.
|
||||
* @param boolean $fullData Serialize all properties or ignore empty one. Default is false (ignore).
|
||||
* @return string
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
||||
*/
|
||||
public function toJson($options = 0, bool $fullData = false)
|
||||
{
|
||||
// phpcs:enable
|
||||
return \json_encode($this->jsonSerialize($fullData), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object into something JSON serializable.
|
||||
*
|
||||
* @param boolean $fullData Serialize all properties or ignore empty one. Default is false (ignore).
|
||||
* @return array<string,float|string|null>
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize(bool $fullData = false)
|
||||
{
|
||||
if ($fullData) {
|
||||
return $this->toArray();
|
||||
}
|
||||
// * Save BDD space dont serialize empty values.
|
||||
return array_filter($this->toArray(), fn ($value) => !\is_null($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array<string,float|string|null>
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$values = $this->humanReadableValues();
|
||||
$units = self::getUnits(true);
|
||||
return \implode("\n", \array_map(
|
||||
fn (float|string|null $value, string $index) =>
|
||||
"{$index} : {$value}" . ($units[$index] ? ' ' . $units[$index] : ''),
|
||||
$values,
|
||||
\array_keys($values)
|
||||
));
|
||||
}
|
||||
}
|
||||
260
app/Lib/XmlInco/PortionAmount.php
Normal file
260
app/Lib/XmlInco/PortionAmount.php
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\XmlInco;
|
||||
|
||||
use App\Enums\XmlInco\PortionType;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Jsonable;
|
||||
|
||||
/**
|
||||
* Si le type de portion est exact, seul la partie basse est utilisée,
|
||||
* Si le type de portion est environ, la partie basse seule ou les deux peuvent
|
||||
* etre utilisés.
|
||||
*
|
||||
* @property-read integer $low_part
|
||||
* @property-read integer $high_part
|
||||
*
|
||||
* @package App\Lib
|
||||
*/
|
||||
class PortionAmount implements \Stringable, \JsonSerializable, Jsonable, Arrayable
|
||||
{
|
||||
/**
|
||||
* Valeur minimale, ou exacte
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private int $low_part;
|
||||
|
||||
/**
|
||||
* Valeur maximale
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private int $high_part;
|
||||
|
||||
/**
|
||||
* The portion is approximative
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private bool $approximative;
|
||||
|
||||
/**
|
||||
* The number of decimals allowed, this should not change
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private static int $allowedDecimalSize = 2;
|
||||
|
||||
/**
|
||||
* Binary masks to store and retrieve values
|
||||
*
|
||||
* @var array<string,integer>
|
||||
*/
|
||||
private static $masks = [
|
||||
'low_part' => ((1 << 15) - 1),
|
||||
'high_part' => (1 << 31) - (1 << 16),
|
||||
'approximative' => 1 << 31,
|
||||
];
|
||||
|
||||
/**
|
||||
* NbPortion
|
||||
*
|
||||
* @param float $low_part Min value is 0, max depends on low_part mask.
|
||||
* @param float $high_part Min value is 0, max depends on low_part mask.
|
||||
* @param boolean $approximative
|
||||
* @return void
|
||||
* @throws \RuntimeException If low_part part does not fit in.
|
||||
* @throws \RuntimeException If high_part part does not fit in.
|
||||
*/
|
||||
public function __construct(float $low_part, float $high_part = 0, bool $approximative = false)
|
||||
{
|
||||
$maxMask = self::$masks['low_part'];
|
||||
$maxValue = \bcdiv($maxMask, pow(10, self::$allowedDecimalSize), self::$allowedDecimalSize);
|
||||
if (($maxValue - $low_part) < 0 or $low_part < 0) {
|
||||
throw new \RuntimeException("low_part has to be a value between 0 and {$maxValue}, given is {$low_part}");
|
||||
}
|
||||
if (($maxValue - $high_part) < 0 or $high_part < 0) {
|
||||
throw new \RuntimeException("high_part has to be a value between 0 and {$maxValue}, given is {$high_part}");
|
||||
}
|
||||
$this->low_part = \bcmul($low_part, pow(10, self::$allowedDecimalSize), self::$allowedDecimalSize);
|
||||
$this->high_part = \bcmul($high_part, pow(10, self::$allowedDecimalSize), self::$allowedDecimalSize);
|
||||
$this->approximative = $approximative;
|
||||
}
|
||||
|
||||
// * Casts
|
||||
|
||||
/**
|
||||
* To database value
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function toDatabaseValue(): int
|
||||
{
|
||||
return $this->low_part |
|
||||
$this->high_part << 16 |
|
||||
($this->approximative ? self::$masks['approximative'] : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make NbPortion from database value
|
||||
*
|
||||
* @param integer $databaseValue
|
||||
* @return \App\Lib\XmlInco\PortionAmount
|
||||
*/
|
||||
public static function fromDatabase(int $databaseValue): self
|
||||
{
|
||||
$low_part = \bcdiv(
|
||||
($databaseValue & self::$masks['low_part']),
|
||||
pow(10, self::$allowedDecimalSize),
|
||||
self::$allowedDecimalSize
|
||||
);
|
||||
$high_part = \bcdiv(
|
||||
($databaseValue & self::$masks['high_part']) >> 16,
|
||||
pow(10, self::$allowedDecimalSize),
|
||||
self::$allowedDecimalSize
|
||||
);
|
||||
$approximative = $databaseValue & self::$masks['approximative'];
|
||||
return new self(
|
||||
$low_part,
|
||||
$high_part,
|
||||
$approximative,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make NbPortion from json value
|
||||
*
|
||||
* @param array<string,integer|boolean> $jsonArray
|
||||
* @return \App\Lib\XmlInco\PortionAmount
|
||||
*/
|
||||
public static function fromJson(array $jsonArray): self
|
||||
{
|
||||
$low_part = \bcdiv(
|
||||
$jsonArray['low_part'],
|
||||
pow(10, self::$allowedDecimalSize),
|
||||
self::$allowedDecimalSize
|
||||
);
|
||||
$high_part = \bcdiv(
|
||||
$jsonArray['high_part'],
|
||||
pow(10, self::$allowedDecimalSize),
|
||||
self::$allowedDecimalSize
|
||||
);
|
||||
$approximative = $jsonArray['approximative'];
|
||||
return new self(
|
||||
$low_part,
|
||||
$high_part,
|
||||
$approximative,
|
||||
);
|
||||
}
|
||||
|
||||
// * STATIC Methods
|
||||
|
||||
/**
|
||||
* Get mask for database query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getMasks(): array
|
||||
{
|
||||
return self::$masks;
|
||||
}
|
||||
|
||||
// * Methods
|
||||
|
||||
/**
|
||||
* Get low part
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getLowValue(): float
|
||||
{
|
||||
return \bcdiv(
|
||||
$this->low_part,
|
||||
pow(10, self::$allowedDecimalSize),
|
||||
self::$allowedDecimalSize
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get high part
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getHighValue(): float
|
||||
{
|
||||
return \bcdiv(
|
||||
$this->high_part,
|
||||
pow(10, self::$allowedDecimalSize),
|
||||
self::$allowedDecimalSize
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Portion type
|
||||
*
|
||||
* @return \App\Enums\XmlInco\PortionType
|
||||
*/
|
||||
public function getType(): PortionType
|
||||
{
|
||||
return match ($this->approximative) {
|
||||
true => PortionType::approximative,
|
||||
false => PortionType::exact
|
||||
};
|
||||
}
|
||||
|
||||
// * Implements Interfaces
|
||||
|
||||
/**
|
||||
* Convert the object to its JSON representation.
|
||||
*
|
||||
* @param integer $options
|
||||
* @return string
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
||||
*/
|
||||
public function toJson($options = 0)
|
||||
{
|
||||
// phpcs:enable
|
||||
return \json_encode($this->jsonSerialize(), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the object into something JSON serializable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance as an array.
|
||||
*
|
||||
* @return array<TKey,TValue>
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'low_part' => $this->low_part,
|
||||
'high_part' => $this->high_part,
|
||||
'approximative' => $this->approximative
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->high_part) {
|
||||
return \trans('entre :low_part et :high_part', [
|
||||
'low_part' => $this->getLowValue(),
|
||||
'high_part' => $this->getHighValue()
|
||||
]);
|
||||
}
|
||||
return $this->approximative ?
|
||||
trans('environ :amount:', ['amount' => $this->getLowValue()]) : $this->getLowValue();
|
||||
}
|
||||
}
|
||||
89
app/Mail/ServiceCredentialRenewed.php
Normal file
89
app/Mail/ServiceCredentialRenewed.php
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServiceCredentialRenewed extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The concerned service.
|
||||
*
|
||||
* @var \App\Models\Service
|
||||
*/
|
||||
public $service;
|
||||
|
||||
/**
|
||||
* The user who did the action.
|
||||
*
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param \App\Models\Service $service
|
||||
* @param \App\Models\User $user
|
||||
*/
|
||||
public function __construct(Service $service, User $user)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->user = $user;
|
||||
$this->service->load('arcadiaBrand');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '!!! Warning !!!, New service credential have been generated !',
|
||||
to: [new Address(
|
||||
config('mail.contactsList.serviceweb', 'equipe.web@ldc.fr'),
|
||||
sprintf('%s service warning', config('app.name'))
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.service-renewed',
|
||||
with: [
|
||||
'appName' => config('app.name'),
|
||||
'service' => $this->service,
|
||||
'brand' => $this->service->arcadiaBrand,
|
||||
'by' => sprintf('%s %s (%s)', $this->user->first_name, $this->user->last_name, $this->user->email),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
133
app/Models/ActivityLog.php
Normal file
133
app/Models/ActivityLog.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* @property string $model Target model.
|
||||
* @property string $model_id Id of the target model.
|
||||
* @property string $event Event of this activity (ActivityLogsEventEnum).
|
||||
* @property string $data Changes.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
*
|
||||
* @method public static function addActivity($model, $eventEnum) Add new activity to the activity logs list.
|
||||
* @method public static function getValuesChanged($activity, $model, $eventEnum)
|
||||
* Get old, new and type of values changed.
|
||||
*
|
||||
* @property-read \App\Models\User $user User BelongsTo relation.
|
||||
*/
|
||||
class ActivityLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'is_anonymous',
|
||||
'model_class',
|
||||
'model_id',
|
||||
'event',
|
||||
'data',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_anonymous' => 'bool',
|
||||
'event' => ActivityLogsEventEnum::class,
|
||||
'data' => 'json',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Add new activity to the activity logs list.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
|
||||
* @return void
|
||||
*/
|
||||
public static function addActivity(Model $model, ActivityLogsEventEnum $eventEnum): void
|
||||
{
|
||||
$isAnonymous = true;
|
||||
/** @var \Illuminate\Database\Eloquent\Model|null */
|
||||
$userModel = auth('backend')->user();
|
||||
if (is_object($userModel)) {
|
||||
$userModel = $userModel->getKeyForSelectQuery();
|
||||
$isAnonymous = false;
|
||||
}
|
||||
$activity = new self();
|
||||
$activity->user_id = $userModel;
|
||||
$activity->is_console = app()->runningInConsole();
|
||||
$activity->is_anonymous = $isAnonymous;
|
||||
$activity->model_class = \get_class($model);
|
||||
$activity->model_id = $model->getKey();
|
||||
$activity->event = $eventEnum;
|
||||
$activity->data = static::getChangedColumns($activity, $model, $eventEnum);
|
||||
$activity->created_at = Carbon::now();
|
||||
$activity->saveOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get old, new columns that has changed.
|
||||
*
|
||||
* @param \self $activity
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array|null
|
||||
{
|
||||
$modelClassName = $activity->model_class;
|
||||
/** Get type of each fields of the target model */
|
||||
$targetModel = $activity->model_class::where(
|
||||
(new $modelClassName())->getRouteKeyName(),
|
||||
$activity->model_id
|
||||
)->first();
|
||||
if ($targetModel != null) {
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
$targetModelTypes = collect($targetModel->toArray())
|
||||
->map(function ($field, $fieldIndex) use ($targetModel) {
|
||||
return Schema::getColumnType($targetModel->getTable(), $fieldIndex);
|
||||
})->toArray();
|
||||
// phpcs:enable
|
||||
}
|
||||
/** Return old, new and type of values changed */
|
||||
return ($eventEnum === ActivityLogsEventEnum::updated) ?
|
||||
array_intersect_key($targetModelTypes, $model->getChanges()) : null;
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
* User BelongsTo relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
118
app/Models/ArcadiaBrand.php
Normal file
118
app/Models/ArcadiaBrand.php
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Rennokki\QueryCache\Traits\QueryCacheable;
|
||||
|
||||
/**
|
||||
* @property-read integer $id The arcadia brand id.
|
||||
* @property-read string $label The arcadia brand name.
|
||||
*
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Service> $services
|
||||
* The Services for the ArcadiaBrand.
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\XmlInco> $xmlIncos
|
||||
* The XmlIncos for the ArcadiaBrand.
|
||||
* @package App\Models
|
||||
*/
|
||||
class ArcadiaBrand extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use QueryCacheable;
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
/**
|
||||
* The timeout cache for this model
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $cacheFor = 0;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'label'
|
||||
];
|
||||
|
||||
/**
|
||||
* ArcadiaBrand
|
||||
*
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
$this->cacheFor = config('api.arcadiaBrandsDBCache') * 60;
|
||||
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the base cache tags that will be present
|
||||
* on all queries.
|
||||
*
|
||||
* ! This is used to flush caching
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCacheBaseTags(): array
|
||||
{
|
||||
return [
|
||||
'incollector_arcadia_brands',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function () {
|
||||
// * Flush Eloquent cache.
|
||||
self::flushQueryCache();
|
||||
});
|
||||
static::updated(function () {
|
||||
// * Flush Eloquent cache.
|
||||
self::flushQueryCache();
|
||||
});
|
||||
static::deleted(function () {
|
||||
// * Flush Eloquent cache.
|
||||
self::flushQueryCache();
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
* Get all of the Services for the ArcadiaBrand
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function services(): HasMany
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the xmlIncos for the ArcadiaBrand
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function xmlIncos(): HasMany
|
||||
{
|
||||
return $this->hasMany(XmlInco::class);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue