fix: 🚨 resolve linters errors

This commit is contained in:
Alexis Gousseau 2026-10-02 12:09:46 +02:00
parent e5380fcacf
commit dafef6e5d5
No known key found for this signature in database
GPG key ID: 7F3C786EE12DBB72
279 changed files with 7808 additions and 5077 deletions

26
.gitignore vendored
View file

@ -31,16 +31,42 @@ resources/theme
.npm/
.composer/
.zshrc
.rector-cache/
# Ignore public favicon
/public/android-chrome-192x192.png
/public/android-chrome-512x512.png
/public/android-icon-144x144.png
/public/android-icon-192x192.png
/public/android-icon-36x36.png
/public/android-icon-48x48.png
/public/android-icon-72x72.png
/public/android-icon-96x96.png
/public/apple-chrome-192x192.png
/public/apple-chrome-512x512.png
/public/apple-icon-114x114.png
/public/apple-icon-120x120.png
/public/apple-icon-144x144.png
/public/apple-icon-152x152.png
/public/apple-icon-180x180.png
/public/apple-icon-57x57.png
/public/apple-icon-60x60.png
/public/apple-icon-72x72.png
/public/apple-icon-76x76.png
/public/apple-icon-precomposed.png
/public/apple-icon.png
/public/apple-touch-icon.png
/public/browserconfig.xml
/public/browserconfig.xml
/public/favicon-16x16.png
/public/favicon-32x32.png
/public/favicon-96x96.png
/public/favicon.ico
/public/manifest.json
/public/ms-icon-144x144.png
/public/ms-icon-150x150.png
/public/ms-icon-310x310.png
/public/ms-icon-70x70.png
/public/mstile-150x150.png
/public/safari-pinned-tab.svg
/public/site.webmanifest

View file

@ -1,19 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Auth;
use App\Models\Customer;
use ErrorException;
use Exception;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider as UserProviderContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use LDC\LDCCRM\CrmApiV4;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use RuntimeException;
use SensitiveParameter;
use Throwable;
/**
* Crm user provider to integrate nicely with Laravel
@ -22,32 +30,28 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
{
/**
* The model to use, specified in auth config guard.
*
* @var string
*/
private $usedModel = Customer::class;
private string $usedModel = Customer::class;
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// * Let laravel find a model
$model = parent::retrieveByCredentials($credentials) ??
self::retrieveTrashed($credentials);
$model = parent::retrieveByCredentials($credentials) ?? self::retrieveTrashed($credentials);
try {
/** @var \LDC\LDCCRM\CrmApiV4 */
$crm = app('crmAPI');
/** @var CrmApiV4 */
$crm = resolve('crmAPI');
// Si pas de compte local.
if (!$model) {
if (! $model) {
$crmCustomer = $crm->getCustomer($credentials['email']);
if (!$crmCustomer) {
if (! $crmCustomer) {
// * Customer is not found on CRM.
return null;
}
@ -62,40 +66,38 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
}
report($e);
Log::error('Unhandled response ' . $e->getApiError()->label());
} catch (CrmException | \Exception $e) {
} catch (CrmException | Exception $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return null;
} //end try
Log::error(__CLASS__ . '::retrieveByCredentials reached end of possibilities.');
} // end try
Log::error(self::class . '::retrieveByCredentials reached end of possibilities.');
return null;
}
/**
* Validate a user against the given credentials.
*
* @param UserContract $model
* @param array $credentials
* @return boolean|null
* @throws \RuntimeException If model provided is not a Customer or connection to server fails.
* @throws \LDC\LDCCRM\Exceptions\CrmException On CRM call error.
* @throws \Exception On CRM call error.
* @throws RuntimeException If model provided is not a Customer or connection to server fails.
* @throws CrmException On CRM call error.
* @throws Exception On CRM call error.
*/
public function validateCredentials(UserContract $model, array $credentials): bool|null
public function validateCredentials(UserContract $model, array $credentials): ?bool
{
/** @var \App\Models\Customer $model */
if (\get_class($model) !== $this->usedModel) {
throw new \RuntimeException('Cannot authenticate any thing else than ' . $this->usedModel);
/** @var Customer $model */
if ($model::class !== $this->usedModel) {
throw new RuntimeException('Cannot authenticate any thing else than ' . $this->usedModel);
}
try {
/** @var \LDC\LDCCRM\CrmApiV4 */
$crm = app('crmAPI');
/** @var CrmApiV4 */
$crm = resolve('crmAPI');
// * If has trait soft delete and customer trashed, then restore customer.
if (
\in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, \class_uses_recursive($model)) and
$model->trashed()
\in_array(SoftDeletes::class, \class_uses_recursive($model)) && $model->trashed()
) {
$model->restore();
}
@ -104,10 +106,10 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
return $crm->login($credentials['email'], $credentials['password']);
} catch (ApiErrorException $e) {
if (
$e->getApiError()->equals(ApiError::modelNotFound) ||
($e->getApiError()->equals(ApiError::authFailed) && !$model->existsInCrm())
$e->getApiError()->equals(ApiError::modelNotFound) || ($e->getApiError()->equals(ApiError::authFailed) && ! $model->existsInCrm())
) {
$model->delete();
return false;
}
if ($e->getApiError()->equals(ApiError::authFailed)) {
@ -119,31 +121,27 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
$model->save();
$model->sendEmailVerificationNotification();
Session::flash('warning', \trans('front.messages.account_not_activated'));
return true;
}
} catch (CrmException | \Exception $e) {
} catch (CrmException | Exception $e) {
report($e);
if (config('app.debug')) {
throw $e;
}
return false;
} //end try
throw_if(config('app.debug'), $e);
return false;
} // end try
Log::error(self::class . '::validateCredentials reached end of possibilities.');
Log::error(__CLASS__ . '::validateCredentials reached end of possibilities.');
return null;
}
/**
* CRM customer passwordless (Laravel 11 introduces password rehashing after login).
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @param boolean $force
* @return void
*/
public function rehashPasswordIfRequired(
UserContract $user,
#[\SensitiveParameter] array $credentials,
#[SensitiveParameter] array $credentials,
bool $force = false
): void {
//
@ -152,15 +150,13 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
/**
* Create a local customer.
*
* @param \LDC\LDCCRM\Models\Customer $crmCustomer
* @return \Illuminate\Database\Eloquent\Model
* @throws \Throwable If saving to DB failed.
* @throws Throwable If saving to DB failed.
*/
private function createLocalCustomer(\LDC\LDCCRM\Models\Customer $crmCustomer): Model
{
/** @var \App\Models\Customer $model */
/** @var Customer $model */
$model = $this->usedModel::withTrashed()->firstOrNew([
'email' => $crmCustomer->email
'email' => $crmCustomer->email,
]);
$model->hashcode = $crmCustomer->hashcode;
$model->email_verified_at = $crmCustomer->status ? now() : null;
@ -175,14 +171,12 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
/**
* Retrieve a trashed customer by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
* @throws \ErrorException If cannot connect to LDAP.
* @throws ErrorException If cannot connect to LDAP.
*/
private function retrieveTrashed(array $credentials): ?Authenticatable
private function retrieveTrashed(array $credentials): ?UserContract
{
// * Make changes
if (empty($credentials)) {
if ($credentials === []) {
return null;
}
@ -192,12 +186,12 @@ class CrmUserProvider extends EloquentUserProvider implements UserProviderContra
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (!Str::contains($key, 'password')) {
if (! Str::contains($key, 'password')) {
$query->where($key, $value);
}
}
/** @var \App\Models\Customer|null $query */
/** @var Customer|null $query */
return $query->withTrashed()->first();
}
}

View file

@ -1,26 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Casts;
use App\Lib\Discount;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AsArrayOfDiscount implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return array<\App\Lib\Discount>
* @param Model $model
* @param mixed $value
* @return array<Discount>
*
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function get($model, string $key, $value, array $attributes): array
{
// phpcs:enable
return \collect(\json_decode($value, true))->map(function ($input) use ($model, $key, $attributes) {
return \collect(\json_decode($value, true))->map(function ($input) use ($model, $key, $attributes): Discount {
$inputCast = new AsDiscount();
return $inputCast->get($model, $key, \json_encode($input), $attributes);
})->all();
}
@ -28,11 +32,10 @@ class AsArrayOfDiscount implements CastsAttributes
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param \App\Lib\Discount $value
* @param array $attributes
* @param Model $model
* @param Discount $value
* @return string Json encoded array<\App\Lib\Discount>
*
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*/
public function set($model, string $key, $value, array $attributes)
@ -40,6 +43,7 @@ class AsArrayOfDiscount implements CastsAttributes
// phpcs:enable
return \json_encode(\collect($value)->map(function ($input) use ($model, $key, $attributes) {
$inputCast = new AsDiscount();
return \json_decode($inputCast->set($model, $key, $input, $attributes));
})->all());
}

View file

@ -1,64 +1,69 @@
<?php
declare(strict_types=1);
namespace App\Casts;
use App\Lib\Discount;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use RuntimeException;
class AsDiscount implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return \App\Lib\Discount
* @param Model $model
* @param mixed $value
*
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/
public function get($model, string $key, $value, array $attributes): Discount
{
// phpcs:enable
$inputData = \json_decode($value, true);
return Discount::make(...\array_values($inputData));
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @param Model $model
* @param mixed $value
* @return string Json encoded \App\Lib\Inputs\Definition\Input
*
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/
public function set($model, string $key, $value, array $attributes)
{
// phpcs:enable
$this->assertIsInput($value);
/** @var \App\Lib\Discount $value */
/** @var Discount $value */
return $value->toJson();
}
/**
* Assert is instance of \App\Lib\Discount
*
* @param mixed $value
* @return void
* @throws \RuntimeException If the value is not an input.
* @throws RuntimeException If the value is not an input.
*/
private function assertIsInput(mixed $value): void
{
if (!\is_object($value) or !($value instanceof Discount)) {
if (! \is_object($value) || ! ($value instanceof Discount)) {
$value = \json_encode($value);
throw new \RuntimeException(
__CLASS__ . ' needs value to be array of \'' . Discount::class . "' given is {$value}"
throw new RuntimeException(
self::class . ' needs value to be array of \'' . Discount::class . "' given is {$value}"
);
}
}

View file

@ -1,9 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use IPLib\Address\AddressInterface;
use IPLib\Factory;
use RuntimeException;
/**
* @template TGet
@ -14,30 +19,30 @@ class Ip implements CastsAttributes
/**
* Transform the attribute from the underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array<string,mixed> $attributes
* @return \IPLib\Address\AddressInterface|null
* @param array<string,mixed> $attributes
* @return AddressInterface|null
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*/
public function get(Model $model, string $key, mixed $value, array $attributes)
{
// phpcs:enable
return \IPLib\Factory::parseAddressString($value);
return Factory::parseAddressString($value);
}
/**
* Transform the attribute to its underlying model values.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param \IPLib\Address\AddressInterface|string|null $value
* @param array<string,mixed> $attributes
* @param AddressInterface|string|null $value
* @param array<string,mixed> $attributes
* @return mixed
* @throws \RuntimeException If value is not or instance of \IPLib\Address\AddressInterface.
*
* @throws RuntimeException If value is not or instance of \IPLib\Address\AddressInterface.
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function set(Model $model, string $key, mixed $value, array $attributes)
@ -47,15 +52,12 @@ class Ip implements CastsAttributes
return null;
}
if (is_string($value)) {
$value = \IPLib\Factory::parseAddressString($value);
}
if (!is_object($value) or !($value instanceof \IPLib\Address\AddressInterface)) {
throw new \RuntimeException(
"{$key} has to be instance of " .
\IPLib\Address\AddressInterface::class
);
$value = Factory::parseAddressString($value);
}
throw_if(! is_object($value) || ! ($value instanceof AddressInterface), RuntimeException::class, "{$key} has to be instance of " .
AddressInterface::class);
$value->getAddressType();
return $value;
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use Illuminate\Console\Command;
@ -12,6 +14,7 @@ class TestMail extends Command
*
* @var string
*/
#[\Override]
protected $signature = 'uwil:mail:test';
/**
@ -19,16 +22,15 @@ class TestMail extends Command
*
* @var string
*/
#[\Override]
protected $description = 'Send test email to equipe.web@ldc.fr';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
public function handle(): void
{
Mail::raw(sprintf('UWIL mail-test from %s %s', \config('app.name'), \config('app.url')), function ($msg) {
Mail::raw(sprintf('UWIL mail-test from %s %s', \config('app.name'), \config('app.url')), function ($msg): void {
$msg->to('equipe.web@ldc.fr')->subject('Test Email');
});
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands\Theme;
use App\Lib\Helpers\ToolboxHelper;
@ -18,6 +20,7 @@ class SwitchCommand extends Command implements PromptsForMissingInput
*
* @var string
*/
#[\Override]
protected $signature = 'uwil:theme:switch {theme : The theme folder name on theme-merge project}';
/**
@ -25,6 +28,7 @@ class SwitchCommand extends Command implements PromptsForMissingInput
*
* @var string
*/
#[\Override]
protected $description = 'Switch GGW theme and install it';
/**
@ -35,22 +39,20 @@ class SwitchCommand extends Command implements PromptsForMissingInput
protected function promptForMissingArgumentsUsing()
{
return [
'theme' => fn () => search(
'theme' => fn (): int | string => 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))
->map(fn (string $dir_path): string => \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()
)
->filter(fn (string $dir_path): bool => $dir_path !== 'mounted')
->all(),
placeholder: 'default-theme'
),
];
}
/**
* Execute the console command.
*
* @return void
*/
public function handle(): void
{
@ -65,7 +67,7 @@ class SwitchCommand extends Command implements PromptsForMissingInput
"{$mount_path} is not a link, cannot remove it"
);
\throw_if(
!File::delete($mount_path),
! File::delete($mount_path),
"{$mount_path} Failed to remove file"
);
}

View file

@ -1,11 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Enums\Users\RoleEnum;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
@ -16,13 +18,15 @@ class UserCreate extends Command
*
* @var string
*/
#[\Override]
protected $signature = 'uwil:user:create';
/**
* The console command description.
*
* @var string|null
* @var string
*/
#[\Override]
protected $description = 'Create a backend admin user';
/**
@ -62,8 +66,6 @@ class UserCreate extends Command
/**
* Execute the console command.
*
* @return integer
*/
public function handle(): int
{
@ -83,13 +85,13 @@ class UserCreate extends Command
$this->addRole();
$user = new User([
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'password' => $this->password,
'role' => $this->role,
'published' => true,
'published_at' => Carbon::now(),
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'password' => $this->password,
'role' => $this->role,
'published' => true,
'published_at' => Date::now(),
]);
$user->saveOrFail();
@ -100,8 +102,6 @@ class UserCreate extends Command
/**
* Add a first name to the user.
*
* @return void
*/
private function addFirstName(): void
{
@ -109,7 +109,7 @@ class UserCreate extends Command
try {
$tmp = $this->ask('Type the wanted user first name', 'Admin');
$validator = Validator::make(['first_name' => $tmp], [
'first_name' => 'required|min:3|max:255'
'first_name' => 'required|min:3|max:255',
]);
$validator->validate();
$this->first_name = $tmp;
@ -118,15 +118,14 @@ class UserCreate extends Command
'Please provide a valid first name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
} // end try
} // end while
}
/**
* Add a last name to the user.
*
* @return void
*/
private function addLastName(): void
{
@ -134,7 +133,7 @@ class UserCreate extends Command
try {
$tmp = $this->ask('Type the wanted user last name', 'Saïtama');
$validator = Validator::make(['last_name' => $tmp], [
'last_name' => 'required|min:3|max:255'
'last_name' => 'required|min:3|max:255',
]);
$validator->validate();
$this->last_name = $tmp;
@ -143,15 +142,14 @@ class UserCreate extends Command
'Please provide a valid last_name %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
} // end try
} // end while
}
/**
* Add an email to the user.
*
* @return void
*/
private function addEmail(): void
{
@ -159,7 +157,7 @@ class UserCreate extends Command
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
'email' => 'required|unique:users,email|email:rfc,dns',
]);
$validator->validate();
$this->email = $tmp;
@ -168,31 +166,26 @@ class UserCreate extends Command
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
} // end try
} // end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
do {
$prefix = $this->password !== null ? 'Failed to confirm, ' : '';
$this->password = $this->secret($prefix . 'Type the wanted user password');
$confirmation = $this->secret('Confirm the password');
} while ($this->password !== $confirmation);
}
/**
* Add a role to the user.
*
* @return void
*/
private function addRole(): void
{
@ -215,7 +208,7 @@ class UserCreate extends Command
$tmp = RoleEnum::marketing;
}
$validator = Validator::make(['role' => $tmp], [
'role' => 'required'
'role' => 'required',
]);
$validator->validate();
$this->role = $tmp;
@ -224,8 +217,9 @@ class UserCreate extends Command
'Please choose from the selection %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
} //end try
}; //end while
} // end try
} // end while
}
}

View file

@ -1,8 +1,9 @@
<?php
declare(strict_types=1);
namespace App\Console;
use App\Jobs\DeleteOlds\DeleteOldCustomers;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -13,12 +14,12 @@ class Kernel extends ConsoleKernel
*
* @var array
*/
#[\Override]
protected $commands = [];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums\ActivityLogs;
use App\Traits\Enums\BaseEnum;
@ -25,7 +27,6 @@ enum ActivityLogsEventEnum: int
self::deleted->name => 'Suppression',
];
/**
* Custom added classes definition
*/
@ -38,8 +39,6 @@ enum ActivityLogsEventEnum: int
/**
* Get Class
*
* @return string
*/
public function bootstrapClass(): string
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums;
use App\Traits\Enums\BaseEnum;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums;
use App\Traits\Enums\BaseEnum;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums\Pagination;
use App\Traits\Enums\BaseEnum;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums;
use App\Traits\Enums\BaseEnum;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums\Theme;
use App\Traits\Enums\BaseEnum;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Enums\Users;
use App\Traits\Enums\BaseEnum;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
@ -10,15 +12,17 @@ class Handler extends ExceptionHandler
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
* @var array<class-string<Throwable>, \Psr\Log\LogLevel::*>
*/
#[\Override]
protected $levels = [];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
* @var array<int, class-string<Throwable>>
*/
#[\Override]
protected $dontReport = [];
/**
@ -26,6 +30,7 @@ class Handler extends ExceptionHandler
*
* @var array<int, string>
*/
#[\Override]
protected $dontFlash = [
'current_password',
'password',
@ -34,14 +39,12 @@ class Handler extends ExceptionHandler
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
public function register(): void
{
$this->reportable(function (Throwable $e) {
$this->reportable(function (Throwable $e): void {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
resolve('sentry')->captureException($e);
}
});
}

View file

@ -1,11 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class ActivityLogController extends Controller
@ -15,14 +19,12 @@ class ActivityLogController extends Controller
/**
* 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
public function index(Request $request): View
{
$this->authorize('isConceptor');
/** @var \Illuminate\Database\Eloquent\Builder $query */
/** @var Builder $query */
$query = ActivityLog::query()->with('user');
/** @var string $search Search field */
@ -45,25 +47,32 @@ class ActivityLogController extends Controller
/** Custom pagination */
$activitylogModels = $this->paginate($query);
return view('back::pages.activity_logs.index', compact('activitylogModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.activity_logs.index';
return view($view, [
'activitylogModels' => $activitylogModels,
'search' => $search,
'searchFields' => $searchFields,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\ActivityLog $activity_log
* @return \Illuminate\Contracts\View\View
*/
public function show(ActivityLog $activity_log): \Illuminate\Contracts\View\View
public function show(ActivityLog $activity_log): View
{
$this->authorize('isConceptor');
$targetModelClass = $activity_log->model_class;
$targetModel = $activity_log->model_class::where(
(new $targetModelClass())->getRouteKeyName(),
new $targetModelClass()->getRouteKeyName(),
$activity_log->model_id
)->first();
return view('back::pages.activity_logs.show', [
/** @var view-string $view */
$view = 'back::pages.activity_logs.show';
return view($view, [
'activitylogModel' => $activity_log,
'targetModel' => $targetModel,
]);

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
@ -15,8 +17,6 @@ class LoginController extends Controller
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -25,8 +25,6 @@ class LoginController extends Controller
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
@ -35,18 +33,17 @@ class LoginController extends Controller
/**
* Show the application's login form.
*
* @return \Illuminate\Contracts\View\View
*/
public function showLoginForm(): View
{
return view('back::auth.login');
/** @var view-string $view */
$view = 'back::auth.login';
return view($view);
}
/**
* Where to redirect users after login.
*
* @return string
*/
protected function redirectTo(): string
{
@ -56,14 +53,14 @@ class LoginController extends Controller
/**
* Attempt to log the user into the application.
*
* @param \Illuminate\Http\Request $request
* @return boolean
* @return bool
*/
protected function attemptLogin(Request $request)
{
$credentials = $this->credentials($request);
// * Attempt login only using published users.
$credentials['published'] = true;
return $this->guard()->attempt(
$credentials,
$request->boolean('remember')

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
@ -13,8 +15,6 @@ class ResetPasswordController extends Controller
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
@ -23,8 +23,6 @@ class ResetPasswordController extends Controller
/**
* Where to redirect users after login.
*
* @return string
*/
protected function redirectTo(): string
{

View file

@ -1,11 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\BannedIps\StoreBannedIpRequest;
use App\Http\Requests\Bo\BannedIps\UpdateBannedIpRequest;
use App\Models\BannedIp;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -22,13 +27,10 @@ class BannedIpController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $bannedipModels */
/** @var Builder $bannedipModels */
$bannedipModels = BannedIp::query();
/** @var string $search Search field */
@ -49,43 +51,42 @@ class BannedIpController extends Controller
/** Custom pagination */
$bannedipModels = $this->paginate($bannedipModels);
return view('back::pages.banned-ips.index', compact('bannedipModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.banned-ips.index';
return view($view, ['bannedipModels' => $bannedipModels, 'search' => $search, 'searchFields' => $searchFields]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\BannedIp $banned_ip
* @return \Illuminate\Contracts\View\View
*/
public function create(BannedIp $banned_ip): \Illuminate\Contracts\View\View
public function create(BannedIp $banned_ip): View
{
return view('back::pages.banned-ips.create', [
/** @var view-string $view */
$view = 'back::pages.banned-ips.create';
return view($view, [
'bannedipModel' => $banned_ip,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\BannedIps\StoreBannedIpRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreBannedIpRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreBannedIpRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$banned_ip = new BannedIp();
$banned_ip->fill($request->validated());
if ($banned_ip->saveOrFail()) {
return redirect()
->route('bo.banned_ips.edit', $banned_ip)
return to_route('bo.banned_ips.edit', $banned_ip)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.banned_ips'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.banned_ips'))]
@ -95,38 +96,33 @@ class BannedIpController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\BannedIp $banned_ip
* @return \Illuminate\Contracts\View\View
*/
public function edit(BannedIp $banned_ip): \Illuminate\Contracts\View\View
public function edit(BannedIp $banned_ip): View
{
return view('back::pages.banned-ips.edit', [
/** @var view-string $view */
$view = 'back::pages.banned-ips.edit';
return view($view, [
'bannedipModel' => $banned_ip,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\BannedIps\UpdateBannedIpRequest $request
* @param \App\Models\BannedIp $banned_ip
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateBannedIpRequest $request, BannedIp $banned_ip): \Illuminate\Http\RedirectResponse
public function update(UpdateBannedIpRequest $request, BannedIp $banned_ip): RedirectResponse
{
return DB::transaction(function () use ($request, $banned_ip) {
$banned_ip->fill($request->validated());
if ($banned_ip->saveOrFail()) {
return redirect()
->route('bo.banned_ips.edit', $banned_ip)
return to_route('bo.banned_ips.edit', $banned_ip)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.banned_ips'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.banned_ips'))]
@ -136,23 +132,19 @@ class BannedIpController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\BannedIp $banned_ip
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(BannedIp $banned_ip): \Illuminate\Http\RedirectResponse
public function destroy(BannedIp $banned_ip): RedirectResponse
{
return DB::transaction(function () use ($banned_ip) {
if ($banned_ip->deleteOrFail()) {
return redirect()
->route('bo.banned_ips.index')
return to_route('bo.banned_ips.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.banned_ips'))]
));
}
return redirect()
->back()
return back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.banned_ips'))]

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
@ -12,21 +14,22 @@ use App\Models\Code;
use App\Models\Participation;
use App\Models\Settings;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Queue;
use Kwaadpepper\Serial\Exceptions\SerialCasterException;
use Throwable;
class CodeController extends Controller
{
/**
* Show code form to generate and export
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
public function index(): View
{
$this->authorize('generate', Code::class);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
$nbCodeBase = Code::query()->max('id');
$nbCodeUsed = Code::query()->whereHas(
'participations',
@ -35,22 +38,23 @@ class CodeController extends Controller
)->count();
$nbCodeExported = Code::query()->where('code_exported', true)->count();
$nbCodeRemainingToExport = Code::query()->where('code_exported', false)->count();
return view('back::pages.codes.index', compact(
'settings',
'nbCodeBase',
'nbCodeUsed',
'nbCodeExported',
'nbCodeRemainingToExport'
));
/** @var view-string $view */
$view = 'back::pages.codes.index';
return view($view, [
'settings' => $settings,
'nbCodeBase' => $nbCodeBase,
'nbCodeUsed' => $nbCodeUsed,
'nbCodeExported' => $nbCodeExported,
'nbCodeRemainingToExport' => $nbCodeRemainingToExport,
]);
}
/**
* Run generate codes action
*
* @param \App\Http\Requests\Bo\Codes\GenerateCodesRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function generate(GenerateCodesRequest $request): \Illuminate\Http\RedirectResponse
public function generate(GenerateCodesRequest $request): RedirectResponse
{
$this->authorize('generate', Code::class);
Queue::push(
@ -66,7 +70,7 @@ class CodeController extends Controller
// phpcs:ignore Generic.Files.LineLength.TooLong
'La tache de génération à été ajoutée, un mail de notification sera envoyé à l\'adresse :email lorsque le traitement sera terminé.',
[
'email' => config('mail.admin')
'email' => config('mail.admin'),
]
)
);
@ -74,11 +78,8 @@ class CodeController extends Controller
/**
* Run export codes
*
* @param \App\Http\Requests\Bo\Codes\ExportCodesRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function export(ExportCodesRequest $request): \Illuminate\Http\RedirectResponse
public function export(ExportCodesRequest $request): RedirectResponse
{
$this->authorize('export', Code::class);
Queue::push(
@ -93,25 +94,22 @@ class CodeController extends Controller
'success',
// phpcs:ignore Generic.Files.LineLength.TooLong
__('L\'export des codes à commencé, un mail de notification sera envoyé à l\'adresse :emaillorsque le traitement sera terminé.', [
'email' => config('mail.admin')
'email' => config('mail.admin'),
])
);
}
/**
* Search for a specific code.
*
* @param \App\Http\Requests\Bo\Codes\CodeSearchRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function search(CodeSearchRequest $request): \Illuminate\Contracts\View\View
public function search(CodeSearchRequest $request): View
{
$this->authorize('search', Code::class);
$code = null;
try {
$code = Code::query()->where('id', Code::serialToInt($request->code))->first();
} catch (\Throwable $e) {
if (\get_class($e) === SerialCasterException::class) {
} catch (Throwable $e) {
if ($e::class === SerialCasterException::class) {
$code = false;
}
} finally {
@ -126,10 +124,13 @@ 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('id', $code->id))
->count();
}
return view('back::pages.codes.search', compact('code', 'used_counter'));
/** @var view-string $view */
$view = 'back::pages.codes.search';
return view($view, ['code' => $code, 'used_counter' => $used_counter]);
}
}

View file

@ -1,60 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Participation;
use App\Models\Settings;
use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
/**
* Dashboard Page
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
public function index(): View
{
// * Pour les stats.
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
$avgParticipUser = 0;
$participationPeriod = [];
$statsUnique = [];
$query = DB::table((new Participation())->getTable());
$dateFrmtr = function (object $row) {
$row->date = Carbon::parse($row->date)->isoFormat('DD/MM/YYYY');
$query = DB::table(new Participation()->getTable());
$dateFrmtr = function (object $row): object {
$row->date = Date::parse($row->date)->isoFormat('DD/MM/YYYY');
return $row;
};
/** @var \Illuminate\Support\Collection<string,object> */
/** @var Collection<string, object> */
$participationsPerDay = (clone $query)
->selectRaw('COUNT(*) AS count, DATE(created_at) AS date')
->groupByRaw('DATE(created_at)')->get()->map($dateFrmtr)->keyBy('date');
/** @var \Illuminate\Support\Collection<object> */
/** @var Collection<object> */
$participationsUniquePerDay = (clone $query)
->selectRaw('DISTINCT customer_id AS user, COUNT(*) AS count, DATE(created_at) AS date')
->groupByRaw('DATE(created_at), user')->get()->map($dateFrmtr);
/** @var \Illuminate\Support\Collection<object> */
/** @var Collection<object> */
$participationsUniquePerCustomer = (clone $query)
->selectRaw("DISTINCT customer_id AS user, COUNT(*) AS count")
->selectRaw('DISTINCT customer_id AS user, COUNT(*) AS count')
->groupBy('user')->orderBy('user')->get();
$participationsTotal = Participation::query()->count();
/** @var \Illuminate\Support\Collection<object> 3 codes EAN les plus utilisés */
/** @var Collection<object> 3 codes EAN les plus utilisés */
$codesEan = (clone $query)
->selectRaw('COUNT(product_id) AS nb_usage, product_id, ean_code')
->join('products', 'product_id', '=', 'products.id')
->groupBy('product_id', 'ean_code')
->orderByDesc('nb_usage')->limit(3)->get();
$participationsUniquePerDay->each(function (object $row) use (&$statsUnique) {
$participationsUniquePerDay->each(function (object $row) use (&$statsUnique): void {
Arr::set($statsUnique, $row->date, (Arr::get($statsUnique, $row->date) ?? 0) + 1);
});
@ -62,23 +64,22 @@ class DashboardController extends Controller
if ($participationsUniquePerCustomer->count() > 0) {
$avgParticipUser = round($participationsTotal / $participationsUniquePerCustomer->count(), 1);
}
// * Création des tableaux avec toutes les dates depuis le début du jeu
// * et le nombre de participations associées pour le graph JS.
$start_date = $settings->start_date;
$end_date = now()->lt($settings->end_date) ? now() : $settings->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));
return view('back::pages.dashboard.index', compact(
'dates',
'count',
'countUnique',
'participationsTotal',
'avgParticipUser',
'codesEan',
'settings'
));
/** @var view-string $view */
$view = 'back::pages.dashboard.index';
return view($view, [
'dates' => $dates,
'count' => $count,
'countUnique' => $countUnique,
'participationsTotal' => $participationsTotal,
'avgParticipUser' => $avgParticipUser,
'codesEan' => $codesEan,
'settings' => $settings,
]);
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
@ -7,6 +9,9 @@ use App\Http\Requests\Bo\FaqQuestions\StoreFaqQuestionRequest;
use App\Http\Requests\Bo\FaqQuestions\UpdateFaqQuestionRequest;
use App\Models\FaqQuestion;
use App\Models\FaqTheme;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -23,13 +28,10 @@ class FaqQuestionController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $faqQuestionModels */
/** @var Builder $faqQuestionModels */
$faqQuestionModels = FaqQuestion::query();
/** @var string $search Search field */
@ -55,38 +57,42 @@ class FaqQuestionController extends Controller
/** Custom pagination */
$faqQuestionModels = $this->paginate($faqQuestionModels);
return view('back::pages.faq-questions.index', compact('faqQuestionModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.faq-questions.index';
return view($view, [
'faqQuestionModels' => $faqQuestionModels,
'search' => $search,
'searchFields' => $searchFields,
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\FaqQuestion $faqQuestion
* @return \Illuminate\Contracts\View\View
*/
public function create(FaqQuestion $faqQuestion): \Illuminate\Contracts\View\View
public function create(FaqQuestion $faqQuestion): View
{
$faqThemeModels = FaqTheme::all();
return view('back::pages.faq-questions.create', [
/** @var view-string $view */
$view = 'back::pages.faq-questions.create';
return view($view, [
'faqQuestionModel' => $faqQuestion,
'faqThemeModels' => $faqThemeModels
'faqThemeModels' => $faqThemeModels,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\FaqQuestions\StoreFaqQuestionRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreFaqQuestionRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreFaqQuestionRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$faqQuestion = new FaqQuestion();
$faqQuestion->fill($request->validated());
if ($faqQuestion->saveOrFail()) {
return redirect()
->route('bo.faq-questions.edit', $faqQuestion)
return to_route('bo.faq-questions.edit', $faqQuestion)
->with(
'success',
trans(
@ -95,8 +101,8 @@ class FaqQuestionController extends Controller
)
);
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => trans_choice('models.classes.faq-questions', 1)]
@ -106,45 +112,38 @@ class FaqQuestionController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\FaqQuestion $faqQuestion
* @return \Illuminate\Contracts\View\View
*/
public function edit(FaqQuestion $faqQuestion): \Illuminate\Contracts\View\View
public function edit(FaqQuestion $faqQuestion): View
{
$faqThemeModels = FaqTheme::all();
return view('back::pages.faq-questions.edit', [
/** @var view-string $view */
$view = 'back::pages.faq-questions.edit';
return view($view, [
'faqQuestionModel' => $faqQuestion,
'faqThemeModels' => $faqThemeModels
'faqThemeModels' => $faqThemeModels,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\FaqQuestions\UpdateFaqQuestionRequest $request
* @param \App\Models\FaqQuestion $faqQuestion
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateFaqQuestionRequest $request,
FaqQuestion $faqQuestion
): \Illuminate\Http\RedirectResponse {
): RedirectResponse {
return DB::transaction(function () use ($request, $faqQuestion) {
$faqQuestion->fill($request->validated());
if ($faqQuestion->saveOrFail()) {
return redirect()
->route(
'bo.faq-questions.edit',
$faqQuestion
)
return to_route('bo.faq-questions.edit', $faqQuestion)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => trans_choice('models.classes.faq-questions', 1)]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => trans_choice('models.classes.faq-questions', 1)]
@ -154,23 +153,19 @@ class FaqQuestionController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\FaqQuestion $faqQuestion
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(FaqQuestion $faqQuestion): \Illuminate\Http\RedirectResponse
public function destroy(FaqQuestion $faqQuestion): RedirectResponse
{
return DB::transaction(function () use ($faqQuestion) {
if ($faqQuestion->deleteOrFail()) {
return redirect()
->route('bo.faq-questions.index')
return to_route('bo.faq-questions.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => trans_choice('models.classes.faq-questions', 1)]
));
}
return redirect()
->back()
return back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => trans_choice('models.classes.faq-questions', 1)]

View file

@ -1,11 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\FaqThemes\StoreFaqThemeRequest;
use App\Http\Requests\Bo\FaqThemes\UpdateFaqThemeRequest;
use App\Models\FaqTheme;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -22,13 +27,10 @@ class FaqThemeController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $faqThemeModels */
/** @var Builder $faqThemeModels */
$faqThemeModels = FaqTheme::query();
/** @var string $search Search field */
@ -49,36 +51,35 @@ class FaqThemeController extends Controller
/** Custom pagination */
$faqThemeModels = $this->paginate($faqThemeModels);
return view('back::pages.faq-themes.index', compact('faqThemeModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.faq-themes.index';
return view($view, ['faqThemeModels' => $faqThemeModels, 'search' => $search, 'searchFields' => $searchFields]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\FaqTheme $faqTheme
* @return \Illuminate\Contracts\View\View
*/
public function create(FaqTheme $faqTheme): \Illuminate\Contracts\View\View
public function create(FaqTheme $faqTheme): View
{
return view('back::pages.faq-themes.create', [
/** @var view-string $view */
$view = 'back::pages.faq-themes.create';
return view($view, [
'faqThemeModel' => $faqTheme,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\FaqThemes\StoreFaqThemeRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreFaqThemeRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreFaqThemeRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$faqTheme = new FaqTheme();
$faqTheme->fill($request->validated());
if ($faqTheme->saveOrFail()) {
return redirect()
->route('bo.faq-themes.edit', $faqTheme)
return to_route('bo.faq-themes.edit', $faqTheme)
->with(
'success',
trans(
@ -87,8 +88,8 @@ class FaqThemeController extends Controller
)
);
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => trans_choice('models.classes.faq-themes', 1)]
@ -98,43 +99,35 @@ class FaqThemeController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\FaqTheme $faqTheme
* @return \Illuminate\Contracts\View\View
*/
public function edit(FaqTheme $faqTheme): \Illuminate\Contracts\View\View
public function edit(FaqTheme $faqTheme): View
{
return view('back::pages.faq-themes.edit', [
/** @var view-string $view */
$view = 'back::pages.faq-themes.edit';
return view($view, [
'faqThemeModel' => $faqTheme,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\FaqThemes\UpdateFaqThemeRequest $request
* @param \App\Models\FaqTheme $faqTheme
* @return \Illuminate\Http\RedirectResponse
*/
public function update(
UpdateFaqThemeRequest $request,
FaqTheme $faqTheme
): \Illuminate\Http\RedirectResponse {
): RedirectResponse {
return DB::transaction(function () use ($request, $faqTheme) {
$faqTheme->fill($request->validated());
if ($faqTheme->saveOrFail()) {
return redirect()
->route(
'bo.faq-themes.edit',
$faqTheme
)
return to_route('bo.faq-themes.edit', $faqTheme)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => trans_choice('models.classes.faq-themes', 1)]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => trans_choice('models.classes.faq-themes', 1)]
@ -144,23 +137,19 @@ class FaqThemeController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\FaqTheme $faqTheme
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(FaqTheme $faqTheme): \Illuminate\Http\RedirectResponse
public function destroy(FaqTheme $faqTheme): RedirectResponse
{
return DB::transaction(function () use ($faqTheme) {
if ($faqTheme->deleteOrFail()) {
return redirect()
->route('bo.faq-themes.index')
return to_route('bo.faq-themes.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => trans_choice('models.classes.faq-themes', 1)]
));
}
return redirect()
->back()
return back()
->with('error', __(
'crud.messages.cannot_be_deleted',
['model' => trans_choice('models.classes.faq-themes', 1)]

View file

@ -1,15 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\InstantWin;
use App\Models\Prize;
use App\Models\Settings;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Carbon\CarbonInterval;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use stdClass;
class InstantWinController extends Controller
{
@ -23,22 +29,21 @@ class InstantWinController extends Controller
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
public function index(): View
{
$instantWinModelList = InstantWin::query()->orderBy('available_at')->get();
$instantWinModelList = InstantWin::query()->oldest('available_at')->get();
return view('back::pages.instant-win.index', [
'instantWinModelList' => $instantWinModelList
/** @var view-string $view */
$view = 'back::pages.instant-win.index';
return view($view, [
'instantWinModelList' => $instantWinModelList,
]);
}
/**
* Returns generated instant win.
*
* @return \Illuminate\Http\JsonResponse
*/
public function generate(): JsonResponse
{
@ -56,23 +61,20 @@ class InstantWinController extends Controller
*/
$day_end = now()->setTime(22, 29);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
$prizeModelList = Prize::all();
/** @var \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinDayCalendar> */
/** @var Collection<array-key, WinDayCalendar> */
$datesGlobal = collect();
/**
* @var \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinTimeElement>
* Liste de tous les lots à gagner
*/
/** @var Collection<array-key, WinTimeElement> Liste de tous les lots à gagner */
$lotsGlobal = collect();
// * Seed srand !
\mt_srand(time());
// * Init prize list
$prizeModelList->each(function (Prize $prize) use ($lotsGlobal, $day_start, $day_end) {
$prizeModelList->each(function (Prize $prize) use ($lotsGlobal, $day_start, $day_end): void {
for ($i = 0; $i < $prize->quantity; $i++) {
$el = new WinTimeElement();
$el->prize = $prize;
@ -84,7 +86,7 @@ class InstantWinController extends Controller
}
});
$lotsGlobal->shuffle(time());
$lotsGlobal->shuffle();
// * Init days calendar
foreach ($settings->start_date->toPeriod($settings->end_date, CarbonInterval::make('1day')) as $date) {
@ -95,8 +97,8 @@ class InstantWinController extends Controller
}
// * Save Instant wins
$this->generateArray($lotsGlobal, $datesGlobal, 0)->each(function (WinDayCalendar $winDayCalendar) {
$winDayCalendar->lots->each(function (WinTimeElement $winTimeElement) {
$this->generateArray($lotsGlobal, $datesGlobal, 0)->each(function (WinDayCalendar $winDayCalendar): void {
$winDayCalendar->lots->each(function (WinTimeElement $winTimeElement): void {
$instantWin = new InstantWin();
$instantWin->available_at = $winTimeElement->time;
$instantWin->prize()->associate($winTimeElement->prize);
@ -114,11 +116,11 @@ class InstantWinController extends Controller
*
* Même algo que celui implémenté par Romain.
*
* @param \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinTimeElement> $lotsGlobal
* Liste de tous les lots à gagner
* @param \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinDayCalendar> $dateGlobal
* @param integer $prizesDone
* @return \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinDayCalendar>
* @param Collection<array-key, WinTimeElement> $lotsGlobal
* Liste de tous les lots à gagner
* @param Collection<array-key, WinDayCalendar> $dateGlobal
* @return Collection<array-key, WinDayCalendar>
*
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
private function generateArray(Collection $lotsGlobal, Collection $dateGlobal, int $prizesDone): Collection
@ -129,7 +131,7 @@ class InstantWinController extends Controller
// ? À-t-on déjà assigné un lot par jour (au moins) ?
// ? Le jour aléatoire sélectionné à déja un lot ?
if ($prizesDone < $dateGlobal->count() and $dateGlobal->get($rKey)->lots->count() > 0) {
if ($prizesDone < $dateGlobal->count() && $dateGlobal->get($rKey)->lots->count() > 0) {
return $this->generateArray($lotsGlobal, $dateGlobal, $prizesDone);
}
@ -142,15 +144,15 @@ 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) =>
$a->time->timestamp - $b->time->timestamp)
->map(function (WinTimeElement $time) use ($date) {
$dateGlobal->each(function (WinDayCalendar $date): void {
$date->lots = $date->lots->sort(fn (WinTimeElement $a, WinTimeElement $b): int | float => $a->time->timestamp - $b->time->timestamp)
->map(function (WinTimeElement $time) use ($date): WinTimeElement {
$time->time->setDate(
$date->time->year,
$date->time->month,
$date->time->day
);
return $time;
});
});
@ -160,23 +162,25 @@ class InstantWinController extends Controller
}
/**
* @property \App\Models\Prize $prize Le lot à gagner.
* @property \Carbon\Carbon $time Une heure aléatoire.
* @property Prize $prize Le lot à gagner.
* @property Carbon $time Une heure aléatoire.
*
* @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses
*/
class WinTimeElement extends \stdClass
class WinTimeElement extends stdClass
{
// phpcs:enable
}
/**
* @property \Carbon\Carbon|\Carbon\CarbonInterface $time
* Un jour dans le temps.
* @property \Illuminate\Support\Collection<array-key,\App\Http\Controllers\Bo\WinTimeElement> $lots
* Liste des lots tombant ce jour.
* @property Carbon|CarbonInterface $time
* Un jour dans le temps.
* @property Collection<array-key, WinTimeElement> $lots
* Liste des lots tombant ce jour.
*
* @phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses
*/
class WinDayCalendar extends \stdClass
class WinDayCalendar extends stdClass
{
// phpcs:enable
}

View file

@ -1,10 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Participation;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
class ParticipationController extends Controller
@ -19,15 +22,15 @@ class ParticipationController extends Controller
/**
* View all winners
*
* @return \Illuminate\Contracts\View\View
*/
public function viewWinnerList(): View
{
$this->authorize('viewWinnerList', Participation::class);
/** @var \Illuminate\Database\Eloquent\Builder $participationModels */
$participationModels = Participation::query()->with(['customer', 'code', 'instantWin', 'product'])->whereHas('instantWin');
/** @var Builder $participationModels */
$participationModels = Participation::query()
->with(['customer', 'code', 'instantWin', 'product'])
->whereHas('instantWin');
/** Sort columns with a query */
$this->sortQuery($participationModels);
@ -35,36 +38,37 @@ class ParticipationController extends Controller
/** Custom pagination */
$participationModels = $this->paginate($participationModels);
return view('back::pages.winners-list.index', compact(
'participationModels'
));
/** @var view-string $view */
$view = 'back::pages.winners-list.index';
return view($view, ['participationModels' => $participationModels]);
}
/**
* Display participation form
*
* @param \App\Models\Participation $participation
* @return \Illuminate\Contracts\View\View
*/
public function setComment(Participation $participation): View
{
$this->authorize('setComment', $participation);
return view('back::pages.winners-list.set-comment', [
'participationModel' => $participation
/** @var view-string $view */
$view = 'back::pages.winners-list.set-comment';
return view($view, [
'participationModel' => $participation,
]);
}
/**
* Set Comment
*
* @param \App\Models\Participation $participation
* @return \Illuminate\Http\RedirectResponse
*/
public function setCommentSubmit(Participation $participation): RedirectResponse
{
$this->authorize('setComment', $participation);
$participation->comment = request()->string('comment');
$participation->comment = request()->string('comment')->toString();
$participation->saveOrFail();
return back()->with('success', \trans(
'Le commentaire à bien été mis à jour'
));
@ -72,15 +76,13 @@ class ParticipationController extends Controller
/**
* Change prize sent
*
* @param \App\Models\Participation $participation
* @return \Illuminate\Http\RedirectResponse
*/
public function changePrizeSent(Participation $participation): RedirectResponse
{
$this->authorize('changePrizeSent', $participation);
$participation->prize_sent = true;
$participation->saveOrFail();
return back()->with('success', \trans(
'Le lot est bien marqué comme envoyé et une notification à été envoyé au gagant'
));

View file

@ -1,11 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Prizes\StorePrizeRequest;
use App\Http\Requests\Bo\Prizes\UpdatePrizeRequest;
use App\Models\Prize;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -22,13 +27,10 @@ class PrizeController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $prizeModels */
/** @var Builder $prizeModels */
$prizeModels = Prize::query();
/** @var string $search Search field */
@ -52,54 +54,53 @@ class PrizeController extends Controller
/** Custom pagination */
$prizeModels = $this->paginate($prizeModels);
return view('back::pages.prizes.index', compact('prizeModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.prizes.index';
return view($view, ['prizeModels' => $prizeModels, 'search' => $search, 'searchFields' => $searchFields]);
}
/**
* Display the specified resource.
*
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function show(Prize $prize): \Illuminate\Contracts\View\View
public function show(Prize $prize): View
{
return view('back::pages.prizes.show', ['prizeModel' => $prize]);
/** @var view-string $view */
$view = 'back::pages.prizes.show';
return view($view, ['prizeModel' => $prize]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function create(Prize $prize): \Illuminate\Contracts\View\View
public function create(Prize $prize): View
{
return view('back::pages.prizes.create', [
'prizeModel' => $prize
/** @var view-string $view */
$view = 'back::pages.prizes.create';
return view($view, [
'prizeModel' => $prize,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Prizes\StorePrizeRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StorePrizeRequest $request): \Illuminate\Http\RedirectResponse
public function store(StorePrizeRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$prize = new Prize();
$prize->fill($request->validated());
if ($prize->saveOrFail()) {
return redirect()
->route('bo.prizes.edit', $prize)
return to_route('bo.prizes.edit', $prize)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.prize'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(__('models.classes.prize'))]
@ -109,38 +110,33 @@ class PrizeController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function edit(Prize $prize): \Illuminate\Contracts\View\View
public function edit(Prize $prize): View
{
return view('back::pages.prizes.edit', [
'prizeModel' => $prize
/** @var view-string $view */
$view = 'back::pages.prizes.edit';
return view($view, [
'prizeModel' => $prize,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Prizes\UpdatePrizeRequest $request
* @param \App\Models\Prize $prize
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdatePrizeRequest $request, Prize $prize): \Illuminate\Http\RedirectResponse
public function update(UpdatePrizeRequest $request, Prize $prize): RedirectResponse
{
return DB::transaction(function () use ($request, $prize) {
$prize->fill($request->validated());
if ($prize->saveOrFail()) {
return redirect()
->route('bo.prizes.edit', $prize)
return to_route('bo.prizes.edit', $prize)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.prize'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(__('models.classes.prize'))]
@ -150,23 +146,19 @@ class PrizeController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Prize $prize
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Prize $prize): \Illuminate\Http\RedirectResponse
public function destroy(Prize $prize): RedirectResponse
{
return DB::transaction(function () use ($prize) {
if ($prize->deleteOrFail()) {
return redirect()
->route('bo.prizes.index')
return to_route('bo.prizes.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.prize'))]
));
}
return redirect()
->back()
return back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(__('models.classes.prize'))]

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
@ -7,6 +9,9 @@ use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Models\Product;
use App\Models\Range;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -23,13 +28,10 @@ class ProductController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $productModels */
/** @var Builder $productModels */
$productModels = Product::query()->with('range');
/** @var string $search Search field */
@ -55,63 +57,63 @@ class ProductController extends Controller
/** Custom pagination */
$productModels = $this->paginate($productModels);
return view('back::pages.products.index', compact('productModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.products.index';
return view($view, ['productModels' => $productModels, 'search' => $search, 'searchFields' => $searchFields]);
}
/**
* Display the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Contracts\View\View
*/
public function show(Product $product): \Illuminate\Contracts\View\View
public function show(Product $product): View
{
return view('back::pages.products.show', ['productModel' => $product]);
/** @var view-string $view */
$view = 'back::pages.products.show';
return view($view, ['productModel' => $product]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function create(Product $product): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
public function create(Product $product): View | RedirectResponse
{
$rangeModels = Range::all();
if (!count($rangeModels)) {
return \redirect()->route('bo.ranges.index')
if (! count($rangeModels)) {
return to_route('bo.ranges.index')
->with(
'error',
'Vous devez ajouter une gamme avant de pouvoir ajouter un produit'
);
}
return view('back::pages.products.create', [
/** @var view-string $view */
$view = 'back::pages.products.create';
return view($view, [
'productModel' => $product,
'rangeModels' => $rangeModels
'rangeModels' => $rangeModels,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreProductRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreProductRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$product = new Product();
$product->fill($request->validated());
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
return to_route('bo.products.edit', $product)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.product'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(__('models.classes.product'))]
@ -121,40 +123,36 @@ class ProductController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Contracts\View\View
*/
public function edit(Product $product): \Illuminate\Contracts\View\View
public function edit(Product $product): View
{
$rangeModels = Range::all();
return view('back::pages.products.edit', [
/** @var view-string $view */
$view = 'back::pages.products.edit';
return view($view, [
'productModel' => $product,
'rangeModels' => $rangeModels
'rangeModels' => $rangeModels,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateProductRequest $request, Product $product): \Illuminate\Http\RedirectResponse
public function update(UpdateProductRequest $request, Product $product): RedirectResponse
{
return DB::transaction(function () use ($request, $product) {
$product->fill($request->validated());
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
return to_route('bo.products.edit', $product)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.product'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(__('models.classes.product'))]
@ -164,23 +162,19 @@ class ProductController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Product $product): \Illuminate\Http\RedirectResponse
public function destroy(Product $product): RedirectResponse
{
return DB::transaction(function () use ($product) {
if ($product->deleteOrFail()) {
return redirect()
->route('bo.products.index')
return to_route('bo.products.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.product'))]
));
}
return redirect()
->back()
return back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(__('models.classes.product'))]

View file

@ -1,11 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ranges\StoreRangeRequest;
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
use App\Models\Range;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -22,13 +27,10 @@ class RangeController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $rangeModels */
/** @var Builder $rangeModels */
$rangeModels = Range::query();
/** @var string $search Search field */
@ -49,54 +51,53 @@ class RangeController extends Controller
/** Custom pagination */
$rangeModels = $this->paginate($rangeModels);
return view('back::pages.ranges.index', compact('rangeModels', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.ranges.index';
return view($view, ['rangeModels' => $rangeModels, 'search' => $search, 'searchFields' => $searchFields]);
}
/**
* Display the specified resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View
*/
public function show(Range $range): \Illuminate\Contracts\View\View
public function show(Range $range): View
{
return view('back::pages.ranges.show', ['rangeModel' => $range]);
/** @var view-string $view */
$view = 'back::pages.ranges.show';
return view($view, ['rangeModel' => $range]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View
*/
public function create(Range $range): \Illuminate\Contracts\View\View
public function create(Range $range): View
{
return view('back::pages.ranges.create', [
/** @var view-string $view */
$view = 'back::pages.ranges.create';
return view($view, [
'rangeModel' => $range,
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\StoreRangeRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreRangeRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreRangeRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$range = new Range();
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
return to_route('bo.ranges.edit', $range)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(__('models.classes.range'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(__('models.classes.range'))]
@ -106,38 +107,33 @@ class RangeController extends Controller
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Contracts\View\View
*/
public function edit(Range $range): \Illuminate\Contracts\View\View
public function edit(Range $range): View
{
return view('back::pages.ranges.edit', [
'rangeModel' => $range
/** @var view-string $view */
$view = 'back::pages.ranges.edit';
return view($view, [
'rangeModel' => $range,
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
* @param \App\Models\Range $range
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateRangeRequest $request, Range $range): \Illuminate\Http\RedirectResponse
public function update(UpdateRangeRequest $request, Range $range): RedirectResponse
{
return DB::transaction(function () use ($request, $range) {
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
return to_route('bo.ranges.edit', $range)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.range'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(__('models.classes.range'))]
@ -147,28 +143,25 @@ class RangeController extends Controller
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Range $range
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Range $range): \Illuminate\Http\RedirectResponse
public function destroy(Range $range): RedirectResponse
{
if ($range->products()->count()) {
return back()->withError(
'Cette gamme est associé a des produits, la supression n\'est pas possible'
);
}
return DB::transaction(function () use ($range) {
if ($range->deleteOrFail()) {
return redirect()
->route('bo.ranges.index')
return to_route('bo.ranges.index')
->with('success', __(
'crud.messages.has_been_deleted',
['model' => Str::singular(__('models.classes.range'))]
));
}
return redirect()
->back()
return back()
->with('error', __(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(__('models.classes.range'))]

View file

@ -1,16 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Lib\Discount;
use App\Mail\Won\DiscountWonMail;
use App\Mail\Won\PrizeIsSentMail;
use App\Mail\Won\PrizeWonMail;
use App\Mail\Won\WinningProofSubmitedMail;
use App\Models\Participation;
use App\Models\Prize;
use App\Models\Settings;
use App\Lib\Discount;
use App\Models\Participation;
use App\Models\User;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -21,209 +25,216 @@ class Re7Controller extends Controller
{
/**
* Re7Controller
*
* @return void
*/
public function __construct()
{
$this->middleware(function (Request $request, \Closure $next) {
$this->middleware(function (Request $request, Closure $next) {
$this->authorize('fo.showRe7');
return $next($request);
});
}
/**
* Show Home page
*
* @return \Illuminate\Contracts\View\View
*/
public function home(): View
{
return view('theme::pages.home');
/** @var view-string $view */
$view = 'theme::pages.home';
return view($view);
}
/**
* Show Teaser page
*
* @return \Illuminate\Contracts\View\View
*/
public function teaser(): View
{
return view('theme::pages.teaser');
/** @var view-string $view */
$view = 'theme::pages.teaser';
return view($view);
}
/**
* Show Teaser page
*
* @return \Illuminate\Contracts\View\View
*/
public function endGame(): View
{
return view('theme::pages.end-game');
/** @var view-string $view */
$view = 'theme::pages.end-game';
return view($view);
}
/**
* Show code input page
*
* @return \Illuminate\Contracts\View\View
*/
public function participate(): View
{
return view('theme::pages.participation.participate');
/** @var view-string $view */
$view = 'theme::pages.participation.participate';
return view($view);
}
/**
* Show code input page
*
* @return \Illuminate\Contracts\View\View
*/
public function participationExhausted(): View
{
return view('theme::pages.participation.participation-exhausted');
/** @var view-string $view */
$view = 'theme::pages.participation.participation-exhausted';
return view($view);
}
// * -- Participation
/**
* Show prize page
*
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function prize(Prize $prize): View
{
// * Fake participation model
$participationModel = new Participation();
$participationModel = 9999999;
return view('theme::pages.participation.won', [
'prizeModel' => $prize,
'participationModel' => $participationModel
/** @var view-string $view */
$view = 'theme::pages.participation.won';
return view($view, [
'prizeModel' => $prize,
'participationModel' => $participationModel,
]);
}
/**
* Show discount page
*
* @return \Illuminate\Contracts\View\View
*/
public function discount(): View
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var \App\Lib\Discount */
/** @var Settings */
$settings = resolve(Settings::class);
/** @var Discount */
$discount = Arr::get(
$settings->web_coupons,
$this->getRandomWeightedElementIndex($settings->web_coupons)
);
if (!$settings->discounts_activated) {
if (! $settings->discounts_activated) {
return abort(404);
}
$mesBrDiscount = new Discount();
return view('theme::pages.participation.won', compact('discount', 'mesBrDiscount'));
/** @var view-string $view */
$view = 'theme::pages.participation.won';
return view($view, ['discount' => $discount, 'mesBrDiscount' => $mesBrDiscount]);
}
/**
* Show loose page
*
* @return \Illuminate\Contracts\View\View
*/
public function loose(): View
{
return view('theme::pages.participation.loose');
/** @var view-string $view */
$view = 'theme::pages.home';
return view($view);
}
// * -- END Participation
/**
* View won prize mail
*
* @param \App\Models\Prize $prize
* @return \Illuminate\Contracts\View\View
*/
public function mailPrize(Prize $prize): View
{
return view('back::pages.re7-preview-mail', [
/** @var view-string $view */
$view = 'back::pages.re7-preview-mail';
return view($view, [
'render_url' => route('re7.mail-print', [
'type' => 'prize',
'prize' => $prize
])
'type' => 'prize',
'prize' => $prize,
]),
]);
}
/**
* View won discount mail
*
* @return \Illuminate\Contracts\View\View
*/
public function mailDiscount(): View
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
if (!$settings->discounts_activated) {
/** @var Settings */
$settings = resolve(Settings::class);
if (! $settings->discounts_activated) {
return abort(404);
}
return view('back::pages.re7-preview-mail', [
/** @var view-string $view */
$view = 'back::pages.re7-preview-mail';
return view($view, [
'render_url' => route('re7.mail-print', [
'type' => 'discount'
])
'type' => 'discount',
]),
]);
}
/**
* View proof mail received mail
*
* @return \Illuminate\Contracts\View\View
*/
public function mailProofReceived(): View
{
return view('back::pages.re7-preview-mail', [
/** @var view-string $view */
$view = 'back::pages.re7-preview-mail';
return view($view, [
'render_url' => route('re7.mail-print', [
'type' => 'proof'
])
'type' => 'proof',
]),
]);
}
/**
* View sent prize mail
*
* @return \Illuminate\Contracts\View\View
*/
public function mailPrizeSent(): View
{
return view('back::pages.re7-preview-mail', [
/** @var view-string $view */
$view = 'back::pages.re7-preview-mail';
return view($view, [
'render_url' => route('re7.mail-print', [
'type' => 'prize-sent'
])
'type' => 'prize-sent',
]),
]);
}
/**
* Print a specific mail
*
* @param string $type
* @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 $prize = null): Response
{
/** @var \App\Models\User */
/** @var User */
$user = auth('backend')->user();
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
switch ($type) {
case 'discount':
/** @var \App\Lib\Discount */
/** @var Discount */
$discount = Arr::get(
$settings->web_coupons,
$this->getRandomWeightedElementIndex($settings->web_coupons)
);
return response((new DiscountWonMail(
return response(new DiscountWonMail(
new Address(
$user->email,
$user->first_name
@ -232,9 +243,9 @@ class Re7Controller extends Controller
$discount,
config('app.url'),
$settings->company,
))->render());
)->render());
case 'prize':
return response((new PrizeWonMail(
return response(new PrizeWonMail(
new Address(
$user->email,
$user->first_name
@ -243,27 +254,27 @@ class Re7Controller extends Controller
$prize,
config('app.url'),
$settings->company,
))->render());
)->render());
case 'prize-sent':
return response((new PrizeIsSentMail(
return response(new PrizeIsSentMail(
new Address(
$user->email,
$user->first_name
),
$settings->name,
$settings->company,
))->render());
)->render());
case 'proof':
return response((new WinningProofSubmitedMail(
return response(new WinningProofSubmitedMail(
new Address(
$user->email,
$user->first_name
),
$settings->name,
$settings->company,
))->render());
)->render());
default:
return response('', 404);
} //end switch
} // end switch
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
@ -7,9 +9,14 @@ use App\Http\Requests\Bo\Settings\UpdateSettingsRequest;
use App\Lib\Discount;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Settings;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use RuntimeException;
use TypeError;
class SettingsController extends Controller
{
@ -23,45 +30,39 @@ class SettingsController extends Controller
/**
* 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 edit(Settings $settings): View
{
return view('back::pages.settings.edit', [
'settingsModel' => $settings
/** @var view-string $view */
$view = 'back::pages.settings.edit';
return view($view, [
'settingsModel' => $settings,
]);
}
/**
* 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, Settings $settings): RedirectResponse
{
return DB::transaction(function () use ($request, $settings) {
$data = $request->validated();
// * Assign parsed Web coupon objects
Arr::set($data, 'web_coupons', collect(Arr::get($data, 'web_coupons'))->filter()
->map(fn (array $coupon) => $this->parseWebCoupon($coupon))
->map(fn (array $coupon): ?\App\Lib\Discount => $this->parseWebCoupon($coupon))
->filter()->all());
$settings->fill($data);
if ($settings->saveOrFail()) {
return redirect()
->route('bo.settings.edit', $settings)
return to_route('bo.settings.edit', $settings)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(__('models.classes.settings'))]
));
}
return redirect()
->back()
return back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(__('models.classes.settings'))]
@ -69,18 +70,16 @@ class SettingsController extends Controller
});
}
/**
* Try to parse webCoupon
*
* @param array<string,string> $coupon
* @return \App\Lib\Discount|null
* @param array<string,string> $coupon
*/
private function parseWebCoupon(array $coupon): Discount | null
private function parseWebCoupon(array $coupon): ?Discount
{
/** @var \Illuminate\Http\UploadedFile|string */
/** @var UploadedFile|string */
$picture = Arr::get($coupon, 'picture');
if (is_object($picture) and ($picture instanceof \Illuminate\Http\UploadedFile)) {
if ($picture instanceof UploadedFile) {
$picture = FileStorageHelper::storeFile(
new Settings(),
$picture,
@ -96,7 +95,7 @@ class SettingsController extends Controller
Arr::get($coupon, 'percentage'),
Arr::get($coupon, 'web_coupon_identifiant'),
);
} catch (\RuntimeException | \TypeError) {
} catch (RuntimeException | TypeError) {
return null;
}
}

View file

@ -1,10 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest;
use App\Models\StaticPage;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -21,13 +26,10 @@ class StaticPageController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
/** @var Builder $query */
$query = StaticPage::query();
/** @var string $search Search field */
@ -50,41 +52,44 @@ class StaticPageController extends Controller
/** Custom pagination */
$staticPageModelList = $this->paginate($query);
return view('back::pages.static_pages.index', compact('staticPageModelList', 'search', 'searchFields'));
/** @var view-string $view */
$view = 'back::pages.static_pages.index';
return view($view, [
'staticPageModelList' => $staticPageModelList,
'search' => $search,
'searchFields' => $searchFields,
]);
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Contracts\View\View
*/
public function edit(StaticPage $static_page): \Illuminate\Contracts\View\View
public function edit(StaticPage $static_page): View
{
return view('back::pages.static_pages.edit', ['staticPageModel' => $static_page]);
/** @var view-string $view */
$view = 'back::pages.static_pages.edit';
return view($view, ['staticPageModel' => $static_page]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest $request
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateStaticPageRequest $request, StaticPage $static_page): \Illuminate\Http\RedirectResponse
public function update(UpdateStaticPageRequest $request, StaticPage $static_page): RedirectResponse
{
return DB::transaction(function () use ($request, $static_page) {
$static_page->fill($request->validated());
if ($static_page->saveOrFail()) {
return redirect()->route('bo.static_pages.edit', $static_page)
return to_route('bo.static_pages.edit', $static_page)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1))
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1)),
]));
}
return redirect()->route('bo.static_pages.edit', $static_page)
return to_route('bo.static_pages.edit', $static_page)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1))
'model' => Str::ucfirst(\trans_choice('models.classes.static_page', 1)),
]));
});
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
@ -7,6 +9,9 @@ use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Models\User;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@ -25,13 +30,10 @@ class UserController extends Controller
/**
* 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
public function index(Request $request): View
{
/** @var \Illuminate\Database\Eloquent\Builder $userModels */
/** @var Builder $userModels */
$userModels = User::query();
/** @var string $search Search field */
@ -60,109 +62,101 @@ class UserController extends Controller
/** Custom pagination */
$userModels = $this->paginate($userModels);
return view('back::pages.users.index', compact('userModels', 'search', 'searchFields'));
}
/** @var view-string $view */
$view = 'back::pages.users.index';
return view($view, ['userModels' => $userModels, 'search' => $search, 'searchFields' => $searchFields]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function create(User $user): \Illuminate\Contracts\View\View
public function create(User $user): View
{
return view('back::pages.users.create', ['userModel' => $user]);
/** @var view-string $view */
$view = 'back::pages.users.create';
return view($view, ['userModel' => $user]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Users\StoreUserRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreUserRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreUserRequest $request): RedirectResponse
{
return DB::transaction(function () use ($request) {
$user = new User();
$user->fill($request->validated());
if ($user->saveOrFail()) {
return redirect()->route('bo.users.edit', $user)
return to_route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
'model' => Str::ucfirst(__('models.classes.user')),
]));
}
return redirect()->back()
return back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(__('models.classes.user'))
'model' => Str::ucfirst(__('models.classes.user')),
]));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function edit(User $user): \Illuminate\Contracts\View\View
public function edit(User $user): View
{
return view('back::pages.users.edit', ['userModel' => $user]);
/** @var view-string $view */
$view = 'back::pages.users.edit';
return view($view, ['userModel' => $user]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Users\UpdateUserRequest $request
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateUserRequest $request, User $user): \Illuminate\Http\RedirectResponse
public function update(UpdateUserRequest $request, User $user): RedirectResponse
{
return DB::transaction(function () use ($request, $user) {
$user->fill($request->validated());
if ($user->saveOrFail()) {
return redirect()->route('bo.users.edit', $user)
return to_route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
'model' => Str::ucfirst(__('models.classes.user')),
]));
}
return redirect()->route('bo.users.edit', $user)
return to_route('bo.users.edit', $user)
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
'model' => Str::ucfirst(__('models.classes.user')),
]));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\User $user
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(User $user): \Illuminate\Http\RedirectResponse
public function destroy(User $user): RedirectResponse
{
if ($user->deleteOrFail()) {
return redirect()->route('bo.users.index')
return to_route('bo.users.index')
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
'model' => Str::ucfirst(__('models.classes.user')),
]));
}
return redirect()->back()
return back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
'model' => Str::ucfirst(__('models.classes.user')),
]));
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(User $user): \Illuminate\Contracts\View\View
public function duplicate(User $user): View
{
return $this->create($user->replicate());
}

View file

@ -1,13 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Enums\Pagination\ItemsPerPaginationEnum;
use App\Enums\Theme\BootstrapThemeEnum;
use App\Lib\Discount;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Cache;
@ -20,7 +25,6 @@ class Controller extends BaseController
use AuthorizesRequests;
use ValidatesRequests;
/**
* Utility function for getting random values with weighting.
* Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50)
@ -30,8 +34,9 @@ class Controller extends BaseController
* weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66%
* chance of being selected. Also note that weights should be integers.
*
* @param array<\App\Lib\Discount> $weightedValues
* @return integer The winning key
* @param array<Discount> $weightedValues
* @return int The winning key
*
* @url https://stackoverflow.com/a/11872928/4355295
*/
protected function getRandomWeightedElementIndex(array $weightedValues): int
@ -45,13 +50,12 @@ class Controller extends BaseController
return $key;
}
}
return $rand;
}
/**
* Gives the redirection path for FO Auth controllers
*
* @return string
*/
protected function authLoggedRedirectPath(): string
{
@ -62,63 +66,57 @@ class Controller extends BaseController
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
* @return \Illuminate\Database\Eloquent\Builder
* @param Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields
* would be concatenate to make the search.
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
?callable $searchQuery = null,
string ...$fields
): \Illuminate\Database\Eloquent\Builder {
): Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
$query->where(function (\Illuminate\Contracts\Database\Query\Builder $query) use ($searchQuery, $words, $search, $fields): void {
foreach ($words as $word) {
if (!strlen($word)) {
if (! strlen($word)) {
continue;
}
$this->searchOnFields($query, $word, $fields);
} //end foreach
} // end foreach
// Search whole using whole sentence.
$this->searchOnFields($query, $search, $fields);
if ($searchQuery) {
$searchQuery($query);
}
});
} //end if
} // end if
return $query;
}
/**
* Build query search on all fields
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param array $fields
* @return \Illuminate\Database\Eloquent\Builder
*/
private function searchOnFields(
Builder $query,
string $search,
array $fields
): \Illuminate\Database\Eloquent\Builder {
): Builder {
$table = $query->getModel()->getTable();
foreach ($fields as $field) {
$field = str_replace('?', '\?', $field);
if (is_array($field)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($field)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
sprintf('UPPER(CONCAT(%s)) LIKE UPPER(?)', collect($field)->map(fn ($q): string => "`$table`.`$q`")->implode(', \' \', ')),
['%' . htmlspecialchars($search) . '%']
);
return $query;
}
// Get models woth enum's label.
@ -129,62 +127,56 @@ class Controller extends BaseController
['%' . htmlspecialchars($array->value) . '%']
);
}
return $query;
}
// Get models with relation's name/label.
if (Str::endsWith($field, '_id')) {
$query->orWhereHas(Str::remove('_id', $field), function ($queryRelation) use ($field, $search) {
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
$queryRelation->where('name', 'like', '%' . $search . '%');
} elseif (Schema::hasColumn(Str::remove('_id', $field) . 's', 'label')) {
$queryRelation->where('label', 'like', '%' . $search . '%');
$query->orWhereHas(
Str::remove('_id', $field),
function (\Illuminate\Contracts\Database\Query\Builder $queryRelation) use ($field, $search): void {
if (Schema::hasColumn(Str::remove('_id', $field) . 's', 'name')) {
$queryRelation->where('name', 'like', '%' . $search . '%');
} elseif (Schema::hasColumn(Str::remove('_id', $field) . 's', 'label')) {
$queryRelation->where('label', 'like', '%' . $search . '%');
}
}
});
);
return $query;
}
$query->orWhereRaw(
"UPPER(`$table`.`$field`) LIKE UPPER(?)",
['%' . htmlspecialchars($this->getSearchValue($query, $search, $field)) . '%']
);
} //end foreach
} // end foreach
return $query;
}
/**
* Get the search value (on string or Enum).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string $search
* @param string $field
* @return string|array
*/
protected function getSearchValue(Builder $query, string $search, string $field): string|array
protected function getSearchValue(Builder $query, string $search, string $field): string | array
{
$cast = $query->getModel()->getCasts()[$field] ?? null;
if (
count($query->getModel()->getCasts()) and // Check if the model has casts.
$cast and // Check if there is cast about specific field.
enum_exists($cast) // Check if the cast is an enum.
count($query->getModel()->getCasts()) && $cast && enum_exists($cast) // Check if the cast is an enum.
) {
// @phpstan-ignore-next-line
$cases = collect($cast::toArray());
$enum = $cases->filter(function ($item) use ($search) {
return preg_match('/' . Str::lower($search) . '/', Str::lower($item->label));
});
$enum = $cases->filter(fn ($item): int | false => preg_match('/' . Str::lower($search) . '/', Str::lower($item->label)));
return $enum->toArray();
} else {
return $search;
}
return $search;
}
/**
* Sort columns with a query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param array $ignore
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder
protected function sortQuery(Builder $query, array $ignore = []): Builder
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
@ -201,8 +193,8 @@ class Controller extends BaseController
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (!in_array(request()->sort_col, $ignore)) {
if (request()->sort_col && request()->sort_way && Schema::hasColumn($table, request()->sort_col)) {
if (! in_array(request()->sort_col, $ignore)) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
@ -210,59 +202,55 @@ class Controller extends BaseController
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
} elseif (Schema::hasColumn($table, 'updated_at')) {
$query = $query->orderBy('updated_at', 'DESC');
$query = $query->latest('updated_at');
Session::put("$rName.sort_col", 'updated_at');
Session::put("$rName.sort_way", 'desc');
} elseif (Schema::hasColumn($table, 'created_at')) {
$query = $query->orderBy('created_at', 'DESC');
$query = $query->latest();
Session::put("$rName.sort_col", 'created_at');
Session::put("$rName.sort_way", 'desc');
}
return $query;
}
/**
* Customize pagination with cache or config (default).
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Enums\Pagination\ItemsPerPaginationEnum|null $pagination
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*/
protected function paginate(
Builder $query,
ItemsPerPaginationEnum $pagination = null
): \Illuminate\Contracts\Pagination\LengthAwarePaginator {
?ItemsPerPaginationEnum $pagination = null
): LengthAwarePaginator {
$currentRoutePath = Str::slug(request()->route()->getName());
$cacheKey = "pagination.{$currentRoutePath}";
$pagination = $pagination ?? ItemsPerPaginationEnum::twelve;
$pagination ??= ItemsPerPaginationEnum::twelve;
$pagination = ToolboxHelper::getValidatedEnum(
Cache::get($cacheKey, $pagination->value()),
'pagination',
'\App\Enums\Pagination\ItemsPerPaginationEnum',
ItemsPerPaginationEnum::class,
);
if (Cache::get($cacheKey) !== $pagination) {
Cache::put($cacheKey, $pagination);
}
$query = $query->paginate($pagination);
return $query;
}
/**
* Set the bootstrap theme or light (default).
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function setTheme(Request $request): \Illuminate\Http\RedirectResponse
protected function setTheme(Request $request): RedirectResponse
{
$theme = ToolboxHelper::getValidatedEnum(
$request->theme ?: Cache::get('theme', BootstrapThemeEnum::light->value()),
'theme',
'\App\Http\Controllers\BootstrapThemeEnum',
);
if (Cache::get("theme") !== $theme) {
Cache::put("theme", $theme);
if (Cache::get('theme') !== $theme) {
Cache::put('theme', $theme);
}
return redirect()->back()->with('success', __('crud.messages.theme_updated'));
return back()->with('success', __('crud.messages.theme_updated'));
}
}

View file

@ -1,20 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class ConfirmPasswordController extends Controller
{
use ConfirmsPasswords;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -23,21 +24,21 @@ class ConfirmPasswordController extends Controller
/**
* Display the password confirmation view.
*
* @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
*/
public function showConfirmForm(): \Illuminate\View\View|\Illuminate\Http\RedirectResponse
public function showConfirmForm(): View | RedirectResponse
{
if (Auth::guard('frontend')->check()) {
return \response()->redirectToRoute('home');
}
return view('theme::auth.passwords.confirm');
/** @var view-string $view */
$view = 'theme::auth.passwords.confirm';
return view($view);
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath(): string
{

View file

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
@ -16,16 +19,13 @@ class ForgotPasswordController extends Controller
/**
* Message to user
*
* @var string
*
* @phpcs:disable Generic.Files.LineLength.TooLong
*/
private $message = 'front.messages.password_reset_email_sent';
//phpcs:enable
private string $message = 'front.messages.password_reset_email_sent';
// phpcs:enable
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -39,13 +39,14 @@ class ForgotPasswordController extends Controller
*/
public function showLinkRequestForm(): View
{
return view('theme::auth.passwords.email');
/** @var view-string $view */
$view = 'theme::auth.passwords.email';
return view($view);
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker(): PasswordBroker
{
@ -56,7 +57,7 @@ class ForgotPasswordController extends Controller
* Get the response for a successful password reset link.
* ! Dans les deux cas on ne divulgue pas si un user existe.
*
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
protected function sendResetLinkResponse()
{
@ -67,9 +68,7 @@ class ForgotPasswordController extends Controller
* Get the response for a failed password reset link.
* ! Dans les deux cas on ne divulgue pas si un user existe.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse
* @return RedirectResponse
*/
protected function sendResetLinkFailedResponse(Request $request, string $response)
{
@ -77,6 +76,7 @@ class ForgotPasswordController extends Controller
if ($response === 'passwords.user') {
return back()->with('info', trans($this->message));
}
// * Renvoyer les autres message d'erreur style throttle.
return back()
->withInput($request->only('email'))

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
@ -28,8 +30,6 @@ class LoginController extends Controller
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -38,8 +38,6 @@ class LoginController extends Controller
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
@ -53,15 +51,15 @@ class LoginController extends Controller
*/
public function showLoginForm(): View
{
return view('theme::auth.login');
/** @var view-string $view */
$view = 'theme::auth.login';
return view($view);
}
/**
* Check if the authenticated user can access the game
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Customer $user
* @return void
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
protected function authenticated(Request $request, Customer $user): void
@ -69,11 +67,11 @@ class LoginController extends Controller
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $user->api_data;
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
$countries = $settings->authorized_countries;
if (count($countries) && !in_array($crmCustomer->country->value, $countries)) {
if (count($countries) && ! in_array($crmCustomer->country->value, $countries)) {
$this->logout($request);
Session::flash('info', trans('front.messages.game_not_available_in_country'));
} else {
@ -86,8 +84,6 @@ class LoginController extends Controller
/**
* The user has logged out of the application.
*
* @param \Illuminate\Http\Request $request
* @return void
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function loggedOut(Request $request): void
@ -97,8 +93,6 @@ class LoginController extends Controller
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath(): string
{

View file

@ -1,10 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Customers\CreateAccountRequest;
use App\Models\Customer;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\Validation\Validator as ValidationValidator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Arr;
@ -14,10 +17,12 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException as IlluminateValidationException;
use Illuminate\View\View;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\ValidationException as CrmValidationException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
use RuntimeException;
class RegisterController extends Controller
{
@ -25,8 +30,6 @@ class RegisterController extends Controller
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -36,7 +39,7 @@ class RegisterController extends Controller
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
* @return StatefulGuard
*/
protected function guard()
{
@ -46,19 +49,21 @@ class RegisterController extends Controller
/**
* Show the application registration form.
*
* @return \Illuminate\View\View
* @return View
*/
public function showRegistrationForm()
{
return view('theme::auth.register');
/** @var view-string $view */
$view = 'theme::auth.register';
return view($view);
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
* @throws IlluminateValidationException If data is incorrect.
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function validator(array $data): ValidationValidator
@ -66,6 +71,7 @@ class RegisterController extends Controller
// phpcs:enable
$request = CreateAccountRequest::capture();
$request->prepareForValidation();
return Validator::make(
$request->validationData(),
$request->rules(),
@ -77,10 +83,8 @@ class RegisterController extends Controller
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\Customer
* @throws IlluminateValidationException If there are validation errors.
* @throws \RuntimeException On unhandled api Error.
* @throws RuntimeException On unhandled api Error.
*/
protected function create(array $data): Customer
{
@ -107,7 +111,7 @@ class RegisterController extends Controller
// * Enregistrement du Hashcode
/** @var \App\Models\Customer $customer */
$customer = Customer::withTrashed()->firstOrNew([
'email' => $data['email']
'email' => $data['email'],
]);
$customer->fill(Arr::only($data, $customer->getFillable()));
$customer->hashcode = $crmCustomer->hashcode;
@ -127,30 +131,28 @@ class RegisterController extends Controller
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerExists)) {
throw IlluminateValidationException::withMessages([
'error' => trans('front.messages.email_already_in_use')
'error' => trans('front.messages.email_already_in_use'),
]);
}
if ($e->getApiError()->equals(ApiError::validationFailed)) {
throw IlluminateValidationException::withMessages([
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
}
report($e);
Log::error(__CLASS__ . '::create reached end of possibilities.');
throw new \RuntimeException(__CLASS__ . '::create reached end of possibilities.');
Log::error(self::class . '::create reached end of possibilities.');
throw new RuntimeException(self::class . '::create reached end of possibilities.', $e->getCode(), $e);
} catch (CrmValidationException $e) {
throw IlluminateValidationException::withMessages([
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
} //end try
} // end try
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
public function redirectPath(): string
{
return $this->authLoggedRedirectPath();
}

View file

@ -1,20 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Contracts\Auth\StatefulGuard;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\CrmApiV4;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException as ExceptionsValidationException;
use TypeError;
class ResetPasswordController extends Controller
{
@ -22,8 +28,6 @@ class ResetPasswordController extends Controller
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -34,8 +38,6 @@ class ResetPasswordController extends Controller
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard(): StatefulGuard
{
@ -46,15 +48,15 @@ class ResetPasswordController extends Controller
* Display the password reset view for the given token.
*
* If no token is present, display the link request form.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function showResetForm(Request $request): \Illuminate\Contracts\View\View
public function showResetForm(Request $request): View
{
$token = $request->route()->parameter('token');
return view('theme::auth.passwords.reset')->with(
/** @var view-string $view */
$view = 'theme::auth.passwords.reset';
return view($view)->with(
['token' => $token, 'email' => $request->email]
);
}
@ -62,32 +64,29 @@ class ResetPasswordController extends Controller
/**
* Reset the given user's password.
*
* @param \App\Models\Customer $customer
* @param string $password
* @return void
* @throws \Illuminate\Validation\ValidationException If something goes wrong.
*/
protected function resetPassword(\App\Models\Customer $customer, string $password): void
protected function resetPassword(Customer $customer, string $password): void
{
/** @var \LDC\LDCCRM\CrmApiV4 */
$crm = app('crmAPI');
/** @var CrmApiV4 */
$crm = resolve('crmAPI');
try {
if (!($crmCustomer = $crm->getCustomer($customer->hashcode))) {
if (! ($crmCustomer = $crm->getCustomer($customer->hashcode))) {
throw ValidationException::withMessages([
'error' => trans('Changement impossible, compte inexistant.')
'error' => trans('Changement impossible, compte inexistant.'),
]);
}
$crmCustomer->password = $password;
$crmCustomer->save();
} catch (\TypeError | CrmException | ApiErrorException $e) {
} catch (TypeError | CrmException | ApiErrorException $e) {
report($e);
throw ValidationException::withMessages([
'error' => trans('front.messages.unexpected_error_try_later')
'error' => trans('front.messages.unexpected_error_try_later'),
]);
} catch (ExceptionsValidationException $e) {
throw ValidationException::withMessages([
'password' => $e->getMessage()
'password' => $e->getMessage(),
]);
}
@ -102,8 +101,6 @@ class ResetPasswordController extends Controller
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath(): string
{
@ -112,8 +109,6 @@ class ResetPasswordController extends Controller
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker(): PasswordBroker
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
@ -8,6 +10,7 @@ use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Events\Verified;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@ -23,8 +26,6 @@ class VerificationController extends Controller
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
@ -35,15 +36,15 @@ class VerificationController extends Controller
/**
* Show the email verification notice.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View
*/
public function show(Request $request): RedirectResponse|View
public function show(Request $request): RedirectResponse | View
{
/** @var view-string $view */
$view = 'theme::auth.verify';
return $request->user('frontend')->hasVerifiedEmail()
? redirect($this->redirectPath())
: view('theme::auth.verify');
: view($view);
}
/**
@ -51,22 +52,16 @@ class VerificationController extends Controller
*
* ! Overloaded trait
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException If request is invalid.
* @throws AuthorizationException If request is invalid.
*/
public function verify(Request $request): JsonResponse|RedirectResponse
public function verify(Request $request): JsonResponse | RedirectResponse
{
/** @var \App\Models\Customer */
/** @var Customer */
$user = Customer::findOrFail($request->route('id'));
if (!hash_equals((string) $request->route('id'), (string) $user->getKey())) {
throw new AuthorizationException();
}
throw_unless(hash_equals((string) $request->route('id'), (string) $user->getKey()), AuthorizationException::class);
if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
throw new AuthorizationException();
}
throw_unless(hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification())), AuthorizationException::class);
if ($user->hasVerifiedEmail()) {
return $request->wantsJson()
@ -74,7 +69,7 @@ class VerificationController extends Controller
: redirect($this->redirectPath());
}
DB::transaction(function () use ($user) {
DB::transaction(function () use ($user): void {
if ($user->markEmailAsVerified()) {
$this->setCrmCustomerAsVerified($user);
event(new Verified($user));
@ -93,8 +88,8 @@ class VerificationController extends Controller
/**
* The user has been verified.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function verified(Request $request)
@ -107,8 +102,6 @@ class VerificationController extends Controller
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath(): string
{
@ -118,9 +111,7 @@ class VerificationController extends Controller
/**
* Set Crm Customer active
*
* @param \App\Models\Customer $customer
* @return void
* @throws \Illuminate\Http\Exceptions\HttpResponseException In case of failure.
* @throws HttpResponseException In case of failure.
*/
private function setCrmCustomerAsVerified(Customer $customer): void
{
@ -137,12 +128,11 @@ class VerificationController extends Controller
if ($e->getApiError()->equals(ApiError::customerAlreadyActive)) {
// * Ignore this and continue, as it may be activated via another CRM service.
return;
} else {
report($e);
redirect($this->redirectPath())->with('error', trans(
'front.messages.unexpected_error_try_later'
))->throwResponse();
}
report($e);
redirect($this->redirectPath())->with('error', trans(
'front.messages.unexpected_error_try_later'
))->throwResponse();
}
}
}

View file

@ -1,11 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Customers\VerifyInformationsRequest;
use App\Http\Requests\Fo\Customers\SingleOptinRequest;
use App\Http\Requests\Fo\Customers\VerifyInformationsRequest;
use App\Models\Customer;
use App\Models\Participation;
use App\Repositories\Fo\CrmRepository;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
@ -19,16 +24,15 @@ use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException as CrmValidationException;
use LDC\LDCCRM\Models\SingleOptin;
use LDC\MESBR\MesBr;
use Illuminate\Http\Response;
class CustomerController extends Controller
{
/** @var \App\Repositories\Fo\CrmRepository $crmRepository */
private $crmRepository;
private readonly CrmRepository $crmRepository;
/**
* ParticipateController
*
* @return void
*/
public function __construct()
{
@ -37,26 +41,24 @@ class CustomerController extends Controller
/**
* Verify profil info form
*
* @return \Illuminate\Contracts\View\View
*/
public function profilVerify(): View
{
// ! Pas de Gate/Policies Ici volontairement, vu avec Vincent
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
$crmCustomer = $customer->api_data;
return view('theme::auth.profil-verify', compact('crmCustomer'));
/** @var view-string $view */
$view = 'theme::auth.profil-verify';
return view($view, ['crmCustomer' => $crmCustomer]);
}
/**
* Profile verify
*
* @param \App\Http\Requests\Fo\Customers\VerifyInformationsRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws IlluminateValidationException In case of any error validation or within the crm.
*/
public function profilVerifySubmit(VerifyInformationsRequest $request): RedirectResponse
@ -65,7 +67,7 @@ class CustomerController extends Controller
try {
return DB::transaction(function () use ($request) {
// * --- FILL AND SAVE CUSTOMER
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
// * Màj, on ne mets pas à jour le mdp ou l'adresse email, juste les champs pour mesBr
@ -89,86 +91,93 @@ class CustomerController extends Controller
} catch (CrmValidationException $e) {
// * Local Validation failure
throw IlluminateValidationException::withMessages([
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::validationFailed)) {
throw IlluminateValidationException::withMessages([
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
}
report($e);
Log::error(__CLASS__ . '::update reached end of possibilities.');
Log::error(self::class . '::update reached end of possibilities.');
} catch (CrmException $e) {
report($e);
Log::error(__CLASS__ . '::error occured on calling crm.');
Log::error(self::class . '::error occured on calling crm.');
throw IlluminateValidationException::withMessages([
'error' => trans('Une erreur est survenue, veuillez réssayer plus tard')
'error' => trans('Une erreur est survenue, veuillez réssayer plus tard'),
]);
} //end try
} // end try
return back()->withInput()->withErrors(trans('front.messages.unexpected_error_try_later'));
}
/**
* View all customer winnig participations
*
* @return \Illuminate\Contracts\View\View
*/
public function viewWinningParticipations(): View
{
$this->authorize('viewWonList', \App\Models\Participation::class);
$this->authorize('viewWonList', Participation::class);
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
// * Recupération des informations CRM
/** @var \LDC\LDCCRM\Models\Customer*/
/** @var \LDC\LDCCRM\Models\Customer */
$crmUser = $this->crmRepository->fetchCrmCustomer($customer);
/** @var \LDC\MESBR\MesBr */
$mesBr = app('mesBrAPI');
/** @var MesBr $mesBr */
$mesBr = resolve('mesBrAPI');
/** @var array{coupons?: list<array{quantity: int|string, printed: int|string}>} $brBasket */
$brBasket = $mesBr->getBasket($crmUser);
if (!empty($brBasket['coupons'])) {
$canPrint = (collect($brBasket['coupons'])->sum('quantity') - collect($brBasket['coupons'])->sum('printed'));
if (! empty($brBasket['coupons'])) {
$coupons = collect($brBasket['coupons']);
$canPrint = $coupons->sum('quantity') - $coupons->sum('printed');
}
$winningList = $customer->participations()
->with(['instantWin'])->where(function (Builder $q) {
->with(['instantWin'])->where(function (Builder $q): void {
$q->whereHas('instantWin');
})->get();
return view('theme::pages.participation.winning-participations', [
/** @var view-string $view */
$view = 'theme::pages.participation.winning-participations';
return view($view, [
'winningList' => $winningList,
'canPrint' => $canPrint ?? null
'canPrint' => $canPrint ?? null,
]);
}
/**
* Call the CRM to add a single optin
*
* @param \App\Http\Requests\Fo\Customers\SingleOptinRequest $request
* @return string|array|null|\Illuminate\Contracts\Translation\Translator
* @throws \LDC\LDCCRM\Exceptions\ValidationException ValidationException.
* @throws \Illuminate\Contracts\Container\BindingResolutionException BindingResolutionException.
* @throws BindingResolutionException BindingResolutionException.
*/
public function addOptIn(SingleOptinRequest $request)
public function addOptIn(SingleOptinRequest $request): Response | string
{
try {
SingleOptin::setStatus($request->email, true);
return \response(trans("front.messages.newsletter_subscription_success"), 200);
return \response(trans('front.messages.newsletter_subscription_success'), 200);
} catch (ApiErrorException $e) {
// Il y a deja un customerInfo avec optin a true pour ce customer.
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists)) {
return trans('front.messages.newsletter_email_already_subscribed');
}
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
resolve('sentry')->captureException($e);
}
return \response(trans('front.messages.unexpected_error_try_later'), 404);
} catch (CrmException $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
resolve('sentry')->captureException($e);
}
return \response(trans('front.messages.unexpected_error_try_later'), 404);
}
}

View file

@ -1,34 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr;
use LDC\MESBR\MesBr;
use RuntimeException;
class DiscountController extends Controller
{
/**
* Fetch PDF BR.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function fetchBrPdf(): \Illuminate\Http\RedirectResponse
public function fetchBrPdf(): RedirectResponse
{
/** @var \App\Models\Customer $customer */
/** @var Customer $customer */
$customer = auth('frontend')->user();
$pdf = $this->fetchBrPdfFromMesBr($customer);
if ($pdf === null) {
\back()->withErrors([
'coupon' => trans('front.messages.pdf_download_error')
'coupon' => trans('front.messages.pdf_download_error'),
])->throwResponse();
}
return redirect()->to(
sprintf(
"%s/basket/%s/print",
'%s/basket/%s/print',
config('mesbr.endpoint'),
$pdf
)
@ -38,53 +41,54 @@ class DiscountController extends Controller
/**
* Fetch coupons PDF.
*
* @param \App\Models\Customer $customer
* @return string|null
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
private function fetchBrPdfFromMesBr(Customer $customer): ?string
{
// phpcs:enable
// * Phase - 1 Recupération des informations CRM
/** @var \App\Models\Customer $customer */
/** @var Customer $customer */
$customer = auth('frontend')->user();
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->api_data;
try {
/** @var \LDC\MESBR\MesBr */
$mesBr = app('mesBrAPI');
/** @var MesBr */
$mesBr = resolve('mesBrAPI');
$result = $mesBr->getBasket($crmCustomer);
$result = \is_object($result) ? (array) $result : $result;
if (Arr::get($result, 'status') !== "OK") {
report(new \RuntimeException(
if (Arr::get($result, 'status') !== 'OK') {
report(new RuntimeException(
\sprintf(
'MESBR : Une erreur est survenue. Code : %s Message : %s',
Arr::get($result, 'code') ?? 'Undefined',
Arr::get($result, 'code') ?? 'Undefined',
Arr::get($result, 'message') ?? 'Undefined',
)
));
return null;
}
// * La réponse est conforme ?
$needed_fields = ['uuid'];
if (!Arr::has($result, $needed_fields)) {
report(new \RuntimeException(sprintf(
if (! Arr::has($result, $needed_fields)) {
report(new RuntimeException(sprintf(
'MESBR : Il manques des champs dans la réponse, attendu => %s, donné => %s',
json_encode($needed_fields),
json_encode($result),
)));
return null;
}
// * Tout est OK
return Arr::get($result, 'uuid') ?: null;
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
report($e);
// * Retenter ?
return null;
} //end try
} // end try
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
@ -7,89 +9,100 @@ use App\Models\FaqQuestion;
use App\Models\FaqTheme;
use App\Models\Range;
use App\Models\Settings;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Gate;
class HomeController extends Controller
{
/**
* Display home page
*
* @return \Illuminate\Contracts\View\View
*/
public function home(): \Illuminate\Contracts\View\View
public function home(): View
{
if (!Gate::check('fo.gameEnabled')) {
if (! Gate::check('fo.gameEnabled')) {
\session()->flash('warning', trans('front.messages.game_missing_required_elements'));
}
return view('theme::pages.home');
/** @var view-string $view */
$view = 'theme::pages.home';
return view($view);
}
/**
* Display products with game stickers
*
* @return \Illuminate\Contracts\View\View
* @phpcs:ignore Squiz.Commenting.FunctionComment.EmptyThrows
* @throws \Illuminate\Http\Exceptions\HttpResponseException
*
* @throws HttpResponseException
*/
public function showProducts(): \Illuminate\Contracts\View\View
public function showProducts(): View
{
// ? Show product list authorized ?
if (!Gate::allows('fo.showProducts')) {
if (! Gate::allows('fo.showProducts')) {
\response()->redirectToRoute('fo.homepage')->throwResponse();
}
$rangeModelList = Range::query()->with('products')->orderBy('id')->get();
return view('theme::pages.products', compact('rangeModelList'));
/** @var view-string $view */
$view = 'theme::pages.products';
return view($view, ['rangeModelList' => $rangeModelList]);
}
/**
* Display Faq page
*
* @return \Illuminate\Contracts\View\View
*/
public function showFaq(): \Illuminate\Contracts\View\View
public function showFaq(): View
{
// ? Show QA authorized ?
if (!Gate::allows('fo.showFaq')) {
if (! Gate::allows('fo.showFaq')) {
\response()->redirectToRoute('fo.homepage')->throwResponse();
}
$questionModelList = FaqQuestion::getCached();
$themeModelList = FaqTheme::getCached();
return view('theme::pages.faq', compact(
'questionModelList',
'themeModelList'
));
/** @var view-string $view */
$view = 'theme::pages.faq';
return view($view, ['questionModelList' => $questionModelList, 'themeModelList' => $themeModelList]);
}
/**
* Show the teaser page if it is activated
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function teaser(): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
public function teaser(): View | RedirectResponse
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
if (!$settings->should_display_teaser_page) {
return redirect()->route('fo.homepage');
/** @var Settings */
$settings = resolve(Settings::class);
if (! $settings->should_display_teaser_page) {
return to_route('fo.homepage');
}
return view('theme::pages.teaser');
/** @var view-string $view */
$view = 'theme::pages.teaser';
return view($view);
}
/**
* Show the end game page if it is activated
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
*/
public function endGame(): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
public function endGame(): View | RedirectResponse
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
if (!$settings->should_display_end_page) {
return redirect()->route('fo.homepage');
/** @var Settings */
$settings = resolve(Settings::class);
if (! $settings->should_display_end_page) {
return to_route('fo.homepage');
}
return view('theme::pages.end-game');
/** @var view-string $view */
$view = 'theme::pages.end-game';
return view($view);
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
@ -7,13 +9,18 @@ use App\Http\Requests\Fo\Customers\CreateAccountRequest;
use App\Http\Requests\Fo\ParticipateRequest;
use App\Lib\Discount as LibDiscount;
use App\Mail\Won\PrizeWonAdminMail;
use App\Models\Code;
use App\Models\Customer;
use App\Models\InstantWin;
use App\Models\Participation;
use App\Models\Product;
use App\Models\Settings;
use App\Notifications\Customer\HasWonDiscount;
use App\Notifications\Customer\HasWonInstantWin;
use App\Repositories\Fo\CrmRepository;
use Exception;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
@ -22,15 +29,15 @@ use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use LDC\MESBR\MesBr;
use RuntimeException;
class ParticipateController extends Controller
{
/** @var \App\Repositories\Fo\CrmRepository $crmRepository */
private $crmRepository;
private readonly CrmRepository $crmRepository;
/**
* ParticipateController
*
* @return void
*/
public function __construct()
{
@ -38,53 +45,55 @@ class ParticipateController extends Controller
$this->middleware(function (Request $request, callable $next) {
$this->authorize('fo.gameEnabled');
$this->assertUserHasEnoughInfoToUseMesBr();
return $next($request);
});
}
/**
* Display participation form
*
* @return \Illuminate\Contracts\View\View
*/
public function participate(): \Illuminate\Contracts\View\View
public function participate(): View
{
if (!Gate::check('participate', Participation::class)) {
return view('theme::pages.participation.participation-exhausted');
if (! Gate::check('participate', Participation::class)) {
/** @var view-string $view */
$view = 'theme::pages.participation.participation-exhausted';
return view($view);
}
return view('theme::pages.participation.participate');
/** @var view-string $view */
$view = 'theme::pages.participation.participate';
return view($view);
}
/**
* Participate Method
*
* @param \App\Http\Requests\Fo\ParticipateRequest $request
* @return \Illuminate\Contracts\View\View
*/
public function participateSubmit(ParticipateRequest $request): \Illuminate\Contracts\View\View
public function participateSubmit(ParticipateRequest $request): View
{
return DB::transaction(function () use ($request) {
// ! On verrouille le ou les instants gagnant
/** @var \App\Models\InstantWin|null */
/** @var InstantWin|null */
$instantWin = InstantWin::query()->lockForUpdate()
->where('available_at', '<', now())
->whereDoesntHave('participation')
->get()->sortBy('available_at')->first();
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Code|null */
/** @var Code|null */
$code = $request->post('unique_code');
/** @var \App\Models\Product|null */
/** @var Product|null */
$product = $request->post('product');
// * Make new participation
$participation = Participation::makeNew(
app('clientIp'),
resolve('clientIp'),
$customer,
$code,
$product
@ -92,19 +101,21 @@ class ParticipateController extends Controller
// * Si l'utilisateur actuel à déjà gagné un gros lot, pas d'instant gagnant.
// ? As-t-on un instant gagnant ?
if (Participation::customerAlreadyHasAnInstantWin($customer) or !$instantWin) {
if (Participation::customerAlreadyHasAnInstantWin($customer) || ! $instantWin) {
// * --- DISCOUNT WIN ---
// ? Les BR sont activés ?
if (!$settings->discounts_activated) {
if (! $settings->discounts_activated) {
// * On enregistre la participation du joueur.
$participation->save();
// * Pas de coupons disponibles, le joueur à perdu
return view('theme::pages.participation.loose');
/** @var view-string $view Pas de coupons disponibles, le joueur à perdu. */
$view = 'theme::pages.participation.loose';
return view($view);
}
// * Le joueur à gagné un coupon.
/** @var \App\Lib\Discount Coupon aléatoire en fonction du ratio de chaque coupon */
/** @var \App\Lib\Discount Coupon aléatoire en fonction du ratio de chaque coupon */
$discount = Arr::get(
$settings->web_coupons,
$this->getRandomWeightedElementIndex($settings->web_coupons)
@ -124,8 +135,11 @@ class ParticipateController extends Controller
$crmCustomer->firstname
));
return view('theme::pages.participation.won', compact('discount'));
} //end if
/** @var view-string $view */
$view = 'theme::pages.participation.won';
return view($view, ['discount' => $discount]);
} // end if
// * --- INSTANT WIN ---
// * On enregistre la participation du joueur.
@ -148,43 +162,42 @@ class ParticipateController extends Controller
$participation->created_at,
$instantWin->prize->label,
$crmCustomer,
optional($code)->code_unique,
optional($product)->ean_code
$code?->code_unique,
$product?->ean_code
));
return view('theme::pages.participation.won', [
'prizeModel' => $instantWin->prize,
/** @var view-string $view */
$view = 'theme::pages.participation.won';
return view($view, [
'prizeModel' => $instantWin->prize,
'participationModel' => $participation,
'crmCustomer' => $crmCustomer
'crmCustomer' => $crmCustomer,
]);
});
}
// * --- PRIVATE METHODS ---
/**
* Try Call CRM + MesBr to setup discount
*
* @param \App\Models\Customer $customer
* @param LibDiscount $discount
* @return void
* @throws \Illuminate\Http\Exceptions\HttpResponseException If Any error occurs.
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
* @throws HttpResponseException If Any error occurs.
*
* @phpcs:disable Generic.Metrics.CyclomaticComplexity
*/
private function setDiscountAsWon(Customer $customer, LibDiscount $discount): void
{
// phpcs:enable
// * Phase - 1 Recupération des informations CRM
/** @var \LDC\LDCCRM\Models\Customer*/
/** @var \LDC\LDCCRM\Models\Customer */
$crmUser = $this->crmRepository->fetchCrmCustomer($customer);
// * Phase - 2 Mise à jour du coupon dans MesBr
try {
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
/** @var \LDC\MESBR\MesBr */
$mesBr = app('mesBrAPI');
/** @var MesBr */
$mesBr = resolve('mesBrAPI');
// * MaJ Customer
/** @var string */
@ -195,23 +208,24 @@ class ParticipateController extends Controller
*
* Id est necessaire pour update panier..
*/
/** @var object|null $result */
$result = $mesBr->updateCustomer($crmUser->hashcode, $crmUser->email, $crmUser->country->label());
if (optional($result)->status !== "OK") {
throw new \RuntimeException(
if ($result?->status !== 'OK') {
throw new RuntimeException(
\sprintf(
'MESBR : Une erreur est survenue. Code : %s Message : %s',
$result->code ?? 'Undefined',
$result->code ?? 'Undefined',
$result->message ?? 'Undefined'
)
);
}
/** @var object|null */
$crm_customer = optional($result)->customer;
if (empty(optional($crm_customer)->hashcode)) {
throw new \RuntimeException(
'MESBR : Une erreur est survenue. Update Customer n\'as pas retourné le customer hashcode'
);
}
/** @var object|null $crm_customer */
$crm_customer = $result->customer ?? null;
throw_if(
empty($crm_customer->hashcode),
RuntimeException::class,
'MESBR : Une erreur est survenue. Update Customer n\'as pas retourné le customer hashcode',
);
return $crm_customer->hashcode;
}, 100);
@ -222,11 +236,11 @@ class ParticipateController extends Controller
strval($discount->web_coupon_identifiant)
);
if (Arr::get($result, 'status') === "OK") {
if (Arr::get($result, 'status') === 'OK') {
// * La réponse est conforme ?
$needed_fields = ['ref'];
if (!Arr::has($result, $needed_fields)) {
throw new \RuntimeException(sprintf(
if (! Arr::has($result, $needed_fields)) {
throw new RuntimeException(sprintf(
'MESBR : Il manques des champs dans la réponse, attendu => %s, donné => %s',
json_encode($needed_fields),
json_encode($result),
@ -237,20 +251,18 @@ class ParticipateController extends Controller
return;
}
throw new \RuntimeException(
throw new RuntimeException(
\sprintf(
'MESBR : Une erreur est survenue. Code : %s Message : %s',
$result["code"] ?? 'Undefined',
$result["message"] ?? 'Undefined'
$result['code'] ?? 'Undefined',
$result['message'] ?? 'Undefined'
)
);
} catch (\RuntimeException $e) {
} catch (RuntimeException $e) {
report($e);
// * Retenter ?
back()->with('error', trans('front.messages.unexpected_error_try_later'))->throwResponse();
} //end try
throw new \Error('Should not reach this point');
} // end try
}
/**
@ -258,35 +270,34 @@ class ParticipateController extends Controller
* Or redirect him to the fill form
* ! Also validates its birth date (because beeing an adult is required to participate)
*
* @return void
* @throws \Illuminate\Http\Exceptions\HttpResponseException If customer data doest not validate
* or and error occurs.
* @throws HttpResponseException If customer data doest not validate
* or and error occurs.
*/
private function assertUserHasEnoughInfoToUseMesBr(): void
{
// * Valider que le client dispose des donnés requises pour MesBr
try {
/** @var \App\Models\Customer */
/** @var Customer */
$authUser = auth('frontend')->user();
$crmCustomer = $this->crmRepository->fetchCrmCustomer($authUser);
// * On valide les données selon les critères du site
$rules = collect((new CreateAccountRequest())->rules());
$rules = collect(new CreateAccountRequest()->rules());
Validator::make(
[
'birth_date' => $crmCustomer->birth_date,
'address' => $crmCustomer->address,
'birth_date' => $crmCustomer->birth_date,
'address' => $crmCustomer->address,
'postal_code' => $crmCustomer->postcode,
'country' => $crmCustomer->country->value,
'city' => $crmCustomer->city
'country' => $crmCustomer->country->value,
'city' => $crmCustomer->city,
],
[
'birth_date' => $rules->get('birth_date'),
'address' => $rules->get('address'),
'birth_date' => $rules->get('birth_date'),
'address' => $rules->get('address'),
'postal_code' => $rules->get('postal_code'),
'country' => $rules->get('country'),
'city' => $rules->get('city')
'country' => $rules->get('country'),
'city' => $rules->get('city'),
]
)->validate();
Session::remove('needCrmInfoUpdate');
@ -295,13 +306,13 @@ class ParticipateController extends Controller
->with('warning', trans(
'front.messages.profile_incomplete_cannot_play'
))->throwResponse();
} catch (\Exception $e) {
} catch (Exception $e) {
report($e);
// * Retenter ?
response()->redirectToRoute('fo.homepage')
->with('error', trans('front.messages.unexpected_error_try_later'))
->throwResponse();
} //end try
} // end try
}
// * --- END PRIVATE METHODS ---

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
@ -11,47 +13,44 @@ class StaticPageController extends Controller
{
/**
* Show the rules view
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function reglement()
public function reglement(): View
{
$staticPageModel = StaticPage::query()->where('type', StaticPageType::rules)->firstOrFail();
return $this->viewStaticPage($staticPageModel);
}
/**
* Show the privacy view
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function charte()
public function charte(): View
{
$staticPageModel = StaticPage::query()->where('type', StaticPageType::confidentiality)->firstOrFail();
return $this->viewStaticPage($staticPageModel);
}
/**
* Show the legals view
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function legals()
public function legals(): View
{
$staticPageModel = StaticPage::query()->where('type', StaticPageType::legals)->firstOrFail();
return $this->viewStaticPage($staticPageModel);
}
/**
* Display a static page
*
* @param \App\Models\StaticPage $staticPageModel
* @return \Illuminate\Contracts\View\View
*/
private function viewStaticPage(StaticPage $staticPageModel): View
{
return view('theme::pages.static_page', [
'staticPageModel' => $staticPageModel
/** @var view-string $view */
$view = 'theme::pages.static_page';
return view($view, [
'staticPageModel' => $staticPageModel,
]);
}
}

View file

@ -1,10 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\SubmitWinningProofRequest;
use App\Mail\Won\WinningProofAdminMail;
use App\Models\Customer;
use App\Models\Participation;
use App\Models\Settings;
use App\Notifications\Customer\HasSubmitedWinProof;
@ -19,42 +22,42 @@ use Illuminate\Validation\ValidationException as IlluminateValidationException;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\ValidationException as CrmValidationException;
use RuntimeException;
class WinningProofController extends Controller
{
/**
* WinningProofController
*
* @return void
*/
public function __construct()
{
$this->middleware(function (Request $request, callable $next) {
$this->authorize('fo.gameEnabled');
$this->authorize('submitProof', $request->route('participation'));
return $next($request);
});
}
/**
* Display winning proof form
*
* @param \App\Models\Participation $participation
* @return \Illuminate\Contracts\View\View
*/
public function displayForm(Participation $participation): View
{
$participation->load('instantWin.prize');
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
$crmCustomer = $customer->api_data;
return view('theme::pages.participation.won-form-only', [
/** @var view-string $view */
$view = 'theme::pages.participation.won-form-only';
return view($view, [
'participationModel' => $participation,
'prizeModel' => $participation->instantWin->prize,
'crmCustomer' => $crmCustomer
'prizeModel' => $participation->instantWin->prize,
'crmCustomer' => $crmCustomer,
]);
}
@ -64,10 +67,8 @@ class WinningProofController extends Controller
* - 2 Save les pictures de preuve de gains
* - 3 Envoi un mail au abonnés (settings notification_emails) avec les infos client
*
* @param \App\Http\Requests\Fo\SubmitWinningProofRequest $request
* @param \App\Models\Participation $participation
* @return \Illuminate\Http\RedirectResponse
* @throws IlluminateValidationException In case of failure.
*
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function displayFormSubmit(
@ -80,14 +81,14 @@ class WinningProofController extends Controller
$participation->load([
'instantWin.prize',
'code',
'product'
'product',
]);
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
// * --- PHASE 1 Update Crm Customer ---
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
$crmCustomer = $customer->api_data;
@ -118,7 +119,7 @@ class WinningProofController extends Controller
$participation->fill(Arr::only($request->validated(), [
'barcode',
'ticket',
'sticker'
'sticker',
]));
$participation->save();
@ -130,8 +131,8 @@ class WinningProofController extends Controller
$participation->created_at,
$participation->instantWin->prize->label,
$crmCustomer,
optional($participation->code)->code_unique,
optional($participation->product)->ean_code,
$participation->code?->code_unique,
$participation->product?->ean_code,
$participation->sticker,
$participation->ticket,
$participation->barcode,
@ -145,16 +146,16 @@ class WinningProofController extends Controller
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::validationFailed)) {
throw IlluminateValidationException::withMessages([
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
}
report($e);
Log::error(__CLASS__ . '::create reached end of possibilities.');
throw new \RuntimeException(__CLASS__ . '::create reached end of possibilities.');
Log::error(self::class . '::create reached end of possibilities.');
throw new RuntimeException(self::class . '::create reached end of possibilities.', $e->getCode(), $e);
} catch (CrmValidationException $e) {
throw IlluminateValidationException::withMessages([
'error' => $e->getMessage()
'error' => $e->getMessage(),
]);
} //end try
} // end try
}
}

View file

@ -1,8 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Http;
use App\Http\Middleware\Authenticate;
use App\Http\Middleware\CheckGameStatus;
use App\Http\Middleware\CheckThemeVersion;
use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\HtmlSanitizer;
use App\Http\Middleware\PreventRequestsDuringMaintenance;
use App\Http\Middleware\RedirectIfAuthenticated;
use App\Http\Middleware\SecureHeaders;
use App\Http\Middleware\TrimStrings;
use App\Http\Middleware\TrustHosts;
use App\Http\Middleware\TrustProxies;
use App\Http\Middleware\ValidateSignature;
use App\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Auth\Middleware\EnsureEmailIsVerified;
use Illuminate\Auth\Middleware\RequirePassword;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Http\Middleware\HandleCors;
use Illuminate\Http\Middleware\SetCacheHeaders;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class Kernel extends HttpKernel
{
@ -13,14 +42,15 @@ class Kernel extends HttpKernel
*
* @var array<int, string>
*/
#[\Override]
protected $middleware = [
\App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
TrustHosts::class,
TrustProxies::class,
HandleCors::class,
PreventRequestsDuringMaintenance::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
];
/**
@ -28,22 +58,23 @@ class Kernel extends HttpKernel
*
* @var array<string, array<int, string>>
*/
#[\Override]
protected $middlewareGroups = [
'web' => [
'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
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
SecureHeaders::class,
CheckThemeVersion::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
ThrottleRequests::class . ':api',
SubstituteBindings::class,
],
];
@ -54,18 +85,19 @@ 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,
#[\Override]
protected $middlewareAliases = [
'auth' => Authenticate::class,
'auth.basic' => AuthenticateWithBasicAuth::class,
'auth.session' => AuthenticateSession::class,
'cache.headers' => SetCacheHeaders::class,
'can' => Authorize::class,
'guest' => RedirectIfAuthenticated::class,
'password.confirm' => RequirePassword::class,
'check-game-status' => CheckGameStatus::class,
'signed' => ValidateSignature::class,
'throttle' => ThrottleRequests::class,
'verified' => EnsureEmailIsVerified::class,
'htmlsanitize' => HtmlSanitizer::class,
];
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
@ -10,27 +12,25 @@ class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo(Request $request): ?string
{
$logins = [
'backend' => route('bo.login')
'backend' => route('bo.login'),
];
if (!$request->expectsJson()) {
if (! $request->expectsJson()) {
// * Notify must login
Session::flash('warning', trans('Veuillez vous connecter pour continuer'));
foreach ($request->route()->gatherMiddleware() as $middleware) {
if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) {
if (\is_string($middleware) && str_contains($middleware, 'auth:')) {
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
if ($route) {
return $route;
}
}
}
return route('fo.login');
}

View file

@ -1,9 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use App\Models\Settings;
use Closure;
use Illuminate\Http\Request;
use RuntimeException;
class CheckGameStatus
{
@ -12,21 +16,20 @@ class CheckGameStatus
*
* * Check with game settings which page should be shown and redirect if needed.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
* @throws \RuntimeException If tha game has an unhandled state.
*
* @throws RuntimeException If tha game has an unhandled state.
*/
public function handle(Request $request, \Closure $next)
public function handle(Request $request, Closure $next)
{
/** @var \App\Models\Settings $settings */
$settings = app(Settings::class);
/** @var Settings $settings */
$settings = resolve(Settings::class);
return match (true) {
// * End page
$settings->should_display_end_page => redirect()->route('fo.end'),
$settings->should_display_end_page => to_route('fo.end'),
// * Teaser
$settings->should_display_teaser_page => redirect()->route('fo.teaser'),
$settings->should_display_teaser_page => to_route('fo.teaser'),
// * Game is ended
$settings->game_has_ended => redirect($settings->redirect_close),
// * Game has started

View file

@ -1,12 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
@ -15,27 +18,22 @@ class CheckThemeVersion
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request):(\Symfony\Component\HttpFoundation\Response) $next
* @return \Symfony\Component\HttpFoundation\Response
* @param Closure(Request):Response $next
*
* @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint
*/
public function handle(Request $request, \Closure $next): Response
public function handle(Request $request, Closure $next): Response
{
// phpcs:enable
if (!app()->runningInConsole()) {
$appInfos = Cache::remember('composer', 84600, function () {
return json_decode(File::get(\app_path('../composer.json')));
});
if (! app()->runningInConsole()) {
$appInfos = Cache::remember('composer', 84600, fn (): mixed => json_decode(File::get(\app_path('../composer.json'))));
// * Assert theme version does not diverge more than minor
$appVer = Str::of($appInfos->version)->explode('.');
$themeVer = Str::of(config('theme.version'))->explode('.');
if (
// * Major check
Arr::get($appVer, 0) !== Arr::get($themeVer, 0) or
// * Patch check
Arr::get($appVer, 1) !== Arr::get($themeVer, 1)
Arr::get($appVer, 0) !== Arr::get($themeVer, 0) || Arr::get($appVer, 1) !== Arr::get($themeVer, 1)
) {
Session::flash('warning', trans(
'Ce thème n\'a semble t-il pas été vérifié pour cette version du moteur'

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
@ -11,5 +13,6 @@ class EncryptCookies extends Middleware
*
* @var array<int, string>
*/
#[\Override]
protected $except = [];
}

View file

@ -1,8 +1,11 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@ -10,19 +13,14 @@ class HtmlSanitizer
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|array ...$params
* @return \Symfony\Component\HttpFoundation\Response
*/
public function handle(Request $request, \Closure $next, string|array ...$params): Response
public function handle(Request $request, Closure $next, string | array ...$params): Response
{
$inputs = $request->all();
/** @var callable|null */
$cleanInput = null;
$cleanInput = function (
null|string|array &$input,
null | string | array &$input,
string $name,
?string $rootName = null
) use (
@ -33,14 +31,14 @@ class HtmlSanitizer
if (is_array($input)) {
array_walk_recursive($input, $cleanInput, $name);
}
if (!is_string($input)) {
if (! is_string($input)) {
return;
}
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
if (in_array($name, $params) || in_array($rootName ?? $name, $params)) {
// ? Is a json input ?
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
// ! Decode each json field recursive.
\array_walk_recursive($isJson, function (string|array &$item) {
\array_walk_recursive($isJson, function (string | array &$item): void {
if (\is_string($item)) {
$item = clean($item);
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
@ -56,6 +54,7 @@ class HtmlSanitizer
};
\array_walk($inputs, $cleanInput);
$request->merge($inputs);
return $next($request);
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
@ -11,5 +13,6 @@ class PreventRequestsDuringMaintenance extends Middleware
*
* @var array<int, string>
*/
#[\Override]
protected $except = [];
}

View file

@ -1,8 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use RuntimeException;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
@ -10,30 +14,31 @@ 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
* @return \Symfony\Component\HttpFoundation\Response
* @throws \RuntimeException If guard is unhandled.
* @param Closure(Request):Response $next
* @param string|null ...$guards
*
* @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, ...$guards): Response
{
// phpcs:enable phpcs:disable
$guards = empty($guards) ? [null] : $guards;
$guards = $guards === [] ? [null] : $guards;
foreach ($guards as $guard) {
if (auth($guard)->check()) {
switch ($guard) {
case 'frontend':
return \redirect()->route('fo.homepage');
return to_route('fo.homepage');
case 'backend':
if (!$request->routeIs('bo.*')) {
if (! $request->routeIs('bo.*')) {
continue 2;
}
return \redirect()->route('bo.dashboard');
return to_route('bo.dashboard');
default:
throw new \RuntimeException("Unhandled guard `{$guard}` .");
throw new RuntimeException("Unhandled guard `{$guard}` .");
}
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
@ -11,7 +13,7 @@ use Symfony\Component\HttpFoundation\Response;
class SecureHeaders
{
private $unwantedHeaderList = [
private array $unwantedHeaderList = [
'X-Powered-By',
'Server',
];
@ -47,7 +49,7 @@ class SecureHeaders
"connect-src 'self' blob: " . implode(' ', array_filter([
$viteDevSocket,
$viteDevOrigin,
'https://yl.s-web.fr'
'https://yl.s-web.fr',
])) . ';',
"img-src 'self' data: blob: " . implode(' ', array_filter([
$viteDevOrigin,
@ -56,11 +58,11 @@ class SecureHeaders
"frame-src 'self' " . implode(' ', [
'https://www.google.com/recaptcha/',
'https://recaptcha.google.com/recaptcha/',
'https://www.google.com/maps/'
'https://www.google.com/maps/',
]) . ';',
"font-src 'self' data: " . implode(' ', array_filter([
$viteDevOrigin,
'https://fonts.gstatic.com' // <-- Point-virgule retiré ici
'https://fonts.gstatic.com',
])) . ';',
"style-src 'self' 'unsafe-inline' " . implode(' ', array_filter([
'https://cdnjs.cloudflare.com',
@ -76,7 +78,7 @@ class SecureHeaders
'https://cdnjs.cloudflare.com',
"$scheme://www.google-analytics.com",
$viteDevOrigin,
])) . ';'
])) . ';',
];
$response->headers->set(

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
@ -11,6 +13,7 @@ class TrimStrings extends Middleware
*
* @var array<int, string>
*/
#[\Override]
protected $except = [
'current_password',
'password',

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
@ -12,15 +14,17 @@ class TrustProxies extends Middleware
*
* @var array<int, string>|string|null
*/
#[\Override]
protected $proxies = '*';
/**
* The headers that should be used to detect proxies.
*
* @var integer
* @var int
*/
#[\Override]
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
@ -11,7 +13,8 @@ class VerifyCsrfToken extends Middleware
*
* @var array<int, string>
*/
#[\Override]
protected $except = [
'/csp-report'
'/csp-report',
];
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\BannedIps;
use App\Models\BannedIp;
@ -11,7 +13,7 @@ class StoreBannedIpRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -20,10 +22,8 @@ class StoreBannedIpRequest extends FormRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'ip' => 'required|ip|unique:banned_ips,ip',
@ -32,8 +32,6 @@ class StoreBannedIpRequest extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{

View file

@ -1,7 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\BannedIps;
use App\Models\BannedIp;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
@ -10,7 +13,7 @@ class UpdateBannedIpRequest extends StoreBannedIpRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -19,18 +22,17 @@ class UpdateBannedIpRequest extends StoreBannedIpRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
/** @var \App\Models\BannedIp */
/** @var BannedIp */
$ipToBanModel = $this->route('banned_ip');
return [
'ip' => [
'required', 'ip',
Rule::unique('banned_ips', 'ip')
->ignore($ipToBanModel->ip, 'ip')
->ignore($ipToBanModel->ip, 'ip'),
],
];
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Codes;
use App\Models\Code;
@ -11,7 +13,7 @@ class CodeSearchRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -26,7 +28,7 @@ class CodeSearchRequest extends FormRequest
public function rules(): array
{
return [
'code' => 'required|string|min:1|max:25'
'code' => 'required|string|min:1|max:25',
];
}
@ -38,7 +40,7 @@ class CodeSearchRequest extends FormRequest
public function attributes(): array
{
return [
'code' => trans('code')
'code' => trans('code'),
];
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Codes;
use App\Models\Code;
@ -10,31 +12,28 @@ class ExportCodesRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('export', Code::class) and $this->json();
return Gate::check('export', Code::class) && $this->json();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$codesAvailableToExport = Code::query()->where('code_exported', false);
/** @var integer|null */
$lastExportedId = optional(Code::query()->where('code_exported', true)
->orderBy('id', 'desc')->first())->id ?? null;
/** @var int|null $lastExportedId */
$lastExportedId = Code::query()->where('code_exported', true)->max('id');
if ($lastExportedId) {
$codesAvailableToExport = $codesAvailableToExport
->where('id', '>', $lastExportedId);
$codesAvailableToExport->where('id', '>', $lastExportedId);
}
$this->merge([
'available_export' => $codesAvailableToExport->count()
'available_export' => $codesAvailableToExport->count(),
]);
}
@ -46,10 +45,10 @@ class ExportCodesRequest extends FormRequest
public function rules(): array
{
return [
'nb_code_tot' => 'required|integer|min:1|lte:available_export',
'nb_code_fic' => 'required|integer|min:1',
'nb_code_ligne' => 'required|integer|min:1',
'available_export' => 'required|integer'
'nb_code_tot' => 'required|integer|min:1|lte:available_export',
'nb_code_fic' => 'required|integer|min:1',
'nb_code_ligne' => 'required|integer|min:1',
'available_export' => 'required|integer',
];
}
@ -61,10 +60,10 @@ class ExportCodesRequest extends FormRequest
public function attributes(): array
{
return [
'nb_code_fic' => trans('nombre total de codes à exporter'),
'nb_code_tot' => trans('nombre de codes par fichier'),
'nb_code_ligne' => trans('nombre de codes par ligne'),
'available_export' => trans('nombre de codes restant à exporté(s)')
'nb_code_fic' => trans('nombre total de codes à exporter'),
'nb_code_tot' => trans('nombre de codes par fichier'),
'nb_code_ligne' => trans('nombre de codes par ligne'),
'available_export' => trans('nombre de codes restant à exporté(s)'),
];
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Codes;
use App\Models\Code;
@ -10,12 +12,10 @@ class GenerateCodesRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('generate', Code::class) and $this->json();
return Gate::check('generate', Code::class) && $this->json();
}
/**
@ -26,7 +26,7 @@ class GenerateCodesRequest extends FormRequest
public function rules(): array
{
return [
'quantity' => 'required|integer|min:1'
'quantity' => 'required|integer|min:1',
];
}
@ -38,7 +38,7 @@ class GenerateCodesRequest extends FormRequest
public function attributes(): array
{
return [
'quantity' => trans('validation.custom.quantity')
'quantity' => trans('validation.custom.quantity'),
];
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\FaqQuestions;
use App\Models\FaqQuestion;
@ -11,7 +13,7 @@ class StoreFaqQuestionRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -20,28 +22,24 @@ class StoreFaqQuestionRequest extends FormRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'question' => 'required|string|min:3|max:255',
'answer' => 'required|string|min:3',
'answer' => 'required|string|min:3',
'theme_id' => 'required|exists:faq_themes,id',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'question' => trans('validation.custom.question'),
'answer' => trans('validation.custom.answer'),
'answer' => trans('validation.custom.answer'),
'theme_id' => trans_choice('models.classes.faq-themes', 1),
];
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\FaqQuestions;
use Illuminate\Support\Facades\Gate;
@ -9,7 +11,7 @@ class UpdateFaqQuestionRequest extends StoreFaqQuestionRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\FaqThemes;
use App\Models\FaqTheme;
@ -11,7 +13,7 @@ class StoreFaqThemeRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -20,20 +22,16 @@ class StoreFaqThemeRequest extends FormRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'name' => 'required|string|min:3|max:255'
'name' => 'required|string|min:3|max:255',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\FaqThemes;
use Illuminate\Support\Facades\Gate;
@ -9,7 +11,7 @@ class UpdateFaqThemeRequest extends StoreFaqThemeRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Prizes;
use App\Models\Prize;
@ -14,7 +16,7 @@ class StorePrizeRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -23,37 +25,34 @@ class StorePrizeRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'pdf_winning_proof' => $this->input('pdf_winning_proof')
'pdf_winning_proof' => $this->input('pdf_winning_proof'),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'label' => 'required|string|min:3|max:255',
'picture_alt' => 'required|string|between:3,255',
'picture_title' => 'required|string|between:3,255',
'arg_title' => 'required|string|min:3|max:255',
'arg_subtitle' => 'nullable|string|min:3|max:255',
'arg_content' => 'required|string|min:15',
'quantity' => 'required|numeric|min:1',
'label' => 'required|string|min:3|max:255',
'picture_alt' => 'required|string|between:3,255',
'picture_title' => 'required|string|between:3,255',
'arg_title' => 'required|string|min:3|max:255',
'arg_subtitle' => 'nullable|string|min:3|max:255',
'arg_content' => 'required|string|min:15',
'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');
return \array_merge(
$rules,
$this->pictureRules('picture', $min_width, $min_height, $max_width, $max_height),
@ -62,20 +61,18 @@ class StorePrizeRequest extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
*/
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

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Prizes;
use Illuminate\Support\Facades\Gate;
@ -9,7 +11,7 @@ class UpdatePrizeRequest extends StorePrizeRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Products;
use App\Models\Product;
@ -15,7 +17,7 @@ class StoreProductRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -34,13 +36,11 @@ class StoreProductRequest extends FormRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'name' => 'required|string|min:3|max:255',
'name' => 'required|string|min:3|max:255',
'ean_code' => [
'required',
'unique:products,ean_code',
@ -48,10 +48,11 @@ class StoreProductRequest extends FormRequest
],
'picture_alt' => 'nullable|required_with:picture|string|between:3,255',
'picture_title' => 'nullable|required_with:picture|string|between:3,255',
'range_id' => 'exists:ranges,id'
'range_id' => 'exists:ranges,id',
];
$width = config('theme.models.products.picture_max_width');
$height = config('theme.models.products.picture_max_height');
return \array_merge(
$rules,
$this->pictureRules('picture', $width, $height, $width, $height, true),
@ -60,28 +61,26 @@ class StoreProductRequest extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
*/
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', [
'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'),
'picture' => trans('validation.attributes.image'),
'ean_code' => trans('validation.custom.ean_code'),
'range_id' => trans('models.classes.range'),
];

View file

@ -1,7 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Products;
use App\Models\Product;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
@ -10,7 +13,7 @@ class UpdateProductRequest extends StoreProductRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -19,13 +22,12 @@ class UpdateProductRequest extends StoreProductRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
/** @var \App\Models\Product */
/** @var Product */
$productModel = $this->route('product');
return array_merge(parent::rules(), [
'ean_code' => [
'required',

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Ranges;
use App\Models\Range;
@ -14,7 +16,7 @@ class StoreRangeRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -33,10 +35,8 @@ class StoreRangeRequest extends FormRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
$rules = [
'name' => 'required|string|min:2|max:255',
@ -45,6 +45,7 @@ class StoreRangeRequest extends FormRequest
];
$width = config('theme.models.ranges.picture_max_width');
$height = config('theme.models.ranges.picture_max_height');
return \array_merge(
$rules,
$this->pictureRules('picture', $width, $height, $width, $height, false),

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Ranges;
use Illuminate\Support\Facades\Gate;
@ -9,7 +11,7 @@ class UpdateRangeRequest extends StoreRangeRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Settings;
use App\Enums\CodeType;
@ -8,15 +10,13 @@ use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use LDC\LDCCRM\Enums\Country;
use Illuminate\Validation\Rules\Enum;
use LDC\LDCCRM\Enums\Country;
class UpdateSettingsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
@ -25,8 +25,6 @@ class UpdateSettingsRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
@ -54,48 +52,46 @@ class UpdateSettingsRequest extends FormRequest
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'name' => 'required|string|min:3|max:255',
'company' => 'required|string|min:3|max:255',
'start_date' => 'required|date',
'end_date' => 'required|date',
'nb_participation_player' => 'required|numeric|min:0',
'nb_participation_ip' => 'required|numeric|min:0',
'nb_unique_code_use' => 'required|numeric|min:0',
'code_type' => ['required', new Enum(CodeType::class)],
'activated_teaser' => 'required|boolean',
'activated_close_page' => 'required|boolean',
'close_page_active_date' => [
'name' => 'required|string|min:3|max:255',
'company' => 'required|string|min:3|max:255',
'start_date' => 'required|date',
'end_date' => 'required|date',
'nb_participation_player' => 'required|numeric|min:0',
'nb_participation_ip' => 'required|numeric|min:0',
'nb_unique_code_use' => 'required|numeric|min:0',
'code_type' => ['required', new Enum(CodeType::class)],
'activated_teaser' => 'required|boolean',
'activated_close_page' => 'required|boolean',
'close_page_active_date' => [
'nullable',
'required_if:activated_close_page,true,1',
'date',
'after_or_equal:end_date'
'after_or_equal:end_date',
],
'redirect_close' => 'required|url:http,https',
'notify_prize_sent' => 'required|boolean',
'discounts_activated' => 'required|boolean',
'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',
'redirect_close' => 'required|url:http,https',
'notify_prize_sent' => 'required|boolean',
'discounts_activated' => 'required|boolean',
'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) {
if ($this->post('discounts_activated') and !collect($value)->filter()->count()) {
'present', 'array', function ($attribute, $value, $fail): void {
if ($this->post('discounts_activated') && ! collect($value)->filter()->count()) {
$fail(trans('validation.min.array', [
'attribute' => Arr::get($this->attributes(), $attribute),
'min' => 1
'min' => 1,
]));
}
if (collect($value)->sum('percentage') > 100) {
$fail(trans('validation.max.numeric', [
'attribute' => Arr::get($this->attributes(), $attribute),
'max' => 100
'max' => 100,
]));
}
}
},
],
'web_coupons.*' => 'array',
'web_coupons.*.web_coupon_identifiant' => 'required|digits:13',
@ -103,7 +99,7 @@ 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, ?string $value, callable $fail): void {
ToolboxHelper::validatePicture(
$attribute,
$value,
@ -112,17 +108,17 @@ class UpdateSettingsRequest extends FormRequest
'maxHeight' => 409,
'maxWith' => 920,
'minHeight' => 409,
'minWith' => 920
'minWith' => 920,
]
);
}
},
],
'notification_emails' => 'required|array|min:1',
'notification_emails.*' => 'email:rfc,spoof,dns',
'authorized_countries' => 'sometimes|array|max:' . count(Country::getValidCountries()),
'authorized_countries.*' => [
'sometimes',
Rule::in(Country::getValidCountries())
Rule::in(Country::getValidCountries()),
],
'winning_proof' => 'required|boolean',
@ -131,8 +127,6 @@ class UpdateSettingsRequest extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{

View file

@ -1,7 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\StaticPages;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
@ -11,7 +14,7 @@ class UpdateStaticPageRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -20,20 +23,18 @@ class UpdateStaticPageRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'content' => (string) Str::of($this->input('content'))
'content' => (string) Str::of($this->input('content')),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
@ -47,8 +48,6 @@ class UpdateStaticPageRequest extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo;
use Illuminate\Foundation\Http\FormRequest;
@ -8,8 +10,6 @@ class UpdateChangeOrderRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
@ -18,20 +18,16 @@ class UpdateChangeOrderRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
$this->merge([
'direction' => $this->route('direction') === 'up' ? true : false
'direction' => $this->route('direction') === 'up',
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Users;
use App\Enums\Users\RoleEnum;
@ -14,7 +16,7 @@ class StoreUserRequest extends FormRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -29,48 +31,44 @@ class StoreUserRequest extends FormRequest
protected function prepareForValidation()
{
$this->merge([
'published' => $this->boolean('published')
'published' => $this->boolean('published'),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'password' => [
'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()
->uncompromised(),
],
'role' => ['required', new Enum(RoleEnum::class)],
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'password' => trans('validation.attributes.password'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication')
'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'),
];
}
}

View file

@ -1,7 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Bo\Users;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
@ -11,7 +14,7 @@ class UpdateUserRequest extends StoreUserRequest
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
* @return bool
*/
public function authorize()
{
@ -25,33 +28,32 @@ class UpdateUserRequest extends StoreUserRequest
*/
protected function prepareForValidation()
{
/** @var \App\Models\User */
/** @var User */
$userModel = $this->route('user');
$this->merge([
'published' => $this->boolean('published'),
'password' => $this->input(
'password' => $this->input(
'password',
) ?? $userModel->password,
'password_confirmation' => $this->input(
'password_confirmation'
) ?? $userModel->password
) ?? $userModel->password,
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var \App\Models\User */
/** @var User */
$userModel = $this->route('user');
return array_merge(parent::rules(), [
'email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
Rule::unique('users', 'email')->ignore($userModel->getRouteKey())
Rule::unique('users', 'email')->ignore($userModel->getRouteKey()),
],
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
@ -59,7 +61,7 @@ class UpdateUserRequest extends StoreUserRequest
->mixedCase()
->numbers()
->symbols()
->uncompromised()
->uncompromised(),
],
]);
}

View file

@ -1,10 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Fo\Customers;
use App\Models\Customer;
use App\Models\Settings;
use App\Repositories\Fo\CrmRepository;
use App\Rules\IsAdult;
use Closure;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
@ -18,8 +22,6 @@ class CreateAccountRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
@ -28,35 +30,30 @@ class CreateAccountRequest extends FormRequest
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation(): void
{
$this->merge([
'phone' => $this->input('phone'),
'opt_in' => $this->boolean('opt_in'),
'ok_cgv' => $this->boolean('ok_cgv')
'ok_cgv' => $this->boolean('ok_cgv'),
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var \App\Models\Settings $settings */
$settings = app(Settings::class);
/** @var Settings $settings */
$settings = resolve(Settings::class);
/** @var \App\Models\Customer|null */
$authUser = auth('frontend')->user();
$countryCode = null;
$countryCode = $this->integer('country') != 0 ?
$this->integer('country') : ($authUser ?
(new CrmRepository())->fetchCrmCustomer($authUser)->country->value : 0);
new CrmRepository()->fetchCrmCustomer($authUser)->country->value : 0);
$countryRules = match ($countryCode) {
Country::belgium->value => [
'phone' => 'phone:BE',
@ -105,13 +102,13 @@ class CreateAccountRequest extends FormRequest
'max:255',
// * Does email already exists in CRM ?
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function (string $attribute, $value, \Closure $fail) {
if (CrmCustomer::findWithEmail(\strval($value))) {
function (string $attribute, $value, Closure $fail): void {
if (CrmCustomer::findWithEmail(\strval($value)) instanceof CrmCustomer) {
$fail('Un compte existe déjà avec cette adresse.');
} else {
// ! Si le compte n'existe pas en CRM mais existe en local.
// ! On le soft-delete pour permettre l'inscription et la restauration propre.
$localCustomer = \App\Models\Customer::where('email', \strval($value))->first();
$localCustomer = Customer::where('email', \strval($value))->first();
if ($localCustomer) {
$localCustomer->delete();
}
@ -123,7 +120,7 @@ class CreateAccountRequest extends FormRequest
'required',
'confirmed',
Password::min(8)->mixedCase()
->numbers()->symbols()->uncompromised()
->numbers()->symbols()->uncompromised(),
],
'ok_cgv' => 'accepted',
'opt_in' => 'required|boolean',
@ -132,8 +129,6 @@ class CreateAccountRequest extends FormRequest
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
@ -157,30 +152,28 @@ class CreateAccountRequest extends FormRequest
/**
* Get custom messages for validator errors.
*
* @return array
*/
public function messages(): array
{
$messages = [
'postal_code' => trans('validation.postal_code', [
'countries' => \implode(', ', Country::getValidCountriesLabel()),
'examples' => 72000
'examples' => 72000,
]),
'phone.phone' => trans('validation.phone', [
'countries' => \implode(', ', Country::getValidCountriesLabel())
'countries' => \implode(', ', Country::getValidCountriesLabel()),
]),
'email.unique' => trans('Un compte existe déjà avec cette adresse.'),
'ok_cgv.accepted' => trans('Vous devez accepter les conditions d\'utilisation du site pour continuer.')
];
'ok_cgv.accepted' => trans('Vous devez accepter les conditions d\'utilisation du site pour continuer.'),
]; // end match
$messages = match ($this->integer('country')) {
return match ($this->integer('country')) {
Country::belgium->value => array_merge(
$messages,
[
'postal_code' => trans('validation.postal_code', [
'countries' => Country::belgium->label(),
'examples' => 1000
'examples' => 1000,
]),
]
),
@ -189,7 +182,7 @@ class CreateAccountRequest extends FormRequest
[
'postal_code' => trans('validation.postal_code', [
'countries' => Country::luxembourg->label(),
'examples' => 1111
'examples' => 1111,
]),
]
),
@ -198,7 +191,7 @@ class CreateAccountRequest extends FormRequest
[
'postal_code' => trans('validation.postal_code', [
'countries' => Country::germany->label(),
'examples' => 10115
'examples' => 10115,
]),
]
),
@ -214,12 +207,10 @@ class CreateAccountRequest extends FormRequest
array_search(Country::germany->label(), Country::getValidCountriesLabel()),
]
)),
'examples' => 72000
'examples' => 72000,
]),
]
),
}; //end match
return $messages;
};
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Fo\Customers;
use Illuminate\Foundation\Http\FormRequest;
@ -8,20 +10,16 @@ class SingleOptinRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof',

View file

@ -1,15 +1,16 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Fo\Customers;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Arr;
class VerifyInformationsRequest extends CreateAccountRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
@ -19,7 +20,7 @@ class VerifyInformationsRequest extends CreateAccountRequest
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{

View file

@ -1,9 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Fo;
use App\Enums\CodeType;
use App\Models\Code;
use App\Models\Customer;
use App\Models\Participation;
use App\Models\Product;
use App\Models\Settings;
@ -19,30 +22,27 @@ class ParticipateRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
// * Use rate limiter to prevent brute force sticker code
if (RateLimiter::tooManyAttempts("participate:{$customer->id}", $perMinute = 5)) {
return false;
}
RateLimiter::hit("participate:{$customer->id}");
return Gate::check('participate', Participation::class);
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation(): void
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
if ($settings->code_type->equals(CodeType::unique) || $settings->code_type->equals(CodeType::both)) {
try {
$this->merge([
@ -83,18 +83,16 @@ class ParticipateRequest extends FormRequest
->first(),
'game_rules' => $this->boolean('game_rules'),
]);
} //end if
} // end if
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
/** @var Settings */
$settings = resolve(Settings::class);
$rules = [
'game_rules' => 'required|boolean|accepted',
@ -105,17 +103,18 @@ class ParticipateRequest extends FormRequest
$rules = \array_merge($rules, ['unique_code' => [
'required',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function (string $attribute, $value, callable $fail) {
function (string $attribute, $value, callable $fail): void {
// ? Le Code unique est resolved ?
if ($value === false or !\is_object($value) or \get_class($value) !== Code::class) {
if ($value === false || ! \is_object($value) || $value::class !== Code::class) {
$fail(trans('front.messages.unique_code_invalid'));
return;
}
/** @var \App\Models\Customer */
/** @var Customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Code $value */
/** @var 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))
@ -123,6 +122,7 @@ class ParticipateRequest extends FormRequest
// ! Une seule participation avec un code donné par joueur
if ($query->exists()) {
$fail(trans('front.messages.unique_code_already_used'));
return;
}
@ -131,37 +131,36 @@ class ParticipateRequest extends FormRequest
if ($value->has_been_used_too_many_times) {
// * Ne pas laisser d'indices, message générique
$fail(trans('front.messages.unique_code_invalid'));
return;
}
}
},
]]);
} //end if
} // end if
// ? Does the game use EAN Codes ?
if ($settings->code_type->equals(CodeType::ean) || $settings->code_type->equals(CodeType::both)) {
$rules = \array_merge($rules, ['ean_code' => [
return \array_merge($rules, ['ean_code' => [
'required',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) {
function ($attribute, $value, $fail): void {
// phpcs:enable
/** @var \App\Models\Produt|mixed */
$product = $this->input('product');
if (
empty($product) or !is_object($product) or
!($product instanceof Model) or !$product->exists
empty($product) || ! is_object($product) || ! ($product instanceof Model) || ! $product->exists
) {
$fail(trans('front.messages.barcode_invalid'));
}
}
},
]]);
}
return $rules;
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{

View file

@ -1,9 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests\Fo;
use App\Http\Requests\Fo\Customers\CreateAccountRequest;
use App\Repositories\Fo\CrmRepository;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
use LDC\LDCCRM\Enums\Country;
@ -12,26 +15,23 @@ class SubmitWinningProofRequest extends CreateAccountRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return auth('frontend')->check() &&
Gate::check('submitProof', $this->route('participation'));
return auth('frontend')->check() && Gate::check('submitProof', $this->route('participation'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
* @return array<string, ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
$authUser = auth('frontend')->user();
$authUser = auth('frontend')->user();
$countryCode = $this->integer('country') != 0 ?
$this->integer('country') : ($authUser ?
(new CrmRepository())->fetchCrmCustomer($authUser)->country->value : 0);
new CrmRepository()->fetchCrmCustomer($authUser)->country->value : 0);
$countryRules = match ($countryCode) {
Country::belgium->value => ['phone' => 'phone:BE'],
Country::luxembourg->value => ['phone' => 'phone:LU'],
@ -75,14 +75,12 @@ class SubmitWinningProofRequest extends CreateAccountRequest
return collect(['sticker', 'ticket', 'barcode'])
->filter(fn (string $proof) => config("proofs.$proof"))
->mapWithKeys(fn (string $proof) => [$proof => $fileRules])
->mapWithKeys(fn (string $proof): array => [$proof => $fileRules])
->all();
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
@ -90,7 +88,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

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Jobs\DeleteOlds;
use App\Models\Customer;
@ -12,12 +14,10 @@ class DeleteOldCustomers
{
/**
* Called when the job will be executed.
*
* @return void
*/
public function handle()
public function handle(): void
{
Log::debug(__CLASS__ . ' Removing old customers');
Log::debug(self::class . ' Removing old customers');
Customer::query()->withTrashed()->where('last_connection', '<=', now()->subMonths(36))->forceDelete();
}
}

View file

@ -1,33 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Mail\Jobs\CodeExportSucceed;
use App\Mail\Jobs\JobFailed;
use App\Models\Code;
use Illuminate\Bus\Queueable;
use Exception;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Mail;
use Throwable;
use ZipArchive;
class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
class ProcessExportCodesUniques implements ShouldBeUnique, ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* The number of seconds the job can run before timing out.
*
* 28800 seconds (8 hours)
*
* @var integer
* @var int
*/
public $timeout = 28800;
@ -36,15 +35,14 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
*
* @var string
*/
protected $exportFileName = null;
protected $exportFileName;
/**
* 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.
* @return void
* @param int $nbCodeFic Nombre de codes par fichiers.
* @param int $nbCodeTot Nombre total de codes à exporter.
* @param int $nbCodeLigne Nombre de codes par ligne.
*/
public function __construct(
protected int $nbCodeFic,
@ -55,12 +53,10 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
/**
* Execute the job.
*
* @return void
*/
public function handle()
public function handle(): void
{
DB::transaction(function () {
DB::transaction(function (): void {
$this->exportFileName = sprintf(
'CodesUniques_%s',
date('dmY_His')
@ -76,8 +72,8 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
$filesPath = $this->generateCsvFiles($lastExportedCodeId);
// * Compression des fichiers.
$zip = new \ZipArchive();
if ($zip->open($exportFilePath, \ZipArchive::CREATE) === true) {
$zip = new ZipArchive();
if ($zip->open($exportFilePath, ZipArchive::CREATE) === true) {
foreach ($filesPath as $filePath) {
if (File::exists($filePath)) {
$zip->addFile($filePath, \pathinfo($filePath, \PATHINFO_BASENAME));
@ -100,21 +96,22 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
$this->nbCodeLigne,
$exportFilePath
));
} catch (\Exception $e) {
} catch (Exception $e) {
// * Clear exported file in case of Failure.
if (File::exists($exportFilePath)) {
File::delete($exportFilePath);
}
throw $e;
} //end try
} // end try
});
}
/**
* Generate CSV File using the job parameters
*
* @param integer|null $lastExportedCodeId
* @param int|null $lastExportedCodeId
* @return array Array of temp files
*
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
private function generateCsvFiles($lastExportedCodeId = null): array
@ -152,7 +149,7 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
} else {
File::append($tempFilePath, Code::intToSerial($code) . ';');
$currentLineCodes++;
} //end if
} // end if
$totalExportedCodes++;
$currentExportedCodes++;
@ -175,29 +172,26 @@ class ProcessExportCodesUniques implements ShouldQueue, ShouldBeUnique
Code::query()->whereIn('id', $exportedCodesId)->update(['code_exported' => true]);
$exportedCodesId = [];
}
} //end if
} //end foreach
} // end if
} // end foreach
// ? Are there exported codes to save ?
if (count($exportedCodesId)) {
Code::query()->whereIn('id', $exportedCodesId)->update(['code_exported' => true]);
}
return $outPaths;
}
/**
* 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__, [
new JobFailed(self::class, [
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
'trace' => $exception->getTraceAsString(),
])
);
}

View file

@ -1,43 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Mail\Jobs\CodeGenerationSucceed;
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;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Throwable;
class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
class ProcessUniqueCode implements ShouldBeUnique, ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* The number of seconds the job can run before timing out.
*
* 7200 seconds (2 hours)
*
* @var integer
* @var int
*/
public $timeout = 7200;
/**
* Create a new job instance.
*
* @param integer $amount Amount of code to generate.
* @param integer $length Length of each code to generate.
* @return void
* @param int $amount Amount of code to generate.
* @param int $length Length of each code to generate.
*/
public function __construct(protected int $amount, protected int $length)
{
@ -45,38 +41,29 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
/**
* Execute the job.
*
* @return void
*/
public function handle()
public function handle(): void
{
DB::transaction(function () {
DB::transaction(function (): void {
// ! Le premier code commence à 1
$last_id = (Code::query()->max('id') ?? 0) + 1;
/** @var \App\Models\Settings $settings */
$settings = app(Settings::class);
/** @var Settings $settings */
$settings = resolve(Settings::class);
$maxSQLPlaceholders = 65535;
$insertColumns = collect(DB::getSchemaBuilder()
->getColumnListing((new Code())->getTable()))
->filter(function ($name) {
// * Ignorer les colonnes avec valeurs par défaut sur la table.
return !in_array($name, ['id', 'code_exported']);
})->count() + 1;
->getColumnListing(new Code()->getTable()))
->reject(fn ($name): bool => in_array($name, ['id', 'code_exported']))->count() + 1;
/**
* Nombre d'insertions max sur un seule requète
*
* @var integer $perPage
*/
$perPage = \floor($maxSQLPlaceholders / $insertColumns);
/**
* Nombre de page totale à insérer.
*
* @var integer $totalPages
*/
$totalPages = \ceil($this->amount / $perPage);
@ -87,7 +74,7 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
$insertToGenerate = min($perPage, $this->amount - $totalGeneratedInserts);
do {
$inserts[] = [
'id' => $last_id++,
'id' => $last_id++,
'date_creation' => now()->format('Y-m-d'),
];
} while (($totalGeneratedInserts < $this->amount) && --$insertToGenerate);
@ -108,16 +95,13 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
/**
* 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__, [
new JobFailed(self::class, [
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
'trace' => $exception->getTraceAsString(),
])
);
}
@ -130,8 +114,8 @@ class ProcessUniqueCode implements ShouldQueue, ShouldBeUnique
public function middleware(): array
{
return [
(new WithoutOverlapping('code_generation'))
->releaseAfter(60)
new WithoutOverlapping('code_generation')
->releaseAfter(60),
];
}
}

View file

@ -1,24 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Lib;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Contracts\Support\Jsonable;
use Illuminate\Support\Arr;
use JsonSerializable;
use ReturnTypeWillChange;
use RuntimeException;
use stdClass;
use TypeError;
/**
* Ligne de Coupon stocké dans settings
*
* @property integer $web_coupon_identifiant EAN 13 format (Mais ne représente pas un EAN 13),
* Format utilisé dans MesBr.
* @property integer $percentage Pourcentage de chance de gagner le lot nombre de 1 à 100.
* @property integer $face_value Valeur faciale en centimes.
* @property 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.
* @property string $picture Image locale du Coupon.
* @package App\Lib\Inputs
* @property int $web_coupon_identifiant EAN 13 format (Mais ne représente pas un EAN 13),
* Format utilisé dans MesBr.
* @property int $percentage Pourcentage de chance de gagner le lot nombre de 1 à 100.
* @property int $face_value Valeur faciale en centimes.
* @property 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.
* @property string $picture Image locale du Coupon.
*/
class Discount extends \stdClass implements Jsonable, Arrayable, \JsonSerializable
class Discount extends stdClass implements Arrayable, Jsonable, JsonSerializable
{
/**
* Properties list
@ -36,33 +42,31 @@ class Discount extends \stdClass implements Jsonable, Arrayable, \JsonSerializab
/**
* Get property
*
* @param string $name
* @return mixed
* @throws \TypeError If prop is undefined.
*
* @throws TypeError If prop is undefined.
*/
public function __get(string $name)
public function __get(string $name): mixed
{
if (
in_array($name, self::$propertiesList)
) {
return $this->{$name};
}
throw new \TypeError("undefined property {$name}");
throw new TypeError("undefined property {$name}");
}
/**
* Make and instance
*
* @param string $picture Image locale du Coupon.
* @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 integer $face_value Valeur faciale en centimes.
* @param integer $percentage Pourcentage de chance de gagner le lot nombre de 1 à 100.
* @param integer $web_coupon_identifiant EAN 13 format (Mais ne représente pas un EAN 13),
* format utilisé dans MesBr.
* @param string $picture Image locale du Coupon.
* @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 int $face_value Valeur faciale en centimes.
* @param int $percentage Pourcentage de chance de gagner le lot nombre de 1 à 100.
* @param int $web_coupon_identifiant EAN 13 format (Mais ne représente pas un EAN 13),
* format utilisé dans MesBr.
*
* @return \App\Lib\Discount
* @throws \RuntimeException If $type or $data is wrong.
* @throws RuntimeException If $type or $data is wrong.
*/
public static function make(
string $picture,
@ -78,54 +82,42 @@ class Discount extends \stdClass implements Jsonable, Arrayable, \JsonSerializab
$ipTypeData->valid_on = $valid_on;
$ipTypeData->picture = $picture;
self::assertIsInputTypeData($ipTypeData);
return $ipTypeData;
}
/**
* Confirm $data is of type InputTypeData
*
* @param \App\Lib\Discount $data
* @return void
* @throws \RuntimeException If $data is wrong.
*
* @throws RuntimeException If $data is wrong.
*/
public static function assertIsInputTypeData(Discount $data)
public static function assertIsInputTypeData(Discount $data): void
{
if (!\is_int($data->web_coupon_identifiant)) {
throw new \RuntimeException("InputTypeData 'web_coupon_identifiant' should be an integer");
}
if (!\is_int($data->percentage)) {
throw new \RuntimeException("InputTypeData 'percentage' should be an integer");
}
if (!\is_int($data->face_value)) {
throw new \RuntimeException("InputTypeData 'face_value' should be an integer");
}
if (!\is_string($data->valid_on)) {
throw new \RuntimeException("InputTypeData 'valid_on' should be a string");
}
if (!\is_string($data->picture)) {
throw new \RuntimeException("InputTypeData 'picture' should be a string");
}
throw_unless(\is_int($data->web_coupon_identifiant), RuntimeException::class, "InputTypeData 'web_coupon_identifiant' should be an integer");
throw_unless(\is_int($data->percentage), RuntimeException::class, "InputTypeData 'percentage' should be an integer");
throw_unless(\is_int($data->face_value), RuntimeException::class, "InputTypeData 'face_value' should be an integer");
throw_unless(\is_string($data->valid_on), RuntimeException::class, "InputTypeData 'valid_on' should be a string");
throw_unless(\is_string($data->picture), RuntimeException::class, "InputTypeData 'picture' should be a string");
}
// * TRANSFORMERS
/**
* Get the instance as an array.
*
* @return array
*/
public function toArray(): array
{
return collect(Arr::mapWithKeys(self::$propertiesList, fn (string $property) => [
$property => $this->{$property}
return collect(Arr::mapWithKeys(self::$propertiesList, fn (string $property): array => [
$property => $this->{$property},
]))->filter()->all();
}
/**
* Convert the object to its JSON representation.
*
* @param integer $options
* @return string
* @param int $options
*
* @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing
*/
public function toJson($options = 0): string
@ -139,7 +131,7 @@ class Discount extends \stdClass implements Jsonable, Arrayable, \JsonSerializab
*
* @return array
*/
#[\ReturnTypeWillChange]
#[ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use Illuminate\Container\Container;
@ -11,10 +13,6 @@ class CollectionHelper
{
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginate(Collection $results, int $pageSize): LengthAwarePaginator
{
@ -25,18 +23,13 @@ class CollectionHelper
/**
* Paginate a collection
*
* @param \Illuminate\Support\Collection $results
* @param integer $pageSize
* @param integer $page
* @return \Illuminate\Pagination\LengthAwarePaginator
*/
public static function paginateManually(Collection $results, int $pageSize, int $page = 1): LengthAwarePaginator
{
$total = $results->count();
return self::paginator($results->forPage($page, $pageSize), $total, $pageSize, $page, [
'path' => Paginator::resolveCurrentPath(),
'path' => Paginator::resolveCurrentPath(),
'pageName' => 'page',
]);
}
@ -44,21 +37,16 @@ class CollectionHelper
/**
* Create a new length-aware paginator instance.
*
* @param \Illuminate\Support\Collection $items
* @param integer $total
* @param integer $perPage
* @param integer $currentPage
* @param array $options
* @return \Illuminate\Pagination\LengthAwarePaginator
* @return LengthAwarePaginator
*/
protected static function paginator(Collection $items, int $total, int $perPage, int $currentPage, array $options)
{
return Container::getInstance()->makeWith(LengthAwarePaginator::class, [
'items' => $items,
'total' => $total,
'perPage' => $perPage,
'items' => $items,
'total' => $total,
'perPage' => $perPage,
'currentPage' => $currentPage,
'options' => $options
'options' => $options,
]);
}
}

View file

@ -1,55 +1,56 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use Sentry\Event;
use Sentry\ExceptionDataBag;
use Sentry\Frame;
use Sentry\Stacktrace;
use Throwable;
class ExceptionsHelper
{
/**
* Handle sentry event
*
* @param \Sentry\Event $event
* @return null|\Sentry\Event
*/
public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
public static function sentryEventHandler(Event $event): ?Event
{
// Remove unwanted reports.
$event->setModules([]);
$event->setOsContext(null);
/** @var \Sentry\ExceptionDataBag[] */
/** @var ExceptionDataBag[] */
$exceptionBag = [];
foreach ($event->getExceptions() as $exceptionDataBag) {
$exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
}
$event->setExceptions($exceptionBag);
return $event;
}
/**
* Anonimize Sentry Exception bag
*
* @param \Sentry\ExceptionDataBag $exceptionDataBag
* @return \Sentry\ExceptionDataBag
*/
private static function getAnonymizedSentryException(
\Sentry\ExceptionDataBag $exceptionDataBag
): \Sentry\ExceptionDataBag {
ExceptionDataBag $exceptionDataBag
): ExceptionDataBag {
$exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
if ($stackTrace = $exceptionDataBag->getStacktrace()) {
if (($stackTrace = $exceptionDataBag->getStacktrace()) instanceof Stacktrace) {
$exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
}
return $exceptionDataBag;
}
/**
* Anonymise sentry stacktrace
*
* @param \Sentry\Stacktrace $stackTrace
* @return \Sentry\Stacktrace
*/
private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
private static function getAnonymizedSentryStackTrace(Stacktrace $stackTrace): Stacktrace
{
/** @var \Sentry\Frame[] */
/** @var Frame[] */
$frames = \array_reverse($stackTrace->getFrames(), true);
$i = \count($frames) - 1;
// * Remove all but keep first (index.php).
@ -66,13 +67,14 @@ class ExceptionsHelper
$stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
}
}
return $stackTrace;
}
/**
* Get anonymized sentry params
*
* @param mixed[] $params
* @param mixed[] $params
* @return string[]
*/
private static function getAnonymizedSentryParams(array $params): array
@ -86,46 +88,44 @@ class ExceptionsHelper
case $type === 'lambda':
case $type === 'object':
continue 2;
case $type === 'array' and \is_array($param):
case $type === 'array' && \is_array($param):
$param = sprintf('Array(%d)', \count($param));
continue 2;
default:
if (\is_numeric($param)) {
$param = \ucfirst($type);
continue 2;
}
if (\is_string($param)) {
$param = sprintf('String(%d)', \strlen($param));
continue 2;
}
} //end switch
} //end foreach
} // end switch
} // end foreach
return $params;
}
/**
* Anonymize at least Sql queries
*
* @param string $message
* @return string
*/
public static function getAnonymizedMessage(string $message): string
{
$message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
if ($sqlPos = \strpos($message, 'to use near')) {
$message = \mb_strcut($message, 0, $sqlPos);
return \mb_strcut($message, 0, $sqlPos);
}
return $message;
}
/**
* Anonymize stack trace
*
* @param \Throwable $e
* @return string
*/
public static function getAnonymizedStackTrace(\Throwable $e): string
public static function getAnonymizedStackTrace(Throwable $e): string
{
$projectPath = \realpath(\sprintf('%s/../', \app_path()));
$traceStack = $e->getTrace();
@ -152,17 +152,15 @@ class ExceptionsHelper
}
$traceStr .= ")\n";
}
} //end foreach
} // end foreach
$maxStackStage++;
$traceStr .= "#$maxStackStage {main}";
return $traceStr;
}
/**
* Handle Args
*
* @param array $stack
* @return string
*/
public static function handleArgs(array $stack): string
{
@ -170,21 +168,15 @@ class ExceptionsHelper
$argsCount = count($stack['args']);
for ($i = 0; $i < $argsCount; $i++) {
$type = \gettype($stack['args'][$i]);
switch ($type) {
case 'object':
$traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
break;
case 'array':
$traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
break;
case 'string':
$traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
break;
default:
$traceArgs .= \gettype($stack['args'][$i]);
}
match ($type) {
'object' => $traceArgs .= sprintf('Object(%s)', $stack['args'][$i]::class),
'array' => $traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i])),
'string' => $traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i])),
default => $traceArgs .= \gettype($stack['args'][$i]),
};
$traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
}
return $traceArgs;
}
}

View file

@ -1,11 +1,17 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use RuntimeException;
use SplFileInfo;
/**
* This class is used to handle files in LDC Laravel stack
@ -33,28 +39,25 @@ class FileStorageHelper
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $publicStorageFilePath The file path from public directory.
* @param string $publicStorageFilePath The file path from public directory.
* @return string The local storage path.
*/
public static function getFileFullPath(string $publicStorageFilePath): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
/** @var FilesystemAdapter */
$disk = Storage::disk('public');
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
return $disk->path($path);
}
/**
* Destroy the file associate with the attribute.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return void
*/
public static function removeFile(Model $model, string $attribute): void
{
$path = \ltrim(\urldecode($model->{$attribute}), '/');
if (\strpos($path, 'storage') !== 0) {
if (! str_starts_with($path, 'storage')) {
return;
}
$path = ToolboxHelper::mbReplace('storage/', '', $path);
@ -65,20 +68,15 @@ class FileStorageHelper
/**
* Destroy the file associate with the attribute old value (before updated to DB).
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @param boolean $onlyIfUpdated
* @return void
*/
public static function removeOldFile(Model $model, string $attribute, bool $onlyIfUpdated = true): void
{
// * Ignore If removal has to be done only if the field has changed and it actually hasnt.
if ($onlyIfUpdated and $model->{$attribute} === ToolboxHelper::getOriginal($model, $attribute)) {
if ($onlyIfUpdated && $model->{$attribute} === ToolboxHelper::getOriginal($model, $attribute)) {
return;
}
$originalPath = \ltrim(\urldecode(ToolboxHelper::getOriginal($model, $attribute)), '/');
if (\strpos($originalPath, 'storage') !== 0) {
if (! str_starts_with($originalPath, 'storage')) {
return;
}
$originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath);
@ -92,37 +90,39 @@ class FileStorageHelper
*
* You can use filename param to overload file name.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param \Illuminate\Http\UploadedFile|\SplFileInfo|string $file The file, will just return itself is string.
* @param boolean $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param boolean $private To store the file in a private space.
* @return string
* @throws \RuntimeException If file copy fails.
* @param Model $model The model that will hold the picture.
* @param UploadedFile|SplFileInfo|string $file The file, will just return itself is string.
* @param bool $slugify Force filename slugification.
* @param string|null $filename This string may be used to store the file.
* @param bool $private To store the file in a private space.
*
* @throws RuntimeException If file copy fails.
*
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
*/
public static function storeFile(
Model $model,
$file,
bool $slugify = false,
string $filename = null,
?string $filename = null,
bool $private = false
): string {
// phpcs:enable
$tableName = $model->getTable();
if (\is_object($file) and $file instanceof \Illuminate\Http\UploadedFile) {
/** @var \Illuminate\Http\UploadedFile $file */
if ($file instanceof UploadedFile) {
/** @var UploadedFile $file */
$filename = $filename ?: $file->getClientOriginalName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
$newPath = str_replace(storage_path('app'), '', $newPath);
if (!$private) {
if (! $private) {
// * Store public file.
$file->storePubliclyAs(
pathinfo($newPath, \PATHINFO_DIRNAME),
pathinfo($newPath, \PATHINFO_BASENAME)
);
return 'storage' . str_replace('/public', '', $newPath);
}
@ -132,23 +132,25 @@ class FileStorageHelper
);
return str_replace('/private/', '', $newPath);
} //end if
if (\is_object($file) and $file instanceof \SplFileInfo) {
/** @var \SplFileInfo $file */
} // end if
if ($file instanceof SplFileInfo) {
/** @var SplFileInfo $file */
$filename = $filename ?: $file->getBaseName();
$filename = $slugify ? self::slugifyFileName($filename) : $filename;
$newPath = self::prepareMoveFile($tableName, $filename, $private);
if (!File::copy($file->getRealPath(), $newPath)) {
throw new \RuntimeException(sprintf(
if (! File::copy($file->getRealPath(), $newPath)) {
throw new RuntimeException(sprintf(
'Failed to copy file %s to %s',
$file->getRealPath(),
$newPath
));
}
return $private ?
\ltrim(str_replace(\storage_path('app/private'), '/storage/app/private', $newPath), '/') :
\ltrim(str_replace(\storage_path('app/public'), '/storage', $newPath), '/');
}
return strval($file);
}
@ -157,22 +159,22 @@ class FileStorageHelper
* If oldHtmlString is passed, a diff is done with new and old files
* to keep only files that are present in the new html string.
*
* @param \Illuminate\Database\Eloquent\Model $model The model that will hold the picture.
* @param string $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
* files using storeModelFilesFromHtml.
* @param Model $model The model that will hold the picture.
* @param string $htmlString The html string to parse and modify files.
* @param string|null $oldHtmlString The old html string with alreadyParsed
* files using storeModelFilesFromHtml.
* @return string Html with files pathes replaced using storage strategy.
*/
public static function storeModelFilesFromHtml(
Model $model,
string $htmlString,
string $oldHtmlString = null
?string $oldHtmlString = null
): string {
\collect(self::onlyUploadedFilesWithFileManager(self::findFilesInHtml($htmlString)))
->each(function (string $path) use ($model, &$htmlString) {
/** @var \Illuminate\Filesystem\FilesystemAdapter */
->each(function (string $path) use ($model, &$htmlString): void {
/** @var FilesystemAdapter */
$disk = Storage::disk('uploads');
$fileInfo = new \SplFileInfo($disk->path(
$fileInfo = new SplFileInfo($disk->path(
ToolboxHelper::mbReplace('/uploads/', '', \urldecode($path))
));
$htmlString = ToolboxHelper::mbReplace(
@ -193,9 +195,8 @@ class FileStorageHelper
* Remove all old files from string attribute html that are stored in public that
* are nove present anymore on new html string
*
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
* @param string $newHtmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @param string $oldHtmlString The old html string with alreadyParsed files using storeModelFilesFromHtml.
*/
public static function removeOldModelFilesFromHtml(string $newHtmlString, string $oldHtmlString): void
{
@ -206,16 +207,15 @@ class FileStorageHelper
* Remove all old files that are stored in public that
* are not present anymore on new array files
*
* @param string[] $oldFiles The old array files.
* @param string[] $newFiles The new array files.
* @return void
* @param string[] $oldFiles The old array files.
* @param string[] $newFiles The new array files.
*/
public static function removeOldFiles(array $oldFiles, array $newFiles): void
{
$oldFiles = \collect(self::onlyUploadedFilesWithFileStorageHelper($oldFiles));
$filesToKeep = \collect(self::onlyUploadedFilesWithFileStorageHelper($newFiles));
$filesToRemove = $oldFiles->diff($filesToKeep);
$filesToRemove->each(function (string $publicStorageFilePath) {
$filesToRemove->each(function (string $publicStorageFilePath): void {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
@ -226,13 +226,12 @@ class FileStorageHelper
/**
* Remove all files from string attribute html that are stored in public
*
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
* @return void
* @param string $htmlString The html string with alreadyParsed files using storeModelFilesFromHtml.
*/
public static function removeModelFilesOfHtml(string $htmlString): void
{
$filesToRemove = \collect(self::onlyUploadedFilesWithFileStorageHelper(self::findFilesInHtml($htmlString)));
$filesToRemove->each(function (string $publicStorageFilePath) {
$filesToRemove->each(function (string $publicStorageFilePath): void {
$path = ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($publicStorageFilePath), '/'));
if (Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
@ -242,13 +241,11 @@ class FileStorageHelper
/**
* Find all occurence of files in html
*
* @param string $html
* @return array
*/
public static function findFilesInHtml(string $html): array
{
preg_match_all('/href="(?P<href>.*)"|src="(?P<src>.*)"|url="(?P<url>.*)"/Um', $html, $m, PREG_SET_ORDER, 0);
return collect($m)->pluck('href')->filter()
->concat(collect($m)->pluck('url')->filter())
->concat(collect($m)->pluck('src')->filter())
@ -258,19 +255,18 @@ class FileStorageHelper
/**
* Filter array to get only uploaded files that exists
* on disk 'public' (using helper storage strategy)
*
* @param array $urls
* @return array
*/
public static function onlyUploadedFilesWithFileStorageHelper(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
->filter(function (string $url): bool {
$path = \parse_url($url, \PHP_URL_PATH);
return \strpos(\ltrim($path, '/'), 'storage/modelfiles') === 0;
return str_starts_with(\ltrim($path, '/'), 'storage/modelfiles');
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('public')
->exists(ToolboxHelper::mbReplace('storage/', '', \ltrim(\urldecode($path), '/')));
})->all();
@ -279,21 +275,19 @@ class FileStorageHelper
/**
* Filter array to get only uploaded files that exists
* on disk 'uploads'
*
* @param array $urls
* @return array
*/
private static function onlyUploadedFilesWithFileManager(array $urls): array
{
return \collect($urls)->unique()
// * Filter to get only /uploads and using app url or empty
->filter(function (string $url) {
->filter(function (string $url): bool {
$host = \parse_url($url, \PHP_URL_HOST);
$path = \parse_url($url, \PHP_URL_PATH);
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) or \is_null($host)) and
\strpos(\ltrim($path, '/'), 'uploads') === 0;
return ($host === \parse_url(config('app.url'), PHP_URL_HOST) || \is_null($host)) && str_starts_with(\ltrim($path, '/'), 'uploads');
})->filter(function (string $url) {
$path = \parse_url($url, \PHP_URL_PATH);
return Storage::disk('uploads')
->exists(ToolboxHelper::mbReplace('uploads/', '', \ltrim(\urldecode($path), '/')));
})->all();
@ -303,10 +297,9 @@ class FileStorageHelper
* Prepare storage path and return
* the file full path
*
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param boolean $private May the file be stored in a private dir.
* @return string
* @param string $tableName Model table name.
* @param string $filename The basename for the file.
* @param bool $private May the file be stored in a private dir.
*/
private static function prepareMoveFile(string $tableName, string $filename, bool $private = false): string
{
@ -325,6 +318,7 @@ class FileStorageHelper
$filename,
);
$filename = self::getFileUniqueName($folderPath, $filename);
return "{$folderPath}/{$filename}";
}
@ -332,11 +326,6 @@ class FileStorageHelper
* Create sub folders to store files using pattern
* storage/modelfiles/posts/10/04/19/5f
* year/month/day/md5 first 2 letters
*
* @param string $storage_dir
* @param string $table
* @param string $filename
* @return string
*/
private static function getStoragePath(string $storage_dir, string $table, string $filename): string
{
@ -350,40 +339,34 @@ class FileStorageHelper
gmdate('d'),
substr($filenameHash, 0, 2)
));
if (!File::exists($pathFolder)) {
if (! File::exists($pathFolder)) {
File::makeDirectory($pathFolder, 0755, true);
}
return $pathFolder;
}
/**
* Get unique filename if the path is already occupied
*
* @param string $folderPath
* @param string $filename
* @return string
* @throws \RuntimeException If a unique filename cannot be obtained.
* @throws RuntimeException If a unique filename cannot be obtained.
*/
private static function getFileUniqueName(string $folderPath, string $filename): string
{
$attempts = 0;
$maxAttempts = 20;
while (File::exists("{$folderPath}/{$filename}")) {
if ($attempts++ >= $maxAttempts) {
throw new \RuntimeException("Failed to have a unique filename for {$folderPath}/{$filename}");
}
throw_if($attempts++ >= $maxAttempts, RuntimeException::class, "Failed to have a unique filename for {$folderPath}/{$filename}");
$filename = pathinfo($filename, \PATHINFO_FILENAME) .
\uniqid('-') . '.' .
pathinfo($filename, \PATHINFO_EXTENSION);
}
return $filename;
}
/**
* Slugify the filename
*
* @param string $filename
* @return string
*/
public static function slugifyFileName(string $filename): string
{
@ -393,8 +376,8 @@ class FileStorageHelper
/**
* Sanitize File name for uploaded files
*
* @param string $filename The original uploaded client file name.
* @param boolean $beautify Beautify the filename.
* @param string $filename The original uploaded client file name.
* @param bool $beautify Beautify the filename.
* @return string The sanitized file name.
*/
public static function sanitizeFileName(string $filename, bool $beautify = true): string
@ -417,7 +400,7 @@ class FileStorageHelper
$filename = Str::of($filename)->ltrim('.-');
// * Optional beautification
if ($beautify) {
$filename = self::beautifyFilename($filename);
$filename = self::beautifyFilename($filename->value());
}
// * Maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
/** @var string */
@ -426,20 +409,18 @@ class FileStorageHelper
$filename = pathinfo($filename, \PATHINFO_FILENAME);
$encoding = mb_detect_encoding($filename);
$encoding = $encoding ? $encoding : null;
$encoding = $encoding ?: null;
return \mb_strcut(
$filename,
0,
255 - (!empty($extension) ? strlen($extension) + 1 : 0),
255 - (! empty($extension) ? strlen($extension) + 1 : 0),
$encoding
) . (!empty($extension) ? ".$extension" : '');
) . (! empty($extension) ? ".$extension" : '');
}
/**
* Beautify a filename
*
* @param string $filename
* @return string
*/
private static function beautifyFilename(string $filename): string
{
@ -450,18 +431,19 @@ class FileStorageHelper
// * "file___name.zip" becomes 'file-name.zip'
'/_+/',
// * "file---name.zip" becomes 'file-name.zip'
'/-+/'
'/-+/',
], '-', $filename);
$filename = preg_replace([
// * "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// * "file...name..zip" becomes "file.name.zip"
'/\.{2,}/'
'/\.{2,}/',
], '.', $filename);
// Lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625 .
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// * ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
}
}

View file

@ -1,75 +1,84 @@
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use App\Models\Settings;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Process\Exceptions\ProcessTimedOutException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use RuntimeException;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
class ToolboxHelper
{
/**
* Run a terminal command and gives the output
* Run a terminal command and gives the output.
*
* @param array|string $command The command to execute on terminal.
* @param boolean $throw Wether to throw en esception on failure or not.
* @param array|string $command The command to execute on terminal.
* @param bool $throw Wether to throw en esception on failure or not.
* @return string|false Return false on failure only if $throw is false.
*
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
*
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
* @throws \Illuminate\Process\Exceptions\ProcessTimedOutException
* @throws \Symfony\Component\Process\Exception\LogicException
* @throws \Symfony\Component\Process\Exception\InvalidArgumentException
*
* @throws ProcessTimedOutException
* @throws LogicException
* @throws InvalidArgumentException
* @throws \Symfony\Component\Process\Exception\RuntimeException
* @throws \LogicException
* @throws \RuntimeException If the command fails.
* @throws RuntimeException If the command fails.
*/
public static function runConsoleCommand(array|string $command, bool $throw = true): string | false
public static function runConsoleCommand(array | string $command, bool $throw = true): string | false
{
// phpcs:enable
$process = \Illuminate\Support\Facades\Process::command($command)->run();
$process = Process::command($command)->run();
if ($process->failed()) {
$code = $process->exitCode();
$ouput = $process->errorOutput();
if ($throw) {
throw new \RuntimeException("Failed to run command exit code {$code} with output \n{$ouput}");
}
throw_if($throw, RuntimeException::class, "Failed to run command exit code {$code} with output \n{$ouput}");
return false;
}
return $process->output();
}
/**
* Assert game is not oppened
* Assert game is not oppened.
*
* @return void
* @throws \RuntimeException If game has started.
* @throws RuntimeException If game has started.
*/
public static function assertGameIsNotOppened(): void
{
/** @var \App\Models\Settings */
$settings = app(Settings::class);
if ($settings->game_is_open) {
throw new \RuntimeException('Cannot delete prize while game is oppened');
}
/** @var Settings */
$settings = resolve(Settings::class);
throw_if($settings->game_is_open, RuntimeException::class, 'Cannot delete prize while game is oppened');
}
/**
* @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2
*
* @param array<int|string, mixed> $array1
* @param array<int|string, mixed> $array2
* @return array<int|string, mixed>
*/
public static function arrayMergeRecursiveDistinct(array &$array1, array &$array2): array
@ -87,89 +96,69 @@ class ToolboxHelper
}
/**
* Validate recaptcha
*
* @param \Illuminate\Http\Request $request
* @param boolean $isV3
* @return void
* Validate recaptcha.
*/
public static function validateRecaptcha(Request $request, bool $isV3 = true)
public static function validateRecaptcha(Request $request, bool $isV3 = true): void
{
Validator::validate($request->all(), [
'g-recaptcha-response' => [
'required', 'string',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($isV3) {
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
function ($attribute, $value, $fail) use ($isV3): void {
try {
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
'secret' => config('services.google.recaptcha.private'),
'secret' => config('services.google.recaptcha.private'),
'response' => \strval($value),
'remoteip' => app('clientIp')
'remoteip' => resolve('clientIp'),
]);
if ($e = $response->toException()) {
throw $e;
}
throw_if($e = $response->toException(), $e);
if ($isV3) {
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
!$response->json('score')
) {
throw new \RuntimeException();
}
throw_if(! $response->json('success')
|| ! $response->json('challenge_ts')
|| Date::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5))
|| $response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
|| ! $response->json('score'), RuntimeException::class);
if (\floatval($response->json('score')) < 0.8) {
$fail(trans('recaptcha.not_enough_score'));
}
return;
}
if (
!$response->json('success') or
!$response->json('challenge_ts') or
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
) {
throw new \RuntimeException();
}
} catch (\Exception $e) {
throw_if(! $response->json('success')
|| ! $response->json('challenge_ts')
|| Date::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5))
|| $response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST), RuntimeException::class);
} catch (Exception) {
$fail(trans('recaptcha.error'));
} //end try
}
]
} // end try
},
],
], [], ['g-recaptcha-response' => 'reCaptcha']);
}
/**
* Strip scheme (https) on oembed urls
* Strip scheme (https) on oembed urls.
* Only embed allowed are for MP4 self hosted videos right now
*
* https is force by ckeditor MediaElements which natively supports
* only cloud hosted medias (youtube, spotify etc..)
*
* @param string $input
* @return string
*/
public static function stripCkEditorSchemesOnMediaInputs(string $input): string
{
$input = \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
return $input;
return \preg_replace('/(?<=<oembed url=")(https:\/\/)(?=.*?">)/m', '', $input);
}
/**
* Assert That no Back Office url is present on txt
* Assert That no Back Office url is present on txt.
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
* @throws ValidationException If url is a bo url.
*/
public static function assertNoInternalBoUrls(string $field, string $text): string
{
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
// @phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
@ -178,34 +167,33 @@ class ToolboxHelper
$request = Request::create($url);
// If a route is match then it do not throws exception.
if (
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST) &&
trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
\parse_url(config('app.url'), \PHP_URL_HOST) === \parse_url($url, \PHP_URL_HOST)
&& trim($routes->match($request)->getPrefix() ?? '', '/') === 'bo'
) {
// Removes the matched url.
throw ValidationException::withMessages([
$field => trans(
'crud.libs.toolbox.assertNoInternalBoUrls',
['attribute' => $field]
)
),
]);
}
return $match;
} catch (NotFoundHttpException $e) {
} catch (NotFoundHttpException) {
return $match;
}
},
$text
);
return $text;
}
/**
* Assert That only external url is present on txt
* Assert That only external url is present on txt.
*
* @param string $field
* @param string $text
* @return string
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
* @throws ValidationException If url is a bo url.
*/
public static function assertNoInternalUrls(string $field, string $text): string
{
@ -213,7 +201,7 @@ class ToolboxHelper
$routes = Route::getRoutes();
// * Url replace and email replace.
$text = preg_replace_callback(
// phpcs:ignore Generic.Files.LineLength.TooLong
// @phpcs:ignore Generic.Files.LineLength.TooLong
'/(?P<url>(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*))/',
function ($matches) use ($routes, $field) {
$match = collect($matches)->first() ?? '';
@ -229,35 +217,31 @@ class ToolboxHelper
$field => trans(
'crud.libs.toolbox.assertNoInternalUrls',
['attribute' => $field, 'appname' => config('app.name')]
)
),
]);
}
return $match;
} catch (NotFoundHttpException $e) {
} catch (NotFoundHttpException) {
return $match;
}
},
$text
);
return $text;
}
/**
* Mutltibyte string replace
*
* @param array|string $search
* @param array|string $replace
* @param array|string $subject
* @param integer $count
* @return array|string|false
* Mutltibyte string replace.
*/
public static function mbReplace(
array|string $search,
array|string $replace,
array|string $subject,
array | string $search,
array | string $replace,
array | string $subject,
int &$count = 0
): array|string|false {
if (!is_array($search) && is_array($replace)) {
): array | string | false {
if (! is_array($search) && is_array($replace)) {
return false;
}
if (is_array($subject)) {
@ -266,7 +250,7 @@ class ToolboxHelper
$string = &self::mbReplace($search, $replace, $string, $count);
}
} elseif (is_array($search)) {
if (!is_array($replace)) {
if (! is_array($replace)) {
foreach ($search as &$string) {
$subject = self::mbReplace($string, $replace, $subject, $count);
}
@ -280,37 +264,37 @@ class ToolboxHelper
}
} else {
$parts = mb_split(preg_quote($search), $subject);
// @phpstan-ignore-next-line
$count = count($parts) - 1;
$subject = implode($replace, $parts);
} //end if
} // end if
return $subject;
}
/**
* Asserts that the field is unique
* Asserts that the field is unique.
*
* @param string $table
* @param string $field
* @param string|integer|float|boolean|null|array $value
* @param integer|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
*
* @throws ValidationException If the field is not unique.
*/
public static function assertFieldIsUnique(
string $table,
string $field,
string|int|float|bool|null|array $value,
string | int | float | bool | null | array $value,
?int $id = null,
?callable $query = null
) {
if (!$query) {
): void {
if (! $query) {
Validator::make([$field => $value], [
self::mbReplace('.', '\.', $field) => Rule::unique($table, $field)->ignore($id),
], [
self::mbReplace('.', '\.', "{$field}.unique")
=> trans('crud.libs.toolbox.assertFieldIsUnique', ['attribute' => $field, 'value' => $value])
self::mbReplace('.', '\.', "{$field}.unique") => trans('crud.libs.toolbox.assertFieldIsUnique', [
'attribute' => $field,
'value' => $value,
]),
])->validate();
return;
}
Validator::make([$field => $value], [
@ -319,16 +303,12 @@ class ToolboxHelper
$field . '.unique' => trans(
'crud.libs.toolbox.assertFieldIsUnique',
['attribute' => $field, 'value' => $value]
)
),
])->validate();
}
/**
* Get original polyfill with autotranslate behavior
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $attribute
* @return mixed
* Get original polyfill with autotranslate behavior.
*/
public static function getOriginal(Model $model, string $attribute): mixed
{
@ -342,17 +322,12 @@ class ToolboxHelper
$model->useFallbackLocale()
);
}
return $model->getOriginal($attribute);
}
/**
* Dynamic model query
*
* @param string $defaultLocale
* @param \Illuminate\Database\Eloquent\Builder $q
* @param string $column
* @param string $value
* @return \Illuminate\Database\Eloquent\Builder
* Dynamic model query.
*/
public static function queryColumnWithLocales(
string $defaultLocale,
@ -362,17 +337,17 @@ class ToolboxHelper
): Builder {
$currentLocale = app()->currentLocale();
$locales = collect(config('app.locales'))
->mapWithKeys(fn (string $locale) => [$locale => $locale]);
if (!$locales->has($defaultLocale)) {
->mapWithKeys(fn (string $locale): array => [$locale => $locale]);
if (! $locales->has($defaultLocale)) {
// * Remove default locale.
$locales->pull($defaultLocale);
}
if (!$locales->has($currentLocale)) {
if (! $locales->has($currentLocale)) {
// * Remove current locale.
$locales->pull($currentLocale);
}
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value) {
return $q->where(function (Builder $q) use ($defaultLocale, $currentLocale, $locales, $column, $value): void {
// * Query default locale
$q->whereRaw("JSON_EXTRACT({$column}, '$.{$defaultLocale}') = '{$value}'");
if ($defaultLocale !== $currentLocale) {
@ -387,12 +362,7 @@ class ToolboxHelper
}
/**
* Generates an array from a dot separated string
*
* @param string $path
* @param mixed $value
* @param string $separator
* @return array
* Generates an array from a dot separated string.
*/
public static function dotStringToNestedArray(string $path, mixed $value, string $separator = '.'): array
{
@ -402,85 +372,77 @@ class ToolboxHelper
foreach ($keys as $key) {
$arr = [$key => $arr];
}
return $arr;
}
/**
* Convert deeply an object to array
* Convert deeply an object to array.
*
* @param mixed $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
* @throws RuntimeException If $input var is a scalar value.
*/
public static function objectToArray(mixed $input): array
{
// Only process if it's an object or array being passed to the function.
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
if (!is_object($input) && !is_array($input)) {
throw new RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (array) $input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
$item = (is_object($item) || is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
}
/**
* Convert deeply an array to object
* Convert deeply an array to object.
*
* @param mixed $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
* @throws RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject(mixed $input): object
{
// Only process if it's an object or array being passed to the function.
if (!(is_object($input) or is_array($input))) {
throw new \RuntimeException('Cannot handle $input of type ' . \gettype($input));
if (!is_object($input) && !is_array($input)) {
throw new RuntimeException('Cannot handle $input of type ' . \gettype($input));
}
$ret = (object) $input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
$item = (is_object($item) || is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
}
/**
* Validate picture
*
* @param string $attribute
* @param string|null $value
* @param callable $fail
* @param object $sizes
* @return void
* Validate picture.
*/
public static function validatePicture(string $attribute, string|null $value, callable $fail, object $sizes)
public static function validatePicture(string $attribute, ?string $value, callable $fail, object $sizes): void
{
if (!($useStorage = Storage::exists($value)) and !File::exists($value)) {
if (! ($useStorage = Storage::exists($value)) && ! File::exists($value)) {
$fail(trans(':attribute n\'existe pas dans le système de fichier', [
'attribute' => $attribute
'attribute' => $attribute,
]));
return;
}
/** @var string */
$fileFullPath = self::getUploadedFileFullPath($value, $useStorage);
if (
!\in_array(self::getUploadedFileMimeType($value, $useStorage), [
'image/jpeg', 'image/jpg', 'image/png', 'image/gif'
! \in_array(self::getUploadedFileMimeType($value, $useStorage), [
'image/jpeg', 'image/jpg', 'image/png', 'image/gif',
])
) {
$fail(trans(':attribute doit être une image de type \'image/jpeg,image/jpg,image/png,image/gif\'', [
'attribute' => $attribute
'attribute' => $attribute,
]));
}
if (!($dimensions = \getimagesize($fileFullPath))) {
if (! ($dimensions = \getimagesize($fileFullPath))) {
$fail(trans(':attribute impossible de valider les dimensions', [
'attribute' => $attribute
'attribute' => $attribute,
]));
} else {
$width = intval($dimensions[0]);
@ -499,60 +461,52 @@ class ToolboxHelper
$yMessage = trans(":attribute la hauteur doit être de {$sizes->minHeight}px");
}
if (($width < $sizes->minWith) or ($width > $sizes->maxWith)) {
if ($width < $sizes->minWith || $width > $sizes->maxWith) {
$fail($xMessage);
}
if (($height < $sizes->minHeight) or ($height > $sizes->maxHeight)) {
if ($height < $sizes->minHeight || $height > $sizes->maxHeight) {
$fail($yMessage);
}
} //end if
} // end if
}
/**
* Get the file full path
*
* @param string $value
* @param boolean $useStorage
* @return string
* Get the file full path.
*/
private static function getUploadedFileFullPath(string $value, bool $useStorage): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
/** @var FilesystemAdapter */
$disk = Storage::disk();
return !$useStorage ? (realpath($value) ?: $value) : $disk->path($value);
return ! $useStorage ? (realpath($value) ?: $value) : $disk->path($value);
}
/**
* Get the file mimetype
* Get the file mimetype.
*
* @param string $value
* @param boolean $useStorage
* @return string|false
*/
private static function getUploadedFileMimeType(string $value, bool $useStorage)
{
return !$useStorage ? File::mimeType($value) : Storage::mimeType($value);
return ! $useStorage ? File::mimeType($value) : Storage::mimeType($value);
}
/**
* Fetch class list from a specific namespace
* Fetch class list from a specific namespace.
*
* @param string $namespace
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingNamespace(string $namespace): array
{
return collect(File::files(\app_path(Str::of($namespace)->replaceFirst('\App', ''))))
->filter(fn (SplFileInfo $splFileInfo) => $splFileInfo->getExtension() === 'php')
return collect(File::files(\app_path(Str::of($namespace)->replaceFirst('\App', '')->value())))
->filter(fn (SplFileInfo $splFileInfo): bool => $splFileInfo->getExtension() === 'php')
->map(fn (SplFileInfo $splFileInfo) => Str::of($splFileInfo->getBasename())
->replace('.' . $splFileInfo->getExtension(), '')->prepend("{$namespace}\\"))->all();
->replace('.' . $splFileInfo->getExtension(), '')->prepend("{$namespace}\\")->value())->all();
}
/**
* Get all classes from a specific namespace that implement a specific interface
* Get all classes from a specific namespace that implement a specific interface.
*
* @param string $namespace
* @param string $interface
* @return array<string> An array of classe namespaces
*/
public static function getClassesUsingInterface(string $namespace, string $interface): array
@ -560,17 +514,12 @@ class ToolboxHelper
return Arr::where(
static::getClassesUsingNamespace($namespace),
fn (string $class_namespace) => \collect(class_implements($class_namespace, true))->filter()
->contains(fn (string $class_interface) => $class_interface === $interface)
->contains(fn (string $class_interface): bool => $class_interface === $interface)
);
}
/**
* Get validated pagination from request.
*
* @param integer $default
* @param string $field
* @param string $enumPath
* @return integer
*/
public static function getValidatedEnum(int $default, string $field, string $enumPath): int
{
@ -579,7 +528,7 @@ class ToolboxHelper
[$field => \request()->get($field)],
[$field => new Enum($enumPath)]
)->validated()[$field]);
} catch (ValidationException $e) {
} catch (ValidationException) {
return $default;
}
}

View file

@ -7,17 +7,8 @@ use LDC\LDCCRM\Models\Customer;
class MesBr
{
private $endpoint;
private $refCamp;
private $refSite;
public function __construct($endpoint, $refCamp, $refSite)
public function __construct(private $endpoint, private $refCamp, private $refSite)
{
$this->endpoint = $endpoint;
$this->refCamp = $refCamp;
$this->refSite = $refSite;
}
public function createCustomer(string $hashcode, string $email, string $country)
@ -43,70 +34,63 @@ class MesBr
public function printCoupons(string $hashcode, array $coupons)
{
return $this->callApi("GET", '/coupons/print', [
'refcamp' => $this->refCamp,
'refcamp' => $this->refCamp,
'hashcode' => $hashcode,
'coupons' => implode(",", $coupons)
'coupons' => implode(",", $coupons)
]);
}
private function callApi($method, $url, $data = [], $path = '/api/v1')
private function callApi(string $method, string $url, array $data = [], string $path = '/api/v1')
{
$url = $this->endpoint . $path . $url;
$curl = curl_init();
switch ($method) {
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data) {
curl_setopt($curl, CURLOPT_POST, true);
if ($data !== []) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
break;
case "PUT":
case "DELETE":
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
if ($data !== []) {
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));
}
break;
default:
if ($data) {
if ($data !== []) {
$url = sprintf("%s?%s", $url, http_build_query($data));
}
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_COOKIESESSION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($curl);
if (!curl_errno($curl)) {
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (!in_array($http_code, [200, 201])) {
return (object)[
'status' => 'KO',
'code' => 500,
'status' => 'KO',
'code' => 500,
'message' => 'Connection to API failed - Code HTTP : ' . $http_code
];
}
} else {
return (object)[
'status' => 'KO',
'code' => 500,
'status' => 'KO',
'code' => 500,
'message' => 'Connection to API failed : ' . curl_error($curl)
];
}
curl_close($curl);
return json_decode($result);
}
// * Added METHODS
/** .
* Update panier
*
* @param \LDC\LDCCRM\Models\Customer $crm_customer
* @param string $crm_customer_hashcode
* @param string $ref_site
* @param string $coupon_id
* @param integer $quantity
* @return array<string,string>
* @return array<string,int|string>
* 'status' => 'OK',
* 'link' => URL,
* 'verif' => URL,
@ -120,16 +104,16 @@ class MesBr
int $quantity = 1
): array {
$res = $this->callAPI("GET", "/updatebasket", [
'refsite' => $ref_site,
'refcamp' => $this->refCamp,
'refsite' => $ref_site,
'refcamp' => $this->refCamp,
'refadherent' => $crm_customer_hashcode,
'nom' => $crm_customer->lastname,
'prenom' => $crm_customer->firstname,
'email' => $crm_customer->email,
'nom' => $crm_customer->lastname,
'prenom' => $crm_customer->firstname,
'email' => $crm_customer->email,
'code_postal' => $crm_customer->postcode,
'commune' => $crm_customer->city,
'pays' => $this->getCountry($crm_customer->country),
'panier' => "({$quantity}){$coupon_id}"
'commune' => $crm_customer->city,
'pays' => $this->getCountry($crm_customer->country),
'panier' => "({$quantity}){$coupon_id}"
], '');
if (!$res) {
@ -148,10 +132,7 @@ class MesBr
/** .
* Update basket
*
* @param \LDC\LDCCRM\Models\Customer $crm_customer
* @param string $coupon_id
* @param integer $quantity
* @return array<string,string>
* @return array<string,int|string>
* 'status' => 'OK',
* 'ref' => 'Basket REF'
*/
@ -161,10 +142,10 @@ class MesBr
int $quantity = 1
): array {
$res = $this->callAPI("GET", "/customer/basket/update", [
'refsite' => $this->refSite,
'refcamp' => $this->refCamp,
'refsite' => $this->refSite,
'refcamp' => $this->refCamp,
'customer_hashcode' => $crm_customer->hashcode,
'panier' => "({$quantity}){$coupon_id}"
'panier' => "({$quantity}){$coupon_id}"
]);
if (!$res) {
@ -181,15 +162,14 @@ class MesBr
/**
* Get customer basket
*
* @param \LDC\LDCCRM\Models\Customer $crm_customer
* @return array
*
* @return array<string,int|string>
*/
public function getBasket(
Customer $crm_customer,
): array {
$res = $this->callApi("GET", '/customer/basket', [
'refcamp' => $this->refCamp,
'refcamp' => $this->refCamp,
'hashcode' => $crm_customer->hashcode
]);
@ -208,15 +188,12 @@ class MesBr
/**
* Translate country to MesBr Country
*
* @param \LDC\LDCCRM\Enums\Country $country
* @return inteager
*/
private function getCountry(Country $country): int
{
return match ($country) {
Country::belgium() => 56,
Country::luxembourg() => 442,
Country::belgium => 56,
Country::luxembourg => 442,
// * France
default => 250
};

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Mail\Jobs;
use Illuminate\Bus\Queueable;
@ -17,11 +19,10 @@ class CodeExportSucceed extends Mailable
/**
* Create a new message 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 string $archive_path Nom de l'archive crée.
* @return void
* @param int $nbCodeFic Nombre de codes par fichiers.
* @param int $nbCodeTot Nombre total de codes à exporter.
* @param int $nbCodeLigne Nombre de codes par ligne.
* @param string $archive_path Nom de l'archive crée.
*/
public function __construct(
protected int $nbCodeFic,
@ -33,10 +34,8 @@ class CodeExportSucceed extends Mailable
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
public function envelope(): Envelope
{
return new Envelope(
to: [new Address(\config('mail.admin'))],
@ -46,18 +45,16 @@ class CodeExportSucceed extends Mailable
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): \Illuminate\Mail\Mailables\Content
public function content(): Content
{
return new Content(
markdown: 'mails.jobs.code-unique-export-succeed',
with: [
'archive_path' => $this->archive_path,
'nbCodeFic' => $this->nbCodeFic,
'nbCodeTot' => $this->nbCodeTot,
'nbCodeLigne' => $this->nbCodeLigne
'nbCodeFic' => $this->nbCodeFic,
'nbCodeTot' => $this->nbCodeTot,
'nbCodeLigne' => $this->nbCodeLigne,
],
);
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Mail\Jobs;
use Illuminate\Bus\Queueable;
@ -17,9 +19,8 @@ class CodeGenerationSucceed extends Mailable
/**
* Create a new job instance.
*
* @param integer $amount Amount of code to generate.
* @param string $gameName The game name.
* @return void
* @param int $amount Amount of code to generate.
* @param string $gameName The game name.
*/
public function __construct(private int $amount, private string $gameName)
{
@ -27,10 +28,8 @@ class CodeGenerationSucceed extends Mailable
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
public function envelope(): Envelope
{
return new Envelope(
to: [new Address(\config('mail.admin'))],
@ -40,16 +39,14 @@ class CodeGenerationSucceed extends Mailable
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): \Illuminate\Mail\Mailables\Content
public function content(): Content
{
return new Content(
markdown: 'mails.jobs.code-generation-succeed',
with: [
'quantity' => $this->amount,
'gameName' => $this->gameName
'gameName' => $this->gameName,
],
);
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Mail\Jobs;
use Illuminate\Bus\Queueable;
@ -16,10 +18,6 @@ class JobFailed extends Mailable
/**
* Create a new message instance.
*
* @param string $job
* @param array $exception
* @return void
*/
public function __construct(public string $job, public array $exception)
{
@ -27,10 +25,8 @@ class JobFailed extends Mailable
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
public function envelope(): Envelope
{
return new Envelope(
to: [new Address(\config('mail.admin'))],
@ -40,16 +36,14 @@ class JobFailed extends Mailable
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): \Illuminate\Mail\Mailables\Content
public function content(): Content
{
return new Content(
markdown: 'mails.jobs.failure',
with: [
'job' => $this->job,
'exception' => $this->exception
'job' => $this->job,
'exception' => $this->exception,
],
);
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Mail;
use App\Models\Settings;
@ -14,24 +16,12 @@ class PasswordReset extends Mailable implements ShouldQueue
use Queueable;
use SerializesModels;
/** @var \App\Models\User */
protected $user;
/** @var \App\Models\Settings */
protected $settings;
/**
* Create a new message instance.
*
* @param \App\Models\User $user
* @param \App\Models\Settings $settings
*
* @return void
*/
public function __construct(User $user, Settings $settings)
public function __construct(protected User $user, protected Settings $settings)
{
$this->user = $user;
$this->settings = $settings;
}
/**
@ -41,8 +31,11 @@ class PasswordReset extends Mailable implements ShouldQueue
*/
public function build()
{
/** @var view-string $view */
$view = 'front.basic.password-email';
return $this->subject('Changement de votre mot de passe')
->view('front.basic.password-email')
->with(['user' => $this->user, 'settings' => $this->settings]);
->view($view)
->with(['user' => $this->user, 'settings' => $this->settings]);
}
}

View file

@ -1,9 +1,10 @@
<?php
declare(strict_types=1);
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;
@ -21,13 +22,11 @@ class DiscountWonMail extends Mailable implements ShouldQueue
/**
* Create a new message instance.
*
* @param \Illuminate\Mail\Mailables\Address $recipient The winner.
* @param string $game_name The game name.
* @param \App\Lib\Discount $discount Amount of the won discount in cents.
* @param string $print_url Url to print the discount.
* @param string $company The company name.
*
* @return void
* @param Address $recipient The winner.
* @param string $game_name The game name.
* @param Discount $discount Amount of the won discount in cents.
* @param string $print_url Url to print the discount.
* @param string $company The company name.
*/
public function __construct(
protected Address $recipient,
@ -35,40 +34,41 @@ class DiscountWonMail extends Mailable implements ShouldQueue
protected Discount $discount,
protected string $print_url,
protected string $company
) {}
) {
}
/**
* Get the message envelope.
*
* @return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope(): \Illuminate\Mail\Mailables\Envelope
public function envelope(): Envelope
{
return new Envelope(
to: [$this->recipient],
subject: trans('front.mails.subjects.game_winning', [
'game_name' => $this->game_name
'game_name' => $this->game_name,
]),
);
}
/**
* Get the message content definition.
*
* @return \Illuminate\Mail\Mailables\Content
*/
public function content(): \Illuminate\Mail\Mailables\Content
public function content(): Content
{
// * Fix for async usage
View::addNamespace('theme', resource_path('theme/mounted/views'));
/** @var view-string $view */
$view = 'theme::mails.won-discount';
return new Content(
markdown: 'theme::mails.won-discount',
markdown: $view,
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,
],
);
}

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