Compare commits

...
Sign in to create a new pull request.

13 commits

Author SHA1 Message Date
Alexis Gousseau
b6b4311d83
chore: 🔖 release v5.0.0 2026-04-15 11:45:27 +02:00
Alexis Gousseau
d6fc06e032
fix: 🚨 resolve some phpcs/phpstan errors 2026-04-15 11:44:56 +02:00
Alexis Gousseau
380985915f
fix: 🐛 update central domains of tenancy configuration 2026-04-15 11:23:14 +02:00
Alexis Gousseau
94a972bddf
feat: ✨ build each theme in its own public directory 2026-04-15 11:21:52 +02:00
Alexis Gousseau
b258467fea
feat: ✨ add commit lint from husky configuration 2026-04-14 16:38:08 +02:00
Vincent BRIDIER
6053f57330 Merge branch 'master' into multi-tenant 2026-01-02 16:50:08 +01:00
Alexis Gousseau
071b39b73e
fix: 🐛 review feedback processing 2025-08-29 15:06:04 +02:00
Alexis Gousseau
0c29757c4e
chore: 🔖 release v4.0.0 2025-08-29 15:04:34 +02:00
Alexis Gousseau
b5a64a122a
fix: 🚨 resolve phpcs/phpstan errors 2025-08-29 15:03:32 +02:00
Alexis Gousseau
a9bd294360
feat: 🛂 change BO Auth for SAML 2025-08-29 15:01:37 +02:00
Alexis Gousseau
5f222c4997
fix: 🐛 validate all tests cases 2025-08-29 15:01:34 +02:00
Alexis Gousseau
566497a576
feat: ✨ set multi tenancy (path) for the front 2025-08-29 14:58:48 +02:00
Alexis Gousseau
6e44941f4f
feat: ✨ set multi tenancy (path) for the back 2025-08-29 14:46:51 +02:00
225 changed files with 5927 additions and 1929 deletions

View file

@ -65,3 +65,13 @@ CODE_PAGINATE=
CODE_CHAR_LIST=
# Code length (chars).
CODE_CHAR_LENGTH=
# SAML
SAML2_DEFAULT_TENANT_UUID=
SAML2_SP_CERT_x509=
SAML2_SP_CERT_PRIVATEKEY=
SAML2_CONTACT_TECHNICAL_EMAIL=
SAML2_CONTACT_SUPPORT_EMAIL=
# MULTI TENANT
TENANT_CENTRAL_DOMAINS=

6
.gitignore vendored
View file

@ -10,10 +10,12 @@
/node_modules
/bootstrap
/public/build
/public/favicons
/public/cache
/public/hot
/public/sitemap.xml
/public/storage
/public/**/
/storage/*.key
/vendor
auth.json
@ -23,14 +25,14 @@ Homestead.yaml
npm-debug.log
yarn-error.log
.npmrc
/resources/theme
# https://lab.s-web.fr/ldc-web/projets/gen-game-web/-/issues/17
public/fichiers
storage
resources/theme
.npm/
.composer/
.zshrc
resources/theme/*
!resources/theme/.gitkeep
# Ignore public favicon
/public/android-chrome-192x192.png

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "resources/theme"]
path = resources/theme
url = ../gen-game-web-themes/theme-merger.git

3
.husky/commit-msg Normal file
View file

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

23
CHANGELOG.md Normal file
View file

@ -0,0 +1,23 @@
# Changelog
All notable changes to `gen-game-web` will be documented in this file.
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
- Commits respect [Conventionnal commits](https://www.conventionalcommits.org/en/v1.0.0/) & use [Gitmoji](https://gitmoji.dev/).
## **[v5.0.0] - 15/04/2026**
### Added
- feat: ✨ set multi tenancy (path) for the back
- feat: ✨ set multi tenancy (domain) for the front
- feat: 🛂 change BO Auth for SAML
- feat: ✨ add commit lint from husky configuration
- feat: ✨ build each theme in its own public directory
### Changed
- fix: 🐛 validate all tests cases
- fix: 🐛 update central domains of tenancy configuration
- fix: 🚨 resolve phpcs/phpstan errors

View file

@ -6,8 +6,6 @@
cp .env.example .env
php artisan key:generate /!\ bien sauvegarder la key
php artisan migrate:fresh --step
# Seulement pour developper un theme par exemple
php artisan db:seed --class DevelopmentSeeder
php artisan uwil:user:create
```
@ -17,8 +15,27 @@ php artisan uwil:user:create
php artisan uwil:user:create
```
## Ajouter Parametrage du jeu
## Ajouter un theme
- Se connecter au back-office et créer un nouveau tenant (**le thème correspond au nom du dossier dans lequel se situe votre thème**),
- Dans votre environnement, rediriger le domaine ciblé vers le `public/nom-du-theme/`,
- Se mettre dans resources/theme et installer le theme voulu:
``` Bash
git clone git@lab.s-web.fr:ldc-web/projets/gen-game-web-themes/le-gaulois/le-gaulois-tdf/le-gaulois-tdf-2025.git
```
- Se remettre à la racine du moteur et compiler votre thème:
```bash
npm run prod-theme le-gaulois-tdf-2025
```
- ou compiler tous les thèmes présents:
```Bash
npm run prod
```
> **Notes:**
> - chaque thème est compilé dans son propre dossier `public/nom-du-theme/`
> - le backend étant partagé à tous les thèmes, il est compilé directement dans `public/`
## Ajouter Parametrage du jeu
**Les lots sont à ajouter avant de générer les instants gagants**. En effet les instants gagants sont liés à des lots.
@ -30,30 +47,3 @@ artisan queue:work
En cas de manipulation pourrie, penser a vider le cache pour supprimer le lock
sur le job de generation des codes.
## THEME
### Instalation
Pour installer le theme bien **mettre à jour les submodules avec git**,
`git submodule update --init --recursive`
puis utiliser la commande `php artisan uwil:theme:switch NOM_DU_THEME` et `php artisan optimize:clear && php artisan optimize`
Pour mettre a jour la liste des themes :
### Mise à jour
**Note** : cette partie n'est pas encore certaine.
- Mettre à jour `git submodule update --remote --merge resources/theme`
Ou
``` Bash
cd resources/themes;
git pull origin main;
cd ../..;
git add resources/themes
```
Puis commit.

View file

@ -4,6 +4,7 @@ namespace App\Casts;
use App\Lib\Discount;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Arr;
class AsDiscount implements CastsAttributes
{
@ -21,11 +22,17 @@ class AsDiscount implements CastsAttributes
*/
public function get($model, string $key, $value, array $attributes): Discount
{
// phpcs:enable
$inputData = \json_decode($value, true);
return Discount::make(...\array_values($inputData));
return Discount::make(
Arr::get($inputData, 'web_coupon_identifiant'),
Arr::get($inputData, 'percentage'),
Arr::get($inputData, 'face_value'),
Arr::get($inputData, 'valid_on'),
Arr::get($inputData, 'picture'),
);
}
/**
* Prepare the given value for storage.
*

View file

@ -1,77 +0,0 @@
<?php
namespace App\Console\Commands\Theme;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use function Laravel\Prompts\search;
class SwitchCommand extends Command implements PromptsForMissingInput
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'uwil:theme:switch {theme : The theme folder name on theme-merge project}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Switch GGW theme and install it';
/**
* Prompt for missing input arguments using the returned questions.
*
* @return array
*/
protected function promptForMissingArgumentsUsing()
{
return [
'theme' => fn () => search(
label: 'Search for a theme:',
placeholder: 'default-theme',
options: fn (string $value): array => \collect(File::directories(\resource_path('theme')))
->map(fn (string $dir_path) => \pathinfo($dir_path, \PATHINFO_BASENAME))
->filter(fn (string $dir_path) => $value ? Str::of($dir_path)->contains($value) : true)
->filter(fn (string $dir_path) => $dir_path !== 'mounted')
->all()
)
];
}
/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
$mount_path = \resource_path('theme/mounted');
/** @var string */
$theme = $this->argument('theme');
if (File::exists($mount_path)) {
\throw_if(
empty(ToolboxHelper::runConsoleCommand("readlink {$mount_path}", false)),
"{$mount_path} is not a link, cannot remove it"
);
\throw_if(
!File::delete($mount_path),
"{$mount_path} Failed to remove file"
);
}
File::link(\resource_path("theme/{$theme}"), $mount_path);
Artisan::call('optimize:clear');
}
}

View file

@ -8,6 +8,7 @@ use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Str;
class UserCreate extends Command
{
@ -21,7 +22,7 @@ class UserCreate extends Command
/**
* The console command description.
*
* @var string|null
* @var string
*/
protected $description = 'Create a backend admin user';
@ -46,13 +47,6 @@ class UserCreate extends Command
*/
protected $email;
/**
* The password of the new user.
*
* @var string|null
*/
protected $password;
/**
* The role of the new user.
*
@ -63,9 +57,9 @@ class UserCreate extends Command
/**
* Execute the console command.
*
* @return integer
* @return void
*/
public function handle(): int
public function handle(): void
{
$this->first_name = null;
$this->addFirstName();
@ -76,26 +70,20 @@ class UserCreate extends Command
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$this->role = null;
$this->addRole();
$user = new User([
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'password' => $this->password,
'role' => $this->role,
'published' => true,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'role' => $this->role,
'published' => true,
'published_at' => Carbon::now(),
]);
$user->saveOrFail();
$this->info('User created 👌');
return 0;
}
/**
@ -173,22 +161,6 @@ class UserCreate extends Command
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
/**
* Add a role to the user.
*
@ -198,20 +170,22 @@ class UserCreate extends Command
{
while (is_null($this->role)) {
try {
$tmp = $this->choice(
$tmp = $this->choice(
'Select his role',
[RoleEnum::conceptor->label(), RoleEnum::admin->label(), RoleEnum::marketing->label()],
RoleEnum::marketing->label(),
$maxAttempts = null,
$allowMultipleSelections = false
[
Str::ucFirst(RoleEnum::conceptor->label()),
Str::ucFirst(RoleEnum::admin->label()),
Str::ucFirst(RoleEnum::marketing->label())
],
Str::ucFirst(RoleEnum::marketing->label()),
);
if ($tmp === RoleEnum::conceptor->label()) {
if ($tmp === Str::ucFirst(RoleEnum::conceptor->label())) {
$tmp = RoleEnum::conceptor;
}
if ($tmp === RoleEnum::admin->label()) {
if ($tmp === Str::ucFirst(RoleEnum::admin->label())) {
$tmp = RoleEnum::admin;
}
if ($tmp === RoleEnum::marketing->label()) {
if ($tmp === Str::ucFirst(RoleEnum::marketing->label())) {
$tmp = RoleEnum::marketing;
}
$validator = Validator::make(['role' => $tmp], [

View file

@ -21,7 +21,7 @@ class Kernel extends ConsoleKernel
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
protected function schedule(Schedule $schedule): void
{
$schedule->job(DeleteOldCustomers::class)->dailyAt('22:50');
}
@ -31,10 +31,8 @@ class Kernel extends ConsoleKernel
*
* @return void
*/
protected function commands()
protected function commands(): void
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}

View file

@ -0,0 +1,37 @@
<?php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
if (!function_exists('themeConfig')) {
/**
* Undocumented function
*
* @return array
* @throws \Exception Folder or config file doesn't exist.
*/
function themeConfig(): array
{
$themeName = tenant()->theme;
return Cache::remember("config-$themeName", 84600, function () use ($themeName) {
$themePath = resource_path("theme/$themeName");
$configFile = "$themePath/theme.config.php";
if (!is_dir($themePath)) {
Log::error("Le dossier du thème [$themeName] est introuvable : $themePath");
throw new \Exception(
"Le dossier du thème [$themeName] est introuvable : $themePath"
);
}
if (!file_exists($configFile)) {
Log::error("Le fichier de config du thème [$themeName] est introuvable : $configFile");
throw new \Exception(
"Le fichier de config du thème [$themeName] est introuvable : $configFile"
);
}
return require $configFile;
});
}
} //end if

View file

@ -3,9 +3,8 @@
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@ -28,7 +27,7 @@ class LoginController extends Controller
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard
{
return Auth::guard('backend');
}
@ -38,7 +37,7 @@ class LoginController extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function showLoginForm(): View
public function showLoginForm(): \Illuminate\Contracts\View\View
{
return view('back::auth.login');
}
@ -59,7 +58,7 @@ class LoginController extends Controller
* @param \Illuminate\Http\Request $request
* @return boolean
*/
protected function attemptLogin(Request $request)
protected function attemptLogin(Request $request): bool
{
$credentials = $this->credentials($request);
// * Attempt login only using published users.
@ -69,4 +68,27 @@ class LoginController extends Controller
$request->boolean('remember')
);
}
/**
* Log the user out of the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
if ($response = $this->loggedOut($request)) {
return $response;
}
return $request->wantsJson()
? new JsonResponse([], 204)
: redirect()->route('bo.login')->with('warning', trans('auth.logout'));
}
}

View file

@ -3,7 +3,6 @@
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Auth;
@ -16,7 +15,7 @@ class ResetPasswordController extends Controller
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard
{
return Auth::guard('backend');
}

View file

@ -7,9 +7,8 @@ use App\Models\Participation;
use App\Models\Settings;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
class BackController extends Controller
{
/**
* Dashboard Page
@ -18,13 +17,24 @@ class DashboardController extends Controller
*/
public function index(): \Illuminate\Contracts\View\View
{
// * Pour les stats.
return view('back::pages.dashboard.index');
}
/**
* Dashboard Page
*
* @return \Illuminate\Contracts\View\View
*/
public function statistics(): \Illuminate\Contracts\View\View
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = Settings::query()->where('tenant_id', \tenant()->getKey())->firstOrFail();
$avgParticipUser = 0;
$participationPeriod = [];
$statsUnique = [];
$query = DB::table((new Participation())->getTable());
$query = Participation::query()->whereHas('code', function ($query) {
$query->where('tenant_id', \tenant()->getKey());
});
$dateFrmtr = function (object $row) {
$row->date = Carbon::parse($row->date)->isoFormat('DD/MM/YYYY');
return $row;
@ -45,7 +55,7 @@ class DashboardController extends Controller
->selectRaw("DISTINCT customer_id AS user, COUNT(*) AS count")
->groupBy('user')->orderBy('user')->get();
$participationsTotal = Participation::query()->count();
$participationsTotal = $query->count();
/** @var \Illuminate\Support\Collection<object> 3 codes EAN les plus utilisés */
$codesEan = (clone $query)
@ -69,9 +79,9 @@ class DashboardController extends Controller
$end_date = now()->lt($settings->end_date) ? now() : $settings->end_date;
$dates = $participationsPerDay->keys();
$count = $participationsPerDay->pluck('count');
$countUnique = $dates->map(fn (string $formated_date) => Arr::get($statsUnique, $formated_date, 0));
$countUnique = $dates->map(fn(string $formated_date) => Arr::get($statsUnique, $formated_date, 0));
return view('back::pages.dashboard.index', compact(
return view('back::pages.statistics', compact(
'dates',
'count',
'countUnique',

View file

@ -78,7 +78,7 @@ class BannedIpController extends Controller
$banned_ip->fill($request->validated());
if ($banned_ip->saveOrFail()) {
return redirect()
->route('bo.banned_ips.edit', $banned_ip)
->route('bo.banned_ips.edit', ['tenant' => \tenant(), 'banned_ip' => $banned_ip])
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.banned_ips'))]
@ -119,7 +119,7 @@ class BannedIpController extends Controller
$banned_ip->fill($request->validated());
if ($banned_ip->saveOrFail()) {
return redirect()
->route('bo.banned_ips.edit', $banned_ip)
->route('bo.banned_ips.edit', ['tenant' => \tenant(), 'banned_ip' => $banned_ip])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.banned_ips'))]
@ -145,7 +145,7 @@ class BannedIpController extends Controller
return DB::transaction(function () use ($banned_ip) {
if ($banned_ip->deleteOrFail()) {
return redirect()
->route('bo.banned_ips.index')
->route('bo.banned_ips.index', ['tenant' => \tenant()])
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.banned_ips'))]

View file

@ -11,7 +11,7 @@ use App\Jobs\ProcessUniqueCode;
use App\Models\Code;
use App\Models\Participation;
use App\Models\Settings;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Queue;
use Kwaadpepper\Serial\Exceptions\SerialCasterException;
@ -25,16 +25,20 @@ class CodeController extends Controller
public function index(): \Illuminate\Contracts\View\View
{
$this->authorize('generate', Code::class);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$nbCodeBase = Code::query()->max('id');
$nbCodeUsed = Code::query()->whereHas(
'participations',
$settings = Settings::query()->where('tenant_id', \tenant()->getKey())->firstOrFail();
$codeQuery = Code::query()->where('tenant_id', \tenant()->getKey());
// Hack to prevent where has modification.
$nbCodeBase = (clone $codeQuery)->max('id');
$nbCodeUsed = (clone $codeQuery)->whereHas(
relation: 'participations',
operator: '>',
count: $settings->nb_unique_code_use
)->count();
$nbCodeExported = Code::query()->where('code_exported', true)->count();
$nbCodeRemainingToExport = Code::query()->where('code_exported', false)->count();
$nbCodeExported = (clone $codeQuery)->where('code_exported', true)->count();
$nbCodeRemainingToExport = (clone $codeQuery)->where('code_exported', false)->count();
return view('back::pages.codes.index', compact(
'settings',
'nbCodeBase',
@ -53,10 +57,12 @@ class CodeController extends Controller
public function generate(GenerateCodesRequest $request): \Illuminate\Http\RedirectResponse
{
$this->authorize('generate', Code::class);
Queue::push(
new ProcessUniqueCode(
intval($request->quantity),
config('codes.codeLength')
config('codes.codeLength'),
\tenant()->setting,
)
);
@ -81,11 +87,13 @@ class CodeController extends Controller
public function export(ExportCodesRequest $request): \Illuminate\Http\RedirectResponse
{
$this->authorize('export', Code::class);
Queue::push(
new ProcessExportCodesUniques(
intval($request->nb_code_fic),
intval($request->nb_code_tot),
intval($request->nb_code_ligne)
intval($request->nb_code_ligne),
\tenant()->setting,
)
);
@ -107,6 +115,7 @@ class CodeController extends Controller
public function search(CodeSearchRequest $request): \Illuminate\Contracts\View\View
{
$this->authorize('search', Code::class);
$code = null;
try {
$code = Code::query()->where('id', Code::serialToInt($request->code))->first();
@ -126,7 +135,9 @@ class CodeController extends Controller
$used_counter = null;
if ($code) {
$used_counter = Participation::query()
->whereHas('code', fn(Builder $q) => $q->where('id', $code->id))
->whereHas('code', fn(Builder $q) => $q
->where('tenant_id', \tenant()->getKey())
->where('id', $code->id))
->count();
}

View file

@ -30,7 +30,9 @@ class FaqQuestionController extends Controller
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $faqQuestionModels */
$faqQuestionModels = FaqQuestion::query();
$faqQuestionModels = FaqQuestion::query()->with('theme')->whereHas('theme', function ($query) {
$query->where('tenant_id', tenant('id'));
});
/** @var string $search Search field */
$search = $request->search;
@ -69,7 +71,7 @@ class FaqQuestionController extends Controller
$faqThemeModels = FaqTheme::all();
return view('back::pages.faq-questions.create', [
'faqQuestionModel' => $faqQuestion,
'faqThemeModels' => $faqThemeModels
'faqThemeModels' => $faqThemeModels
]);
}
@ -86,7 +88,7 @@ class FaqQuestionController extends Controller
$faqQuestion->fill($request->validated());
if ($faqQuestion->saveOrFail()) {
return redirect()
->route('bo.faq-questions.edit', $faqQuestion)
->route('bo.faq-questions.edit', ['tenant' => \tenant(), 'faq_question' => $faqQuestion])
->with(
'success',
trans(
@ -134,10 +136,7 @@ class FaqQuestionController extends Controller
$faqQuestion->fill($request->validated());
if ($faqQuestion->saveOrFail()) {
return redirect()
->route(
'bo.faq-questions.edit',
$faqQuestion
)
->route('bo.faq-questions.edit', ['tenant' => \tenant(), 'faq_question' => $faqQuestion])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => trans_choice('models.classes.faq-questions', 1)]
@ -163,7 +162,7 @@ class FaqQuestionController extends Controller
return DB::transaction(function () use ($faqQuestion) {
if ($faqQuestion->deleteOrFail()) {
return redirect()
->route('bo.faq-questions.index')
->route('bo.faq-questions.index', ['tenant' => \tenant(), 'faq_question' => $faqQuestion])
->with('success', __(
'crud.messages.has_been_deleted',
['model' => trans_choice('models.classes.faq-questions', 1)]

View file

@ -78,7 +78,7 @@ class FaqThemeController extends Controller
$faqTheme->fill($request->validated());
if ($faqTheme->saveOrFail()) {
return redirect()
->route('bo.faq-themes.edit', $faqTheme)
->route('bo.faq-themes.edit', ['tenant' => \tenant(), 'faq_theme' => $faqTheme])
->with(
'success',
trans(
@ -124,10 +124,7 @@ class FaqThemeController extends Controller
$faqTheme->fill($request->validated());
if ($faqTheme->saveOrFail()) {
return redirect()
->route(
'bo.faq-themes.edit',
$faqTheme
)
->route('bo.faq-themes.edit', ['tenant' => \tenant(), 'faq_theme' => $faqTheme])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => trans_choice('models.classes.faq-themes', 1)]
@ -153,7 +150,7 @@ class FaqThemeController extends Controller
return DB::transaction(function () use ($faqTheme) {
if ($faqTheme->deleteOrFail()) {
return redirect()
->route('bo.faq-themes.index')
->route('bo.faq-themes.index', ['tenant' => \tenant()])
->with('success', __(
'crud.messages.has_been_deleted',
['model' => trans_choice('models.classes.faq-themes', 1)]

View file

@ -5,9 +5,7 @@ namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\InstantWin;
use App\Models\Prize;
use App\Models\Settings;
use Carbon\CarbonInterval;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
@ -28,7 +26,9 @@ class InstantWinController extends Controller
*/
public function index(): \Illuminate\Contracts\View\View
{
$instantWinModelList = InstantWin::query()->orderBy('available_at')->get();
$instantWinModelList = InstantWin::query()->with('prize')->whereHas('prize', function ($query) {
$query->where('tenant_id', tenant('id'));
})->orderBy('available_at')->get();
return view('back::pages.instant-win.index', [
'instantWinModelList' => $instantWinModelList
@ -40,7 +40,7 @@ class InstantWinController extends Controller
*
* @return \Illuminate\Http\JsonResponse
*/
public function generate(): JsonResponse
public function generate(): \Illuminate\Http\JsonResponse
{
$this->authorize('generate', InstantWin::class);
@ -57,7 +57,7 @@ class InstantWinController extends Controller
$day_end = now()->setTime(22, 29);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
$prizeModelList = Prize::all();
/** @var \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinDayCalendar> */
@ -105,7 +105,9 @@ class InstantWinController extends Controller
});
// * Output instant wins to ajax response
return response()->json(InstantWin::all());
return response()->json(InstantWin::query()->with('prize')->whereHas('prize', function ($query) {
$query->where('tenant_id', tenant('id'));
})->orderBy('available_at')->get()->toArray());
});
}
@ -143,7 +145,7 @@ class InstantWinController extends Controller
// * Tri par heure et assignation du jour correct.
$dateGlobal->each(function (WinDayCalendar $date) {
$date->lots = $date->lots->sort(fn (WinTimeElement $a, WinTimeElement $b) =>
$date->lots = $date->lots->sort(fn(WinTimeElement $a, WinTimeElement $b) =>
$a->time->timestamp - $b->time->timestamp)
->map(function (WinTimeElement $time) use ($date) {
$time->time->setDate(

View file

@ -27,7 +27,11 @@ class ParticipationController extends Controller
$this->authorize('viewWinnerList', Participation::class);
/** @var \Illuminate\Database\Eloquent\Builder $participationModels */
$participationModels = Participation::query()->with(['customer', 'code', 'instantWin', 'product'])->whereHas('instantWin');
$participationModels = Participation::query()->with(['customer', 'code', 'instantWin', 'product'])
->whereHas('customer', function ($query) {
$query->where('tenant_id', tenant('id'));
})
->whereHas('instantWin');
/** Sort columns with a query */
$this->sortQuery($participationModels);
@ -35,9 +39,7 @@ class ParticipationController extends Controller
/** Custom pagination */
$participationModels = $this->paginate($participationModels);
return view('back::pages.winners-list.index', compact(
'participationModels'
));
return view('back::pages.winners-list.index', compact('participationModels'));
}
/**

View file

@ -92,7 +92,7 @@ class PrizeController extends Controller
$prize->fill($request->validated());
if ($prize->saveOrFail()) {
return redirect()
->route('bo.prizes.edit', $prize)
->route('bo.prizes.edit', ['tenant' => \tenant(), 'prize' => $prize])
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.prize'))]
@ -133,7 +133,7 @@ class PrizeController extends Controller
$prize->fill($request->validated());
if ($prize->saveOrFail()) {
return redirect()
->route('bo.prizes.edit', $prize)
->route('bo.prizes.edit', ['tenant' => \tenant(), 'prize' => $prize])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.prize'))]
@ -159,7 +159,7 @@ class PrizeController extends Controller
return DB::transaction(function () use ($prize) {
if ($prize->deleteOrFail()) {
return redirect()
->route('bo.prizes.index')
->route('bo.prizes.index', ['tenant' => \tenant()])
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.prize'))]

View file

@ -30,7 +30,9 @@ class ProductController extends Controller
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $productModels */
$productModels = Product::query()->with('range');
$productModels = Product::query()->with('range')->whereHas('range', function ($query) {
$query->where('tenant_id', tenant('id'));
});
/** @var string $search Search field */
$search = $request->search;
@ -79,7 +81,7 @@ class ProductController extends Controller
{
$rangeModels = Range::all();
if (!count($rangeModels)) {
return \redirect()->route('bo.ranges.index')
return \redirect()->route('bo.ranges.index', ['tenant' => \tenant()])
->with(
'error',
'Vous devez ajouter une gamme avant de pouvoir ajouter un produit'
@ -104,7 +106,7 @@ class ProductController extends Controller
$product->fill($request->validated());
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->route('bo.products.edit', ['tenant' => \tenant(), 'product' => $product])
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.product'))]
@ -147,7 +149,7 @@ class ProductController extends Controller
$product->fill($request->validated());
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->route('bo.products.edit', ['tenant' => \tenant(), 'product' => $product])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.product'))]
@ -173,7 +175,7 @@ class ProductController extends Controller
return DB::transaction(function () use ($product) {
if ($product->deleteOrFail()) {
return redirect()
->route('bo.products.index')
->route('bo.products.index', ['tenant' => \tenant()])
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.product'))]

View file

@ -89,7 +89,7 @@ class RangeController extends Controller
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->route('bo.ranges.edit', ['tenant' => \tenant(), 'range' => $range])
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.range'))]
@ -130,7 +130,7 @@ class RangeController extends Controller
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->route('bo.ranges.edit', ['tenant' => \tenant(), 'range' => $range])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.range'))]
@ -161,7 +161,7 @@ class RangeController extends Controller
return DB::transaction(function () use ($range) {
if ($range->deleteOrFail()) {
return redirect()
->route('bo.ranges.index')
->route('bo.ranges.index', ['tenant' => \tenant()])
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.range'))]

View file

@ -8,12 +8,9 @@ use App\Mail\Won\PrizeIsSentMail;
use App\Mail\Won\PrizeWonMail;
use App\Mail\Won\WinningProofSubmitedMail;
use App\Models\Prize;
use App\Models\Settings;
use App\Models\Discount;
use App\Models\Participation;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Support\Arr;
@ -37,7 +34,7 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function home(): View
public function home(): \Illuminate\Contracts\View\View
{
return view('theme::pages.home');
}
@ -47,7 +44,7 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function teaser(): View
public function teaser(): \Illuminate\Contracts\View\View
{
return view('theme::pages.teaser');
}
@ -57,7 +54,7 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function endGame(): View
public function endGame(): \Illuminate\Contracts\View\View
{
return view('theme::pages.end-game');
}
@ -67,7 +64,7 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function participate(): View
public function participate(): \Illuminate\Contracts\View\View
{
return view('theme::pages.participation.participate');
}
@ -77,7 +74,7 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function participationExhausted(): View
public function participationExhausted(): \Illuminate\Contracts\View\View
{
return view('theme::pages.participation.participation-exhausted');
}
@ -90,13 +87,13 @@ class Re7Controller extends Controller
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function prize(Prize $prize): View
public function prize(Prize $prize): \Illuminate\Contracts\View\View
{
// * Fake participation model
$participationModel = new Participation();
$participationModel = 9999999;
return view('theme::pages.participation.won', [
'prizeModel' => $prize,
'prizeModel' => $prize,
'participationModel' => $participationModel
]);
}
@ -106,10 +103,10 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function discount(): View
public function discount(): \Illuminate\Contracts\View\View
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Lib\Discount */
$discount = Arr::get(
$settings->web_coupons,
@ -129,7 +126,7 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function loose(): View
public function loose(): \Illuminate\Contracts\View\View
{
return view('theme::pages.participation.loose');
}
@ -142,12 +139,13 @@ class Re7Controller extends Controller
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function mailPrize(Prize $prize): View
public function mailPrize(Prize $prize): \Illuminate\Contracts\View\View
{
return view('back::pages.re7-preview-mail', [
'render_url' => route('re7.mail-print', [
'type' => 'prize',
'prize' => $prize
'tenant' => \tenant(),
'type' => 'prize',
'prize' => $prize,
])
]);
}
@ -157,17 +155,18 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function mailDiscount(): View
public function mailDiscount(): \Illuminate\Contracts\View\View
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if (!$settings->discounts_activated) {
return abort(404);
}
return view('back::pages.re7-preview-mail', [
'render_url' => route('re7.mail-print', [
'type' => 'discount'
'tenant' => \tenant(),
'type' => 'discount'
])
]);
}
@ -177,11 +176,12 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function mailProofReceived(): View
public function mailProofReceived(): \Illuminate\Contracts\View\View
{
return view('back::pages.re7-preview-mail', [
'render_url' => route('re7.mail-print', [
'type' => 'proof'
'tenant' => \tenant(),
'type' => 'proof'
])
]);
}
@ -191,11 +191,12 @@ class Re7Controller extends Controller
*
* @return \Illuminate\Contracts\View\View
*/
public function mailPrizeSent(): View
public function mailPrizeSent(): \Illuminate\Contracts\View\View
{
return view('back::pages.re7-preview-mail', [
'render_url' => route('re7.mail-print', [
'type' => 'prize-sent'
'tenant' => \tenant(),
'type' => 'prize-sent'
])
]);
}
@ -207,13 +208,13 @@ class Re7Controller extends Controller
* @param \App\Models\Prize|null $prize
* @return \Illuminate\Http\Response
*/
public function mailPrint(string $type, Prize|null $prize = null): Response
public function mailPrint(string $type, Prize|null $prize = null): \Illuminate\Http\Response
{
/** @var \App\Models\User */
$user = auth('backend')->user();
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
switch ($type) {
case 'discount':
@ -259,8 +260,7 @@ class Re7Controller extends Controller
$user->email,
$user->first_name
),
$settings->name,
$settings->company,
$settings
))->render());
default:
return response('', 404);

View file

@ -13,37 +13,27 @@ use Illuminate\Support\Str;
class SettingsController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Settings::class);
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Settings $settings
* @return \Illuminate\Contracts\View\View
*/
public function edit(Settings $settings): \Illuminate\Contracts\View\View
public function edition(): \Illuminate\Contracts\View\View
{
return view('back::pages.settings.edit', [
'settingsModel' => $settings
]);
$settingsModel = Settings::query()->where('tenant_id', tenant('id'))->firstOrFail();
$this->authorize('view', $settingsModel);
return view('back::pages.settings.edit', compact('settingsModel'));
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Settings\UpdateSettingsRequest $request
* @param \App\Models\Settings $settings
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateSettingsRequest $request, Settings $settings): \Illuminate\Http\RedirectResponse
public function update(UpdateSettingsRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $settings) {
return DB::transaction(function () use ($request) {
$data = $request->validated();
// * Assign parsed Web coupon objects
@ -51,10 +41,11 @@ class SettingsController extends Controller
->map(fn (array $coupon) => $this->parseWebCoupon($coupon))
->filter()->all());
$settings->fill($data);
if ($settings->saveOrFail()) {
$settingsModel = Settings::query()->where('tenant_id', tenant('id'))->firstOrFail();
$settingsModel->fill($data);
if ($settingsModel->saveOrFail()) {
return redirect()
->route('bo.settings.edit', $settings)
->route('bo.settings.edition', ['tenant' => \tenant()])
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.settings'))]
@ -69,14 +60,13 @@ class SettingsController extends Controller
});
}
/**
* Try to parse webCoupon
*
* @param array<string,string> $coupon
* @return \App\Lib\Discount|null
*/
private function parseWebCoupon(array $coupon): Discount | null
private function parseWebCoupon(array $coupon): Discount|null
{
/** @var \Illuminate\Http\UploadedFile|string */
$picture = Arr::get($coupon, 'picture');

View file

@ -77,7 +77,7 @@ class StaticPageController extends Controller
$static_page->fill($request->validated());
if ($static_page->saveOrFail()) {
return redirect()->route('bo.static_pages.edit', $static_page)
return redirect()->route('bo.static_pages.edit', ['tenant' => \tenant(), 'static_page' => $static_page])
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1))
]));

View file

@ -0,0 +1,185 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
use App\Http\Requests\Bo\Tenants\UpdateTenantRequest;
use App\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TenantController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tenant::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/**
* @var \Illuminate\Database\Eloquent\Builder $tenantModels
* @phpstan-ignore larastan.relationExistence
*/
$tenantModels = Tenant::query()->with('domains');
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$tenantModels,
$search,
null,
'id',
);
}
$searchFields = \implode(', ', [
trans('validation.custom.theme'),
trans('validation.custom.domain'),
]);
/** Sort columns with a query */
$this->sortQuery($tenantModels);
/** Custom pagination */
$tenantModels = $this->paginate($tenantModels);
return view('back::pages.tenants.index', compact('tenantModels', 'search', 'searchFields'));
}
/**
* Display the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function show(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('back::pages.tenants.show', ['tenantModel' => $tenant]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function create(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('back::pages.tenants.create', [
'tenantModel' => $tenant
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreTenantRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$tenant = new Tenant();
$tenant->fill(Arr::except($request->validated(), 'domain'));
if ($tenant->saveOrFail()) {
$tenant->createDomain(Arr::only($request->validated(), 'domain'));
return redirect()
->route('bo.tenants.edit', $tenant)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.tenant'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(__('models.classes.tenant'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function edit(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('back::pages.tenants.edit', [
'tenantModel' => $tenant->load('domains'),
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\UpdateTenantRequest $request
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateTenantRequest $request, Tenant $tenant): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $tenant) {
$tenant->fill(Arr::except($request->validated(), 'domain'));
if ($tenant->saveOrFail()) {
$domainModel = $tenant->domains->firstOrFail();
// @phpstan-ignore property.notFound
$domainModel->domain = Arr::get($request->validated(), 'domain');
$domainModel->saveOrFail();
return redirect()
->route('bo.tenants.edit', $tenant)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.tenant'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(__('models.classes.tenant'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Tenant $tenant): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($tenant) {
if ($tenant->deleteOrFail()) {
return redirect()
->route('bo.tenants.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.tenant'))]
));
}
return redirect()
->back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(__('models.classes.tenant'))]
));
});
}
}

View file

@ -5,9 +5,11 @@ namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Models\Tenant;
use App\Models\User;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -72,7 +74,8 @@ class UserController extends Controller
*/
public function create(User $user): \Illuminate\Contracts\View\View
{
return view('back::pages.users.create', ['userModel' => $user]);
$tenants = Tenant::query()->orderBy('theme', 'ASC')->paginate(10);
return view('back::pages.users.create', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
@ -85,9 +88,10 @@ class UserController extends Controller
{
return DB::transaction(function () use ($request) {
$user = new User();
$user->fill($request->validated());
$user->fill(Arr::except($request->validated(), 'tenants'));
if ($user->saveOrFail()) {
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
return redirect()->route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
@ -108,7 +112,8 @@ class UserController extends Controller
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
return view('back::pages.users.edit', ['userModel' => $user]);
$tenants = Tenant::query()->orderBy('theme', 'ASC')->paginate(10);
return view('back::pages.users.edit', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
@ -121,9 +126,10 @@ class UserController extends Controller
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $user) {
$user->fill($request->validated());
$user->fill(Arr::except($request->validated(), 'tenants'));
if ($user->saveOrFail()) {
$user->tenants()->sync(collect($request->validated('tenants'))->pluck('id'));
return redirect()->route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))

View file

@ -4,11 +4,12 @@ namespace App\Http\Controllers;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use App\Enums\Theme\BootstrapThemeEnum;
use App\Http\Requests\Bo\PaginateModelsRequest;
use App\Http\Requests\Bo\ThemeRequest;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
@ -64,7 +65,7 @@ class Controller extends BaseController
*
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param callable|null $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
@ -73,7 +74,7 @@ class Controller extends BaseController
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
callable|null $searchQuery = null,
string ...$fields
): \Illuminate\Database\Eloquent\Builder {
if (count($fields)) {
@ -230,7 +231,7 @@ class Controller extends BaseController
*/
protected function paginate(
Builder $query,
ItemsPerPaginationEnum $pagination = null
ItemsPerPaginationEnum|null $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}";
@ -250,10 +251,10 @@ class Controller extends BaseController
/**
* Set the bootstrap theme or light (default).
*
* @param \Illuminate\Http\Request $request
* @param \App\Http\Requests\Bo\ThemeRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function setTheme(Request $request): \Illuminate\Http\RedirectResponse
protected function setTheme(ThemeRequest $request): \Illuminate\Http\RedirectResponse
{
$theme = ToolboxHelper::getValidatedEnum(
$request->theme ?: Cache::get('theme', BootstrapThemeEnum::light->value()),
@ -265,4 +266,20 @@ class Controller extends BaseController
}
return redirect()->back()->with('success', __('crud.messages.theme_updated'));
}
/**
* Get a paginate resource.
*
* @param \App\Http\Requests\Bo\PaginateModelsRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function jsonPaginateModels(PaginateModelsRequest $request): \Illuminate\Http\JsonResponse
{
/** @var \Illuminate\Database\Eloquent\Model $models */
$models = $request->targetModel::query()
->where($request->input('fieldName') ?? 'name', 'like', "%{$request->input('search')}%")
->orderBy($request->input('fieldName') ?? 'name', 'ASC')
->paginate($request->input('paginate') ?? 10);
return \response()->json($models);
}
}

View file

@ -129,12 +129,14 @@ class CustomerController extends Controller
$mesBr = app('mesBrAPI');
$brBasket = $mesBr->getBasket($crmUser);
if (!empty($brBasket['coupons'])) {
$canPrint = (collect($brBasket['coupons'])->sum('quantity') - collect($brBasket['coupons'])->sum('printed'));
$canPrint = (
collect($brBasket['coupons'])->sum('quantity') - collect($brBasket['coupons'])->sum('printed')
);
}
$winningList = $customer->participations()
->with(['instantWin'])->where(function (Builder $q) {
$q->whereHas('instantWin');
->with(['instantWin'])->where(function (Builder $query) {
$query->whereHas('instantWin');
})->get();
return view('theme::pages.participation.winning-participations', compact('winningList', 'canPrint'));

View file

@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Models\FaqQuestion;
use App\Models\FaqTheme;
use App\Models\Range;
use App\Models\Settings;
use Illuminate\Support\Facades\Gate;
class HomeController extends Controller
@ -71,7 +70,7 @@ class HomeController extends Controller
public function teaser(): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if (!$settings->should_display_teaser_page) {
return redirect()->route('fo.homepage');
}
@ -86,7 +85,7 @@ class HomeController extends Controller
public function endGame(): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if (!$settings->should_display_end_page) {
return redirect()->route('fo.homepage');
}

View file

@ -10,8 +10,6 @@ use App\Mail\Won\PrizeWonAdminMail;
use App\Models\Customer;
use App\Models\InstantWin;
use App\Models\Participation;
use App\Models\Discount;
use App\Models\Settings;
use App\Notifications\Customer\HasWonDiscount;
use App\Notifications\Customer\HasWonInstantWin;
use App\Repositories\Fo\CrmRepository;
@ -73,7 +71,7 @@ class ParticipateController extends Controller
->get()->sortBy('available_at')->first();
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
@ -88,7 +86,7 @@ class ParticipateController extends Controller
app('clientIp'),
$customer,
$code,
$product
$product,
);
// * Si l'utilisateur actuel à déjà gagné un gros lot, pas d'instant gagnant.
@ -182,7 +180,7 @@ class ParticipateController extends Controller
// * Phase - 2 Mise à jour du coupon dans MesBr
try {
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \LDC\MESBR\MesBr */
$mesBr = app('mesBrAPI');

View file

@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\SubmitWinningProofRequest;
use App\Mail\Won\WinningProofAdminMail;
use App\Models\Participation;
use App\Models\Settings;
use App\Notifications\Customer\HasSubmitedWinProof;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
@ -84,7 +83,7 @@ class WinningProofController extends Controller
]);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
// * --- PHASE 1 Update Crm Customer ---
/** @var \App\Models\Customer */
@ -122,7 +121,7 @@ class WinningProofController extends Controller
]));
$participation->save();
$customer->notify(new HasSubmitedWinProof($request->input('firstname')));
$customer->notify(new HasSubmitedWinProof($request->input('firstname'), $settings));
// * Dispatch notification to mail list
Mail::to($settings->notification_emails)->send(new WinningProofAdminMail(

View file

@ -29,7 +29,7 @@ class Kernel extends HttpKernel
* @var array<string, array<int, string>>
*/
protected $middlewareGroups = [
'web' => [
'back' => [
'htmlsanitize',
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
@ -38,12 +38,25 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class,
\App\Http\Middleware\CheckThemeVersion::class
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
'front' => [
\Stancl\Tenancy\Middleware\InitializeTenancyByDomain::class,
\Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains::class,
\App\Http\Middleware\SetThemeNamespace::class,
'htmlsanitize',
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class,
\App\Http\Middleware\CheckThemeVersion::class,
],
'saml' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
],
];
@ -55,17 +68,18 @@ class Kernel extends HttpKernel
* @var array<string, string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'check-game-status' => \App\Http\Middleware\CheckGameStatus::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'check-game-status' => \App\Http\Middleware\CheckGameStatus::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
'restrict_tenant_access' => \App\Http\Middleware\RestrictTenantAccess::class,
];
}

View file

@ -17,11 +17,11 @@ class Authenticate extends Middleware
protected function redirectTo(Request $request): ?string
{
$logins = [
'backend' => route('bo.login')
'backend' => route('bo.login'),
];
if (!$request->expectsJson()) {
// * Notify must login
Session::flash('warning', trans('Veuillez vous connecter pour continuer'));
Session::flash('warning', trans('auth.login'));
foreach ($request->route()->gatherMiddleware() as $middleware) {
if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) {
@ -33,7 +33,6 @@ class Authenticate extends Middleware
}
return route('fo.login');
}
return null;
}
}

View file

@ -2,7 +2,6 @@
namespace App\Http\Middleware;
use App\Models\Settings;
use Illuminate\Http\Request;
class CheckGameStatus
@ -20,7 +19,7 @@ class CheckGameStatus
public function handle(Request $request, \Closure $next)
{
/** @var \App\Models\Settings $settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
return match (true) {
// * End page

View file

@ -8,7 +8,6 @@ use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class CheckThemeVersion
{
@ -18,9 +17,10 @@ class CheckThemeVersion
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):(\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Exception Folder or config file doesn't exist.
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, \Closure $next): Response
public function handle(Request $request, \Closure $next): \Symfony\Component\HttpFoundation\Response
{
// phpcs:enable
@ -30,7 +30,7 @@ class CheckThemeVersion
});
// * Assert theme version does not diverge more than minor
$appVer = Str::of($appInfos->version)->explode('.');
$themeVer = Str::of(config('theme.version'))->explode('.');
$themeVer = Str::of(themeConfig()['version'])->explode('.');
if (
// * Major check
Arr::get($appVer, 0) !== Arr::get($themeVer, 0) or
@ -41,7 +41,7 @@ class CheckThemeVersion
'Ce thème n\'a semble t-il pas été vérifié pour cette version du moteur'
));
}
}
} //end if
return $next($request);
}

View file

@ -3,25 +3,26 @@
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string|null ...$guards
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param string|null ...$guards
* @return \Symfony\Component\HttpFoundation\Response
* @throws \RuntimeException If guard is unhandled.
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, \Closure $next, ...$guards): Response
{
public function handle(
Request $request,
\Closure $next,
string|null ...$guards
): \Symfony\Component\HttpFoundation\Response {
// phpcs:enable phpcs:disable
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (auth($guard)->check()) {
switch ($guard) {
@ -29,7 +30,7 @@ class RedirectIfAuthenticated
return \redirect()->route('fo.homepage');
case 'backend':
if (!$request->routeIs('bo.*')) {
continue;
continue 2;
}
return \redirect()->route('bo.dashboard');
default:
@ -37,7 +38,6 @@ class RedirectIfAuthenticated
}
}
}
return $next($request);
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Http\Middleware;
use App\Enums\Users\RoleEnum;
use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
class RestrictTenantAccess
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{
/** @var \App\Models\User|null $user */
$user = \auth('backend')->user();
if ($user) {
if (
!$request->routeIs('bo.dashboard') &&
!$request->routeIs('bo.users.*') &&
!$request->routeIs('bo.tenants.*') &&
$request->tenant &&
!\in_array($request->tenant, Tenant::get()->pluck('id')->toArray())
) {
// @phpstan-ignore argument.type
abort('404');
}
$tenantKey = Tenant::query()->value((new Tenant())->getKeyName());
if (
(Tenant::count() && $request->routeIs('bo.tenants.*') && $user->role->equals(RoleEnum::conceptor)) ||
(Tenant::count() && $request->routeIs('bo.users.*') && $user->role->equals(RoleEnum::conceptor))
) {
tenancy()->initialize($tenantKey);
return $next($request);
}
if (
Tenant::count() && ($request->routeIs('bo.dashboard')
|| $request->routeIs('bo.users.edit', $user->getKey()))
) {
tenancy()->initialize(($user->tenants->count()) ? $user->tenants->first() : $tenantKey);
return $next($request);
}
} //end if
return $next($request);
}
}

View file

@ -7,7 +7,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
@ -31,7 +30,7 @@ class SecureHeaders
* @return \Symfony\Component\HttpFoundation\Response
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, Closure $next): Response
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{
// phpcs:enable
// * For Vite DEV
@ -39,7 +38,6 @@ class SecureHeaders
View::share('nonce', $nonce = Vite::useCspNonce());
// * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-{$nonce}' 'strict-dynamic'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ?
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList);
@ -93,7 +91,6 @@ class SecureHeaders
'https: blob:',
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
"$scheme://www.google-analytics.com",
// * VITE DEV
config('app.debug') ? "https://{$host}:4443" : '',
]) . ';'

View file

@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\View;
class SetThemeNamespace
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{
/** @var string $themeName */
$themeName = \tenant()->theme;
View::addNamespace('theme', resource_path("theme/$themeName/views"));
View::share('themeResources', "resources/theme/$themeName");
config()->set('image-resizer.templates', themeConfig()['resizes'] ?? []);
return $next($request);
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
class PaginateModelsRequest 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
{
return [
'targetModel' => [
'required',
'string',
'starts_with:App\\Models\\',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
if (!class_exists($value)) {
$fail("Le modèle spécifié n'existe pas.");
return;
}
if (!is_subclass_of($value, \Illuminate\Database\Eloquent\Model::class)) {
$fail("Le modèle spécifié n'est pas un modèle Eloquent.");
}
},
],
'page' => 'required|integer|min:1',
'paginate' => 'required|integer|min:1',
'fieldName' => 'required|string|alpha_dash|max:255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'targetModel' => trans('validation.custom.targetModel'),
'page' => trans('validation.custom.page'),
'paginate' => trans('validation.custom.paginate'),
'fieldName' => trans('validation.custom.fieldName'),
];
}
}

View file

@ -50,10 +50,10 @@ class StorePrizeRequest extends FormRequest
'quantity' => 'required|numeric|min:1',
'pdf_winning_proof' => 'nullable|file|mimes:pdf',
];
$min_width = config('theme.models.prizes.picture_min_width');
$min_height = config('theme.models.prizes.picture_min_height');
$max_width = config('theme.models.prizes.picture_max_width');
$max_height = config('theme.models.prizes.picture_max_height');
$min_width = themeConfig()['models']['prizes']['picture_min_width'];
$min_height = themeConfig()['models']['prizes']['picture_min_height'];
$max_width = themeConfig()['models']['prizes']['picture_max_width'];
$max_height = themeConfig()['models']['prizes']['picture_max_height'];
return \array_merge(
$rules,
$this->pictureRules('picture', $min_width, $min_height, $max_width, $max_height),
@ -68,14 +68,14 @@ class StorePrizeRequest extends FormRequest
public function attributes(): array
{
return [
'label' => trans('validation.custom.label'),
'picture' => trans('validation.custom.picture'),
'picture_alt' => trans('validation.custom.picture_alt'),
'picture_title' => trans('validation.custom.picture_title'),
'arg_title' => trans('validation.attributes.title'),
'arg_subtitle' => trans('validation.custom.subtitle'),
'arg_content' => trans('validation.attributes.content'),
'quantity' => trans('validation.custom.quantity'),
'label' => trans('validation.custom.label'),
'picture' => trans('validation.custom.picture'),
'picture_alt' => trans('validation.custom.picture_alt'),
'picture_title' => trans('validation.custom.picture_title'),
'arg_title' => trans('validation.attributes.title'),
'arg_subtitle' => trans('validation.custom.subtitle'),
'arg_content' => trans('validation.attributes.content'),
'quantity' => trans('validation.custom.quantity'),
'pdf_winning_proof' => trans('validation.custom.pdf_winning_proof'),
];
}

View file

@ -17,7 +17,7 @@ class StoreProductRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return Gate::check('create', Product::class);
}
@ -27,7 +27,7 @@ class StoreProductRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->mergePicture('picture');
}
@ -37,11 +37,11 @@ class StoreProductRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'name' => 'required|string|min:3|max:255',
'ean_code' => [
$rules = [
'name' => 'required|string|min:3|max:255',
'ean_code' => [
'required',
'unique:products,ean_code',
'regex:/^[0-9]{7}$|^[0-9]{8}$|^[0-9]{13}$/',
@ -50,11 +50,13 @@ class StoreProductRequest extends FormRequest
'picture_title' => 'nullable|required_with:picture|string|between:3,255',
'range_id' => 'exists:ranges,id'
];
$width = config('theme.models.products.picture_max_width');
$height = config('theme.models.products.picture_max_height');
$min_width = themeConfig()['models']['products']['picture_min_width'];
$min_height = themeConfig()['models']['products']['picture_min_height'];
$max_width = themeConfig()['models']['products']['picture_max_width'];
$max_height = themeConfig()['models']['products']['picture_max_height'];
return \array_merge(
$rules,
$this->pictureRules('picture', $width, $height, $width, $height, true),
$this->pictureRules('picture', $min_width, $min_height, $max_width, $max_height, true),
);
}
@ -66,24 +68,24 @@ class StoreProductRequest extends FormRequest
public function attributes(): array
{
return [
'name' => trans('validation.attributes.name'),
'name' => trans('validation.attributes.name'),
'picture_title' => trans(':field1 :inter1:field2 :inter2:model', [
'field1' => Str::ucfirst(trans('validation.custom.attribute_title')),
'inter1' => trans('validation.custom.of_the.vowel'),
'field2' => trans('validation.attributes.image'),
'inter2' => trans('validation.custom.of_the.male'),
'model' => trans('models.classes.product'),
'model' => trans('models.classes.product'),
]),
'picture_alt' => trans(':field1 :inter1:field2 :inter2:model', [
'picture_alt' => trans(':field1 :inter1:field2 :inter2:model', [
'field1' => Str::ucfirst(trans('validation.custom.attribute_alt')),
'inter1' => trans('validation.custom.of_the.vowel'),
'field2' => trans('validation.attributes.image'),
'inter2' => trans('validation.custom.of_the.male'),
'model' => trans('models.classes.product'),
'model' => trans('models.classes.product'),
]),
'picture' => trans('validation.attributes.image'),
'ean_code' => trans('validation.custom.ean_code'),
'range_id' => trans('models.classes.range'),
'picture' => trans('validation.attributes.image'),
'ean_code' => trans('validation.custom.ean_code'),
'range_id' => trans('models.classes.range'),
];
}
}

View file

@ -12,7 +12,7 @@ class UpdateProductRequest extends StoreProductRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return Gate::check('update', $this->route('product'));
}
@ -22,7 +22,7 @@ class UpdateProductRequest extends StoreProductRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
/** @var \App\Models\Product */
$productModel = $this->route('product');

View file

@ -38,16 +38,18 @@ class StoreRangeRequest extends FormRequest
*/
public function rules()
{
$rules = [
$rules = [
'name' => 'required|string|min:2|max:255',
'picture_alt' => 'nullable|required_with:picture|string|between:3,255',
'picture_title' => 'nullable|required_with:picture|string|between:3,255',
];
$width = config('theme.models.ranges.picture_max_width');
$height = config('theme.models.ranges.picture_max_height');
$min_width = themeConfig()['models']['ranges']['picture_min_width'];
$min_height = themeConfig()['models']['ranges']['picture_min_height'];
$max_width = themeConfig()['models']['ranges']['picture_max_width'];
$max_height = themeConfig()['models']['ranges']['picture_max_height'];
return \array_merge(
$rules,
$this->pictureRules('picture', $width, $height, $width, $height, false),
$this->pictureRules('picture', $min_width, $min_height, $max_width, $max_height, false),
);
}
}

View file

@ -4,6 +4,7 @@ namespace App\Http\Requests\Bo\Settings;
use App\Enums\CodeType;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Settings;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
@ -20,7 +21,8 @@ class UpdateSettingsRequest extends FormRequest
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('settings'));
$settingsModel = Settings::query()->where('tenant_id', tenant('id'))->firstOrFail();
return Gate::check('update', $settingsModel);
}
/**
@ -82,7 +84,9 @@ class UpdateSettingsRequest extends FormRequest
'discounts_site' => 'nullable|required_if:discounts_activated,true,1|string|min:3|max:255',
'discounts_campaign' => 'nullable|required_if:discounts_activated,true,1|string|min:3|max:255',
'web_coupons' => [
'present', 'array', function ($attribute, $value, $fail) {
'present',
'array',
function ($attribute, $value, $fail) {
if ($this->post('discounts_activated') and !collect($value)->filter()->count()) {
$fail(trans('validation.min.array', [
'attribute' => Arr::get($this->attributes(), $attribute),
@ -103,7 +107,8 @@ class UpdateSettingsRequest extends FormRequest
'web_coupons.*.face_value' => 'required|numeric|min:10|max:300',
'web_coupons.*.valid_on' => 'required|string|min:3|max:255',
'web_coupons.*.picture' => [
'required', function (string $attribute, $value, callable $fail) {
'required',
function (string $attribute, $value, callable $fail) {
ToolboxHelper::validatePicture(
$attribute,
$value,

View file

@ -0,0 +1,49 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use App\Models\Tenant;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreTenantRequest extends FormRequest
{
use HasPicture;
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', Tenant::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, string>
*/
public function rules(): array
{
return [
'theme' => 'required|string|min:3|max:255|unique:tenants,theme',
'domain' => 'required|string|min:3|max:255|unique:domains,domain',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string, string>
*/
public function attributes(): array
{
return [
'theme' => trans('validation.custom.theme'),
'domain' => trans('validation.custom.domain'),
];
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
class UpdateTenantRequest extends StoreTenantRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tenant'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, string>
*/
public function rules(): array
{
/** @var \App\Models\Tenant */
$tenantModel = $this->route('tenant');
return array_merge(parent::rules(), [
'theme' => [
'required',
'string',
'min:3',
'max:255',
Rule::unique('tenants', 'theme')->ignore($tenantModel->getRouteKey())
],
'domain' => [
'required',
'string',
'min:3',
'max:255',
Rule::unique('domains', 'domain')->ignore($tenantModel->domains->first()->getKey())
],
]);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Bo;
use App\Enums\Theme\BootstrapThemeEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
class ThemeRequest 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
{
return [
'theme' => ['required', new Enum(BootstrapThemeEnum::class)],
];
}
/**
* Get custom attributes for validator errors.
*
* @return array<string,\Illuminate\Contracts\Translation\Translator|string|array<mixed>|null>
*/
public function attributes(): array
{
return [
'theme' => trans('validation.custom.theme'),
];
}
}

View file

@ -6,8 +6,9 @@ use App\Enums\Users\RoleEnum;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
class StoreUserRequest extends FormRequest
{
@ -16,7 +17,7 @@ class StoreUserRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return Gate::check('create', User::class);
}
@ -26,10 +27,11 @@ class StoreUserRequest extends FormRequest
*
* @return void
*/
protected function prepareForValidation()
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->boolean('published')
'published' => $this->boolean('published'),
'tenants' => ($this->input('role') == RoleEnum::conceptor->value()) ? null : $this->input('tenants'),
]);
}
@ -40,20 +42,20 @@ class StoreUserRequest extends FormRequest
*/
public function rules(): array
{
$isAdmin = $this->input('role') == RoleEnum::admin->value();
return [
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised()
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
Rule::unique('users', 'email')
],
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
'role' => ['required', new Enum(RoleEnum::class)],
'tenants' => $isAdmin ? 'required|array' : 'nullable|array',
'tenants.*' => $isAdmin ? 'required|array' : 'nullable|array',
'tenants.*.id' => $isAdmin ? 'required|exists:tenants,id' : 'nullable|exists:tenants,id',
'published' => 'required|boolean',
];
}
@ -66,11 +68,11 @@ class StoreUserRequest extends FormRequest
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'password' => trans('validation.attributes.password'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication')
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'role' => trans('validation.attributes.role'),
'tenants' => Str::of(trans('models.classes.tenant'))->plural(),
'published' => trans('validation.attributes.published'),
];
}
}

View file

@ -2,9 +2,9 @@
namespace App\Http\Requests\Bo\Users;
use App\Enums\Users\RoleEnum;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\Rules\Enum;
class UpdateUserRequest extends StoreUserRequest
{
@ -13,31 +13,11 @@ class UpdateUserRequest extends StoreUserRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return Gate::check('update', $this->route('user'));
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
/** @var \App\Models\User */
$userModel = $this->route('user');
$this->merge([
'published' => $this->boolean('published'),
'password' => $this->input(
'password',
) ?? $userModel->password,
'password_confirmation' => $this->input(
'password_confirmation'
) ?? $userModel->password
]);
}
/**
* Get the validation rules that apply to the request.
*
@ -45,22 +25,18 @@ class UpdateUserRequest extends StoreUserRequest
*/
public function rules(): array
{
/** @var \App\Models\User */
$userModel = $this->route('user');
return array_merge(parent::rules(), [
'email' => [
/** @var \App\Models\User $userModel */
$userModel = request()->route()->parameter('user');
$rules = [
'email' => [
'required',
'string',
'unique:users,email,' . $userModel->getKey(),
'email:rfc,strict,dns,spoof,filter',
Rule::unique('users', 'email')->ignore($userModel->getRouteKey())
'max:255'
],
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised()
],
]);
'role' => ['sometimes', new Enum(RoleEnum::class)],
];
return \array_merge(parent::rules(), $rules);
}
}

View file

@ -2,7 +2,6 @@
namespace App\Http\Requests\Fo\Customers;
use App\Models\Settings;
use App\Repositories\Fo\CrmRepository;
use App\Rules\IsAdult;
use Illuminate\Foundation\Http\FormRequest;
@ -48,7 +47,7 @@ class CreateAccountRequest extends FormRequest
public function rules(): array
{
/** @var \App\Models\Settings $settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer|null */
$authUser = auth('frontend')->user();
@ -197,7 +196,7 @@ class CreateAccountRequest extends FormRequest
]),
]
),
}; //end match
}; // end match
return $messages;
}

View file

@ -11,7 +11,7 @@ class SingleOptinRequest extends FormRequest
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return true;
}
@ -21,7 +21,7 @@ class SingleOptinRequest extends FormRequest
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof',

View file

@ -6,7 +6,6 @@ use App\Enums\CodeType;
use App\Models\Code;
use App\Models\Participation;
use App\Models\Product;
use App\Models\Settings;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Http\FormRequest;
@ -42,7 +41,7 @@ class ParticipateRequest extends FormRequest
protected function prepareForValidation(): void
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if ($settings->code_type->equals(CodeType::unique) || $settings->code_type->equals(CodeType::both)) {
try {
$this->merge([
@ -94,10 +93,10 @@ class ParticipateRequest extends FormRequest
public function rules(): array
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
$rules = [
'game_rules' => 'required|boolean|accepted',
'game_rules' => 'required|boolean|accepted',
];
// ? Does the game use unique Codes ?
@ -118,8 +117,8 @@ class ParticipateRequest extends FormRequest
/** @var \App\Models\Code $value */
// * Check si l'utilisateur n'a pas déjà utilisé ce code.
$query = Participation::query()
->whereHas('code', fn (Builder $q) => $q->where('id', $value->id))
->whereHas('customer', fn (Builder $q) => $q->where('id', $customer->id));
->whereHas('code', fn(Builder $q) => $q->where('id', $value->id))
->whereHas('customer', fn(Builder $q) => $q->where('id', $customer->id));
// ! Une seule participation avec un code donné par joueur
if ($query->exists()) {
$fail(trans('Vous avez déjà utilisé ce code unique.'));

View file

@ -38,10 +38,10 @@ class SubmitWinningProofRequest extends CreateAccountRequest
'country'
]
), [
'phone' => 'required|phone:FR',
'phone' => 'required|phone:FR',
// * Min 10 KB, Max 2MB
'sticker' => 'required|mimes:jpeg,jpg,png,pdf|min:10|max:2048',
'ticket' => 'required|mimes:jpeg,jpg,png,pdf|min:10|max:2048',
'ticket' => 'required|mimes:jpeg,jpg,png,pdf|min:10|max:2048',
'barcode' => 'required|mimes:jpeg,jpg,png,pdf|min:10|max:2048',
]);
}
@ -57,7 +57,7 @@ class SubmitWinningProofRequest extends CreateAccountRequest
parent::attributes(),
[
'sticker' => trans('validation.custom.sticker'),
'ticket' => trans('validation.custom.ticket'),
'ticket' => trans('validation.custom.ticket'),
'barcode' => trans('validation.custom.barcode'),
]
);

View file

@ -5,6 +5,7 @@ namespace App\Jobs;
use App\Mail\Jobs\CodeExportSucceed;
use App\Mail\Jobs\JobFailed;
use App\Models\Code;
use App\Models\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -38,19 +39,29 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
*/
protected $exportFileName = null;
/**
* Setting of the current model.
*
* @var \App\Models\Settings|null $settingModel
*/
protected $settingModel = null;
/**
* Create a new job instance.
*
* @param integer $nbCodeFic Nombre de codes par fichiers.
* @param integer $nbCodeTot Nombre total de codes à exporter.
* @param integer $nbCodeLigne Nombre de codes par ligne.
* @param integer $nbCodeFic Nombre de codes par fichiers.
* @param integer $nbCodeTot Nombre total de codes à exporter.
* @param integer $nbCodeLigne Nombre de codes par ligne.
* @param Settings $settings Setting of the current tenant.
* @return void
*/
public function __construct(
protected int $nbCodeFic,
protected int $nbCodeTot,
protected int $nbCodeLigne
protected int $nbCodeLigne,
protected Settings $settings,
) {
$this->settingModel = $settings;
}
/**
@ -58,7 +69,7 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
*
* @return void
*/
public function handle()
public function handle(): void
{
DB::transaction(function () {
$this->exportFileName = sprintf(
@ -70,6 +81,7 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
try {
$lastExportedCodeId = Code::query()
->where('tenant_id', $this->settingModel->tenant_id)
->where('code_exported', true)
->orderBy('id', 'desc')->first()->id ?? null;
@ -172,7 +184,10 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
}
// ? Have we reached pagination limit for mass saving ?
if (count($exportedCodesId) === $paginate) {
Code::query()->whereIn('id', $exportedCodesId)->update(['code_exported' => true]);
Code::query()
->where('tenant_id', $this->settingModel->tenant_id)
->whereIn('id', $exportedCodesId)
->update(['code_exported' => true]);
$exportedCodesId = [];
}
} //end if
@ -180,7 +195,10 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
// ? Are there exported codes to save ?
if (count($exportedCodesId)) {
Code::query()->whereIn('id', $exportedCodesId)->update(['code_exported' => true]);
Code::query()
->where('tenant_id', $this->settingModel->tenant_id)
->whereIn('id', $exportedCodesId)
->update(['code_exported' => true]);
}
return $outPaths;
}
@ -192,12 +210,12 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
* @param \Throwable $exception
* @return void
*/
public function failed(\Throwable $exception)
public function failed(\Throwable $exception): void
{
Mail::to(config('mail.admin'))->send(
new JobFailed(__CLASS__, [
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
'trace' => $exception->getTraceAsString()
])
);
}

View file

@ -32,15 +32,24 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
*/
public $timeout = 7200;
/**
* Setting of the current model.
*
* @var \App\Models\Settings|null $settingModel
*/
protected $settingModel = null;
/**
* Create a new job instance.
*
* @param integer $amount Amount of code to generate.
* @param integer $length Length of each code to generate.
* @param integer $amount Amount of code to generate.
* @param integer $length Length of each code to generate.
* @param Settings $settings Setting of the current tenant.
* @return void
*/
public function __construct(protected int $amount, protected int $length)
public function __construct(protected int $amount, protected int $length, protected Settings $settings)
{
$this->settingModel = $settings;
}
/**
@ -48,15 +57,12 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
*
* @return void
*/
public function handle()
public function handle(): void
{
DB::transaction(function () {
// ! Le premier code commence à 1
$last_id = (Code::query()->max('id') ?? 0) + 1;
/** @var \App\Models\Settings $settings */
$settings = app(Settings::class);
$maxSQLPlaceholders = 65535;
$insertColumns = collect(DB::getSchemaBuilder()
@ -66,18 +72,10 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
return !in_array($name, ['id', 'code_exported']);
})->count() + 1;
/**
* Nombre d'insertions max sur un seule requète
*
* @var integer $perPage
*/
/** @var integer $perPage Nombre d'insertions max sur un seule requète. */
$perPage = \floor($maxSQLPlaceholders / $insertColumns);
/**
* Nombre de page totale à insérer.
*
* @var integer $totalPages
*/
/** @var integer $totalPages Nombre de page totale à insérer. */
$totalPages = \ceil($this->amount / $perPage);
$totalGeneratedInserts = 0;
@ -87,7 +85,8 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
$insertToGenerate = min($perPage, $this->amount - $totalGeneratedInserts);
do {
$inserts[] = [
'id' => $last_id++,
'id' => $last_id++,
'tenant_id' => $this->settingModel->tenant_id,
'date_creation' => now()->format('Y-m-d'),
];
} while (($totalGeneratedInserts < $this->amount) && --$insertToGenerate);
@ -101,23 +100,23 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
// * Fin de la génération.
Mail::to(config('mail.admin'))->send(new CodeGenerationSucceed(
$this->amount,
$settings->name
$this->settingModel->name,
));
});
}
/**
* In case of job failure
* In case of job failure.
*
* @param \Throwable $exception
* @return void
*/
public function failed(\Throwable $exception)
public function failed(\Throwable $exception): void
{
Mail::to(config('mail.admin'))->send(
new JobFailed(__CLASS__, [
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
'trace' => $exception->getTraceAsString()
])
);
}

View file

@ -53,28 +53,28 @@ class Discount extends \stdClass implements Jsonable, Arrayable, \JsonSerializab
/**
* Make and instance
*
* @param integer $web_coupon_identifiant EAN 13 format (Mais ne représente pas un EAN 13),
* @param string $web_coupon_identifiant EAN 13 format (Mais ne représente pas un EAN 13),
* Format utilisé dans MesBr.
* @param integer $percentage Pourcentage de chance de gagner le lot nombre de 1 à 100.
* @param integer $face_value Valeur faciale en centimes.
* @param string $valid_on Text valable sur.. (duplicat de ce qui est inscrit dans MesBr)
* @param string $percentage Pourcentage de chance de gagner le lot nombre de 1 à 100.
* @param string $face_value Valeur faciale en centimes.
* @param string $valid_on Text valable sur.. (duplicat de ce qui est inscrit dans MesBr)
* Pour affichage sur la page de gain est dans le mail.
* @param string $picture Image locale du Coupon.
* @param string $picture Image locale du Coupon.
*
* @return \App\Lib\Discount
* @throws \RuntimeException If $type or $data is wrong.
*/
public static function make(
int $web_coupon_identifiant,
int $percentage,
int $face_value,
string $web_coupon_identifiant,
string $percentage,
string $face_value,
string $valid_on,
string $picture
): self {
$ipTypeData = new self();
$ipTypeData->web_coupon_identifiant = $web_coupon_identifiant;
$ipTypeData->percentage = $percentage;
$ipTypeData->face_value = $face_value;
$ipTypeData->web_coupon_identifiant = (int) $web_coupon_identifiant;
$ipTypeData->percentage = (int) $percentage;
$ipTypeData->face_value = (int) $face_value;
$ipTypeData->valid_on = $valid_on;
$ipTypeData->picture = $picture;
self::assertIsInputTypeData($ipTypeData);

View file

@ -105,7 +105,7 @@ class FileStorageHelper
Model $model,
$file,
bool $slugify = false,
string $filename = null,
string|null $filename = null,
bool $private = false
): string {
// phpcs:enable
@ -166,7 +166,7 @@ class FileStorageHelper
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
string|null $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {

View file

@ -2,7 +2,6 @@
namespace App\Lib\Helpers;
use App\Models\Settings;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
@ -61,7 +60,7 @@ class ToolboxHelper
public static function assertGameIsNotOppened(): void
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if ($settings->game_is_open) {
throw new \RuntimeException('Cannot delete prize while game is oppened');
}
@ -98,7 +97,7 @@ class ToolboxHelper
Validator::validate($request->all(), [
'g-recaptcha-response' => [
'required', 'string',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($isV3) {
try {
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
@ -292,7 +291,7 @@ class ToolboxHelper
* @param string $table
* @param string $field
* @param string|integer|float|boolean|null|array $value
* @param integer|null $id
* @param integer|string|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
@ -301,7 +300,7 @@ class ToolboxHelper
string $table,
string $field,
string|int|float|bool|null|array $value,
?int $id = null,
int|string|null $id = null,
?callable $query = null
) {
if (!$query) {

View file

@ -3,7 +3,6 @@
namespace App\Mail\Won;
use App\Lib\Discount;
use App\Models\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
@ -35,7 +34,9 @@ class DiscountWonMail extends Mailable implements ShouldQueue
protected Discount $discount,
protected string $print_url,
protected string $company
) {}
) {
// Keep empty.
}
/**
* Get the message envelope.
@ -60,15 +61,17 @@ class DiscountWonMail extends Mailable implements ShouldQueue
public function content(): \Illuminate\Mail\Mailables\Content
{
// * Fix for async usage
View::addNamespace('theme', resource_path('theme/mounted/views'));
/** @var string $themeName; */
$themeName = \tenant()->theme;
View::addNamespace('theme', resource_path("theme/$themeName/views"));
return new Content(
markdown: 'theme::mails.won-discount',
with: [
'first_name' => $this->recipient->name ?? $this->recipient->address,
'game_name' => $this->game_name,
'discount' => $this->discount,
'print_url' => $this->print_url,
'company' => $this->company,
'game_name' => $this->game_name,
'discount' => $this->discount,
'print_url' => $this->print_url,
'company' => $this->company,
],
);
}

View file

@ -28,7 +28,9 @@ class PrizeIsSentMail extends Mailable implements ShouldQueue
protected Address $recipient,
protected string $game_name,
protected string $company
) {}
) {
// Keep empty.
}
/**
* Get the message envelope.
@ -51,13 +53,15 @@ class PrizeIsSentMail extends Mailable implements ShouldQueue
public function content(): \Illuminate\Mail\Mailables\Content
{
// * Fix for async usage
View::addNamespace('theme', resource_path('theme/mounted/views'));
/** @var string $themeName; */
$themeName = \tenant()->theme;
View::addNamespace('theme', resource_path("theme/$themeName/views"));
return new Content(
markdown: 'theme::mails.prize-sent',
with: [
'first_name' => $this->recipient->name ?? $this->recipient->address,
'game_name' => $this->game_name,
'company' => $this->company,
'game_name' => $this->game_name,
'company' => $this->company,
],
);
}

View file

@ -34,7 +34,9 @@ class PrizeWonMail extends Mailable implements ShouldQueue
protected Prize $prize,
protected string $print_url,
protected string $company
) {}
) {
// Keep empty.
}
/**
* Get the message envelope.
@ -58,15 +60,18 @@ class PrizeWonMail extends Mailable implements ShouldQueue
*/
public function content(): \Illuminate\Mail\Mailables\Content
{
// * Fix for async usage
View::addNamespace('theme', resource_path('theme/mounted/views'));
/** @var \App\Models\Tenant $tenantModel */
$tenantModel = $this->prize->tenant;
/** @var string $themeName; */
$themeName = $tenantModel->theme;
View::addNamespace('theme', resource_path("theme/$themeName/views"));
return new Content(
markdown: 'theme::mails.won-prize',
with: [
'first_name' => $this->recipient->name ?? $this->recipient->address,
'game_name' => $this->game_name,
'first_name' => $this->recipient->name ?? $this->recipient->address,
'game_name' => $this->game_name,
'prize_label' => $this->prize->label,
'company' => $this->company
'company' => $this->company,
],
);
}

View file

@ -2,6 +2,7 @@
namespace App\Mail\Won;
use App\Models\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
@ -19,16 +20,16 @@ class WinningProofSubmitedMail extends Mailable implements ShouldQueue
/**
* Create a new message instance.
*
* @param \Illuminate\Mail\Mailables\Address $recipient The winner that has submited winning proof.
* @param string $game_name The game name.
* @param string $company The company name.
* @param \Illuminate\Mail\Mailables\Address $recipient The winner that has submited winning proof.
* @param \App\Models\Settings $settingsModel The game settings.
* @return void
*/
public function __construct(
protected Address $recipient,
protected string $game_name,
protected string $company
) {}
protected Settings $settingsModel,
) {
// Keep empty.
}
/**
* Get the message envelope.
@ -51,13 +52,17 @@ class WinningProofSubmitedMail extends Mailable implements ShouldQueue
public function content(): \Illuminate\Mail\Mailables\Content
{
// * Fix for async usage
View::addNamespace('theme', resource_path('theme/mounted/views'));
/** @var \App\Models\Tenant $tenantModel */
$tenantModel = $this->settingsModel->tenant;
/** @var string $themeName; */
$themeName = $tenantModel->theme;
View::addNamespace('theme', resource_path("theme/$themeName/views"));
return new Content(
markdown: 'theme::mails.submited-proof',
with: [
'first_name' => $this->recipient->name ?? $this->recipient->address,
'game_name' => $this->game_name,
'company' => $this->company
'game_name' => $this->settingsModel->name,
'company' => $this->settingsModel->company,
],
);
}

View file

@ -7,6 +7,7 @@ use App\Traits\Models\ActivityLog;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* BannedIp.
@ -18,6 +19,7 @@ use Illuminate\Database\Eloquent\Model;
class BannedIp extends Model
{
use ActivityLog;
use BelongsToTenant;
use IsCachedAsCollection;
use HasFactory;

View file

@ -174,7 +174,7 @@ class Code extends Model
public function hasBeenUsedTooManyTimes(): Attribute
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
return Attribute::make(
get: fn (): bool => $settings->nb_unique_code_use ?
Code::query()->where('id', $this->id)->whereHas(

View file

@ -24,6 +24,7 @@ use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property string $email Email.
@ -40,6 +41,7 @@ use LDC\LDCCRM\Models\Customer as CrmCustomer;
class Customer extends Authenticatable implements MustVerifyEmail
{
use ActivityLog;
use BelongsToTenant;
use Notifiable;
use Prunable;
use SoftDeletes;

View file

@ -6,7 +6,7 @@ use App\Traits\Models\ActivityLog;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* FaqQuestion.
@ -23,6 +23,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class FaqQuestion extends Model
{
use ActivityLog;
use BelongsToTenant;
use HasFactory;
use IsCachedAsCollection;
@ -51,7 +52,7 @@ class FaqQuestion extends Model
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
static::created(fn () => self::removeCached());
static::updated(fn () => self::removeCached());
@ -65,7 +66,7 @@ class FaqQuestion extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function theme(): BelongsTo
public function theme(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(FaqTheme::class, 'theme_id');
}

View file

@ -6,8 +6,8 @@ use App\Traits\Models\ActivityLog;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Validation\ValidationException;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* FaqTheme.
@ -25,6 +25,7 @@ use Illuminate\Validation\ValidationException;
class FaqTheme extends Model
{
use ActivityLog;
use BelongsToTenant;
use HasFactory;
use IsCachedAsCollection;
@ -51,7 +52,7 @@ class FaqTheme extends Model
throw ValidationException::withMessages([
'error' =>
// phpcs:ignore Generic.Files.LineLength.TooLong
trans("Il n'est pas possible de supprimer ce theme car il est associé a des questions,\u{00A0} suppirmer les questions d'abord.")
trans("Il n'est pas possible de supprimer ce theme car il est associé a des questions,\u{00A0} supprimer les questions avant.")
]);
}
});
@ -66,7 +67,7 @@ class FaqTheme extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function questions(): HasMany
public function questions(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(FaqQuestion::class, 'theme_id');
}

View file

@ -6,6 +6,7 @@ use App\Traits\Models\ActivityLog;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* InstantWin.
@ -20,6 +21,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class InstantWin extends Model
{
use ActivityLog;
use BelongsToTenant;
use IsCachedAsCollection;
/**
@ -45,15 +47,15 @@ class InstantWin extends Model
*/
protected static function booted()
{
static::creating(function () {
static::created(function () {
InstantWin::removeCached();
Prize::removeCached();
});
static::updating(function () {
static::updated(function () {
InstantWin::removeCached();
Prize::removeCached();
});
static::deleting(function () {
static::deleted(function () {
InstantWin::removeCached();
Prize::removeCached();
});

View file

@ -9,8 +9,6 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use IPLib\Address\AddressInterface;
/**
@ -42,7 +40,7 @@ class Participation extends Model
/**
* The attributes that are mass assignable.
*
* @var array<int,string>
* @var array<int, string>
*/
protected $fillable = [
'barcode',
@ -64,7 +62,7 @@ class Participation extends Model
/**
* The model's attributes.
*
* @var array<string,mixed>
* @var array<string, mixed>
*/
protected $attributes = [
'sticker' => null,
@ -86,7 +84,7 @@ class Participation extends Model
static::updating(function (self $model) {
self::manageFields($model);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if (
// ? Les notification doivent être envoyées ?
$settings->notify_prize_sent and
@ -118,7 +116,7 @@ class Participation extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function customer(): BelongsTo
public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Customer::class);
}
@ -128,7 +126,7 @@ class Participation extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function code(): BelongsTo
public function code(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Code::class, 'code_unique_id');
}
@ -138,7 +136,7 @@ class Participation extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function product(): BelongsTo
public function product(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Product::class);
}
@ -148,7 +146,7 @@ class Participation extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function instantWin(): HasOne
public function instantWin(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(InstantWin::class);
}
@ -237,7 +235,7 @@ class Participation extends Model
$model->sticker = FileStorageHelper::storeFile($model, $model->sticker, true);
}
if (!$model->comment) {
$model->comment = '';
$model->comment = null;
}
}
}

View file

@ -10,6 +10,7 @@ use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Validation\ValidationException;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* Prize.
@ -34,6 +35,7 @@ use Illuminate\Validation\ValidationException;
class Prize extends Model
{
use ActivityLog;
use BelongsToTenant;
use IsCachedAsCollection;
/**

View file

@ -7,8 +7,7 @@ use App\Traits\Models\ActivityLog;
use App\Traits\Models\IsCachedAsCollection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* Product.
@ -31,6 +30,7 @@ class Product extends Model
{
use ActivityLog;
use HasFactory;
use BelongsToTenant;
use IsCachedAsCollection;
/**
@ -93,7 +93,7 @@ class Product extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function range(): BelongsTo
public function range(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Range::class);
}
@ -103,7 +103,7 @@ class Product extends Model
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function participations(): HasMany
public function participations(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Participation::class);
}

View file

@ -7,6 +7,7 @@ use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* Range.
@ -26,6 +27,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Range extends Model
{
use ActivityLog;
use BelongsToTenant;
use HasFactory;
/**

View file

@ -13,47 +13,50 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
use LDC\LDCCRM\Enums\Country;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* Settings.
*
* @property string $name The game name
* @property string $company Name of the company that organizes the game
* @property \Illuminate\Support\Carbon $start_date Start date of the game
* @property \Illuminate\Support\Carbon $end_date End date of the game
* @property int $nb_participation_player Max paticipation per player (0 = unlimited)
* @property int $nb_participation_ip Max paticipation per ip (0 = unlimited)
* @property int $nb_unique_code_use Max unique code use (0 = unlimited)
* Dans tous les cas 1 joueur ne peut utiliser
* qu'une seule fois 1 code donné
* @property boolean $notify_prize_sent Should the game send notifications
* when the prize is sent ?
* @property \App\Enums\CodeType $code_type Wich codes must be used.
* @property boolean $activated_teaser Is the teaser active ?
* @property boolean $activated_close_page Is the close page active ?
* @property \Illuminate\Support\Carbon $close_page_active_date End date of the close page
* @property string $redirect_close Link to redirect when the game is ended
* @property boolean $discounts_activated Are the discounts active ?
* @property string $discounts_site Discount MesBr site
* @property string $discounts_campaign Discount MesBr campaign
* @property array<\App\Lib\Discount> $web_coupons Array of web coupons
* @property array<string> $notification_emails Array of email to notify when there's a winnner
* @property array<string> $authorized_countries All countries eligible to participate.
* @property boolean $winning_proof Is the winning proof needed
* @property-read \Illuminate\Support\Carbon $created_at Creation Date.
* @property-read \Illuminate\Support\Carbon $updated_at Last update Date.
* @property string $name The game name
* @property string $company Name of the company that organizes the game
* @property \Illuminate\Support\Carbon $start_date Start date of the game
* @property \Illuminate\Support\Carbon $end_date End date of the game
* @property int $nb_participation_player Max paticipation per player (0 = unlimited)
* @property int $nb_participation_ip Max paticipation per ip (0 = unlimited)
* @property int $nb_unique_code_use Max unique code use (0 = unlimited)
* Dans tous les cas 1 joueur ne peut utiliser
* qu'une seule fois 1 code donné
* @property boolean $notify_prize_sent Should the game send notifications
* when the prize is sent ?
* @property \App\Enums\CodeType $code_type Wich codes must be used.
* @property boolean $activated_teaser Is the teaser active ?
* @property boolean $activated_close_page Is the close page active ?
* @property \Illuminate\Support\Carbon $close_page_active_date End date of the close page
* @property string $redirect_close Link to redirect when the game is ended
* @property boolean $discounts_activated Are the discounts active ?
* @property string $discounts_site Discount MesBr site
* @property string $discounts_campaign Discount MesBr campaign
* @property array<\App\Lib\Discount> $web_coupons Array of web coupons
* @property array<string> $notification_emails Array of email to notify when there's a winnner
* @property array<string> $authorized_countries All countries eligible to participate.
* @property integer $winning_proof Is the winning proof needed
* @property string|null $tenant_id Associated tenant.
*
* @property-read boolean $game_is_open Le jeu est ouvert ?
* @property-read boolean $game_has_begun Le jeu à démarré ?
* @property-read boolean $game_has_ended Le jeu est terminé ?
* @property-read boolean $should_display_teaser_page Doit afficher la page de teaser ?
* @property-read boolean $should_display_end_page Doit afficher la page fin de jeu ?
* @property-read \Illuminate\Support\Carbon $created_at Creation Date.
* @property-read \Illuminate\Support\Carbon $updated_at Last update Date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs One-to-many relationship.
*/
class Settings extends Model
{
use ActivityLog;
use BelongsToTenant;
use IsCachedAsCollection;
/**
@ -112,7 +115,7 @@ class Settings extends Model
*
* @return void
*/
protected static function booted()
protected static function booted(): void
{
static::created(function () {
self::removeCached();
@ -162,7 +165,7 @@ class Settings extends Model
public function checkGameCanBePlayed(): bool
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
if (!(bool) Prize::getCached()->count()) {
report(new \RuntimeException('Need prizes to enable the game'));
return false;
@ -201,10 +204,10 @@ class Settings extends Model
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function gameHasBegun(): Attribute
public function gameHasBegun(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::make(
get: fn (): bool => $this->start_date->lte(now())
get: fn(): bool => \tenant()->setting->start_date->lte(now())
);
}
@ -215,10 +218,10 @@ class Settings extends Model
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function gameHasEnded(): Attribute
public function gameHasEnded(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::make(
get: fn (): bool => $this->game_has_begun and $this->end_date->lte(now())
get: fn(): bool => \tenant()->setting->game_has_begun and \tenant()->setting->end_date->lte(now())
);
}
@ -229,10 +232,10 @@ class Settings extends Model
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function gameIsOpen(): Attribute
public function gameIsOpen(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::make(
get: fn (): bool => $this->game_has_begun and !$this->game_has_ended
get: fn(): bool => $this->game_has_begun and !$this->game_has_ended
);
}
@ -243,10 +246,10 @@ class Settings extends Model
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function shouldDisplayTeaserPage(): Attribute
public function shouldDisplayTeaserPage(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::make(
get: fn (): bool => !$this->game_has_begun and
get: fn(): bool => !$this->game_has_begun and
!$this->game_has_ended and
$this->activated_teaser
);
@ -259,10 +262,10 @@ class Settings extends Model
*
* @return \Illuminate\Database\Eloquent\Casts\Attribute
*/
public function shouldDisplayEndPage(): Attribute
public function shouldDisplayEndPage(): \Illuminate\Database\Eloquent\Casts\Attribute
{
return Attribute::make(
get: fn (): bool => $this->game_has_begun and
get: fn(): bool => $this->game_has_begun and
$this->game_has_ended and
$this->activated_close_page and
$this->close_page_active_date->gte(now())
@ -293,6 +296,36 @@ class Settings extends Model
return $countries;
}
/**
* Set default settings for a specific tenant.
*
* @param \App\Models\Tenant $tenant
* @return void
*/
public function setDefaultSettings(Tenant $tenant): void
{
$settings = new Settings();
$settings->name = 'Nom du jeu';
$settings->company = 'Société';
$settings->start_date = now();
$settings->end_date = now()->addDays(30);
$settings->nb_participation_player = 30;
$settings->nb_participation_ip = 30;
$settings->nb_unique_code_use = 1;
$settings->code_type = CodeType::unique;
$settings->activated_teaser = false;
$settings->activated_close_page = false;
$settings->notify_prize_sent = false;
$settings->redirect_close = "https://example.test";
$settings->discounts_activated = false;
$settings->authorized_countries = [];
$settings->notification_emails = [];
$settings->web_coupons = [];
$settings->winning_proof = 0;
$settings->tenant_id = $tenant->getKey();
$settings->saveOrFail();
}
// * RELATIONS
/**

View file

@ -7,7 +7,9 @@ use App\Lib\Helpers\ToolboxHelper;
use App\Traits\Models\ActivityLog;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Str;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property \App\Enums\StaticPageType $type Page type (StaticPageEnum).
@ -16,6 +18,7 @@ use Illuminate\Support\Str;
* @property string $title Title for the page.
* @property string $slug Slug of the title for the page.
* @property string $content WYSIWYG text content using ckeditor.
* @property string|null $tenant_id Associated tenant.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
*
@ -24,6 +27,7 @@ use Illuminate\Support\Str;
class StaticPage extends Model
{
use ActivityLog;
use BelongsToTenant;
use HasFactory;
/**
@ -32,10 +36,12 @@ class StaticPage extends Model
* @var array<int, string>
*/
protected $fillable = [
'type',
'seo_title',
'seo_description',
'title',
'content',
'tenant_id',
];
/**
@ -58,14 +64,14 @@ class StaticPage extends Model
{
static::creating(function (self $model) {
// * ASSERTIONS
self::assertTitleIsUnique($model->title);
self::assertTitleIsUnique($model);
// * CHANGES
$model->slug = Str::slug($model->title);
});
static::updating(function (self $model) {
// * ASSERTIONS
self::assertTitleIsUnique($model->title, $model->id);
self::assertTitleIsUnique($model);
// * CHANGES
$model->slug = Str::slug($model->title);
@ -77,11 +83,11 @@ class StaticPage extends Model
/**
* Get the value of the model's route key.
*
* @return mixed
* @return string|integer
*/
public function getRouteKey()
public function getRouteKey(): string|int
{
/** @var \App\Enums\StaticPageType */
/** @var \App\Enums\StaticPageType $enum */
$enum = $this->getAttribute($this->getRouteKeyName());
return $enum->value();
}
@ -103,16 +109,55 @@ class StaticPage extends Model
/**
* Asserts using validation that the title is unique
*
* @param string|null $title
* @param integer|null $id
* @param self $model
* @return void
* @throws \Illuminate\Validation\ValidationException If an actu title already exists.
* @throws \Illuminate\Validation\ValidationException If a static page title already exists.
*/
private static function assertTitleIsUnique(string|null $title, ?int $id = null)
private static function assertTitleIsUnique(self $model): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id);
ToolboxHelper::assertFieldIsUnique(
$table,
'title',
$model->title,
$model->getKey(),
function (Builder $query) use ($model) {
$query->where('tenant_id', $model->tenant->getKey());
}
);
ToolboxHelper::assertFieldIsUnique(
$table,
'slug',
Str::slug($model->title),
$model->getKey(),
function (Builder $query) use ($model) {
$query->where('tenant_id', $model->tenant->getKey());
}
);
}
/**
* Set default settings for a specific tenant.
*
* @param \App\Models\Tenant $tenant
* @return void
*/
public function setDefaultPages(Tenant $tenant): void
{
collect([StaticPageType::confidentiality, StaticPageType::legals, StaticPageType::rules])
->map(function (StaticPageType $enum) use ($tenant) {
$staticPageModel = new StaticPage();
$staticPageModel->seo_title = sprintf('Seo title pour la page de %s', $enum->label());
$staticPageModel->seo_description = sprintf(
'Seo description pour la page de %s',
$enum->label()
);
$staticPageModel->title = sprintf('Page de %s', $enum->label());
$staticPageModel->type = $enum;
$staticPageModel->content = 'Ceci est le contenu de la page.';
$staticPageModel->tenant_id = $tenant->getkey();
$staticPageModel->saveOrFail();
});
}
// * RELATIONS

128
app/Models/Tenant.php Normal file
View file

@ -0,0 +1,128 @@
<?php
namespace App\Models;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Stancl\Tenancy\Contracts\Tenant as TenantContract;
use Stancl\Tenancy\Database\Concerns\HasDomains;
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
/**
* Settings.
*
* @property string $id Tenant id.
* @property string $theme Tenant theme.
* @property-read \Illuminate\Support\Carbon $created_at Creation Date.
* @property-read \Illuminate\Support\Carbon $updated_at Last update Date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs One-to-many relationship.
*/
class Tenant extends BaseTenant implements TenantContract
{
use HasDomains;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = ['id', 'theme'];
/**
* Define the custom columns (that won't be stored in the data JSON column)
* by overriding the getCustomColumns() method.
*
* @see https://tenancyforlaravel.com/docs/v3/tenants
*
* @return array
*/
public static function getCustomColumns(): array
{
return [
'id',
'theme',
];
}
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
self::assertThemeIsUnique($model->theme);
$model->id = Str::slug($model->theme);
});
static::created(function (self $model) {
(new Settings())->setDefaultSettings($model);
(new StaticPage())->setDefaultPages($model);
self::resetCache();
});
static::updating(function (self $model) {
self::assertThemeIsUnique($model->theme, $model->getKey());
$model->id = Str::slug($model->theme);
});
static::updated(function () {
self::resetCache();
});
static::deleted(function () {
self::resetCache();
});
}
// * METHODS
/**
* Asserts using validation that the theme is unique
*
* @param string|null $theme
* @param string|null $id
* @return void
* @throws \Illuminate\Validation\ValidationException If theme already exists.
*/
private static function assertThemeIsUnique(string|null $theme, ?string $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'theme', $theme, $id);
ToolboxHelper::assertFieldIsUnique($table, 'id', Str::slug($theme), $id);
}
/**
* Reset all cache elements related to this model.
*
* @return void
*/
private static function resetCache(): void
{
Cache::forget('tenantsGlobal');
self::setTenantsCached();
}
/**
* Set all tenants in cache.
*
* @return \Illuminate\Support\Collection
*/
public static function setTenantsCached(): \Illuminate\Support\Collection
{
return Cache::remember('tenantsGlobal', 86400, function () {
return Tenant::get();
});
}
// * RELATIONS
/**
* Has One Relationship Method.
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function setting(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(Settings::class);
}
}

View file

@ -8,20 +8,20 @@ use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
/**
* @property string $first_name Prénom.
* @property string $last_name Nom.
* @property string $email Email.
* @property string $password Mot de passe.
* @property \App\Enums\Users\RoleEnum $role Rôle.
* @property \Illuminate\Support\Carbon $published État de publication.
* @property \Illuminate\Support\Carbon $published_at Date de mise à jour de l'état de publication.
* @property-read \Illuminate\Support\Carbon $created_at Created date.
* @property-read \Illuminate\Support\Carbon $updated_at Updated date.
* @property string $first_name User firstname.
* @property string $last_name User lastname.
* @property string $email User email.
* @property string $remember_token Remember_token.
* @property \App\Enums\Users\RoleEnum $role User role.
* @property \Illuminate\Support\Carbon $last_connection User last_connection.
* @property boolean $published User published status.
* @property \Illuminate\Support\Carbon $published_at User publication status update date.
* @property-read \Illuminate\Support\Carbon $created_at User created date.
* @property-read \Illuminate\Support\Carbon $updated_at User updated date.
*
* @property-read \App\Models\ActivityLog $activityLogs Activities logs One-to-many relationship.
* @property-read \App\Models\ActivityLog $activityLogs Activities logs HasMany relationship.
*/
class User extends Authenticatable
{
@ -38,7 +38,6 @@ class User extends Authenticatable
'first_name',
'last_name',
'email',
'password',
'role',
'published',
'published_at',
@ -50,7 +49,6 @@ class User extends Authenticatable
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
@ -61,12 +59,11 @@ class User extends Authenticatable
*/
protected $casts = [
'role' => RoleEnum::class,
'email_verified_at' => 'datetime',
'last_connection' => 'date',
'published' => 'boolean',
'published_at' => 'datetime',
];
// * EVENTS
/**
* Perform any actions required after the model boots.
*
@ -75,54 +72,56 @@ class User extends Authenticatable
protected static function booted(): void
{
static::creating(function (self $model) {
self::updatePassword($model);
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
AuthorizationException::class
);
self::checkElevationPrivileges($model);
});
static::updating(function (self $model) {
self::updatePassword($model);
if (optional(auth('backend')->user())->getKey() === $model->getKey()) {
\validator(
['published' => $model->published],
['published' => 'required|boolean|accepted'],
['published.accepted' => trans('Vous ne pouvez pas déactiver votre propre compte')],
)->validate();
}
// * Prevent user elevation privileges
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $model->role->value(),
AuthorizationException::class
);
self::updatePublishedStatus($model);
self::checkElevationPrivileges($model);
});
}
// * METHODS
/**
* Update password.
* Check if the authenticable user can update the published status.
*
* @param self $model
* @param self $user
* @return void
*/
private static function updatePassword(self $model): void
private static function updatePublishedStatus(self $user): void
{
if (Hash::needsRehash($model->password)) {
$model->password = Hash::make($model->password);
if (optional(auth('backend')->user())->getKey() === $user->getKey()) {
\validator(
['published' => $user->published],
['published' => 'required|boolean|accepted'],
['published.accepted' => trans('Vous ne pouvez pas désactiver votre propre compte')],
)->validate();
}
}
/**
* Prevent user elevation privileges.
*
* @param self $user
* @return void
*/
private static function checkElevationPrivileges(self $user): void
{
throw_if(
auth('backend')->user() and auth('backend')->user()->role->value() > $user->role->value(),
AuthorizationException::class
);
}
// * RELATIONS
/**
* Activities logs One-to-many relationship.
* The tenants that belong to the user.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function activityLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
public function tenants(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->hasMany(ActivityLog::class);
return $this->belongsToMany(Tenant::class);
}
}

View file

@ -2,7 +2,6 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
@ -30,7 +29,7 @@ class DestroyCustomer extends Notification
public function toMail(object $notifiable): MailMessage
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
/** @var string $first_name */

View file

@ -2,7 +2,6 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
@ -32,7 +31,7 @@ class DestroyedCustomer extends Notification
{
// phpcs:enable
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */

View file

@ -17,12 +17,16 @@ class HasSubmitedWinProof extends Notification implements ShouldQueue
/**
* Create a new notification instance.
*
* @param string $first_name The client first name.
* @param string $first_name The client first name.
* @param \App\Models\Settings $settingsModel The settings name.
* @return void
*/
public function __construct(
protected string $first_name
) {}
protected string $first_name,
protected Settings $settingsModel,
) {
// Keep empty.
}
/**
* Get the notification's delivery channels.
@ -45,16 +49,13 @@ class HasSubmitedWinProof extends Notification implements ShouldQueue
*/
public function toMail(object $notifiable): Mailable
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var \App\Models\Customer $notifiable */
return (new WinningProofSubmitedMail(
new Address(
$notifiable->email,
$this->first_name,
),
$settings->name,
$settings->company,
$this->settingsModel,
));
}
}

View file

@ -4,7 +4,6 @@ namespace App\Notifications\Customer;
use App\Lib\Discount;
use App\Mail\Won\DiscountWonMail;
use App\Models\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -18,14 +17,16 @@ class HasWonDiscount extends Notification implements ShouldQueue
/**
* Create a new notification instance.
*
* @param Discount $discount The discount as set in Settings.
* @param string $first_name The client first name.
* @param \App\Lib\Discount $discount The discount as set in Settings.
* @param string $first_name The client first name.
* @return void
*/
public function __construct(
protected Discount $discount,
protected string $first_name
) {}
) {
// Keep empty.
}
/**
* Get the notification's delivery channels.
@ -49,7 +50,7 @@ class HasWonDiscount extends Notification implements ShouldQueue
public function toMail(object $notifiable): Mailable
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
return (new DiscountWonMail(
new Address(

View file

@ -4,7 +4,6 @@ namespace App\Notifications\Customer;
use App\Mail\Won\PrizeWonMail;
use App\Models\Prize;
use App\Models\Settings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -48,8 +47,10 @@ class HasWonInstantWin extends Notification implements ShouldQueue
*/
public function toMail(object $notifiable): Mailable
{
/** @var \App\Models\Tenant $tenantModel */
$tenantModel = $this->prize->tenant;
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = $tenantModel->setting;
/** @var \App\Models\Customer $notifiable */
return (new PrizeWonMail(
new Address(

View file

@ -3,7 +3,6 @@
namespace App\Notifications\Customer;
use App\Mail\Won\PrizeIsSentMail;
use App\Models\Settings;
use Illuminate\Contracts\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Notifications\Notification;
@ -32,7 +31,7 @@ class PrizeHasBeenSent extends Notification
public function toMail(object $notifiable): Mailable
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
return (new PrizeIsSentMail(
new Address(

View file

@ -2,7 +2,6 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Auth\Notifications\ResetPassword as NotificationsResetPassword;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\URL;
@ -49,7 +48,7 @@ class ResetPassword extends NotificationsResetPassword
{
// phpcs:enable
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
$resetUrl = $this->resetUrl($notifiable);

View file

@ -2,7 +2,6 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
@ -30,7 +29,7 @@ class UpdateEmail extends Notification
public function toMail(object $notifiable): MailMessage
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
/** @var string $first_name */

View file

@ -2,7 +2,6 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
@ -30,7 +29,7 @@ class UpdatePassword extends Notification
public function toMail(object $notifiable): MailMessage
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
/** @var string $first_name */

View file

@ -2,7 +2,6 @@
namespace App\Notifications\Customer;
use App\Models\Settings;
use Illuminate\Auth\Notifications\VerifyEmail as NotificationsVerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Carbon;
@ -49,7 +48,7 @@ class VerifyEmail extends NotificationsVerifyEmail
{
// phpcs:enable
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
/** @var \App\Models\Customer $notifiable */
/** @var string $verificationUrl */

View file

@ -2,8 +2,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\BannedIp;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -17,21 +15,18 @@ class BannedIpPolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\BannedIp $banned_ip
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function view(User $authUser, BannedIp $banned_ip): bool
public function view(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
@ -42,34 +37,28 @@ class BannedIpPolicy
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\BannedIp $banned_ip
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, BannedIp $banned_ip): bool
public function update(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\BannedIp $banned_ip
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, BannedIp $banned_ip): bool
public function delete(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -2,7 +2,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -16,7 +15,7 @@ class CodePolicy
*/
public function generate(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
@ -27,7 +26,7 @@ class CodePolicy
*/
public function export(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
@ -38,6 +37,6 @@ class CodePolicy
*/
public function search(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -3,7 +3,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\FaqQuestion;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -17,21 +16,18 @@ class FaqQuestionPolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\FaqQuestion $faq_question
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function view(User $authUser, FaqQuestion $faq_question): bool
public function view(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
@ -42,34 +38,28 @@ class FaqQuestionPolicy
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\FaqQuestion $faq_question
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, FaqQuestion $faq_question): bool
public function update(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\FaqQuestion $faq_question
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, FaqQuestion $faq_question): bool
public function delete(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
}

View file

@ -3,7 +3,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\FaqTheme;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -17,21 +16,18 @@ class FaqThemePolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\FaqTheme $faq_theme
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function view(User $authUser, FaqTheme $faq_theme): bool
public function view(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
@ -42,34 +38,28 @@ class FaqThemePolicy
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\FaqTheme $faq_theme
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, FaqTheme $faq_theme): bool
public function update(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\FaqTheme $faq_theme
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, FaqTheme $faq_theme): bool
public function delete(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
}

View file

@ -2,7 +2,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\InstantWin;
use App\Models\Prize;
use App\Models\User;
@ -18,7 +17,7 @@ class InstantWinPolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
@ -29,7 +28,11 @@ class InstantWinPolicy
*/
public function generate(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin) and
((Prize::getCached()->count() and !InstantWin::count()) ? true : false);
$instantWinCount = InstantWin::query()->with('prize')->whereHas('prize', function ($query) {
$query->where('tenant_id', tenant('id'));
})->count();
$prizeCached = Prize::getCached()->count();
return UserStaticRules::restrictTenant($authUser) and
(($prizeCached and !$instantWinCount) ? true : false);
}
}

View file

@ -6,7 +6,6 @@ use App\Enums\Users\RoleEnum;
use App\Models\BannedIp;
use App\Models\Customer;
use App\Models\Participation;
use App\Models\Settings;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
use Illuminate\Database\Eloquent\Builder;
@ -21,7 +20,7 @@ class ParticipationPolicy
*/
public function viewWinnerList(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
@ -33,7 +32,7 @@ class ParticipationPolicy
public function participate(Customer $authUser): bool
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
// * Participation par compte
$playerCanParticipate = true;
@ -41,7 +40,7 @@ class ParticipationPolicy
if ($settings->nb_participation_player) {
$playerCanParticipateAccount = (Participation::query()->whereHas(
'customer',
fn (Builder $q) => $q->where('customer_id', $authUser->id)
fn(Builder $q) => $q->where('customer_id', $authUser->id)
)->count() < $settings->nb_participation_player);
}
// * Participation par IP
@ -49,7 +48,7 @@ class ParticipationPolicy
if ($settings->nb_participation_ip) {
$playerCanParticipateIp = (Participation::query()->whereHas(
'customer',
fn (Builder $q) => $q->where('ip', app('clientIp'))
fn(Builder $q) => $q->where('ip', app('clientIp'))
)->count() < $settings->nb_participation_ip);
}
$playerCanParticipate = (!empty($playerCanParticipateAccount) && !empty($playerCanParticipateIp)) ?
@ -89,12 +88,12 @@ class ParticipationPolicy
public function submitProof(Customer $authUser, Participation $model): bool
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
$settings = \tenant()->setting;
return $settings->winning_proof and Participation::query()
->where('prize_sent', false)
->whereHas(
'customer',
fn (Builder $q) =>
fn(Builder $q) =>
$q->where('id', $authUser->id)
)->where('id', $model->id)->whereHas('instantWin')
->where(function (Builder $q) {
@ -108,15 +107,12 @@ class ParticipationPolicy
/**
* Can a user change comment ?
*
* @param \App\Models\User $authUser
* @param \App\Models\Participation $model
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function setComment(User $authUser, Participation $model): bool
public function setComment(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
@ -128,6 +124,6 @@ class ParticipationPolicy
*/
public function changePrizeSent(User $authUser, Participation $model): bool
{
return !$model->prize_sent and UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return !$model->prize_sent and UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
}

View file

@ -2,9 +2,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\Prize;
use App\Models\Settings;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -18,21 +15,18 @@ class PrizePolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Prize $prize
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function view(User $authUser, Prize $prize): bool
public function view(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
@ -44,42 +38,33 @@ class PrizePolicy
public function create(User $authUser): bool
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
return !$settings->game_is_open and
UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
$settings = \tenant()->setting;
return !$settings->game_is_open and UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Prize $prize
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, Prize $prize): bool
public function update(User $authUser): bool
{
// phpcs:enable
/** @var \App\Models\Settings */
$settings = app(Settings::class);
return !$settings->game_is_open and
UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
$settings = \tenant()->setting;
return !$settings->game_is_open and UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Prize $prize
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, Prize $prize): bool
public function delete(User $authUser): bool
{
// phpcs:enable
/** @var \App\Models\Settings */
$settings = app(Settings::class);
return !$settings->game_is_open and
UserStaticRules::atLeastRole($authUser, RoleEnum::admin);
$settings = \tenant()->setting;
return !$settings->game_is_open and UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -3,7 +3,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\Product;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -17,21 +16,18 @@ class ProductPolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Product $product
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function view(User $authUser, Product $product): bool
public function view(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
@ -42,34 +38,28 @@ class ProductPolicy
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Product $product
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, Product $product): bool
public function update(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Product $product
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, Product $product): bool
public function delete(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
}

View file

@ -3,7 +3,6 @@
namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\Range;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -17,21 +16,19 @@ class RangePolicy
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Range $range
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function view(User $authUser, Range $range): bool
public function view(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
@ -42,34 +39,28 @@ class RangePolicy
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Range $range
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, Range $range): bool
public function update(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Range $range
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, Range $range): bool
public function delete(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::marketing);
return UserStaticRules::restrictTenant($authUser, RoleEnum::marketing);
}
}

View file

@ -44,4 +44,18 @@ abstract class UserStaticRules
// phpcs:enable
return $user->role->value() <= $minRole->value();
}
/**
* Allow client users associated to the tenant OR admin users.
*
* @param \App\Models\User $authUser
* @param \App\Enums\Users\RoleEnum $minRole
* @return boolean
*/
public static function restrictTenant(User $authUser, RoleEnum $minRole = RoleEnum::admin): bool
{
return UserStaticRules::atLeastRole($authUser, $minRole) &&
\in_array(\tenant()->getKey(), $authUser->tenants->pluck('id')->toArray()) ||
UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
}

View file

@ -4,7 +4,6 @@ namespace App\Policies;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use App\Models\Settings;
use App\Policies\Rules\UserStaticRules;
class SettingsPolicy
@ -20,17 +19,25 @@ class SettingsPolicy
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Settings $settings
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function update(User $authUser, Settings $settings): bool
public function update(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, RoleEnum::conceptor);
}
}

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