Compare commits
No commits in common. "main" and "evo" have entirely different histories.
242 changed files with 7096 additions and 13147 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
|
||||
|
|
|
|||
28
CHANGELOG.md
28
CHANGELOG.md
|
|
@ -1,28 +0,0 @@
|
|||
# CHANGELOG
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### Evo - Bugfix à faire
|
||||
|
||||
- [ ] **[FO]** Rajouter la Feature manquante : si une seule gamme -> afficher directement les produits de la gamme dans ranges.index (pour le header et footer)
|
||||
- [x] **[BO]** Rajouter status sur Brands,Ranges,Products avec labels si non publiés (prise en compte des ascendants)
|
||||
- [x] **[BO/FO]** Booleans sur Brands et Ranges pour définir si on affiche les enfant sur une étagere ou non (avec labels et prise en compte des ascendants)
|
||||
- Sur la home :
|
||||
- [x] **[BO/FO]** Rajouter une phrase de sous-titre I18N en dessous du logo LDC
|
||||
- [ ] **[FO]** Masquer le footer de la home
|
||||
- [ ] **[FO]** Layout Brands 2 par 2 avec Logo et en dessous title
|
||||
- [ ] **[FO]** Changer ordre langues FR|NL|EN
|
||||
- [ ] **[FO]** drapeau US -> UK
|
||||
- [x] **[BO/FO]** Footer: "Découvrez nos autres marques" passe dans settings et i18n
|
||||
- Sur les produits:
|
||||
- [ ] **[FO]** Sur le header et footer: Couleur de la **Range** en BG
|
||||
- [ ] **[FO]** Sur la modal: plus de color et title centré
|
||||
- [ ] **[FO]** Align flex center vertical et horizontal pour les views (brands.index -> home, ranges.index, products.index)
|
||||
|
||||
### Use cases à produire pour les données Seedés en BO (User stories)
|
||||
|
||||
- ~~**[BO]** Un remplissage avec des valeurs réaliste autre que lorem ipsum pour une brand avec 2 ranges et 4 produits chacun dans toute les langues~~
|
||||
- [x] **[BO]** Une marque avec une seule gamme, valeurs réalistes et i18n fields (1 seul produit)
|
||||
- [x] **[BO]** Une gamme avec un seul produit
|
||||
|
||||
## 1.0.0 Initial Realeas
|
||||
49
Dockerfile
49
Dockerfile
|
|
@ -1,49 +0,0 @@
|
|||
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/
|
||||
|
||||
USER root
|
||||
RUN chmod -R 755 /app/srcs/storage
|
||||
USER ${USER}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,28 +28,35 @@ class UserCreate extends Command
|
|||
/**
|
||||
* The first name of the new user.
|
||||
*
|
||||
* @var string|null
|
||||
* @var string
|
||||
*/
|
||||
protected $first_name;
|
||||
|
||||
/**
|
||||
* The last name of the new user.
|
||||
*
|
||||
* @var string|null
|
||||
* @var string
|
||||
*/
|
||||
protected $last_name;
|
||||
|
||||
/**
|
||||
* The email of the new user.
|
||||
*
|
||||
* @var string|null
|
||||
* @var string
|
||||
*/
|
||||
protected $email;
|
||||
|
||||
/**
|
||||
* The password of the new user.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $password;
|
||||
|
||||
/**
|
||||
* The role of the new user.
|
||||
*
|
||||
* @var string|null
|
||||
* @var string
|
||||
*/
|
||||
protected $role;
|
||||
|
||||
|
|
@ -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.
|
||||
*
|
||||
|
|
@ -194,7 +221,7 @@ class UserCreate extends Command
|
|||
$this->role = $tmp;
|
||||
} catch (ValidationException $e) {
|
||||
$this->error(sprintf(
|
||||
'Please choose from the selection %s',
|
||||
'Please choose from the selection',
|
||||
\implode(',', collect($e->errors())->flatten()->all())
|
||||
));
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ enum ActivityLogsEventEnum: int
|
|||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::created->name => 'Création',
|
||||
|
|
|
|||
|
|
@ -260,8 +260,6 @@ enum Country: int
|
|||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::afghanistan->name => 'Afghanistan',
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ enum StaticPageType: int
|
|||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::contact->name => 'Contact',
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ enum BootstrapThemeEnum: int
|
|||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::light->name => 'enums.bootstrap-theme.light',
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ enum RoleEnum: int
|
|||
|
||||
/**
|
||||
* Optionnal labels definition.
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::conceptor->name => 'enums.user-role.conceptor',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ class ActivityLogController extends Controller
|
|||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\ActivityLog $activity_log
|
||||
* @param App\Models\ActivityLog $activity_log
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function show(ActivityLog $activity_log): \Illuminate\Contracts\View\View
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
|
|||
use App\Http\Requests\Bo\Brands\StoreBrandRequest;
|
||||
use App\Http\Requests\Bo\Brands\UpdateBrandRequest;
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -32,12 +31,7 @@ class BrandController extends Controller
|
|||
public function index(Request $request): View
|
||||
{
|
||||
/** @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),
|
||||
]);
|
||||
$query = Brand::query();
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
|
|
@ -85,7 +79,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.brand.edit', $brand)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.brand', 1))
|
||||
]));
|
||||
|
|
@ -105,12 +99,6 @@ 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),
|
||||
]);
|
||||
return view('bo.pages.brands.edit', ['brandModel' => $brand]);
|
||||
}
|
||||
|
||||
|
|
@ -127,12 +115,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))
|
||||
]));
|
||||
|
|
@ -147,11 +135,8 @@ class BrandController extends Controller
|
|||
*/
|
||||
public function destroy(Brand $brand): RedirectResponse
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
$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')
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.brand', 1))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -16,20 +16,4 @@ class DashboardController extends Controller
|
|||
{
|
||||
return view('bo.pages.dashboard');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Chagne current locale on BO.
|
||||
*
|
||||
* @param string $lang
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function changeLang(string $lang): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if (!\in_array($lang, config('app.locales'))) {
|
||||
$lang = config('app.fallback_locale');
|
||||
}
|
||||
session()->put('lang', $lang);
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Bo;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Bo\Fonts\StoreFontRequest;
|
||||
use App\Http\Requests\Bo\Fonts\UpdateFontRequest;
|
||||
use App\Models\Font;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FontController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Font::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 = Font::query();
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$search,
|
||||
null,
|
||||
'font_name',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
trans('validation.attributes.name'),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
/** Custom pagination */
|
||||
$fontModelList = $this->paginate($query);
|
||||
|
||||
return view('bo.pages.fonts.index', compact('fontModelList', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function create(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$fontModelList = Font::all();
|
||||
return view('bo.pages.fonts.create', ['fontModelList' => $fontModelList]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Fonts\StoreFontRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreFontRequest $request): RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$font = new Font();
|
||||
$font->fill($request->validated());
|
||||
|
||||
if ($font->saveOrFail()) {
|
||||
return redirect()->route('bo.fonts.edit', ['tenant' => \tenant(), 'font' => $font])
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Font $font
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(Font $font): View
|
||||
{
|
||||
$fontModelList = Font::all();
|
||||
return view('bo.pages.fonts.edit', [
|
||||
'fontModelList' => $fontModelList,
|
||||
'fontModel' => $font
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\Fonts\UpdateFontRequest $request
|
||||
* @param \App\Models\Font $font
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(UpdateFontRequest $request, Font $font): RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request, $font) {
|
||||
$font->fill($request->validated());
|
||||
|
||||
if ($font->saveOrFail()) {
|
||||
return redirect()->route('bo.fonts.edit', ['tenant' => \tenant(), 'font' => $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])
|
||||
->with('error', __('crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Font $font
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Font $font): RedirectResponse
|
||||
{
|
||||
if ($font->deleteOrFail()) {
|
||||
return redirect()->route('bo.fonts.index', ['tenant' => \tenant()])
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', __('crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.font', 1))
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ use App\Http\Requests\Bo\Products\StoreProductRequest;
|
|||
use App\Http\Requests\Bo\Products\UpdateProductRequest;
|
||||
use App\Models\Product;
|
||||
use App\Models\Range;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -27,16 +26,13 @@ class ProductController extends Controller
|
|||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Client\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
/** @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),
|
||||
]);
|
||||
$query = Product::query()->with('range');
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
|
|
@ -55,20 +51,10 @@ class ProductController extends Controller
|
|||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
if ($request->input('range_id')) {
|
||||
$query->whereHas('range', fn(Builder $q) => $q->where('id', $request->input('range_id')));
|
||||
}
|
||||
|
||||
/** Custom pagination */
|
||||
$productModelList = $this->paginate($query);
|
||||
$rangeList = Range::query()->pluck('name', 'id');
|
||||
|
||||
return view('bo.pages.products.index', compact(
|
||||
'productModelList',
|
||||
'rangeList',
|
||||
'search',
|
||||
'searchFields'
|
||||
));
|
||||
return view('bo.pages.products.index', compact('productModelList', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -95,7 +81,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.product.edit', $product)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.product', 1))
|
||||
]));
|
||||
|
|
@ -115,14 +101,6 @@ class ProductController extends Controller
|
|||
*/
|
||||
public function edit(Product $product): View
|
||||
{
|
||||
$product->load([
|
||||
'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),
|
||||
]);
|
||||
$rangeModelList = Range::all();
|
||||
return view('bo.pages.products.edit', [
|
||||
'rangeModelList' => $rangeModelList,
|
||||
|
|
@ -143,12 +121,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))
|
||||
]));
|
||||
|
|
@ -164,10 +142,7 @@ class ProductController extends Controller
|
|||
public function destroy(Product $product): RedirectResponse
|
||||
{
|
||||
if ($product->deleteOrFail()) {
|
||||
/** @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')
|
||||
->with('success', __('crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.product', 1))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -2,15 +2,11 @@
|
|||
|
||||
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;
|
||||
use App\Models\Brand;
|
||||
use App\Models\Font;
|
||||
use App\Models\Range;
|
||||
use App\Traits\Controllers\ChangesModelOrder;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -19,8 +15,6 @@ use Illuminate\Support\Str;
|
|||
|
||||
class RangeController extends Controller
|
||||
{
|
||||
use ChangesModelOrder;
|
||||
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
|
|
@ -32,17 +26,13 @@ class RangeController extends Controller
|
|||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Illuminate\Http\Client\Request $request
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
/** @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),
|
||||
]);
|
||||
$query = Range::query()->with('brand');
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->input('search');
|
||||
|
|
@ -61,20 +51,10 @@ class RangeController extends Controller
|
|||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
if ($request->input('brand_id')) {
|
||||
$query->whereHas('brand', fn(Builder $q) => $q->where('id', $request->input('brand_id')));
|
||||
}
|
||||
|
||||
/** Custom pagination */
|
||||
$rangeModelList = $this->paginate($query);
|
||||
$brandList = Brand::query()->pluck('name', 'id');
|
||||
|
||||
return view('bo.pages.ranges.index', compact(
|
||||
'rangeModelList',
|
||||
'brandList',
|
||||
'search',
|
||||
'searchFields'
|
||||
));
|
||||
return view('bo.pages.ranges.index', compact('rangeModelList', 'search', 'searchFields'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -85,12 +65,7 @@ class RangeController extends Controller
|
|||
public function create(): \Illuminate\Contracts\View\View
|
||||
{
|
||||
$brandModelList = Brand::all();
|
||||
$fontModelList = Font::all();
|
||||
return view('bo.pages.ranges.create', [
|
||||
'brandModelList' => $brandModelList,
|
||||
'fontModelList' => $fontModelList,
|
||||
'widths' => Width::toArray()
|
||||
]);
|
||||
return view('bo.pages.ranges.create', ['brandModelList' => $brandModelList]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,7 +81,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.range.edit', $range)
|
||||
->with('success', __('crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(\trans_choice('models.classes.range', 1))
|
||||
]));
|
||||
|
|
@ -126,18 +101,10 @@ class RangeController extends Controller
|
|||
*/
|
||||
public function edit(Range $range): View
|
||||
{
|
||||
$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),
|
||||
]);
|
||||
$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 +121,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))
|
||||
]));
|
||||
|
|
@ -174,11 +141,8 @@ class RangeController extends Controller
|
|||
*/
|
||||
public function destroy(Range $range): RedirectResponse
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
$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')
|
||||
->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'))
|
||||
]));
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class StaticPageController extends Controller
|
|||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\StaticPage $static_page
|
||||
* @param App\Models\StaticPage $static_page
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(StaticPage $static_page): \Illuminate\Contracts\View\View
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
]));
|
||||
|
|
@ -105,7 +102,7 @@ class UserController extends Controller
|
|||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\User $user
|
||||
* @param App\Models\User $user
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function edit(User $user): \Illuminate\Contracts\View\View
|
||||
|
|
@ -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)) {
|
||||
|
|
@ -94,7 +94,7 @@ class Controller extends BaseController
|
|||
return $query;
|
||||
}
|
||||
// Get models with relation's name/label.
|
||||
if (Str::endsWith($field, '_id')) {
|
||||
if (Str::endsWith($field, '_id') && $query->getModel()->has(Str::remove('_id', $field))) {
|
||||
$query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) {
|
||||
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
|
||||
$queryRelation->where('name', 'like', '%' . $search . '%');
|
||||
|
|
@ -122,15 +122,12 @@ class Controller extends BaseController
|
|||
*/
|
||||
protected function getSearchValue(Builder $query, string $search, string $field): string|array
|
||||
{
|
||||
$cast = $query->getModel()->getCasts()[$field] ?? null;
|
||||
if (
|
||||
count($query->getModel()->getCasts()) and // Check if the model has casts.
|
||||
$cast and // Check if there is cast about specific field.
|
||||
enum_exists($cast) // Check if the cast is an enum.
|
||||
isset($query->getModel()->getCasts()[$field]) and // Check if there is cast about specific field.
|
||||
enum_exists($query->getModel()->getCasts()[$field]) // Check if the cast is an enum.
|
||||
) {
|
||||
/** @var \App\Traits\Enums $cast */
|
||||
// @phpstan-ignore-next-line
|
||||
$cases = collect($cast::toArray());
|
||||
$cases = collect($query->getModel()->getCasts()[$field]::toArray());
|
||||
$enum = $cases->filter(function ($item) use ($search) {
|
||||
return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label));
|
||||
});
|
||||
|
|
@ -151,12 +148,12 @@ class Controller extends BaseController
|
|||
{
|
||||
$rName = request()->route()->getName();
|
||||
$table = $query->getModel()->getTable();
|
||||
if (request()->input('sort_col')) {
|
||||
Session::put("$rName.sort_col", request()->input('sort_col'));
|
||||
if (request()->sort_col) {
|
||||
Session::put("$rName.sort_col", request()->sort_col);
|
||||
Session::put("$rName.sorted", true);
|
||||
}
|
||||
if (request()->input('sort_way')) {
|
||||
Session::put("$rName.sort_way", request()->input('sort_way'));
|
||||
if (request()->sort_way) {
|
||||
Session::put("$rName.sort_way", request()->sort_way);
|
||||
Session::put("$rName.sorted", true);
|
||||
}
|
||||
if (request()->rst) {
|
||||
|
|
@ -164,16 +161,12 @@ class Controller extends BaseController
|
|||
Session::remove("$rName.sort_col");
|
||||
Session::remove("$rName.sort_way");
|
||||
}
|
||||
if (
|
||||
request()->input('sort_col') and
|
||||
request()->input('sort_way') and
|
||||
Schema::hasColumn($table, request()->input('sort_col'))
|
||||
) {
|
||||
if (!in_array(request()->input('sort_col'), $ignore)) {
|
||||
$query = $query->orderBy(
|
||||
\sprintf("$table.%s", request()->input('sort_col')),
|
||||
request()->input('sort_way')
|
||||
);
|
||||
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
|
||||
if (
|
||||
!isset($ignore) or
|
||||
(isset($ignore) and !in_array(request()->sort_col, $ignore))
|
||||
) {
|
||||
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
|
||||
}
|
||||
} elseif (Schema::hasColumn($table, 'order')) {
|
||||
$query = $query->orderBy('order', 'ASC');
|
||||
|
|
@ -200,7 +193,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}";
|
||||
|
|
|
|||
|
|
@ -37,14 +37,7 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\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 +54,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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Lang
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// phpcs:enable
|
||||
$prefix = Route::current()->action['prefix'];
|
||||
if (\in_array($prefix, ['/{tenant}/bo', '/bo'])) {
|
||||
app()->setLocale(session()->get('lang', config('app.locale')));
|
||||
return $next($request);
|
||||
}
|
||||
if (Route::current()->getName() == null && count($request->all()) > 0) {
|
||||
return redirect()->route('home', $request->all());
|
||||
}
|
||||
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
|
||||
{
|
||||
|
|
@ -30,9 +29,6 @@ class StoreBrandRequest extends FormRequest
|
|||
protected function prepareForValidation()
|
||||
{
|
||||
$this->mergePicture('picture');
|
||||
$this->merge([
|
||||
'status' => $this->boolean('status')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,8 +40,7 @@ class StoreBrandRequest extends FormRequest
|
|||
{
|
||||
$rules = [
|
||||
'name' => 'required|min:2|max:60',
|
||||
'color' => ['required', new Hex()],
|
||||
'status' => 'present|boolean'
|
||||
'color' => 'required|color_hex'
|
||||
];
|
||||
|
||||
return \array_merge(
|
||||
|
|
@ -63,7 +58,6 @@ class StoreBrandRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'color' => trans('validation.custom.color'),
|
||||
'status' => \trans('validation.custom.status'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Fonts;
|
||||
|
||||
use App\Models\Font;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class StoreFontRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::allows('create', Font::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'font_name' => $this->input('font_name'),
|
||||
'font' => $this->input('font')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'font_name' => [
|
||||
'present', 'required_without:font', 'nullable',
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
function (string $attribute, $value, callable $fail) {
|
||||
$font = $this->input('font');
|
||||
if (is_null($font) and !in_array($value, Font::DEFAULT_FONTS)) {
|
||||
$fail(trans('validation.in_array', [
|
||||
'other' => implode(',', Font::DEFAULT_FONTS)
|
||||
]));
|
||||
}
|
||||
}
|
||||
],
|
||||
'font' => 'present|nullable'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'font_name' => trans('validation.attributes.name'),
|
||||
'font' => trans('validation.custom.font'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Bo\Fonts;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class UpdateFontRequest extends StoreFontRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::allows('update', $this->route('font'));
|
||||
}
|
||||
}
|
||||
|
|
@ -27,11 +27,10 @@ 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', ''))
|
||||
]);
|
||||
$this->mergePicture('picture');
|
||||
}
|
||||
|
|
@ -44,10 +43,9 @@ 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'
|
||||
'range_id' => 'required|exists:ranges,id'
|
||||
];
|
||||
|
||||
return \array_merge(
|
||||
|
|
@ -64,10 +62,7 @@ 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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -32,9 +29,6 @@ class StoreRangeRequest extends FormRequest
|
|||
protected function prepareForValidation()
|
||||
{
|
||||
$this->mergePicture('picture');
|
||||
$this->merge([
|
||||
'status' => $this->boolean('status')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -44,27 +38,14 @@ 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)],
|
||||
'status' => 'present|boolean',
|
||||
'brand_id' => 'required|exists:brands,id'
|
||||
];
|
||||
|
||||
$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', 500, 500, 800, 500, false)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -77,10 +58,6 @@ class StoreRangeRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'brand_id' => \trans_choice('models.classes.brand', 1),
|
||||
'font_id' => \trans_choice('models.classes.font', 1),
|
||||
'color' => \trans('validation.custom.color'),
|
||||
'width' => \trans('validation.custom.width'),
|
||||
'status' => \trans('validation.custom.status'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,20 +37,15 @@ 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 = [
|
||||
'title' => 'required|min:2|max:60',
|
||||
'color' => 'required|color_hex',
|
||||
'items_by_line' => 'required|numeric|min:3|max:6',
|
||||
];
|
||||
|
||||
return \array_merge(
|
||||
$rules,
|
||||
$this->pictureRules('logo', 420, 500, 420, 500, false)
|
||||
$this->pictureRules('logo', 200, 200, 200, 200, false)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -63,12 +57,9 @@ 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'),
|
||||
'items_by_line' => \trans('validation.custom.items_by_line'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ class UpdateChangeOrderRequest extends FormRequest
|
|||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'direction' => $this->route('direction') === 'up' ? true : false
|
||||
'direction' => $this->string('direction') === 'up' ? true : false
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
@ -108,7 +108,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
|
|||
$rules = array_merge($rules, [
|
||||
"data.$iptName.value" => [
|
||||
function (string $attribute, $value, callable $fail) use ($sizes) {
|
||||
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
|
||||
return ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
|
||||
}
|
||||
],
|
||||
$this->compileValidate(
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
namespace App\Http\Requests\Bo\Users;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
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 +40,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 +67,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') ??
|
||||
$this->route('user')->password,
|
||||
'password_confirmation' => $this->input('password_confirmation') ??
|
||||
$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($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,28 +2,26 @@
|
|||
|
||||
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;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
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 +44,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 +53,39 @@ 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.
|
||||
*/
|
||||
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";
|
||||
|
|
@ -102,11 +99,7 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
|
||||
// * Empty too old files
|
||||
collect(File::allFiles($this->exportFolder))->each(function (\SplFileInfo $file) {
|
||||
$timestamp = $file->getCTime() ?: $file->getMTime() ?: $file->getATime();
|
||||
if (!$timestamp) {
|
||||
throw new \RuntimeException('Failed to get modified time');
|
||||
}
|
||||
if (now()->subDays(7)->gt(Carbon::createFromTimestamp($timestamp))) {
|
||||
if (now()->subDays(7)->gt(Carbon::parse($file->getCTime() ?: $file->getMTime() ?: $file->getATime()))) {
|
||||
Log::info('Removed exported file ' . $file->getRealPath());
|
||||
File::delete($file->getRealPath());
|
||||
}
|
||||
|
|
@ -132,36 +125,23 @@ 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, Str::of($file)->explode('/')->take(-2)->implode('/'))) {
|
||||
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,106 +149,73 @@ 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>
|
||||
* }
|
||||
* @param \Illuminate\Database\Eloquent\Collection $data
|
||||
* @return array
|
||||
*/
|
||||
protected function getAssociatedFileList(array $data): array
|
||||
protected function getAssociatedFileList(Collection $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($data)->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)
|
||||
])->collect();
|
||||
|
||||
/** @var string $logo */
|
||||
$logo = Arr::get($this->getSettings(), 'logo');
|
||||
/** @var array<string> $fonts */
|
||||
$fonts = collect(Arr::get($data, 'fonts'))->pluck('font')->filter()->all();
|
||||
/** @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();
|
||||
/** @var \Illuminate\Support\Collection<int, string> $productList */
|
||||
$productList = $items->pluck(1)->map(fn(Collection $ranges) => $ranges
|
||||
->map(fn(Collection $range) => $range->pluck(1)))->flatten();
|
||||
$brandList = $items->pluck(0);
|
||||
$rangeList = $items->pluck(1)->map(fn (SupportCollection $ranges) => $ranges
|
||||
->map(fn (SupportCollection $range) => $range->pluck(0)))->flatten();
|
||||
$productList = $items->pluck(1)->map(fn (SupportCollection $ranges) => $ranges
|
||||
->map(fn (SupportCollection $range) => $range->pluck(1)->map(fn ($products) => $products
|
||||
->map(fn (SupportCollection $product) => $product))))->flatten();
|
||||
return [
|
||||
'settings' => $logo,
|
||||
'brands' => $brandList->all(),
|
||||
'fonts' => $fonts,
|
||||
'ranges' => $rangeList->all(),
|
||||
'products' => $productList->all(),
|
||||
'settings' => collect(Arr::get($this->getSettings(), 'logo')),
|
||||
'brands' => $brandList,
|
||||
'ranges' => $rangeList,
|
||||
'products' => $productList,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get export file content
|
||||
*
|
||||
* @param array<string,array<int,\App\Models\Brand>|array<int,\App\Models\Font>> $data
|
||||
* @return array<'version'|'settings'|'fonts'|'items',string|array<string,number|string|boolean>|\App\Models\Font|\App\Models\Brand>
|
||||
* @param \Illuminate\Database\Eloquent\Collection $data
|
||||
* @return array
|
||||
*/
|
||||
protected function getFile(array $data): array
|
||||
protected function getFile(Collection $data): array
|
||||
{
|
||||
// phpcs:enable
|
||||
$settings = $this->getSettings();
|
||||
$tableName = $settings->getTable();
|
||||
$settings->logo = Str::start(
|
||||
\pathinfo($settings->logo, \PATHINFO_BASENAME),
|
||||
"pictures/{$tableName}/"
|
||||
);
|
||||
return [
|
||||
'version' => $this->getVersion(),
|
||||
'settings' => Arr::except($settings->toArray(), ['id']),
|
||||
'fonts' => Arr::map(Arr::get($data, 'fonts'), function (Font $font) {
|
||||
$font->font = $font->font ? Str::start(
|
||||
\pathinfo($font->font, \PATHINFO_BASENAME),
|
||||
"fonts/"
|
||||
) : $font->font;
|
||||
return $font;
|
||||
}),
|
||||
'items' => Arr::map(Arr::get($data, 'brands'), function (Brand $brand) {
|
||||
'settings' => $this->getSettings(),
|
||||
'items' => $data->map(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;
|
||||
})
|
||||
})->toArray()
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -277,8 +224,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<\Illuminate\Support\Collection<string>> $filesToZip
|
||||
* @return array<string>
|
||||
* @throws \RuntimeException If temp folder caanot be cleared in order to work.
|
||||
*/
|
||||
|
|
@ -293,25 +239,17 @@ 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;
|
||||
};
|
||||
$toCopy = ['settings', 'brands', 'ranges', 'products'];
|
||||
foreach ($toCopy as $element) {
|
||||
$targetPath = Str::of($this->tempFolder)->append("/pictures/{$element}");
|
||||
File::makeDirectory($targetPath, 0700, true);
|
||||
$newPaths[$element] = collect($filesToZip[$element])->map($copyPath($targetPath))->all();
|
||||
$targetPath = Str::of($this->tempFolder)->append("/{$element}");
|
||||
File::makeDirectory($targetPath);
|
||||
$newPaths[$element] = $filesToZip[$element]->map($copyPath($targetPath));
|
||||
}
|
||||
// * 'fonts'
|
||||
$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));
|
||||
File::copy(\public_path($path), $targetFilePath);
|
||||
return $targetFilePath;
|
||||
})->all();
|
||||
return collect($newPaths)->flatten()->all();
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +259,7 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
* @param \App\Models\Brand|\App\Models\Range|\App\Models\Product $model
|
||||
* @return void
|
||||
*/
|
||||
private function setPictureExportPath(Brand|Range|Product|Font &$model): void
|
||||
private function setPictureExportPath(Brand|Range|Product &$model): void
|
||||
{
|
||||
$tableName = $model->getTable();
|
||||
$model->picture = Str::start(
|
||||
|
|
@ -343,37 +281,20 @@ class ShowroomExport implements ShouldQueue, ShouldBeUnique
|
|||
/**
|
||||
* Get app settings
|
||||
*
|
||||
* @return \App\Models\Settings
|
||||
* @return array
|
||||
*/
|
||||
private function getSettings(): Settings
|
||||
private function getSettings(): array
|
||||
{
|
||||
return Settings::getFromCache($this->tenantId);
|
||||
return Settings::getCached()->first()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app data
|
||||
*
|
||||
* @return array<'brands'|'fonts',array<int,\App\Models\Brand>|array<int,\App\Models\Font>>
|
||||
* @return \Illuminate\Database\Eloquent\Collection<\App\Models\Brand>
|
||||
*/
|
||||
private function getData(): array
|
||||
private function getData(): Collection
|
||||
{
|
||||
return [
|
||||
'brands' => Brand::query()
|
||||
->where([['status', true], ['tenant_id', $this->tenantId]])
|
||||
->whereHas('ranges')
|
||||
->with('ranges', function (Builder $q) {
|
||||
return $q->where('status', true)
|
||||
->whereHas('products')
|
||||
->with('products', function (Builder $q) {
|
||||
return $q->where('status', true);
|
||||
});
|
||||
})
|
||||
->get()
|
||||
->all(),
|
||||
'fonts' => Font::query()
|
||||
->where('tenant_id', $this->tenantId)
|
||||
->get()
|
||||
->all()
|
||||
];
|
||||
return Brand::query()->with('ranges.products')->get();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
app/Lib/Helpers/CollectionHelper.php
Normal file
64
app/Lib/Helpers/CollectionHelper.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CollectionHelper
|
||||
{
|
||||
/**
|
||||
* Paginate a collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @param integer $pageSize
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
|
||||
{
|
||||
$page = Paginator::resolveCurrentPage('page');
|
||||
|
||||
return self::paginateManually($results, $pageSize, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate a collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $results
|
||||
* @param integer $pageSize
|
||||
* @param integer $page
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
$total = $results->count();
|
||||
|
||||
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => 'page',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new length-aware paginator instance.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $items
|
||||
* @param integer $total
|
||||
* @param integer $perPage
|
||||
* @param integer $currentPage
|
||||
* @param array $options
|
||||
* @return \Illuminate\Pagination\LengthAwarePaginator
|
||||
*/
|
||||
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
|
||||
{
|
||||
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'perPage' => $perPage,
|
||||
'currentPage' => $currentPage,
|
||||
'options' => $options
|
||||
]);
|
||||
}
|
||||
}
|
||||
190
app/Lib/Helpers/ExceptionsHelper.php
Normal file
190
app/Lib/Helpers/ExceptionsHelper.php
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
class ExceptionsHelper
|
||||
{
|
||||
/**
|
||||
* Handle sentry event
|
||||
*
|
||||
* @param \Sentry\Event $event
|
||||
* @return null|\Sentry\Event
|
||||
*/
|
||||
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
|
||||
{
|
||||
// Remove unwanted reports.
|
||||
$event->setModules([]);
|
||||
$event->setOsContext(null);
|
||||
|
||||
/** @var \Sentry\ExceptionDataBag[] */
|
||||
$exceptionBag = [];
|
||||
foreach ($event->getExceptions() as $exceptionDataBag) {
|
||||
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
|
||||
}
|
||||
$event->setExceptions($exceptionBag);
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonimize Sentry Exception bag
|
||||
*
|
||||
* @param \Sentry\ExceptionDataBag $exceptionDataBag
|
||||
* @return \Sentry\ExceptionDataBag
|
||||
*/
|
||||
private static function getAnonymizedSentryException(
|
||||
\Sentry\ExceptionDataBag $exceptionDataBag
|
||||
): \Sentry\ExceptionDataBag {
|
||||
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
|
||||
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
|
||||
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
|
||||
}
|
||||
return $exceptionDataBag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymise sentry stacktrace
|
||||
*
|
||||
* @param \Sentry\Stacktrace $stackTrace
|
||||
* @return \Sentry\Stacktrace
|
||||
*/
|
||||
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
|
||||
{
|
||||
/** @var \Sentry\Frame[] */
|
||||
$frames = \array_reverse($stackTrace->getFrames(), true);
|
||||
$i = \count($frames) - 1;
|
||||
// * Remove all but keep first (index.php).
|
||||
while ($i-- > 0) {
|
||||
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
|
||||
}
|
||||
foreach ($frames as $index => $frame) {
|
||||
$frame->setPostContext([]);
|
||||
$frame->setPreContext([]);
|
||||
$frame->setContextLine('');
|
||||
$frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
|
||||
$stackTrace->addFrame($frame);
|
||||
if ($index === 0) {
|
||||
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
|
||||
}
|
||||
}
|
||||
return $stackTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get anonymized sentry params
|
||||
*
|
||||
* @param mixed[] $params
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getAnonymizedSentryParams(array $params): array
|
||||
{
|
||||
foreach ($params as &$param) {
|
||||
$type = \strtolower(\gettype($param));
|
||||
if (\is_string($param)) {
|
||||
$type = \explode(' ', \trim(strtolower($param)))[0] ?? null;
|
||||
}
|
||||
switch (true) {
|
||||
case $type === 'lambda':
|
||||
case $type === 'object':
|
||||
continue 2;
|
||||
case $type === 'array' and \is_array($param):
|
||||
$param = sprintf('Array(%d)', \count($param));
|
||||
continue 2;
|
||||
default:
|
||||
if (\is_numeric($param)) {
|
||||
$param = \ucfirst($type);
|
||||
continue 2;
|
||||
}
|
||||
if (\is_string($param)) {
|
||||
$param = sprintf('String(%d)', \strlen($param));
|
||||
continue 2;
|
||||
}
|
||||
} //end switch
|
||||
} //end foreach
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Anonymize at least Sql queries
|
||||
*
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnonymizedMessage(string $message): string
|
||||
{
|
||||
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
|
||||
if ($sqlPos = \strpos($message, 'to use near')) {
|
||||
$message = \mb_strcut($message, 0, $sqlPos);
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymize stack trace
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return string
|
||||
*/
|
||||
public static function getAnonymizedStackTrace(\Throwable $e): string
|
||||
{
|
||||
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
|
||||
$traceStack = $e->getTrace();
|
||||
$traceStr = '';
|
||||
$maxStackStage = 0;
|
||||
foreach ($traceStack as $stackStage => $stack) {
|
||||
$maxStackStage = $stackStage;
|
||||
$traceStr .= \sprintf(
|
||||
'#%s %s(%d): ',
|
||||
$stackStage,
|
||||
str_replace($projectPath, '', $stack['file'] ?? ''),
|
||||
$stack['line'] ?? ''
|
||||
);
|
||||
if (isset($stack['class'])) {
|
||||
$traceStr .= "{$stack['class']}->{$stack['function']}(";
|
||||
if (isset($stack['args'])) {
|
||||
$traceStr .= self::handleArgs($stack);
|
||||
}
|
||||
$traceStr .= ")\n";
|
||||
} elseif (isset($stack['function'])) {
|
||||
$traceStr .= "{$stack['function']}(";
|
||||
if (isset($stack['args'])) {
|
||||
$traceStr .= self::handleArgs($stack);
|
||||
}
|
||||
$traceStr .= ")\n";
|
||||
}
|
||||
} //end foreach
|
||||
$maxStackStage++;
|
||||
$traceStr .= "#$maxStackStage {main}";
|
||||
return $traceStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Args
|
||||
*
|
||||
* @param array $stack
|
||||
* @return string
|
||||
*/
|
||||
public static function handleArgs(array $stack): string
|
||||
{
|
||||
$traceArgs = '';
|
||||
$argsCount = count($stack['args']);
|
||||
for ($i = 0; $i < $argsCount; $i++) {
|
||||
$type = \gettype($stack['args'][$i]);
|
||||
switch ($type) {
|
||||
case 'object':
|
||||
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
|
||||
break;
|
||||
case 'array':
|
||||
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
|
||||
break;
|
||||
case 'string':
|
||||
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
|
||||
break;
|
||||
default:
|
||||
$traceArgs .= \gettype($stack['args'][$i]);
|
||||
}
|
||||
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
|
||||
}
|
||||
return $traceArgs;
|
||||
}
|
||||
}
|
||||
468
app/Lib/Helpers/FileStorageHelper.php
Normal file
468
app/Lib/Helpers/FileStorageHelper.php
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
<?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,
|
||||
$private
|
||||
);
|
||||
$filename = self::getFileUniqueName($folderPath, $filename);
|
||||
return "{$folderPath}/{$filename}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sub folders to store files using pattern
|
||||
* storage/modelfiles/posts/10/04/19/5f
|
||||
* year/month/day/md5 first 2 letters
|
||||
*
|
||||
* @param string $storage_dir
|
||||
* @param string $table
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
|
||||
{
|
||||
$filenameHash = \md5($filename);
|
||||
$pathFolder = \storage_path(sprintf(
|
||||
'%s/%s/%s/%s/%s/%s',
|
||||
$storage_dir,
|
||||
$table,
|
||||
gmdate('y'),
|
||||
gmdate('m'),
|
||||
gmdate('d'),
|
||||
substr($filenameHash, 0, 2)
|
||||
));
|
||||
if (!File::exists($pathFolder)) {
|
||||
File::makeDirectory($pathFolder, 0755, true);
|
||||
}
|
||||
return $pathFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique filename if the path is already occupied
|
||||
*
|
||||
* @param string $folderPath
|
||||
* @param string $filename
|
||||
* @return string
|
||||
* @throws \RuntimeException If a unique filename cannot be obtained.
|
||||
*/
|
||||
private static function getFileUniqueName(string $folderPath, string $filename): string
|
||||
{
|
||||
$attempts = 0;
|
||||
$maxAttempts = 20;
|
||||
while (File::exists("{$folderPath}/{$filename}")) {
|
||||
if ($attempts++ >= $maxAttempts) {
|
||||
throw new \RuntimeException("Failed to have a unique filename for {$folderPath}/{$filename}");
|
||||
}
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
|
||||
\uniqid('-') . '.' .
|
||||
pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
}
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slugify the filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public static function slugifyFileName(string $filename): string
|
||||
{
|
||||
return Str::slug(pathinfo($filename, \PATHINFO_FILENAME)) . '.' . pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize File name for uploaded files
|
||||
*
|
||||
* @param string $filename The original uploaded client file name.
|
||||
* @param boolean $beautify Beautify the filename.
|
||||
* @return string The sanitized file name.
|
||||
*/
|
||||
public static function sanitizeFileName(string $filename, bool $beautify = true): string
|
||||
{
|
||||
// * Sanitize filename
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$filename = preg_replace(
|
||||
'~
|
||||
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
||||
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
||||
[#\[\]@!$&\'()+,;=]| # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
|
||||
~x',
|
||||
'-',
|
||||
$filename
|
||||
);
|
||||
// phpcs:enable
|
||||
// * Avoids '.' '..' or '.hiddenFiles' .
|
||||
$filename = Str::of($filename)->ltrim('.-');
|
||||
// * Optional beautification
|
||||
if ($beautify) {
|
||||
$filename = static::beautifyFilename($filename);
|
||||
}
|
||||
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
|
||||
/** @var string */
|
||||
$extension = pathinfo($filename, \PATHINFO_EXTENSION);
|
||||
/** @var string */
|
||||
$filename = pathinfo($filename, \PATHINFO_FILENAME);
|
||||
|
||||
$encoding = mb_detect_encoding($filename);
|
||||
$encoding = $encoding ? $encoding : null;
|
||||
return \mb_strcut(
|
||||
$filename,
|
||||
0,
|
||||
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
|
||||
$encoding
|
||||
) . (!empty($extension) ? ".$extension" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautify a filename
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
private static function beautifyFilename(string $filename): string
|
||||
{
|
||||
// Reduce consecutive characters.
|
||||
$filename = preg_replace([
|
||||
// * "file name.zip" becomes 'file-name.zip'
|
||||
'/ +/',
|
||||
// * "file___name.zip" becomes 'file-name.zip'
|
||||
'/_+/',
|
||||
// * "file---name.zip" becomes 'file-name.zip'
|
||||
'/-+/'
|
||||
], '-', $filename);
|
||||
$filename = preg_replace([
|
||||
// * "file--.--.-.--name.zip" becomes "file.name.zip"
|
||||
'/-*\.-*/',
|
||||
// * "file...name..zip" becomes "file.name.zip"
|
||||
'/\.{2,}/'
|
||||
], '.', $filename);
|
||||
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
|
||||
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
|
||||
// * ".file-name.-" becomes "file-name"
|
||||
$filename = trim($filename, '.-');
|
||||
return $filename;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,23 +2,37 @@
|
|||
|
||||
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 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 +41,554 @@ 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);
|
||||
// If a route is match then it do not throws exception.
|
||||
if (
|
||||
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
|
||||
$routes->match($request)
|
||||
) {
|
||||
// Removes the matched url.
|
||||
throw ValidationException::withMessages([
|
||||
$field => trans(
|
||||
'crud.libs.toolbox.assertNoInternalUrls',
|
||||
['attribute' => $field, 'appname' => config('app.name')]
|
||||
)
|
||||
]);
|
||||
}
|
||||
return $match;
|
||||
} catch (NotFoundHttpException $e) {
|
||||
return $match;
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutltibyte string replace
|
||||
*
|
||||
* @param array|string $search
|
||||
* @param array|string $replace
|
||||
* @param array|string $subject
|
||||
* @param integer $count
|
||||
* @return array|string
|
||||
*/
|
||||
public static function mbReplace(
|
||||
array|string $search,
|
||||
array|string $replace,
|
||||
array|string $subject,
|
||||
int &$count = 0
|
||||
): array|string {
|
||||
if (!is_array($search) && is_array($replace)) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($subject)) {
|
||||
// Call mb_replace for each single string in $subject .
|
||||
foreach ($subject as &$string) {
|
||||
$string = &self::mbReplace($search, $replace, $string, $count);
|
||||
}
|
||||
} elseif (is_array($search)) {
|
||||
if (!is_array($replace)) {
|
||||
foreach ($search as &$string) {
|
||||
$subject = self::mbReplace($string, $replace, $subject, $count);
|
||||
}
|
||||
} else {
|
||||
$n = max(count($search), count($replace));
|
||||
while ($n--) {
|
||||
$subject = self::mbReplace(current($search), current($replace), $subject, $count);
|
||||
next($search);
|
||||
next($replace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$parts = mb_split(preg_quote($search), $subject);
|
||||
$count = count($parts) - 1;
|
||||
$subject = implode($replace, $parts);
|
||||
} //end if
|
||||
return $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field is unique
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @param string|integer|float|boolean|null|array $value
|
||||
* @param integer|null $id
|
||||
* @param callable|null $query
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
|
||||
*/
|
||||
public static function assertFieldIsUnique(
|
||||
string $table,
|
||||
string $field,
|
||||
string|int|float|bool|null|array $value,
|
||||
?int $id = null,
|
||||
?callable $query = null
|
||||
) {
|
||||
if (!$query) {
|
||||
Validator::make([$field => $value], [
|
||||
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
|
||||
], [
|
||||
self::mbReplace('.', '\.', "{$field}.unique")
|
||||
=> trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value])
|
||||
])->validate();
|
||||
return;
|
||||
}
|
||||
Validator::make([$field => $value], [
|
||||
$field => Rule::unique($table)->where($query)->ignore($id),
|
||||
], [
|
||||
$field . '.unique' => trans(
|
||||
'crud.libs.toolbox.assertFieldIsUnique',
|
||||
['attribute' => $field, 'value' => $value]
|
||||
)
|
||||
])->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get original polyfill with autotranslate behavior
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param string $attribute
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getOriginal(Model $model, string $attribute): mixed
|
||||
{
|
||||
if (\in_array('Spatie\Translatable\HasTranslations', \class_uses_recursive($model))) {
|
||||
return $model->getTranslation(
|
||||
$attribute,
|
||||
$model->getLocale(),
|
||||
$model->useFallbackLocale()
|
||||
);
|
||||
}
|
||||
return $model->getOriginal($attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic model query
|
||||
*
|
||||
* @param string $defaultLocale
|
||||
* @param \Illuminate\Contracts\Database\Eloquent\Builder $q
|
||||
* @param string $column
|
||||
* @param string $value
|
||||
* @return \Illuminate\Contracts\Database\Eloquent\Builder
|
||||
*/
|
||||
public static function queryColumnWithLocales(
|
||||
string $defaultLocale,
|
||||
Builder $q,
|
||||
string $column,
|
||||
string $value
|
||||
): Builder {
|
||||
$currentLocale = app()->currentLocale();
|
||||
$locales = collect(config('app.locales'))
|
||||
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
|
||||
if (!$locales->has($defaultLocale)) {
|
||||
// * Remove default locale.
|
||||
$locales->pull($defaultLocale);
|
||||
}
|
||||
if (!$locales->has($currentLocale)) {
|
||||
// * Remove current locale.
|
||||
$locales->pull($currentLocale);
|
||||
}
|
||||
|
||||
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value) {
|
||||
// * Query default locale
|
||||
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$defaultLocale}') = '{$value}'");
|
||||
if ($defaultLocale !== $currentLocale) {
|
||||
// * Query current locale
|
||||
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$currentLocale}') = '{$value}'");
|
||||
}
|
||||
// * Query any other locale
|
||||
$locales->each(fn ($locale) => $q->orWhereRaw(
|
||||
"JSON_EXTRACT({$column}, '$.{$locale}') = '{$value}'"
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array from a dot separated string
|
||||
*
|
||||
* @param string $path
|
||||
* @param mixed $value
|
||||
* @param string $separator
|
||||
* @return array
|
||||
*/
|
||||
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
|
||||
{
|
||||
$arr = $value;
|
||||
$keys = array_reverse(explode($separator, $path));
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$arr = [$key => $arr];
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an object to array
|
||||
*
|
||||
* @param array|object $input
|
||||
* @return array
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function objectToArray($input): array
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (is_object($input) or is_array($input)) {
|
||||
$ret = (array)$input;
|
||||
foreach ($ret as &$item) {
|
||||
// Recursively process EACH element only if not scalar.
|
||||
$item = (is_object($item) or is_array($item)) ?
|
||||
self::objectToArray($item) : $item;
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert deeply an array to object
|
||||
*
|
||||
* @param array|object $input
|
||||
* @return object
|
||||
* @throws \RuntimeException If $input var is a scalar value.
|
||||
*/
|
||||
public static function arrayToObject($input): object
|
||||
{
|
||||
// Only process if it's an object or array being passed to the function.
|
||||
if (is_object($input) or is_array($input)) {
|
||||
$ret = (object)$input;
|
||||
foreach ($ret as &$item) {
|
||||
// Recursively process EACH element only if not scalar.
|
||||
$item = (is_object($item) or is_array($item)) ?
|
||||
self::arrayToObject($item) : $item;
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate picture
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param string|null $value
|
||||
* @param callback $fail
|
||||
* @param object $sizes
|
||||
* @return void
|
||||
*/
|
||||
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes)
|
||||
{
|
||||
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
|
||||
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
return;
|
||||
}
|
||||
/** @var string */
|
||||
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
|
||||
if (
|
||||
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
|
||||
'image/jpeg', 'image/jpg', 'image/png', 'image/gif'
|
||||
])
|
||||
) {
|
||||
$fail(trans(':attribute doit être une image de type \'image/jpeg,image/jpg,image/png,image/gif\'', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
}
|
||||
if (!($dimensions = \getimagesize($fileFullPath)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
|
||||
$fail(trans(':attribute impossible de valider les dimensions', [
|
||||
'attribute' => $attribute
|
||||
]));
|
||||
} else {
|
||||
$width = intval($dimensions[0]);
|
||||
$height = intval($dimensions[1]);
|
||||
|
||||
$xMessage = trans(
|
||||
":attribute la largeur doit être comprise entre {$sizes->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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,19 +3,18 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Enums\ActivityLogs\ActivityLogsEventEnum;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
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 string $model Target model.
|
||||
* @property string $model_id Id of the target model.
|
||||
* @property string $event Event of this activity (ActivityLogsEventEnum).
|
||||
* @property string $data Changes.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
*
|
||||
* @property-read \App\Models\User $user User BelongsTo relation.
|
||||
*/
|
||||
|
|
@ -23,7 +22,6 @@ class ActivityLog extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
use Notifiable;
|
||||
use BelongsToTenant;
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
|
|
@ -35,7 +33,7 @@ class ActivityLog extends Model
|
|||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'is_anonymous',
|
||||
|
|
@ -58,29 +56,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
|
||||
|
||||
/**
|
||||
|
|
@ -107,20 +82,19 @@ class ActivityLog extends Model
|
|||
$activity->model_id = $model->getKey();
|
||||
$activity->event = $eventEnum;
|
||||
$activity->data = static::getChangedColumns($activity, $model, $eventEnum);
|
||||
// @phpstan-ignore-next-line
|
||||
$activity->created_at = now();
|
||||
$activity->created_at = Carbon::now();
|
||||
$activity->saveOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get old, new columns that has changed.
|
||||
*
|
||||
* @param self $activity
|
||||
* @param \self $activity
|
||||
* @param \Illuminate\Database\Eloquent\Model $model
|
||||
* @param \App\Enums\ActivityLogs\ActivityLogsEventEnum $eventEnum
|
||||
* @return array
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array
|
||||
public static function getChangedColumns(self $activity, Model $model, ActivityLogsEventEnum $eventEnum): array|null
|
||||
{
|
||||
$modelClassName = $activity->model_class;
|
||||
/** Get type of each fields of the target model */
|
||||
|
|
@ -129,17 +103,16 @@ class ActivityLog extends Model
|
|||
$activity->model_id
|
||||
)->first();
|
||||
if ($targetModel != null) {
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
|
||||
$targetModelTypes = collect($targetModel->toArray())
|
||||
// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
->map(function ($field, $fieldIndex) use ($targetModel) {
|
||||
return Schema::getColumnType($targetModel->getTable(), $fieldIndex);
|
||||
})->toArray();
|
||||
// phpcs:enable
|
||||
/** Return old, new and type of values changed */
|
||||
return ($eventEnum === ActivityLogsEventEnum::updated) ?
|
||||
array_intersect_key($targetModelTypes, $model->getChanges()) : [];
|
||||
}
|
||||
return [];
|
||||
/** Return old, new and type of values changed */
|
||||
return ($eventEnum === ActivityLogsEventEnum::updated) ?
|
||||
array_intersect_key($targetModelTypes, $model->getChanges()) : null;
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
|
|
|||
|
|
@ -3,26 +3,18 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Casts\HtmlColor;
|
||||
use App\Helpers\ShowroomFileHelper;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
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 $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,40 +23,25 @@ use Illuminate\Http\UploadedFile;
|
|||
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',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Translatable fields array
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public $translatable = [
|
||||
'name',
|
||||
'slug',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'color' => HtmlColor::class,
|
||||
'status' => 'bool',
|
||||
'color' => HtmlColor::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -76,31 +53,25 @@ class Brand extends Model
|
|||
{
|
||||
static::creating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name);
|
||||
static::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);
|
||||
static::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,23 +80,13 @@ 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
|
||||
{
|
||||
return $this->hasMany(Range::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the products for the Brand
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
|
||||
*/
|
||||
public function products(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Product::class, Range::class);
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\Models\ActivityLog;
|
||||
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
|
||||
*/
|
||||
public const DEFAULT_FONTS = ['serif', 'sans-serif'];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'font_name',
|
||||
'font',
|
||||
'tenant_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertFontIsWoff2($model->font);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
if ($model->font) {
|
||||
$model->font = FileHelper::storeFile($model, $model->font, true, prefix: $model->tenant_id);
|
||||
$font_basename = Str::of(\pathinfo($model->font, \PATHINFO_BASENAME))->explode('.')->get(0);
|
||||
$model->font_name = Str::slug($font_basename);
|
||||
}
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertFontIsWoff2($model->font);
|
||||
|
||||
// * TRANSFORMATIONS
|
||||
if ($model->font) {
|
||||
$model->font = FileHelper::storeFile($model, $model->font, true, prefix: $model->tenant_id);
|
||||
$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');
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'font');
|
||||
});
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Assert font is Woff2
|
||||
*
|
||||
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string|null $font
|
||||
* @return void
|
||||
* @throws \Illuminate\Validation\ValidationException If font is not Woff 2 file type.
|
||||
*/
|
||||
private static function assertFontIsWoff2(
|
||||
\Illuminate\Http\UploadedFile|\SplFileInfo|string|null $font
|
||||
): void {
|
||||
$exception = ValidationException::withMessages(['font' => trans(
|
||||
'La police doit être au format Woff2, utilisez transfonter.org'
|
||||
)]);
|
||||
if (!$font) {
|
||||
return;
|
||||
}
|
||||
if (is_string($font) and Str::of($font)->lower()->explode('.')->last() !== 'woff2') {
|
||||
throw $exception;
|
||||
}
|
||||
if ($font instanceof \Illuminate\Http\UploadedFile and $font->getMimeType() !== 'font/woff2') {
|
||||
throw $exception;
|
||||
}
|
||||
if (
|
||||
!($font instanceof \Illuminate\Http\UploadedFile) and
|
||||
$font instanceof \SplFileInfo and
|
||||
Str::lower($font->getExtension()) !== 'woff2'
|
||||
) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,26 +2,18 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
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.
|
||||
* @property string $slug Slug of the name for the product.
|
||||
* @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.
|
||||
*
|
||||
|
|
@ -30,43 +22,19 @@ use App\Helpers\ShowroomFileHelper;
|
|||
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',
|
||||
'description',
|
||||
'picture',
|
||||
'status',
|
||||
'range_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Translatable fields array
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public $translatable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'status' => 'bool',
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
|
|
@ -76,31 +44,25 @@ class Product extends Model
|
|||
{
|
||||
static::creating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name);
|
||||
static::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);
|
||||
static::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');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -116,16 +78,6 @@ class Product extends Model
|
|||
return $this->belongsTo(Range::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the brand that owns the Product
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
|
||||
*/
|
||||
public function brand(): HasOneThrough
|
||||
{
|
||||
return $this->hasOneThrough(Brand::class, Range::class, 'id', 'id', 'range_id', 'brand_id');
|
||||
}
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,78 +2,37 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\Width;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Traits\Models\HasTranslations;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
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.
|
||||
* @property string $slug Slug of the name for the range.
|
||||
* @property string $picture Picture path for the range.
|
||||
* @property string $color HTML Color string.
|
||||
* @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 $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.
|
||||
*
|
||||
* @property-read \App\Models\Brand $brand Brand Belongs-to.
|
||||
* @property-read \App\Models\Font $font Font Belongs-to.
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products Products Has-many.
|
||||
*/
|
||||
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',
|
||||
'picture',
|
||||
'brand_id',
|
||||
'font_id',
|
||||
'color',
|
||||
'width',
|
||||
'order',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Translatable fields array
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
public $translatable = [
|
||||
'name',
|
||||
'slug',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'status' => 'bool',
|
||||
'width' => Width::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -85,41 +44,32 @@ class Range extends Model
|
|||
{
|
||||
static::creating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertNameIsUnique($model->name);
|
||||
static::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);
|
||||
}
|
||||
self::setOrder($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);
|
||||
static::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');
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
FileHelper::removeFile($model, 'picture');
|
||||
FileHelper::removeFile($model, 'font');
|
||||
FileStorageHelper::removeFile($model, 'picture');
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONSHIPS
|
||||
|
||||
/**
|
||||
* Get the brand that owns the Range
|
||||
* Get the brand that owns the Product
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
|
|
@ -128,20 +78,10 @@ class Range extends Model
|
|||
return $this->belongsTo(Brand::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font that owns the Range
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function font(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Font::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
|
|
@ -164,15 +104,4 @@ class Range extends Model
|
|||
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
|
||||
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($name), $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set order after the last element of the list.
|
||||
*
|
||||
* @param self $model
|
||||
* @return void
|
||||
*/
|
||||
private static function setOrder(self $model): void
|
||||
{
|
||||
$model->order = \intval(self::query()->max('order')) + 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,16 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use App\Lib\Helpers\FileStorageHelper;
|
||||
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 $title App title.
|
||||
* @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 integer $items_by_line Items per line.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
|
||||
*
|
||||
|
|
@ -28,33 +20,17 @@ class Settings extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
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',
|
||||
'title',
|
||||
'color',
|
||||
'logo',
|
||||
'home_timeout',
|
||||
'tenant_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'locales' => 'array',
|
||||
'sub_title' => 'array',
|
||||
'footer_phrase' => 'array',
|
||||
'items_by_line',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -65,130 +41,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',
|
||||
|
|
@ -54,14 +54,14 @@ class StaticPage extends Model
|
|||
{
|
||||
static::creating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertTitleIsUnique($model->title);
|
||||
static::assertTitleIsUnique($model->title);
|
||||
|
||||
// * CHANGES
|
||||
$model->slug = Str::slug($model->title);
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
// * ASSERTIONS
|
||||
self::assertTitleIsUnique($model->title, $model->id);
|
||||
static::assertTitleIsUnique($model->title, $model->id);
|
||||
|
||||
// * CHANGES
|
||||
$model->slug = Str::slug($model->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) {
|
||||
static::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) {
|
||||
static::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,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\Font;
|
||||
use App\Models\User;
|
||||
use App\Policies\Rules\UserStaticRules;
|
||||
|
||||
class FontPolicy
|
||||
{
|
||||
/**
|
||||
* 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::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Font $font
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function view(User $authUser, Font $font): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Font $font
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function update(User $authUser, Font $font): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Font $font
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function delete(User $authUser, Font $font): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Font $font
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function restore(User $authUser, Font $font): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @param \App\Models\Font $font
|
||||
* @return boolean
|
||||
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
||||
*/
|
||||
public function forceDelete(User $authUser, Font $font): bool
|
||||
{
|
||||
// phpcs:enable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,15 +100,4 @@ class RangePolicy
|
|||
// phpcs:enable
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can publish/unpublish the model.
|
||||
*
|
||||
* @param \App\Models\User $authUser
|
||||
* @return boolean
|
||||
*/
|
||||
public function changeOrder(User $authUser): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 @@
|
|||
|
||||
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,24 +41,6 @@ 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);
|
||||
}
|
||||
|
||||
|
|
@ -103,16 +80,13 @@ class RouteServiceProvider extends ServiceProvider
|
|||
}
|
||||
// * Flatten if needed
|
||||
$cspReport = collect(request()->json('csp-report'))
|
||||
->flatMap(function ($v, $k) {
|
||||
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
|
||||
})->all();
|
||||
->flatMap(function ($v, $k) {
|
||||
return [$k => is_array($v) ? collect($v)->flatten()->implode(', ') : $v];
|
||||
})->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ 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
|
||||
{
|
||||
|
|
@ -21,19 +20,9 @@ class TranslationServiceProvider extends ServiceProvider
|
|||
foreach ($this->collectLocalesStrings() as $locale) {
|
||||
// * Suported locales
|
||||
Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) {
|
||||
$phpTransFallback = $this->phpTranslations(config('app.fallback_locale'));
|
||||
$phpTransLocale = $this->phpTranslations($locale);
|
||||
$jsonTransFallback = $this->jsonTranslations(config('app.fallback_locale'));
|
||||
$jsonTransLocale = $this->jsonTranslations($locale);
|
||||
$translations = [
|
||||
'php' => ToolboxHelper::arrayMergeRecursiveDistinct(
|
||||
$phpTransLocale,
|
||||
$phpTransFallback,
|
||||
),
|
||||
'json' => ToolboxHelper::arrayMergeRecursiveDistinct(
|
||||
$jsonTransLocale,
|
||||
$jsonTransFallback,
|
||||
),
|
||||
$translations = [
|
||||
'php' => $this->phpTranslations($locale),
|
||||
'json' => $this->jsonTranslations($locale),
|
||||
];
|
||||
return $translations;
|
||||
});
|
||||
|
|
@ -47,13 +36,11 @@ class TranslationServiceProvider extends ServiceProvider
|
|||
*/
|
||||
private function collectLocalesStrings(): Collection
|
||||
{
|
||||
// * En dur dans ->keys array<int,string>.
|
||||
// @phpstan-ignore-next-line
|
||||
return collect(File::allFiles(resource_path('lang/')))->flatMap(function (SplFileInfo $file) {
|
||||
if ($file->getRelativePath() and \strpos($file->getRelativePath(), 'vendor') !== 0) {
|
||||
return [$file->getRelativePath() => ''];
|
||||
}
|
||||
})->keys()->merge(config('app.locales', []));
|
||||
})->keys();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -66,10 +53,6 @@ class TranslationServiceProvider extends ServiceProvider
|
|||
{
|
||||
$path = resource_path("lang/$locale");
|
||||
|
||||
if (!File::exists($path) or !File::isReadable($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
|
||||
$translation = $file->getBasename('.php');
|
||||
return [$translation => trans($translation, [], $locale)];
|
||||
|
|
@ -86,10 +69,10 @@ class TranslationServiceProvider extends ServiceProvider
|
|||
{
|
||||
$path = resource_path("lang/$locale.json");
|
||||
|
||||
if (!File::exists($path) or !File::isReadable($path)) {
|
||||
return [];
|
||||
if (is_string($path) && is_readable($path)) {
|
||||
return json_decode(file_get_contents($path), true) ?? [];
|
||||
}
|
||||
|
||||
return json_decode(File::get($path), true) ?? [];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -37,16 +37,18 @@ trait ChangesModelOrder
|
|||
|
||||
return DB::transaction(function () use ($request, $className, $model) {
|
||||
/** @var \Illuminate\Database\Eloquent\Model|null */
|
||||
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
|
||||
$tmp = $className::where('order', $request->direction ? '<' : '>', $model->order)
|
||||
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
|
||||
|
||||
if (!$tmp) {
|
||||
return back()->with('error', trans('crud.messages.order_not_changed'));
|
||||
}
|
||||
|
||||
$newOrder = $tmp->getOriginal('order');
|
||||
$tmp->update(['order' => $model->getOriginal('order')]);
|
||||
$model->update(['order' => $newOrder]);
|
||||
$newOrder = $tmp->order;
|
||||
$tmp->order = $model->order;
|
||||
$model->order = $newOrder;
|
||||
$tmp->saveOrFail();
|
||||
$model->saveOrFail();
|
||||
Session::flash('success', trans('crud.messages.order_changed'));
|
||||
|
||||
return back();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ trait UpdateModelPublished
|
|||
*/
|
||||
public function changePublished(Model $model)
|
||||
{
|
||||
$model->update(['published' => !$model->getOriginal('published')]);
|
||||
$model->published = !$model->published;
|
||||
$model->saveOrFail();
|
||||
return redirect()->back()->with('success', trans(__('crud.messages.publish_status_saved')));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -118,9 +118,9 @@ trait BaseEnum
|
|||
/**
|
||||
* Fetch enum using value.
|
||||
*
|
||||
* @param self|integer|string $value
|
||||
* @param string $field
|
||||
* @return self|null
|
||||
* @param \App\Traits\Enums\BaseEnum|integer|string $value
|
||||
* @param string $field
|
||||
* @return \App\Traits\Enums\BaseEnum|null
|
||||
*/
|
||||
public static function make(self|int|string $value, string $field = 'value'): self|null
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits\Models;
|
||||
|
||||
use Spatie\Translatable\HasTranslations as BaseHasTranslations;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
|
||||
trait HasTranslations
|
||||
{
|
||||
use BaseHasTranslations;
|
||||
|
||||
/**
|
||||
* Set a given attribute on the model.
|
||||
*
|
||||
* ! Fix of spatie translatable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||
*/
|
||||
public function setAttribute($key, $value)
|
||||
{
|
||||
// phpcs:enable
|
||||
if ($this->isTranslatableAttribute($key) && is_array($value)) {
|
||||
return $this->setTranslation($key, $this->getLocale(), $value);
|
||||
}
|
||||
|
||||
// Pass arrays and untranslatable attributes to the parent method.
|
||||
if (!$this->isTranslatableAttribute($key) || is_array($value)) {
|
||||
return parent::setAttribute($key, $value);
|
||||
}
|
||||
|
||||
// * Store Uploaded file
|
||||
if (\is_object($value) and $value instanceof \Illuminate\Http\UploadedFile) {
|
||||
$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);
|
||||
}
|
||||
|
||||
// If the attribute is translatable and not already translated, set a
|
||||
// translation for the current app locale.
|
||||
return $this->setTranslation($key, $this->getLocale(), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of an attribute using its mutator for array conversion.
|
||||
*
|
||||
* ! Fix of spatie translatable.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
|
||||
*/
|
||||
protected function mutateAttributeForArray($key, $value)
|
||||
{
|
||||
// phpcs:enable
|
||||
if ($this->isTranslatableAttribute($key)) {
|
||||
return $this->getTranslation($key, $this->getLocale(), $this->useFallbackLocale());
|
||||
}
|
||||
|
||||
return parent::mutateAttributeForArray($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model for a bound value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param string|null $field
|
||||
* @return \Illuminate\Database\Eloquent\Model|null
|
||||
*/
|
||||
public function resolveRouteBinding(mixed $value, $field = null)
|
||||
{
|
||||
/** @var string */
|
||||
$field = $field ?: ((request()->route() ?
|
||||
request()->route()->bindingFieldFor(Str::singular($this->getTable())) : null) ??
|
||||
$this->getRouteKeyName()
|
||||
);
|
||||
if ($this->isTranslatableAttribute($field)) {
|
||||
// Perform the query to find the parameter value in the database.
|
||||
return ToolboxHelper::queryColumnWithLocales(
|
||||
\config('app.fallback_locale'),
|
||||
$this->query(),
|
||||
$field,
|
||||
\strval($value)
|
||||
)->firstOrFail();
|
||||
}
|
||||
return parent::resolveRouteBinding($value, $field);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,7 @@ use Illuminate\Support\Facades\Cache;
|
|||
/**
|
||||
* ! Use this only on limited elements, (not on tags, or products, or posts for ex.)
|
||||
*
|
||||
* @property array<string,string|integer|float|boolean> $cacheConditions
|
||||
* Eloquent condition to cache elements (to be used in where statement).
|
||||
* @property private static array $cacheConditions Eloquent condition to cache elements (to be used in where statement).
|
||||
*/
|
||||
trait IsCachedAsCollection
|
||||
{
|
||||
|
|
@ -19,7 +18,7 @@ trait IsCachedAsCollection
|
|||
* Get all available instances from cache.
|
||||
* If cache was reset, it will fetch them from DB.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection<(int|string), mixed>
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public static function getCached(): Collection
|
||||
{
|
||||
|
|
@ -53,9 +52,7 @@ trait IsCachedAsCollection
|
|||
{
|
||||
$conditions = [];
|
||||
if (property_exists(get_called_class(), 'cacheConditions')) {
|
||||
// * https://github.com/phpstan/phpstan/issues/8808
|
||||
// @phpstan-ignore-next-line
|
||||
$conditions = collect(self::${'cacheConditions'})->all();
|
||||
$conditions = collect(static::${'cacheConditions'})->all();
|
||||
}
|
||||
Cache::put(static::getCacheKeyName(), static::queryCached(static::query())
|
||||
->where($conditions)->get()
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
@ -75,7 +75,7 @@ trait HasPicture
|
|||
(strlen($requiredWithout)) ? 'required_without:' . $requiredWithout : '',
|
||||
($size) ? 'max:' . $size : '',
|
||||
function (string $attribute, $value, callable $fail) use ($dimensions) {
|
||||
ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
|
||||
return ToolboxHelper::validatePicture($attribute, $value, $fail, $dimensions);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
@ -89,9 +89,6 @@ trait HasPicture
|
|||
* @param integer|null $minHeight
|
||||
* @param integer|null $maxWidth
|
||||
* @param integer|null $maxHeight
|
||||
* @param integer $limitMin
|
||||
* @param integer $limitMax
|
||||
* @param boolean $nullable
|
||||
* @return array
|
||||
*/
|
||||
public function picturesRules(
|
||||
|
|
@ -99,24 +96,21 @@ trait HasPicture
|
|||
?int $minWidth = null,
|
||||
?int $minHeight = null,
|
||||
?int $maxWidth = null,
|
||||
?int $maxHeight = null,
|
||||
int $limitMin = 1,
|
||||
int $limitMax = 5,
|
||||
bool $nullable = false
|
||||
?int $maxHeight = null
|
||||
): array {
|
||||
$sizes = (object) [
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
$sizes = (object)[
|
||||
'minWidth' => $minWidth ?? $this->minWidth,
|
||||
'minHeight' => $minHeight ?? $this->minHeight,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight,
|
||||
'maxWidth' => $maxWidth ?? $this->maxWidth,
|
||||
'maxHeight' => $maxHeight ?? $this->maxHeight
|
||||
];
|
||||
return [
|
||||
$name => [!$nullable ? 'required' : 'nullable', 'array', "between:{$limitMin},{$limitMax}"],
|
||||
$name => 'sometimes|array',
|
||||
"{$name}Validate" => "required_with:{$name}|array",
|
||||
"{$name}Validate.*" => [
|
||||
'required',
|
||||
function (string $attribute, $value, callable $fail) use ($sizes) {
|
||||
ToolboxHelper::validatePicture($attribute, $value, $fail, $sizes);
|
||||
return $this->validatePicture($attribute, $value, $fail, $sizes);
|
||||
}
|
||||
]
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The first thing we will do is create a new Laravel application instance
|
||||
| which serves as the "glue" for all the components of Laravel, and is
|
||||
| the IoC container for the system binding all of the various parts.
|
||||
|
|
||||
*/
|
||||
|
||||
$app = new Illuminate\Foundation\Application(
|
||||
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Bind Important Interfaces
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, we need to bind some important interfaces into the container so
|
||||
| we will be able to resolve them when needed. The kernels serve the
|
||||
| incoming requests to this application from both the web and CLI.
|
||||
|
|
||||
*/
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Http\Kernel::class,
|
||||
App\Http\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Console\Kernel::class,
|
||||
App\Console\Kernel::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||
App\Exceptions\Handler::class
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Return The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This script returns the application instance. The instance is given to
|
||||
| the calling script so we can separate the building of the instances
|
||||
| from the actual running of the application and sending responses.
|
||||
|
|
||||
*/
|
||||
|
||||
return $app;
|
||||
2
bootstrap/cache/.gitignore
vendored
2
bootstrap/cache/.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
||||
|
|
@ -1,44 +1,39 @@
|
|||
{
|
||||
"name": "uwil/showroom-bo",
|
||||
"version": "2.3.3",
|
||||
"name": "uwil/laravel-base-project",
|
||||
"version": "1.0.0",
|
||||
"type": "project",
|
||||
"description": "Showroom backoffice for Showroom add data management",
|
||||
"description": "Laravel base project reference.",
|
||||
"support": {
|
||||
"email": "equipe.web@ldc.fr"
|
||||
},
|
||||
"keywords": [
|
||||
"showroom",
|
||||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"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",
|
||||
"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"
|
||||
"sentry/sentry-laravel": "*",
|
||||
"sixlive/laravel-json-schema-assertions": "^4.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",
|
||||
"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 +47,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 +89,6 @@
|
|||
"php-http/discovery": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"audit": {
|
||||
"block-insecure": false
|
||||
},
|
||||
"gitlab-domains": [
|
||||
"lab.s-web.fr"
|
||||
]
|
||||
|
|
|
|||
10854
composer.lock
generated
10854
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,14 +82,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'locale' => 'fr',
|
||||
'locales' => [
|
||||
'fr',
|
||||
'en',
|
||||
'nl',
|
||||
'es',
|
||||
'de',
|
||||
],
|
||||
'locale' => 'fr',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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,8 @@ 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' => 'product ' . fake()->unique()->safeColorName(),
|
||||
'color' => fake()->unique()->hexColor()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Font>
|
||||
*/
|
||||
class FontFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -18,10 +17,8 @@ class ProductFactory extends Factory
|
|||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'product ' . fake()->unique()->words(3, true),
|
||||
'description' => '<strong>TEXT</strong>' . fake()->realText(),
|
||||
'status' => fake()->boolean(75),
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
'name' => 'product' . fake()->unique()->words(3, true),
|
||||
'description' => '<strong>TEXT</strong>' . fake()->realText()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -10,13 +9,6 @@ use Illuminate\Database\Eloquent\Factories\Factory;
|
|||
*/
|
||||
class RangeFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Range order
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private static int $order = 0;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
|
|
@ -25,12 +17,7 @@ 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
|
|
@ -18,11 +17,9 @@ class SettingsFactory extends Factory
|
|||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'sub_title' => fake()->unique()->realText(65),
|
||||
'footer_phrase' => fake()->unique()->realText(38),
|
||||
'title' => fake()->unique()->monthName(),
|
||||
'color' => fake()->unique()->hexColor(),
|
||||
'home_timeout' => \random_int(20, 100),
|
||||
'tenant_id' => Tenant::query()->first('id'),
|
||||
'items_by_line' => \random_int(3, 6),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -34,8 +36,8 @@ class UserFactory extends Factory
|
|||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
return $this->state(fn(array $attributes) => [
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB |
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue