Compare commits

...

No commits in common. "addBanner" and "issue-63" have entirely different histories.

751 changed files with 17630 additions and 87560 deletions

View file

@ -1,10 +0,0 @@
.git/
.gitlab/
.gitlab-ci.yml
*.md
.vscode/
auth.json
Dockerfile
manifest.yaml
node_modules/
production-resources.yaml

View file

@ -56,14 +56,6 @@ MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
MAIL_LIST_BREEDER=
MAIL_LIST_COMMITMENTS=
MAIL_LIST_EXPORT=
MAIL_LIST_FINANCE=
MAIL_LIST_INFO=
MAIL_LIST_PRESS=
MAIL_LIST_RESTAURATION=
VITE_APP_NAME="${APP_NAME}"
# TELESCOPE
@ -75,23 +67,3 @@ SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=
# GOOGLE
GOOGLE_MAPS_KEY=
# MEILISEARCH
SCOUT_PREFIX=
MEILISEARCH_HOST=
MEILISEARCH_KEY=
# ADMIN SECURE
UWIL_ADMIN_SECURE_CIDR_LIST=""
UWIL_ADMIN_SECURE_CIDR_PROJECT_ALLOWED_LIST=""
# MATOMO
MATOMO_SITE_ID=
# RECAPTCHA
GOOGLE_RECAPTCHA_PUBLIC=
GOOGLE_RECAPTCHA_PRIVATE=
GOOGLE_RECAPTCHA_VERSION=

View file

@ -66,10 +66,5 @@ module.exports = {
},
],
'prettier/prettier': 'error',
"no-console": [
"warn", {
allow: ["warn", "error"],
},
],
},
}

37
.gitignore vendored
View file

@ -16,53 +16,16 @@
/public/build
/public/hot
/public/storage
/public/apple-touch-icon.png
/public/safari-pinned-tab.svg
/public/site.webmanifest
/public/web-app-manifest-192x192.png
/public/web-app-manifest-512x512.png
/storage/*.key
/storage/pail
/storage/uploads
/vendor
Homestead.json
Homestead.yaml
Thumbs.db
.npmrc
.gitconfig
# Files from new stack
.zshrc
/.composer
/.npm
.gitconfig
# Ignore public favicon
/public/favicon-back
/public/android-icon-36x36.png
/public/android-icon-48x48.png
/public/android-icon-72x72.png
/public/android-icon-96x96.png
/public/android-icon-144x144.png
/public/android-icon-192x192.png
/public/apple-icon-57x57.png
/public/apple-icon-60x60.png
/public/apple-icon-72x72.png
/public/apple-icon-76x76.png
/public/apple-icon-114x114.png
/public/apple-icon-120x120.png
/public/apple-icon-144x144.png
/public/apple-icon-152x152.png
/public/apple-icon-180x180.png
/public/browserconfig.xml
/public/favicon-16x16.png
/public/favicon-32x32.png
/public/favicon-96x96.png
/public/favicon.ico
/public/ms-icon-70x70.png
/public/ms-icon-144x144.png
/public/ms-icon-150x150.png
/public/ms-icon-310x310.png
/public/manifest.json
/public/uploads

View file

@ -1,23 +0,0 @@
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"
stages:
- lint
- security
- build
- test
- deploy

View file

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

View file

@ -1,48 +0,0 @@
FROM composer:2.8 AS vendor
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 postcss.config.js tailwind.config.js tsconfig.json vite.config.ts .eslintrc.json .stylelintrc.json /app/
COPY resources/ /app/resources/
COPY --from=vendor /app/vendor/ /app/vendor/
WORKDIR /app
RUN npm config set @ldc-web:registry https://lab.s-web.fr/api/v4/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm config set @uwil:registry=https://lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/
RUN npm config set -- '//lab.s-web.fr/api/v4/groups/ldc-web/-/packages/npm/:_authToken' "${GITLAB_PASSWORD}"
RUN npm ci && npm run prod
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/front/ /app/srcs/public/
COPY --chown=${USER}:${USER} ./resources/favicon/back/ /app/srcs/public/back/
COPY --from=vendor --chown=${USER}:${USER} /app/vendor/ /app/srcs/vendor/
COPY --from=frontend --chown=${USER}:${USER} /app/public/build/ /app/srcs/public/build/
USER root
RUN chmod -R 755 /app/srcs/storage
USER ${USER}

View file

@ -1,91 +0,0 @@
# Intégrer des images dans un formulaire Inertia
## Côté model
Le model va devoir utiliser le trait `HasUploadedFile`
Ce trait aura pour but de mettre par défaut des évenements de model permettant de gérer la suppression des fichiers lors d'un **update** (en utilisant les methode de `FileHelper`, donc un update qui ne touche pas au fichier n'aura évidemment pas d'effet) ou d'un **delete**.
A ce jour le trait permet de gérer :
- Les champs simples
- Les champs de type translatable ` {"fr": "storage/***, "en": ...}`
- Les champs de type tableau : `medias.0.image` (version bêta donc pas sûr encore pour ça)
Pour que tout fonctionne il faut renseigner dans le model un tableau avec le nom des fields images
```php
/**
* Champs contenant des images dynamiques à gérer.
*
* @var array
*/
protected array $imageFields = ['picture'];
// ici les methodes de nettoyages se focaliseront uniquement sur le champs picture.
```
Le trait par défaut va pouvoir gérer les cas de **softDelete**, un model qui n'a pas ce trait aura une supression de tous ses fichiers sur un **delete**, bien entendu dans le cas d'un model ayant le trait `SoftDeletes` les suppressions se feront au **forceDelete**.
## Request et enregistrement
La `storeRequest` d'un model ayant des images, devra importer le trait `HasInertiaPicture` et utiliser la méthode `mergeInertiaPicture`
```php
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->mergeInertiaPicture('picture');
// cette partie est à revoir car le paramètre picture ne sera sans doute plus utile
}
```
Cette méthode permet de merge le fichier de la request présent dans un tableau à part des attributes classiques...dans le tableau des attributes.
La request devra aussi, par simplicité utiliser la méthode de validation proposée par le trait
```php
'picture' => $this->pictureRules("nullable|file|mimes:jpg,jpeg,png|max:5120|dimensions:width=525,height=295"),
```
Cette méthode permet de valider soit un fichier, utilisant les validations qu'on lui passe en paramètre, soit une string (dans le cas d'un update; avec utilisation d'une regex pour vérifier que la string correspond à l'arborescence d'un fichier sur laravel) soit null si `nullable` est déclaré.
Dans le controller associé à la request, il faudra utiliser une nouvelle méthode (proposée par le trait de model)
```php
$newModel->inertiaFill($request->validated());
```
Deux possibilité, soit il n'y a pas de traitement spécifique à appliquer au model avant de le fill auquel cas cette méthode est envoyée au trait qui s'occupe de store les fichiers nécessaires, supprimer au besoin, et de fill le tout.
Soit, une relation est présente et le fill n'est pas possible en l'état, donc il faut un traitement.
Ce traitement peut être effectuer dans le controller directement :
```php
$attributes = Arr::except($request->validated(), ['categories']);
$newModel->traitInertiaFill($attributes);
```
Soit pour éviter le DRY (entre update et create par exemple), la méthode peut être utilisée directement dans le model, et renommé exactement pareil pour que ça reste transparent côté controller, auquel cas il faut "renommer" la méthode du trait pour éviter qu'elle ne s'appelle elle même :
```php
use HasUploadedFile {
HasUploadedFile::inertiaFill as traitInertiaFill;
}
public function inertiaFill(array $attributes)
{
$attributes = Arr::except($attributes, ['categories']);
$this->traitInertiaFill($attributes);
}
```
## Côté front
Un point important, lors de l'utilisation de `useForm` d'Inertia, il faudra utiliser un post au moment du submit (car utiliser un put ou un patch; pour l'update du form, ne fonctionne pas...la présence d'un fichier vide la request)
```js
const submit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const routeUrl = route(`back.${modelRoute}.${action}`, model?.id);
if (method !== Methods.PATCH && method !== Methods.POST) throw new Error("The method must be post or patch");
post(routeUrl); // on utilise post ici
};
```
Mais dans le `useForm` on surcharge la méthode :
```js
const { post, setData, data, reset } = useForm({
_method: method === Methods.POST ? 'post' : 'put',
picture: model?.picture,
});
```

View file

@ -1,46 +0,0 @@
<?php
namespace App\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class LocalDateTime implements CastsAttributes
{
/**
* Get local date time.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return \Carbon\Carbon|null
* @phpcs:disable
*/
public function get(Model $model, string $key, mixed $value, array $attributes): ?\Carbon\Carbon
{
// phpcs:enable
return $value
? Carbon::parse($value)->setTimezone(config('app.timezone'))
: null;
}
/**
* Set the locale date time.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return \Carbon\Carbon|null
* @phpcs:disable
*/
public function set(Model $model, string $key, mixed $value, array $attributes): ?\Carbon\Carbon
{
// phpcs:enable
return $value
? Carbon::parse($value, config('app.timezone'))->setTimezone('UTC')
: null;
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Console;
use App\Jobs\CleanTrashModels;
use App\Jobs\NewsStatusUpdate;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule): void
{
$schedule->job(CleanTrashModels::class)->daily();
$schedule->job(NewsStatusUpdate::class)->everyMinute();
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Contracts;
interface TranslatableModel
{
/**
* Get all translatable attributes.
*
* @param string|null $key
* @param array|null $allowedLocales
* @return array
*/
public function getTranslations(?string $key = null, ?array $allowedLocales = null): array;
/**
* Verefy that a specific attribute has locale.
*
* @param string $key
* @param string|null $locale
* @return boolean
*/
public function hasTranslation(string $key, ?string $locale = null): bool;
}

View file

@ -1,27 +0,0 @@
<?php
namespace App\Enums\Contact;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum BreedType: int
{
use BaseEnum;
case chicken = 1;
case duck = 2;
case chickenEggs = 3;
case other = 4;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::chicken->name => 'enums.breedType.chicken',
self::duck->name => 'enums.breedType.duck',
self::chickenEggs->name => 'enums.breedType.chickenEggs',
self::other->name => 'enums.breedType.other'
];
}

View file

@ -1,65 +0,0 @@
<?php
namespace App\Enums\Contact;
use App\Helpers\RouteHelper;
use Illuminate\Support\Facades\Lang;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ContactFormType: int
{
use BaseEnum;
case collaborator = 1;
case breeder = 2;
case professional = 3;
case restauration = 4;
case export = 5;
case finance = 6;
case commitments = 7;
case product = 8;
case info = 9;
case press = 10;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::collaborator->name => 'enums.contactFormType.collaborator',
self::breeder->name => 'enums.contactFormType.breeder',
self::professional->name => 'enums.contactFormType.professional',
self::restauration->name => 'enums.contactFormType.restauration',
self::export->name => 'enums.contactFormType.export',
self::finance->name => 'enums.contactFormType.finance',
self::commitments->name => 'enums.contactFormType.commitments',
self::product->name => 'enums.contactFormType.product',
self::info->name => 'enums.contactFormType.info',
self::press->name => 'enums.contactFormType.press',
];
/**
* Get the slug for each type.
*
* @return array
* @phpcs:disable Generic.Metrics.CyclomaticComplexity
*/
public function getContactMails(): ?array
{
// phpcs:enable
return match ($this) {
self::breeder => config('mail.to.breeder'),
self::restauration => config('mail.to.restauration'),
self::export => config('mail.to.export'),
self::finance => config('mail.to.finance'),
self::commitments => config('mail.to.commitments'),
self::info => config('mail.to.info'),
self::press => config('mail.to.press'),
self::collaborator,
self::product,
self::professional => null
// phpcs:ignore Squiz.Commenting.InlineComment
}; //end match
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Enums\Contact;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum CustomerAskingType: int
{
use BaseEnum;
case collectiveRestauration = 1;
case commercialRestauration = 2;
case snaking = 3;
case gastronomy = 4;
case other = 5;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::collectiveRestauration->name => 'enums.customerAskingType.collectiveRestauration',
self::commercialRestauration->name => 'enums.customerAskingType.commercialRestauration',
self::snaking->name => 'enums.customerAskingType.snaking',
self::gastronomy->name => 'enums.customerAskingType.gastronomy',
self::other->name => 'enums.customerAskingType.other',
];
}

View file

@ -1,25 +0,0 @@
<?php
namespace App\Enums\Contact;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ExportAskingType: int
{
use BaseEnum;
case customer = 1;
case provider = 2;
case other = 3;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::customer->name => 'enums.exportAskingType.customer',
self::provider->name => 'enums.exportAskingType.provider',
self::other->name => 'enums.exportAskingType.other',
];
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Enums\Contact;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum PressType: int
{
use BaseEnum;
case groupLogo = 1;
case companyLogo = 2;
case brandLogo = 3;
case photo = 4;
case other = 5;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::groupLogo->name => 'enums.pressType.groupLogo',
self::companyLogo->name => 'enums.pressType.companyLogo',
self::brandLogo->name => 'enums.pressType.brandLogo',
self::photo->name => 'enums.pressType.photo',
self::other->name => 'enums.pressType.other'
];
}

View file

@ -1,221 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum Department: string
{
use BaseEnum;
case ain = '01';
case aisne = '02';
case allier = '03';
case alpesdehauteprovence = '04';
case hautesalpes = '05';
case alpesmaritimes = '06';
case ardeche = '07';
case ardennes = '08';
case ariege = '09';
case aube = '10';
case aude = '11';
case aveyron = '12';
case bouchesdurhone = '13';
case calvados = '14';
case cantal = '15';
case charente = '16';
case charentemaritime = '17';
case cher = '18';
case correze = '19';
case corsedusud = '2A';
case hautecorse = '2B';
case cotedor = '21';
case cotesdarmor = '22';
case creuse = '23';
case dordogne = '24';
case doubs = '25';
case drome = '26';
case eure = '27';
case eureetloir = '28';
case finistere = '29';
case gard = '30';
case hautegaronne = '31';
case gers = '32';
case gironde = '33';
case herault = '34';
case illeetvilaine = '35';
case indre = '36';
case indreetloire = '37';
case isere = '38';
case jura = '39';
case landes = '40';
case loiretcher = '41';
case loire = '42';
case hauteloire = '43';
case loireatlantique = '44';
case loiret = '45';
case lot = '46';
case lotetgaronne = '47';
case lozere = '48';
case maineetloire = '49';
case manche = '50';
case marne = '51';
case hautemarne = '52';
case mayenne = '53';
case meurtheetmoselle = '54';
case meuse = '55';
case morbihan = '56';
case moselle = '57';
case nievre = '58';
case nord = '59';
case oise = '60';
case orne = '61';
case pasdecalais = '62';
case puydedome = '63';
case pyreneesatlantiques = '64';
case hautespyrenees = '65';
case pyreneesorientales = '66';
case basrhin = '67';
case hautrhin = '68';
case rhone = '69';
case hautesaone = '70';
case saoneetloire = '71';
case sarthe = '72';
case savoie = '73';
case hautesavoie = '74';
case paris = '75';
case seinemaritime = '76';
case seineetmarne = '77';
case yvelines = '78';
case deuxsevres = '79';
case somme = '80';
case tarn = '81';
case tarnetgaronne = '82';
case var = '83';
case vaucluse = '84';
case vendee = '85';
case vienne = '86';
case hautevienne = '87';
case vosges = '88';
case yonne = '89';
case territoiredebelfort = '90';
case essonne = '91';
case hautsdeseine = '92';
case seinesaintdenis = '93';
case valdemarne = '94';
case valdoise = '95';
case guadeloupe = '971';
case martinique = '972';
case guyane = '973';
case lareunion = '974';
case mayotte = '976';
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::ain->name => 'Ain',
self::aisne->name => 'Aisne',
self::allier->name => 'Allier',
self::alpesdehauteprovence->name => 'Alpes-de-Haute-Provence',
self::hautesalpes->name => 'Hautes-alpes',
self::alpesmaritimes->name => 'Alpes-maritimes',
self::ardeche->name => 'Ardèche',
self::ardennes->name => 'Ardennes',
self::ariege->name => 'Ariège',
self::aube->name => 'Aube',
self::aude->name => 'Aude',
self::aveyron->name => 'Aveyron',
self::bouchesdurhone->name => 'Bouches-du-Rhône',
self::calvados->name => 'Calvados',
self::cantal->name => 'Cantal',
self::charente->name => 'Charente',
self::charentemaritime->name => 'Charente-maritime',
self::cher->name => 'Cher',
self::correze->name => 'Corrèze',
self::corsedusud->name => 'Corse-du-sud',
self::hautecorse->name => 'Haute-Corse',
self::cotedor->name => 'Côte-d\'Or',
self::cotesdarmor->name => 'Côtes-d\'Armor',
self::creuse->name => 'Creuse',
self::dordogne->name => 'Dordogne',
self::doubs->name => 'Doubs',
self::drome->name => 'Drôme',
self::eure->name => 'Eure',
self::eureetloir->name => 'Eure-et-loir',
self::finistere->name => 'Finistère',
self::gard->name => 'Gard',
self::hautegaronne->name => 'Haute-garonne',
self::gers->name => 'Gers',
self::gironde->name => 'Gironde',
self::herault->name => 'Hérault',
self::illeetvilaine->name => 'Ille-et-vilaine',
self::indre->name => 'Indre',
self::indreetloire->name => 'Indre-et-loire',
self::isere->name => 'Isère',
self::jura->name => 'Jura',
self::landes->name => 'Landes',
self::loiretcher->name => 'Loir-et-cher',
self::loire->name => 'Loire',
self::hauteloire->name => 'Haute-loire',
self::loireatlantique->name => 'Loire-atlantique',
self::loiret->name => 'Loiret',
self::lot->name => 'Lot',
self::lotetgaronne->name => 'Lot-et-garonne',
self::lozere->name => 'Lozère',
self::maineetloire->name => 'Maine-et-loire',
self::manche->name => 'Manche',
self::marne->name => 'Marne',
self::hautemarne->name => 'Haute-marne',
self::mayenne->name => 'Mayenne',
self::meurtheetmoselle->name => 'Meurthe-et-moselle',
self::meuse->name => 'Meuse',
self::morbihan->name => 'Morbihan',
self::moselle->name => 'Moselle',
self::nievre->name => 'Nièvre',
self::nord->name => 'Nord',
self::oise->name => 'Oise',
self::orne->name => 'Orne',
self::pasdecalais->name => 'Pas-de-calais',
self::puydedome->name => 'Puy-de-dôme',
self::pyreneesatlantiques->name => 'Pyrénées-atlantiques',
self::hautespyrenees->name => 'Hautes-Pyrénées',
self::pyreneesorientales->name => 'Pyrénées-orientales',
self::basrhin->name => 'Bas-rhin',
self::hautrhin->name => 'Haut-rhin',
self::rhone->name => 'Rhône',
self::hautesaone->name => 'Haute-saône',
self::saoneetloire->name => 'Saône-et-loire',
self::sarthe->name => 'Sarthe',
self::savoie->name => 'Savoie',
self::hautesavoie->name => 'Haute-savoie',
self::paris->name => 'Paris',
self::seinemaritime->name => 'Seine-maritime',
self::seineetmarne->name => 'Seine-et-marne',
self::yvelines->name => 'Yvelines',
self::deuxsevres->name => 'Deux-sèvres',
self::somme->name => 'Somme',
self::tarn->name => 'Tarn',
self::tarnetgaronne->name => 'Tarn-et-Garonne',
self::var->name => 'Var',
self::vaucluse->name => 'Vaucluse',
self::vendee->name => 'Vendée',
self::vienne->name => 'Vienne',
self::hautevienne->name => 'Haute-vienne',
self::vosges->name => 'Vosges',
self::yonne->name => 'Yonne',
self::territoiredebelfort->name => 'Territoire de belfort',
self::essonne->name => 'Essonne',
self::hautsdeseine->name => 'Hauts-de-seine',
self::seinesaintdenis->name => 'Seine-Saint-Denis',
self::valdemarne->name => 'Val-de-marne',
self::valdoise->name => 'Val-d\'Oise',
self::guadeloupe->name => 'Guadeloupe',
self::martinique->name => 'Martinique',
self::guyane->name => 'Guyane',
self::lareunion->name => 'La réunion',
self::mayotte->name => 'Mayotte'
];
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Enums\Document;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum DocumentStatusEnum: int
{
use BaseEnum;
case unpublished = 1;
case published = 2;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::unpublished->name => 'enums.finance-documents.unpublished',
self::published->name => 'enums.finance-documents.published',
];
}

View file

@ -1,129 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum DynamicFieldTypes: int
{
use BaseEnum;
// Name must be one word in lowercase.
case string = 1;
case textarea = 2;
case wysiwyg = 3;
case number = 4;
case boolean = 5;
case url = 6;
case email = 7;
case image = 8;
case list = 9;
case file = 10;
case vimeoid = 11;
case youtubeid = 12;
case date = 13;
case color = 14;
case model = 15;
case staticroute = 16;
case video = 17;
// Specific inputs.
case position = 101;
case contactform = 102;
/**
* Labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::string->name => 'enums.dynamic-field-types.string',
self::textarea->name => 'enums.dynamic-field-types.textarea',
self::wysiwyg->name => 'enums.dynamic-field-types.wysiwyg',
self::number->name => 'enums.dynamic-field-types.number',
self::boolean->name => 'enums.dynamic-field-types.boolean',
self::url->name => 'enums.dynamic-field-types.url',
self::email->name => 'enums.dynamic-field-types.email',
self::image->name => 'enums.dynamic-field-types.image',
self::list->name => 'enums.dynamic-field-types.list',
self::file->name => 'enums.dynamic-field-types.file',
self::vimeoid->name => 'enums.dynamic-field-types.vimeoid',
self::youtubeid->name => 'enums.dynamic-field-types.youtubeid',
self::date->name => 'enums.dynamic-field-types.date',
self::color->name => 'enums.dynamic-field-types.color',
self::model->name => 'enums.dynamic-field-types.model',
self::staticroute->name => 'enums.dynamic-field-types.staticroute',
self::video->name => 'enums.dynamic-field-types.video',
// Specific inputs.
self::position->name => 'enums.dynamic-field-types.position',
self::contactform->name => 'enums.dynamic-field-types.contactform',
];
/**
* Validations definition.
*/
private const VALIDATIONS = [
self::string->name => 'required|string|between:3,255',
self::textarea->name => 'required|string|between:3,255',
self::wysiwyg->name => 'required|string|string|between:15,5000',
self::number->name => 'required|numeric|min:0',
self::boolean->name => 'required|boolean',
self::url->name => 'required|url:https',
self::email->name => 'required|email:rfc,strict,dns,spoof,filter',
self::image->name => 'required|file|mimes:jpg,jpeg,png,webp|max:5120|dimensions:width=100,height=100',
self::list->name => 'required|array|min:1|max:10',
self::file->name => 'required|file|mimes:pdf|max:5120',
self::vimeoid->name => 'required',
self::youtubeid->name => 'required',
self::date->name => 'required|date',
self::color->name => 'required|string|hex_color',
self::model->name => 'required|numeric|exists:dynamic_pages,id',
self::staticroute->name => 'required|numeric|between:1,99',
self::video->name => 'required',
// Specific inputs.
self::position->name => 'required|numeric|between:1,5',
self::contactform->name => 'required|numeric|between:1,10'
];
/**
* Get validation.
*
* @return string
*/
public function getValidation(): string
{
return self::VALIDATIONS[$this->name];
}
/**
* Is array definition.
*
* @return array
* @phpstan-ignore-next-line
*/
private const ISARRAY = [
self::string->name => false,
self::textarea->name => false,
self::wysiwyg->name => false,
self::number->name => false,
self::boolean->name => false,
self::url->name => false,
self::email->name => false,
self::image->name => false,
self::list->name => true,
self::file->name => false,
self::vimeoid->name => false,
self::youtubeid->name => false,
self::date->name => false,
self::color->name => false,
self::model->name => false,
self::staticroute->name => false,
self::video->name => false,
// Specific inputs.
self::position->name => false,
self::contactform->name => false,
];
}

View file

@ -1,25 +0,0 @@
<?php
namespace App\Enums\DynamicPages;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum PageStatusEnum: int
{
use BaseEnum;
case draft = 1;
case published = 2;
case unpublished = 3;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::draft->name => 'enums.page-status.draft',
self::published->name => 'enums.page-status.published',
self::unpublished->name => 'enums.page-status.unpublished',
];
}

View file

@ -1,27 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ElementPositionEnum: int
{
use BaseEnum;
case showleft = 1;
case showright = 2;
case showtop = 3;
case showbottom = 4;
/**
* Labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::showleft->name => 'enums.element-position.showleft',
self::showright->name => 'enums.element-position.showright',
self::showtop->name => 'enums.element-position.showtop',
self::showbottom->name => 'enums.element-position.showbottom',
];
}

View file

@ -1,50 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum ModelsList: int
{
use BaseEnum;
/**
* Model name in snake case.
* ex: dynamic_pages, products, recipes etc...
*/
case dynamic_pages = 1;
case news = 2;
case documents = 3;
/**
* Labels definition (key of the translation).
* etc: enums.model-list.dynamic-page etc...
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::dynamic_pages->name => 'enums.model-list.dynamic-page',
self::news->name => 'enums.model-list.news',
self::documents->name => 'enums.model-list.document',
];
/**
* Target field used to present the resource.
* ex: title, label, name etc...
*/
private const TARGETFIELD = [
self::dynamic_pages->name => 'title',
self::news->name => 'title',
self::documents->name => 'name',
];
/**
* Get target field.
*
* @return string
*/
public function getTargetField(): string
{
return self::TARGETFIELD[$this->name];
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Enums\News;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum NewsStatusEnum: int
{
use BaseEnum;
case unpublished = 1;
case published = 2;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::unpublished->name => 'enums.news-status.unpublished',
self::published->name => 'enums.news-status.published',
];
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Enums\News;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum NewsYearsEnum: int
{
use BaseEnum;
case nineteen = 1;
case twenty = 2;
case twentyOne = 3;
case twentyTwo = 4;
case twentyThree = 5;
case twentyFour = 6;
/**
* Labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::nineteen->name => '2019/2020',
self::twenty->name => '2020/2021',
self::twentyOne->name => '2021/2022',
self::twentyTwo->name => '2022/2023',
self::twentyThree->name => '2023/2024',
self::twentyFour->name => '2024/2025',
];
/**
* Get the years to display using the app locale
*
* @return boolean
*/
public function yearsWithLocal(): bool
{
$locale = config('app.locale');
return match (true) {
$this === self::nineteen => true,
default => $locale === "fr" ? true : false,
};
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum OrdersEnum: int
{
use BaseEnum;
case asc = 1;
case desc = 2;
/**
* Labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::asc->name => 'enums.orders.asc',
self::desc->name => 'enums.orders.desc',
];
/**
* Get the orders to display using the app locale.
*
* @return boolean
*/
public function ordersWithLocal(): bool
{
$locale = config('app.locale');
return match ($this) {
self::asc,
/** @phpstan-ignore-next-line */
self::desc => true,
default => $locale === "fr" ? true : false,
};
}
/**
* Optional factory from label.
*
* @param string $name
* @return self
*/
public static function makeFromLabel(string $name): ?self
{
foreach (self::cases() as $case) {
if ($case->name() === $name) {
return $case;
}
}
return null;
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Enums;
use App\Enums\Contact\ContactFormType;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum StaticRoutesEnum: int
{
use BaseEnum;
// Set all cases for each static routes.
/**
* Get array of each route with his matching value.
*
* @param callable|null $callback
* @return array
*/
public static function getRoutes(?callable $callback = null)
{
// $dataKeyValue = array_merge([], [
// self::search->name => (object) [
// 'label' => trans('front.search.title'),
// 'route' => '/recherche?texte=LDC',
// ]
// ]);
// return collect($dataKeyValue)
// ->reject(fn($item) => is_null($item->label) || is_null($item->route))
// ->map(function ($item, $key) {
// $targetEnum = collect(self::cases())
// ->first(fn($enum) => $enum->name === $key);
// return (object) [
// 'label' => ucfirst($item->label),
// 'value' => $targetEnum->value,
// 'route' => $item->route,
// ];
// })
// ->when(!is_null($callback), fn ($collection) => $collection->filter($callback))
// ->values()
// ->toArray();
}
/**
* Get the url of a given enum value.
*
* @param integer $value
* @return string|null
*/
public static function getRoute(int $value): ?string
{
$collect = collect(self::getRoutes(fn ($route) => $route->value == $value));
return $collect->isEmpty() ? null : $collect->first()->route;
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum VideoProviderEnum: int
{
use BaseEnum;
case youtube = 1;
case vimeo = 2;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::youtube->name => 'youtube',
self::vimeo->name => 'viméo',
];
/**
* Is array definition.
*
* @return array
* @phpstan-ignore-next-line
*/
private const COLOR = [
self::youtube->name => "#FF0000",
self::vimeo->name => "#00B2EA",
];
}

View file

@ -1,62 +0,0 @@
<?php
namespace App\Enums;
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
enum YearsEnum: int
{
use BaseEnum;
case fourteen = 1;
case fifteen = 2;
case sixteen = 3;
case seventeen = 4;
case eighteen = 5;
case nineteen = 6;
case twenty = 7;
case twenty_one = 8;
case twenty_two = 9;
case twenty_three = 10;
case twenty_four = 11;
case twenty_five = 12;
case twenty_six = 13;
/**
* Optionnal labels definition.
*
* @phpstan-ignore-next-line
*/
private const LABELS = [
self::twenty_six->name => '2026/2027',
self::twenty_five->name => '2025/2026',
self::twenty_four->name => '2024/2025',
self::twenty_three->name => '2023/2024',
self::twenty_two->name => '2022/2023',
self::twenty_one->name => '2021/2022',
self::twenty->name => '2020/2021',
self::nineteen->name => '2019/2020',
self::eighteen->name => '2018/2019',
self::seventeen->name => '2017/2018',
self::sixteen->name => '2016/2017',
self::fifteen->name => '2015/2016',
self::fourteen->name => '2014/2015',
];
/**
* Optional factory from label.
*
* @param string $label
* @return self
*/
public static function makeFromLabel(string $label): ?self
{
foreach (self::cases() as $case) {
if ($case->label() === $label) {
return $case;
}
}
return null;
}
}

View file

@ -1,9 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class AbortContactMailer extends Exception
{
}

View file

@ -1,102 +0,0 @@
<?php
namespace App\Helpers;
use App\Enums\Document\DocumentStatusEnum;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class DocumentHelper
{
/**
* Extract the Issuu embed URL from an iframe or raw Issuu input.
*
* This helper is used when storing a document.
* It allows users to paste either:
* - a full Issuu iframe embed code, or
* - a direct Issuu embed URL
*
* The method normalizes the input by extracting and returning
* the usable Issuu embed URL (`https://e.issuu.com/embed.html?...`)
* that can safely be used on front-end pages.
*
* HTML entities (e.g. `&amp;`) are automatically decoded.
*
* @param string|null $issuIframe Raw Issuu iframe HTML or embed URL.
* @return string|null The normalized Issuu embed URL, or null if none is found.
*/
public static function extractDocumentFromIssuIframe(?string $issuIframe): ?string
{
$content = trim((string) $issuIframe);
if ($content === '') {
return null;
}
if (str_starts_with($content, 'https://e.issuu.com/embed.html')) {
return html_entity_decode($content, ENT_QUOTES);
}
preg_match(
'/src="(https:\/\/e\.issuu\.com\/embed\.html\?[^"]+)"/',
$content,
$matches
);
return isset($matches[1])
? html_entity_decode($matches[1], ENT_QUOTES)
: null;
}
/**
* Return a document query with all the request filters applied
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder $query
*/
public static function getQueryWithFilters(Request $request, Builder $query): \Illuminate\Database\Eloquent\Builder
{
$statusFilter = $request->status;
if ($statusFilter) {
$query->where('status', DocumentStatusEnum::make($statusFilter, 'name')->value);
}
$hasNoLinkFilter = request()->boolean('hasNoLink');
$query->when($hasNoLinkFilter, function ($query) {
$locale = app()->getLocale();
$query->where(function ($q) use ($locale) {
$q->whereNull("issuu_path->$locale")
->whereNull("path->$locale");
});
});
$categoriesFilter = $request->input('categories');
$query->when($categoriesFilter, function ($query, $categoriesFilter) {
$categoryIds = collect(explode(',', $categoriesFilter))
->map(fn($id) => (int) $id)
->filter()
->values();
$query->whereHas('categories', function ($q) use ($categoryIds) {
$q->whereIn('document_categories.id', $categoryIds);
});
});
$tagsFilter = $request->input('tags');
$query->when($tagsFilter, function ($query, $tagsFilter) {
$tagIds = collect(explode(',', $tagsFilter))
->map(fn($id) => (int) $id)
->filter()
->values();
$query->whereHas('tags', function ($q) use ($tagIds) {
$q->whereIn('document_tags.id', $tagIds);
});
});
return $query;
}
}

View file

@ -1,214 +0,0 @@
<?php
namespace App\Helpers;
use App\Enums\DynamicFieldTypes;
use App\Enums\DynamicPages\PageStatusEnum;
use App\Models\DynamicPage;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\FileHelper;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class DynamicPageHelper
{
/**
* Remove imgs from wysiwyg OR dynamic wysiwyg OR image input.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param boolean $forceDeleteImage
* In forceDelete event we need to ensure all the images will be destroyed
* @return void
*/
public static function manageImgs(Model $model, bool $forceDeleteImage = false): void
{
$locale = config('app.locale');
$currentContent = json_decode($model->getAttributes()['content'], true);
$originalContent = $model->getOriginal('content');
$availableLocales = array_keys(config('laravellocalization.supportedLocales', ['fr' => "Français"]));
// On delete it's all the locales.
$localesToProcess = ($forceDeleteImage) ? $availableLocales : [$locale];
foreach ($localesToProcess as $lang) {
if (isset($originalContent[$lang])) {
$currentContentLocal = is_array($currentContent[$lang])
? $currentContent[$lang]
: [DynamicFieldTypes::wysiwyg->name . '_' => $currentContent[$lang]];
$originalContentLocal = is_array($originalContent[$lang])
? $originalContent[$lang]
: [DynamicFieldTypes::wysiwyg->name . '_' => $originalContent[$lang]];
static::removeDynamicImages($model, $originalContentLocal, $currentContentLocal, $forceDeleteImage);
static::removeWysiwygImages($originalContentLocal, $currentContentLocal, $forceDeleteImage);
}
}
}
/**
* Remove imgs from image inputs.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param array $oldContent
* @param array $newContent
* @param boolean $forceDeleteImage
* In forceDelete event we need to ensure all the images will be destroyed
* @return void
*/
protected static function removeDynamicImages(
Model $model,
array $oldContent,
array $newContent,
bool $forceDeleteImage
): void {
$prefix = DynamicFieldTypes::image->name . '_';
$oldImages = static::extractImagePaths($oldContent, $prefix);
$newImages = static::extractImagePaths($newContent, $prefix);
$allPaths = collect(array_keys($oldImages))
->merge(array_keys($newImages))
->unique();
foreach ($allPaths as $key) {
$old = $oldImages[$key] ?? null;
$new = $newImages[$key] ?? null;
// Case n°1 : Same values when force delete.
if ((is_string($old) === is_string($new)) && $forceDeleteImage) {
$path = ToolboxHelper::mbReplace('storage/', '', $new);
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
}
// Case n°2: old = string, new = null → deletion.
if (is_string($old) && is_null($new)) {
// Remove the original file.
FileHelper::removeOldFile($model, "content.$key");
continue;
}
// Case n°3: old = null, new = string → addition.
if (is_null($old) && is_string($new)) {
// Nothing to do.
continue;
}
// Case n°4: old = string, new = other string → replacement.
if (is_string($old) && is_string($new) && $old !== $new) {
FileHelper::removeOldFile($model, "content.$key");
continue;
}
} //end foreach
}
/**
* Extract image path for fields with image key prefix.
*
* @param array $array
* @param string $prefix
* @param string $path
* @return array
*/
protected static function extractImagePaths(
array $array,
string $prefix,
string $path = ''
): array {
$result = [];
foreach ($array as $key => $value) {
$currentPath = $path === '' ? $key : "$path.$key";
if (Str::startsWith($key, $prefix) && is_string($value)) {
$result[$currentPath] = $value;
}
if (is_array($value)) {
$result += static::extractImagePaths($value, $prefix, $currentPath);
}
}
return $result;
}
/**
* Remove imgs from wysiwygs.
*
* @param array $oldContent
* @param array $newContent
* @param boolean $forceDeleteImage
* @return void
*/
protected static function removeWysiwygImages(array $oldContent, array $newContent, bool $forceDeleteImage): void
{
$prefix = DynamicFieldTypes::wysiwyg->name . '_';
static::getDynamicFieldKeys($oldContent, $newContent, $prefix)
->each(function ($key) use ($oldContent, $newContent, $forceDeleteImage) {
$old = data_get($oldContent, $key);
$new = $forceDeleteImage
? ''
: data_get($newContent, $key, '');
if ($old) {
FileHelper::removeOldModelFilesFromHtml($new ?? '', $old);
}
});
}
/**
* Get all value that correspond to a prefix in field input and group them.
*
* @param array $oldContent
* @param array $newContent
* @param string $prefix
* @return \Illuminate\Support\Collection
*/
protected static function getDynamicFieldKeys(
array $oldContent,
array $newContent,
string $prefix
): \Illuminate\Support\Collection {
$oldFieldsPath = static::extractImagePaths($oldContent, $prefix);
$newFieldsPath = static::extractImagePaths($newContent, $prefix);
return collect(array_keys($oldFieldsPath))
->merge(array_keys($newFieldsPath))
->unique();
}
/**
* Get all segments url for dynamic pages.
*
* @param \App\Models\DynamicPage $page
* @param \Illuminate\Database\Eloquent\Collection $pages
* @param string $locale
* @return array|null
*/
public static function getAllDynamicPageSegments(DynamicPage $page, Collection $pages, string $locale): ?array
{
$segments = [];
$parentId = $page->page_id ?? ($page->parentPage->id ?? null);
// Browse all relationships.
while ($parentId) {
/** @var \App\Models\DynamicPage|null $parent */
$parent = $pages->find($parentId);
if (!$parent || !$parent->status->equals(PageStatusEnum::published)) {
return null;
}
$parentSlug = $parent->getTranslation('slug', $locale);
if (! $parentSlug) {
return null;
}
array_unshift($segments, $parentSlug);
$parentId = $parent->page_id;
}
return $segments;
}
}

View file

@ -1,339 +0,0 @@
<?php
namespace App\Helpers;
class FinancePageChartHelper
{
/**
* Extracts labels and values for the "Évolution du chiffre d’affaires" line chart.
*
* @param array $item Raw data for the chart.
* @return array{labels: array, values: array} Normalized chart data.
*/
public static function getAffairEvolution(array $item): array
{
$list = $item['list_affair_values'] ?? [];
return [
'labels' => array_column($list, 'string_label'),
'values' => array_map('floatval', array_column($list, 'string_value')),
];
}
/**
* Extracts labels and values for the "Répartition du chiffre d’affaires" pie chart.
*
* @param array $item Raw data for the chart.
* @return array{labels: array, values: array} Normalized chart data.
*/
public static function getRepartitionAffair(array $item): array
{
$list = $item['list_last_repartition_affair_values'] ?? [];
return [
'labels' => array_column($list, 'string_label'),
'values' => array_map('floatval', array_column($list, 'string_value')),
];
}
/**
* Extracts active and passive values for the stacked bar chart (Bilan Simplifié).
*
* @param array $item Raw data for the chart.
* @return array{labels: array, datasets: array} Normalized stacked datasets.
*/
public static function getBilanSimplify(array $item): array
{
$active = $item['list_bilan_simplify_active_values'] ?? [];
$passive = $item['list_bilan_simplify_passive_values'] ?? [];
$datasets = [];
foreach ($active as $a) {
$datasets[] = [
'label' => $a['string_label_active_simplify'],
'data' => [floatval($a['string_value_active_simplify'])],
'stack' => 'Actif'
];
}
foreach ($passive as $p) {
$datasets[] = [
'label' => $p['string_label_passive_simplify'],
'data' => [floatval($p['string_value_passive_simplify'])],
'stack' => 'Passif'
];
}
return [
'labels' => [$item['string_label']],
'datasets' => $datasets
];
}
/**
* Extracts labels and values for the autofinancing mixed chart.
*
* @param array $item Raw data for the chart.
* @return array{labels: array, cpt: array, ivt: array} CAF and investment values.
*/
public static function getAutofinancement(array $item): array
{
$labels = [];
$valuesCPT = [];
$valuesIVT = [];
foreach ($item['list_autofinancement_values'] as $row) {
$labels[] = $row['string_autofinancement_label'];
$inner = $row['list_autofinancement_values'][0];
$valuesCPT[] = floatval($inner['string_autofinancement_capabilities']);
$valuesIVT[] = floatval($inner['string_industrial_investissment']);
}
return [
'labels' => $labels,
'cpt' => $valuesCPT,
'ivt' => $valuesIVT,
];
}
/**
* Routes chart building based on chart type.
*
* @param string $type Chart identifier.
* @param array $data Raw chart data.
* @return array Chart.js-compatible configuration.
*/
public static function buildChart(string $type, array $data): array
{
switch ($type) {
case 'list_affair':
return self::buildAffairEvolution($data);
case 'list_last_repartition_affair':
return self::buildRepartitionAffair($data);
case 'list_bilan_simplify':
return self::buildBilanSimplify($data);
case 'capacite_dautofinancement':
return self::buildAutofinancement($data);
default:
return [];
}
}
/**
* Builds a line chart configuration for the revenue evolution.
*
* @param array $item Parsed chart data.
* @return array Chart.js configuration.
*/
private static function buildAffairEvolution(array $item): array
{
$data = self::getAffairEvolution($item);
return [
'type' => 'line',
'data' => [
'labels' => $data['labels'],
'datasets' => [[
'label' => $item['string_label'],
'data' => $data['values'],
'borderColor' => 'rgba(54, 162, 235, 0.2)',
'backgroundColor' => 'rgba(54, 162, 235, 1)',
'borderWidth' => 1,
]]
],
'options' => [
'responsive' => true,
'elements' => [
'point' => [
'radius' => 12,
'hitRadius' => 14,
'hoverRadius' => 14,
'pointStyle' => 'rect'
],
'line' => [
'borderWidth' => 0,
'fill' => 'rect'
]
],
'legend' => ['display' => false]
]
];
}
/**
* Builds a pie chart configuration for CA repartition.
*
* @param array $item Parsed chart data.
* @return array Chart.js configuration.
*/
private static function buildRepartitionAffair(array $item): array
{
$data = self::getRepartitionAffair($item);
return [
'type' => 'pie',
'data' => [
'labels' => $data['labels'],
'datasets' => [[
'label' => $item['string_label'],
'data' => $data['values'],
'backgroundColor' => [
'rgba(85, 154, 213, 1)',
'rgba(64, 114, 203, 1)',
'rgba(242, 125, 32, 1)',
'rgba(165, 165, 165, 1)',
'rgba(255, 192, 0, 1)',
'rgba(75, 192, 192, 1)',
],
'borderColor' => [
'rgba(85, 154, 213, 0.2)',
'rgba(64, 114, 203, 0.2)',
'rgba(242, 125, 32, 0.2)',
'rgba(165, 165, 165, 0.2)',
'rgba(255, 192, 0, 0.2)',
'rgba(75, 192, 192, 0.2)',
],
'borderWidth' => 1
]]
],
'options' => [
'scales' => [
'y' => ['beginAtZero' => true]
],
'tooltips' => [
'callbacks' => [
'label' => 'function() { return null; }'
]
]
]
];
}
/**
* Builds a stacked bar chart for the simplified balance sheet (Actif vs Passif).
*
* @param array $item Parsed chart data.
* @return array Chart.js configuration.
*/
private static function buildBilanSimplify(array $item): array
{
$colors = [
'rgb(90, 155, 211)',
'rgb(255, 170, 0)',
'rgb(165, 165, 165)',
'rgb(238, 126, 52)',
'rgb(69, 116, 198)',
'rgb(113, 174, 71)',
'rgb(93, 169, 156)',
'rgb(253, 199, 0)',
'rgb(165, 165, 165)',
'rgb(238, 166, 52)',
'rgb(69, 80, 178)',
];
$i = 0;
$datasetActif = array_map(function ($a) use ($colors, &$i) {
return [
'label' => $a['string_label_active_simplify'],
'data' => [floatval($a['string_value_active_simplify'])],
'stack' => 'Actif',
'backgroundColor' => $colors[$i++ % count($colors)]
];
}, $item['list_bilan_simplify_active_values'] ?? []);
$datasetPassif = array_map(function ($a) use ($colors, &$i) {
return [
'label' => $a['string_label_passive_simplify'],
'data' => [floatval($a['string_value_passive_simplify'])],
'stack' => 'Passif',
'backgroundColor' => $colors[$i++ % count($colors)]
];
}, $item['list_bilan_simplify_passive_values'] ?? []);
$labels = ['Actif Passif'];
return [
'type' => 'bar',
'data' => [
'labels' => $labels,
'datasets' => array_merge($datasetActif, $datasetPassif)
],
'options' => [
'responsive' => true,
'scales' => [
'x' => ['stacked' => true],
'y' => ['stacked' => true]
],
'plugins' => [
'legend' => ['position' => 'bottom']
],
'interaction' => [
'mode' => 'index',
'intersect' => false
],
'tooltips' => [
'callbacks' => [
'title' => 'function() { return null; }'
]
]
]
];
}
/**
* Builds a mixed chart combining bars (CAF) and line (Investissements).
*
* @param array $item Parsed chart data.
* @return array Chart.js configuration.
*/
private static function buildAutofinancement(array $item): array
{
$data = self::getAutofinancement($item);
return [
'type' => 'bar',
'data' => [
'labels' => $data['labels'],
'datasets' => [
[
'type' => 'line',
'label' => "Investissements industriels",
'data' => $data['ivt'],
'borderColor' => 'rgba(231, 122, 37, 1)',
'borderWidth' => 2,
'tension' => 0.3,
'order' => 1
],
[
'type' => 'bar',
'label' => "Capacité d'autofinancement",
'data' => $data['cpt'],
'backgroundColor' => 'rgba(64, 114, 203, 1)',
'borderColor' => 'rgba(64, 114, 203, 0.2)',
'borderWidth' => 1,
'order' => 0
]
]
],
'options' => [
'responsive' => true,
'point' => [
'radius' => 6,
'hitRadius' => 8,
'hoverRadius' => 8,
],
'legend' => [
'position' => 'bottom'
],
]
];
}
}

View file

@ -1,25 +0,0 @@
<?php
namespace App\Helpers;
use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Http\Middleware\HandleInertiaRequests;
class InertiaErrorHelper
{
/**
* Render the default error vue.
*
* @param Request $request
* @param integer $status
* @return \Symfony\Component\HttpFoundation\Response
*/
public static function render(Request $request, int $status): \Symfony\Component\HttpFoundation\Response
{
$shared = (new HandleInertiaRequests())->share($request);
return Inertia::render('Errors/Default', array_merge($shared, [
'status' => $status,
]))->toResponse($request)->setStatusCode($status);
}
}

View file

@ -1,102 +0,0 @@
<?php
namespace App\Helpers;
use App\Models\DynamicPage;
use Illuminate\Routing\Route;
use Uwil\LaravelCommon\Controllers\Controller;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
class RouteHelper extends Controller
{
/**
* Check if a route name exists and return its URL.
*
* This method verifies if the specified route name is registered in Laravel.
* If the route exists, it returns the corresponding URL using the route helper.
* Otherwise, it returns a "#" as a fallback.
*
* @param string $routeName The name of the route to check.
* @param string|null $locale The target locale to get the route.
* @return string The route URL if it exists, or "#" otherwise.
*/
public static function routeNameChecker(string $routeName, mixed $locale = null): string
{
if (is_null($locale)) {
$locale = app()->getLocale();
}
$definitiveRouteName = sprintf("$routeName.%s", $locale);
if (\Illuminate\Support\Facades\Route::has($definitiveRouteName)) {
return route($definitiveRouteName);
}
return "#";
}
/**
* Return true or false, if the passed route is actually active.
*
* @param string $routeName
* @param mixed $locale
* @return boolean
*/
public static function isRouteActive(string $routeName, mixed $locale = null): bool
{
if (is_null($locale)) {
$locale = app()->getLocale();
}
$definitiveRouteName = sprintf("$routeName.%s", $locale);
return \request()->routeIs($definitiveRouteName);
}
/**
* Get the localized url in the langage switcher.
*
* @param string $localeCode LocaleCode from LaravelLocalization::getSupportedLocales().
* @return string
*/
public static function getLocalizedUrl(string $localeCode): string
{
$route = request()->route();
if (!$route) {
return LaravelLocalization::getLocalizedURL($localeCode);
}
return self::resolve(
self::getDynamicPageLocalizedUrl($route, $localeCode),
) ?? LaravelLocalization::getLocalizedURL($localeCode, null, [], false);
}
/**
* Resolve the url to be returned for the localized switcher.
*
* @param string|null ...$urls
* @return string|null
*/
private static function resolve(?string ...$urls): ?string
{
foreach ($urls as $url) {
if ($url !== null) {
return $url;
}
}
return null;
}
/**
* Return the localized url for all dynamic pages.
*
* @param \Illuminate\Routing\Route $route
* @param string $localeCode
* @return string|false|null
*/
private static function getDynamicPageLocalizedUrl(Route $route, string $localeCode): string|false|null
{
/** @var \App\Models\DynamicPage|null $dynamicPage */
$dynamicPage = $route->parameter('dynamicPage');
if ($dynamicPage instanceof DynamicPage) {
$path = $dynamicPage->getLocalizedPath($localeCode);
$defPath = $path ?? "";
return LaravelLocalization::getLocalizedURL($localeCode, "/{$defPath}", [], false);
}
return null;
}
}

View file

@ -1,111 +0,0 @@
<?php
namespace App\Helpers;
use App\Contracts\TranslatableModel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class TranslationHelper
{
/**
* Get all cached translations.
*
* @param string|null $locale
* @return array
*/
public static function getCachedTranslations(?string $locale = null): array
{
if (is_null($locale)) {
$locale = app()->getLocale();
}
return Cache::remember(sprintf('translations_%s', app()->getLocale()), 86400, function () {
$translator = App::make('translator');
$property = (new \ReflectionClass($translator))->getProperty('loaded');
$property->setAccessible(true);
return $property->getValue($translator);
});
}
/**
* Assert fallback locale translation is present.
*
* @param \App\Contracts\TranslatableModel $model
* @param array $excludeKeys Key of the model we need to keep nullable.
* @return void
* @throws \Illuminate\Validation\ValidationException Translations missing.
*/
public static function validateFallbackTranslations(TranslatableModel $model, array $excludeKeys = []): void
{
$localeFallback = config('app.fallback_locale');
$missingTranslations = [];
$excludeFields = array_merge(['slug'], $excludeKeys);
foreach (Arr::except($model->getTranslations(), $excludeFields) as $key => $value) {
if (!is_null($model->{$key}) && !$model->hasTranslation($key, $localeFallback)) {
$validationTranslations = self::getCachedTranslations()['validation'];
$field = (array_key_exists($key, $validationTranslations['attributes']))
? trans("validation.attributes.$key")
: trans("validation.custom.$key");
$missingTranslations = array_merge($missingTranslations, [
$key => trans_choice('back.missing_translations', 1, [
'field' => $field,
'locales' => Str::upper($localeFallback),
]),
]);
}
}
if ($missingTranslations) {
throw ValidationException::withMessages($missingTranslations);
}
}
/**
* Assert that a value is unique inside a JSON translation field.
*
* The uniqueness can optionally be scoped to another attribute/value pair.
* If $anotherDbAttribute and $anotherValue are provided, the uniqueness
* will be checked only within that scope. Otherwise, the value must be
* globally unique.
*
* @param \Illuminate\Database\Eloquent\Model $model The model instance being validated.
* @param string $value The translated value to check.
* @param string $dbAttribute The JSON column containing translations.
* @param string|null $anotherValue Optional value used to scope uniqueness.
* @param string|null $anotherDbAttribute Optional database column used to scope uniqueness.
* @param string|null $errorAttribute Optional attribute name used in the validation error.
* @param string|null $errorTranslation Optional translated attribute label for the error message.
*
* @return void
*
* @throws \Illuminate\Validation\ValidationException If the value is already used.
*/
public static function assertTranslationFieldIsUnique(
Model $model,
string $value,
string $dbAttribute,
?string $anotherValue = null,
?string $anotherDbAttribute = null,
?string $errorAttribute = null,
?string $errorTranslation = null,
): void {
$modelClass = get_class($model);
$query = $modelClass::query()
->whereKeyNot($model->getKey())
->whereRaw("JSON_SEARCH(`{$dbAttribute}`, 'one', ?) IS NOT NULL", [$value]);
if ($anotherDbAttribute !== null) {
$query->where($anotherDbAttribute, $anotherValue);
}
if ($query->exists()) {
throw ValidationException::withMessages([
$errorAttribute ?? $dbAttribute => trans("validation.unique", [
'attribute' => $errorTranslation ?? trans("validation.attributes.$dbAttribute"),
])
]);
}
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Company;
use Illuminate\Support\Str;
class CompanyController
{
/**
* Retrieve all published companies with their associated activity and sector.
*
* @return \Illuminate\Http\JsonResponse
*/
public function getCompanies(): \Illuminate\Http\JsonResponse
{
$companies = Company::query()->where('published', true)->with('companyActivity', 'companySector')->get();
$data = $companies->map(function ($company) {
return array_filter([
'name' => $company->name,
'logo' => $company->logo,
'informations' => $company->informations,
'responsable' => $company->responsable,
'employees' => $company->employees,
'url' => $company->url,
'latitude' => $company->latitude,
'longitude' => $company->longitude,
'type_activite' => Str::lower($company->companyActivity->name),
'filiere' => $company->companySector?->name,
], fn($value) => !is_null($value));
});
return response()->json($data);
}
}

View file

@ -1,25 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Uwil\LaravelBackend\Controllers\AuthenticatedSessionController as PackageAuthenticatedSessionController;
class AuthenticatedSessionController extends PackageAuthenticatedSessionController
{
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('backend')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return to_route('front.dynamic-pages.accueil.fr');
}
}

View file

@ -1,250 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\CompanyActivities\StoreCompanyActivityRequest;
use App\Http\Requests\Back\CompanyActivities\UpdateCompanyActivityRequest;
use App\Models\CompanyActivity;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
class CompanyActivityController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "company-activities";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(CompanyActivity::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\CompanyActivity> $query */
$query = CompanyActivity::query()->with('companies');
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->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 */
$companyActivityModels = $this->paginate($query);
return Inertia::render('CompanyActivities/Index', [
'companyActivityModels' => CommonResource::collection($companyActivityModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\CompanyActivity $company_activity
* @return \Inertia\Response
*/
public function create(CompanyActivity $company_activity): \Inertia\Response
{
return Inertia::render('CompanyActivities/Create', [
'data' => CommonResource::make($company_activity),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\CompanyActivity $company_activity
* @return \Inertia\Response
*/
public function show(CompanyActivity $company_activity): \Inertia\Response
{
$companies = $company_activity->companies()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('CompanyActivities/Show', [
'data' => CommonResource::make($company_activity),
'companies' => CommonResource::collection($companies),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\CompanyActivities\StoreCompanyActivityRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreCompanyActivityRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$pageConfigModel = new CompanyActivity();
$pageConfigModel->fill($request->validated());
if ($pageConfigModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $pageConfigModel)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst()
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst()
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\CompanyActivity $company_activity
* @return \Inertia\Response
*/
public function edit(CompanyActivity $company_activity): \Inertia\Response
{
return Inertia::render('CompanyActivities/Edit', [
'data' => CommonResource::make($company_activity),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\CompanyActivities\UpdateCompanyActivityRequest $request
* @param \App\Models\CompanyActivity $company_activity
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateCompanyActivityRequest $request,
CompanyActivity $company_activity,
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $company_activity) {
$company_activity->fill($request->validated());
if ($company_activity->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $company_activity)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::of(trans('models.classes.company'))->ucfirst()
]));
}
return redirect()->route("back.$this->modelName.edit", $company_activity)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::of(trans('models.classes.company'))->ucfirst()
]));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\CompanyActivity $company_activity
* @return \Inertia\Response
*/
public function duplicate(CompanyActivity $company_activity): \Inertia\Response
{
return $this->create($company_activity->replicate());
}
/**
* Restore the specified resource.
*
* @param integer $company_activity
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $company_activity): \Illuminate\Http\RedirectResponse
{
$company_activity = CompanyActivity::withTrashed()->findOrFail($company_activity);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company_activity->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries)
->with('success', __('crud.messages.has_been_restored', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_restored', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
/**
* Destroy the specified resource.
*
* @param \App\Models\CompanyActivity $company_activity
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(CompanyActivity $company_activity): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company_activity->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
/**
* Destroy the specified resource.
*
* @param \App\Models\CompanyActivity|integer $company_activity
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(CompanyActivity|int $company_activity): \Illuminate\Http\RedirectResponse
{
if (is_int($company_activity)) {
$company_activity = CompanyActivity::withTrashed()->findOrFail($company_activity);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company_activity->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
}

View file

@ -1,259 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\Companies\StoreCompanyRequest;
use App\Http\Requests\Back\Companies\UpdateCompanyRequest;
use App\Models\Company;
use App\Models\CompanyActivity;
use App\Models\CompanySector;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
class CompanyController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "companies";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Company::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Company> $query */
$query = Company::query()->with(['companyActivity', 'companySector']);
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'name',
'latitude',
'longitude',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.name'),
trans('validation.custom.latitude'),
trans('validation.custom.longitude'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$companyModels = $this->paginate($query);
return Inertia::render('Companies/Index', [
'companyModels' => CommonResource::collection($companyModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Company $company
* @return \Inertia\Response
*/
public function create(Company $company): \Inertia\Response
{
$companyActivityModels = CompanyActivity::getModelsCached();
$companySectorModels = CompanySector::getModelsCached();
return Inertia::render('Companies/Create', [
'data' => CommonResource::make($company),
'companyActivityModels' => $companyActivityModels,
'companySectorModels' => $companySectorModels,
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\Company $company
* @return \Inertia\Response
*/
public function show(Company $company): \Inertia\Response
{
return Inertia::render('Companies/Show', [
'data' => CommonResource::make($company->load(['companyActivity', 'companySector'])),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\Companies\StoreCompanyRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreCompanyRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$pageConfigModel = new Company();
$pageConfigModel->inertiaFill($request->validated());
if ($pageConfigModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $pageConfigModel)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst()
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst()
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Company $company
* @return \Inertia\Response
*/
public function edit(Company $company): \Inertia\Response
{
$companyActivityModels = CompanyActivity::getModelsCached();
$companySectorModels = CompanySector::getModelsCached();
return Inertia::render('Companies/Edit', [
'data' => CommonResource::make($company),
'companyActivityModels' => $companyActivityModels,
'companySectorModels' => $companySectorModels,
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\Companies\UpdateCompanyRequest $request
* @param \App\Models\Company $company
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateCompanyRequest $request, Company $company): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $company) {
$company->inertiaFill($request->validated());
if ($company->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $company)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::of(trans('models.classes.company'))->ucfirst()
]));
}
return redirect()->route("back.$this->modelName.edit", $company)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::of(trans('models.classes.company'))->ucfirst()
]));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Company $company
* @return \Inertia\Response
*/
public function duplicate(Company $company): \Inertia\Response
{
return $this->create($company->replicate());
}
/**
* Restore the specified resource.
*
* @param integer $company
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $company): \Illuminate\Http\RedirectResponse
{
$company = Company::withTrashed()->findOrFail($company);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries)
->with('success', __('crud.messages.has_been_restored', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_restored', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
/**
* Destroy the specified resource.
*
* @param \App\Models\Company $company
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Company $company): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
/**
* Destroy the specified resource.
*
* @param \App\Models\Company|integer $company
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(Company|int $company): \Illuminate\Http\RedirectResponse
{
if (is_int($company)) {
$company = Company::withTrashed()->findOrFail($company);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
}

View file

@ -1,252 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\CompanySectors\StoreCompanySectorRequest;
use App\Http\Requests\Back\CompanySectors\UpdateCompanySectorRequest;
use App\Models\CompanySector;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
class CompanySectorController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "company-sectors";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(CompanySector::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\CompanySector> $query */
$query = CompanySector::query()->with('companies');
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->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 */
$companySectorModels = $this->paginate($query);
return Inertia::render('CompanySectors/Index', [
'companySectorModels' => CommonResource::collection($companySectorModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\CompanySector $company_sector
* @return \Inertia\Response
*/
public function create(CompanySector $company_sector): \Inertia\Response
{
$companies = $company_sector->companies()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('CompanySectors/Create', [
'data' => CommonResource::make($company_sector),
'companies' => CommonResource::collection($companies),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\CompanySector $company_sector
* @return \Inertia\Response
*/
public function show(CompanySector $company_sector): \Inertia\Response
{
$companies = $company_sector->companies()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('CompanySectors/Show', [
'data' => CommonResource::make($company_sector),
'companies' => CommonResource::collection($companies),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\CompanySectors\StoreCompanySectorRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreCompanySectorRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$pageConfigModel = new CompanySector();
$pageConfigModel->fill($request->validated());
if ($pageConfigModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $pageConfigModel)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst()
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst()
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\CompanySector $company_sector
* @return \Inertia\Response
*/
public function edit(CompanySector $company_sector): \Inertia\Response
{
return Inertia::render('CompanySectors/Edit', [
'data' => CommonResource::make($company_sector),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\CompanySectors\UpdateCompanySectorRequest $request
* @param \App\Models\CompanySector $company_sector
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateCompanySectorRequest $request,
CompanySector $company_sector,
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $company_sector) {
$company_sector->fill($request->validated());
if ($company_sector->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $company_sector)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::of(trans('models.classes.company'))->ucfirst()
]));
}
return redirect()->route("back.$this->modelName.edit", $company_sector)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::of(trans('models.classes.company'))->ucfirst()
]));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\CompanySector $company_sector
* @return \Inertia\Response
*/
public function duplicate(CompanySector $company_sector): \Inertia\Response
{
return $this->create($company_sector->replicate());
}
/**
* Restore the specified resource.
*
* @param integer $company_sector
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $company_sector): \Illuminate\Http\RedirectResponse
{
$company_sector = CompanySector::withTrashed()->findOrFail($company_sector);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company_sector->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries)
->with('success', __('crud.messages.has_been_restored', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_restored', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
/**
* Destroy the specified resource.
*
* @param \App\Models\CompanySector $company_sector
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(CompanySector $company_sector): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company_sector->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
/**
* Destroy the specified resource.
*
* @param \App\Models\CompanySector|integer $company_sector
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(CompanySector|int $company_sector): \Illuminate\Http\RedirectResponse
{
if (is_int($company_sector)) {
$company_sector = CompanySector::withTrashed()->findOrFail($company_sector);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($company_sector->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries)
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::of(trans('models.classes.company'))->plural()->ucfirst(),
]));
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Enums\Contact\ContactFormType;
use App\Models\ContactCount;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
class ContactController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "contact-counts";
/**
* Display a listing of the resource.
*
* @return \Inertia\Response
*/
public function index(): \Inertia\Response
{
$query = ContactCount::query();
$contactCountModels = $this->paginate($query);
$formTypeEnum = ContactFormType::toArray();
return Inertia::render('ContactCount/Index', [
'contactCountModels' => CommonResource::collection($contactCountModels),
'contactFormType' => $formTypeEnum,
'modelName' => $this->modelName,
]);
}
}

View file

@ -1,205 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\DocumentCategories\StoreDocumentCategoryRequest;
use App\Http\Requests\Back\DocumentCategories\UpdateDocumentCategoryRequest;
use App\Models\DocumentCategory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
class DocumentCategoryController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "document-categories";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(DocumentCategory::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\DocumentCategory> $query */
$query = DocumentCategory::query()->withCount('documents');
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->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 */
$docModels = $this->paginate($query);
return Inertia::render('DocumentCategory/Index', [
'documentCategoryModel' => CommonResource::collection($docModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\DocumentCategory $document_category
* @return \Inertia\Response
*/
public function create(DocumentCategory $document_category): \Inertia\Response
{
return Inertia::render('DocumentCategory/Create', [
'data' => CommonResource::make($document_category),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\DocumentCategory $document_category
* @return \Inertia\Response
*/
public function show(DocumentCategory $document_category): \Inertia\Response
{
$documents = $document_category->documents()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('DocumentCategory/Show', [
'data' => CommonResource::make($document_category),
'documents' => CommonResource::collection($documents),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\DocumentCategories\StoreDocumentCategoryRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreDocumentCategoryRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$documentCategoryModel = new DocumentCategory();
$documentCategoryModel->fill($request->validated());
if ($documentCategoryModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $documentCategoryModel);
}
return redirect()->route("back.$this->modelName.create");
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\DocumentCategory $document_category
* @return \Inertia\Response
*/
public function edit(DocumentCategory $document_category): \Inertia\Response
{
return Inertia::render('DocumentCategory/Edit', [
'data' => CommonResource::make($document_category),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\DocumentCategories\UpdateDocumentCategoryRequest $request
* @param \App\Models\DocumentCategory $document_category
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateDocumentCategoryRequest $request,
DocumentCategory $document_category,
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $document_category) {
$document_category->fill($request->validated());
if ($document_category->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $document_category);
}
return redirect()->route("back.$this->modelName.edit", $document_category);
});
}
/**
* Restore the specified resource.
*
* @param integer $document_category
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $document_category): \Illuminate\Http\RedirectResponse
{
$document_category = DocumentCategory::withTrashed()->findOrFail($document_category);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document_category->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\DocumentCategory $document_category
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(DocumentCategory $document_category): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document_category->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\DocumentCategory|integer $document_category
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(DocumentCategory|int $document_category): \Illuminate\Http\RedirectResponse
{
if (is_int($document_category)) {
$document_category = DocumentCategory::withTrashed()->findOrFail($document_category);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document_category->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,191 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Enums\Document\DocumentStatusEnum;
use App\Enums\YearsEnum;
use App\Helpers\DocumentHelper;
use App\Http\Requests\Back\Documents\StoreDocumentRequest;
use App\Http\Requests\Back\Documents\UpdateDocumentRequest;
use App\Models\Document;
use App\Models\DocumentCategory;
use App\Models\DocumentTag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
class DocumentController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "documents";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Document::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Document> $query */
$query = DocumentHelper::getQueryWithFilters($request, Document::query()->with(['categories', 'tags']));
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'name',
'path'
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.name'),
trans('validation.custom.path'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$docModels = $this->paginate($query);
return Inertia::render('Document/Index', [
'documentsModel' => CommonResource::collection($docModels),
'searchFields' => $searchFields,
'enumDocumentStatus' => DocumentStatusEnum::toArray(),
'categories' => DocumentCategory::getModelsCached(),
'tags' => DocumentTag::getModelsCached(),
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Document $document
* @return \Inertia\Response
*/
public function create(Document $document): \Inertia\Response
{
return Inertia::render('Document/Create', [
'data' => CommonResource::make($document->load('tags')),
'enumDocumentStatus' => DocumentStatusEnum::toArray(),
'enumYears' => YearsEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\Document $document
* @return \Inertia\Response
*/
public function show(Document $document): \Inertia\Response
{
return Inertia::render('Document/Show', [
'data' => CommonResource::make($document->load('tags', 'categories')),
'enumDocumentStatus' => DocumentStatusEnum::toArray(),
'enumYears' => YearsEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\Documents\StoreDocumentRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreDocumentRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$document = new Document();
$document->inertiaFill($request->validated());
if ($document->saveOrFail()) {
$document->tags()->syncWithoutDetaching($request->input('tags', []));
$document->categories()->syncWithoutDetaching($request->input('categories', []));
return redirect()->route("back.$this->modelName.edit", $document);
}
return redirect()->route("back.$this->modelName.create");
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Document $document
* @return \Inertia\Response
*/
public function edit(Document $document): \Inertia\Response
{
return Inertia::render('Document/Edit', [
'data' => CommonResource::make($document->load('tags', 'categories')),
'enumDocumentStatus' => DocumentStatusEnum::toArray(),
'enumYears' => YearsEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\Documents\UpdateDocumentRequest $request
* @param \App\Models\Document $document
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateDocumentRequest $request, Document $document): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $document) {
$document->inertiaFill($request->validated());
if ($document->saveOrFail()) {
$document->tags()->sync($request->input('tags', []));
$document->categories()->sync($request->input('categories', []));
return redirect()->route("back.$this->modelName.edit", $document);
}
return redirect()->route("back.$this->modelName.edit", $document);
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Document $document
* @return \Inertia\Response
*/
public function duplicate(Document $document): \Inertia\Response
{
return $this->create($document->replicate());
}
/**
* Destroy the specified resource.
*
* @param \App\Models\Document $document
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Document $document): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,205 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\DocumentTags\StoreDocumentTagRequest;
use App\Http\Requests\Back\DocumentTags\UpdateDocumentTagRequest;
use App\Models\DocumentTag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
class DocumentTagController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "document-tags";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(DocumentTag::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\DocumentTag> $query */
$query = DocumentTag::query()->withCount('documents');
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->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 */
$docModels = $this->paginate($query);
return Inertia::render('DocumentTag/Index', [
'documentTagModel' => CommonResource::collection($docModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\DocumentTag $document_tag
* @return \Inertia\Response
*/
public function create(DocumentTag $document_tag): \Inertia\Response
{
return Inertia::render('DocumentTag/Create', [
'data' => CommonResource::make($document_tag),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\DocumentTag $document_tag
* @return \Inertia\Response
*/
public function show(DocumentTag $document_tag): \Inertia\Response
{
$documents = $document_tag->documents()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('DocumentTag/Show', [
'data' => CommonResource::make($document_tag),
'documents' => CommonResource::collection($documents),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\DocumentTags\StoreDocumentTagRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreDocumentTagRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$documentTag = new DocumentTag();
$documentTag->fill($request->validated());
if ($documentTag->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $documentTag);
}
return redirect()->route("back.$this->modelName.create");
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\DocumentTag $document_tag
* @return \Inertia\Response
*/
public function edit(DocumentTag $document_tag): \Inertia\Response
{
return Inertia::render('DocumentTag/Edit', [
'data' => CommonResource::make($document_tag),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\DocumentTags\UpdateDocumentTagRequest $request
* @param \App\Models\DocumentTag $document_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateDocumentTagRequest $request,
DocumentTag $document_tag,
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $document_tag) {
$document_tag->fill($request->validated());
if ($document_tag->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $document_tag);
}
return redirect()->route("back.$this->modelName.edit", $document_tag);
});
}
/**
* Restore the specified resource.
*
* @param integer $document_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $document_tag): \Illuminate\Http\RedirectResponse
{
$document_tag = DocumentTag::withTrashed()->findOrFail($document_tag);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document_tag->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\DocumentTag $document_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(DocumentTag $document_tag): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document_tag->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\DocumentTag|integer $document_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(DocumentTag|int $document_tag): \Illuminate\Http\RedirectResponse
{
if (is_int($document_tag)) {
$document_tag = DocumentTag::withTrashed()->findOrFail($document_tag);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($document_tag->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,247 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Enums\Contact\ContactFormType;
use App\Enums\DynamicPages\PageStatusEnum;
use App\Enums\ElementPositionEnum;
use App\Http\Requests\Back\DynamicPages\StoreDynamicPageRequest;
use App\Http\Requests\Back\DynamicPages\UpdateDynamicPageRequest;
use App\Models\DynamicPage;
use App\Models\PageConfig;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
class DynamicPageController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "dynamic-pages";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(DynamicPage::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\DynamicPage> $query */
$query = DynamicPage::query()->with(['parentPage', 'config']);
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed()->with([
'parentPage' => fn($q) => $q->withTrashed(),
'config' => fn($q) => $q->withTrashed()
]);
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'title',
'content',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.title'),
trans('validation.attributes.content'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$dynamicPageModels = $this->paginate($query);
return Inertia::render('DynamicPages/Index', [
'dynamicPageModels' => CommonResource::collection($dynamicPageModels),
'searchFields' => $searchFields,
'enumDynamicPageStatus' => PageStatusEnum::toArray(),
'enumElementPosition' => ElementPositionEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\DynamicPage $dynamic_page
* @return \Inertia\Response
*/
public function create(DynamicPage $dynamic_page): \Inertia\Response
{
$dynamicPageModels = DynamicPage::getModelsCached();
$pageConfigModels = PageConfig::getModelsCached();
return Inertia::render('DynamicPages/Create', [
'data' => CommonResource::make($dynamic_page),
'dynamicPageModels' => $dynamicPageModels,
'pageConfigModels' => $pageConfigModels,
'enumDynamicPageStatus' => PageStatusEnum::toArray(),
'enumElementPosition' => ElementPositionEnum::toArray(),
'enumContactFormType' => ContactFormType::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\DynamicPage $dynamic_page
* @return \Inertia\Response
*/
public function show(DynamicPage $dynamic_page): \Inertia\Response
{
return Inertia::render('DynamicPages/Show', [
'data' => CommonResource::make($dynamic_page->load(['parentPage', 'config'])),
'enumDynamicPageStatus' => PageStatusEnum::toArray(),
'enumElementPosition' => ElementPositionEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\DynamicPages\StoreDynamicPageRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreDynamicPageRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$dynamicPageModel = new DynamicPage();
$dynamicPageModel->inertiaFill($request->validated());
if ($dynamicPageModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $dynamicPageModel);
}
return redirect()->back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\DynamicPage $dynamic_page
* @return \Inertia\Response
*/
public function edit(DynamicPage $dynamic_page): \Inertia\Response
{
$dynamicPageModel = $dynamic_page->load(['parentPage', 'config']);
$dynamicPageModels = DynamicPage::getModelsCached()
->filter(fn ($model) => $model->getKey() !== $dynamic_page->getKey())
->values();
$pageConfigModels = PageConfig::getModelsCached();
return Inertia::render('DynamicPages/Edit', [
'data' => CommonResource::make($dynamicPageModel),
'dynamicPageModels' => $dynamicPageModels,
'pageConfigModels' => $pageConfigModels,
'enumDynamicPageStatus' => PageStatusEnum::toArray(),
'enumElementPosition' => ElementPositionEnum::toArray(),
'enumContactFormType' => ContactFormType::toArray(),
'modelName' => $this->modelName,
'dynamicPageUrl' => DynamicPage::getAccessModel($dynamic_page->id),
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\DynamicPages\UpdateDynamicPageRequest $request
* @param \App\Models\DynamicPage $dynamic_page
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateDynamicPageRequest $request,
DynamicPage $dynamic_page
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $dynamic_page) {
$dynamic_page->inertiaFill($request->validated());
if ($dynamic_page->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $dynamic_page);
}
return redirect()->route("back.$this->modelName.edit", $dynamic_page);
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\DynamicPage $dynamic_page
* @return \Inertia\Response
*/
public function duplicate(DynamicPage $dynamic_page): \Inertia\Response
{
return $this->create($dynamic_page->replicate());
}
/**
* Restore the specified resource.
*
* @param integer $dynamic_page
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $dynamic_page): \Illuminate\Http\RedirectResponse
{
$dynamic_page = DynamicPage::withTrashed()->findOrFail($dynamic_page);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($dynamic_page->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\DynamicPage $dynamic_page
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(DynamicPage $dynamic_page): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($dynamic_page->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\DynamicPage|integer $dynamic_page
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(DynamicPage|int $dynamic_page): \Illuminate\Http\RedirectResponse
{
if (is_int($dynamic_page)) {
$dynamic_page = DynamicPage::withTrashed()->findOrFail($dynamic_page);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($dynamic_page->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,204 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\ModelAccessRequest;
use App\Http\Requests\Back\ModelListRequest;
use App\Http\Requests\Back\RetrieveDataFromTokenRequest;
use App\Http\Requests\Back\UploadFileRequest;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
use Uwil\LaravelCommon\Helpers\FileHelper;
class InputsController extends Controller
{
/**
* Get class from table name.
*
* @param string $tableName
* @return string|null
*/
private function getClassFromTableName(string $tableName): ?string
{
$class = '\\App\\Models\\' . Str::studly(Str::singular($tableName));
return class_exists($class) ? $class : null;
}
/**
* Get a paginate collection from specific model.
*
* @param \App\Http\Requests\Back\ModelListRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function modelList(ModelListRequest $request): \Illuminate\Http\JsonResponse
{
// Validated data from request.
$validated = $request->validated();
// Get the class of the target model and verify its existence.
$modelClass = $this->getClassFromTableName($validated['model']);
if (!$modelClass) {
return response()->json(['error' => 'Invalid model class'], 404);
}
// Determine the field to display.
$model = new $modelClass();
$isTranslatable = method_exists($model, 'getTranslations');
$locale = app()->getLocale();
$field = $validated['field'] ?? null;
if (!$field) {
$field = collect(['title', 'name', 'label'])
->first(fn($f) => in_array($f, $model->getFillable(), true))
?? $model->getKeyName();
}
$getValue = function ($item) use ($field, $locale, $isTranslatable) {
if ($isTranslatable) {
return $item->getTranslation($field, $locale, false);
}
return data_get($item, $field);
};
// Fetch all cached models once.
$allItems = $modelClass::getModelsCached()->filter(function ($item) use ($getValue) {
return filled($getValue($item));
});
// Apply search and exclude filters.
$items = $allItems
->when(!empty($validated['exclude']), function ($collection) use ($validated) {
return $collection->reject(fn($item) => $item->id == $validated['exclude']);
})
->when(!empty($validated['search']), function ($collection) use ($validated, $getValue) {
return $collection->filter(function ($item) use ($validated, $getValue) {
$value = $getValue($item);
return is_string($value)
&& Str::contains(Str::lower($value), Str::lower($validated['search']));
});
})
->sortBy(fn ($item) => $getValue($item))
->values();
// Paginate manually.
$itemsPaginate = CollectionHelper::paginateManually(
$items,
$validated['pageSize'],
$validated['page']
);
$selectedValue = null;
// Get selected model.
if (isset($validated['selectedIds'])) {
$selectedValue = $allItems->whereIn('id', $validated['selectedIds']);
}
return response()->json([
'items' => $itemsPaginate,
'field' => $field,
'model' => $selectedValue,
]);
}
/**
* Get access models.
*
* @param \App\Http\Requests\Back\ModelAccessRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function modelAccess(ModelAccessRequest $request): \Illuminate\Http\JsonResponse
{
$validated = $request->validated();
$modelClass = $this->getClassFromTableName($validated['modelName']);
if (!$modelClass) {
return response()->json(['error' => 'Invalid model class'], 404);
}
$url = (method_exists($modelClass, 'getAccessModel'))
? $modelClass::getAccessModel($validated['selectedEntry'])
: self::getAccessModel($validated['selectedEntry'], $validated['modelName']);
$token = Crypt::encryptString(json_encode([
'modelName' => $validated['modelName'],
'modelId' => $validated['selectedEntry'],
]));
return response()->json([
'url' => $url,
'token' => $token,
]);
}
/**
* Get the url to access a specific resource.
*
* @param integer $modelId Id of the target model.
* @param string $modelName Name of the target model.
* @return string|null
*/
private function getAccessModel(int $modelId, string $modelName): string|null
{
$targetModel = $this->getClassFromTableName($modelName)::getModelsCached()->firstWhere('id', $modelId);
if (!$targetModel) {
return null;
}
return parse_url(route("front.$modelName.show", $targetModel->slug), PHP_URL_PATH) ?? "/";
}
/**
* Retrieve some data from specific token.
*
* @param \App\Http\Requests\Back\RetrieveDataFromTokenRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function retrieveDataFromToken(RetrieveDataFromTokenRequest $request): \Illuminate\Http\JsonResponse
{
$validated = $request->validated();
$payload = json_decode(Crypt::decryptString($validated['token']), true);
$modelClass = $this->getClassFromTableName($payload['modelName']);
$modelId = null;
if ($modelClass && $modelClass::getModelsCached()->contains('id', $payload['modelId'])) {
$modelId = $payload['modelId'];
}
return response()->json([
'modelName' => $payload['modelName'],
'modelId' => $modelId,
]);
}
/**
* Upload a file sent in a request.
* Used for picture upload in CKEditor.
*
* @param \App\Http\Requests\Back\UploadFileRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function uploadFile(UploadFileRequest $request): \Illuminate\Http\JsonResponse
{
// Get the class of the target model and verify its existence.
$modelName = Str::singular($request->validated()['folder']);
$modelClass = "App\\Models\\$modelName";
if (!class_exists($modelClass)) {
return response()->json(['error' => 'Invalid model class'], 404);
}
// Get a new instance of the model.
$model = new $modelClass();
/** @var \Illuminate\Http\UploadedFile $image Get the file from the request data. */
$image = $request->file('upload');
// Store the uploaded file.
$path = FileHelper::storeFile($model, $image, true, private: false);
// Return the path of the file.
return response()->json(['url' => "/{$path}"]);
}
}

View file

@ -1,208 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Enums\News\NewsStatusEnum;
use App\Http\Requests\Back\NewsCategories\StoreNewsCategoryRequest;
use App\Http\Requests\Back\NewsCategories\UpdateNewsCategoryRequest;
use App\Models\NewsCategory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
class NewsCategoryController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "news-categories";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(NewsCategory::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\News> $query */
$query = NewsCategory::query()->withCount('news');
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->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 */
$newsModels = $this->paginate($query);
return Inertia::render('NewsCategory/Index', [
'newsCategoryModels' => CommonResource::collection($newsModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\NewsCategory $news_category
* @return \Inertia\Response
*/
public function create(NewsCategory $news_category): \Inertia\Response
{
return Inertia::render('NewsCategory/Create', [
'data' => CommonResource::make($news_category),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\NewsCategory $news_category
* @return \Inertia\Response
*/
public function show(NewsCategory $news_category): \Inertia\Response
{
$news = $news_category->news()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('NewsCategory/Show', [
'data' => CommonResource::make($news_category),
'news' => CommonResource::collection($news),
'enumNewsStatus' => NewsStatusEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\NewsCategories\StoreNewsCategoryRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreNewsCategoryRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$newsModel = new NewsCategory();
$newsModel->fill($request->validated());
if ($newsModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $newsModel);
}
return redirect()->route("back.$this->modelName.create");
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\NewsCategory $news_category
* @return \Inertia\Response
*/
public function edit(NewsCategory $news_category): \Inertia\Response
{
return Inertia::render('NewsCategory/Edit', [
'data' => CommonResource::make($news_category),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\NewsCategories\UpdateNewsCategoryRequest $request
* @param \App\Models\NewsCategory $news_category
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateNewsCategoryRequest $request,
NewsCategory $news_category,
): \Illuminate\Http\RedirectResponse {
return DB::transaction(function () use ($request, $news_category) {
$news_category->fill($request->validated());
if ($news_category->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $news_category);
}
return redirect()->route("back.$this->modelName.edit", $news_category);
});
}
/**
* Restore the specified resource.
*
* @param integer $news_category
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $news_category): \Illuminate\Http\RedirectResponse
{
$news_category = NewsCategory::withTrashed()->findOrFail($news_category);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news_category->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\NewsCategory $news_category
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(NewsCategory $news_category): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news_category->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\NewsCategory|integer $news_category
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(NewsCategory|int $news_category): \Illuminate\Http\RedirectResponse
{
if (is_int($news_category)) {
$news_category = NewsCategory::withTrashed()->findOrFail($news_category);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news_category->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,211 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Enums\News\NewsStatusEnum;
use App\Http\Requests\Back\News\StoreNewsRequest;
use App\Http\Requests\Back\News\UpdateNewsRequest;
use App\Models\News;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
class NewsController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "news";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(News::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\News> $query */
$query = News::query()->with('categories');
if ($request->routeIs("back.news.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'title',
);
}
$searchFields = \implode(', ', [
trans('validation.attributes.title'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$newsModels = $this->paginate($query);
return Inertia::render('News/Index', [
'newsModels' => CommonResource::collection($newsModels),
'searchFields' => $searchFields,
'enumNewsStatus' => NewsStatusEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\News $news
* @return \Inertia\Response
*/
public function create(News $news): \Inertia\Response
{
return Inertia::render('News/Create', [
'data' => CommonResource::make($news),
'enumNewsStatus' => NewsStatusEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\News $news
* @return \Inertia\Response
*/
public function show(News $news): \Inertia\Response
{
return Inertia::render('News/Show', [
'data' => CommonResource::make($news->load('categories', 'tags')),
'enumNewsStatus' => NewsStatusEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\News\StoreNewsRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreNewsRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$newsModel = new News();
$newsModel->inertiaFill($request->validated());
if ($newsModel->saveOrFail()) {
$newsModel->categories()->syncWithoutDetaching($request->input('categories', []));
$newsModel->tags()->syncWithoutDetaching($request->input('tags', []));
return redirect()->route("back.$this->modelName.edit", $newsModel);
}
return redirect()->route("back.$this->modelName.create");
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\News $news
* @return \Inertia\Response
*/
public function edit(News $news): \Inertia\Response
{
return Inertia::render('News/Edit', [
'data' => CommonResource::make($news->load('categories', 'tags')),
'enumNewsStatus' => NewsStatusEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\News\UpdateNewsRequest $request
* @param \App\Models\News $news
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateNewsRequest $request, News $news): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $news) {
$news->inertiaFill($request->validated());
if ($news->saveOrFail()) {
$news->categories()->sync($request->input('categories', []));
$news->tags()->sync($request->input('tags', []));
return redirect()->route("back.$this->modelName.edit", $news);
}
return redirect()->route("back.$this->modelName.edit", $news);
});
}
/**
* Restore the specified resource.
*
* @param integer $news
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $news): \Illuminate\Http\RedirectResponse
{
$news = News::withTrashed()->findOrFail($news);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\News $news
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(News $news): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\News|integer $news
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(News|int $news): \Illuminate\Http\RedirectResponse
{
if (is_int($news)) {
$news = News::withTrashed()->findOrFail($news);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,206 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Enums\News\NewsStatusEnum;
use App\Http\Requests\Back\NewsTags\StoreNewsTagRequest;
use App\Http\Requests\Back\NewsTags\UpdateNewsTagRequest;
use App\Models\NewsTag;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
class NewsTagController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "news-tags";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(NewsTag::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\News> $query */
$query = NewsTag::query()->withCount('news');
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->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 */
$newsTagModels = $this->paginate($query);
return Inertia::render('NewsTag/Index', [
'newsTagModels' => CommonResource::collection($newsTagModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\NewsTag $news_tag
* @return \Inertia\Response
*/
public function create(NewsTag $news_tag): \Inertia\Response
{
return Inertia::render('NewsTag/Create', [
'data' => CommonResource::make($news_tag),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\NewsTag $news_tag
* @return \Inertia\Response
*/
public function show(NewsTag $news_tag): \Inertia\Response
{
$news = $news_tag->news()->paginate(ItemsPerPaginationEnum::five->value());
return Inertia::render('NewsTag/Show', [
'data' => CommonResource::make($news_tag),
'news' => CommonResource::collection($news),
'enumNewsStatus' => NewsStatusEnum::toArray(),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\NewsTags\StoreNewsTagRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreNewsTagRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$newsModel = new NewsTag();
$newsModel->fill($request->validated());
if ($newsModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $newsModel);
}
return redirect()->route("back.$this->modelName.create");
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\NewsTag $news_tag
* @return \Inertia\Response
*/
public function edit(NewsTag $news_tag): \Inertia\Response
{
return Inertia::render('NewsTag/Edit', [
'data' => CommonResource::make($news_tag),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\NewsTags\UpdateNewsTagRequest $request
* @param \App\Models\NewsTag $news_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateNewsTagRequest $request, NewsTag $news_tag): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $news_tag) {
$news_tag->fill($request->validated());
if ($news_tag->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $news_tag);
}
return redirect()->route("back.$this->modelName.edit", $news_tag);
});
}
/**
* Restore the specified resource.
*
* @param integer $news_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $news_tag): \Illuminate\Http\RedirectResponse
{
$news_tag = NewsTag::withTrashed()->findOrFail($news_tag);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news_tag->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\NewsTag $news_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(NewsTag $news_tag): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news_tag->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\NewsTag|integer $news_tag
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(NewsTag|int $news_tag): \Illuminate\Http\RedirectResponse
{
if (is_int($news_tag)) {
$news_tag = NewsTag::withTrashed()->findOrFail($news_tag);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($news_tag->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,218 +0,0 @@
<?php
namespace App\Http\Controllers\Back;
use App\Http\Requests\Back\PageConfigs\StorePageConfigRequest;
use App\Http\Requests\Back\PageConfigs\UpdatePageConfigRequest;
use App\Models\PageConfig;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
use Uwil\LaravelBackend\Resources\CommonResource;
use Uwil\LaravelCommon\Controllers\Controller;
class PageConfigController extends Controller
{
/** @var string $modelName Name of the model in kebab case. */
private $modelName = "page-configs";
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(PageConfig::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Inertia\Response
*/
public function index(Request $request): \Inertia\Response
{
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\PageConfig> $query */
$query = PageConfig::query();
if ($request->routeIs("back.$this->modelName.trash")) {
$query = $query->onlyTrashed();
}
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$search,
null,
'label',
'view',
'config',
);
}
$searchFields = \implode(', ', [
trans('validation.custom.label'),
trans('validation.custom.view'),
trans('validation.custom.config'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
/** Custom pagination */
$pageConfigModels = $this->paginate($query);
return Inertia::render('PageConfigs/Index', [
'pageConfigModels' => CommonResource::collection($pageConfigModels),
'searchFields' => $searchFields,
'modelName' => $this->modelName,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\PageConfig $page_config
* @return \Inertia\Response
*/
public function create(PageConfig $page_config): \Inertia\Response
{
return Inertia::render('PageConfigs/Create', [
'data' => CommonResource::make($page_config),
'modelName' => $this->modelName,
]);
}
/**
* Show a specific resource.
*
* @param \App\Models\PageConfig $page_config
* @return \Inertia\Response
*/
public function show(PageConfig $page_config): \Inertia\Response
{
return Inertia::render('PageConfigs/Show', [
'data' => CommonResource::make($page_config),
'modelName' => $this->modelName,
]);
}
/**
* Store a newly created resource.
*
* @param \App\Http\Requests\Back\PageConfigs\StorePageConfigRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StorePageConfigRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$pageConfigModel = new PageConfig();
$pageConfigModel->fill($request->validated());
if ($pageConfigModel->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $pageConfigModel);
}
return redirect()->back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\PageConfig $page_config
* @return \Inertia\Response
*/
public function edit(PageConfig $page_config): \Inertia\Response
{
return Inertia::render('PageConfigs/Edit', [
'data' => CommonResource::make($page_config->load('pages')),
'modelName' => $this->modelName,
]);
}
/**
* Update the specified resource.
*
* @param \App\Http\Requests\Back\PageConfigs\UpdatePageConfigRequest $request
* @param \App\Models\PageConfig $page_config
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdatePageConfigRequest $request, PageConfig $page_config): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $page_config) {
$page_config->fill($request->validated());
if ($page_config->saveOrFail()) {
return redirect()->route("back.$this->modelName.edit", $page_config);
}
return redirect()->route("back.$this->modelName.edit", $page_config);
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\PageConfig $page_config
* @return \Inertia\Response
*/
public function duplicate(PageConfig $page_config): \Inertia\Response
{
return $this->create($page_config->replicate());
}
/**
* Restore the specified resource.
*
* @param integer $page_config
* @return \Illuminate\Http\RedirectResponse
*/
public function restore(int $page_config): \Illuminate\Http\RedirectResponse
{
$page_config = PageConfig::withTrashed()->findOrFail($page_config);
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($page_config->restore()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\PageConfig $page_config
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(PageConfig $page_config): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($page_config->delete()) {
return redirect()->route("back.$this->modelName.index", $previousQueries);
}
return redirect()->back();
}
/**
* Destroy the specified resource.
*
* @param \App\Models\PageConfig|integer $page_config
* @return \Illuminate\Http\RedirectResponse
*/
public function forceDestroy(PageConfig|int $page_config): \Illuminate\Http\RedirectResponse
{
if (is_int($page_config)) {
$page_config = PageConfig::withTrashed()->findOrFail($page_config);
}
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
if ($page_config->forceDelete()) {
return redirect()->route("back.$this->modelName.trash", $previousQueries);
}
return redirect()->back();
}
}

View file

@ -1,83 +0,0 @@
<?php
namespace App\Http\Controllers\Front;
use App\Enums\Contact\ContactFormType;
use App\Exceptions\AbortContactMailer;
use App\Http\Requests\Front\Contact\ContactRequest;
use App\Jobs\Mails\SendContactMail;
use App\Models\ContactCount;
use App\Models\MailContactData;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\HtmlString;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class ContactController
{
/**
* Contact send.
*
* @param \App\Http\Requests\Front\Contact\ContactRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function send(ContactRequest $request): \Illuminate\Http\RedirectResponse
{
$validatedData = $request->validated();
$messageContact = new MailContactData($validatedData);
$contactMessage = "";
$hasContactError = false;
$newsletterMessage = null;
$hasNewsletterError = false;
/** @var boolean $isNewsletter */
$isNewsletter = isset($validatedData['newsletter']) ? $validatedData['newsletter'] : false;
try {
dispatch(new SendContactMail($messageContact))
->onConnection('database')
->onQueue('database');
} catch (AbortContactMailer $e) {
Log::error('Fail to build mail from contact page', [
'exception' => $e
]);
$hasContactError = true;
$contactMessage = trans('Une erreur est survenue lors de l\'envoi du message de contact.');
}
if ($isNewsletter) {
try {
SingleOptin::setStatus($request->email, true);
$newsletterMessage = trans("Votre inscription à la newsletter a été prise en compte");
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists)) {
$newsletterMessage = trans("Vous êtes déjà inscrit avec cette adresse email");
}
report($e);
$hasNewsletterError = true;
// phpcs:ignore Generic.Files.LineLength
$newsletterMessage = trans('Une erreur est survenue lors de l\'inscription à la newsletter, merci de réessayer ultérieurement.');
} catch (CrmException $e) {
report($e);
$hasNewsletterError = true;
// phpcs:ignore Generic.Files.LineLength
$newsletterMessage = trans('Une erreur est survenue lors de l\'inscription à la newsletter, merci de réessayer ultérieurement.');
}
}
ContactCount::updateOrInsert(
['type' => $validatedData['enumType']],
['submissions' => DB::raw('submissions + 1')]
);
$contactMessage = trans('Votre message a bien été envoyé.');
$message = $newsletterMessage ? $contactMessage . ' <br /> ' . $newsletterMessage : $contactMessage;
return redirect()->back()
->with(
$hasContactError || $hasNewsletterError ? 'info' : 'success',
new HtmlString($message)
);
}
}

View file

@ -2,93 +2,17 @@
namespace App\Http\Controllers\Front;
use App\Http\Requests\Front\SingleOptinRequest;
use App\Http\Requests\Front\UnsubscribeNewsletterRequest;
use Illuminate\Support\Facades\Log;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException;
use LDC\LDCCRM\Models\Customer;
use LDC\LDCCRM\Models\SingleOptin;
use Uwil\LaravelCommon\Controllers\Controller;
class HomeController extends Controller
{
/**
* unsubscribeNewsletter method
* Show home page.
*
* @param \App\Http\Requests\Front\UnsubscribeNewsletterRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function unsubscribeNewsletter(UnsubscribeNewsletterRequest $request): \Illuminate\Contracts\View\View
public function homepage(): \Illuminate\Contracts\View\View
{
$error = null;
$hashcode = $request->hashcode;
try {
SingleOptin::unsetStatusWithHashcode($hashcode);
} catch (ApiErrorException $e) {
Log::debug($e->getMessage());
if ($e->getApiError()->equals(ApiError::modelNotFound)) {
if ($customer = Customer::find($hashcode)) {
$customer->opt_in = false;
// ! Les données peuvent être parsés en retour de la crm et inconsitantes (Héritage vielle crm).
try {
$customer->save();
} catch (ValidationException $e) {
$error = null;
}
} else {
abort(404);
}
} else {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
$error = true;
}
} catch (CrmException $e) {
Log::debug($e->getMessage());
$error = true;
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
} //end try
if (!is_null($error)) {
return view('front.pages.newsletter-unsubscribe')->with(
'error',
trans('Une erreur est survenue, merci de réessayer ultérieurement')
);
}
return view('front.pages.newsletter-unsubscribe');
}
/**
* Call the CRM to add a single optin
*
* @param \App\Http\Requests\Front\SingleOptinRequest $request
* @return string|array|null|\Illuminate\Contracts\Translation\Translator
* @throws \LDC\LDCCRM\Exceptions\ValidationException ValidationException.
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
*/
public function addOptIn(
SingleOptinRequest $request,
): string|array|null|\Illuminate\Contracts\Translation\Translator {
try {
SingleOptin::setStatus($request->email, true);
return \response(trans("Votre inscription à la newsletter a été prise en compte"), 200);
} catch (ApiErrorException $e) {
// Il y a deja un customerInfo avec optin a true pour ce customer.
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists)) {
return trans('Vous êtes déjà inscrit avec cette adresse email');
}
\report($e);
return \response(trans('Une erreur est survenue, merci de réessayer ultérieurement'), 404);
} catch (CrmException $e) {
\report($e);
return \response(trans('Une erreur est survenue, merci de réessayer ultérieurement'), 404);
}
return view('front.pages.home');
}
}

View file

@ -1,20 +0,0 @@
<?php
namespace App\Http\Controllers\Front;
use App\Models\News;
use Uwil\LaravelCommon\Controllers\Controller;
class NewsController extends Controller
{
/**
* Show news show page.
*
* @param \App\Models\News $news
* @return \Illuminate\Contracts\View\View
*/
public function show(News $news): \Illuminate\Contracts\View\View
{
return view('front.pages.news.show', ['model' => $news->load('categories')]);
}
}

View file

@ -1,336 +0,0 @@
<?php
namespace App\Http\Controllers\Front;
use App\Enums\OrdersEnum;
use App\Enums\YearsEnum;
use App\Http\Requests\Front\PostsRequest;
use App\Models\Document;
use App\Models\DocumentTag;
use App\Models\News;
use App\Models\NewsTag;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Controllers\Controller;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class PostsController extends Controller
{
/** @var string */
protected const DOCUMENTS = "documents";
/** @var string */
protected const NEWS = "news";
/**
* Extract unique years from date field in a collection.
*
* @param EloquentCollection $model
* @param string $field
* @return \Illuminate\Support\Collection
*/
private function getUniqueYearsFromCollection(
EloquentCollection $model,
string $field,
): \Illuminate\Support\Collection {
return $model->pluck($field)
->map(fn($date) => Carbon::parse($date)->year)
->unique()
->values();
}
/**
* Filter input collection to only keep items whose value exists in a reference collection.
*
* @param \Illuminate\Support\Collection $array1
* @param \Illuminate\Support\Collection $array2
* @param string|null $field
* @return array
*/
private function getMatchingValues(Collection $array1, Collection $array2, ?string $field = null): array
{
return $array2
->filter(fn ($item) => $array1->contains($field ? $item[$field] : $item))
->values()
->toArray();
}
/**
* Build the common filters configuration for front-end listing pages.
* If the type is "documents" the "order" filter is included.
*
* @param string $type
* @param string $category
* @param EloquentCollection $publishedTags
* @param EloquentCollection $model
* @return array
*/
private function getFilters(
string $type,
string $category,
EloquentCollection $publishedTags,
EloquentCollection $model,
): array {
$filters = [];
/** @phpstan-ignore-next-line */
$tags = $publishedTags->map(fn(DocumentTag|NewsTag $tag) => [
'title' => $tag->name,
'tag' => $tag->slug,
'category' => $category,
])
->values();
$enumYears = collect(YearsEnum::toArray())
->map(fn($year) => YearsEnum::make((int) $year->value))
->map(fn($year) => [
'title' => trans($year->label()),
'tag' => Str::replace('/', '-', $year->label()),
'value' => $type === self::NEWS ? explode('/', $year->label())[1] : $year->value,
'category' => $category,
])
->sortDesc()
->values();
$orders = collect(OrdersEnum::toArray())
->map(fn($order) => OrdersEnum::make((int) $order->value))
->filter(fn($orderLocal) => $orderLocal->ordersWithLocal())
->map(fn($orderLocal) => [
'title' => ucfirst(trans($orderLocal->label())),
'tag' => Str::slug($orderLocal->name),
'category' => $category,
])
->values();
// Sort displayed years.
$years = $this->getMatchingValues(
$this->getUniqueYearsFromCollection($model, 'published_at'),
$enumYears,
'value'
);
if ($type === self::DOCUMENTS) {
$filters[] = [
'title' => ucfirst(__('front.news.order')),
'name' => 'order',
'fields' => $orders,
];
}
$filters[] = [
'title' => $type === self::DOCUMENTS
? ucfirst(__('front.news.document'))
: ucfirst(__('front.news.theme')),
'name' => 'tags',
'fields' => $tags,
];
$filters[] = [
'title' => $type === self::DOCUMENTS
? ucfirst(__('front.news.exercise'))
: ucfirst(__('front.news.year')),
'name' => 'years',
'fields' => $years,
];
return $filters;
}
/**
* Returns news in JSON format filtered by category and language.
* Optionally, the results can be further filtered by tags, and years.
*
* @param \App\Http\Requests\Front\PostsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxLoadNews(PostsRequest $request): \Illuminate\Http\JsonResponse
{
$validatedData = $request->validated();
$perPage = 9;
$selectedFilters = [];
/** @var string $slugCategory */
$slugCategory = $validatedData['category'];
app()->setLocale($validatedData['locale']);
$publishedNewsTags = NewsTag::getTagsByCategoryInCurrentLocale($slugCategory);
$newsModel = News::getPublishedNewsByCategoryInCurrentLocale($slugCategory);
$requestedTags = collect($validatedData['filters']['tags'] ?? '')
->filter()
->unique()
->values();
$selectedTags = $publishedNewsTags->filter(fn ($tag) => $requestedTags->contains($tag->slug ?? null))
->map(fn ($tag) => [
'id' => $tag->id,
'title' => $tag->name,
'tag' => $tag->slug,
'color' => $tag->color,
'category' => $slugCategory,
])
->values();
if ($selectedTags->isNotEmpty()) {
$newsModel = $newsModel->filter(function ($newsModel) use ($selectedTags) {
return $newsModel->tags->contains(
fn ($tag) => $selectedTags->contains('id', $tag->id)
);
});
$selectedFilters['tags'] = $selectedTags;
}
$selectedYears = collect($validatedData['filters']['years'] ?? '')
->map(fn (string $label) => YearsEnum::makeFromLabel(Str::replace('-', '/', $label)))
->filter()
->map(fn($year) => [
'title' => $year->label(),
'tag' => Str::replace('/', '-', $year->label()),
'value' => explode('/', $year->label())[1],
'category' => $slugCategory,
])
->values();
if ($selectedYears->isNotEmpty()) {
$newsModel = $newsModel->filter(function ($news) use ($selectedYears) {
return $selectedYears->contains('value', $news->published_at->year);
});
$selectedFilters['years'] = $selectedYears;
}
$newsModel = $newsModel->sortByDesc('published_at');
$filters = $this->getFilters('news', $slugCategory, $publishedNewsTags, $newsModel);
// Custom pagination.
$newsModel = CollectionHelper::paginate($newsModel, $perPage);
return response()->json([
'pagination' => [
'total' => $newsModel->total(),
'nbItems' => count($newsModel->items()),
'currentPage' => $newsModel->currentPage(),
'lastPage' => $newsModel->lastPage(),
'hasMore' => $newsModel->currentPage() < $newsModel->lastPage(),
],
'filters' => view('components.front.posts.filters-dropdowns', ['filters' => $filters])->render(),
'selectedFilters' => view('components.front.posts.selected-filters', [
'selectedFilters' => $selectedFilters,
])->render(),
'news' => view('components.front.posts.news', ['newsList' => $newsModel->items()])->render()
]);
}
/**
* Returns documents in JSON format filtered by category and language.
* Optionally, the results can be further filtered by order, tags, and years.
*
* @param \App\Http\Requests\Front\PostsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxLoadDocuments(PostsRequest $request): \Illuminate\Http\JsonResponse
{
$validatedData = $request->validated();
$perPage = 3;
$selectedFilters = [];
/** @var string $slugCategory */
$slugCategory = $validatedData['category'];
app()->setLocale($validatedData['locale']);
$documentTagModels = DocumentTag::getTagsByCategoryInCurrentLocale($slugCategory);
$documentModels = Document::getPublishedDocumentsByCategoryInCurrentLocale($slugCategory);
$requestedTags = collect($validatedData['filters']['tags'] ?? '')
->filter()
->unique()
->values();
$selectedTags = $documentTagModels
->filter(fn ($tag) => $requestedTags->contains($tag->slug ?? null))
->map(fn ($tag) => [
'id' => $tag->id,
'title' => $tag->name,
'tag' => $tag->slug,
'color' => $tag->color,
'category' => $slugCategory,
])
->values();
if ($selectedTags->isNotEmpty()) {
$documentModels = $documentModels->filter(function ($documentModel) use ($selectedTags) {
return $documentModel->tags->contains(
fn ($tag) => $selectedTags->contains('id', $tag->id)
);
});
$selectedFilters['tags'] = $selectedTags;
}
$selectedYears = collect($validatedData['filters']['years'] ?? '')
->map(fn (string $label) => YearsEnum::makeFromLabel(Str::replace('-', '/', $label)))
->filter()
->map(fn($year) => [
'title' => $year->label(),
'tag' => Str::replace('/', '-', $year->label()),
'value' => $year->value,
'category' => $slugCategory,
])
->values();
if ($selectedYears->isNotEmpty()) {
$documentModels = $documentModels->filter(function ($documentModel) use ($selectedYears) {
return $selectedYears->contains('value', $documentModel->exercice);
});
$selectedFilters['years'] = $selectedYears;
}
$selectedOrder = collect($validatedData['filters']['order'] ?? '')
->map(fn (string $label) => OrdersEnum::makeFromLabel($label))
->filter()
->map(fn($order) => [
'title' => trans($order->label()),
'tag' => $order->name(),
'category' => $slugCategory,
])
->values();
if ($selectedOrder->isNotEmpty()) {
$firstTag = $selectedOrder->map(fn($order) => $order['tag'])->first();
$documentModels = $firstTag === 'desc'
? $documentModels->sortByDesc('published_at')
: $documentModels->sortBy('published_at');
$selectedFilters['order'] = $selectedOrder->all();
} else {
$documentModels = $documentModels->sortByDesc('published_at');
}
$filters = $this->getFilters('documents', $slugCategory, $documentTagModels, $documentModels);
// Custom pagination.
$documentModels = CollectionHelper::paginate($documentModels, $perPage);
return response()->json([
'pagination' => [
'total' => $documentModels->total(),
'nbItems' => count($documentModels->items()),
'currentPage' => $documentModels->currentPage(),
'lastPage' => $documentModels->lastPage(),
'hasMore' => $documentModels->currentPage() < $documentModels->lastPage(),
],
'filters' => view('components.front.posts.filters-dropdowns', ['filters' => $filters])->render(),
'selectedFilters' => view('components.front.posts.selected-filters', [
'selectedFilters' => $selectedFilters,
])->render(),
'news' => view('components.front.posts.documents', [
'documentsList' => $documentModels->items(),
])->render()
]);
}
}

View file

@ -1,188 +0,0 @@
<?php
namespace App\Http\Controllers\Front;
use App\Http\Requests\Front\Search\SearchAjaxRequest;
use App\Http\Requests\Front\Search\SearchRequest;
use Uwil\LaravelCommon\Controllers\Controller;
class SearchController extends Controller
{
/**
* Access to the search page.
*
* @param \App\Http\Requests\Front\Search\SearchRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function search(SearchRequest $request): \Illuminate\Contracts\View\View
{
$queryKey = trans('front.search.query');
$searchTerm = $request->validated()[$queryKey];
return view('front.pages.search', compact('searchTerm'));
}
/**
* Load results from search.
*
* @param \App\Http\Requests\Front\Search\SearchAjaxRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function ajaxSearchLoadResults(SearchAjaxRequest $request): \Illuminate\Http\JsonResponse
{
$class = $request->validated()['class'];
$searchTerm = $request->validated()['search'];
$currentPage = (int) $request->validated()['page'];
$currentLocale = $request->validated()['locale'];
$perPage = 5;
app()->setLocale($currentLocale);
$searchQuery = $this->buildExactSearchQuery($searchTerm);
$models = $class::search($searchQuery, function ($engine, $query, $options) use ($class, $currentLocale) {
$options['attributesToSearchOn'] = $class::searchTransFields($currentLocale);
return $engine->search($query, $options);
})
->where('status', $class::searchStatusEnum())
->paginateRaw(perPage: $perPage, page: $currentPage);
// Render items.
$itemsHtml = collect($models['hits'])->map(fn ($result) => view('components.front.search.card', [
'class' => $class,
'result' => $result,
'mainField' => $class::searchMainField(),
'routeName' => $class::searchRouteName($result['id_key'] ?? null),
'customBreadcrumbs' => true,
'searchTerm' => $searchTerm,
'currentLocale' => $currentLocale,
])->render())->implode('');
// Pagination.
$paginationHtml = view('components.front.search.pagination', [
'models' => $models,
])->render();
return response()->json([
'items' => $itemsHtml,
'pagination' => $paginationHtml,
'totalHits' => $models['totalHits'],
]);
}
/**
* Set strict search term.
*
* @param string $searchTerm
* @return string
*/
private function buildExactSearchQuery(string $searchTerm): string
{
$words = preg_split('/\s+/', trim($searchTerm), -1, PREG_SPLIT_NO_EMPTY);
if (count($words) === 1) {
return '"' . $words[0] . '"';
}
$exactTerms = array_map(function ($word) {
return $word . '*';
}, $words);
return '+' . implode(' +', $exactTerms);
}
/**
* Highlights all search terms in a text.
*
* @param string $text
* @param string $searchTerm
* @param integer $contextLength
* @return string
* @phpcs:disable Generic.Metrics.CyclomaticComplexity
*/
public static function highlightTerms(string $text, string $searchTerm, int $contextLength = 150): string
{
// phpcs:enable
if (trim($searchTerm) === '') {
return $text;
}
// Clean terms list.
$words = preg_split('/\s+/', $searchTerm, -1, PREG_SPLIT_NO_EMPTY);
if (empty($words)) {
return $text;
}
$fullText = strip_tags($text);
$textLength = strlen($fullText);
$ranges = [];
// Collection of relevant ranges around each word.
foreach ($words as $word) {
$pattern = '/(?<!\w)' . preg_quote($word, '/') . '(?!\w)/iu';
// Check if current word match exactly with terms.
if (preg_match_all($pattern, $fullText, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[0] as $match) {
$matchPos = $match[1];
$matchLen = strlen($match[0]);
$ranges[] = [
'start' => max(0, $matchPos - $contextLength),
'end' => min($textLength, $matchPos + $matchLen + $contextLength),
];
}
}
}
// Return the entire text if there isn't matches.
if (empty($ranges)) {
return $text;
}
// Merging overlapping intervals.
usort($ranges, fn($a, $b) => $a['start'] <=> $b['start']);
$merged = [];
foreach ($ranges as $r) {
if (empty($merged) || $r['start'] > $merged[array_key_last($merged)]['end']) {
$merged[] = $r;
} else {
$merged[array_key_last($merged)]['end'] = max(
$merged[array_key_last($merged)]['end'],
$r['end']
);
}
}
$snippets = [];
foreach ($merged as $r) {
$snippet = substr($fullText, $r['start'], $r['end'] - $r['start']);
// Cut cleanly without breaking the words.
if ($r['start'] > 0) {
$snippet = preg_replace('/^\S*\s/', '…', $snippet, 1);
}
if ($r['end'] < $textLength) {
$snippet = preg_replace('/\s\S*$/', '…', $snippet, 1);
}
// Highlighting searched words.
foreach ($words as $word) {
$pattern = '/(?<!\w)' . preg_quote($word, '/') . '(?!\w)/iu';
$snippet = preg_replace(
$pattern,
'<span class="search-highlight">$0</span>',
$snippet
);
}
$snippets[] = trim($snippet);
if (count($snippets) >= 3) {
break;
}
} //end foreach
$result = implode('… ', $snippets);
$result = preg_replace('/\s*…\s*(?:…\s*)+/', ' … ', $result);
return trim($result);
}
}

View file

@ -1,69 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
class ForceLocale
{
/**
* This middleware forces the locale:
* - when it is changed from the back office
* - and when the form submitted in the request belongs to a different locale
*
* The issue occurred when two browser tabs were open in different locales:
* the data being saved could end up using the other tab’s locale.
*
* @param \Illuminate\Http\Request $request
* @param Closure $next
* @return mixed
* @throws ValidationException Not supported locale.
*/
public function handle(Request $request, Closure $next): mixed
{
$routeName = $request->route()?->getName();
if (! $routeName || ! Str::startsWith($routeName, 'back.')) {
return $next($request);
}
$locale = (Session::has('lang')) ? Session::get('lang') : config('app.fallback_locale');
App::setLocale($locale);
// 1. Is Post method or Inertia request ?
if (
in_array($request->method(), ['POST', 'PUT', 'PATCH'], true) and
($request->headers->has('X-Inertia') && $request->headers->get('X-Inertia') === 'true')
) {
// 1. Request Locale
$requestLocale = $request->locale ?? config('app.fallback_locale');
// 2. If prefix is not in supportedLocales
if (!in_array($requestLocale, LaravelLocalization::getSupportedLanguagesKeys(), true)) {
throw ValidationException::withMessages(["Langue" => "Not supported locale."]);
}
// 3. Set new locale if locale and prefix are not the same
if ($requestLocale !== app()->getLocale()) {
$this->changeLang($requestLocale);
}
}
return $next($request);
}
/**
* Change lang function
*
* @param string $lang
* @return void
*/
private function changeLang(string $lang): void
{
Session::put('lang', $lang);
app()->setLocale($lang);
}
}

View file

@ -2,28 +2,12 @@
namespace App\Http\Middleware;
use App\Enums\DynamicFieldTypes;
use App\Enums\ModelsList;
use App\Enums\StaticRoutesEnum;
use App\Enums\VideoProviderEnum;
use App\Helpers\TranslationHelper;
use App\Models\Company;
use App\Models\CompanyActivity;
use App\Models\CompanySector;
use App\Models\ContactCount;
use App\Models\Document;
use App\Models\DocumentCategory;
use App\Models\DocumentTag;
use App\Models\DynamicPage;
use App\Models\News;
use App\Models\NewsCategory;
use App\Models\NewsTag;
use App\Models\PageConfig;
use Diglactic\Breadcrumbs\Breadcrumbs;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Gate;
use Inertia\Middleware;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Uwil\LaravelBackend\Enums\RoleEnum;
use Uwil\LaravelBackend\Models\User;
use Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum;
@ -46,7 +30,12 @@ class HandleInertiaRequests extends Middleware
public function share(Request $request): array
{
// Get translations from translator app.
$translations = TranslationHelper::getCachedTranslations();
$translations = Cache::remember(sprintf('translations_%s', app()->getLocale()), 86400, function () {
$translator = App::make('translator');
$property = (new \ReflectionClass($translator))->getProperty('loaded');
$property->setAccessible(true);
return $property->getValue($translator);
});
// Get all errors messages of session from middleware and redirect response.
$errors = array_filter([
@ -58,91 +47,24 @@ class HandleInertiaRequests extends Middleware
$props = [
...parent::share($request),
'appEnv' => [
'mode' => config('app.env'),
'name' => config('app.name'),
'url' => config('app.url'),
'locale' => app()->getLocale(),
'locales' => LaravelLocalization::getSupportedLanguagesKeys(),
'fallbackLocale' => app()->getFallbackLocale(),
'appEnv' => [
'mode' => config('app.env'),
'name' => config('app.name'),
'locale' => app()->getLocale(),
],
'auth' => [
'auth' => [
'user' => $request->user(),
'policies' => [
'activityLogs' => [
'viewAny' => Gate::allows('viewAny', User::class),
],
'users' => [
'users' => [
'viewAny' => Gate::allows('viewAny', User::class),
'create' => Gate::allows('create', User::class),
],
'dynamicPages' => [
'viewAny' => Gate::allows('viewAny', DynamicPage::class),
'viewTrash' => Gate::allows('viewTrash', DynamicPage::class),
'create' => Gate::allows('create', DynamicPage::class),
],
'pageConfigs' => [
'viewAny' => Gate::allows('viewAny', PageConfig::class),
'viewTrash' => Gate::allows('viewTrash', PageConfig::class),
'create' => Gate::allows('create', PageConfig::class),
],
'contact_counts' => [
"viewAny" => Gate::allows('viewAny', ContactCount::class)
],
'news' => [
'viewAny' => Gate::allows('viewAny', News::class),
'viewTrash' => Gate::allows('viewTrash', News::class),
'create' => Gate::allows('create', News::class),
],
"newsCategories" => [
'viewAny' => Gate::allows('viewAny', NewsCategory::class),
'viewTrash' => Gate::allows('viewTrash', NewsCategory::class),
'create' => Gate::allows('create', NewsCategory::class),
],
"newsTags" => [
'viewAny' => Gate::allows('viewAny', NewsTag::class),
'viewTrash' => Gate::allows('viewTrash', NewsTag::class),
'create' => Gate::allows('create', NewsTag::class),
],
'companies' => [
'viewAny' => Gate::allows('viewAny', Company::class),
'viewTrash' => Gate::allows('viewTrash', Company::class),
'create' => Gate::allows('create', Company::class),
],
'companyActivities' => [
'viewAny' => Gate::allows('viewAny', CompanyActivity::class),
'viewTrash' => Gate::allows('viewTrash', CompanyActivity::class),
'create' => Gate::allows('create', CompanyActivity::class),
],
'companySectors' => [
'viewAny' => Gate::allows('viewAny', CompanySector::class),
'viewTrash' => Gate::allows('viewTrash', CompanySector::class),
'create' => Gate::allows('create', CompanySector::class),
],
'documents' => [
'viewAny' => Gate::allows('viewAny', Document::class),
'create' => Gate::allows('create', Document::class),
],
"documentCategories" => [
'viewAny' => Gate::allows('viewAny', DocumentCategory::class),
'viewTrash' => Gate::allows('viewTrash', DocumentCategory::class),
'create' => Gate::allows('create', DocumentCategory::class),
],
'documentTags' => [
'viewAny' => Gate::allows('viewAny', DocumentTag::class),
'viewTrash' => Gate::allows('viewTrash', DocumentTag::class),
'create' => Gate::allows('create', DocumentTag::class),
],
]
],
'enumItemsPerPagination' => ItemsPerPaginationEnum::toArray(),
'enumUserRole' => RoleEnum::toArray(),
'enumDynamicFieldTypes' => DynamicFieldTypes::toArray(),
'enumModelsList' => ModelsList::toArray(),
'enumStaticRoutes' => StaticRoutesEnum::getRoutes(),
'videoProviderEnum' => VideoProviderEnum::toArray(),
'errors' => $errors,
'translations' => $translations,
'enumItemsPerPagination' => ItemsPerPaginationEnum::toArray(),
'enumUserRole' => RoleEnum::toArray(),
'errors' => $errors,
'translations' => $translations,
];
if (Breadcrumbs::exists($request->route()->getName())) {
$props = array_merge($props, [

View file

@ -1,22 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class Lang
{
/**
* Handle an incoming request.
* Not used but need it for uwil package.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, Closure $next): mixed
{
return $next($request);
}
}

View file

@ -1,81 +0,0 @@
<?php
namespace App\Http\Requests\Back\Companies;
use App\Models\Company;
use App\Traits\Requests\HasInertiaPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Uwil\LaravelCommon\Rules\WysiwygLength;
class StoreCompanyRequest extends FormRequest
{
use HasInertiaPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', Company::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->boolean('published'),
]);
$this->mergeInertiaPicture();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'required|string|between:3,255|unique:companies,name',
'logo' => $this->pictureRules(
'required|nullable|file|mimes:jpg,jpeg,png,webp|max:5120|dimensions:width=500,height=500',
),
'responsable' => 'required|nullable|string|between:3,255',
'url' => 'nullable|string|between:3,255',
'informations' => ['required', 'string', new WysiwygLength(10, 5000)],
'employees' => 'required|nullable|numeric|between:0,100000',
'latitude' => 'required|numeric|between:-90,90',
'longitude' => 'required|numeric|between:-180,180',
'activity_id' => 'required|numeric|exists:company_activities,id',
'sector_id' => 'required|nullable|numeric|exists:company_sectors,id',
'published' => 'required|boolean',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
'logo' => trans('validation.custom.logo'),
'informations' => trans('validation.custom.informations'),
'responsable' => trans('validation.custom.responsable'),
'latitude' => trans('validation.custom.latitude'),
'longitude' => trans('validation.custom.longitude'),
'activity_id' => trans('models.classes.activity'),
'sector_id' => trans('models.classes.sector'),
'published' => trans('validation.custom.published'),
];
}
}

View file

@ -1,36 +0,0 @@
<?php
namespace App\Http\Requests\Back\Companies;
use Illuminate\Support\Facades\Gate;
class UpdateCompanyRequest extends StoreCompanyRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('company'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$rules = [
'name' => [
'required',
'string',
'between:3,255',
'unique:companies,name,' . request()->company->getKey(),
],
];
return \array_merge(parent::rules(), $rules);
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Http\Requests\Back\CompanyActivities;
use App\Models\CompanyActivity;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreCompanyActivityRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', CompanyActivity::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'required|string|between:3,255',
'color' => 'required|string|hex_color',
'published' => 'required|boolean',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.custom.name'),
'color' => trans('validation.custom.color'),
'published' => trans('validation.custom.published'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\CompanyActivities;
use Illuminate\Support\Facades\Gate;
class UpdateCompanyActivityRequest extends StoreCompanyActivityRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('company_activity'));
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace App\Http\Requests\Back\CompanySectors;
use App\Models\CompanySector;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreCompanySectorRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', CompanySector::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'required|string|between:3,255',
'published' => 'required|boolean',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.custom.name'),
'published' => trans('validation.custom.published'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\CompanySectors;
use Illuminate\Support\Facades\Gate;
class UpdateCompanySectorRequest extends StoreCompanySectorRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('company_sector'));
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace App\Http\Requests\Back\DocumentCategories;
use App\Models\DocumentCategory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreDocumentCategoryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', DocumentCategory::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|string|between:3,255',
'slug' => 'required|string|between:3,255'
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\DocumentCategories;
use Illuminate\Support\Facades\Gate;
class UpdateDocumentCategoryRequest extends StoreDocumentCategoryRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('document_category'));
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Http\Requests\Back\DocumentTags;
use App\Models\DocumentTag;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreDocumentTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', DocumentTag::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|string|between:3,255',
'slug' => 'required|string|between:3,255',
'color' => 'required|string|hex_color',
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\DocumentTags;
use Illuminate\Support\Facades\Gate;
class UpdateDocumentTagRequest extends StoreDocumentTagRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('document_tag'));
}
}

View file

@ -1,72 +0,0 @@
<?php
namespace App\Http\Requests\Back\Documents;
use App\Enums\Document\DocumentStatusEnum;
use App\Enums\YearsEnum;
use App\Models\Document;
use App\Traits\Requests\HasInertiaPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class StoreDocumentRequest extends FormRequest
{
use HasInertiaPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', Document::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->mergeInertiaPicture();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|string|between:3,255',
'exercice' => ['nullable', new Enum(YearsEnum::class)],
'status' => ['required', new Enum(DocumentStatusEnum::class)],
'published_at' => 'nullable|date',
'unpublished_at' => 'nullable|date|after:published_at',
'path' => $this->pictureRules('nullable|file|mimes:pdf|max:10240'),
'issuu_path' => 'nullable|string|between:3,1000',
'is_issuu' => 'required|boolean',
'tags' => 'nullable|array',
'tags.*' => 'integer|exists:document_tags,id',
'categories' => 'nullable|array',
'categories.*' => 'integer|exists:document_categories,id',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'issuu_path' => trans('validation.custom.issuu_path'),
'is_issuu' => trans('validation.custom.is_issuu'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\Documents;
use Illuminate\Support\Facades\Gate;
class UpdateDocumentRequest extends StoreDocumentRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('document'));
}
}

View file

@ -1,110 +0,0 @@
<?php
namespace App\Http\Requests\Back\DynamicPages;
use App\Enums\DynamicPages\PageStatusEnum;
use App\Http\Requests\Back\UpdateDynamicFieldRequest;
use App\Models\DynamicPage;
use App\Traits\Requests\HasInertiaPicture;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
use Uwil\LaravelCommon\Rules\WysiwygLength;
class StoreDynamicPageRequest extends UpdateDynamicFieldRequest
{
use HasInertiaPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', DynamicPage::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'config_id' => $this->config_id != 0 ? $this->config_id : null,
'isLocked' => $this->boolean('isLocked'),
'isRoot' => $this->boolean('isRoot'),
'slug' => $this->boolean('isRoot') ? "" : $this->slug,
]);
if ($this->config_id) {
parent::prepareForValidation();
}
$this->mergeInertiaPicture();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$rules = [
'title' => 'required|string|between:3,255',
'page_id' => 'sometimes|nullable|numeric|exists:dynamic_pages,id',
'config_id' => 'sometimes|nullable|numeric|exists:page_configs,id',
'status' => ['required', new Enum(PageStatusEnum::class)],
'isLocked' => 'required|boolean',
'isRoot' => 'required|boolean',
];
if ($this->boolean('isRoot')) {
$rules['slug'] = ['present', 'nullable'];
} else {
$rules['slug'] = ['required', 'string', 'regex:/^[-A-Za-z0-9]{3,255}$/'];
}
if ($this->config_id) {
$rules = \array_merge(
['content' => 'required|array|min:1'],
parent::rules(),
$rules,
);
} else {
$rules = \array_merge([
'content' => ['required', 'string', new WysiwygLength()],
], $rules);
}
return $rules;
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
$attributes = [
'title' => trans('validation.attributes.title'),
'page_id' => trans_choice('models.classes.dynamic-page', 1),
'config_id' => trans_choice('models.classes.page-config', 1),
'status' => trans('validation.custom.status'),
'isLocked' => trans('validation.custom.locked'),
'isRoot' => trans('validation.custom.root'),
];
if ($this->config_id) {
$attributes = \array_merge(
parent::attributes(),
$attributes,
);
} else {
$attributes = \array_merge([
'content' => trans('validation.attributes.content'),
], $attributes);
}
return $attributes;
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\DynamicPages;
use Illuminate\Support\Facades\Gate;
class UpdateDynamicPageRequest extends StoreDynamicPageRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('dynamic_page'));
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Http\Requests\Back;
use App\Traits\Requests\Api\HasLaravelFailedValidation;
use Illuminate\Foundation\Http\FormRequest;
class ModelAccessRequest extends FormRequest
{
use HasLaravelFailedValidation;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check() and $this->ajax();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'modelName' => 'required|string|between:3,255|regex:/^[a-z]+(?:_[a-z]+)*$/',
'selectedEntry' => "required|numeric|exists:{$this->input('modelName')},id",
];
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace App\Http\Requests\Back;
use App\Traits\Requests\Api\HasLaravelFailedValidation;
use Illuminate\Foundation\Http\FormRequest;
class ModelListRequest extends FormRequest
{
use HasLaravelFailedValidation;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check() and $this->ajax();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'model' => 'required|string|between:3,255|not_in:users',
'selectedIds' => 'nullable|array',
'selectedIds.*' => 'required|numeric',
'field' => 'nullable|string|between:3,255',
'search' => 'nullable|string|max:255',
'exclude' => 'nullable|numeric',
'page' => 'required|numeric|min:1',
'pageSize' => 'required|numeric|min:1',
];
}
}

View file

@ -1,111 +0,0 @@
<?php
namespace App\Http\Requests\Back\News;
use App\Enums\News\NewsStatusEnum;
use App\Models\News;
use App\Rules\Video;
use App\Traits\Requests\HasInertiaPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
use Uwil\LaravelCommon\Rules\IsValidVimeoId;
use Uwil\LaravelCommon\Rules\WysiwygLength;
class StoreNewsRequest extends FormRequest
{
use HasInertiaPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', News::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->mergeInertiaPicture();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'seo_title' => 'required|string|between:3,255',
'seo_description' => 'required|string|between:3,255',
'title' => 'required|string|between:3,255',
'slug' => 'required|string|between:3,255',
'picture' => [
function ($attribute, $value, $fail) {
$extension = null;
if ($value instanceof \Illuminate\Http\UploadedFile) {
$extension = strtolower($value->getClientOriginalExtension());
} elseif (is_string($value)) {
$extension = strtolower(pathinfo($value, PATHINFO_EXTENSION));
}
if ($extension === 'gif') {
$rules = 'required|file|mimes:gif|max:5120';
} else {
$rules = 'required|file|mimes:jpg,jpeg,png,webp|max:5120|dimensions:width=525,height=295';
}
/** @var callable[] $validatorClosures */
$validatorClosures = $this->pictureRules($rules);
foreach ($validatorClosures as $validatorClosure) {
$validatorClosure($attribute, $value, $fail);
}
}
],
'picture_alt' => 'nullable|string|between:3,60',
'picture_title' => 'nullable|string|between:3,60',
'content' => ['required', 'string', new WysiwygLength(10, 5000)],
'status' => ['required', new Enum(NewsStatusEnum::class)],
'categories' => 'nullable|array',
'categories.*' => 'integer|exists:news_categories,id',
'tags' => 'nullable|array',
'tags.*' => 'integer|exists:news_tags,id',
'published_at' => 'nullable|date',
'unpublished_at' => 'nullable|date|after:published_at',
'inverted' => 'boolean',
'has_show_picture' => 'boolean',
'vimeo_id' => ['string', 'nullable', 'min:8', 'max:10', new IsValidVimeoId()]
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'seo_title' => trans('validation.custom.seo.title'),
'seo_description' => trans('validation.custom.seo.description'),
'title' => trans('validation.attributes.title'),
'slug' => trans('validation.custom.slug'),
'categories' => trans_choice('models.classes.news-category', \INF),
'tags' => trans_choice('models.classes.news-tag', \INF),
'picture' => trans('validation.attributes.image'),
'picture_alt' => trans('validation.custom.image_alt'),
'picture_title' => trans('validation.custom.image_title'),
'status' => trans('validation.custom.status'),
'content' => trans('validation.attributes.content'),
'published_at' => trans('validation.custom.published_at'),
'unpublished_at' => trans('validation.custom.unpublished_at'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\News;
use Illuminate\Support\Facades\Gate;
class UpdateNewsRequest extends StoreNewsRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('news'));
}
}

View file

@ -1,45 +0,0 @@
<?php
namespace App\Http\Requests\Back\NewsCategories;
use App\Models\NewsCategory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreNewsCategoryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', NewsCategory::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'slug' => 'required|string|between:3,255',
'name' => 'required|string|between:3,255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.custom.name'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\NewsCategories;
use Illuminate\Support\Facades\Gate;
class UpdateNewsCategoryRequest extends StoreNewsCategoryRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('news_category'));
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace App\Http\Requests\Back\NewsTags;
use App\Models\NewsTag;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreNewsTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', NewsTag::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'slug' => 'required|string|between:3,255',
'name' => 'required|string|between:3,255',
'color' => 'required|string|hex_color',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'name' => trans('validation.custom.name'),
'color' => trans('validation.custom.color'),
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\NewsTags;
use Illuminate\Support\Facades\Gate;
class UpdateNewsTagRequest extends StoreNewsTagRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('news_tag'));
}
}

View file

@ -1,116 +0,0 @@
<?php
namespace App\Http\Requests\Back\PageConfigs;
use App\Enums\DynamicFieldTypes;
use App\Models\PageConfig;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class StorePageConfigRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', PageConfig::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'label' => 'required|string|between:3,255',
'view' => 'sometimes|nullable|string|between:3,255',
'config' => ['required', 'array', 'min:1'],
] + $this->configRules('config.*');
}
/**
* Recursively generates rules for a given key.
*
* @param string $prefix
* @param integer $depth
* @return array
*/
protected function configRules(string $prefix, int $depth = 1): array
{
$rules = [
"{$prefix}" => 'required|array|between:5,6',
"{$prefix}.key" => [
'required',
'string',
'between:3,255',
'regex:/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/',
function ($attribute, $value, $fail) {
$parentPath = implode('.', array_slice(explode('.', $attribute), 0, -2));
$levelItems = data_get($this->input(), $parentPath, []);
$keysAtLevel = collect($levelItems)->pluck('key')->filter()->toArray();
if (count(array_filter($keysAtLevel, fn($k) => $k === $value)) > 1) {
$fail("The {$attribute} must be unique at its level.");
}
}
],
"{$prefix}.order" => 'required|numeric|min:1',
"{$prefix}.type" => ['required', new Enum(DynamicFieldTypes::class)],
"{$prefix}.label" => 'required|string|between:3,255',
"{$prefix}.validation" => 'required|string|between:3,255',
];
if ($depth < 10) {
$rules["{$prefix}.children"] = ['sometimes', 'array'];
$rules += $this->configRules("{$prefix}.children.*", $depth + 1);
}
return $rules;
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'label' => trans('validation.custom.label'),
'view' => trans('validation.custom.view'),
'config' => trans('validation.custom.config'),
] + $this->configAttributes('config.*');
}
/**
* Recursively generates attributes for a given key.
*
* @param string $prefix
* @param integer $depth
* @return array
*/
protected function configAttributes(string $prefix, int $depth = 1): array
{
$rules = [
"{$prefix}" => trans('validation.custom.config'),
"{$prefix}.key" => trans('validation.custom.key'),
"{$prefix}.order" => trans('validation.custom.order'),
"{$prefix}.type" => trans('validation.custom.type'),
"{$prefix}.label" => trans('validation.custom.label'),
"{$prefix}.validation" => trans('validation.custom.validation'),
];
if ($depth < 10) {
$rules["{$prefix}.children"] = trans('validation.custom.children');
$rules += $this->configAttributes("{$prefix}.children.*", $depth + 1);
}
return $rules;
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Back\PageConfigs;
use Illuminate\Support\Facades\Gate;
class UpdatePageConfigRequest extends StorePageConfigRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('page_config'));
}
}

View file

@ -1,47 +0,0 @@
<?php
namespace App\Http\Requests\Back;
use App\Traits\Requests\Api\HasLaravelFailedValidation;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Crypt;
class RetrieveDataFromTokenRequest extends FormRequest
{
use HasLaravelFailedValidation;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check() and $this->ajax();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'token' => [
'required',
'string',
function ($attribute, $value, $fail) {
try {
$payload = json_decode(Crypt::decryptString($value), true);
if (!is_array($payload) || !isset($payload['modelName'], $payload['modelId'])) {
$fail("Le {$attribute} est invalide.");
}
} catch (\Illuminate\Contracts\Encryption\DecryptException $e) {
$fail("Le {$attribute} est invalide.");
}
},
],
];
}
}

View file

@ -1,426 +0,0 @@
<?php
namespace App\Http\Requests\Back;
use App\Enums\Contact\ContactFormType;
use App\Enums\ElementPositionEnum;
use App\Enums\DynamicFieldTypes;
use App\Enums\StaticRoutesEnum;
use App\Models\PageConfig;
use App\Rules\Video;
use App\Traits\Requests\HasInertiaPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Rules\Enum;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
use Uwil\LaravelCommon\Rules\WysiwygLength;
abstract class UpdateDynamicFieldRequest extends FormRequest
{
use HasInertiaPicture;
/** @var \App\Models\PageConfig|null $configModel */
private $configModel = null;
/** @var string $fieldName */
private $fieldName = "content";
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->configModel = PageConfig::query()->whereKey($this->input('config_id'))->firstOrFail();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
// phpcs:enable
$rules = [];
foreach ($this->configModel->config as $input) {
$rules = array_merge($rules, $this->recursiveRules($input, $this->fieldName));
} //end foreach
return $rules;
}
/**
* Get rules from a specific input type.
*
* @param array $input
* @param string $prefix
* @return array
* @throws \UnexpectedValueException Unrecognized dynamic type.
* @phpcs:disable Generic.Metrics.CyclomaticComplexity
*/
private function recursiveRules(array &$input, string $prefix): array
{
// phpcs:enable
$rules = [];
$ruleKey = "$prefix.$input[key]";
switch ($input['type']) {
case DynamicFieldTypes::string->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::string->getValidation(), $input)
]);
break;
case DynamicFieldTypes::textarea->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::textarea->getValidation(), $input)
]);
break;
case DynamicFieldTypes::wysiwyg->value:
$defaultRules = explode('|', DynamicFieldTypes::wysiwyg->getValidation());
$wysiwygRules = $this->compileValidate(DynamicFieldTypes::wysiwyg->getValidation(), $input);
// Get the minimum and maximum values of the ‘between’ rule and
// remove them from the rules so that our custom rule WysiwygLength can apply it.
$defaults = $this->extractMinMax($defaultRules);
['min' => $min, 'max' => $max] = $this->extractMinMax($wysiwygRules, $defaults);
$newWysiwygRules = array_values(array_filter($wysiwygRules, fn($rule) => !(
is_string($rule) && preg_match('/^(min|max|between):/', $rule)
)));
$rules = array_merge($rules, [
$ruleKey => array_merge($newWysiwygRules, [
new WysiwygLength($min, $max),
])
]);
break;
case DynamicFieldTypes::number->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::number->getValidation(), $input)
]);
break;
case DynamicFieldTypes::boolean->value:
$all = $this->all();
$modified = $this->castBooleanFields($all);
$this->merge($modified);
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::boolean->getValidation(), $input)
]);
break;
case DynamicFieldTypes::url->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(explode("|", DynamicFieldTypes::url->getValidation()), [
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
try {
ToolboxHelper::assertNoInternalUrls($attribute, $value);
ToolboxHelper::assertNoInternalBoUrls($attribute, $value);
} catch (ValidationException $e) {
$fail(collect($e->errors())->first()[0]);
}
}
]),
]);
break;
case DynamicFieldTypes::email->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::email->getValidation(), $input)
]);
break;
case DynamicFieldTypes::image->value:
$imgRules = $this->compileValidate(DynamicFieldTypes::image->getValidation(), $input);
$rules = array_merge($rules, [
$ruleKey => $this->pictureRules(implode("|", $imgRules)),
]);
break;
case DynamicFieldTypes::list->value:
$rules = array_merge($rules, [
"$ruleKey.*.order" => "required|numeric|min:1",
$ruleKey => $this->compileValidate(DynamicFieldTypes::list->getValidation(), $input),
]);
foreach ($input['children'] as $key => $children) {
$rules = array_merge($rules, $this->recursiveRules($children, $ruleKey . ".*"));
}
break;
case DynamicFieldTypes::file->value:
$imgRules = $this->compileValidate(DynamicFieldTypes::file->getValidation(), $input);
$rules = array_merge($rules, [
$ruleKey => $this->pictureRules(implode("|", $imgRules)),
]);
break;
case DynamicFieldTypes::vimeoid->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::vimeoid->getValidation(), $input)
]);
break;
case DynamicFieldTypes::youtubeid->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::youtubeid->getValidation(), $input)
]);
break;
case DynamicFieldTypes::date->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::date->getValidation(), $input)
]);
break;
case DynamicFieldTypes::color->value:
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::color->getValidation(), $input)
]);
break;
case DynamicFieldTypes::model->value:
// Set a new rule key for the model name.
$ruleKeyReserved = preg_replace('/([^.\s]+)$/', 'reserved_$1', $ruleKey);
$rules = array_merge($rules, [
$ruleKey => $this->compileValidate(DynamicFieldTypes::model->getValidation(), $input),
$ruleKeyReserved => 'required|string|between:3,255',
]);
break;
case DynamicFieldTypes::staticroute->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(
$this->compileValidate(DynamicFieldTypes::staticroute->getValidation(), $input),
[new Enum(StaticRoutesEnum::class)],
),
]);
break;
// Specific inputs.
case DynamicFieldTypes::position->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(
$this->compileValidate(DynamicFieldTypes::position->getValidation(), $input),
[new Enum(ElementPositionEnum::class)],
),
]);
break;
case DynamicFieldTypes::contactform->value:
$rules = array_merge($rules, [
$ruleKey => array_merge(
$this->compileValidate(DynamicFieldTypes::position->getValidation(), $input),
[new Enum(ContactFormType::class)],
),
]);
break;
case DynamicFieldTypes::video->value:
$rules = array_merge($rules, [
$ruleKey => new Video(),
]);
break;
default:
throw new \UnexpectedValueException("Unrecognized dynamic type: " . ($input['type'] ?? 'null'));
} //end switch
return $rules;
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
$attributes = [];
foreach ($this->configModel->config as $input) {
$attributes = array_merge($attributes, $this->recursiveAttributes($input, $this->fieldName));
}
return $attributes;
}
/**
* Get attributes from a specific input type.
*
* @param array $input
* @param string $prefix
* @return array
*/
private function recursiveAttributes(array &$input, string $prefix): array
{
$attributes = [];
$attributeKey = sprintf("%s.%s", $prefix, $input['key']);
$attributes = array_merge($attributes, [$attributeKey => Str::lower($input['label'])]);
if (isset($input['children'])) {
foreach ($input['children'] as $key => $children) {
$attributes = array_merge($attributes, $this->recursiveAttributes($children, $attributeKey . ".*"));
}
}
return $attributes;
}
/**
* Merge native validations and custom validations.
*
* @param string|array $defaultValidations
* @param array $input
* @return array
*/
private function compileValidate(string|array $defaultValidations, array $input): array
{
$newValidations = $this->mergeValidations($defaultValidations, $input);
// Get unique rule and priorise important rules first.
$rules = \collect($newValidations)->unique()->sort(function ($valueA, $valueB) {
$valueA = $this->ruleHasToComeFirst($valueA);
$valueB = $this->ruleHasToComeFirst($valueB);
return $valueA - $valueB;
});
// Conditions the rules.
$hasRequire = $rules->first(fn($rule) => $rule === 'required') ? true : false;
$hasRequireCond = $rules->first(fn($rule) => \strpos($rule, 'required_') !== false) ? true : false;
$hasNullable = $rules->first(fn($rule) => \strpos($rule, 'nullable') !== false) ? true : false;
$hasSometime = $rules->first(fn($rule) => \strpos($rule, 'sometimes') !== false) ? true : false;
$hasPresent = $rules->first(fn($rule) => \strpos($rule, 'present') !== false) ? true : false;
if ($hasRequire and ($hasRequireCond or $hasNullable or $hasSometime or $hasPresent)) {
$rules = $rules->reject(fn($rule) => $rule === 'required');
}
return $rules->all();
}
/**
* Merge new validations into default validations.
*
* @param string|array $defaultValidations
* @param array $input
* @return array
*/
private function mergeValidations(string|array $defaultValidations, array $input): array
{
if (\is_string($defaultValidations)) {
$defaultValidations = explode('|', $defaultValidations);
}
/** @var array|string $inputValidations */
$inputValidations = \collect($input)->get('validation', $input);
$inputValidations = \is_string($inputValidations) ? explode('|', $inputValidations) : $inputValidations;
foreach ($inputValidations as $validate) {
// Split a string by a special character.
$parts = explode(':', $validate, 2);
// Check if there is a special character.
if (count($parts) > 1) {
[$ruleName, $ruleValue] = $parts;
// Deletes any old rule that starts with “ruleName:”.
$defaultValidations = collect($defaultValidations)
->reject(function ($existingRule) use ($ruleName) {
return is_string($existingRule) && str_starts_with($existingRule, sprintf('%s:', $ruleName));
})
->values()
->all();
// Add the new rule.
$defaultValidations[] = $validate;
} else {
// Check if rule without special character already exist.
if (!in_array($validate, $defaultValidations)) {
// Add the input rule.
$defaultValidations[] = $validate;
}
}
} //end foreach
return $defaultValidations;
}
/**
* Does the rule need to come first ?
*
* @param string $rule
* @return integer
*/
private function ruleHasToComeFirst(string $rule): int
{
$rule = \collect(\explode(':', $rule, 2))->filter()->first(null, '');
$rules = \collect([
'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',
'nullable',
'sometimes',
'present',
'required'
]);
$itemKey = $rules->search(fn(string $firstRule) => \strpos($rule, $firstRule) === 0);
if (\is_bool($itemKey)) {
return $rules->count() + 1;
}
return $itemKey;
}
/**
* Recursively traverses an array and converts it to Boolean
* all values whose key begins with “boolean_”.
*
* @param array $data
* @return array
*/
private function castBooleanFields(array $data): array
{
foreach ($data as $key => $value) {
// If the value is an array, recursive method.
if (is_array($value)) {
$data[$key] = $this->castBooleanFields($value);
continue;
}
// Check if the key start with the pattern enum_.
if (str_starts_with($key, sprintf("%s_", DynamicFieldTypes::boolean->name))) {
// Accepts boolean-like values (true/false, one/zero, on/off) as strings or numbers.
$data[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
}
return $data;
}
/**
* Extract min and max values from laravel validation.
*
* @param array $rules
* @param array|null $fallback
* @return array
*/
private function extractMinMax(array $rules, ?array $fallback = null): array
{
$min = $fallback['min'] ?? null;
$max = $fallback['max'] ?? null;
foreach ($rules as $rule) {
if (str_starts_with($rule, 'between:')) {
[$min, $max] = array_map(
'intval',
explode(',', substr($rule, 8))
);
return compact('min', 'max');
}
if (str_starts_with($rule, 'min:')) {
$min = (int) substr($rule, 4);
}
if (str_starts_with($rule, 'max:')) {
$max = (int) substr($rule, 4);
}
}
return compact('min', 'max');
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace App\Http\Requests\Back;
use App\Traits\Requests\Api\HasLaravelFailedValidation;
use Illuminate\Foundation\Http\FormRequest;
class UploadFileRequest extends FormRequest
{
use HasLaravelFailedValidation;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('backend')->check() and $this->ajax();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'upload' => [
'required',
'image',
'mimes:jpeg,png,jpg',
'max:5048',
'dimensions:min_width=567,min_height=567,max_width:1008,max_height:1008',
],
'folder' => 'required|string|between:3,255',
];
}
}

View file

@ -1,125 +0,0 @@
<?php
namespace App\Http\Requests\Front\Contact;
use App\Enums\Contact\BreedType;
use App\Enums\Contact\ContactFormType;
use App\Enums\Contact\CustomerAskingType;
use App\Enums\Contact\ExportAskingType;
use App\Enums\Contact\PressType;
use App\Enums\Department;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
class ContactRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version'));
$typeContactForm = $this->input('enumType');
// Validation for common fields.
$rules = [
'enumType' => ['required', Rule::enum(ContactFormType::class)],
'lastname' => ['required', 'min:1', 'max:60'],
'firstname' => ['required', 'min:1', 'max:60'],
'message' => ['required', 'min:10', 'max:255'],
'email' => ['required', 'min:1', 'max:60', 'email:rfc,dns,spoof'],
];
// Validation for breeder enum.
if (ContactFormType::breeder->equals(ContactFormType::make($typeContactForm))) {
$rules = array_merge($rules, [
'phone' => ['required', 'min:1', 'max:60'],
'typeelevage' => ['array', 'min:1'],
'typeelevage.*' => [Rule::enum(BreedType::class)],
'zonegeo' => ['array', 'min:1'],
'zonegeo.*' => [Rule::enum(Department::class)],
]);
}
// Validation for restauration enum.
if (ContactFormType::restauration->equals(ContactFormType::make($typeContactForm))) {
$rules = array_merge($rules, [
'company' => 'string|max:255',
]);
}
// Add the country field.
if (
ContactFormType::restauration->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::export->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::commitments->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::info->equals(ContactFormType::make($typeContactForm))
) {
$rules = array_merge($rules, [
'country' => ['required', 'min:1', 'max:150'],
]);
}
// Add the optional phone field.
if (
ContactFormType::restauration->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::export->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::finance->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::commitments->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::info->equals(ContactFormType::make($typeContactForm))
) {
$rules = array_merge($rules, [
'phone' => ['max:60'],
]);
}
// Add the town and postalcode fields.
if (
ContactFormType::restauration->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::export->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::finance->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::commitments->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::info->equals(ContactFormType::make($typeContactForm)) ||
ContactFormType::press->equals(ContactFormType::make($typeContactForm))
) {
$rules = array_merge($rules, [
'town' => 'string|required|min:2|max:255',
'postalcode' => 'string|required|min:2|max:60',
]);
}
// Validation for restauration and export commun fields.
if (
ContactFormType::restauration->equals(ContactFormType::make($typeContactForm))
) {
$rules = array_merge($rules, [
'type' => ['array', 'min:1'],
'type.*' => [Rule::enum(CustomerAskingType::class)],
]);
}
if (
ContactFormType::export->equals(ContactFormType::make($typeContactForm))
) {
$rules = array_merge($rules, [
'type' => ['array', 'min:1'],
'type.*' => [Rule::enum(ExportAskingType::class)],
]);
}
// Validation for press enum.
if (ContactFormType::press->equals(ContactFormType::make($typeContactForm))) {
$rules = array_merge($rules, [
'subject' => ['required', Rule::enum(PressType::class)],
'company' => 'required|string|min:2|max:255',
'phone' => ['required', 'max:60'],
'newsletter' => 'boolean'
]);
}
return $rules;
}
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Http\Requests\Front;
use App\Traits\Requests\Api\HasLaravelFailedValidation;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class PostsRequest extends FormRequest
{
use HasLaravelFailedValidation;
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'page' => 'required|integer',
'category' => 'required|string',
'locale' => ['required', 'string', Rule::in(config('app.locales'))],
'filters' => 'nullable|array',
'filters.order' => 'nullable|string|in:asc,desc',
'filters.tags' => 'nullable|array',
'filters.tags.*' => 'string',
'filters.years' => 'nullable|array',
'filters.years.*' => 'string',
];
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace App\Http\Requests\Front\Search;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Http\FormRequest;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
class SearchAjaxRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return $this->ajax();
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$supportedLocales = implode(',', LaravelLocalization::getSupportedLanguagesKeys());
return [
'search' => 'required|string|between:3,255',
'page' => 'required|integer|min:1',
'locale' => "required|string|in:$supportedLocales",
'class' => [
'required',
'string',
'starts_with:App\\Models\\',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if (!class_exists($value)) {
$fail("The specified model does not exist.");
return;
}
if (!is_subclass_of($value, Model::class)) {
$fail("The specified model is not an Eloquent model.");
}
},
],
];
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Http\Requests\Front\Search;
use Illuminate\Foundation\Http\FormRequest;
class SearchRequest extends FormRequest
{
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge(['page' => (int) $this->page ?: 1]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$queryKey = trans('front.search.query');
$pageKey = trans('front.search.page');
return [
$queryKey => 'required|string|between:3,255',
$pageKey => 'required|integer|min:1',
];
}
}

View file

@ -1,20 +0,0 @@
<?php
namespace App\Http\Requests\Front;
use Illuminate\Foundation\Http\FormRequest;
class SingleOptinRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof',
];
}
}

View file

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

View file

@ -1,47 +0,0 @@
<?php
namespace App\Jobs;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Symfony\Component\Finder\SplFileInfo;
class CleanTrashModels implements ShouldQueue, ShouldBeUnique
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* Delete model if it has been deleted for more than 30 days.
*
* @return void
*/
public function handle(): void
{
$modelsPath = app_path('Models');
$dateLimit = Carbon::now()->subDays(30);
$models = collect(File::allFiles($modelsPath))
->map(fn(SplFileInfo $file) => 'App\\Models\\' . str_replace(
['/', '.php'],
['\\', ''],
$file->getRelativePathname()
))
->filter(fn(string $class) => method_exists($class, 'initializeSoftDeletes'))
->values()
->each(function (string $class) use ($dateLimit) {
$class::onlyTrashed()
->where('deleted_at', '<', $dateLimit)
->cursor()
->each(fn($model) => $model->forceDelete());
});
}
}

View file

@ -1,53 +0,0 @@
<?php
namespace App\Jobs\Mails;
use App\Enums\Contact\ContactFormType;
use App\Mail\ContactMail;
use App\Models\MailContactData;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class SendContactMail implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
/** @var array List of recipients mails */
private array $mailList;
/**
* Create a new job instance.
*
* @param \App\Models\MailContactData $data
* @return void
*/
public function __construct(private MailContactData $data)
{
$this->mailList = ContactFormType::make($data->enumType)->getContactMails();
}
/**
* Execute the job.
*
* @return void
*/
public function handle(): void
{
foreach ($this->mailList as $recipient) {
Mail::to($recipient)->send(new ContactMail($this->data));
}
Log::debug(
sprintf(
'Sent %s to %s',
$this->data->enumType->label(),
implode(',', $this->mailList)
)
);
}
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Jobs;
use App\Enums\News\NewsStatusEnum;
use App\Models\News;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
/**
* Update status attribute reading its
* published_at and unpublished_at attributes.
*/
class NewsStatusUpdate
{
/**
* Called when the job will be executed.
*
* @return void
*/
public function handle(): void
{
// Publish all unpublish models if the published_at date has passed.
/** @var \Illuminate\Database\Eloquent\Collection<array-key, \App\Models\News> */
$newsListToPublish = News::query()
->where('published_at', '<=', Carbon::now())
->whereNotNull('published_at')
->where('status', NewsStatusEnum::unpublished)
->get();
Log::debug(__CLASS__ . " news to publish " . $newsListToPublish->count());
foreach ($newsListToPublish as $news) {
$news->status = NewsStatusEnum::published;
$news->saveQuietly();
}
// Unpublish all publish models if the unpublished_at date has passed.
/** @var \Illuminate\Database\Eloquent\Collection<array-key, \App\Models\News> */
$newsListToUnPublish = News::query()
->where('unpublished_at', '<=', Carbon::now())
->whereNotNull('unpublished_at')
->where('status', NewsStatusEnum::published)->get();
Log::debug(__CLASS__ . " news to unpublish " . $newsListToPublish->count());
foreach ($newsListToUnPublish as $news) {
$news->status = NewsStatusEnum::unpublished;
$news->saveQuietly();
}
}
}

View file

@ -1,76 +0,0 @@
<?php
namespace App\Mail;
use App\Models\MailContactData;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
class ContactMail extends Mailable
{
use Queueable;
use SerializesModels;
/**
* The order instance.
*
* @var \App\Models\MailContactData
*/
protected $data;
/**
* Create a new message instance.
*
* @param \App\Models\MailContactData $data
* @return void
*/
public function __construct(MailContactData $data)
{
$this->data = $data;
}
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
{
return new Envelope(
from: new Address(\config('mail.from.address'), \config('mail.from.name')),
subject: sprintf(
'%s - ' . config('app.name'),
ucfirst($this->data->enumType->label())
),
replyTo: [new Address($this->data->email, sprintf('%s %s', $this->data->firstname, $this->data->lastname))]
);
}
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): \Illuminate\Mail\Mailables\Content
{
return new Content(
view: 'emails.contact',
text: 'emails.contact-text',
with: ['data' => $this->data]
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View file

@ -1,97 +0,0 @@
<?php
namespace App\Models;
use App\Models\CompanyActivity;
use App\Models\CompanySector;
use App\Traits\Models\HasCache;
use App\Traits\Models\HasUwilImages;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
/**
* @property integer $id Id.
* @property string $name Name of the company.
* @property string $logo Logo of the company.
* @property string $responsable Responsable of the company.
* @property string $url Link the website of the company.
* @property string $informations Informations of the company.
* @property string $employees Effectif number of of the company.
* @property string $latitude Latitude coordination of the company.
* @property string $longitude Longitude of the company.
* @property integer $activity_id Activity associated to the company.
* @property integer|null $sector_id Sector associated to the company.
* @property string $published Publish status of the company.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property-read \Illuminate\Support\Carbon $deleted_at Deleted date.
*/
class Company extends Model
{
use ActivityLog;
use HasCache;
use HasFactory;
use SoftDeletes;
use HasUwilImages;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'logo',
'responsable',
'url',
'informations',
'employees',
'latitude',
'longitude',
'activity_id',
'sector_id',
'published',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'published' => 'boolean',
];
/**
* Relations should be included in the cache.
*
* @var list<string>
*/
protected static $cacheRelations = [
'companyActivity',
];
// * RELATIONSHIPS
/**
* Get the activity for this company.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\CompanyActivity,$this>
*/
public function companyActivity(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(CompanyActivity::class, 'activity_id');
}
/**
* Get the sector for this company.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\CompanySector,$this>
*/
public function companySector(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(CompanySector::class, 'sector_id');
}
}

View file

@ -1,124 +0,0 @@
<?php
namespace App\Models;
use App\Contracts\TranslatableModel;
use App\Helpers\TranslationHelper;
use App\Models\Company;
use App\Traits\Models\HasCache;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Validation\ValidationException;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
/**
* @property integer $id Id.
* @property string $name Name of the company activity.
* @property string $color Color of the company activity.
* @property string $published Publish status of the company activity.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property-read \Illuminate\Support\Carbon $deleted_at Deleted date.
*/
class CompanyActivity extends Model implements TranslatableModel
{
use ActivityLog;
use HasCache;
use HasFactory;
use HasTranslations;
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'color',
'published',
];
/**
* The translatable columns.
*
* @var list<string>
*/
public $translatable = [
'name',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'published' => 'boolean',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
self::manageFields($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::deleting(function (self $model) {
if ($model->companies()->exists()) {
throw ValidationException::withMessages([
'error' => trans('crud.messages.cannot_delete_relation'),
]);
}
});
}
// * METHODS
/**
* Manage model fields.
*
* @param self $model
* @return void
*/
private static function manageFields(self $model): void
{
TranslationHelper::assertTranslationFieldIsUnique($model, $model->name, 'name');
self::validateFallbackTranslations($model);
}
/**
* Assert fallback locale translation is present.
*
* @param self $model
* @return void
* @throws \Illuminate\Validation\ValidationException Translations missing.
*/
public static function validateFallbackTranslations(self $model): void
{
if (!$model->published) {
return;
}
TranslationHelper::validateFallbackTranslations($model);
}
// * RELATIONSHIPS
/**
* Get companies for this activity.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function companies(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Company::class, 'activity_id');
}
}

View file

@ -1,122 +0,0 @@
<?php
namespace App\Models;
use App\Contracts\TranslatableModel;
use App\Helpers\TranslationHelper;
use App\Models\Company;
use App\Traits\Models\HasCache;
use App\Traits\Models\HasTranslations;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Validation\ValidationException;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
/**
* @property integer $id Id.
* @property string $name Name of the company sector.
* @property string $published Publish status of the company sector.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property-read \Illuminate\Support\Carbon $deleted_at Deleted date.
*/
class CompanySector extends Model implements TranslatableModel
{
use ActivityLog;
use HasCache;
use HasFactory;
use HasTranslations;
use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'published',
];
/**
* The translatable columns.
*
* @var list<string>
*/
public $translatable = [
'name',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'published' => 'boolean',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
self::manageFields($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::deleting(function (self $model) {
if ($model->companies()->exists()) {
throw ValidationException::withMessages([
'sector_id' => trans('crud.messages.cannot_delete_relation'),
]);
}
});
}
// * METHODS
/**
* Manage model fields.
*
* @param self $model
* @return void
*/
private static function manageFields(self $model): void
{
TranslationHelper::assertTranslationFieldIsUnique($model, $model->name, 'name');
self::validateFallbackTranslations($model);
}
/**
* Assert fallback locale translation is present.
*
* @param self $model
* @return void
* @throws \Illuminate\Validation\ValidationException Translations missing.
*/
public static function validateFallbackTranslations(self $model): void
{
if (!$model->published) {
return;
}
TranslationHelper::validateFallbackTranslations($model);
}
// * RELATIONSHIPS
/**
* Get companies for this sector.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function companies(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Company::class, 'sector_id');
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace App\Models;
use App\Enums\Contact\ContactFormType;
use Illuminate\Database\Eloquent\Model;
class ContactCount extends Model
{
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'type',
'submissions'
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'type' => ContactFormType::class,
];
}

View file

@ -1,409 +0,0 @@
<?php
namespace App\Models;
use App\Contracts\TranslatableModel;
use App\Enums\Document\DocumentStatusEnum;
use App\Enums\YearsEnum;
use App\Helpers\DocumentHelper;
use App\Helpers\TranslationHelper;
use App\Models\Documentable;
use App\Traits\Models\HasCache;
use App\Traits\Models\HasTranslations;
use App\Traits\Models\HasUwilImages;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Laravel\Scout\Searchable;
use Uwil\LaravelActivityLogs\Traits\ActivityLog;
/**
* @property integer $id
* Id of the document.
* @property string $name
* Name of the document.
* @property string $path
* Path of the document.
* @property boolean $is_issuu
* Issu status of the document.
* @property string $issuu_path
* Issu path of the document.
* @property string $exercice
* Exercice of the document.
* @property \App\Enums\Document\DocumentStatusEnum $status
* Status of the document.
* @property-read \Carbon\Carbon $created_at
* Created date of the document.
* @property-read \Carbon\Carbon $updated_at
* Updated date of the document.
* @property-read \Illuminate\Support\Carbon $deleted_at
* Deleted date of the document.
* @property \Illuminate\Database\Eloquent\Collection<int, \App\Models\DocumentCategory> $categories
* Associated document categories of the document.
* @property \Illuminate\Database\Eloquent\Collection<int, \App\Models\DocumentTag> $tags
* Associated document tags of the document.
*/
class Document extends Model implements TranslatableModel
{
use ActivityLog;
use HasFactory;
use HasTranslations;
use HasCache;
use HasUwilImages {
HasUwilImages::inertiaFill as traitInertiaFill;
}
use Searchable;
/**
* Champs contenant des images dynamiques à gérer.
*
* @var array
*/
protected array $imageFields = [
'path'
];
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'path',
'is_issuu',
'issuu_path',
'exercice',
'status',
'published_at',
'unpublished_at'
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'status' => DocumentStatusEnum::class,
'exercice' => YearsEnum::class,
'published_at' => 'datetime',
'unpublished_at' => 'datetime',
'is_issuu' => 'boolean',
];
/**
* The translatable columns.
*
* @var list<string>
*/
public $translatable = [
'name',
'path',
'issuu_path'
];
/**
* Relations should be included in the cache.
*
* @var list<string>
*/
protected static $cacheRelations = [
'categories',
'tags',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
self::manageFields($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
}
// * METHODS
/**
* Adjusts model status using current status and publish dates.
* Case 1: publish model when all null -> set published_at date.
* Case 2: unpublish model when all null -> nothing to do.
* Case 3: publish model when published_at is already set -> only change status.
* Case 4: unpublish model when published_at is already set -> can't update model.
* Case 5: publish model when unpublished_at is already set -> can't update model.
* Case 6: unpublish model when unpublished_at is already set -> only change status.
*
* @param self $model
* @return void
*/
private static function makeStatusSense(self $model): void
{
/** @var \Carbon\Carbon|null */
$published_at = $model->published_at;
/** @var \Carbon\Carbon|null */
$unpublished_at = $model->unpublished_at;
$wantedStatus = $model->status;
// Check if dates are already set.
if ($published_at or $unpublished_at) {
$model->status = $model->planificated_status;
}
// If there is a diff, no update.
if ($wantedStatus !== $model->status) {
session()->flash('error', \trans(
'La publication est modifiée en concordance avec les dates enregistrées.
Pour modifier l\'état de publication, veuillez modifier ou enlever les dates correspondantes.'
));
}
// Set default published_at date when publishing model.
if (!$published_at && $model->status->equals(DocumentStatusEnum::published)) {
$model->published_at = Carbon::now();
}
}
/**
* Getter the current status following planification dates planificated_status.
*
* @return \App\Enums\Document\DocumentStatusEnum
*/
protected function getPlanificatedStatusAttribute(): \App\Enums\Document\DocumentStatusEnum
{
/** @var \Carbon\Carbon|null */
$published_at = $this->published_at;
/** @var \Carbon\Carbon|null */
$unpublished_at = $this->unpublished_at;
if ($published_at and $unpublished_at) {
return ($unpublished_at->greaterThanOrEqualTo(now()) and $published_at->lessThan(now()))
? DocumentStatusEnum::published
: DocumentStatusEnum::unpublished;
}
if ($published_at) {
return $published_at->lessThan(now())
? DocumentStatusEnum::published
: DocumentStatusEnum::unpublished;
}
if ($unpublished_at) {
return $unpublished_at->greaterThanOrEqualTo(now())
? DocumentStatusEnum::published
: DocumentStatusEnum::unpublished;
}
return DocumentStatusEnum::unpublished;
}
/**
* Manage model fields.
*
* @param self $model
* @return void
*/
private static function manageFields(self $model): void
{
// Check unique fields.
TranslationHelper::assertTranslationFieldIsUnique($model, $model->name, 'name');
// Check translations.
self::validateFallbackTranslations($model);
// Set status.
self::makeStatusSense($model);
}
/**
* Assert fallback locale translation is present.
*
* @param self $model
* @return void
* @throws \Illuminate\Validation\ValidationException Translations missing.
*/
public static function validateFallbackTranslations(self $model): void
{
if (! $model->status->equals(DocumentStatusEnum::published)) {
return;
}
TranslationHelper::validateFallbackTranslations($model, ['path', 'issuu_path']);
}
/**
* Get the url to access a specific resource.
*
* @param integer $id Id field of the target model.
* @return string|null
*/
public static function getAccessModel(int $id): ?string
{
$targetModel = self::getModelsCached()->firstWhere('id', $id);
if (!$targetModel) {
return null;
}
return sprintf('/%s', $targetModel->path);
}
// * MEILISEARCH
/**
* Get model's setting to apply on scout search (meilisearch).
*
* @var array
*/
public static $searchSettings = [
'filterableAttributes' => ['status'],
'sortableAttributes' => ['status']
];
/**
* Get fields that we want to keep in the current locale.
*
* @param string $currentLocale
* @return array
*/
public static function searchTransFields(string $currentLocale): array
{
return [
"name_$currentLocale",
"path_$currentLocale",
];
}
/**
* Get enum status of the model.
*
* @return integer
*/
public static function searchStatusEnum(): int
{
return DocumentStatusEnum::published->value;
}
/**
* Get front route.
*
* @return string
*/
public static function searchRouteName(): string
{
return 'front.documents.show';
}
/**
* Get model identificable field.
*
* @return string
*/
public static function searchMainField(): string
{
return 'name';
}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
$locales = config('laravellocalization.supportedLocales');
$data = [
'id' => $this->id,
'status' => $this->status->value,
];
foreach ($locales as $locale => $_) {
$data["name_$locale"] = $this->hasTranslation('name', $locale)
? $this->getTranslation('name', $locale)
: null;
$data["path_$locale"] = $this->hasTranslation('path', $locale)
? $this->getTranslation('path', $locale)
: null;
}
return $data;
}
/**
* Override HasUwilImages trait method to fill the model without relations.
*
* @param array $attributes The attributes to fill the model with.
* @return void
*/
public function inertiaFill(array $attributes): void
{
// Set issu_path if defined.
$issuuPath = data_get($attributes, "issuu_path");
if ($issuuPath) {
$attributes['issuu_path'] = DocumentHelper::extractDocumentFromIssuIframe(
data_get($attributes, "issuu_path"),
);
}
// Ommit some relations.
$attributes = Arr::except($attributes, ['tags', 'categories']);
$this->traitInertiaFill($attributes);
}
/**
* Get all published news by category.
*
* @param string $slugCategory
* @return \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document>
*/
public static function getPublishedDocumentsByCategoryInCurrentLocale(
string $slugCategory,
): \Illuminate\Database\Eloquent\Collection {
$locale = App::getLocale();
return Document::getModelsCached()
->where('status', DocumentStatusEnum::published)
->filter(function ($document) use ($slugCategory, $locale) {
$hasPath = !empty($document->getTranslation('path', $locale, false));
$hasIssuuPath = $document->is_issuu === true
&& !empty($document->getTranslation('issuu_path', $locale, false));
return ($hasPath || $hasIssuuPath) && $document->categories->contains(
fn($documentCategoryModel) => $documentCategoryModel->slug === $slugCategory
);
});
}
// * RELATIONSHIPS
/**
* Relation to Tag.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function tags(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(
DocumentTag::class,
'documentable',
'documentables',
'document_id',
'documentable_id'
)->using(Documentable::class)->withTimestamps();
}
/**
* Relation to Category.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function categories(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(
DocumentCategory::class,
'documentable',
'documentables',
'document_id',
'documentable_id'
)->using(Documentable::class)->withTimestamps();
}
}

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