Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
255fbdade6 | ||
|
|
93897518db | ||
|
|
95a3dfd1f2 | ||
|
|
dfac5d53b0 | ||
|
|
9d5456d897 | ||
|
|
78a953f9ac | ||
|
|
ca8076a953 | ||
|
|
b204334e9e | ||
|
|
8e57beaf11 |
192 changed files with 7504 additions and 10286 deletions
|
|
@ -1,16 +1,2 @@
|
|||
.git
|
||||
*.md
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
.npmrc
|
||||
tests/
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
LICENSE
|
||||
.vscode
|
||||
vendor/
|
||||
public/build/
|
||||
auth.json
|
||||
|
|
|
|||
37
.env.example
37
.env.example
|
|
@ -3,6 +3,7 @@ APP_ENV=local
|
|||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
ASSET_URL=http://localhost
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
|
|
@ -36,22 +37,26 @@ MAIL_PASSWORD=null
|
|||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
MAIL_TO_ADDRESS=
|
||||
MAIL_TO_NAME=
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
# Sentry
|
||||
SENTRY_LARAVEL_DSN=
|
||||
|
||||
# UWIL ADMIN SECURE
|
||||
# - LIST SEPARATED WITH ',' (Required)
|
||||
UWIL_ADMIN_SECURE_CIDR_LIST=""
|
||||
# - LIST SEPARATED WITH ',' (Optional)
|
||||
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
|
||||
|
||||
|
||||
# SAML
|
||||
SAML2_DEFAULT_TENANT_UUID=
|
||||
SAML2_SP_CERT_x509=
|
||||
SAML2_SP_CERT_PRIVATEKEY=
|
||||
SAML2_CONTACT_TECHNICAL_EMAIL=
|
||||
SAML2_CONTACT_SUPPORT_EMAIL=
|
||||
|
|
|
|||
|
|
@ -1,23 +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/docker-multistage.yml"
|
||||
- project: "ldc-web/shared-ci"
|
||||
ref: "main"
|
||||
file: "test/test.yml"
|
||||
- project: "ldc-web/shared-ci"
|
||||
ref: "main"
|
||||
file: "deploy/deploy.yml"
|
||||
- project: 'ldc-web/shared-ci'
|
||||
ref: 'main'
|
||||
file: '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
|
||||
- lint
|
||||
- security
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
|
|
|
|||
50
Dockerfile
50
Dockerfile
|
|
@ -1,49 +1,5 @@
|
|||
ARG PHP_8_5_TAG=latest
|
||||
|
||||
FROM composer:2.8 AS vendor
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG GITLAB_USER
|
||||
ARG GITLAB_PASSWORD
|
||||
|
||||
COPY database/ database/
|
||||
|
||||
COPY composer.json composer.json
|
||||
COPY composer.lock composer.lock
|
||||
|
||||
RUN composer config gitlab-token.lab.s-web.fr "${GITLAB_USER}" "${GITLAB_PASSWORD}"
|
||||
RUN composer install \
|
||||
--ignore-platform-reqs \
|
||||
--no-interaction \
|
||||
--no-plugins \
|
||||
--no-scripts \
|
||||
--no-dev \
|
||||
--prefer-dist
|
||||
|
||||
FROM node:20.18 AS frontend
|
||||
|
||||
ARG GITLAB_PASSWORD
|
||||
|
||||
RUN mkdir -p /app/public/build
|
||||
|
||||
COPY package*.json tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
|
||||
COPY resources/ /app/resources/
|
||||
WORKDIR /app
|
||||
RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/
|
||||
RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
|
||||
RUN npm ci && npm run prod
|
||||
|
||||
ARG PHP_8_5_TAG
|
||||
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/uwil-production-8.5:${PHP_8_5_TAG}
|
||||
|
||||
ARG USER=www-data
|
||||
|
||||
COPY --chown=${USER}:${USER} . /app/srcs
|
||||
COPY --chown=${USER}:${USER} ./resources/favicon /app/srcs/public
|
||||
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
|
||||
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
|
||||
|
||||
FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest
|
||||
COPY --chown=www-data:www-data . /app/srcs
|
||||
USER root
|
||||
RUN chmod -R 755 /app/srcs/storage
|
||||
USER ${USER}
|
||||
USER www-data
|
||||
|
|
|
|||
67
README.md
67
README.md
|
|
@ -1,5 +1,66 @@
|
|||
# Showroom BO
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
## Usage
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
|
||||
Dans la stack, avec l'env `local` il faut mettre l'url du service Traefik (ex: `http://172.18.0.2:443`) dans la variable APP_URL
|
||||
## About Laravel
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
|
||||
## Learning Laravel
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
|
||||
|
||||
You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains over 2000 video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
|
||||
## Laravel Sponsors
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel [Patreon page](https://patreon.com/taylorotwell).
|
||||
|
||||
### Premium Partners
|
||||
|
||||
- **[Vehikl](https://vehikl.com/)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Cubet Techno Labs](https://cubettech.com)**
|
||||
- **[Cyber-Duck](https://cyber-duck.co.uk)**
|
||||
- **[Many](https://www.many.co.uk)**
|
||||
- **[Webdock, Fast VPS Hosting](https://www.webdock.io/en)**
|
||||
- **[DevSquad](https://devsquad.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
|
||||
- **[OP.GG](https://op.gg)**
|
||||
- **[WebReinvent](https://webreinvent.com/?utm_source=laravel&utm_medium=github&utm_campaign=patreon-sponsors)**
|
||||
- **[Lendio](https://lendio.com)**
|
||||
|
||||
## Contributing
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
|
|
|
|||
13
RGPD.md
13
RGPD.md
|
|
@ -1,13 +0,0 @@
|
|||
# RGPD
|
||||
|
||||
## Site
|
||||
|
||||
### En base de données
|
||||
|
||||
| Table | Colonne | Contenu | Utilisation |
|
||||
| ----- | ------- | ------- | ----------- |
|
||||
| activity_logs | user_id | Identifiant de l'utilisateur (back) | Tracer les modifications dans le BO |
|
||||
| --- | --- | --- | --- |
|
||||
| users | first_name | Prénom | Affichage à différents endroits du BO |
|
||||
| users | last_name | Nom | Affichage à différents endroits du BO |
|
||||
| users | email | Adresse mail | Identifiant, sert à la connexion au BO |
|
||||
|
|
@ -3,14 +3,9 @@
|
|||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\ShowroomExport;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use RuntimeException;
|
||||
|
||||
class ExportShowRoom extends Command implements PromptsForMissingInput
|
||||
class ExportShowRoom extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
|
|
@ -26,57 +21,13 @@ class ExportShowRoom extends Command implements PromptsForMissingInput
|
|||
*/
|
||||
protected $description = 'Export showroom data : a zip file with json data and associated pictures';
|
||||
|
||||
/**
|
||||
* The tenant_id of the tenant that contains the datas to export.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $tenantId;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException If tenantId isn't a string. Should never happen but it makes intelephense happy.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->tenantId = null;
|
||||
$this->addTenantId();
|
||||
|
||||
if (!$this->tenantId) {
|
||||
throw new RuntimeException('\'tenantId\' must be a valid string.');
|
||||
}
|
||||
\dispatch_sync(new ShowroomExport($this->tenantId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a role to the user.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addTenantId(): void
|
||||
{
|
||||
while (is_null($this->tenantId)) {
|
||||
try {
|
||||
$tmp = $this->choice(
|
||||
'Select a tenant',
|
||||
Tenant::all()->pluck('id')->toArray(),
|
||||
Tenant::query()->first()->id,
|
||||
);
|
||||
$validator = Validator::make(['tenant_id' => $tmp], [
|
||||
'tenant_id' => 'required'
|
||||
]);
|
||||
$validator->validate();
|
||||
$this->tenantId = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please choose from the selection %s',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
} //end try
|
||||
}; //end while
|
||||
\dispatch_sync(new ShowroomExport());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,13 @@ class UserCreate extends Command
|
|||
*/
|
||||
protected $email;
|
||||
|
||||
/**
|
||||
* The password of the new user.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* The role of the new user.
|
||||
*
|
||||
|
|
@ -69,6 +76,9 @@ class UserCreate extends Command
|
|||
$this->email = null;
|
||||
$this->addEmail();
|
||||
|
||||
$this->password = null;
|
||||
$this->addPassword();
|
||||
|
||||
$this->role = null;
|
||||
$this->addRole();
|
||||
|
||||
|
|
@ -76,6 +86,7 @@ class UserCreate extends Command
|
|||
'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(),
|
||||
|
|
@ -162,6 +173,22 @@ class UserCreate extends Command
|
|||
}; //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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\Enums\BaseEnum;
|
||||
|
||||
enum Width: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case half = 1;
|
||||
case full = 2;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::half->name => 'enums.width.half',
|
||||
self::full->name => 'enums.width.full',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Custom percent width value
|
||||
*/
|
||||
private const PERCENTVALUE = [
|
||||
self::half->name => '50 %',
|
||||
self::full->name => '100 %'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get percent width value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function percentValue(): string
|
||||
{
|
||||
return self::PERCENTVALUE[$this->name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom dimension validation for the enum
|
||||
*/
|
||||
private const DIMVALIDATION = [
|
||||
self::half->name => [
|
||||
'width' => 600,
|
||||
'height' => 500
|
||||
],
|
||||
self::full->name => [
|
||||
'width' => 1300,
|
||||
'height' => 500
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Get percent width value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dimValidation(): array
|
||||
{
|
||||
return self::DIMVALIDATION[$this->name];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ShowroomFileHelper
|
||||
{
|
||||
/**
|
||||
* Store file using UUID as name.
|
||||
*
|
||||
* @param \Illuminate\Http\UploadedFile $file
|
||||
* @param Model $model
|
||||
* @phpstan-param Model&object{tenant_id: string|int} $model
|
||||
* @return string
|
||||
*/
|
||||
public static function storeFileWithUuidName(UploadedFile $file, Model $model): string
|
||||
{
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$uuidName = (string) Str::uuid() . ($extension ? '.' . $extension : '');
|
||||
return FileHelper::storeFile(
|
||||
model: $model,
|
||||
file: $file,
|
||||
slugify: false,
|
||||
filename: $uuidName,
|
||||
prefix: $model->tenant_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -33,10 +33,9 @@ class BrandController extends Controller
|
|||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = Brand::query()->withCount([
|
||||
'ranges',
|
||||
'products',
|
||||
'ranges as published_ranges' => fn(Builder $q) => $q->where('ranges.status', true),
|
||||
'products as published_products' => fn(Builder $q) => $q->where('products.status', true),
|
||||
'ranges', 'products',
|
||||
'ranges as published_ranges' => fn (Builder $q) => $q->where('ranges.status', true),
|
||||
'products as published_products' => fn (Builder $q) => $q->where('products.status', true),
|
||||
]);
|
||||
|
||||
/** @var string $search Search field */
|
||||
|
|
@ -85,7 +84,7 @@ class BrandController extends Controller
|
|||
$brand->fill($request->validated());
|
||||
|
||||
if ($brand->saveOrFail()) {
|
||||
return redirect()->route('bo.brands.edit', ['tenant' => \tenant(), 'brand' => $brand])
|
||||
return redirect()->route('bo.brands.edit', $brand)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.brand', 1))
|
||||
]));
|
||||
|
|
@ -106,10 +105,9 @@ class BrandController extends Controller
|
|||
public function edit(Brand $brand): View
|
||||
{
|
||||
$brand->loadCount([
|
||||
'ranges',
|
||||
'products',
|
||||
'ranges as published_ranges' => fn(Builder $q) => $q->where('ranges.status', true),
|
||||
'products as published_products' => fn(Builder $q) => $q->where('products.status', true),
|
||||
'ranges', 'products',
|
||||
'ranges as published_ranges' => fn (Builder $q) => $q->where('ranges.status', true),
|
||||
'products as published_products' => fn (Builder $q) => $q->where('products.status', true),
|
||||
]);
|
||||
return view('bo.pages.brands.edit', ['brandModel' => $brand]);
|
||||
}
|
||||
|
|
@ -127,12 +125,12 @@ class BrandController extends Controller
|
|||
$brand->fill($request->validated());
|
||||
|
||||
if ($brand->saveOrFail()) {
|
||||
return redirect()->route('bo.brands.edit', ['tenant' => \tenant(), 'brand' => $brand])
|
||||
return redirect()->route('bo.brands.edit', $brand)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.brand', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.brands.edit', ['tenant' => \tenant(), 'brand' => $brand])
|
||||
return redirect()->route('bo.brands.edit', $brand)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.brand', 1))
|
||||
]));
|
||||
|
|
@ -151,7 +149,7 @@ class BrandController extends Controller
|
|||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($brand->deleteOrFail()) {
|
||||
return redirect()->route('bo.brands.index', array_merge(['tenant' => \tenant()], $previousQueries))
|
||||
return redirect()->route('bo.brands.index', $previousQueries)
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.brand', 1))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Bo;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ShowroomExport;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class ExportController extends Controller
|
||||
{
|
||||
|
|
@ -13,17 +14,11 @@ class ExportController extends Controller
|
|||
*
|
||||
* @return \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
*/
|
||||
public function exportToZip(): \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
public function exportToZip(): StreamedResponse
|
||||
{
|
||||
$exportFolder = storage_path('app/export');
|
||||
|
||||
if (!File::exists($exportFolder)) {
|
||||
File::makeDirectory($exportFolder, 0700, true);
|
||||
}
|
||||
|
||||
$exportedFiles = File::allFiles($exportFolder);
|
||||
\dispatch_sync(new ShowroomExport(\tenant('id')));
|
||||
$exportedFile = collect(File::allFiles($exportFolder))->diff($exportedFiles)->first();
|
||||
$exportedFiles = File::allFiles(storage_path('app/export'));
|
||||
\dispatch_sync(new ShowroomExport());
|
||||
$exportedFile = collect(File::allFiles(storage_path('app/export')))->diff($exportedFiles)->first();
|
||||
if (!$exportedFile) {
|
||||
response()->redirectToRoute('bo.dashboard')->withErrors([
|
||||
'Export failed to catch the exported file'
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class FontController extends Controller
|
|||
$query,
|
||||
$search,
|
||||
null,
|
||||
'font_name',
|
||||
'name',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
|
|
@ -80,7 +80,7 @@ class FontController extends Controller
|
|||
$font->fill($request->validated());
|
||||
|
||||
if ($font->saveOrFail()) {
|
||||
return redirect()->route('bo.fonts.edit', ['tenant' => \tenant(), 'font' => $font])
|
||||
return redirect()->route('bo.fonts.edit', $font)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
|
|
@ -120,12 +120,12 @@ class FontController extends Controller
|
|||
$font->fill($request->validated());
|
||||
|
||||
if ($font->saveOrFail()) {
|
||||
return redirect()->route('bo.fonts.edit', ['tenant' => \tenant(), 'font' => $font])
|
||||
return redirect()->route('bo.fonts.edit', $font)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.fonts.edit', ['tenant' => \tenant(), 'font' => $font])
|
||||
return redirect()->route('bo.fonts.edit', $font)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
|
|
@ -141,7 +141,7 @@ class FontController extends Controller
|
|||
public function destroy(Font $font): RedirectResponse
|
||||
{
|
||||
if ($font->deleteOrFail()) {
|
||||
return redirect()->route('bo.fonts.index', ['tenant' => \tenant()])
|
||||
return redirect()->route('bo.fonts.index')
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ class ProductController extends Controller
|
|||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = Product::query()->with(['range', 'brand'])->withCount([
|
||||
'range as parent_published' => fn(Builder $q) => $q->where('ranges.status', true),
|
||||
'brand as parent_parent_published' => fn(Builder $q) => $q->where('brands.status', true),
|
||||
'range as parent_published' => fn (Builder $q) => $q->where('ranges.status', true),
|
||||
'range as parent_shelf' => fn (Builder $q) => $q->where('ranges.shelf', true),
|
||||
'brand as parent_parent_published' => fn (Builder $q) => $q->where('brands.status', true),
|
||||
]);
|
||||
|
||||
/** @var string $search Search field */
|
||||
|
|
@ -56,7 +57,7 @@ class ProductController extends Controller
|
|||
$this->sortQuery($query);
|
||||
|
||||
if ($request->input('range_id')) {
|
||||
$query->whereHas('range', fn(Builder $q) => $q->where('id', $request->input('range_id')));
|
||||
$query->whereHas('range', fn (Builder $q) => $q->where('id', $request->input('range_id')));
|
||||
}
|
||||
|
||||
/** Custom pagination */
|
||||
|
|
@ -95,7 +96,7 @@ class ProductController extends Controller
|
|||
$product->fill($request->validated());
|
||||
|
||||
if ($product->saveOrFail()) {
|
||||
return redirect()->route('bo.products.edit', ['tenant' => tenant(), 'product' => $product])
|
||||
return redirect()->route('bo.products.edit', $product)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.product', 1))
|
||||
]));
|
||||
|
|
@ -116,12 +117,11 @@ class ProductController extends Controller
|
|||
public function edit(Product $product): View
|
||||
{
|
||||
$product->load([
|
||||
'range',
|
||||
'brand'
|
||||
'range', 'brand'
|
||||
]);
|
||||
$product->loadCount([
|
||||
'range as parent_published' => fn(Builder $q) => $q->where('ranges.status', true),
|
||||
'brand as parent_parent_published' => fn(Builder $q) => $q->where('brands.status', true),
|
||||
'range as parent_published' => fn (Builder $q) => $q->where('ranges.status', true),
|
||||
'brand as parent_parent_published' => fn (Builder $q) => $q->where('brands.status', true),
|
||||
]);
|
||||
$rangeModelList = Range::all();
|
||||
return view('bo.pages.products.edit', [
|
||||
|
|
@ -143,12 +143,12 @@ class ProductController extends Controller
|
|||
$product->fill($request->validated());
|
||||
|
||||
if ($product->saveOrFail()) {
|
||||
return redirect()->route('bo.products.edit', ['tenant' => tenant(), 'product' => $product])
|
||||
return redirect()->route('bo.products.edit', $product)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.product', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.products.edit', ['tenant' => tenant(), 'product' => $product])
|
||||
return redirect()->route('bo.products.edit', $product)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.product', 1))
|
||||
]));
|
||||
|
|
@ -167,7 +167,7 @@ class ProductController extends Controller
|
|||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
return redirect()->route('bo.products.index', array_merge(['tenant' => \tenant()], $previousQueries))
|
||||
return redirect()->route('bo.products.index', $previousQueries)
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.product', 1))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Enums\Width;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Ranges\StoreRangeRequest;
|
||||
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
|
||||
|
|
@ -40,8 +39,9 @@ class RangeController extends Controller
|
|||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = Range::query()->with(['brand', 'font'])->withCount([
|
||||
'products',
|
||||
'products as published_products' => fn(Builder $q) => $q->where('products.status', true),
|
||||
'brand as parent_published' => fn(Builder $q) => $q->where('status', true),
|
||||
'products as published_products' => fn (Builder $q) => $q->where('products.status', true),
|
||||
'brand as parent_shelf' => fn (Builder $q) => $q->where('shelf', true),
|
||||
'brand as parent_published' => fn (Builder $q) => $q->where('status', true),
|
||||
]);
|
||||
|
||||
/** @var string $search Search field */
|
||||
|
|
@ -62,7 +62,7 @@ class RangeController extends Controller
|
|||
$this->sortQuery($query);
|
||||
|
||||
if ($request->input('brand_id')) {
|
||||
$query->whereHas('brand', fn(Builder $q) => $q->where('id', $request->input('brand_id')));
|
||||
$query->whereHas('brand', fn (Builder $q) => $q->where('id', $request->input('brand_id')));
|
||||
}
|
||||
|
||||
/** Custom pagination */
|
||||
|
|
@ -88,8 +88,7 @@ class RangeController extends Controller
|
|||
$fontModelList = Font::all();
|
||||
return view('bo.pages.ranges.create', [
|
||||
'brandModelList' => $brandModelList,
|
||||
'fontModelList' => $fontModelList,
|
||||
'widths' => Width::toArray()
|
||||
'fontModelList' => $fontModelList
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +105,7 @@ class RangeController extends Controller
|
|||
$range->fill($request->validated());
|
||||
|
||||
if ($range->saveOrFail()) {
|
||||
return redirect()->route('bo.ranges.edit', ['tenant' => tenant(), 'range' => $range])
|
||||
return redirect()->route('bo.ranges.edit', $range)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.range', 1))
|
||||
]));
|
||||
|
|
@ -128,16 +127,15 @@ class RangeController extends Controller
|
|||
{
|
||||
$range->loadCount([
|
||||
'products',
|
||||
'products as published_products' => fn(Builder $q) => $q->where('products.status', true),
|
||||
'brand as parent_published' => fn(Builder $q) => $q->where('status', true),
|
||||
'products as published_products' => fn (Builder $q) => $q->where('products.status', true),
|
||||
'brand as parent_published' => fn (Builder $q) => $q->where('status', true),
|
||||
]);
|
||||
$brandModelList = Brand::all();
|
||||
$fontModelList = Font::all();
|
||||
return view('bo.pages.ranges.edit', [
|
||||
'brandModelList' => $brandModelList,
|
||||
'fontModelList' => $fontModelList,
|
||||
'rangeModel' => $range,
|
||||
'widths' => Width::toArray()
|
||||
'rangeModel' => $range
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -154,12 +152,12 @@ class RangeController extends Controller
|
|||
$range->fill($request->validated());
|
||||
|
||||
if ($range->saveOrFail()) {
|
||||
return redirect()->route('bo.ranges.edit', ['tenant' => tenant(), 'range' => $range])
|
||||
return redirect()->route('bo.ranges.edit', $range)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.range', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.ranges.edit', ['tenant' => tenant(), 'range' => $range])
|
||||
return redirect()->route('bo.ranges.edit', $range)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.range', 1))
|
||||
]));
|
||||
|
|
@ -178,7 +176,7 @@ class RangeController extends Controller
|
|||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($range->deleteOrFail()) {
|
||||
return redirect()->route('bo.ranges.index', array_merge(['tenant' => \tenant()], $previousQueries))
|
||||
return redirect()->route('bo.ranges.index', $previousQueries)
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.range', 1))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ class SettingsController extends Controller
|
|||
$settings->fill($request->validated());
|
||||
|
||||
if ($settings->saveOrFail()) {
|
||||
return redirect()->route('bo.settings.edit', ['tenant' => \tenant(), 'settings' => $settings])
|
||||
return redirect()->route('bo.settings.edit', $settings)
|
||||
->with('success', __('crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(\trans('models.classes.settings'))
|
||||
]));
|
||||
}
|
||||
return redirect()->route('bo.settings.edit', ['tenant' => \tenant(), 'settings' => $settings])
|
||||
return redirect()->route('bo.settings.edit', $settings)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(\trans('models.classes.settings'))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
|
||||
use App\Models\Font;
|
||||
use App\Models\Settings;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class TenantController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $query */
|
||||
$query = Tenant::query();
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$search,
|
||||
null,
|
||||
'name',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
trans('validation.attributes.name'),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
/** Custom pagination */
|
||||
$tenantModelList = $this->paginate($query);
|
||||
|
||||
return view('bo.pages.tenants.index', compact('tenantModelList', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
return view('bo.pages.tenants.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreTenantRequest $request): RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$tenant = new Tenant();
|
||||
$tenant->fill($request->validated());
|
||||
|
||||
if ($tenant->saveOrFail()) {
|
||||
$this->createTenantRelatedMandatoryModels($tenant);
|
||||
return redirect()->route('bo.tenants.index')
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Tenant $tenant
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Tenant $tenant): RedirectResponse
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($tenant->deleteOrFail()) {
|
||||
Storage::disk('public')->deleteDirectory("modelfiles/{$tenant->id}");
|
||||
return redirect()->route('bo.tenants.index', $previousQueries)
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.tenant', 1))
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create mandatory models related to a new tenant.
|
||||
*
|
||||
* @param \App\Models\Tenant $tenant
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createTenantRelatedMandatoryModels(Tenant $tenant): void
|
||||
{
|
||||
Settings::createNewData($tenant->id);
|
||||
|
||||
$fontSerif = new Font(['font_name' => 'serif', 'tenant_id' => $tenant->id]);
|
||||
$fontSansSerif = new Font(['font_name' => 'sans-serif', 'tenant_id' => $tenant->id]);
|
||||
$fontSerif->saveOrFail();
|
||||
$fontSansSerif->saveOrFail();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Users\StoreUserRequest;
|
||||
use App\Http\Requests\Bo\Users\UpdateUserRequest;
|
||||
|
|
@ -33,7 +32,7 @@ class UserController extends Controller
|
|||
public function index(Request $request): \Illuminate\Contracts\View\View
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $userModels */
|
||||
$userModels = User::query()->with('tenants');
|
||||
$userModels = User::query();
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->search;
|
||||
|
|
@ -85,12 +84,10 @@ class UserController extends Controller
|
|||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$user = new User();
|
||||
$user->fill($request->safe()->except(['tenants']));
|
||||
$user->fill($request->validated());
|
||||
|
||||
if ($user->saveOrFail()) {
|
||||
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
|
||||
|
||||
return redirect()->route('bo.users.edit', ['user' => $user])
|
||||
return redirect()->route('bo.users.edit', $user)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
|
|
@ -123,19 +120,15 @@ class UserController extends Controller
|
|||
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $user) {
|
||||
$user->fill($request->safe()->except('tenants'));
|
||||
|
||||
if (RoleEnum::conceptor->equals(\auth('backend')->user()->role)) {
|
||||
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
|
||||
}
|
||||
$user->fill($request->validated());
|
||||
|
||||
if ($user->saveOrFail()) {
|
||||
return redirect()->route('bo.users.edit', ['user' => $user])
|
||||
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' => $user])
|
||||
return redirect()->route('bo.users.edit', $user)
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(__('models.classes.user'))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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\Validation\ValidatesRequests;
|
||||
|
|
@ -13,7 +14,6 @@ use Illuminate\Support\Facades\Cache;
|
|||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
|
|
@ -26,7 +26,7 @@ class Controller extends BaseController
|
|||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
|
||||
* @param string $search The query string.
|
||||
* @param callable|null $searchQuery 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.
|
||||
|
|
@ -35,7 +35,7 @@ class Controller extends BaseController
|
|||
protected function searchQuery(
|
||||
Builder $query,
|
||||
string $search,
|
||||
callable|null $searchQuery = null,
|
||||
callable $searchQuery = null,
|
||||
string ...$fields
|
||||
): \Illuminate\Database\Eloquent\Builder {
|
||||
if (count($fields)) {
|
||||
|
|
@ -200,7 +200,7 @@ class Controller extends BaseController
|
|||
*/
|
||||
protected function paginate(
|
||||
Builder $query,
|
||||
ItemsPerPaginationEnum|null $pagination = null
|
||||
ItemsPerPaginationEnum $pagination = null
|
||||
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
|
||||
$currentRoutePath = Str::slug(request()->route()->getName());
|
||||
$cacheKey = "pagination.{$currentRoutePath}";
|
||||
|
|
|
|||
|
|
@ -38,13 +38,7 @@ class Kernel extends HttpKernel
|
|||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\Lang::class,
|
||||
\App\Http\Middleware\SecureHeaders::class,
|
||||
],
|
||||
|
||||
'saml' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\App\Http\Middleware\SecureHeaders::class
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
|
@ -61,17 +55,16 @@ class Kernel extends HttpKernel
|
|||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'restrict_tenant_access' => \App\Http\Middleware\RestrictTenantAccess::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
class HtmlSanitizer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class Lang
|
|||
{
|
||||
// phpcs:enable
|
||||
$prefix = Route::current()->action['prefix'];
|
||||
if (\in_array($prefix, ['/{tenant}/bo', '/bo'])) {
|
||||
if ($prefix === '/bo') {
|
||||
app()->setLocale(session()->get('lang', config('app.locale')));
|
||||
return $next($request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Tenant;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RestrictTenantAccess
|
||||
{
|
||||
/** @var list<string> $routesWithoutTenant List of routes without tenant in the url. */
|
||||
private $routesWithoutTenant = [
|
||||
'bo.dashboard',
|
||||
'bo.users.*',
|
||||
'bo.tenants.*'
|
||||
];
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
$user = \auth('backend')->user();
|
||||
|
||||
if ($user && Tenant::count()) {
|
||||
$this->throwNotFoundIfTenantDoesntExist($request);
|
||||
if ($request->routeIs($this->routesWithoutTenant)) {
|
||||
$tenant = ($user->tenants->count()) ? $user->tenants->first() : Tenant::first()->getKey();
|
||||
tenancy()->initialize($request->tenant ?? $tenant);
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
if (
|
||||
!\in_array($request->tenant, $user->tenants->pluck('id')->toArray()) &&
|
||||
!(RoleEnum::conceptor->equals($user->role))
|
||||
) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if (RoleEnum::conceptor->equals($user->role)) {
|
||||
tenancy()->initialize($request->tenant ?? Tenant::query()->first()->id);
|
||||
return $next($request);
|
||||
}
|
||||
} //end if
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw NotFoundException if the tenant doesn't exist,
|
||||
* instead of Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedByPathException.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function throwNotFoundIfTenantDoesntExist(Request $request)
|
||||
{
|
||||
if (
|
||||
!$request->routeIs($this->routesWithoutTenant) &&
|
||||
$request->tenant &&
|
||||
!Tenant::query()->find($request->tenant)
|
||||
) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ use App\Models\Brand;
|
|||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Rules\Hex;
|
||||
|
||||
class StoreBrandRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -31,6 +30,7 @@ class StoreBrandRequest extends FormRequest
|
|||
{
|
||||
$this->mergePicture('picture');
|
||||
$this->merge([
|
||||
'shelf' => $this->boolean('shelf'),
|
||||
'status' => $this->boolean('status')
|
||||
]);
|
||||
}
|
||||
|
|
@ -44,7 +44,8 @@ class StoreBrandRequest extends FormRequest
|
|||
{
|
||||
$rules = [
|
||||
'name' => 'required|min:2|max:60',
|
||||
'color' => ['required', new Hex()],
|
||||
'color' => 'required|color_hex',
|
||||
'shelf' => 'present|boolean',
|
||||
'status' => 'present|boolean'
|
||||
];
|
||||
|
||||
|
|
@ -64,6 +65,7 @@ class StoreBrandRequest extends FormRequest
|
|||
return [
|
||||
'color' => trans('validation.custom.color'),
|
||||
'status' => \trans('validation.custom.status'),
|
||||
'shelf' => \trans('validation.custom.shelf'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ class StoreProductRequest extends FormRequest
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'description' => (string) Str::of($this->input('description', '')),
|
||||
'status' => $this->boolean('status')
|
||||
'description' => (string)Str::of($this->input('description', '')),
|
||||
'status' => $this->boolean('status')
|
||||
]);
|
||||
$this->mergePicture('picture');
|
||||
}
|
||||
|
|
@ -44,10 +44,10 @@ class StoreProductRequest extends FormRequest
|
|||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'name' => 'required|min:2|max:130',
|
||||
'name' => 'required|min:2|max:130',
|
||||
'description' => 'present',
|
||||
'status' => 'present|boolean',
|
||||
'range_id' => 'required|exists:ranges,id'
|
||||
'status' => 'present|boolean',
|
||||
'range_id' => 'required|exists:ranges,id'
|
||||
];
|
||||
|
||||
return \array_merge(
|
||||
|
|
@ -64,10 +64,8 @@ class StoreProductRequest extends FormRequest
|
|||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => \trans('validation.attributes.name'),
|
||||
'description' => \trans('validation.attributes.description'),
|
||||
'status' => \trans('validation.custom.status'),
|
||||
'range_id' => \trans_choice('models.classes.range', 1),
|
||||
'range_id' => \trans_choice('models.classes.range', 1),
|
||||
'status' => \trans('validation.custom.status'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
namespace App\Http\Requests\Bo\Ranges;
|
||||
|
||||
use App\Enums\Width;
|
||||
use App\Models\Range;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Rules\Hex;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreRangeRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -33,6 +30,7 @@ class StoreRangeRequest extends FormRequest
|
|||
{
|
||||
$this->mergePicture('picture');
|
||||
$this->merge([
|
||||
'shelf' => $this->boolean('shelf'),
|
||||
'status' => $this->boolean('status')
|
||||
]);
|
||||
}
|
||||
|
|
@ -44,27 +42,19 @@ class StoreRangeRequest extends FormRequest
|
|||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
||||
$rules = [
|
||||
'name' => 'required|min:2|max:90',
|
||||
'brand_id' => 'required|exists:brands,id',
|
||||
'font_id' => 'required|exists:fonts,id',
|
||||
'color' => ['required', new Hex()],
|
||||
'width' => ['required', Rule::enum(Width::class)],
|
||||
'color' => 'required|color_hex',
|
||||
'width' => 'required|numeric|min:1|max:2',
|
||||
'shelf' => 'present|boolean',
|
||||
'status' => 'present|boolean',
|
||||
];
|
||||
|
||||
$widthEnum = Width::make($this->input('width'));
|
||||
return \array_merge(
|
||||
$rules,
|
||||
$this->pictureRules(
|
||||
'picture',
|
||||
$widthEnum->dimValidation()['width'],
|
||||
$widthEnum->dimValidation()['height'],
|
||||
$widthEnum->dimValidation()['width'],
|
||||
$widthEnum->dimValidation()['height'],
|
||||
false
|
||||
)
|
||||
$this->pictureRules('picture', 600, 500, 1300, 500, false)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +71,7 @@ class StoreRangeRequest extends FormRequest
|
|||
'color' => \trans('validation.custom.color'),
|
||||
'width' => \trans('validation.custom.width'),
|
||||
'status' => \trans('validation.custom.status'),
|
||||
'shelf' => \trans('validation.custom.shelf'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ namespace App\Http\Requests\Bo\Settings;
|
|||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use App\Rules\Hex;
|
||||
|
||||
class UpdateSettingsRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -26,7 +25,7 @@ class UpdateSettingsRequest extends FormRequest
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->mergePicture('logo');
|
||||
}
|
||||
|
|
@ -38,15 +37,11 @@ class UpdateSettingsRequest extends FormRequest
|
|||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$numberOfLocales = count(config('app.locales'));
|
||||
$rules = [
|
||||
'locales' => "required|array|between:1,$numberOfLocales",
|
||||
'sub_title' => "required|array|between:0,$numberOfLocales",
|
||||
'sub_title.*' => 'nullable|min:2|max:110',
|
||||
'footer_phrase' => "required|array|between:1,$numberOfLocales",
|
||||
'footer_phrase.*' => 'required|min:2|max:110',
|
||||
'color' => ['required', new Hex()],
|
||||
'home_timeout' => 'required|numeric|min:20|max:900',
|
||||
$rules = [
|
||||
'sub_title' => 'nullable|min:2|max:110',
|
||||
'footer_phrase' => 'required|min:2|max:110',
|
||||
'color' => 'required|color_hex',
|
||||
'home_timeout' => 'required|numeric|min:20|max:900',
|
||||
];
|
||||
|
||||
return \array_merge(
|
||||
|
|
@ -63,12 +58,11 @@ class UpdateSettingsRequest extends FormRequest
|
|||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'locales' => \trans('validation.custom.locales'),
|
||||
'color' => \trans('validation.custom.color'),
|
||||
'logo' => \trans('validation.custom.logo'),
|
||||
'home_timeout' => \trans('validation.custom.home_timeout'),
|
||||
'sub_title.*' => \trans('validation.custom.sub_title'),
|
||||
'footer_phrase.*' => \trans('validation.custom.footer_phrase'),
|
||||
'color' => \trans('validation.custom.color'),
|
||||
'logo' => \trans('validation.custom.logo'),
|
||||
'home_timeout' => \trans('validation.custom.home_timeout'),
|
||||
'sub_title' => \trans('validation.custom.sub_title'),
|
||||
'footer_phrase' => \trans('validation.custom.footer_phrase'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class UpdateStaticPageRequest extends FormRequest
|
|||
public function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'content' => (string) Str::of($this->input('content'))
|
||||
'content' => (string)Str::of($this->input('content'))
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Tenants;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class StoreTenantRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::allows('create', Tenant::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|min:2|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
namespace App\Http\Requests\Bo;
|
||||
|
||||
use App\Enums\InputType;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
abstract class UpdateMiniCrudRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -99,7 +99,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
}) : $input['dimensions'];
|
||||
|
||||
// * Adapt dimensions
|
||||
$sizes = (object) [
|
||||
$sizes = (object)[
|
||||
'minHeight' => $dim['height'],
|
||||
'maxHeight' => $dim['height'],
|
||||
'minWith' => $dim['width'],
|
||||
|
|
@ -133,8 +133,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
case InputType::url->value:
|
||||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
'required',
|
||||
'url',
|
||||
'required', 'url',
|
||||
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
function ($attribute, $value, $fail) {
|
||||
try {
|
||||
|
|
@ -209,22 +208,12 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
{
|
||||
$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;
|
||||
'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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use App\Models\User;
|
|||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class StoreUserRequest extends FormRequest
|
||||
{
|
||||
|
|
@ -40,13 +41,19 @@ class StoreUserRequest extends FormRequest
|
|||
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',
|
||||
'role' => ['required', new Enum(RoleEnum::class)],
|
||||
'published' => 'required|boolean',
|
||||
'tenants' => 'nullable|array',
|
||||
'tenants.*.id' => 'required|exists:tenants,id',
|
||||
'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',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +68,7 @@ class StoreUserRequest extends FormRequest
|
|||
'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')
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace App\Http\Requests\Bo\Users;
|
|||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class UpdateUserRequest extends StoreUserRequest
|
||||
{
|
||||
|
|
@ -25,7 +26,11 @@ class UpdateUserRequest extends StoreUserRequest
|
|||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'published' => $this->boolean('published'),
|
||||
'published' => $this->boolean('published'),
|
||||
'password' => $this->input('password') ??
|
||||
\optional($this->route('user'))->password,
|
||||
'password_confirmation' => $this->input('password_confirmation') ??
|
||||
\optional($this->route('user'))->password
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -40,9 +45,16 @@ class UpdateUserRequest extends StoreUserRequest
|
|||
'email' => [
|
||||
'required',
|
||||
'email:rfc,strict,dns,spoof,filter',
|
||||
Rule::unique('users', 'email')
|
||||
->ignore(\optional($this->route('user'))->getRouteKey())
|
||||
]
|
||||
Rule::unique('users', 'email')->ignore(\optional($this->route('user'))->getRouteKey())
|
||||
],
|
||||
'password' => [
|
||||
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
|
||||
->letters()
|
||||
->mixedCase()
|
||||
->numbers()
|
||||
->symbols()
|
||||
->uncompromised()
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\Mails;
|
||||
|
||||
use App\Mail\TenantDeleted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendTenantDeletedMail implements ShouldQueue
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
|
||||
/**
|
||||
* Name of the deleted tenant.
|
||||
*
|
||||
* @var string
|
||||
* */
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
Mail::to(config('mail.to.address'))->send(new TenantDeleted($this->name));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Font;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use App\Models\Settings;
|
||||
use App\Models\Tenant;
|
||||
use Carsdotcom\JsonSchemaValidation\SchemaValidator;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
|
|
@ -23,7 +22,7 @@ use Illuminate\Support\Facades\Cache;
|
|||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use sixlive\Laravel\JsonSchemaAssertions\SchemaAssertion;
|
||||
|
||||
class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
||||
{
|
||||
|
|
@ -46,13 +45,6 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
*/
|
||||
private string $tempFolder = 'app/temp/export';
|
||||
|
||||
/**
|
||||
* Working dir
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $tenantId = '';
|
||||
|
||||
/**
|
||||
* The number of seconds after which the job's unique lock will be released.
|
||||
*
|
||||
|
|
@ -62,33 +54,40 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param string $tenantId
|
||||
*
|
||||
* @throws RuntimeException If there's no Tenant for the given id.
|
||||
*/
|
||||
public function __construct(string $tenantId)
|
||||
public function __construct()
|
||||
{
|
||||
if (!Tenant::query()->where('id', $tenantId)->first()) {
|
||||
throw new RuntimeException(\sprintf('No Tenant found for id : %s', $tenantId));
|
||||
}
|
||||
$this->exportFolder = \storage_path($this->exportFolder);
|
||||
$this->tempFolder = \storage_path($this->tempFolder);
|
||||
$this->tenantId = $tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID for the job.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return "9999999";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException If a schema file could not be found.
|
||||
* @throws \RuntimeException If a schema could not be found or data could not be validated against schema.
|
||||
* Or Time stamp on file could not be fetched.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$data = $this->getData();
|
||||
$associatedFileList = $this->getAssociatedFileList($data);
|
||||
$fileContent = $this->getFile($data);
|
||||
$this->assertSchemaIsValid('app.schema.json', json_encode($fileContent));
|
||||
$schemaContent = ToolboxHelper::getJsonSchema('app.schema.json');
|
||||
if ($schemaContent === false) {
|
||||
throw new \RuntimeException("The schema could not be found 'app'");
|
||||
}
|
||||
$this->assertSchemaIsValid($schemaContent, json_encode($fileContent));
|
||||
$filesToZip = $this->preparePicturesForZip($associatedFileList);
|
||||
|
||||
$dataFilePath = "{$this->tempFolder}/data.json";
|
||||
|
|
@ -132,36 +131,25 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
*/
|
||||
protected function makeZipFile(array $filesToZip, string $dataFilePath): string
|
||||
{
|
||||
File::ensureDirectoryExists($this->exportFolder);
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$zipFileName = 'showroom-bo-export_' . now()->isoFormat('DD-MM-YYYY_HH-mm-ss') . '.zip';
|
||||
$zipFilePath = "{$this->exportFolder}/{$zipFileName}";
|
||||
|
||||
if ($zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||
$zipFileName = Str::of('showroom-bo-export')->append('_')
|
||||
->append(now()->isoFormat('DD-MM-YYYY_HH-mm-ss'))->append('.zip');
|
||||
$zipFilePath = Str::of($this->exportFolder)->append("/{$zipFileName}");
|
||||
if ($zip->open($zipFilePath, \ZipArchive::CREATE) !== true) {
|
||||
throw new \RuntimeException("Failed to open zip file {$zipFilePath}");
|
||||
}
|
||||
|
||||
foreach ($filesToZip as $file) {
|
||||
if (! is_file($file)) {
|
||||
throw new \RuntimeException("File to zip does not exist: {$file}");
|
||||
}
|
||||
$fileToAdd = Str::of($file)->replace($this->tempFolder, '')
|
||||
->start('/')->explode('/')->filter()->implode('/');
|
||||
if (! $zip->addFile($file, $fileToAdd)) {
|
||||
if (!$zip->addFile($file, $fileToAdd)) {
|
||||
throw new \RuntimeException("Failed to add file {$file} into zip");
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_file($dataFilePath) || ! $zip->addFile($dataFilePath, basename($dataFilePath))) {
|
||||
if (!$zip->addFile($dataFilePath, \pathinfo($dataFilePath, \PATHINFO_BASENAME))) {
|
||||
throw new \RuntimeException("Failed to add file {$dataFilePath} into zip");
|
||||
}
|
||||
|
||||
if (! $zip->close()) {
|
||||
throw new \RuntimeException(
|
||||
"Failed to write zip {$zipFilePath}: " . $zip->getStatusString()
|
||||
);
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
return $zipFilePath;
|
||||
}
|
||||
|
|
@ -169,38 +157,33 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
/**
|
||||
* Assert schema is valid
|
||||
*
|
||||
* @param string $schemaFileName
|
||||
* @param array $schema
|
||||
* @param string $fileContent
|
||||
* @return void
|
||||
* @throws \Carsdotcom\JsonSchemaValidation\Exceptions\JsonSchemaValidationException In case of failure.
|
||||
* @throws \PHPUnit\Framework\AssertionFailedError I case of failure.
|
||||
*/
|
||||
protected function assertSchemaIsValid(string $schemaFileName, string $fileContent): void
|
||||
protected function assertSchemaIsValid(array $schema, string $fileContent): void
|
||||
{
|
||||
SchemaValidator::validateEncodedStringOrThrow($fileContent, $schemaFileName);
|
||||
$assertion = new SchemaAssertion($schema);
|
||||
$assertion->assert($fileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get associated files list to export
|
||||
*
|
||||
* @param array<string,array<int,\App\Models\Brand>|array<int,\App\Models\Font>> $data
|
||||
* @return array{
|
||||
* settings: string,
|
||||
* brands: array<int, string>,
|
||||
* fonts: array<int, string>,
|
||||
* ranges: array<int, string>,
|
||||
* products: array<int, string>
|
||||
* }
|
||||
* @return array<'settings'|'brands'|'fonts'|'ranges'|'products',string|array<string,string>>
|
||||
*/
|
||||
protected function getAssociatedFileList(array $data): array
|
||||
{
|
||||
$groupBy = fn(array $path) => Str::of(Arr::get($path, 0))->explode('/')->get(2);
|
||||
$items = collect(Arr::get($data, 'brands'))->map(fn(Brand $brand) => [
|
||||
$groupBy = fn (array $path) => Str::of(Arr::get($path, 0))->explode('/')->get(2);
|
||||
$items = collect(Arr::get($data, 'brands'))->map(fn (Brand $brand) => [
|
||||
$brand->picture,
|
||||
// @phpstan-ignore-next-line
|
||||
$brand->ranges->map(fn(Range $range) => [
|
||||
$brand->ranges->map(fn (Range $range) => [
|
||||
$range->picture,
|
||||
// @phpstan-ignore-next-line
|
||||
$range->products->map(fn(Product $product) => [
|
||||
$range->products->map(fn (Product $product) => [
|
||||
$product->picture
|
||||
])->collect()->groupBy($groupBy)
|
||||
])->collect()->groupBy($groupBy)
|
||||
|
|
@ -213,16 +196,16 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
/** @var \Illuminate\Support\Collection<int, string> $brandList */
|
||||
$brandList = $items->pluck(0);
|
||||
/** @var \Illuminate\Support\Collection<int, string> $rangeList */
|
||||
$rangeList = $items->pluck(1)->map(fn(Collection $ranges) => $ranges
|
||||
->map(fn(Collection $range) => $range->pluck(0)))->flatten();
|
||||
$rangeList = $items->pluck(1)->map(fn (Collection $ranges) => $ranges
|
||||
->map(fn (Collection $range) => $range->pluck(0)))->flatten();
|
||||
/** @var \Illuminate\Support\Collection<int, string> $productList */
|
||||
$productList = $items->pluck(1)->map(fn(Collection $ranges) => $ranges
|
||||
->map(fn(Collection $range) => $range->pluck(1)))->flatten();
|
||||
$productList = $items->pluck(1)->map(fn (Collection $ranges) => $ranges
|
||||
->map(fn (Collection $range) => $range->pluck(1)))->flatten();
|
||||
return [
|
||||
'settings' => $logo,
|
||||
'brands' => $brandList->all(),
|
||||
'fonts' => $fonts,
|
||||
'ranges' => $rangeList->all(),
|
||||
'brands' => $brandList->all(),
|
||||
'fonts' => $fonts,
|
||||
'ranges' => $rangeList->all(),
|
||||
'products' => $productList->all(),
|
||||
];
|
||||
}
|
||||
|
|
@ -254,19 +237,14 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
}),
|
||||
'items' => Arr::map(Arr::get($data, 'brands'), function (Brand $brand) {
|
||||
$this->setPictureExportPath($brand);
|
||||
$brand->setRelation(
|
||||
'ranges',
|
||||
// @phpstan-ignore argument.type
|
||||
$brand->ranges->map(function (Range $range) {
|
||||
$this->setPictureExportPath($range);
|
||||
// @phpstan-ignore argument.type
|
||||
$range->setRelation('products', $range->products->map(function (Product $product) {
|
||||
$this->setPictureExportPath($product);
|
||||
return $product;
|
||||
}));
|
||||
return $range;
|
||||
})
|
||||
);
|
||||
$brand->setRelation('ranges', $brand->ranges->map(function (Range $range) {
|
||||
$this->setPictureExportPath($range);
|
||||
$range->setRelation('products', $range->products->map(function (Product $product) {
|
||||
$this->setPictureExportPath($product);
|
||||
return $product;
|
||||
}));
|
||||
return $range;
|
||||
}));
|
||||
return $brand;
|
||||
})
|
||||
];
|
||||
|
|
@ -277,8 +255,7 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
/**
|
||||
* Get the files that should be zipped
|
||||
*
|
||||
* @phpcs:ignore
|
||||
* @param array{settings: string,brands: array<int, string>,fonts: array<string>,ranges: array<int, string>,products: array<int, string>} $filesToZip
|
||||
* @param array<'brands'|'fonts'|'products'|'ranges'|'settings',array<string,string>|string> $filesToZip
|
||||
* @return array<string>
|
||||
* @throws \RuntimeException If temp folder caanot be cleared in order to work.
|
||||
*/
|
||||
|
|
@ -293,7 +270,7 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
throw new \RuntimeException("{$this->tempFolder} is not empty, refuse to prepare export");
|
||||
}
|
||||
|
||||
$copyPath = fn(string $targetPath) => function (string $path) use ($targetPath): string {
|
||||
$copyPath = fn (string $targetPath) => function (string $path) use ($targetPath): string {
|
||||
$targetFilePath = Str::of($targetPath)->append('/')->append(\pathinfo($path, \PATHINFO_BASENAME));
|
||||
File::copy(\public_path($path), $targetFilePath);
|
||||
return $targetFilePath;
|
||||
|
|
@ -308,7 +285,7 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
$targetPath = Str::of($this->tempFolder)->append("/fonts");
|
||||
File::makeDirectory($targetPath, 0700, true);
|
||||
$newPaths['fonts'] = collect($filesToZip['fonts'])->map(function ($path) use ($targetPath) {
|
||||
$targetFilePath = (string) $targetPath->append('/')->append(\pathinfo($path, \PATHINFO_BASENAME));
|
||||
$targetFilePath = (string)$targetPath->append('/')->append(\pathinfo($path, \PATHINFO_BASENAME));
|
||||
File::copy(\public_path($path), $targetFilePath);
|
||||
return $targetFilePath;
|
||||
})->all();
|
||||
|
|
@ -347,7 +324,7 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
*/
|
||||
private function getSettings(): Settings
|
||||
{
|
||||
return Settings::getFromCache($this->tenantId);
|
||||
return Settings::getCached()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -358,22 +335,16 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
private function getData(): array
|
||||
{
|
||||
return [
|
||||
'brands' => Brand::query()
|
||||
->where([['status', true], ['tenant_id', $this->tenantId]])
|
||||
'brands' => Brand::query()->where('status', true)
|
||||
->whereHas('ranges')
|
||||
->with('ranges', function (Builder $q) {
|
||||
return $q->where('status', true)
|
||||
$q->where('status', true)
|
||||
->whereHas('products')
|
||||
->with('products', function (Builder $q) {
|
||||
return $q->where('status', true);
|
||||
$q->where('status', true);
|
||||
});
|
||||
})
|
||||
->get()
|
||||
->all(),
|
||||
'fonts' => Font::query()
|
||||
->where('tenant_id', $this->tenantId)
|
||||
->get()
|
||||
->all()
|
||||
})->get()->all(),
|
||||
'fonts' => Font::get()->all()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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";
|
||||
} else {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
467
app/Lib/Helpers/FileStorageHelper.php
Normal file
467
app/Lib/Helpers/FileStorageHelper.php
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
<?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
|
||||
{
|
||||
/**
|
||||
* Remove all files from string attribute html that are stored in public
|
||||
*
|
||||
* @param string $publicStorageFilePath The file path from public directory.
|
||||
* @return string The local storage path.
|
||||
*/
|
||||
public static function getFileFullPath(string $publicStorageFilePath): string
|
||||
{
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter */
|
||||
$disk = Storage::disk('uploads');
|
||||
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
|
||||
return $disk->path($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
$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 = self::beautifyFilename($filename);
|
||||
}
|
||||
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
|
||||
/** @var string */
|
||||
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
/** @var string */
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME);
|
||||
|
||||
$encoding = mb_detect_encoding($filename);
|
||||
$encoding = $encoding ? $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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,23 +2,67 @@
|
|||
|
||||
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 Uwil\LaravelCommon\Helpers\ToolboxHelper as CommonToolboxHelper;
|
||||
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 extends CommonToolboxHelper
|
||||
class ToolboxHelper
|
||||
{
|
||||
/**
|
||||
* Get the contrast for any color
|
||||
*
|
||||
* @param string $hexcolor An HTML Hex color.
|
||||
* @return string "#000000" or "#FFFFFF".
|
||||
*/
|
||||
public static function getContrastYiq(string $hexcolor): string
|
||||
{
|
||||
$r = intval(\substr($hexcolor, 1, 2), 16);
|
||||
$g = intval(\substr($hexcolor, 3, 2), 16);
|
||||
$b = intval(\substr($hexcolor, 5, 2), 16);
|
||||
$yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
|
||||
return ($yiq >= 128) ? "#000000" : "#FFFFFF";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HTML Hex color to RGBA
|
||||
*
|
||||
* @param string $hexcolor An HTML Hex color.
|
||||
* @param integer $alpha The transparency to apply.
|
||||
* @return string RGBA string color.
|
||||
*/
|
||||
public static function toRgba(string $hexcolor, int $alpha = 1): string
|
||||
{
|
||||
$r = intval(\substr($hexcolor, 1, 3), 16);
|
||||
$g = intval(\substr($hexcolor, 3, 5), 16);
|
||||
$b = intval(\substr($hexcolor, 5, 7), 16);
|
||||
return "rgba({$r}, {$g}, {$b}, {$alpha})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific json schema
|
||||
*
|
||||
* @depends `sixlive/laravel-json-schema-assertions`
|
||||
*
|
||||
* @param string $schema
|
||||
* @return array|false Returns false if the schema cannot be found.
|
||||
* @throws \RuntimeException If the schema does not exists.
|
||||
*/
|
||||
public static function getJsonSchema(string $schema): array|false
|
||||
{
|
||||
$schemaPath = Str::of(config('json-schema.local_base_prefix'))->finish("/{$schema}");
|
||||
$schemaPath = Str::of(config('json-schema-assertions.schema_base_path'))->finish("/{$schema}");
|
||||
if (!File::exists($schemaPath)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -27,11 +71,557 @@ class ToolboxHelper extends CommonToolboxHelper
|
|||
throw new \RuntimeException("{$schemaPath} is not a json schema .");
|
||||
}
|
||||
$schemaContent = json_decode($schemaContent, true);
|
||||
$schemaRoute = route('fo.schema', ['schema' => $schema]);
|
||||
if (app()->environment('local')) {
|
||||
$schemaRoute = Str::replace('https', 'http', $schemaRoute);
|
||||
}
|
||||
Arr::set($schemaContent, '$id', $schemaRoute);
|
||||
Arr::set($schemaContent, '$id', route('fo.schema', ['schema' => $schema]));
|
||||
return $schemaContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a terminal command and gives the output
|
||||
*
|
||||
* @param array|string $command The command to execute on terminal.
|
||||
* @param boolean $throw Wether to throw en esception on failure or not.
|
||||
* @return string|false Return false on failure only if $throw is false.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
|
||||
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
* @throws \Illuminate\Process\Exceptions\ProcessTimedOutException
|
||||
* @throws \Symfony\Component\Process\Exception\LogicException
|
||||
* @throws \Symfony\Component\Process\Exception\InvalidArgumentException
|
||||
* @throws \Symfony\Component\Process\Exception\RuntimeException
|
||||
* @throws \LogicException
|
||||
* @throws \RuntimeException If the command fails.
|
||||
*/
|
||||
public static function runConsoleCommand(array|string $command, bool $throw = true): string | false
|
||||
{
|
||||
// phpcs:enable
|
||||
$process = \Illuminate\Support\Facades\Process::command($command)->run();
|
||||
if ($process->failed()) {
|
||||
$code = $process->exitCode();
|
||||
$ouput = $process->errorOutput();
|
||||
if ($throw) {
|
||||
throw new \RuntimeException("Failed to run command exit code {$code} with output \n{$ouput}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return $process->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(trans('recaptcha.not_enough_score'));
|
||||
}
|
||||
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(trans('recaptcha.error'));
|
||||
} //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);
|
||||
// Assert route matches existing routes.
|
||||
$routes->match($request);
|
||||
// ? Is url internal ?
|
||||
if (\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)) {
|
||||
// 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|false
|
||||
*/
|
||||
public static function mbReplace(
|
||||
array|string $search,
|
||||
array|string $replace,
|
||||
array|string $subject,
|
||||
int &$count = 0
|
||||
): array|string|false {
|
||||
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)) and
|
||||
// @phpstan-ignore-next-line
|
||||
$model->isTranslatableAttribute($attribute)
|
||||
) {
|
||||
// @phpstan-ignore-next-line
|
||||
return $model->getTranslation(
|
||||
$attribute,
|
||||
// @phpstan-ignore-next-line
|
||||
$model->getLocale(),
|
||||
// @phpstan-ignore-next-line
|
||||
$model->useFallbackLocale()
|
||||
);
|
||||
}
|
||||
return $model->getOriginal($attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mixed $input
|
||||
* @return array
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function objectToArray(mixed $input): array
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (!(is_object($input) or is_array($input))) {
|
||||
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an array to object
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return object
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function arrayToObject(mixed $input): object
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (!(is_object($input) or is_array($input))) {
|
||||
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate picture
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param string|null $value
|
||||
* @param callable $fail
|
||||
* @param object $sizes
|
||||
* @return void
|
||||
*/
|
||||
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes): void
|
||||
{
|
||||
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
|
||||
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
return;
|
||||
}
|
||||
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
|
||||
if (
|
||||
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
|
||||
'image/jpeg', 'image/jpg', 'image/png', 'image/gif'
|
||||
])
|
||||
) {
|
||||
$fail(trans(':attribute doit être une image de type \'image/jpeg,image/jpg,image/png,image/gif\'', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
}
|
||||
if (!($dimensions = \getimagesize($fileFullPath))) {
|
||||
$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->minWidth}px et {$sizes->maxWidth}px"
|
||||
);
|
||||
if ($sizes->minWidth === $sizes->maxWidth) {
|
||||
$xMessage = trans(":attribute la largeur doit être de {$sizes->minWidth}px");
|
||||
}
|
||||
$yMessage = trans(
|
||||
":attribute la hauteur doit être comprise entre {$sizes->minWidth}px et {$sizes->maxWidth}px"
|
||||
);
|
||||
if ($sizes->minHeight === $sizes->maxHeight) {
|
||||
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
|
||||
}
|
||||
|
||||
if (($width < $sizes->minWidth) or ($width > $sizes->maxWidth)) {
|
||||
$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,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TenantDeleted extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/** @var string $name */
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function build(): mixed
|
||||
{
|
||||
return $this
|
||||
->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->subject(sprintf('Suppression du tenant %s', $this->name))
|
||||
->markdown('bo.mails.tenant-deleted', ['name' => $this->name]);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,14 +7,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
|
||||
/**
|
||||
* @property string $model Target model.
|
||||
* @property string $model_id Id of the target model.
|
||||
* @property \App\Enums\ActivityLogs\ActivityLogsEventEnum $event Event of this activity (ActivityLogsEventEnum).
|
||||
* @property array $data Changes.
|
||||
* @property string $tenant_id Id of the related tenant.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
*
|
||||
* @property-read \App\Models\User $user User BelongsTo relation.
|
||||
|
|
@ -23,7 +21,6 @@ class ActivityLog extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
use BelongsToTenant;
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
|
|
@ -35,7 +32,7 @@ class ActivityLog extends Model
|
|||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'is_anonymous',
|
||||
|
|
@ -58,29 +55,6 @@ class ActivityLog extends Model
|
|||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Models that are not related to a tenant.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
private $nonTenantRelatedModels = [
|
||||
User::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
$model->tenant_id = !\in_array($model->model_class, $model->nonTenantRelatedModels) ?
|
||||
\tenant('id') :
|
||||
null;
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,26 +3,22 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Casts\HtmlColor;
|
||||
use App\Helpers\ShowroomFileHelper;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
/**
|
||||
* @property string $name Name of the brand.
|
||||
* @property string $slug Slug of the name for the brand.
|
||||
* @property string $color HTML Color string.
|
||||
* @property string $picture Picture path for the brand.
|
||||
* @property boolean $shelf Les enfant sont rendus sur un étagere.
|
||||
* @property boolean $status Visible ? (présent dans l'export).
|
||||
* @property string $tenant_id Id of the related tenant.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
|
||||
*
|
||||
|
|
@ -32,18 +28,17 @@ class Brand extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
use HasTranslations;
|
||||
use BelongsToTenant;
|
||||
use ActivityLog;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'color',
|
||||
'picture',
|
||||
'shelf',
|
||||
'status',
|
||||
];
|
||||
|
||||
|
|
@ -64,6 +59,7 @@ class Brand extends Model
|
|||
*/
|
||||
protected $casts = [
|
||||
'color' => HtmlColor::class,
|
||||
'shelf' => 'bool',
|
||||
'status' => 'bool',
|
||||
];
|
||||
|
||||
|
|
@ -79,28 +75,22 @@ class Brand extends Model
|
|||
self::assertNameIsUnique($model->name);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->slug = Str::slug($model->name);
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->picture instanceof UploadedFile) {
|
||||
$model->picture = ShowroomFileHelper::storeFileWithUUIDName($model->picture, $model);
|
||||
}
|
||||
$model->slug = Str::slug($model->name);
|
||||
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name, $model->id);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->slug = Str::slug($model->name);
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->picture instanceof UploadedFile) {
|
||||
$model->picture = ShowroomFileHelper::storeFileWithUUIDName($model->picture, $model);
|
||||
}
|
||||
$model->slug = Str::slug($model->name);
|
||||
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
FileHelper::removeOldFile($model, 'picture');
|
||||
FileStorageHelper::removeOldFile($model, 'picture');
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'picture');
|
||||
FileStorageHelper::removeFile($model, 'picture');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +99,7 @@ class Brand extends Model
|
|||
/**
|
||||
* Get all of the ranges for the Brand
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\Range, $this>
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function ranges(): HasMany
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,26 +2,21 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
/**
|
||||
* @property string $font_name The slugified font name.
|
||||
* @property string|null $font The font path.
|
||||
* @property string $tenant_id Id of the related tenant.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
|
||||
*/
|
||||
class Font extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use BelongsToTenant;
|
||||
use ActivityLog;
|
||||
|
||||
/**
|
||||
* Default available fonts
|
||||
|
|
@ -31,12 +26,11 @@ class Font extends Model
|
|||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'font_name',
|
||||
'font',
|
||||
'tenant_id',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +46,7 @@ class Font extends Model
|
|||
|
||||
// * TRANSFORMATIONS
|
||||
if ($model->font) {
|
||||
$model->font = FileHelper::storeFile($model, $model->font, true, prefix: $model->tenant_id);
|
||||
$model->font = FileStorageHelper::storeFile($model, $model->font, true);
|
||||
$font_basename = Str::of(\pathinfo($model->font, \PATHINFO_BASENAME))->explode('.')->get(0);
|
||||
$model->font_name = Str::slug($font_basename);
|
||||
}
|
||||
|
|
@ -63,16 +57,16 @@ class Font extends Model
|
|||
|
||||
// * TRANSFORMATIONS
|
||||
if ($model->font) {
|
||||
$model->font = FileHelper::storeFile($model, $model->font, true, prefix: $model->tenant_id);
|
||||
$model->font = FileStorageHelper::storeFile($model, $model->font, true);
|
||||
$font_basename = Str::of(\pathinfo($model->font, \PATHINFO_BASENAME))->explode('.')->get(0);
|
||||
$model->font_name = Str::slug($font_basename);
|
||||
}
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
FileHelper::removeOldFile($model, 'font');
|
||||
FileStorageHelper::removeOldFile($model, 'font');
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'font');
|
||||
FileStorageHelper::removeFile($model, 'font');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,14 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use App\Helpers\ShowroomFileHelper;
|
||||
|
||||
/**
|
||||
* @property string $name Name of the product.
|
||||
|
|
@ -21,7 +17,6 @@ use App\Helpers\ShowroomFileHelper;
|
|||
* @property string $description WYSIWYG text description using ckeditor.
|
||||
* @property string $picture Picture path for the product.
|
||||
* @property boolean $status Visible ? (présent dans l'export).
|
||||
* @property string $tenant_id Id of the related tenant.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
|
||||
*
|
||||
|
|
@ -31,13 +26,11 @@ class Product extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
use HasTranslations;
|
||||
use BelongsToTenant;
|
||||
use ActivityLog;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
|
|
@ -79,28 +72,22 @@ class Product extends Model
|
|||
self::assertNameIsUnique($model->name);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->slug = Str::slug($model->name);
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->picture instanceof UploadedFile) {
|
||||
$model->picture = ShowroomFileHelper::storeFileWithUUIDName($model->picture, $model);
|
||||
}
|
||||
$model->slug = Str::slug($model->name);
|
||||
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name, $model->id);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->slug = Str::slug($model->name);
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->picture instanceof UploadedFile) {
|
||||
$model->picture = ShowroomFileHelper::storeFileWithUUIDName($model->picture, $model);
|
||||
}
|
||||
$model->slug = Str::slug($model->name);
|
||||
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
FileHelper::removeOldFile($model, 'picture');
|
||||
FileStorageHelper::removeOldFile($model, 'picture');
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'picture');
|
||||
FileStorageHelper::removeFile($model, 'picture');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,19 +2,14 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Width;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use App\Helpers\ShowroomFileHelper;
|
||||
|
||||
/**
|
||||
* @property string $name Name of the range.
|
||||
|
|
@ -24,8 +19,8 @@ use App\Helpers\ShowroomFileHelper;
|
|||
* @property integer $width Width or the range to display
|
||||
* (1 or 2 columns, 2 is full width).
|
||||
* @property integer $order Order for the range to display.
|
||||
* @property boolean $shelf Les enfant sont rendus sur un étagere.
|
||||
* @property boolean $status Visible ? (présent dans l'export).
|
||||
* @property string $tenant_id Id of the related tenant.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
|
||||
*
|
||||
|
|
@ -37,13 +32,11 @@ class Range extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
use HasTranslations;
|
||||
use BelongsToTenant;
|
||||
use ActivityLog;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
|
|
@ -53,6 +46,7 @@ class Range extends Model
|
|||
'color',
|
||||
'width',
|
||||
'order',
|
||||
'shelf',
|
||||
'status',
|
||||
];
|
||||
|
||||
|
|
@ -72,8 +66,8 @@ class Range extends Model
|
|||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'shelf' => 'bool',
|
||||
'status' => 'bool',
|
||||
'width' => Width::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -88,11 +82,8 @@ class Range extends Model
|
|||
self::assertNameIsUnique($model->name);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->slug = Str::slug($model->name);
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->picture instanceof UploadedFile) {
|
||||
$model->picture = ShowroomFileHelper::storeFileWithUUIDName($model->picture, $model);
|
||||
}
|
||||
$model->slug = Str::slug($model->name);
|
||||
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
||||
self::setOrder($model);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
|
|
@ -100,19 +91,16 @@ class Range extends Model
|
|||
self::assertNameIsUnique($model->name, $model->id);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->slug = Str::slug($model->name);
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->picture instanceof UploadedFile) {
|
||||
$model->picture = ShowroomFileHelper::storeFileWithUUIDName($model->picture, $model);
|
||||
}
|
||||
$model->slug = Str::slug($model->name);
|
||||
$model->picture = FileStorageHelper::storeFile($model, $model->picture, true);
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
FileHelper::removeOldFile($model, 'picture');
|
||||
FileHelper::removeOldFile($model, 'font');
|
||||
FileStorageHelper::removeOldFile($model, 'picture');
|
||||
FileStorageHelper::removeOldFile($model, 'font');
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'picture');
|
||||
FileHelper::removeFile($model, 'font');
|
||||
FileStorageHelper::removeFile($model, 'picture');
|
||||
FileStorageHelper::removeFile($model, 'font');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +129,7 @@ class Range extends Model
|
|||
/**
|
||||
* Get all of the products for the Brand
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\Product, $this>
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function products(): HasMany
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,24 +2,18 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use App\Traits\Models\IsCachedAsCollection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Helpers\ShowroomFileHelper;
|
||||
|
||||
/**
|
||||
* @property array<int, string> $locales Selected locales used in the application.
|
||||
* @property array<string, string> $sub_title Sous-titre de l'application.
|
||||
* @property array<string, string> $footer_phrase Texte en bas de page.
|
||||
* @property string $sub_title Sous-titre de l'application'.
|
||||
* @property string $footer_phrase Texte en bas de page.
|
||||
* @property string $color HTML Color string.
|
||||
* @property string $logo Logo path for the app.
|
||||
* @property integer $home_timeout Timeout time in seconds before returning to homepage.
|
||||
* @property string $tenant_id Id of the related tenant.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
|
||||
*
|
||||
|
|
@ -27,34 +21,30 @@ use App\Helpers\ShowroomFileHelper;
|
|||
class Settings extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasTranslations;
|
||||
use IsCachedAsCollection;
|
||||
use BelongsToTenant;
|
||||
use ActivityLog;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'locales',
|
||||
'sub_title',
|
||||
'footer_phrase',
|
||||
'color',
|
||||
'logo',
|
||||
'home_timeout',
|
||||
'tenant_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
* Translatable fields array
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'locales' => 'array',
|
||||
'sub_title' => 'array',
|
||||
'footer_phrase' => 'array',
|
||||
public $translatable = [
|
||||
'sub_title',
|
||||
'footer_phrase',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -65,130 +55,21 @@ class Settings extends Model
|
|||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
self::manageFields($model);
|
||||
});
|
||||
static::created(function (self $model) {
|
||||
self::resetCache($model->tenant_id);
|
||||
// * TRANSFORMATIONS
|
||||
$model->logo = FileStorageHelper::storeFile($model, $model->logo, true);
|
||||
});
|
||||
static::created(fn () => self::removeCached());
|
||||
static::updating(function (self $model) {
|
||||
self::manageFields($model);
|
||||
// * TRANSFORMATIONS
|
||||
$model->logo = FileStorageHelper::storeFile($model, $model->logo, true);
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
FileHelper::removeOldFile($model, 'logo');
|
||||
self::resetCache($model->tenant_id);
|
||||
FileStorageHelper::removeOldFile($model, 'logo');
|
||||
self::removeCached();
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'logo');
|
||||
self::removeFromCache($model->tenant_id);
|
||||
FileStorageHelper::removeFile($model, 'logo');
|
||||
self::removeCached();
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Common methods function.
|
||||
*
|
||||
* @param self $model
|
||||
* @return void
|
||||
*/
|
||||
private static function manageFields(self $model)
|
||||
{
|
||||
// * TRANSFORMATIONS
|
||||
/** @phpstan-ignore-next-line */
|
||||
if ($model->logo instanceof UploadedFile) {
|
||||
$model->logo = ShowroomFileHelper::storeFileWithUUIDName($model->logo, $model);
|
||||
}
|
||||
if (!$model->locales) {
|
||||
$model->locales = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get from cache a Setting model with a key like this : {tenant}_settings and for remember it 24 hours.
|
||||
*
|
||||
* @param string|null $tenant_id
|
||||
*
|
||||
* @return self|null
|
||||
*/
|
||||
public static function getFromCache(?string $tenant_id): self|null
|
||||
{
|
||||
if (!$tenant_id) {
|
||||
return null;
|
||||
};
|
||||
|
||||
return Cache::remember(
|
||||
self::getCacheKey($tenant_id),
|
||||
86400,
|
||||
function () use ($tenant_id) {
|
||||
return self::query()->where('tenant_id', $tenant_id)->first();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all cache elements related to a given tenant id.
|
||||
*
|
||||
* @param string $tenant_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function resetCache(string $tenant_id): void
|
||||
{
|
||||
Cache::forget(self::getCacheKey($tenant_id));
|
||||
self::getFromCache($tenant_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove from cache a Setting model.
|
||||
*
|
||||
* @param string $tenant_id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function removeFromCache(string $tenant_id): void
|
||||
{
|
||||
Cache::forget(self::getCacheKey($tenant_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for a setting related to its tenant.
|
||||
*
|
||||
* @param string $tenant_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCacheKey(string $tenant_id): string
|
||||
{
|
||||
return \sprintf('%s_settings', $tenant_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Setting with fake datas.
|
||||
*
|
||||
* @param string $tenantId The ID of the related tenant.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createNewData(string $tenantId)
|
||||
{
|
||||
$settings = new Settings();
|
||||
$settings->logo = FileHelper::storeFile(
|
||||
new Settings(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/settings/default-picture-200x200.jpg')
|
||||
),
|
||||
prefix: $tenantId
|
||||
);
|
||||
$settings->locales = ['fr'];
|
||||
$settings->sub_title = [
|
||||
'fr' => 'FR',
|
||||
];
|
||||
$settings->footer_phrase = [
|
||||
'fr' => 'FR',
|
||||
];
|
||||
$settings->color = '#000000';
|
||||
$settings->home_timeout = 60;
|
||||
$settings->tenant_id = $tenantId;
|
||||
$settings->saveOrFail();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Enums\StaticPageType;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
/**
|
||||
* @property \App\Enums\StaticPageType $type Page type (StaticPageEnum).
|
||||
|
|
@ -27,7 +27,7 @@ class StaticPage extends Model
|
|||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'seo_title',
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Jobs\Mails\SendTenantDeletedMail;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
|
||||
use Stancl\Tenancy\Contracts\Tenant as TenantContract;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Tenant extends BaseTenant implements TenantContract
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'name',
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the custom columns (that won't be stored in the data JSON column)
|
||||
* by overriding the getCustomColumns() method.
|
||||
*
|
||||
* @see https://tenancyforlaravel.com/docs/v3/tenants
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getCustomColumns(): array
|
||||
{
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->id = Str::slug($model->name);
|
||||
});
|
||||
static::created(function () {
|
||||
self::resetCache();
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name, $model->id);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
$model->id = Str::slug($model->name);
|
||||
});
|
||||
static::updated(function () {
|
||||
self::resetCache();
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
self::resetCache();
|
||||
dispatch(new SendTenantDeletedMail($model->name));
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Reset all cache elements related to a given tenant id.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function resetCache(): void
|
||||
{
|
||||
Cache::forget('all_tenants');
|
||||
self::getFromCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get from cache a Setting model with a key like this : {tenant}_settings and for remember it 24 hours.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public static function getFromCache(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return Cache::remember(
|
||||
'all_tenants',
|
||||
86400,
|
||||
function () {
|
||||
return self::query()->get();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts using validation that the name is unique
|
||||
*
|
||||
* @param string|null $name
|
||||
* @param string|null $id
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If name already exists.
|
||||
*/
|
||||
private static function assertNameIsUnique(string|null $name, ?string $id = null)
|
||||
{
|
||||
$table = (new self())->getTable();
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'id', Str::slug($name), $id);
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* The tenants that belong to the user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The settings associated to the tenant.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function settings(): HasMany
|
||||
{
|
||||
return $this->hasMany(Settings::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The fonts associated to the tenant.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function fonts(): HasMany
|
||||
{
|
||||
return $this->hasMany(Font::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brands associated to the tenant.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function brands(): HasMany
|
||||
{
|
||||
return $this->hasMany(Brand::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brands associated to the tenant.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function products(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brands associated to the tenant.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function ranges(): HasMany
|
||||
{
|
||||
return $this->hasMany(Range::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The activity logs associated to the tenant.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function activityLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(ActivityLog::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@ use App\Enums\Users\RoleEnum;
|
|||
use App\Traits\Models\ActivityLog;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
/**
|
||||
* @property string $first_name Firstname.
|
||||
* @property string $last_name Lastname.
|
||||
* @property string $email Email.
|
||||
* @property string $password Password.
|
||||
* @property \App\Enums\Users\RoleEnum $role Role.
|
||||
* @property \Illuminate\Support\Carbon $published Published status.
|
||||
* @property \Illuminate\Support\Carbon $published_at Publication status update date.
|
||||
|
|
@ -31,25 +32,26 @@ class User extends Authenticatable
|
|||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'password',
|
||||
'role',
|
||||
'published',
|
||||
'published_at',
|
||||
'tenant_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'remember_token'
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -72,6 +74,7 @@ class User extends Authenticatable
|
|||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
self::updatePassword($model);
|
||||
// * Prevent user elevation privileges
|
||||
throw_if(
|
||||
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
|
||||
|
|
@ -79,6 +82,7 @@ class User extends Authenticatable
|
|||
);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
self::updatePassword($model);
|
||||
if (optional(auth('backend')->user())->getKey() === $model->getKey()) {
|
||||
\validator(
|
||||
['published' => $model->published],
|
||||
|
|
@ -94,15 +98,30 @@ class User extends Authenticatable
|
|||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* The tenants that belong to the user.
|
||||
* Update password.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
* @param self $model
|
||||
* @return void
|
||||
*/
|
||||
public function tenants(): BelongsToMany
|
||||
private static function updatePassword(self $model): void
|
||||
{
|
||||
return $this->belongsToMany(Tenant::class);
|
||||
if (Hash::needsRehash($model->password)) {
|
||||
$model->password = Hash::make($model->password);
|
||||
}
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
* Activities logs One-to-many relationship.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function activityLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(ActivityLog::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class TenantPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewAny(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Tenant $tenant
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function delete(User $authUser, Tenant $tenant): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Tenant $tenant
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function forceDelete(User $authUser, \App\Models\Tenant $tenant): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ class UserPolicy
|
|||
*/
|
||||
public function viewAny(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -126,18 +126,4 @@ class UserPolicy
|
|||
UserStaticRules::atLeastRole($authUser, RoleEnum::admin) and
|
||||
UserStaticRules::atLeastRole($authUser, $user->role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can edit the thenants associated to a user.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\User $user
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function editTenants(User $authUser, User $user): bool
|
||||
{
|
||||
return $authUser->getRouteKey() !== $user->getRouteKey() and
|
||||
UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ class AppServiceProvider extends ServiceProvider
|
|||
// * HTTPS FIX
|
||||
URL::forceScheme('https');
|
||||
|
||||
if (app()->environment('local')) {
|
||||
URL::forceRootUrl(config('app.url'));
|
||||
}
|
||||
|
||||
// * HA PROXY FIX
|
||||
App::singleton('clientIp', function () {
|
||||
if (config('app.env') === 'local') {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
|
@ -28,7 +27,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||
return $user->role === RoleEnum::conceptor;
|
||||
});
|
||||
Gate::define('exportZip', function (User $user) {
|
||||
return $user->role === RoleEnum::conceptor && Tenant::count();
|
||||
return $user->role === RoleEnum::conceptor;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,6 @@ namespace App\Providers;
|
|||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Slides\Saml2\Events\SignedIn;
|
||||
use Slides\Saml2\Events\SignedOut;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -35,49 +28,4 @@ class EventServiceProvider extends ServiceProvider
|
|||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Event::listen('Slides\Saml2\Events\SignedIn', function (SignedIn $event) {
|
||||
|
||||
$messageId = $event->getAuth()->getLastMessageId();
|
||||
|
||||
$fromCache = Cache::get(config('cache.prefix') . "_saml_message_id_" . $messageId);
|
||||
if ($fromCache) {
|
||||
logger()->error(
|
||||
'SAML MESSAGE ID HANDLER',
|
||||
['error' => 'Message ID : ' . $messageId . " replayed !"]
|
||||
);
|
||||
abort(403);
|
||||
} else {
|
||||
Cache::put(config('cache.prefix') . "_saml_message_id_" . $messageId, $messageId, 28800);
|
||||
// Cache for 8h.
|
||||
}
|
||||
|
||||
$user = $event->getSaml2User();
|
||||
$email = $user->getNameId();
|
||||
|
||||
$lUser = User::where('email', '=', $email)->first();
|
||||
|
||||
if (!$lUser) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
Auth::guard('backend')->login($lUser);
|
||||
session(['sessionIndex' => $user->getSessionIndex()]);
|
||||
$lUser->last_connection = now();
|
||||
$lUser->saveQuietly();
|
||||
});
|
||||
|
||||
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
Event::listen('Slides\Saml2\Events\SignedOut', function (SignedOut $event) {
|
||||
Auth::guard('backend')->logout();
|
||||
Session::save();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Brand;
|
||||
use App\Models\Font;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use App\Models\Settings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
|
|
@ -46,25 +42,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
->group(base_path('routes/web-fo.php'));
|
||||
});
|
||||
|
||||
// Bind tenant related Models.
|
||||
$tenantRelatedModels = [
|
||||
'brand' => Brand::class,
|
||||
'font' => Font::class,
|
||||
'product' => Product::class,
|
||||
'range' => Range::class,
|
||||
'settings' => Settings::class,
|
||||
];
|
||||
|
||||
foreach ($tenantRelatedModels as $key => $tenantRelatedModel) {
|
||||
Route::bind($key, function (string $value) use ($tenantRelatedModel) {
|
||||
return $tenantRelatedModel::query()
|
||||
->where('id', $value)
|
||||
->where('tenant_id', \request()->tenant)
|
||||
->firstOrFail();
|
||||
});
|
||||
}
|
||||
|
||||
Route::model('user', User::class);
|
||||
Route::model('range', Range::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -108,11 +87,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
})->all();
|
||||
// * Filter array keys
|
||||
$cspReport = collect(array_intersect_key($cspReport, [
|
||||
'blocked-uri' => '',
|
||||
'document-uri' => '',
|
||||
'original-policy' => '',
|
||||
'referrer' => '',
|
||||
'violated-directive' => ''
|
||||
'blocked-uri' => '', 'document-uri' => '', 'original-policy' => '',
|
||||
'referrer' => '', 'violated-directive' => ''
|
||||
]));
|
||||
\Illuminate\Support\Facades\Log::warning(sprintf(
|
||||
"CSP REPORT for %s : \n %s",
|
||||
|
|
|
|||
|
|
@ -1,178 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Stancl\JobPipeline\JobPipeline;
|
||||
use Stancl\Tenancy\Events;
|
||||
use Stancl\Tenancy\Jobs;
|
||||
use Stancl\Tenancy\Listeners;
|
||||
use Stancl\Tenancy\Middleware;
|
||||
|
||||
class TenancyServiceProvider extends ServiceProvider
|
||||
{
|
||||
/** @var string By default, no namespace is used to support the callable array syntax. */
|
||||
public static string $controllerNamespace = '';
|
||||
|
||||
/**
|
||||
* Returns array of all Tenant related events.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function events()
|
||||
{
|
||||
return [
|
||||
// Tenant events.
|
||||
Events\CreatingTenant::class => [],
|
||||
Events\TenantCreated::class => [
|
||||
JobPipeline::make([
|
||||
Jobs\CreateDatabase::class,
|
||||
Jobs\MigrateDatabase::class,
|
||||
])->send(function (Events\TenantCreated $event) {
|
||||
return $event->tenant;
|
||||
})->shouldBeQueued(false),
|
||||
// `false` by default, but you probably want to make this `true` for production.
|
||||
],
|
||||
Events\SavingTenant::class => [],
|
||||
Events\TenantSaved::class => [],
|
||||
Events\UpdatingTenant::class => [],
|
||||
Events\TenantUpdated::class => [],
|
||||
Events\DeletingTenant::class => [],
|
||||
Events\TenantDeleted::class => [
|
||||
JobPipeline::make([
|
||||
Jobs\DeleteDatabase::class,
|
||||
])->send(function (Events\TenantDeleted $event) {
|
||||
return $event->tenant;
|
||||
})->shouldBeQueued(false),
|
||||
// `false` by default, but you probably want to make this `true` for production.
|
||||
],
|
||||
|
||||
// Domain events.
|
||||
Events\CreatingDomain::class => [],
|
||||
Events\DomainCreated::class => [],
|
||||
Events\SavingDomain::class => [],
|
||||
Events\DomainSaved::class => [],
|
||||
Events\UpdatingDomain::class => [],
|
||||
Events\DomainUpdated::class => [],
|
||||
Events\DeletingDomain::class => [],
|
||||
Events\DomainDeleted::class => [],
|
||||
|
||||
// Database events.
|
||||
Events\DatabaseCreated::class => [],
|
||||
Events\DatabaseMigrated::class => [],
|
||||
Events\DatabaseSeeded::class => [],
|
||||
Events\DatabaseRolledBack::class => [],
|
||||
Events\DatabaseDeleted::class => [],
|
||||
|
||||
// Tenancy events.
|
||||
Events\InitializingTenancy::class => [],
|
||||
Events\TenancyInitialized::class => [
|
||||
Listeners\BootstrapTenancy::class,
|
||||
],
|
||||
|
||||
Events\EndingTenancy::class => [],
|
||||
Events\TenancyEnded::class => [
|
||||
Listeners\RevertToCentralContext::class,
|
||||
],
|
||||
|
||||
Events\BootstrappingTenancy::class => [],
|
||||
Events\TenancyBootstrapped::class => [],
|
||||
Events\RevertingToCentralContext::class => [],
|
||||
Events\RevertedToCentralContext::class => [],
|
||||
|
||||
// Resource syncing.
|
||||
Events\SyncedResourceSaved::class => [
|
||||
Listeners\UpdateSyncedResource::class,
|
||||
],
|
||||
|
||||
/**
|
||||
* Fired only when a synced resource is changed in a different DB than the origin DB
|
||||
* (to avoid infinite loops).
|
||||
*/
|
||||
Events\SyncedResourceChangedInForeignDatabase::class => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
// Empty.
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot method.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->bootEvents();
|
||||
$this->mapRoutes();
|
||||
|
||||
$this->makeTenancyMiddlewareHighestPriority();
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot events.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function bootEvents()
|
||||
{
|
||||
foreach ($this->events() as $event => $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof JobPipeline) {
|
||||
$listener = $listener->toListener();
|
||||
}
|
||||
|
||||
Event::listen($event, $listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map tenants routes.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapRoutes()
|
||||
{
|
||||
$this->app->booted(function () {
|
||||
if (file_exists(base_path('routes/tenant.php'))) {
|
||||
Route::namespace(static::$controllerNamespace)
|
||||
->group(base_path('routes/tenant.php'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Make Tenancy Middleware Highest Priority
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function makeTenancyMiddlewareHighestPriority()
|
||||
{
|
||||
$tenancyMiddleware = [
|
||||
// Even higher priority than the initialization middleware.
|
||||
Middleware\PreventAccessFromCentralDomains::class,
|
||||
|
||||
Middleware\InitializeTenancyByDomain::class,
|
||||
Middleware\InitializeTenancyBySubdomain::class,
|
||||
Middleware\InitializeTenancyByDomainOrSubdomain::class,
|
||||
Middleware\InitializeTenancyByPath::class,
|
||||
Middleware\InitializeTenancyByRequestData::class,
|
||||
];
|
||||
|
||||
foreach (array_reverse($tenancyMiddleware) as $middleware) {
|
||||
$this->app[\Illuminate\Contracts\Http\Kernel::class]->prependToMiddlewarePriority($middleware);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
class TranslationServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use App\Models\Settings;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
|
|
@ -37,14 +35,7 @@ class ViewServiceProvider extends ServiceProvider
|
|||
} else {
|
||||
Paginator::useBootstrapFive();
|
||||
}
|
||||
|
||||
View::composer('*', function () {
|
||||
View::share('settingsGlobal', Settings::getFromCache(tenant('id')));
|
||||
});
|
||||
|
||||
if (Schema::hasTable('tenants')) {
|
||||
View::share('tenantGlobal', Tenant::getFromCache());
|
||||
}
|
||||
} //end if
|
||||
View::share('settingsGlobal', Settings::getCached()->first());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class Hex implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $forceFull;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $allowAlpha;
|
||||
|
||||
/**
|
||||
* __construct method
|
||||
*
|
||||
* @param boolean $forceFull
|
||||
* @param boolean $allowAlpha
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(bool $forceFull = false, bool $allowAlpha = false)
|
||||
{
|
||||
$this->forceFull = $forceFull;
|
||||
|
||||
$this->allowAlpha = $allowAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @param \Closure(string):\Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
* @return void
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
$pattern = '/^#([a-fA-F0-9]{6}';
|
||||
|
||||
if (!$this->forceFull) {
|
||||
$pattern .= '|[a-fA-F0-9]{3}';
|
||||
}
|
||||
|
||||
if ($this->allowAlpha) {
|
||||
$pattern .= '|[a-fA-F0-9]{8}';
|
||||
|
||||
if (!$this->forceFull) {
|
||||
$pattern .= '|[a-fA-F0-9]{4}';
|
||||
}
|
||||
}
|
||||
|
||||
$pattern .= ')$/';
|
||||
|
||||
if (!(bool) preg_match($pattern, $value)) {
|
||||
$fail(trans($this->message()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return 'The hex code is not valid';
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ trait BaseEnum
|
|||
}
|
||||
$baseArray[$index] = $elements[$enum->name];
|
||||
}
|
||||
return [$enum->name => (object) $baseArray];
|
||||
return [$enum->name => (object)$baseArray];
|
||||
})->all());
|
||||
}
|
||||
return self::getCache('enums');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Traits\Models;
|
||||
|
||||
use App\Observers\ModelObserver;
|
||||
use App\Models\ActivityLog as ActivityLogModel;
|
||||
|
||||
/**
|
||||
* If there is a static function in a trait (named boot[TraitName]),
|
||||
|
|
@ -21,16 +20,4 @@ trait ActivityLog
|
|||
{
|
||||
static::observe(ModelObserver::class);
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
* Activities logs HasMany relationship.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function activityLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(ActivityLogModel::class, 'model_id')->with('user');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Traits\Models;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Spatie\Translatable\HasTranslations as BaseHasTranslations;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
trait HasTranslations
|
||||
{
|
||||
|
|
@ -39,7 +39,7 @@ trait HasTranslations
|
|||
$storeAsPrivate = (isset($this->privatePictures) and
|
||||
\is_array($this->privatePictures) and
|
||||
\in_array($key, $this->privatePictures)) ? true : false;
|
||||
$value = FileHelper::storeFile($this, $value, true, null, $storeAsPrivate);
|
||||
$value = FileStorageHelper::storeFile($this, $value, true, null, $storeAsPrivate);
|
||||
}
|
||||
|
||||
// If the attribute is translatable and not already translated, set a
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Traits\Models;
|
||||
|
||||
use App\Enums\InputType;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
trait MiniCrudModel
|
||||
|
|
@ -38,7 +38,7 @@ trait MiniCrudModel
|
|||
switch ($input['inputType']) {
|
||||
case InputType::text->value:
|
||||
$input['text'] = clean($input['text']);
|
||||
$input['text'] = FileHelper::storeModelFilesFromHtml($this, $input['text']);
|
||||
$input['text'] = FileStorageHelper::storeModelFilesFromHtml($this, $input['text']);
|
||||
break;
|
||||
} //end switch
|
||||
} //end foreach
|
||||
|
|
@ -67,7 +67,7 @@ trait MiniCrudModel
|
|||
switch ($input['inputType']) {
|
||||
case InputType::text->value:
|
||||
$original = ToolboxHelper::getOriginal($this, 'data');
|
||||
FileHelper::removeOldModelFilesFromHtml(
|
||||
FileStorageHelper::removeOldModelFilesFromHtml(
|
||||
$input['text'],
|
||||
$original[$index]['text'] ?? ''
|
||||
);
|
||||
|
|
@ -87,7 +87,7 @@ trait MiniCrudModel
|
|||
foreach ($this->data as $input) {
|
||||
switch ($input['inputType']) {
|
||||
case InputType::text->value:
|
||||
FileHelper::removeModelFilesOfHtml($input['text']);
|
||||
FileStorageHelper::removeModelFilesOfHtml($input['text']);
|
||||
break;
|
||||
} //end switch
|
||||
} //end foreach
|
||||
|
|
@ -151,7 +151,7 @@ trait MiniCrudModel
|
|||
$data[$o]['value'] = \preg_replace(
|
||||
'/^\/|^/',
|
||||
'',
|
||||
FileHelper::storeFile($this, $input['value'], true)
|
||||
FileStorageHelper::storeFile($this, $input['value'], true)
|
||||
);
|
||||
break;
|
||||
case InputType::selectEnum->value:
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ trait HasPicture
|
|||
?string $requiredWith = null,
|
||||
?string $requiredWithout = null,
|
||||
): array {
|
||||
$dimensions = (object) [
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
$dimensions = (object)[
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
'minHeight' => $minHeight ?? $this->minHeight,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight
|
||||
];
|
||||
return [
|
||||
|
|
@ -104,10 +104,10 @@ trait HasPicture
|
|||
int $limitMax = 5,
|
||||
bool $nullable = false
|
||||
): array {
|
||||
$sizes = (object) [
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
$sizes = (object)[
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
'minHeight' => $minHeight ?? $this->minHeight,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight,
|
||||
];
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uwil/showroom-bo",
|
||||
"version": "2.3.3",
|
||||
"version": "1.1.0",
|
||||
"type": "project",
|
||||
"description": "Showroom backoffice for Showroom add data management",
|
||||
"support": {
|
||||
|
|
@ -12,33 +12,30 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.5",
|
||||
"24slides/laravel-saml2": "^2.4",
|
||||
"carsdotcom/laravel-json-schema": "^2.3",
|
||||
"diglactic/laravel-breadcrumbs": "^10.0",
|
||||
"php": "^8.2",
|
||||
"diglactic/laravel-breadcrumbs": "^8.0",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"laravel-lang/locales": "^2.0",
|
||||
"laravel/framework": "^12.0",
|
||||
"kaishiyoku/laravel-color-validation": "^2.1",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^4.2",
|
||||
"mews/purifier": "^3.4",
|
||||
"sentry/sentry-laravel": "^4.0",
|
||||
"sentry/sentry-laravel": "^3.8",
|
||||
"sixlive/laravel-json-schema-assertions": "^4.2",
|
||||
"spatie/laravel-translatable": "^6.5",
|
||||
"stancl/tenancy": "^3.9",
|
||||
"uwil/admin-secure-laravel": "^5.0",
|
||||
"uwil/k8s-laravel": "^2.0",
|
||||
"uwil/laravel-common": "^3.2"
|
||||
"uwil/k8s-laravel": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"kitloong/laravel-migrations-generator": "^7.0",
|
||||
"larastan/larastan": "^3.0",
|
||||
"laravel-lang/common": "^6.0",
|
||||
"kitloong/laravel-migrations-generator": "^6.8",
|
||||
"krlove/eloquent-model-generator": "dev-l10-compatibility",
|
||||
"larastan/larastan": "^2.8",
|
||||
"laravel-lang/common": "^4.0",
|
||||
"laravel/pint": "^1.0",
|
||||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"slevomat/coding-standard": "^8.10",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"thedoctor0/laravel-factory-generator": "^1.2"
|
||||
|
|
@ -52,7 +49,10 @@
|
|||
},
|
||||
"repositories": [
|
||||
{
|
||||
"name": "lab.s-web.fr",
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/laravel-shift/eloquent-model-generator.git"
|
||||
},
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://lab.s-web.fr/api/v4/group/23/-/packages/composer/packages.json"
|
||||
}
|
||||
|
|
@ -91,9 +91,6 @@
|
|||
"php-http/discovery": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"audit": {
|
||||
"block-insecure": false
|
||||
},
|
||||
"gitlab-domains": [
|
||||
"lab.s-web.fr"
|
||||
]
|
||||
|
|
|
|||
10859
composer.lock
generated
10859
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -41,7 +41,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'debug' => (bool) env('APP_DEBUG', false),
|
||||
'debug' => (bool)env('APP_DEBUG', false),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
@ -56,6 +56,8 @@ return [
|
|||
|
||||
'url' => env('APP_URL', 'http://localhost'),
|
||||
|
||||
'asset_url' => env('ASSET_URL'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Timezone
|
||||
|
|
@ -80,13 +82,11 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'locale' => 'fr',
|
||||
'locale' => 'fr',
|
||||
'locales' => [
|
||||
'fr',
|
||||
'en',
|
||||
'nl',
|
||||
'es',
|
||||
'de',
|
||||
'nl'
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -63,11 +63,6 @@ return [
|
|||
'throw' => false,
|
||||
],
|
||||
|
||||
'schemas' => [
|
||||
'driver' => 'local',
|
||||
// Must match the 'config.json-schema.local_base_prefix' value.
|
||||
'root' => base_path('schemas'),
|
||||
]
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Base URL
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This is an absolute base URL where schemas will be accessible.
|
||||
|
|
||||
| (URL our local schemas must be relative to)
|
||||
|
|
||||
*/
|
||||
|
||||
'base_url' => 'file://localhost',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Schema base directory
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Where will all local schema files be stored?
|
||||
|
|
||||
*/
|
||||
|
||||
'local_base_prefix' => base_path('schemas/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Schema base directory for tests
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Where will all local schema files be stored?
|
||||
|
|
||||
*/
|
||||
|
||||
'local_base_prefix_tests' => base_path('tests/Schemas/'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Storage disk name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| In order to generate schamas automatically, a storage disk needs to be created
|
||||
| that points to the same directory as `local_base_prefix`. This is the name
|
||||
| of that storage disk.
|
||||
|
|
||||
*/
|
||||
|
||||
'storage_disk_name' => 'schemas',
|
||||
];
|
||||
|
|
@ -96,11 +96,6 @@ return [
|
|||
'name' => env('MAIL_FROM_NAME', 'Example'),
|
||||
],
|
||||
|
||||
'to' => [
|
||||
'address' => env('MAIL_TO_ADDRESS', 'hello@example.com'),
|
||||
'name' => env('MAIL_TO_NAME', 'Example'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Markdown Mail Settings
|
||||
|
|
|
|||
397
config/saml2.php
397
config/saml2.php
|
|
@ -1,397 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Tenant Model
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This will allow you to override the tenant model with your own.
|
||||
|
|
||||
*/
|
||||
|
||||
'tenantModel' => \Slides\Saml2\Models\Tenant::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Use built-in routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If "useRoutes" set to true, the package defines five new routes:
|
||||
|
|
||||
| Method | URI | Name
|
||||
| -------|---------------------------------|------------------
|
||||
| POST | {routesPrefix}/{uuid}/acs | saml.acs
|
||||
| GET | {routesPrefix}/{uuid}/login | saml.login
|
||||
| GET | {routesPrefix}/{uuid}/logout | saml.logout
|
||||
| GET | {routesPrefix}/{uuid}/metadata | saml.metadata
|
||||
| GET | {routesPrefix}/{uuid}/sls | saml.sls
|
||||
|
|
||||
*/
|
||||
|
||||
'useRoutes' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Built-in routes prefix
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may define the prefix for built-in routes.
|
||||
|
|
||||
*/
|
||||
|
||||
'routesPrefix' => '/saml2',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Middle groups to use for the SAML routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Note, Laravel 5.2 requires a group which includes StartSession
|
||||
|
|
||||
*/
|
||||
|
||||
'routesMiddleware' => ['saml'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Signature validation
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to true if you want to use parameters from $_SERVER to validate the signature.
|
||||
|
|
||||
*/
|
||||
|
||||
'retrieveParametersFromServer' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login redirection URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The redirection URL after successful login.
|
||||
|
|
||||
*/
|
||||
|
||||
'loginRoute' => '/bo',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logout redirection URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The redirection URL after successful logout.
|
||||
|
|
||||
*/
|
||||
|
||||
'logoutRoute' => '/bo',
|
||||
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Login error redirection URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The redirection URL after login failing.
|
||||
|
|
||||
*/
|
||||
|
||||
'errorRoute' => '/bo/login-error',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Strict mode.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If 'strict' is True, then the PHP Toolkit will reject unsigned
|
||||
| or unencrypted messages if it expects them signed or encrypted
|
||||
| Also will reject the messages if not strictly follow the SAML
|
||||
| standard: Destination, NameId, Conditions... are validated too.
|
||||
|
|
||||
*/
|
||||
|
||||
'strict' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Debug mode.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When enabled, errors must be printed.
|
||||
|
|
||||
*/
|
||||
|
||||
'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Whether to use `X-Forwarded-*` headers to determine port/domain/protocol.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If 'proxyVars' is True, then the Saml lib will trust proxy headers
|
||||
| e.g X-Forwarded-Proto / HTTP_X_FORWARDED_PROTO. This is useful if
|
||||
| your application is running behind a load balancer which terminates SSL.
|
||||
|
|
||||
*/
|
||||
|
||||
'proxyVars' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Service Provider configuration.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| General setting of the service provider.
|
||||
|
|
||||
*/
|
||||
|
||||
'sp' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| NameID format.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specifies constraints on the name identifier to be used to
|
||||
| represent the requested subject.
|
||||
|
|
||||
*/
|
||||
|
||||
'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| SP Certificates.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Usually x509cert and privateKey of the SP are provided by files placed at
|
||||
| the certs folder. But we can also provide them with the following parameters.
|
||||
|
|
||||
*/
|
||||
|
||||
'x509cert' => env('SAML2_SP_CERT_x509', ''),
|
||||
'privateKey' => env('SAML2_SP_CERT_PRIVATEKEY', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Identifier (URI) of the SP entity.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Leave blank to use the 'saml.metadata' route.
|
||||
|
|
||||
*/
|
||||
|
||||
'entityId' => env('SAML2_SP_ENTITYID', ''),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| The Assertion Consumer Service (ACS) URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| URL Location where the <Response> from the IdP will be returned, using HTTP-POST binding.
|
||||
| Leave blank to use the 'saml.acs' route.
|
||||
|
|
||||
*/
|
||||
|
||||
'assertionConsumerService' => [
|
||||
'url' => '',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| The Single Logout Service URL.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specifies info about where and how the <Logout Response> message MUST be
|
||||
| returned to the requester, in this case our SP.
|
||||
|
|
||||
| URL Location where the <Response> from the IdP will be returned, using HTTP-Redirect binding.
|
||||
| Leave blank to use the 'saml.sls' route.
|
||||
|
|
||||
*/
|
||||
|
||||
'singleLogoutService' => [
|
||||
'url' => ''
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| OneLogin security settings.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
|
|
||||
|
|
||||
*/
|
||||
|
||||
'security' => [
|
||||
|
||||
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
|
||||
'digestAlgorithm' => 'http://www.w3.org/2001/04/xmlenc#sha256',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| NameId encryption
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
|
||||
| will be encrypted.
|
||||
|
|
||||
*/
|
||||
|
||||
'nameIdEncrypted' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| AuthnRequest signage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates whether the <samlp:AuthnRequest> messages sent by
|
||||
| this SP will be signed. The Metadata of the SP will offer this info
|
||||
|
|
||||
*/
|
||||
|
||||
'authnRequestsSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logout request signage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates whether the <samlp:logoutRequest> messages sent by this SP
|
||||
| will be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'logoutRequestSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logout response signage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates whether the <samlp:logoutResponse> messages sent by this SP
|
||||
| will be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'logoutResponseSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Whether need to sign metadata.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The possible values:
|
||||
| - false
|
||||
| - true (use certs)
|
||||
| - array:
|
||||
| ```
|
||||
| [
|
||||
| 'keyFileName' => 'metadata.key',
|
||||
| 'certFileName' => 'metadata.crt'
|
||||
| ]
|
||||
| ```
|
||||
|
|
||||
*/
|
||||
|
||||
'signMetadata' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Requirement to sign messages.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
|
||||
| <samlp:LogoutResponse> elements received by this SP to be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'wantMessagesSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Requirement to sign assertion elements.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates a requirement for the <saml:Assertion> elements received by
|
||||
| this SP to be signed.
|
||||
|
|
||||
*/
|
||||
|
||||
'wantAssertionsSigned' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Requirement to encrypt NameID.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Indicates a requirement for the NameID received by this SP to be encrypted.
|
||||
|
|
||||
*/
|
||||
|
||||
'wantNameIdEncrypted' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication context.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Set to false and no AuthContext will be sent in the AuthNRequest,
|
||||
|
|
||||
| Set true or don't present this parameter and you will get an
|
||||
| AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
|
||||
|
|
||||
| Set an array with the possible auth context values:
|
||||
| ['urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509']
|
||||
|
|
||||
*/
|
||||
|
||||
'requestedAuthnContext' => true,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Contact information.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| It is recommended to supply a technical and support contacts.
|
||||
|
|
||||
*/
|
||||
|
||||
'contactPerson' => [
|
||||
'technical' => [
|
||||
'givenName' => env('SAML2_CONTACT_TECHNICAL_NAME', env('APP_NAME', 'Technical')),
|
||||
'emailAddress' => env('SAML2_CONTACT_TECHNICAL_EMAIL', 'no@reply.com')
|
||||
],
|
||||
'support' => [
|
||||
'givenName' => env('SAML2_CONTACT_SUPPORT_NAME', env('APP_NAME', 'Support')),
|
||||
'emailAddress' => env('SAML2_CONTACT_SUPPORT_EMAIL', 'no@reply.com')
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Organization information.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The info in en_US lang is recommended, add more if required.
|
||||
|
|
||||
*/
|
||||
|
||||
'organization' => [
|
||||
'en-US' => [
|
||||
'name' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
|
||||
'displayname' => env('SAML2_ORGANIZATION_NAME', env('APP_NAME', 'Name')),
|
||||
'url' => env('SAML2_ORGANIZATION_URL', env('APP_URL', 'http://url'))
|
||||
],
|
||||
],
|
||||
|
||||
/**
|
||||
* Default tenant UUID
|
||||
*/
|
||||
|
||||
'default_tenant_uuid' => env('SAML2_DEFAULT_TENANT_UUID', null)
|
||||
|
||||
];
|
||||
|
|
@ -55,7 +55,7 @@ return [
|
|||
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
|
||||
|
||||
// @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate.
|
||||
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_TRACES_SAMPLE_RATE'),
|
||||
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float)env('SENTRY_TRACES_SAMPLE_RATE'),
|
||||
|
||||
'before_send' => [\Uwil\LaravelCommon\Helpers\ExceptionsHelper::class, 'sentryEventHandler'],
|
||||
'before_send' => [\App\Lib\Helpers\ExceptionsHelper::class, 'sentryEventHandler'],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,191 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Stancl\Tenancy\Database\Models\Domain;
|
||||
|
||||
return [
|
||||
'tenant_model' => \App\Models\Tenant::class,
|
||||
'id_generator' => Stancl\Tenancy\UUIDGenerator::class,
|
||||
|
||||
'domain_model' => Domain::class,
|
||||
|
||||
/**
|
||||
* The list of domains hosting your central app.
|
||||
*
|
||||
* Only relevant if you're using the domain or subdomain identification middleware.
|
||||
*/
|
||||
'central_domains' => [],
|
||||
|
||||
/**
|
||||
* Tenancy bootstrappers are executed when tenancy is initialized.
|
||||
* Their responsibility is making Laravel features tenant-aware.
|
||||
*
|
||||
* To configure their behavior, see the config keys below.
|
||||
*/
|
||||
'bootstrappers' => [
|
||||
Stancl\Tenancy\Bootstrappers\CacheTenancyBootstrapper::class,
|
||||
Stancl\Tenancy\Bootstrappers\FilesystemTenancyBootstrapper::class,
|
||||
Stancl\Tenancy\Bootstrappers\QueueTenancyBootstrapper::class,
|
||||
],
|
||||
|
||||
/**
|
||||
* Database tenancy config. Used by DatabaseTenancyBootstrapper.
|
||||
*/
|
||||
'database' => [
|
||||
'central_connection' => env('DB_CONNECTION', 'mysql'),
|
||||
|
||||
/**
|
||||
* Connection used as a "template" for the dynamically created tenant database connection.
|
||||
* Note: don't name your template connection tenant. That name is reserved by package.
|
||||
*/
|
||||
'template_tenant_connection' => null,
|
||||
|
||||
/**
|
||||
* Tenant database names are created like this:
|
||||
* prefix + tenant_id + suffix.
|
||||
*/
|
||||
'prefix' => 'tenant',
|
||||
'suffix' => '',
|
||||
|
||||
/**
|
||||
* TenantDatabaseManagers are classes that handle the creation & deletion of tenant databases.
|
||||
*/
|
||||
'managers' => [
|
||||
'sqlite' => Stancl\Tenancy\TenantDatabaseManagers\SQLiteDatabaseManager::class,
|
||||
'mysql' => Stancl\Tenancy\TenantDatabaseManagers\MySQLDatabaseManager::class,
|
||||
'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLDatabaseManager::class,
|
||||
],
|
||||
],
|
||||
|
||||
/**
|
||||
* Cache tenancy config. Used by CacheTenancyBootstrapper.
|
||||
*
|
||||
* This works for all Cache facade calls, cache() helper
|
||||
* calls and direct calls to injected cache stores.
|
||||
*
|
||||
* Each key in cache will have a tag applied on it. This tag is used to
|
||||
* scope the cache both when writing to it and when reading from it.
|
||||
*
|
||||
* You can clear cache selectively by specifying the tag.
|
||||
*/
|
||||
'cache' => [
|
||||
// This tag_base, followed by the tenant_id, will form a tag that will be applied on each cache call.
|
||||
'tag_base' => 'tenant',
|
||||
],
|
||||
|
||||
/**
|
||||
* Filesystem tenancy config. Used by FilesystemTenancyBootstrapper.
|
||||
* https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers/#filesystem-tenancy-boostrapper.
|
||||
*/
|
||||
'filesystem' => [
|
||||
/**
|
||||
* Each disk listed in the 'disks' array will be suffixed by the suffix_base, followed by the tenant_id.
|
||||
*/
|
||||
'suffix_base' => 'tenant',
|
||||
'disks' => [
|
||||
'local',
|
||||
'public',
|
||||
// 's3',
|
||||
],
|
||||
|
||||
/**
|
||||
* Use this for local disks.
|
||||
*
|
||||
* See https://tenancyforlaravel.com/docs/v3/tenancy-bootstrappers/#filesystem-tenancy-boostrapper
|
||||
*/
|
||||
'root_override' => [
|
||||
// Disks whose roots should be overridden after storage_path() is suffixed.
|
||||
'local' => '%storage_path%/app/',
|
||||
'public' => '%storage_path%/app/public/',
|
||||
],
|
||||
|
||||
/**
|
||||
* Should storage_path() be suffixed.
|
||||
*
|
||||
* Note: Disabling this will likely break local disk tenancy.
|
||||
* Only disable this if you're using an external file storage service like S3.
|
||||
*
|
||||
* For the vast majority of applications, this feature should be enabled. But in some
|
||||
* edge cases, it can cause issues (like using Passport with Vapor - see #196), so
|
||||
* you may want to disable this if you are experiencing these edge case issues.
|
||||
*/
|
||||
'suffix_storage_path' => true,
|
||||
|
||||
/**
|
||||
* By default, asset() calls are made multi-tenant too. You can use global_asset() and mix()
|
||||
* for global, non-tenant-specific assets. However, you might have some issues when using
|
||||
* packages that use asset() calls inside the tenant app. To avoid such issues, you can
|
||||
* disable asset() helper tenancy and explicitly use tenant_asset() calls in places
|
||||
* where you want to use tenant-specific assets (product images, avatars, etc).
|
||||
*/
|
||||
'asset_helper_tenancy' => true,
|
||||
],
|
||||
|
||||
/**
|
||||
* Redis tenancy config. Used by RedisTenancyBootstrapper.
|
||||
*
|
||||
* Note: You need phpredis to use Redis tenancy.
|
||||
*
|
||||
* Note: You don't need to use this if you're using Redis only for cache.
|
||||
* Redis tenancy is only relevant if you're making direct Redis calls,
|
||||
* either using the Redis facade or by injecting it as a dependency.
|
||||
*/
|
||||
'redis' => [
|
||||
// Each key in Redis will be prepended by this prefix_base, followed by the tenant id.
|
||||
'prefix_base' => 'tenant',
|
||||
// Redis connections whose keys are prefixed, to separate one tenant's keys from another.
|
||||
'prefixed_connections' => [
|
||||
// 'default',
|
||||
],
|
||||
],
|
||||
|
||||
/**
|
||||
* Features are classes that provide additional functionality
|
||||
* not needed for tenancy to be bootstrapped. They are run
|
||||
* regardless of whether tenancy has been initialized.
|
||||
*
|
||||
* See the documentation page for each class to
|
||||
* understand which ones you want to enable.
|
||||
*/
|
||||
'features' => [
|
||||
//phpcs:disable
|
||||
// Stancl\Tenancy\Features\UserImpersonation::class,
|
||||
// Stancl\Tenancy\Features\TelescopeTags::class,
|
||||
// Stancl\Tenancy\Features\UniversalRoutes::class,
|
||||
// Stancl\Tenancy\Features\TenantConfig::class, // https://tenancyforlaravel.com/docs/v3/features/tenant-config
|
||||
// Stancl\Tenancy\Features\CrossDomainRedirect::class, // https://tenancyforlaravel.com/docs/v3/features/cross-domain-redirect
|
||||
// Stancl\Tenancy\Features\ViteBundler::class,
|
||||
//phpcs:enable
|
||||
],
|
||||
|
||||
/**
|
||||
* Should tenancy routes be registered.
|
||||
*
|
||||
* Tenancy routes include tenant asset routes. By default, this route is
|
||||
* enabled. But it may be useful to disable them if you use external
|
||||
* storage (e.g. S3 / Dropbox) or have a custom asset controller.
|
||||
*/
|
||||
'routes' => true,
|
||||
|
||||
/**
|
||||
* Parameters used by the tenants:migrate command.
|
||||
*/
|
||||
'migration_parameters' => [
|
||||
// This needs to be true to run migrations in production.
|
||||
'--force' => true,
|
||||
'--path' => [database_path('migrations/tenant')],
|
||||
'--realpath' => true,
|
||||
],
|
||||
|
||||
/**
|
||||
* Parameters used by the tenants:seed command.
|
||||
*/
|
||||
'seeder_parameters' => [
|
||||
// Root seeder class.
|
||||
'--class' => 'DatabaseSeeder',
|
||||
// This needs to be true to seed tenant databases in production.
|
||||
// phpcs:ignore
|
||||
// '--force' => true,
|
||||
],
|
||||
];
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -18,10 +17,10 @@ class BrandFactory extends Factory
|
|||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'brand ' . fake()->unique()->safeColorName(),
|
||||
'color' => fake()->unique()->hexColor(),
|
||||
'status' => fake()->boolean(75),
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
'name' => 'brand ' . fake()->unique()->safeColorName(),
|
||||
'color' => fake()->unique()->hexColor(),
|
||||
'shelf' => fake()->boolean(75),
|
||||
'status' => fake()->boolean(75)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -17,8 +16,6 @@ class FontFactory extends Factory
|
|||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -20,8 +19,7 @@ class ProductFactory extends Factory
|
|||
return [
|
||||
'name' => 'product ' . fake()->unique()->words(3, true),
|
||||
'description' => '<strong>TEXT</strong>' . fake()->realText(),
|
||||
'status' => fake()->boolean(75),
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
'status' => fake()->boolean(75)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -25,12 +24,12 @@ class RangeFactory extends Factory
|
|||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'gamme ' . fake()->unique()->streetName(),
|
||||
'color' => fake()->unique()->hexColor(),
|
||||
'width' => fake()->numberBetween(1, 2),
|
||||
'order' => self::$order++,
|
||||
'status' => fake()->boolean(75),
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
'name' => 'gamme ' . fake()->unique()->streetName(),
|
||||
'color' => fake()->unique()->hexColor(),
|
||||
'width' => fake()->numberBetween(1, 2),
|
||||
'order' => self::$order++,
|
||||
'shelf' => fake()->boolean(75),
|
||||
'status' => fake()->boolean(75)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -22,7 +21,6 @@ class SettingsFactory extends Factory
|
|||
'footer_phrase' => fake()->unique()->realText(38),
|
||||
'color' => fake()->unique()->hexColor(),
|
||||
'home_timeout' => \random_int(20, 100),
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class UserFactory extends Factory
|
|||
'first_name' => fake()->firstname(),
|
||||
'last_name' => fake()->lastname(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
|
||||
'remember_token' => Str::random(10),
|
||||
'role' => \collect(RoleEnum::toArray())->random()->value,
|
||||
'published' => $published,
|
||||
|
|
@ -35,7 +37,7 @@ class UserFactory extends Factory
|
|||
public function unverified(): static
|
||||
{
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
return $this->state(fn(array $attributes) => [
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTenantsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('tenants', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
|
||||
$table->string('name')->comment('Human-readable name of the tenant.');
|
||||
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('tenants');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('email_verified_at');
|
||||
$table->dropColumn('password');
|
||||
$table->date('last_connection')->nullable()->after('role');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||
$table->string('password')->after('email_verified_at');
|
||||
$table->dropColumn('last_connection');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$tables = [
|
||||
['name' => 'activity_logs', 'after' => 'data', 'nullable' => true],
|
||||
['name' => 'brands', 'after' => 'status', 'nullable' => false],
|
||||
['name' => 'fonts', 'after' => 'font', 'nullable' => false],
|
||||
['name' => 'products', 'after' => 'range_id', 'nullable' => false],
|
||||
['name' => 'ranges', 'after' => 'font_id', 'nullable' => false],
|
||||
['name' => 'settings', 'after' => 'home_timeout', 'nullable' => false],
|
||||
];
|
||||
|
||||
foreach ($tables as $entry) {
|
||||
$tableName = $entry['name'];
|
||||
$after = $entry['after'];
|
||||
$nullable = $entry['nullable'];
|
||||
|
||||
Schema::table($tableName, function (Blueprint $table) use ($after, $nullable) {
|
||||
$table->string('tenant_id')
|
||||
->nullable($nullable)
|
||||
->index()
|
||||
->after($after)
|
||||
->comment('Tenant reference.');
|
||||
});
|
||||
|
||||
Schema::table($tableName, function (Blueprint $table) use ($tableName) {
|
||||
$table->foreign('tenant_id', $tableName . '_tenant_id_foreign')
|
||||
->references('id')
|
||||
->on('tenants')
|
||||
->onUpdate('cascade')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
} //end foreach
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$tables = [
|
||||
'activity_logs',
|
||||
'brands',
|
||||
'fonts',
|
||||
'products',
|
||||
'ranges',
|
||||
'settings',
|
||||
];
|
||||
|
||||
foreach ($tables as $entry) {
|
||||
Schema::table($entry, function (Blueprint $table) {
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
}
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropUnique(['tenant_id', 'email']);
|
||||
$table->unique('email');
|
||||
$table->dropColumn('tenant_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tenant_user', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->string('tenant_id', 255);
|
||||
|
||||
$table->primary(['user_id', 'tenant_id']);
|
||||
|
||||
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tenant_user');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('brands', function (Blueprint $table) {
|
||||
$table->dropColumn('shelf');
|
||||
});
|
||||
Schema::table('ranges', function (Blueprint $table) {
|
||||
$table->dropColumn('shelf');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('brands', function (Blueprint $table) {
|
||||
$table->boolean('shelf')->comment('Les enfant sont rendus sur un étagere.');
|
||||
});
|
||||
Schema::table('ranges', function (Blueprint $table) {
|
||||
$table->boolean('shelf')->comment('Les enfant sont rendus sur un étagere.');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->text('locales')->comment('Selected locales used in the application.')->after('id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('settings', function (Blueprint $table) {
|
||||
$table->dropColumn('locales');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
namespace Database\Seeders\Development;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class BrandSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -24,12 +24,11 @@ class BrandSeeder extends Seeder
|
|||
File::deleteDirectory(\storage_path('app/public/modelfiles/' . (new Brand())->getTable()));
|
||||
|
||||
Brand::factory(fake()->randomDigitNot([0, 1, 2, 3]))->make()->each(function (Brand $brand) {
|
||||
$brand->picture = FileHelper::storeFile(
|
||||
$brand->picture = FileStorageHelper::storeFile(
|
||||
new Brand(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/brands/default-picture-600x350.jpg')
|
||||
),
|
||||
prefix: $brand->tenant_id
|
||||
)
|
||||
);
|
||||
$brand->save();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace Database\Seeders\Development;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Font;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class FontSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -21,18 +21,15 @@ class FontSeeder extends Seeder
|
|||
|
||||
collect(['Kanit-Regular.woff2', 'RubikBurned-Regular.woff2'])->each(function (string $font_filename) {
|
||||
$font_basename = Str::of($font_filename)->explode('.')->get(0);
|
||||
$font = Font::factory(1)->makeOne();
|
||||
$font->fill([
|
||||
Font::factory(1)->makeOne([
|
||||
'font_name' => Str::slug($font_basename),
|
||||
'font' => FileHelper::storeFile(
|
||||
'font' => FileStorageHelper::storeFile(
|
||||
new Font(),
|
||||
new \SplFileInfo(
|
||||
\resource_path("../database/factories/assets/fonts/{$font_filename}")
|
||||
),
|
||||
prefix: $font->tenant_id
|
||||
)
|
||||
),
|
||||
]);
|
||||
$font->saveOrFail();
|
||||
])->saveOrFail();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace Database\Seeders\Development;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class ProductSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -30,12 +30,11 @@ class ProductSeeder extends Seeder
|
|||
foreach ($rangeList as $rangeModel) {
|
||||
Product::factory($hasDoneOne === 0 ? 1 : \random_int(6, 10))->make()
|
||||
->each(function (Product $product) use ($rangeModel) {
|
||||
$product->picture = FileHelper::storeFile(
|
||||
$product->picture = FileStorageHelper::storeFile(
|
||||
new Product(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/products/default-picture-800x800.jpg')
|
||||
),
|
||||
prefix: $product->tenant_id
|
||||
)
|
||||
);
|
||||
$product->range()->associate($rangeModel);
|
||||
$product->save();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Database\Seeders\Development;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Font;
|
||||
use App\Models\Range;
|
||||
|
|
@ -9,7 +10,6 @@ use Illuminate\Database\Seeder;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class RangeSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -28,23 +28,21 @@ class RangeSeeder extends Seeder
|
|||
$fonts = Font::all();
|
||||
|
||||
Brand::all()->each(
|
||||
fn(Brand $brand) =>
|
||||
fn (Brand $brand) =>
|
||||
Range::factory(\random_int(6, 16))->make()->each(function (Range $range) use ($brand, $fonts) {
|
||||
if ($range->width === 1) {
|
||||
$range->picture = FileHelper::storeFile(
|
||||
$range->picture = FileStorageHelper::storeFile(
|
||||
new Range(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/ranges/default-picture-600x500.jpg')
|
||||
),
|
||||
prefix: $range->tenant_id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$range->picture = FileHelper::storeFile(
|
||||
$range->picture = FileStorageHelper::storeFile(
|
||||
new Range(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/ranges/default-picture-1300x500.jpg')
|
||||
),
|
||||
prefix: $range->tenant_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace Database\Seeders\Development\Realistic;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class BrandSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -17,16 +17,16 @@ class BrandSeeder extends Seeder
|
|||
{
|
||||
$brand = Brand::factory()->makeOne();
|
||||
$brand->fill([
|
||||
'name' => 'Los Pollos Hermanos',
|
||||
'color' => '#FFFFFF',
|
||||
'name' => 'Los Pollos Hermanos',
|
||||
'color' => '#FFFFFF',
|
||||
'shelf' => true,
|
||||
'status' => true,
|
||||
]);
|
||||
$brand->picture = FileHelper::storeFile(
|
||||
$brand->picture = FileStorageHelper::storeFile(
|
||||
new Brand(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/brands/los_pollos_hermanos.png')
|
||||
),
|
||||
prefix: $brand->tenant_id
|
||||
)
|
||||
);
|
||||
$brand->saveOrFail();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace Database\Seeders\Development\Realistic;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Font;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class FontSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -20,18 +20,15 @@ class FontSeeder extends Seeder
|
|||
collect(['Montserrat-Regular.woff2'])->each(function (string $font_filename) {
|
||||
File::delete(\storage_path('app/public/modelfiles/' . (new Font())->getTable() . "/{$font_filename}"));
|
||||
$font_basename = Str::of($font_filename)->explode('.')->get(0);
|
||||
$font = Font::factory(1)->makeOne();
|
||||
$font->fill([
|
||||
Font::factory(1)->makeOne([
|
||||
'font_name' => Str::slug($font_basename),
|
||||
'font' => FileHelper::storeFile(
|
||||
'font' => FileStorageHelper::storeFile(
|
||||
new Font(),
|
||||
new \SplFileInfo(
|
||||
\resource_path("../database/factories/assets/fonts/{$font_filename}")
|
||||
),
|
||||
prefix: $font->tenant_id
|
||||
)
|
||||
),
|
||||
]);
|
||||
$font->saveOrFail();
|
||||
])->saveOrFail();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace Database\Seeders\Development\Realistic;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class ProductSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -18,46 +18,35 @@ class ProductSeeder extends Seeder
|
|||
public function run(): void
|
||||
{
|
||||
$productsNameList = collect([
|
||||
'Basic',
|
||||
'Westsite',
|
||||
'New Mexico',
|
||||
'Albuquerque',
|
||||
'South Valley',
|
||||
'Taos',
|
||||
'Rio Grande',
|
||||
'Supreme',
|
||||
'Sante Fe',
|
||||
'Vegetarian'
|
||||
'Basic', 'Westsite', 'New Mexico', 'Albuquerque', 'South Valley',
|
||||
'Taos', 'Rio Grande', 'Supreme', 'Sante Fe', 'Vegetarian'
|
||||
]);
|
||||
|
||||
$brand = Brand::query()->whereRaw("JSON_EXTRACT(name, '$.fr') = 'Los Pollos Hermanos'")->firstOrFail();
|
||||
$brand->ranges->each(
|
||||
// @phpstan-ignore-next-line
|
||||
fn(Range $range) => Product::factory($productsNameList->count())->make()
|
||||
fn (Range $range) => Product::factory($productsNameList->count())->make()
|
||||
->each(function (Product $product, int $index) use ($range, $productsNameList) {
|
||||
$product->name = $productsNameList->pull($index);
|
||||
$product->setTranslations('name', [
|
||||
'fr' => 'FR ' . $product->name,
|
||||
'nl' => 'NL ' . $product->name,
|
||||
'en' => 'EN ' . $product->name,
|
||||
'es' => 'ES ' . $product->name,
|
||||
'de' => 'DE ' . $product->name,
|
||||
$product->setRawAttributes([
|
||||
'name' => \json_encode([
|
||||
'fr' => 'FR ' . $product->name,
|
||||
'nl' => 'NL ' . $product->name,
|
||||
'en' => 'EN ' . $product->name,
|
||||
]),
|
||||
'description' => \json_encode([
|
||||
'fr' => 'FR ' . $product->description,
|
||||
'nl' => 'NL ' . $product->description,
|
||||
'en' => 'EN ' . $product->description,
|
||||
])
|
||||
]);
|
||||
$product->setTranslations('description', [
|
||||
'fr' => 'FR ' . $product->description,
|
||||
'nl' => 'NL ' . $product->description,
|
||||
'en' => 'EN ' . $product->description,
|
||||
'es' => 'ES ' . $product->description,
|
||||
'de' => 'DE ' . $product->description,
|
||||
]);
|
||||
$product->picture = FileHelper::storeFile(
|
||||
$product->picture = FileStorageHelper::storeFile(
|
||||
new Product(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/products/default-picture-800x800.jpg')
|
||||
),
|
||||
prefix: $product->tenant_id
|
||||
)
|
||||
);
|
||||
$product->status = true;
|
||||
$product->status = true;
|
||||
$product->range()->associate($range);
|
||||
$product->save();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
namespace Database\Seeders\Development\Realistic;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Font;
|
||||
use App\Models\Range;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class RangeSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -22,24 +22,24 @@ class RangeSeeder extends Seeder
|
|||
$brand = Brand::query()->whereRaw("JSON_EXTRACT(name, '$.fr') = 'Los Pollos Hermanos'")->firstOrFail();
|
||||
$font = Font::query()->where('font_name', Str::slug('Montserrat-Regular'))->firstOrFail();
|
||||
$range = Range::factory()->makeOne();
|
||||
$range->setTranslations('name', [
|
||||
'fr' => 'Burritos',
|
||||
'nl' => 'Burrito\'s',
|
||||
'en' => 'Burritos',
|
||||
'es' => 'Burritos',
|
||||
'de' => 'Burritos',
|
||||
$range->setRawAttributes([
|
||||
'name' => \json_encode([
|
||||
'fr' => 'Burritos',
|
||||
'nl' => 'Burrito\'s',
|
||||
'en' => 'Burritos',
|
||||
])
|
||||
]);
|
||||
$range->fill([
|
||||
'picture' => FileHelper::storeFile(
|
||||
'picture' => FileStorageHelper::storeFile(
|
||||
new Range(),
|
||||
new \SplFileInfo(
|
||||
\resource_path("../database/factories/assets/ranges/burittos.jpg")
|
||||
),
|
||||
prefix: $range->tenant_id
|
||||
)
|
||||
),
|
||||
'color' => '#311e91',
|
||||
'width' => 1,
|
||||
'status' => true
|
||||
'color' => '#311e91',
|
||||
'width' => 1,
|
||||
'shelf' => true,
|
||||
'status' => true
|
||||
]);
|
||||
$range->brand()->associate($brand);
|
||||
$range->font()->associate($font);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace Database\Seeders\Mandatory;
|
||||
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Models\Settings;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
class SettingsSeeder extends Seeder
|
||||
{
|
||||
|
|
@ -25,12 +25,11 @@ class SettingsSeeder extends Seeder
|
|||
File::deleteDirectory(\storage_path('app/public/modelfiles/' . (new Settings())->getTable()));
|
||||
|
||||
Settings::factory(1)->make()->each(function (Settings $settings) {
|
||||
$settings->logo = FileHelper::storeFile(
|
||||
$settings->logo = FileStorageHelper::storeFile(
|
||||
new Settings(),
|
||||
new \SplFileInfo(
|
||||
\resource_path('../database/factories/assets/settings/default-picture-200x200.jpg')
|
||||
),
|
||||
prefix: $settings->tenant_id
|
||||
)
|
||||
);
|
||||
$attributes = $settings->getAttributes();
|
||||
$settings->setRawAttributes([
|
||||
|
|
@ -38,15 +37,11 @@ class SettingsSeeder extends Seeder
|
|||
'fr' => 'FR ' . $settings->sub_title,
|
||||
'nl' => 'NL ' . $settings->sub_title,
|
||||
'en' => 'EN ' . $settings->sub_title,
|
||||
'es' => 'ES ' . $settings->sub_title,
|
||||
'de' => 'DE ' . $settings->sub_title,
|
||||
]),
|
||||
'footer_phrase' => \json_encode([
|
||||
'fr' => 'FR ' . $settings->footer_phrase,
|
||||
'nl' => 'NL ' . $settings->footer_phrase,
|
||||
'en' => 'EN ' . $settings->footer_phrase,
|
||||
'es' => 'ES ' . $settings->footer_phrase,
|
||||
'de' => 'DE ' . $settings->footer_phrase,
|
||||
])
|
||||
]);
|
||||
$settings->fill(Arr::except($attributes, ['sub_title', 'footer_phrase']));
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders\Mandatory;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class TenantSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
if (!Tenant::count()) {
|
||||
Tenant::create(['name' => 'Tenant 1']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ namespace Database\Seeders;
|
|||
|
||||
use Database\Seeders\Mandatory\FontSeeder;
|
||||
use Database\Seeders\Mandatory\SettingsSeeder;
|
||||
use Database\Seeders\Mandatory\TenantSeeder;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class MandatorySeeder extends Seeder
|
||||
|
|
@ -17,7 +16,6 @@ class MandatorySeeder extends Seeder
|
|||
public function run(): void
|
||||
{
|
||||
$this->call([
|
||||
TenantSeeder::class,
|
||||
SettingsSeeder::class,
|
||||
FontSeeder::class
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# From https://stackoverflow.com/a/76488746/17320077
|
||||
|
||||
work=true
|
||||
|
||||
function stopQueues() {
|
||||
echo "Stopping queues..."
|
||||
work=false
|
||||
php artisan queue:restart
|
||||
sleep 45s
|
||||
exit 0
|
||||
}
|
||||
|
||||
# handle graceful shutdown
|
||||
trap "" SIGPIPE
|
||||
trap stopQueues EXIT
|
||||
|
||||
echo "Starting PHP queue workers..."
|
||||
|
||||
while $work; do
|
||||
php artisan queue:work database --queue=default,database --sleep=3 --tries=3 --max-time=3600 --no-interaction
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Si besoin de plusieurs threads puis terminer par un done sans &
|
||||
#(while $work; do
|
||||
# php artisan queue:work --queue="$(grep -oP 'SQS_QUEUE=\K.*' .env)" --no-interaction
|
||||
# sleep 1
|
||||
#done &)
|
||||
104
manifest.yaml
104
manifest.yaml
|
|
@ -1,3 +1,4 @@
|
|||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
|
|
@ -9,8 +10,7 @@ data:
|
|||
APP_DEBUG: "${APP_APP_DEBUG}"
|
||||
APP_URL: "https://${APP_DOMAIN}"
|
||||
LOG_CHANNEL: "k8s"
|
||||
DB_HOST: "${APP_GLOBAL_OVH_MYSQL_HOST}"
|
||||
DB_PORT: "${APP_GLOBAL_OVH_MYSQL_PORT}"
|
||||
DB_HOST: "mariadb-service.common-services"
|
||||
DB_DATABASE: "${APP_DB_DATABASE}"
|
||||
DB_USERNAME: "${APP_DB_USERNAME}"
|
||||
DB_PASSWORD: "${APP_DB_PASSWORD}"
|
||||
|
|
@ -21,17 +21,24 @@ data:
|
|||
MAIL_ENCRYPTION: "${APP_MAIL_ENCRYPTION}"
|
||||
MAIL_FROM_ADDRESS: "${APP_MAIL_FROM_ADDRESS}"
|
||||
MAIL_FROM_NAME: "${APP_APP_NAME}"
|
||||
MAIL_TO_ADDRESS: "${APP_MAIL_TO_ADDRESS}"
|
||||
MAIL_TO_NAME: "${APP_MAIL_TO_NAME}"
|
||||
SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}"
|
||||
CRM_ENDPOINT: "${APP_CRM_ENDPOINT}"
|
||||
CRM_TOKEN: "${APP_CRM_TOKEN}"
|
||||
CRM_PASSWORD: "${APP_CRM_PASSWORD}"
|
||||
MESBR_REF_SITE: "${APP_MESBR_REF_SITE}"
|
||||
MESBR_REF_CAMP: "${APP_MESBR_REF_CAMP}"
|
||||
MESBR_ENDPOINT: "${APP_MESBR_ENDPOINT}"
|
||||
LDC_WEB_API_KEY: "${APP_LDC_WEB_API_KEY}"
|
||||
SAML2_SP_CERT_x509: "${APP_SAML2_SP_CERT_x509}"
|
||||
SAML2_SP_CERT_PRIVATEKEY: "${APP_SAML2_SP_CERT_PRIVATEKEY}"
|
||||
SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}"
|
||||
SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}"
|
||||
SAML2_DEFAULT_TENANT_UUID: "${APP_SAML2_DEFAULT_TENANT_UUID}"
|
||||
CACHE_DRIVER: "${APP_CACHE_DRIVER}"
|
||||
UWIL_ADMIN_SECURE_CIDR_LIST: "${APP_GLOBAL_UWIL_ADMIN_SECURE_CIDR_LIST}"
|
||||
GOOGLE_RECAPTCHA_PRIVATE: "${APP_GOOGLE_RECAPTCHA_PRIVATE}"
|
||||
GOOGLE_RECAPTCHA_PUBLIC: "${APP_GOOGLE_RECAPTCHA_PUBLIC}"
|
||||
GOOGLE_RECAPTCHA_VERSION: "${APP_GOOGLE_RECAPTCHA_VERSION}"
|
||||
SENTRY_LARAVEL_DSN: "${APP_SENTRY_LARAVEL_DSN}"
|
||||
MATOMO_SITE_ID: "${APP_MATOMO_SITE_ID}"
|
||||
MAIL_CONTACT_RECLAIM: "${APP_MAIL_CONTACT_RECLAIM}"
|
||||
MAIL_CONTACT_INFO: "${APP_MAIL_CONTACT_INFO}"
|
||||
MAIL_CONTACT_ASSOCIATIVE: "${APP_MAIL_CONTACT_ASSOCIATIVE}"
|
||||
MAIL_CONTACT_PARTNERSHIP: "${APP_MAIL_CONTACT_PARTNERSHIP}"
|
||||
MAIL_CONTACT_COMMERCIAL: "${APP_MAIL_CONTACT_COMMERCIAL}"
|
||||
MAIL_CONTACT_VARIOUS: "${APP_MAIL_CONTACT_VARIOUS}"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
|
@ -45,6 +52,7 @@ spec:
|
|||
resources:
|
||||
requests:
|
||||
storage: $APP_PVC_SIZE
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
|
|
@ -63,41 +71,6 @@ spec:
|
|||
containers:
|
||||
- name: ${CI_PROJECT_PATH_SLUG}-container
|
||||
image: $IMAGE_TAG
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ${CI_PROJECT_PATH_SLUG}-config-map
|
||||
resources:
|
||||
limits:
|
||||
cpu: "${APP_CI_CPU_LIMIT}"
|
||||
memory: "${APP_CI_MEMORY_LIMIT}"
|
||||
requests:
|
||||
cpu: "${APP_CI_CPU_REQUEST}"
|
||||
memory: "${APP_CI_MEMORY_REQUEST}"
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /app/srcs/storage
|
||||
- name: ${CI_PROJECT_PATH_SLUG}-queue-worker
|
||||
image: $IMAGE_TAG
|
||||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command:
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"kill -s TERM 1; while test $(ps ax | grep -v grep | grep bash | wc -l) -gt 0; do sleep 2s; done",
|
||||
]
|
||||
resources:
|
||||
limits:
|
||||
cpu: "${APP_CI_CPU_LIMIT}"
|
||||
memory: "${APP_CI_MEMORY_LIMIT}"
|
||||
requests:
|
||||
cpu: "${APP_CI_CPU_REQUEST}"
|
||||
memory: "${APP_CI_MEMORY_REQUEST}"
|
||||
command:
|
||||
- "bash"
|
||||
args:
|
||||
- "k8s-queues.sh"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ${CI_PROJECT_PATH_SLUG}-config-map
|
||||
|
|
@ -107,14 +80,15 @@ spec:
|
|||
imagePullSecrets:
|
||||
- name: docker-registry-credentials
|
||||
securityContext:
|
||||
runAsUser: 82
|
||||
runAsGroup: 82
|
||||
fsGroup: 82
|
||||
runAsUser: 33
|
||||
runAsGroup: 33
|
||||
fsGroup: 33
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
volumes:
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
|
@ -125,9 +99,10 @@ spec:
|
|||
app: ${CI_PROJECT_PATH_SLUG}-app
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
targetPort: 443
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: ${CI_PROJECT_PATH_SLUG}-ingress-route
|
||||
|
|
@ -142,7 +117,21 @@ spec:
|
|||
- name: ${CI_PROJECT_PATH_SLUG}-service
|
||||
port: 80
|
||||
tls:
|
||||
secretName: ${CRT_TLS_NAME}
|
||||
secretName: $APP_DOMAIN
|
||||
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: $APP_DOMAIN
|
||||
spec:
|
||||
dnsNames:
|
||||
- $APP_DOMAIN
|
||||
secretName: $APP_DOMAIN
|
||||
issuerRef:
|
||||
name: $APP_CERT_ISSUER
|
||||
kind: ClusterIssuer
|
||||
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
|
|
@ -150,9 +139,6 @@ metadata:
|
|||
name: ${CI_PROJECT_PATH_SLUG}-cronjob
|
||||
spec:
|
||||
schedule: "* * * * *"
|
||||
successfulJobsHistoryLimit: 1
|
||||
failedJobsHistoryLimit: 1
|
||||
concurrencyPolicy: Forbid
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
|
|
@ -190,8 +176,8 @@ spec:
|
|||
persistentVolumeClaim:
|
||||
claimName: ${CI_PROJECT_PATH_SLUG}-pvc-storage
|
||||
securityContext:
|
||||
runAsUser: 82
|
||||
runAsGroup: 82
|
||||
fsGroup: 82
|
||||
runAsUser: 33
|
||||
runAsGroup: 33
|
||||
fsGroup: 33
|
||||
fsGroupChangePolicy: "OnRootMismatch"
|
||||
restartPolicy: OnFailure
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@
|
|||
<rule ref="Generic.Files.LineLength"/>
|
||||
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
|
||||
<rule ref="Generic.Formatting.MultipleStatementAlignment"/>
|
||||
<rule ref="Generic.Formatting.SpaceAfterCast"/>
|
||||
<rule ref="Generic.Formatting.NoSpaceAfterCast"/>
|
||||
<rule ref="Generic.Functions.CallTimePassByReference"/>
|
||||
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
|
||||
<rule ref="Generic.Metrics.CyclomaticComplexity"/>
|
||||
<rule ref="Generic.Metrics.NestingLevel"/>
|
||||
|
|
@ -42,8 +43,10 @@
|
|||
<rule ref="Generic.PHP.NoSilencedErrors"/>
|
||||
<rule ref="Generic.Strings.UnnecessaryStringConcat"/>
|
||||
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
|
||||
<rule ref="Generic.WhiteSpace.LanguageConstructSpacing"/>
|
||||
<rule ref="Generic.WhiteSpace.ScopeIndent"/>
|
||||
<rule ref="MySource.PHP.EvalObjectFactory"/>
|
||||
<rule ref="MySource.PHP.GetRequestData"/>
|
||||
<rule ref="MySource.PHP.AjaxNullComparison"/>
|
||||
<rule ref="PEAR.Classes.ClassDeclaration"/>
|
||||
<rule ref="PEAR.Commenting.InlineComment"/>
|
||||
<rule ref="PSR1.Classes.ClassDeclaration"/>
|
||||
|
|
@ -76,6 +79,7 @@
|
|||
<rule ref="Squiz.Scope.MemberVarScope"/>
|
||||
<rule ref="Squiz.Scope.MethodScope"/>
|
||||
<rule ref="Squiz.Scope.StaticThisUsage"/>
|
||||
<rule ref="Squiz.WhiteSpace.LanguageConstructSpacing"/>
|
||||
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
|
||||
<rule ref="Squiz.WhiteSpace.MemberVarSpacing">
|
||||
<properties>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,3 @@ parameters:
|
|||
|
||||
# Level 9 is the highest level
|
||||
level: 5
|
||||
|
||||
ignoreErrors:
|
||||
- '#Trait App\\Traits\\Models\\MiniCrudModel is used zero times and is not analysed#'
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: $APP_DOMAIN
|
||||
spec:
|
||||
dnsNames:
|
||||
- $APP_DOMAIN
|
||||
secretName: $APP_DOMAIN
|
||||
issuerRef:
|
||||
name: $APP_CERT_ISSUER
|
||||
kind: ClusterIssuer
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue