Tons of fixes Refonte SYNC 1

- Authentification System OK
This commit is contained in:
Jérémy Munsch 2022-11-16 12:47:41 +01:00
parent ffe82091fb
commit f99e259bd5
No known key found for this signature in database
GPG key ID: 18361AF3BFEC0FEC
122 changed files with 3902 additions and 2907 deletions

View file

@ -0,0 +1,127 @@
<?php
namespace App\Auth;
use App\Models\Customer;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider as UserProviderContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
/**
* Crm user provider to integrate nicely with Laravel
*/
class CrmUserProvider extends EloquentUserProvider implements UserProviderContract
{
/**
* The model to use, specified in auth config guard.
* @var string
*/
private $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);
try {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
// Si pas de compte local.
if (!$model) {
$crmCustomer = $crm->getCustomer($credentials['email']);
if (!$crmCustomer) {
// * Customer is not found on CRM.
return null;
}
$model = $this->createLocalCustomer($crmCustomer);
}
return $model;
} catch (ApiErrorException $e) {
// * Customer is not found.
if ($e->getApiError()->equals(ApiError::modelNotFound())) {
return null;
}
report($e);
Log::error('Unhandled response ', $e->getApiError()->label);
} 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.');
return null;
}
/**
* Validate a user against the given credentials.
*
* @param UserContract $model
* @param array $credentials
* @return boolean
* @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.
*/
public function validateCredentials(UserContract $model, array $credentials)
{
// FIXME: handle customerNotActivated
/** @var \Illuminate\Database\Eloquent\Model $model */
if (\get_class($model) !== $this->usedModel) {
throw new \RuntimeException('Cannot authenticate any thing else than ' . $this->usedModel);
}
try {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
// * Ne devrait pas survenir, $crm->login throws ApiError::authFailed()
return $crm->login($credentials['email'], $credentials['password']);
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::authFailed())) {
return false;
}
} catch (CrmException | \Exception $e) {
report($e);
if (config('app.debug')) {
throw $e;
}
return false;
} //end try
Log::error(__CLASS__ . '::validateCredentials reached end of possibilities.');
return null;
}
/**
* Create a local customer
*
* @param \LDC\LDCCRM\Models\Customer $crmCustomer
* @return \Illuminate\Database\Eloquent\Model
* @throws \Throwable If saving to DB failed.
*/
private function createLocalCustomer(\LDC\LDCCRM\Models\Customer $crmCustomer): Model
{
/** @var \Illuminate\Database\Eloquent\Model $model */
$model = $this->usedModel::firstOrNew([
'email' => $crmCustomer->email
]);
$model->hashcode = $crmCustomer->hashcode;
$model->saveOrFail();
return $model;
}
}

38
app/Enums/UserRole.php Normal file
View file

@ -0,0 +1,38 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self admin()
* @method static self user()
*/
final class UserRole extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'admin' => 1,
'user' => 0,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'admin' => trans('Administrateur'),
'user' => trans('Utilisateur'),
];
}
}

104
app/Enums/WeightUnit.php Normal file
View file

@ -0,0 +1,104 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self g()
* @method static self kg()
* @method static self ton()
*/
final class WeightUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'g' => 'G',
'kg' => 'KG',
'ton' => 'T',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'g' => __('g'),
'kg' => __('kg'),
'ton' => __('t'),
];
}
/**
* Enum conversions a partir de gramme
*
* @return array
*/
protected static function conversions(): array
{
return [
'g' => 1,
'kg' => 1000,
'ton' => 1000000,
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'conversion') {
return $this->conversions()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
}
/**
* Format weight into a displayable unit.
* @param integer $weightG The weight in grams.
* @param \App\Enums\WeightUnit|null $forceUnit To force specific unit display.
* @return string
*/
public static function getDisplayableWeight(int $weightG, self $forceUnit = null): string
{
if ($forceUnit) {
number_format(round($weightG / $forceUnit->conversion, 2), 2, ',', '') . $forceUnit->label;
}
$displayable = self::convertWeightToAdequatUnit($weightG);
return \number_format($displayable->value, 2, ',', '') . ' ' . $displayable->enum->label;
}
/**
* Convert value to the adequat unit
* @param integer $weightG The weight in grams.
* @return object<'enum'|'value',self>
*/
private function convertWeightToAdequatUnit(int $weightG): object
{
foreach (self::conversions() as $key => $conversionRate) {
$convertedTest = round($weightG / $conversionRate, 2);
if ($convertedTest > 0.001 and $convertedTest <= 1000) {
return (object)[
'enum' => self::make($key),
'value' => $convertedTest
];
}
}
return (object)[
'enum' => \collect(self::toArray())->last(),
'value' => $weightG
];
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Events;
use Illuminate\Auth\Events\Verified;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class LogVerifiedUser
{
/**
* Handle the given event.
*
* @param \Illuminate\Auth\Events\Verified $event
* @return void
*/
public function handle(Verified $event)
{
/** @var \App\Models\Customer */
$customer = $event->user;
Auth::guard('frontend')->login($customer);
Session::flash('success', trans('Votre compte à bien été validé, vous êtes à présent connecté.'));
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Events;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class PasswordHasReset
{
/**
* Handle the given event.
*
* @param \Illuminate\Auth\Events\PasswordReset $event
* @return void
*/
public function handle(PasswordReset $event)
{
/** @var \App\Models\Customer */
$customer = $event->user;
Auth::guard('frontend')->login($customer);
Session::flash('success', trans('Votre mot de passe à bien été réinitialisé, vous êtes à présent connecté.'));
}
}

View file

@ -3,7 +3,6 @@
namespace App\Exceptions;
use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
class Handler extends ExceptionHandler
{
@ -23,40 +22,4 @@ class Handler extends ExceptionHandler
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
}
/**
* Handle unauthenticated as exception
*
* @param mixed $request
* @param AuthenticationException $exception
* @return \Illuminate\Http\RedirectResponse
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
$guard = $exception->guards()[0];
switch ($guard) {
case 'customer':
$login = 'auth.connexion';
break;
default:
$login = 'auth.login';
break;
}
// Cf. issue :
// Https://stackoverflow.com/questions/52417438/laravel-unauthenticated-redirect-issue-with-multiple-authentication.
// Session::forget('url.intented'); .
return redirect()->guest(route($login));
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* Display login form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
return view('auth.login-bo');
}
/**
* Compute login form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required',
'password' => 'required'
]);
$credentials = $request->only(['email', 'password']);
if (Auth::attempt($credentials)) {
$user = Auth::user();
if ($user->role === 1 || $user->role === 2) {
return redirect()->route('bo.dashboard');
} else {
if ($user->email_verified_at === null) {
Auth::logout();
return view('auth.login')->withErrors('Compte non activé.');
}
return redirect()->intended('/');
}
}
return view('auth.login')->withErrors('Informations incorrectes.');
}
/**
* logout
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function logout(Request $request)
{
// @phpcs:enable
Auth::logout();
return redirect()->route('home');
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class RegisterController extends Controller
{
/**
* Display register form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
return view('auth.register');
}
/**
* Compute regioster form
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*
*/
public function register(Request $request)
{
$request->validate([
'lastname' => 'required|string|max:255',
'firstname' => 'required|string|max:255',
'date_of_birth' => 'required|date|before_or_equal:' . Carbon::now()->subYears(18)->toDateString(),
'email' => 'required|string|email|max:255|unique:users|confirmed',
'password' => 'required|string|min:8',
]);
User::create([
'firstname' => $request->firstname,
'lastname' => $request->lastname,
'date_of_birth' => $request->date_of_birth,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => 3
]);
// Envoi mail activation.
return redirect()->route('auth.register')->with('success', 'Mail envoyé pour confirmation');
}
}

View file

@ -1,23 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
/**
* Display reset password form
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
return view('auth.reset-password');
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
use AuthenticatesUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('backend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\View\View
*/
public function showLoginForm()
{
return view('back.auth.login');
}
/**
* Where to redirect users after login.
* @return string
*/
protected function redirectTo(): string
{
return \route('bo.dashboard');
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Bo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Support\Facades\Auth;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('backend');
}
/**
* Where to redirect users after login.
* @return string
*/
protected function redirectTo(): string
{
return \route('bo.login');
}
}

View file

@ -18,7 +18,7 @@ class DownloadController extends Controller
*/
public function download(string $filePath): StreamedResponse
{
if (!\auth()->check()) {
if (!\auth('backend')->check()) {
abort(404);
}
/** @var \Illuminate\Filesystem\FilesystemAdapter */

View file

@ -16,7 +16,7 @@ use App\Mail\OrderStatusMail;
use App\Mail\OrderConfirmMail;
use App\Mail\OrderConfirmAdminMail;
use App\Jobs\CsvOrderWrite;
use App\Models\Customers;
use App\Models\Customer;
use App\Models\Setting;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
@ -583,7 +583,7 @@ class OrderController extends Controller
*/
public function getCustomerInfo(int $id, string $mode = null)
{
$cust = Customers::findOrFail($id);
$cust = Customer::findOrFail($id);
if (!is_null($mode)) {
if ($mode == "tippy") {
return view('back.customer.tippy-card', compact('cust'));

View file

@ -44,18 +44,9 @@ class ProductController extends Controller
$this->searchQuery(
$products,
$request->search,
// * Search in ranges
function ($q) use ($request) {
$q->orWhereHas('ranges', function ($q) use ($request) {
$q->whereRaw(
'UPPER(CONCAT(`ranges`.`name`)) LIKE UPPER(?)',
['%' . filter_var($request->range, FILTER_SANITIZE_STRING) . '%']
);
});
},
null,
'name',
'description',
'gamme',
'reference'
);
} //end if

View file

@ -1,628 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Fo\CustomerRegisterRequest;
use App\Http\Requests\Fo\LoginRequest;
use App\Http\Requests\Fo\UpdateInfosRequest;
use App\Mail\PasswordResetMail;
use App\Mail\RegisterConfirmMail;
use App\Models\Customers;
use App\Models\Shop;
use App\Models\Order;
use App\Models\PasswordReset;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Enums\Country;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
use LDC\LDCCRM\Models\SingleOptin;
class CustomerController extends Controller
{
/**
* Affiche la page d'inscription
*
* @return \Illuminate\Http\Response
*/
public function registerForm()
{
if (Auth::guard('customer')->check()) {
return redirect()->route('home');
}
return view('auth.register');
}
/**
* Enregistre le customer dans la base de données
* et CRM
*
* @param \App\Http\Requests\Fo\CustomerRegisterRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function register(CustomerRegisterRequest $request)
{
try {
return DB::transaction(function () use ($request) {
$crmCustomer = new CrmCustomer(\array_intersect_key(
$request->validated(),
[
'firstname' => '',
'lastname' => '',
'birth_date' => '',
'address' => '',
'postcode' => '',
'country' => '',
'city' => '',
'phone' => '',
'email' => '',
'password' => '',
'opt_in' => '',
]
));
$crmCustomer->city = $request->town;
$crmCustomer->postcode = $request->postal_code;
$crmCustomer->opt_in = $request->optin ? true : false;
// * Country Is forced to france
$crmCustomer->country = Country::france();
$crmCustomer->save();
// * Enrregistrement du Hashcode
/** @var \App\Models\Customers $customer */
$customer = new Customers($request->all());
$customer->hashcode = $crmCustomer->hashcode;
$customer->saveOrFail();
Mail::to($request->email)->send(new RegisterConfirmMail($customer, $request->firstname));
return view('auth.register-success', ['mail' => $request->email]);
});
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerExists())) {
return back()->withInput()->withErrors('Un compte est déjà associé à cette adresse email.');
}
if ($e->getApiError()->equals(ApiError::validationFailed())) {
return back()->withInput()->withErrors($e->getMessage());
}
report($e);
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} catch (ModelNotFoundException $e) {
return back()->withErrors("Salarié $request->matricule, $request->code introuvable.");
} catch (ValidationException $e) {
return back()->withErrors($e->getMessage());
} catch (\Exception | \TypeError | CrmException $e) {
report($e);
// * Une erreur critique est survenue avec la CRM
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} //end try
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
}
/**
* Retourne la page de connexion
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function authenticateForm(Request $request)
{
if (Auth::guard('customer')->check()) {
return redirect()->route('account.dashboard');
}
if ($request->get('url')) {
Session::put('url.intended', $request->get('url'));
Session::save();
}
return view('auth.login');
}
/**
* Gère l'authentification des customers
*
* @param \App\Http\Requests\Fo\LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function authenticate(LoginRequest $request)
{
/** @var Customers|null */
$customer = Customers::where('email', $request->email)->first();
try {
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
// * Ne devrait pas survenir, $crm->login throws ApiError::authFailed()
if (!$crm->login($request->email, $request->password)) {
// * mauvais identifiants
return back()->withErrors('Email ou mot de passe incorrect.');
}
// Dans tout les cas : récupérer le crm user.
$crmCustomer = $crm->getCustomer($request->email);
// Si pas de compte local mais existe en CRM.
if (!$customer) {
$customer = $this->createLocalCustomer($crmCustomer);
}
return $this->processLogin($customer, $request->remember);
} catch (ApiErrorException $e) {
return $this->handleLoginApiErrorException($e, $request);
} catch (CrmException | \Exception $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} //end try
}
/**
* Active le compte d'un customer
*
* @param mixed $hashcode
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function activate($hashcode)
{
$hashcode = filter_var($hashcode, FILTER_SANITIZE_STRING);
if (!($customer = Customers::select('hashcode')->where('hashcode', $hashcode)->first())) {
return view('auth.activate')->with('message', 'Ce lien d\'activation n\'est pas valide.');
}
try {
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->apiData;
if ($crmCustomer->activate()) {
return view('auth.activate')
->with('message', 'Votre compte a bien été activé, vous pouvez désormais vous connecter.');
}
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerAlreadyActive())) {
// phpcs:disable Generic.Files.LineLength.TooLong
return view('auth.activate')
->with('message', 'Votre compte est déjà activé, vous pouvez dès à présent vous connecter avec celui-ci.');
}
report($e);
// Une erreur critique est survenue avec la CRM.
return view('auth.activate')
->with('message', 'Une erreur est survenue, merci de réessayer ultérieurement.');
} catch (CrmException $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return view('auth.activate')
->with('message', 'Une erreur est survenue, merci de réessayer ultérieurement.');
} //end try
return view('auth.activate')->with('message', 'Ce lien d\'activation n\'est pas valide.');
}
/**
* deconnexion customer
*
* @return \Illuminate\Http\RedirectResponse
*/
public function logout()
{
if (Auth::guard('customer')->check()) {
Auth::guard('customer')->logout();
return redirect()->route('home');
}
}
/**
* page d'accès à la procédure mdp oublié
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function forgotPasswordForm(Request $request)
{
// @phpcs:enable
return view('auth.passwords.forgot');
}
/**
* initialisation de la procédure de mdp oublié
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function passwordReset(Request $request)
{
$request->validate([
'email' => 'required|email:rfc|max:255',
]);
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$customerExist = Customers::where('email', $email)->count() === 0 ? false : true;
if ($customerExist) {
// Suppression des anciens tokens.
PasswordReset::where('email', $email)->delete();
$token = Str::random(80);
$passwordReset = new PasswordReset();
$passwordReset->email = $email;
$passwordReset->token = $token;
$passwordReset->created_at = now();
$passwordReset->save();
Mail::to($email)->send(new PasswordResetMail($token));
return back()->with(
'success',
__("La procédure pour changer votre mot de passe a été envoyé sur votre email.")
);
} else {
return back()->withErrors(__("Cette adresse email n'est pas enregistrée dans nos services."));
}
}
/**
* Print the view used to change the password of a user when he required it
* @param string $token Token used to identificate the user to change the password.
* @return \Illuminate\Contracts\View\View The view to print
*/
public function viewChangePassword(string $token)
{
$pwdReset = PasswordReset::where('token', $token)->first();
if (!$pwdReset) {
abort(419);
}
$customer = Customers::where('email', $pwdReset->email)->first();
if (!$customer) {
abort(404);
}
return view('auth.passwords.reset', compact('customer'))->with('token', $token);
}
/**
* changement de mot de passe (depuis la procédure de mdp oublié)
*
* @param Request $request
* @param mixed $token
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function changePassword(Request $request, $token)
{
$request->validate([
'password' => 'required|confirmed|min:8|max:255'
]);
/** @var \App\Models\PasswordReset $passwordReset */
$passwordReset = PasswordReset::where('token', $token)->first();
/** @var \App\Models\Customers $customer */
$customer = Customers::where('email', optional($passwordReset)->email)->firstOrFail();
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
try {
if ($crm->login($customer->email, $request->password)) {
return back()->withErrors('Vous ne pouvez pas utiliser un de vos anciens mot de passe.');
}
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch (ApiErrorException $e) {
// * continue
}
try {
if (!($crmCustomer = $crm->getCustomer($customer->hashcode))) {
return back()
->withErrors('Changement impossible, compte inexistant.');
}
$crmCustomer->password = $request->password;
$crmCustomer->save();
} catch (\TypeError | CrmException | ApiErrorException $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} catch (ValidationException $e) {
return back()->withErrors(sprintf('Nouveau mot de passe : %s', $e->getMessage()));
}
$passwordReset->deleteOrFail();
return view('auth.passwords.reset-ok', compact('customer'));
}
/**
* profil client (mon compte)
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function dashboard(Request $request)
{
// @phpcs:enable
// Show Profile.
/** @var \App\Models\Customers|null $customer */
$customer = Auth::guard('customer')->user();
if (!$customer) {
abort(404);
}
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->apiData;
/** @var object $crmInfos */
$crmInfos = \json_decode(\json_encode($crmCustomer));
$birthDate = explode('-', $crmCustomer->birth_date);
return view('customer.dashboard', compact('customer', 'crmInfos', 'birthDate'));
}
/**
* Send a request to the CRM to change the given customer's informations
* @param \App\Http\Requests\Fo\UpdateInfosRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function updateInfos(UpdateInfosRequest $request)
{
if (($result = $this->updateUserInfo($request)) === true) {
return back()->with('success', trans(
'Vos informations ont bien été enregistrées.'
));
}
return back()->withInput()->withErrors(
$result !== false ? $result : trans(
'Une erreur est survenue, merci de réessayer ultérieurement.'
)
);
}
/**
* changement de mot de passe depuis le profil
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function changePasswordWhenAuthenticated(Request $request)
{
$request->validate([
'password' => 'required|min:8|confirmed|max:255'
]);
$crm = app('crmApi');
$customer = $crm->changePwdCustomer(Auth::guard('customer')->user()->crm_id, $request->password);
if ($customer->status === "OK") {
return back()->with('success', __("Modifications enregistrés !"));
} else {
return back()->withErrors(__("Une erreur est survenue, merci de réessayer ultérieurement."));
}
}
/**
* affichage des commandes du customer connecté
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function myOrders(Request $request)
{
// @phpcs:enable
$orders = Order::where('customer_id', Auth::guard('customer')->user()->id)->orderBy('date', 'DESC')->get();
return view('customer.my-orders', compact('orders'));
}
/**
* Show detail commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(string $reference)
{
$order = Order::where('reference', '=', $reference)
->where('customer_id', Auth::guard('customer')->user()->id)
->firstOrFail();
if ($order->status_id == 1) {
$shop = null;
} else {
$shop = Shop::find($order->timeslot->shop->id);
}
return view('customer.order-detail', compact('order', 'shop'));
}
/**
* delete customers
*
* @param integer $id
* @return void
*/
public function delete(int $id)
{
dump('deleting' . $id);
die('Not implemented yet');
}
/**
* Create a local customer
*
* @param CrmCustomer $crmCustomer
* @return \app\Models\Customers
* @throws \Throwable If saving to DB failed.
*/
private function createLocalCustomer(CrmCustomer $crmCustomer): Customers
{
/** @var \App\Models\Customers $customer */
$customer = Customers::firstOrNew([
'email' => $crmCustomer->email
]);
$customer->hashcode = $crmCustomer->hashcode;
$customer->saveOrFail();
return $customer;
}
/**
* Handle Api error exception
*
* @param ApiErrorException $e
* @param LoginRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
private function handleLoginApiErrorException(ApiErrorException $e, LoginRequest $request)
{
/** @var Customers|null */
$customer = Customers::where('email', $request->email)->first();
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
switch ($e->getApiError()) {
case ApiError::authFailed():
// * mauvais identifiants
return back()->withErrors('Email ou mot de passe incorrect.');
case ApiError::customerNotActivated():
// * le compte n'est pas activé
if (!$customer) {
try {
if (!($crmCustomer = $crm->getCustomer($request->email))) {
Log::critical(sprintf('Customer %s coulnd not be retrieved from crm', $request->email));
return back()->withErrors(
trans('Une erreur est survenue, merci de réessayer ultérieurement.')
);
}
$customer = $this->createLocalCustomer($crmCustomer);
} catch (CrmException | ApiErrorException $e) {
report($e);
// Une erreur critique est survenue avec la CRM.
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
}
}
Mail::to($request->email)->send(new RegisterConfirmMail($customer, null));
return back()
->withErrors(
trans('Votre compte n\'est pas activé, vérifiez dans vos emails pour y trouver
le mail de confirmation. Un lien d\'activation vous a été renvoyé.')
);
default:
report($e);
// * une erreur non gérée
return back()->withErrors('Une erreur est survenue, merci de réessayer ultérieurement.');
} //end switch
}
/**
* Login a customer
*
* @param Customers $customer
* @param boolean $remember
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
*/
private function processLogin(Customers $customer, bool $remember)
{
/** @var \Illuminate\Auth\SessionGuard */
$guard = auth('customer');
$guard->login($customer, $remember);
$url = Session::pull('url.intended');
Session::forget('url.intended');
Session::save();
return !empty($url) ? redirect()->to($url) : redirect()->route('account.dashboard');
}
/**
* Updates user info
*
* @param \App\Http\Requests\Fo\UpdateInfosRequest $request
* @return boolean|string return strictly true if success,
* false il failed, could be a string with message error if failed
*/
private function updateUserInfo(UpdateInfosRequest $request)
{
/** @var \App\Models\Customers */
$customer = auth('customer')->user();
if (!$customer) {
return \redirect()->back();
}
/** @var \LDC\LDCCRM\Models\Customer */
$crmCustomer = $customer->apiData;
$data = [
'firstname' => $request->firstname,
'lastname' => $request->lastname,
'birth_date' => $request->year . '-' . $request->month . '-' . $request->day,
'address' => $request->address,
'postcode' => $request->postcode,
'city' => $request->city,
'phone' => \strval($request->phone),
'opt_in' => \boolval($request->optin),
];
// * remove phone if empty
if (isset($data['phone']) and !strlen($data['phone'])) {
unset($data['phone']);
}
try {
$crmCustomer->fill($data);
$crmCustomer->save();
} catch (ValidationException $e) {
return $e->getMessage();
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::validationFailed())) {
return $e->getMessage();
}
report($e);
return false;
} catch (CrmException | \TypeError $e) {
report($e);
return false;
}
return true;
}
/**
* Call the CRM to add a single optin
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
*/
public function addOptIn(Request $request)
{
$validator = Validator::make($request->all(), ['email' => 'required|email']);
if ($validator->fails()) {
return __('L\'adresse email est incorrecte');
}
try {
SingleOptin::setStatus($request->email, true);
return trans('Votre inscription à la newsletter a été prise en compte');
} catch (ApiErrorException $e) {
// * Il y a deja un customerInfo avec optin a true pour ce customer
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists())) {
return trans('Vous êtes déjà inscrit avec cette adresse email');
}
report($e);
return trans('Une erreur est survenue, merci de réessayer ultérieurement');
} catch (CrmException $e) {
report($e);
return trans('Une erreur est survenue, merci de réessayer ultérieurement');
}
return trans('Une erreur est survenue, merci de réessayer ultérieurement ');
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
use Illuminate\Support\Facades\Auth;
class ConfirmPasswordController extends Controller
{
use ConfirmsPasswords;
/**
* Display the password confirmation view.
*
* @return \Illuminate\View\View
*/
public function showConfirmForm()
{
if (Auth::guard('frontend')->check()) {
return \response()->redirectToRoute('home');
}
return view('front.auth.passwords.confirm');
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('home')->getTargetUrl();
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
{
use SendsPasswordResetEmails;
/**
* Message to user
*
* @var string
*/
private $message =
'Si votre compte existe vous devriez recevoir un message avec un lien pour réinitialiser votre mot de passe';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend');
}
/**
* Display the form to request a password reset link.
*
* @return \Illuminate\View\View
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker('customers');
}
/**
* 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
*/
protected function sendResetLinkResponse()
{
return back()->with('info', trans($this->message));
}
/**
* 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
*/
protected function sendResetLinkFailedResponse(Request $request, string $response)
{
// ! Ne pas notifier que l'utilisateur n'existe pas.
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'))
->withErrors(['email' => trans($response)]);
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class LoginController extends Controller
{
use AuthenticatesUsers;
use ThrottlesLogins;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend')->except('logout');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('frontend');
}
/**
* Show the application's login form.
*
* @return \Illuminate\View\View
*/
public function showLoginForm()
{
return view('auth.login');
}
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param null $user
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing, Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
protected function authenticated(Request $request, $user)
{
/** @var \App\Models\Customer|null */
$user = Auth::guard('frontend')->user();
if ($user) {
$user->saveOrFail();
Session::flash('info', trans('Vous êtes à présent connecté'));
}
// FIXME: Read destroy basket here.
// OrderController::detroyCurrentBasket();
}
/**
* The user has logged out of the application.
*
* @param \Illuminate\Http\Request $request
* @return mixed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
protected function loggedOut(Request $request)
{
Session::flash('info', trans('Vous êtes à présent déconnecté'));
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('home')->getTargetUrl();
}
}

View file

@ -0,0 +1,174 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Customers\CreateAccountRequest;
use App\Models\Customer;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Enums\Country;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\ValidationException as CrmValidationException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
class RegisterController extends Controller
{
use RegistersUsers;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('frontend');
}
/**
* Show the application registration form.
*
* @return \Illuminate\View\View
*/
public function showRegistrationForm()
{
return view('auth.register');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
* @throws \Illuminate\Validation\ValidationException If local client cannot validate data.
*/
protected function validator(array $data)
{
$data['optin'] = \request()->boolean('optin');
$data['ok_cgv'] = \request()->boolean('ok_cgv');
// * Let Crm client validate data
try {
(new CrmCustomer())->fill($this->getOnlyCrmData($data));
} catch (CrmValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
$request = new CreateAccountRequest();
$data['phone'] = $data['phone'] ?? null;
$data['opt_in'] = ($data['opt_in'] ?? false) ? true : false;
return Validator::make(
$data,
$request->rules(),
$request->messages(),
$request->attributes()
);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\Models\Customer
* @throws \Illuminate\Validation\ValidationException If there are validation errors.
* @throws \RuntimeException On unhandled api Error.
*/
protected function create(array $data)
{
$data['country'] = Country::france();
try {
return DB::transaction(function () use ($data) {
$crmCustomer = new CrmCustomer($this->getOnlyCrmData($data));
// * Country Is forced to france
$crmCustomer->country = Country::france();
$crmCustomer->save();
// * Enregistrement du Hashcode
/** @var \App\Models\Customer $customer */
$customer = new Customer($data);
$customer->hashcode = $crmCustomer->hashcode;
// * Customer will be logged in.
$customer->last_connection = \now();
$customer->saveOrFail();
if ($customer->saveOrFail()) {
// phpcs:ignore Generic.Files.LineLength.TooLong
Session::flash('success', trans('Votre compte à bien été crée, vous devez valider votre adresse email, un email de confirmation vous à été envoyé.'));
}
return $customer;
});
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerExists())) {
throw ValidationException::withMessages([
'error' => 'Un compte est déjà associé à cette adresse email.'
]);
}
if ($e->getApiError()->equals(ApiError::validationFailed())) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
report($e);
Log::error(__CLASS__ . '::create reached end of possibilities.');
throw new \RuntimeException(__CLASS__ . '::create reached end of possibilities.');
} //end try
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('account.dashboard')->getTargetUrl();
}
/**
* Get only data used in crm
*
* @param array $data
* @return array
*/
private function getOnlyCrmData(array $data): array
{
return \array_intersect_key(
$data,
[
'firstname' => '',
'lastname' => '',
'birth_date' => '',
'address' => '',
'postcode' => '',
'country' => '',
'city' => '',
'phone' => '',
'email' => '',
'password' => '',
'opt_in' => '',
]
);
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\PasswordReset;
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\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException as ExceptionsValidationException;
class ResetPasswordController extends Controller
{
use ResetsPasswords;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest:frontend');
}
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard('frontend');
}
/**
* 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\Factory|\Illuminate\View\View
*/
public function showResetForm(Request $request)
{
$token = $request->route()->parameter('token');
return view('auth.passwords.reset')->with(
['token' => $token, 'email' => $request->email]
);
}
/**
* Reset the given user's password.
*
* @param mixed $customer
* @param mixed $password
* @return void
* @throws \Illuminate\Validation\ValidationException If something goes wrong.
*/
protected function resetPassword($customer, $password)
{
/** @var \App\Models\Customer $user */
/** @var string $password */
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
try {
if (!($crmCustomer = $crm->getCustomer($customer->hashcode))) {
throw ValidationException::withMessages([
'error' => 'Changement impossible, compte inexistant.'
]);
}
$crmCustomer->password = $password;
$crmCustomer->save();
} catch (\TypeError | CrmException | ApiErrorException $e) {
report($e);
throw ValidationException::withMessages([
'error' => 'Une erreur est survenue, merci de réessayer ultérieurement.'
]);
} catch (ExceptionsValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
$customer->setRememberToken(Str::random(60));
$customer->save();
event(new PasswordReset($customer));
$this->guard()->login($customer);
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('home')->getTargetUrl();
}
/**
* Get the broker to be used during password reset.
*
* @return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker('customers');
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Fo\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Request;
class VerificationController extends Controller
{
use VerifiesEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:frontend');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Show the email verification notice.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function show(Request $request)
{
return $request->user()->hasVerifiedEmail()
? redirect($this->redirectPath())
: view('auth.verify');
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('home')->getTargetUrl();
}
}

View file

@ -1,8 +1,9 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Fo;
use App\Enums\GeneralConfigType;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@ -111,8 +112,8 @@ class BasketController extends Controller
$row->setDiscountRate($product->discountRate);
$cartextendedItem = array(
'cartItem' => $row,
'product' => $product
'cartItem' => $row,
'product' => $product
);
$dbpanier->add($cartextendedItem);
}
@ -149,21 +150,19 @@ class BasketController extends Controller
'countItems' => Cart::count(),
);
$cartReturn = array($dbpanier,$cartInfosNoPrice);
$cartReturn = array($dbpanier, $cartInfosNoPrice);
/** @var \App\Models\GeneralConfig */
$config = Cache::get('GeneralConfigGlobal')->where('type', GeneralConfigType::global())->first();
if (
$config->data['orders_display_prices_for_auth']['value'] and
Auth::guard('customer')->check()
) {
// ? Don't show prices only for auth customers ?
if (!$config->data['orders_display_prices_for_auth']['value']) {
return $cartReturn;
}
// ? Customer is auth ?
if (Auth::guard('frontend')->check()) {
$cartReturn = array($dbpanier, $cartInfos);
}
if ($config->data['orders_display_prices_for_public']['value']) {
$cartReturn = array($dbpanier,$cartInfos);
}
return $cartReturn;
}
}

View file

@ -0,0 +1,279 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Customers\ConfirmDeleteRequest;
use App\Http\Requests\Fo\Customers\UpdateEmailRequest;
use App\Http\Requests\Fo\Customers\UpdatePasswordRequest;
use App\Http\Requests\Fo\Customers\UpdateInfosRequest;
use App\Models\Customer;
use App\Models\Shop;
use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Exceptions\ValidationException as CrmValidationException;
use LDC\LDCCRM\Models\SingleOptin;
class CustomerController extends Controller
{
// * --- Backoffice ---
/**
* profil client (mon compte)
*
* @return \Illuminate\Contracts\View\View
*/
public function dashboard()
{
// Show Profile.
/** @var \App\Models\Customer|null $customer */
$customer = Auth::guard('frontend')->user();
if (!$customer) {
abort(404);
}
/** @var \LDC\LDCCRM\Models\Customer $crmCustomer */
$crmCustomer = $customer->apiData;
/** @var object $crmInfos */
$crmInfos = \json_decode(\json_encode($crmCustomer));
return view('customer.dashboard', compact('customer', 'crmInfos'));
}
/**
* Show update customer informations form
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function email()
{
return view('auth.change-email');
}
/**
* Uupdate customer email
*
* @param \App\Http\Requests\Fo\Customers\UpdateEmailRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function emailSubmit(UpdateEmailRequest $request)
{
/** @var \App\Models\Customer */
$customer = \auth('frontend')->user();
// FIXME:Add warning email to customer !!!
return $this->updateCrmCustomerInfo($customer, ['email' => $request->get('email')]) ?
back()->with('success', trans('Vos informations ont bien été enregistrées.')) :
back()->withInput()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Show update customer password form
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function password()
{
return view('auth.passwords.change');
}
/**
* Update customer password
*
* @param UpdatePasswordRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function passwordSubmit(UpdatePasswordRequest $request)
{
/** @var \App\Models\Customer */
$customer = \auth('frontend')->user();
// FIXME:Add warning email to customer !!!
return $this->updateCrmCustomerInfo($customer, ['password' => $request->get('password')]) ?
back()->with('success', trans('Vos informations ont bien été enregistrées.')) :
back()->withInput()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Send a request to the CRM to change the given customer's informations
* @param \App\Http\Requests\Fo\UpdateInfosRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function updateInfos(UpdateInfosRequest $request)
{
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
return $this->updateCrmCustomerInfo($customer, $request->validated()) ?
back()->with('success', trans('Vos informations ont bien été enregistrées.')) :
back()->withInput()->withErrors(trans('Une erreur est survenue, merci de réessayer ultérieurement.'));
}
/**
* Show customer delete account page
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function destroy()
{
return view('customer.destroy');
}
/**
* Show customer delete account page
*
* @param \App\Http\Requests\Fo\Customers\ConfirmDeleteRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function destroySubmit(ConfirmDeleteRequest $request)
{
if ($request->confirm) {
try {
return DB::transaction(function () {
// FIXME:Add warning email to customer !!!
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
/** @var \Ldc\LDCCRM\Models\Customer */
$crmCustomer = $customer->api_data;
$customer->deleteOrFail();
$crmCustomer->delete();
// * Force logout
auth('frontend')->logout();
return response()->redirectToRoute('home')
->with('info', trans(
// phpcs:ignore Generic.Files.LineLength.TooLong
'Votre compte à bien été supprimé, si vous avez des commandes en cours vous pouvez utiliser le formulaire de contact en cas de besoin.'
));
});
} catch (\Exception $e) {
report($e);
Session::flash('error', trans('Une erreur est survenue veuillez réssayer plus tard.'));
} //end try
} //end if
return back();
}
/**
* Updates user info
*
* @param \App\Models\Customer $customer
* @param array $data
* @return boolean
* @throws \Illuminate\Validation\ValidationException If something failed.
*/
private function updateCrmCustomerInfo(Customer $customer, array $data)
{
return DB::transaction(function () use ($customer, $data) {
try {
/** @var \LDC\LDCCRM\Models\Customer */
$crmCustomer = $customer->apiData;
if (\array_key_exists('email', $data)) {
$customer->email = $data['email'];
$customer->saveOrFail();
}
$crmCustomer->fill($data);
return $crmCustomer->save();
} catch (CrmValidationException $e) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::validationFailed())) {
throw ValidationException::withMessages([
'error' => $e->getMessage()
]);
}
report($e);
return false;
} //end try
});
}
// * --- END Backoffice ---
/**
* affichage des commandes du customer connecté
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function myOrders(Request $request)
{
// @phpcs:enable
$orders = Order::where('customer_id', Auth::guard('frontend')->user()->id)->orderBy('date', 'DESC')->get();
return view('customer.my-orders', compact('orders'));
}
/**
* Show detail commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(string $reference)
{
$order = Order::where('reference', '=', $reference)
->where('customer_id', Auth::guard('frontend')->user()->id)
->firstOrFail();
if ($order->status_id == 1) {
$shop = null;
} else {
$shop = Shop::find($order->timeslot->shop->id);
}
return view('customer.order-detail', compact('order', 'shop'));
}
/**
* Call the CRM to add a single optin
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
*/
public function addOptIn(Request $request)
{
$validator = Validator::make($request->all(), ['email' => 'required|email']);
if ($validator->fails()) {
return __('L\'adresse email est incorrecte');
}
try {
SingleOptin::setStatus($request->email, true);
return trans('Votre inscription à la newsletter a été prise en compte');
} catch (ApiErrorException $e) {
// * Il y a deja un customerInfo avec optin a true pour ce customer
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists())) {
return trans('Vous êtes déjà inscrit avec cette adresse email');
}
report($e);
return trans('Une erreur est survenue, merci de réessayer ultérieurement');
} catch (CrmException $e) {
report($e);
return trans('Une erreur est survenue, merci de réessayer ultérieurement');
}
return trans('Une erreur est survenue, merci de réessayer ultérieurement ');
}
}

View file

@ -1,7 +1,8 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\Range;
use App\Models\Tag;
@ -43,9 +44,9 @@ class ProductController extends Controller
$products = Product::where('range_id', $range->id)->where('status', 1)->paginate(9);
// Find promo within range.
$offres = Product::where('range_id', $range->id)
->where('status', 1)
->has('discounts')
->with('discounts')->get();
->where('status', 1)
->has('discounts')
->with('discounts')->get();
return view('range', compact('range', 'products', 'offres'));
}
@ -65,17 +66,8 @@ class ProductController extends Controller
if (!$product || !$product->status) {
abort(404);
}
$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
if ($product->additionnalProductInfo === null && !empty($product_type)) {
// N'existe pas on renseigne un vide.
$product_type = $product->product_type;
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product->$product_type = new $modelClass();
}
$associated = $product->products;
return view('product', compact('product', 'associated'));
return view('product', ['productModel' => $product]);
}
/**

View file

@ -1,7 +1,8 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Models\Range;
use Illuminate\Http\Request;

View file

@ -10,7 +10,7 @@ use App\Models\Slider;
use App\Models\StaticPage;
use Illuminate\Http\Request;
class StaticPagesController extends Controller
class StaticPageController extends Controller
{
/**
* Display static page type home
@ -28,7 +28,7 @@ class StaticPagesController extends Controller
$products = Product::where('status', 1)->where('star', 1)->get();
$slider = Slider::where('slug', 'home')->first();
$slides = $slider->showableSlides;
$slides = $slider->showable_slides;
$quickorder_ranges = Range::with('products')->get();
foreach ($quickorder_ranges as $range) {

View file

@ -2,7 +2,6 @@
namespace App\Http;
use App\Http\Middleware\IsAdmin;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
@ -64,7 +63,6 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'is-admin' => IsAdmin::class,
'htmlsanitize' => \App\Http\Middleware\HtmlSanitizer::class,
];
}

View file

@ -3,6 +3,7 @@
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Support\Facades\Session;
class Authenticate extends Middleware
{
@ -14,8 +15,24 @@ class Authenticate extends Middleware
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('auth.login');
$logins = [
'frontend' => route('login'),
'backend' => route('bo.login')
];
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) {
$route = $logins[str_replace('auth:', '', $middleware)] ?? null;
if ($route) {
return $route;
}
}
}
return route('login');
}
abort(401);
}
}

View file

@ -1,26 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class IsAdmin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$role = Auth::user()->role;
if ($role === 1 || $role === 2) {
return $next($request);
}
abort(404, "Cette page n'existe pas.");
}
}

View file

@ -19,5 +19,8 @@ class TrustProxies extends Middleware
*
* @var integer
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
protected $headers = Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO;
}

View file

@ -11,6 +11,5 @@ class VerifyCsrfToken extends Middleware
*
* @var array
*/
protected $except = [
];
protected $except = [];
}

View file

@ -13,7 +13,7 @@ class DeleteTimeslotExceptionsRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -13,7 +13,7 @@ class SaveTimeslotExceptionsRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -13,7 +13,7 @@ class ChangeOrderRequest extends FormRequest
*/
public function authorize()
{
return auth()->check();
return auth('backend')->check();
}
/**

View file

@ -15,7 +15,7 @@ class StoreDiscountRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -15,7 +15,7 @@ class CreateProductRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -3,6 +3,7 @@
namespace App\Http\Requests\Bo\Products;
use App\Enums\TaxRate;
use App\Enums\WeightUnit;
use App\Http\Requests\Bo\UpdateMiniCrudRequest;
use App\Models\Product;
use App\Traits\Requests\HasPicture;
@ -68,6 +69,7 @@ class StoreProductRequest extends UpdateMiniCrudRequest
public function prepareForValidation(): void
{
$this->merge([
'weight' => \floatval($this->weight) * WeightUnit::kg()->conversion,
'star' => $this->star ? true : false,
'status' => $this->status ? true : false,
'orderable' => $this->orderable ? true : false,

View file

@ -13,7 +13,7 @@ class FilterRangeRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -30,7 +30,7 @@ class StoreRangeRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -15,7 +15,7 @@ class StoreShopRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -13,7 +13,7 @@ class StoreSliderRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -30,7 +30,7 @@ class StoreSlideRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -30,7 +30,7 @@ class UpdateStaticPageRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -14,7 +14,7 @@ class StoreTagRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -14,7 +14,7 @@ class UpdateTimeslotRequest extends FormRequest
*/
public function authorize()
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -25,7 +25,7 @@ abstract class UpdateMiniCrudRequest extends FormRequest
*/
public function authorize(): bool
{
return auth()->check();
return auth('backend')->check();
}
/**

View file

@ -13,7 +13,7 @@ class SearchXmlInco extends FormRequest
*/
public function authorize()
{
return auth()->check() and $this->ajax();
return auth('backend')->check() and $this->ajax();
}
/**

View file

@ -1,82 +0,0 @@
<?php
namespace App\Http\Requests\Fo;
use App\Enums\GeneralConfigType;
use App\Rules\Adult;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Cache;
use Illuminate\Validation\Rule;
class CustomerRegisterRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
/** @var \App\Models\GeneralConfig */
$config = Cache::get('GeneralConfigGlobal')->where('type', GeneralConfigType::global())->first();
/**
* specify rules for various cases of registration
* cf enum for register modes
* si register autorisé pour salarié et tout public
*/
// FIXME: REmove stuff from salaries here.
if (
config('bosettings.register_salaries') &&
$config->data['customer_register_public_mode']['value']
) {
$matriculeRule = 'nullable|exists:salaries,matricule';
$codeRule = [
'nullable',
Rule::exists('salaries', 'code')
->where('matricule', data_get($this->all(), 'matricule'))
];
} elseif (
config('bosettings.register_salaries') &&
!$config->data['customer_register_public_mode']['value']
) {
// * si register autorisé pour salarié seulement
$matriculeRule = 'required|exists:salaries,matricule';
$codeRule = [
'required',
Rule::exists('salaries', 'code')
->where('matricule', data_get($this->all(), 'matricule'))
];
} else {
// * rien de précisé ou public seulement alors
$matriculeRule = 'sometimes';
$codeRule = 'sometimes';
}//end if
return [
'matricule' => $matriculeRule,
'code' => $codeRule,
'lastname' => 'required|string|max:255',
'firstname' => 'required|string|max:255',
'phone' => 'sometimes|max:255',
'address' => 'sometimes|max:255',
'postal_code' => 'required',
'town' => 'required|string|max:255',
'email' => 'required|email:rfc,dns,spoof|max:255',
'password' => 'required|confirmed|min:8|max:255',
'ok_cgv' => 'accepted',
'optin' => 'sometimes',
'birth_date' => ['required', new Adult()],
];
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Fo\Customers;
use Illuminate\Foundation\Http\FormRequest;
class ConfirmDeleteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('frontend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'confirm' => 'accepted'
];
}
/**
* Get custom messages for validator errors.
*
* @return array
*/
public function messages()
{
return [
'confirm.accepted' => trans('Pour supprimer votre compte vous devez cocher la case de confirmation.')
];
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace App\Http\Requests\Fo\Customers;
use App\Rules\Adult;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use LDC\LDCCRM\Enums\Country;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
class CreateAccountRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return Auth::guard('frontend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
public function prepareForValidation()
{
$this->merge([
'phone' => $this->phone,
'opt_in' => $this->opt_in ? true : false
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'lastname' => 'required|string|max:255',
'firstname' => 'required|string|max:255',
'phone' => 'nullable|max:255|phone:FR',
'address' => 'required|string|min:1|max:255',
'postcode' => 'required|string|min:1|postal_code:FR',
'city' => 'required|string|min:1|max:255',
'birth_date' => ['required', new Adult()],
'email' => [
'required', 'email:rfc,dns,spoof', 'max:255',
// * Does email already exists in CRM ?
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function (string $attribute, $value, \Closure $fail) {
if (CrmCustomer::findWithEmail(\strval($value))) {
$fail('Un compte existe déjà avec cette adresse.');
}
},
'unique:customers,email',
],
'password' => 'required|confirmed|min:8|max:255',
'ok_cgv' => 'accepted',
'opt_in' => 'required|boolean',
];
}
/**
* Get custom attributes for validator errors.
* @return array
*/
public function attributes()
{
return [
'lastname' => trans('nom'),
'firstname' => trans('prénom'),
'phone' => trans('téléphone'),
'address' => trans('adresse'),
'postcode' => trans('code postal'),
'city' => trans('ville'),
'birth_date' => trans('date de naissance'),
'email' => trans('email'),
'password' => trans('mot de passe'),
'ok_cgv' => trans('conditions d\'utilisation'),
'opt_in' => trans('newsletter'),
];
}
/**
* Get custom messages for validator errors.
*
* @return array
*/
public function messages()
{
return [
// phpcs:ignore Generic.Files.LineLength.TooLong
'phone.phone' => trans('Le champ :attribute est invalide, il accepte les numéros des pays suivants :countries.', [
'countries' => Country::france()->label
]),
// phpcs:ignore Generic.Files.LineLength.TooLong
'country.in' => trans('Le champ :attribute est invalide, il accepte les pays suivants :countries.', [
'countries' => Country::france()->label
]),
'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.'),
];
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Requests\Fo\Customers;
use App\Rules\CrmCustomerPassword;
use Illuminate\Foundation\Http\FormRequest;
use Ldc\LDCCRM\Models\Customer as CrmCustomer;
class UpdateEmailRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return \auth('frontend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
/** @var \Ldc\LDCCRM\Models\Customer */
$crmCustomer = \auth('frontend')->user()->api_data;
return [
'password' => [
'required',
new CrmCustomerPassword($crmCustomer)
],
'email' => [
'required', 'email:rfc,dns,spoof', 'max:255', 'unique:customers,email',
// * Does email already exists in CRM ?
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function (string $attribute, $value, \Closure $fail) {
if (CrmCustomer::findWithEmail(\strval($value))) {
$fail('Un compte existe déjà avec cette adresse.');
}
},
'unique:customers,email',
],
];
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Fo\Customers;
class UpdateInfosRequest extends CreateAccountRequest
{
/** @var array */
private static $skeleton = [
'lastname',
'firstname',
'phone',
'address',
'postcode',
'city',
'opt_in',
'birth_date'
];
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return \array_intersect_key(
parent::rules(),
\array_flip(self::$skeleton)
);
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Http\Requests\Fo\Customers;
use App\Rules\CrmCustomerPassword;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;
class UpdatePasswordRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('frontend')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
/** @var \Ldc\LDCCRM\Models\Customer */
$crmCustomer = \auth('frontend')->user()->api_data;
return [
'old_password' => [
'required',
new CrmCustomerPassword($crmCustomer)
],
'password' => [
'required', 'string', 'max:255', 'confirmed',
Password::min(8)->mixedCase()->letters()->numbers()->uncompromised()
]
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'old_password' => trans('ancien mot de passe'),
'password' => trans('mot de passe'),
];
}
}

View file

@ -1,35 +0,0 @@
<?php
namespace App\Http\Requests\Fo;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
$this->merge([
'remember' => $this->remember ? true : false
]);
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email:rfc,filter',
'password' => 'required',
'remember' => 'required|boolean'
];
}
}

View file

@ -1,58 +0,0 @@
<?php
namespace App\Http\Requests\Fo;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
class UpdateInfosRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return Auth::guard('customer')->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'firstname' => 'required|string|max:255',
'lastname' => 'required|string|max:255',
'day' => 'required|numeric|gt:0|lt:32',
'month' => 'required|numeric|gt:0|lt:13',
'year' => [
'required',
'numeric',
'gt:' . (date('Y') - 101), 'lt:' . (date('Y') + 1),
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attr, $value, $fail) {
$MJ = Carbon::now()->subYears(18);
$dateTest = Carbon::createFromDate($this->year, $this->month, $this->day);
if (
$dateTest->format('dmY') !== $this->day . $this->month . $this->year ||
$dateTest->getTimestamp() > time()
) {
$fail(__('Veuillez entrer une date de naissance valide'));
}
if (!$dateTest->lessThanOrEqualTo($MJ)) {
$fail(__("Vous devez être majeur pour vous inscrire."));
}
}
],
'address' => 'max:255',
'phone' => 'max:255',
'city' => 'required|string|max:255',
'postcode' => 'required'
];
}
}

View file

@ -3,19 +3,19 @@
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Models\Customers;
use App\Models\Customer;
class CustomerCrmResource extends JsonResource
{
/** @var \App\Models\Customers */
/** @var \App\Models\Customer */
private $customer;
/**
* CustomerCrmResource
*
* @param \App\Models\Customers $customer
* @param \App\Models\Customer $customer
*/
public function __construct(Customers $customer)
public function __construct(Customer $customer)
{
$this->customer = $customer;
}

View file

@ -468,6 +468,7 @@ class XmlIncoReader extends XmlJobs
if (empty($imgName)) {
$imgName = strval($xmlData->INCO->NO_ART);
}
// FIXME: Comply with store image name only.
// * Store only picture name.
$imgNamePath = $imgName . "_" . $index;

View file

@ -205,4 +205,50 @@ class ToolboxHelper
}
return $ref;
}
/**
* Convert deeply an object to array
*
* @param array|object $input
* @return array
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function objectToArray($input): array
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (array)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::objectToArray($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
}
}
/**
* Convert deeply an array to object
*
* @param array|object $input
* @return object
* @throws \RuntimeException If $input var is a scalar value.
*/
public static function arrayToObject($input): object
{
// Only process if it's an object or array being passed to the function.
if (is_object($input) or is_array($input)) {
$ret = (object)$input;
foreach ($ret as &$item) {
// Recursively process EACH element only if not scalar.
$item = (is_object($item) or is_array($item)) ?
self::arrayToObject($item) : $item;
}
return $ret;
} else {
throw new \RuntimeException('cannot handle $input of type ' . \gettype($input));
}
}
}

View file

@ -2,6 +2,7 @@
namespace App\Mail;
use App\Models\Customer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
@ -13,21 +14,30 @@ class PasswordResetMail extends Mailable implements ShouldQueue
use SerializesModels;
/**
* Reset token
* target Customer
*
* @var \App\Models\Customer
*/
private $customer;
/**
* Reset url
*
* @var string
*/
private $token;
private $resetUrl;
/**
* Create a new message instance.
*
* @param string $token
* @param \App\Models\Customer $customer
* @param string $resetUrl
* @return void
*/
public function __construct(string $token)
public function __construct(Customer $customer, string $resetUrl)
{
$this->token = $token;
$this->customer = $customer;
$this->resetUrl = $resetUrl;
}
/**
@ -37,9 +47,10 @@ class PasswordResetMail extends Mailable implements ShouldQueue
*/
public function build()
{
$token = $this->token;
$resetUrl = $this->resetUrl;
return $this->subject(
"Réinitialiser votre accès - " . config('theme.site_name')
)->view('mails.password-reset', compact('token'));
)->to($this->customer->email)
->view('mails.password-reset', compact('resetUrl'));
}
}

View file

@ -2,7 +2,7 @@
namespace App\Mail;
use App\Models\Customers;
use App\Models\Customer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
@ -16,10 +16,17 @@ class RegisterConfirmMail extends Mailable implements ShouldQueue
/**
* target Customer
*
* @var \App\Models\Customers
* @var \App\Models\Customer
*/
private $customer;
/**
* The confirmation Url
*
* @var string
*/
private $confirmUrl;
/**
* Customer's firstname
*
@ -30,14 +37,16 @@ class RegisterConfirmMail extends Mailable implements ShouldQueue
/**
* Create a new message instance.
*
* @param \App\Models\Customers $customer
* @param string|null $firstname
* @param \App\Models\Customer $customer
* @param string $confirmUrl
* @param string $firstname
* @return void
*/
public function __construct(Customers $customer, string $firstname = null)
public function __construct(Customer $customer, string $confirmUrl, string $firstname)
{
$this->firstname = $firstname;
$this->customer = $customer;
$this->confirmUrl = $confirmUrl;
$this->firstname = $firstname;
$this->customer = $customer;
}
/**
@ -47,9 +56,11 @@ class RegisterConfirmMail extends Mailable implements ShouldQueue
*/
public function build()
{
$customer = $this->customer;
$firstname = $this->firstname;
$confirmUrl = $this->confirmUrl;
$customer = $this->customer;
$firstname = $this->firstname;
return $this->subject("Confirmation d'inscription " . config('theme.site_name'))
->view('mails.register-ok', compact('customer', 'firstname'));
->to($customer->email, $firstname)
->view('mails.register-ok', compact('customer', 'firstname', 'confirmUrl'));
}
}

223
app/Models/Customer.php Normal file
View file

@ -0,0 +1,223 @@
<?php
namespace App\Models;
use App\Http\Resources\CustomerCrmResource;
use App\Notifications\Customer\ResetPassword as NotificationResetPassword;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Notifications\Customer\VerifyEmail as NotificationVerifyEmail;
use Illuminate\Contracts\Auth\CanResetPassword;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
/**
* @property \LDC\LDCCRM\Models\Customer api_data
* @package App\Models
*/
class Customer extends Authenticatable implements MustVerifyEmail, CanResetPassword
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'email',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'last_connection' => 'datetime',
'email_verified_at' => 'datetime',
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
// * EVENT CLOSURES.
static::creating(function (self $model) {
static::assertEmailIsUnique($model->id, $model->email);
});
static::updating(function (self $model) {
static::assertEmailIsUnique($model->id, $model->email);
});
}
// * RELATIONSHIPS
// * METHODS
/**
* Asserts using validation that the email is unique
*
* @param mixed $id
* @param mixed $email
* @return void
* @throws \Illuminate\Validation\ValidationException If user email as already used.
*/
private static function assertEmailIsUnique($id, $email)
{
self::assertFieldIsUnique($id, 'email', $email);
}
// * --- AUTH ---
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new NotificationVerifyEmail());
}
/**
* Mark the given user's email as verified.
*
* @return boolean
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function markEmailAsVerified()
{
try {
return $this->api_data->activate() && parent::markEmailAsVerified();
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::customerAlreadyActive())) {
return true && parent::markEmailAsVerified();
}
throw $e;
}
}
/**
* Send the password reset notification.
*
* @param mixed $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
/** @var string $token */
$this->notify(new NotificationResetPassword($token));
}
// * --- END AUTH ---
/**
* Asserts that the field is unique
*
* @param mixed $id
* @param string $field
* @param mixed $value
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
*/
private static function assertFieldIsUnique($id, string $field, $value)
{
Validator::make([$field => $value], [
$field => Rule::unique('customers', $field)->ignore($id),
], [
$field . '.unique' => trans('La valeur :value pour le champ :attribute est déjà utilisé', [
'attribute' => $field,
'value' => $value
])
])->validate();
}
/**
* Get the crm Data of a customer
*
* @return \LDC\LDCCRM\Models\Customer
* @throws \LDC\LDCCRM\Exceptions\CrmException If cannot fetch data from crm.
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
public function getApiDataAttribute(): CrmCustomer
{
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($this->hashcode);
if (!$crmCustomer) {
throw new CrmException('CRM get Customer returned null');
}
return $crmCustomer;
}
/**
* Set the crm Data of a customer
* @param CustomerCrmResource $resource
*
* @return CrmCustomer
*
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function updateApiData(CustomerCrmResource $resource): CrmCustomer
{
$data = $resource->toArray(null);
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($data['hashcode']);
if (!$crmCustomer) {
throw new CrmException('CRM get Customer returned null');
}
$crmCustomer->fill($data);
$crmCustomer->save();
return $crmCustomer;
}
/**
* This mutate Customer attributes to array
* including only non critical properties and crm values
* (One that can be send to front office without security breach)
*
* @return array
*/
public function toArray()
{
if ($this->hashcode) {
return collect(array_merge(
parent::toArray(),
(array)$this->apiData
))->intersectByKeys([
'email' => '',
'firstname' => '',
'lastname' => '',
'address' => '',
'address2' => '',
'postcode' => '',
'birth_date' => '',
'city' => '',
'country' => '',
'opt_in' => '',
'phone' => ''
])->toArray();
} else {
return parent::toArray();
}
}
}

View file

@ -1,116 +0,0 @@
<?php
namespace App\Models;
use App\Http\Resources\CustomerCrmResource;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Models\Salarie;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\Customer as CrmCustomer;
class Customers extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'email',
'username'
];
/** @var string */
protected $guard = 'customer';
/**
* hasOne salarie relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function salarie()
{
return $this->hasOne(Salarie::class, 'customer_id');
}
/**
* Get the crm Data of a customer
*
* @return \LDC\LDCCRM\Models\Customer
* @throws \LDC\LDCCRM\Exceptions\CrmException If cannot fetch data from crm.
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
public function getApiDataAttribute(): CrmCustomer
{
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($this->hashcode);
if (!$crmCustomer) {
throw new CrmException('CRM get Customer returned null');
}
return $crmCustomer;
}
/**
* Set the crm Data of a customer
* @param CustomerCrmResource $resource
*
* @return CrmCustomer
*
* @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function updateApiData(CustomerCrmResource $resource): CrmCustomer
{
$data = $resource->toArray(null);
/** @var \LDC\LDCCRM\CrmApiV2 */
$crm = app('crmAPI');
$crmCustomer = $crm->getCustomer($data['hashcode']);
if (!$crmCustomer) {
throw new CrmException('CRM get Customer returned null');
}
$crmCustomer->fill($data);
$crmCustomer->save();
return $crmCustomer;
}
/**
* This mutate Customer attributes to array
* including only non critical properties and crm values
* (One that can be send to front office without security breach)
*
* @return array
*/
public function toArray()
{
if ($this->hashcode) {
return collect(array_merge(
parent::toArray(),
(array)$this->apiData
))->intersectByKeys([
'email' => '',
'firstname' => '',
'lastname' => '',
'address' => '',
'address2' => '',
'postcode' => '',
'birth_date' => '',
'city' => '',
'country' => '',
'opt_in' => '',
'phone' => ''
])->toArray();
} else {
return parent::toArray();
}
}
}

View file

@ -79,7 +79,7 @@ class Order extends Model
*/
public function customer()
{
return $this->belongsTo(Customers::class);
return $this->belongsTo(Customer::class);
}
/**

View file

@ -33,6 +33,8 @@ use Kwaadpepper\Enum\Traits\CastsEnums;
* @property float discountedPriceTtc|string Price after discount VAT included
* @property float discountAmount Discount amount
* @property float discountRate Discount rate
* @property array data The overlapped data like defined in template.
* @property array dataObject The overlapped data like defined in template as object.
*
* @package App\Models
*/
@ -193,19 +195,7 @@ class Product extends Model implements Buyable
*/
public function discounts()
{
return $this->belongsToMany(Discount::class)
->whereDate('date_start', '<=', now())
->whereDate('date_end', '>=', now());
}
/**
* belongsTo tax relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function tax()
{
return $this->belongsTo(TaxRate::class);
return $this->belongsToMany(Discount::class, 'product_discount');
}
/**
@ -295,15 +285,32 @@ class Product extends Model implements Buyable
}
/**
* Get inco nutritionnal values
* Getter data_object, the object overloaded attributes from theme
*
* @return array
* @return object
*/
public function getIncoNutritionalValues(): array
public function getDataObjectAttribute(): object
{
return ToolboxHelper::arrayToObject($this->data);
}
/**
* Get inco nutritionnal values, if product is inco.
*
* @return object|null
*/
public function getIncoNutritionalValues(): ?object
{
if (!$this->hasAndIsInco()) {
return null;
}
$nutri_values = $this->xmlInco->nutritional_values;
return [
if (empty($nutri_values)) {
return null;
}
return (object)[
// Pour la ref.
'nv_ref_weight_g' => $nutri_values['taille_ref'],
// Energie Kj.
@ -610,31 +617,6 @@ class Product extends Model implements Buyable
return \intval($this->attributes['price']) / 100;
}
/**
* Setter weight
*
* ! Stored as integer (grammes)
*
* @param float $value
* @return void
*/
public function setWeightAttribute(float $value)
{
$this->attributes['weight'] = \intval($value * 100);
}
/**
* Getter weight
*
* ! Stored as (grammes)
*
* @return float
*/
public function getWeightAttribute(): float
{
return \intval($this->attributes['weight']) / 100;
}
// * Dynamic Minicrud inputs
/**
@ -786,56 +768,42 @@ class Product extends Model implements Buyable
/**
* Getter for discounted price
*
* @return float
* @return integer
*/
public function getDiscountedPriceHtAttribute()
public function getDiscountedPriceHtAttribute(): int
{
$discountedPriceHt = $this->priceHt - $this->discountAmount;
$roundingdiscountedPriceHt = round($discountedPriceHt * 1000) / 1000;
return \floatval($roundingdiscountedPriceHt);
return $this->priceHt - $this->discountAmount;
}
/**
* Getter for net price
*
* @return float
* @return integer
*/
public function getPriceTtcAttribute()
public function getPriceTtcAttribute(): int
{
$priceTtc = $this->priceHt * (1 + ($this->tax->rate / 100));
$priceTtc = $this->priceHt * (1 + ($this->tax_rate->value / 100));
/**
* On travaille avec des centimes puis on redivise par 100 pour avoir des unités monetaires
* on augmente la précision à 3 digits, l'affichage formate les prix sur 2 décimales
*/
return \floatval(round($priceTtc * 1000) / 1000);
return round($priceTtc);
}
/**
* Getter for net discounted price
*
* @return float|string
* @return integer
*/
public function getDiscountedPriceTtcAttribute()
{
/** @var \App\Models\GeneralConfig */
$config = Cache::get('GeneralConfigGlobal')->where('type', GeneralConfigType::global())->first();
$discountedPriceTtc = $this->discountedPriceHt * (1 + ($this->tax->rate / 100));
$discountedPriceTtc = $this->discountedPriceHt * (1 + ($this->tax_rate->value / 100));
// On travaille avec des centimes puis on redivise par 100 pour avoir des unités monetaires.
$roundingDiscountedPriceTTC = \floatval(round($discountedPriceTtc * 1000) / 1000);
// Prix transmis en fonction du setting show_prices.
if (
$config->data['orders_display_prices_for_auth']['value'] and
Auth::guard('customer')->check()
) {
return $roundingDiscountedPriceTTC;
}
if ($config->data['orders_display_prices_for_public']['value']) {
return $roundingDiscountedPriceTTC;
}
return '-.--';
// * Show prices for anyone.
return $roundingDiscountedPriceTTC;
}
/**
@ -900,4 +868,30 @@ class Product extends Model implements Buyable
// @phpcs:enable
return \floatval($this->priceHt);
}
/**
* Config says to display prices ?
* @return boolean
*/
public static function displayPrices(): bool
{
/** @var \App\Models\GeneralConfig */
$config = Cache::get('GeneralConfigGlobal')->where('type', GeneralConfigType::global())->first();
if ($config->data['orders_display_prices_for_auth']['value']) {
return Auth::guard('frontend')->check();
}
return true;
}
/**
* Get displayable price value
*
* @param mixed $value
* @return string
*/
public static function fmtCurrency($value): string
{
return self::displayPrices() ?
sprintf('%s € TTC', number_format(\intval($value) / 100, 2, ',', ' ')) : '-.-- €';
}
}

View file

@ -81,11 +81,11 @@ class Slider extends Model
}
/**
* filtered slides relation by showable date
* Getter showable_slides filtered slides relation by showable date
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function showableSlides()
public function getShowableSlidesAttribute()
{
$now = Carbon::now();
return $this->hasMany(Slide::class, 'slider_id')

View file

@ -2,13 +2,16 @@
namespace App\Models;
use App\Enums\UserRole;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Kwaadpepper\Enum\Traits\CastsEnums;
class User extends Authenticatable
{
use CastsEnums;
use HasFactory;
use Notifiable;
@ -45,6 +48,15 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
];
/**
* The attributes that should be cast to Enums.
*
* @var array
*/
protected $enumCasts = [
'role' => UserRole::class,
];
/**
* Perform any actions required after the model boots.
*

View file

@ -0,0 +1,23 @@
<?php
namespace App\Notifications\Customer;
use App\Mail\PasswordResetMail;
use Illuminate\Auth\Notifications\ResetPassword as NotificationsResetPassword;
class ResetPassword extends NotificationsResetPassword
{
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
/** @var \App\Models\Customer $notifiable */
$resetUrl = $this->resetUrl($notifiable);
return new PasswordResetMail($notifiable, $resetUrl);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Notifications\Customer;
use App\Mail\RegisterConfirmMail;
use Illuminate\Auth\Notifications\VerifyEmail as NotificationsVerifyEmail;
class VerifyEmail extends NotificationsVerifyEmail
{
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
/** @var \App\Models\Customer $notifiable */
$verificationUrl = $this->verificationUrl($notifiable);
return new RegisterConfirmMail(
$notifiable,
$verificationUrl,
$notifiable->api_data->firstname
);
}
}

View file

@ -21,8 +21,8 @@ class ProductPolicy
}
/**
* @param App\Models\User|App\Models\Customers $user
* @param \App\Models\Product $product
* @param App\Models\User|App\Models\Customer $user
* @param \App\Models\Product $product
*
* @return \Illuminate\Auth\Access\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed

View file

@ -79,13 +79,12 @@ class AppServiceProvider extends ServiceProvider
View::share('published_tags', $tags);
}
Blade::directive('fmt_currency', function ($expression) {
return "<?php
if(is_float($expression)) {
echo sprintf('%s € TTC', number_format($expression, 2, ',', ' '));
} else {
echo $expression;
} ?>";
Blade::directive('fmt_sellunit', function ($expression) {
if (config('bosettings.sell_unit_management') == "strict") {
return "<?php if(!empty($expression)) { echo sprintf('/ %s', SellUnit::make($expression)->label); } ?>";
} else {
return "<?php if(!empty($expression)) { echo sprintf('/ %s', $expression); } ?>";
}
});
Blade::directive('excerpt_product_title', function ($expression) {

View file

@ -2,6 +2,7 @@
namespace App\Providers;
use App\Auth\CrmUserProvider;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
@ -13,6 +14,10 @@ class AuthServiceProvider extends ServiceProvider
*/
public function boot()
{
$this->app->auth->provider('crm', function ($app, array $config) {
return new CrmUserProvider($app['hash'], $config['model']);
});
$this->registerPolicies();
}
}

View file

@ -2,7 +2,11 @@
namespace App\Providers;
use App\Events\LogVerifiedUser;
use App\Events\PasswordHasReset;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Events\Verified;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@ -17,6 +21,12 @@ class EventServiceProvider extends ServiceProvider
Registered::class => [
SendEmailVerificationNotification::class,
],
Verified::class => [
LogVerifiedUser::class
],
PasswordReset::class => [
PasswordHasReset::class
],
];
/**

View file

@ -43,10 +43,19 @@ class RouteServiceProvider extends ServiceProvider
$this->routes(function () {
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
->namespace($this->namespace)
->group(base_path('routes/web-bo.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web-fo.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web-shop.php'));
});
// * BINDERS
Route::model('discount', Discount::class);
Route::model('product', Product::class);
Route::model('range', Range::class);
@ -85,9 +94,15 @@ class RouteServiceProvider extends ServiceProvider
*/
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
});
RateLimiter::for('web', function (Request $request) {
return Limit::perMinute(150)->by(optional($request->user())->id ?: app('clientIp'));
});
RateLimiter::for('credentials', function (Request $request) {
return Limit::perMinute(30)->by(optional($request->user())->id ?: $request->ip());
});
}
/**

View file

@ -0,0 +1,64 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Models\Customer;
class CrmCustomerPassword implements Rule
{
/**
* Crm Customer
*
* @var \LDC\LDCCRM\Models\Customer
*/
private $crmCustomer;
/**
* Create a new rule instance.
*
* @param \LDC\LDCCRM\Models\Customer $crmCustomer
* @return void
*/
public function __construct(Customer $crmCustomer)
{
$this->crmCustomer = $crmCustomer;
}
/**
* Determine if the validation rule passes.
*
* @param mixed $attribute
* @param mixed $value
* @return boolean
* @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any unhandled error occurs.
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function passes($attribute, $value)
{
// phpcs:enable
try {
return Customer::login($this->crmCustomer->email, strval($value));
} catch (ApiErrorException $e) {
if ($e->getApiError()->equals(ApiError::authFailed())) {
return false;
}
throw $e;
}
// Pass.
return true;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return trans('Votre ancien mot de passe n\'as pu être vérifié');
}
}

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View file

View file

@ -22,9 +22,10 @@
"kwaadpepper/image-resizer": "^0.1.2",
"laravel/framework": "^8.83",
"laravel/tinker": "^2.5",
"laravel/ui": "^3.4",
"ldc/ldccrmv2client": "^3.0",
"mews/purifier": "^3.3",
"propaganistas/laravel-phone": "^4.3",
"propaganistas/laravel-phone": "^4.4",
"spatie/laravel-sitemap": "^5.8"
},
"require-dev": {

75
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c943a3eea10496c9e31f2d7be2a3999c",
"content-hash": "889af30f60588d725b5c17d1cacb64eb",
"packages": [
{
"name": "alexusmai/laravel-file-manager",
@ -2078,6 +2078,67 @@
},
"time": "2022-03-23T12:38:24+00:00"
},
{
"name": "laravel/ui",
"version": "v3.4.6",
"source": {
"type": "git",
"url": "https://github.com/laravel/ui.git",
"reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c",
"reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c",
"shasum": ""
},
"require": {
"illuminate/console": "^8.42|^9.0",
"illuminate/filesystem": "^8.42|^9.0",
"illuminate/support": "^8.82|^9.0",
"illuminate/validation": "^8.42|^9.0",
"php": "^7.3|^8.0"
},
"require-dev": {
"orchestra/testbench": "^6.23|^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Ui\\UiServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Ui\\": "src/",
"Illuminate\\Foundation\\Auth\\": "auth-backend/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel UI utilities and presets.",
"keywords": [
"laravel",
"ui"
],
"support": {
"source": "https://github.com/laravel/ui/tree/v3.4.6"
},
"time": "2022-05-20T13:38:08+00:00"
},
{
"name": "ldc/ldccrmv2client",
"version": "3.0.5",
@ -3034,16 +3095,16 @@
},
{
"name": "propaganistas/laravel-phone",
"version": "4.3.8",
"version": "4.4.0",
"source": {
"type": "git",
"url": "https://github.com/Propaganistas/Laravel-Phone.git",
"reference": "5b0fef1d28e720c0a594fe0f22cb1d5b1d576474"
"reference": "ba5aa49226793faf4bd37b38bd627d49269611d9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Propaganistas/Laravel-Phone/zipball/5b0fef1d28e720c0a594fe0f22cb1d5b1d576474",
"reference": "5b0fef1d28e720c0a594fe0f22cb1d5b1d576474",
"url": "https://api.github.com/repos/Propaganistas/Laravel-Phone/zipball/ba5aa49226793faf4bd37b38bd627d49269611d9",
"reference": "ba5aa49226793faf4bd37b38bd627d49269611d9",
"shasum": ""
},
"require": {
@ -3094,9 +3155,9 @@
],
"support": {
"issues": "https://github.com/Propaganistas/Laravel-Phone/issues",
"source": "https://github.com/Propaganistas/Laravel-Phone/tree/4.3.8"
"source": "https://github.com/Propaganistas/Laravel-Phone/tree/4.4.0"
},
"time": "2022-09-04T18:30:31+00:00"
"time": "2022-11-10T09:52:44+00:00"
},
{
"name": "psr/container",

View file

@ -14,7 +14,7 @@ return [
*/
'defaults' => [
'guard' => 'web',
'guard' => 'backend',
'passwords' => 'users',
],
@ -36,21 +36,21 @@ return [
*/
'guards' => [
'web' => [
'backend' => [
'driver' => 'session',
'provider' => 'users',
],
'frontend' => [
'driver' => 'session',
'provider' => 'customers',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'customer' => [
'driver' => 'session',
'provider' => 'customers',
],
],
/*
@ -70,6 +70,7 @@ return [
|
*/
// * Added "crm" Provider (AuthServiceProvider)
'providers' => [
'users' => [
'driver' => 'eloquent',
@ -77,8 +78,8 @@ return [
],
'customers' => [
'driver' => 'eloquent',
'model' => App\Models\Customers::class,
'driver' => 'crm',
'model' => App\Models\Customer::class,
]
],
@ -98,9 +99,9 @@ return [
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'customers' => [
'provider' => 'customers',
'table' => 'customer_password_resets',
'expire' => 60,
'throttle' => 60,
],

10
config/ldcweb-cookies.php Normal file
View file

@ -0,0 +1,10 @@
<?php
/**
* LDC Service Web Cookies
* Version 2.1.1
*/
return [
'analyticsKey' => env('ANALYTICS_GOOGLE_ID')
];

View file

@ -38,47 +38,22 @@ class MigrateSettingsToGeneralConfigs extends Migration
'tooltip' => 'Adresse email à laquelle seront envoyé les formulaires de contact.'
],
'confirmed_orders_destination_email' => [
'label' => 'Adresse de reception des messages "contactez nous"',
'label' => 'Adresse de reception des messages "nouvelle commande"',
'inputType' => InputType::email(),
'value' => 'equipe.web@ldc.fr',
'validate' => 'required|email:strict,rfc,spoof,dns',
'helper' => 'Information lorsqu\'un client valide une commande',
'tooltip' => ''
],
'customer_register_public_mode' => [
'label' => 'Public',
'inputType' => InputType::boolean(),
'value' => true,
'validate' => 'boolean',
'helper' => 'Mode d\'inscription',
'tooltip' => sprintf(
'%s %s %s',
// phpcs:disable
'La partie front office de votre boutique doit tenir compte de ce paramètre pour qu\'il prenne effet.',
'<br/> Le thème doit indiquer quel modes sont pris en charge via la config de thème: <code>register_mode</code>',
'<br/> Si aucun choix ne s\'affiche c\'est que votre boutique ne gère que l\'inscription tous publics.'
// phpcs:enable
)
],
'orders_display_prices_for_auth' => [
'label' => 'Client Connecté ',
'label' => 'Prix uniquement pour les clients connectés',
'inputType' => InputType::boolean(),
'value' => true,
'validate' => 'boolean',
'helper' => 'Affichage des prix',
'helper' => 'Afficher les prix uniquement pour les clients connectés',
'tooltip' =>
// phpcs:ignore Generic.Files.LineLength.TooLong
'Affichage des prix sous certaines conditions : <ul><li>Client Connecté</li><li>Public</li></ul>'
],
'orders_display_prices_for_public' => [
'label' => 'Public',
'inputType' => InputType::boolean(),
'value' => true,
'validate' => 'boolean',
'helper' => '',
'tooltip' => ''
'Affichage des prix sous certaines conditions : <ul><li>Client Connecté</li></ul>'
],
]
]);

View file

@ -1,6 +1,7 @@
<?php
use App\Enums\DefaultProductType;
use App\Enums\WeightUnit;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
@ -105,13 +106,13 @@ class ReworkProducts extends Migration
$products->each(function (object $product) {
$weight = 0;
if (Str::lower($product->weight_unit) === 'g') {
$weight = intval($product->weight);
$weight = round($product->weight);
}
if (Str::lower($product->weight_unit) === 'kg') {
$weight = intval($product->weight) * 1000;
$weight = round($product->weight * WeightUnit::kg()->conversion);
}
if (Str::lower($product->weight_unit) === 'ton') {
$weight = intval($product->weight) * 1000 * 1000;
$weight = round($product->weight * WeightUnit::ton()->conversion);
}
DB::table('products')->where('id', $product->id)->update([
'weight' => $weight,

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDateFiledsOnCustomers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('customers', function (Blueprint $table) {
$table->timestamp('email_verified_at')->nullable()->after('remember_token');
$table->timestamp('last_connection')->nullable()->after('email_verified_at');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('customers', function (Blueprint $table) {
$table->dropColumn('email_verified_at');
$table->dropColumn('last_connection');
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('customer_password_resets', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('customer_password_resets');
}
};

2107
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,7 @@
"@voerro/vue-tagsinput": "^2.7.1",
"@vue/eslint-config-typescript": "^11.0.1",
"axios": "^0.21.1",
"bootstrap": "^5.1.3",
"bootstrap": "^5.2.2",
"clean-webpack-plugin": "^4.0.0",
"cropperjs": "^1.5.12",
"eslint": "^8.23.1",

View file

@ -0,0 +1,4 @@
.nav-link.active {
color: $primary;
font-weight: bold;
}

View file

@ -1,102 +1,22 @@
body {
font-size: 0.875rem;
}
.lni {
width: 16px;
height: 16px;
font-size: 20px !important;
font-weight: bold !important;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
z-index: 100; /* Behind the navbar */
z-index: 100;
top: 0;
/* rtl:raw:
right: 0;
*/
bottom: 0;
/* rtl:remove */
left: 0;
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
margin: 48px 0 0;
}
@media (max-width: 767.98px) {
.sidebar {
top: 5rem;
}
#topNavBar {
height: $top-nav-bar-height;
}
.sidebar-sticky {
#footer {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: 0.5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
z-index: 100;
height: $footer-bar-height;
}
.sidebar .nav-link {
color: #333;
font-weight: 500;
}
.sidebar .nav-link .lni {
margin-right: 8px;
color: #727272;
}
.sidebar .nav-link.active {
color: #2470dc;
}
.sidebar .nav-link:hover .lni,
.sidebar .nav-link.active .lni {
color: inherit;
}
.sidebar-heading {
font-size: 0.75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
background-color: rgba(0, 0, 0, 0.25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25);
font-size: 1rem;
}
.navbar .navbar-toggler {
top: 0.25rem;
right: 1rem;
}
.navbar .form-control {
padding: 0.75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
border-color: rgba(255, 255, 255, 0.1);
background-color: rgba(255, 255, 255, 0.1);
color: #fff;
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.25);
#mainContainer {
min-height: calc(100vh - $top-nav-bar-height - $footer-bar-height);
}

View file

@ -9,17 +9,6 @@ $font-size-base: 1rem;
$line-height-base: 1.6;
// Colors
$blue: #0060df;
$indigo: #6574cd;
$purple: #9561e2;
$pink: #f66d9b;
$red: #e3342f;
$orange: #f6993f;
$yellow: #ffed4a;
$green: #38c172;
$teal: #4dc0b5;
$cyan: #6cb2eb;
$color1: #f3ebe4;
$color2: #07386a;
$color3: #0f437e;
@ -32,6 +21,39 @@ $color9: #c6bcb3;
$color10: #999595;
$color11: #ab9d8e;
// * Boostrap Colors
$white: #fff !default;
$gray-100: #f8f9fa !default;
$gray-200: #e9ecef !default;
$gray-300: #dee2e6 !default;
$gray-400: #ced4da !default;
$gray-500: #adb5bd !default;
$gray-600: #6c757d !default;
$gray-700: #495057 !default;
$gray-800: #343a40 !default;
$gray-900: #212529 !default;
$black: #000 !default;
$blue: #0d6efd !default;
$indigo: #6610f2 !default;
$purple: #6f42c1 !default;
$pink: #d63384 !default;
$red: #dc3545 !default;
$orange: #fd7e14 !default;
$yellow: #ffc107 !default;
$green: #198754 !default;
$teal: #20c997 !default;
$cyan: #0dcaf0 !default;
$primary: $blue !default;
$secondary: $gray-600 !default;
$success: $green !default;
$info: $cyan !default;
$warning: $yellow !default;
$danger: $red !default;
$light: $gray-100 !default;
$dark: $gray-900 !default;
.large-tooltip .tooltip-inner {
width: max-content !important; //uses the content as a guide of default length
max-width: 800px !important; //constrained to max width
@ -43,3 +65,11 @@ $color11: #ab9d8e;
max-width: 100% !important;
}
}
// * bootstrap overload
$nav-link-color: #000;
$nav-link-color-active: $color3;
// * Layout settings
$top-nav-bar-height: 3.6rem;
$footer-bar-height: 2.6rem;

View file

@ -12,12 +12,13 @@
@import "icons";
// Bootstrap
@import "~bootstrap/scss/bootstrap";
@import "~bootstrap";
@import "bootstrapOverload";
// Toastify
@import "~toastify-js/src/toastify.css";
@import "dashboard";
@import "smoothdnd";
@import "forms";

View file

@ -83,9 +83,8 @@ export default defineComponent({
const self = this,
ckeditor = this.$refs.ckeditor as HTMLElement,
wordCounter = this.$refs.wordCounter as HTMLElement,
// * Specific to Calliope
verticalTopMenuOffset = document.querySelector(
"body > div> .navbar"
verticalTopMenuOffset = document.getElementById(
"topNavBar"
) as HTMLElement | null;
if (!verticalTopMenuOffset) {
@ -224,13 +223,13 @@ export default defineComponent({
"fullScreen",
"sourceEditing",
],
ui: {
viewportOffset: {
top: verticalTopMenuOffset.getBoundingClientRect().height,
right: 0,
bottom: 0,
left: 0,
},
},
ui: {
viewportOffset: {
top: verticalTopMenuOffset.getBoundingClientRect().height,
right: 0,
bottom: 0,
left: 0,
},
},
table: {

View file

@ -73,6 +73,7 @@ import {
faTrash,
faPaste,
faCalendarDays,
faEllipsisVertical,
} from "@fortawesome/free-solid-svg-icons";
// import { faXm } from "@fortawesome/free-brands-svg-icons";
import {
@ -168,5 +169,6 @@ library.add(faCopy);
library.add(faPaste);
library.add(faArrowRight);
library.add(faCalendarDays);
library.add(faEllipsisVertical);
dom.watch();

View file

@ -0,0 +1,73 @@
@extends('layouts.bo-layout')
@section('content')
<div class="container p-2 p-md-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Connexion Backoffice') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('bo.login') }}">
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Adresse Email') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Mode de passe') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Se souvenir de moi') }}
</label>
</div>
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Connexion') }}
</button>
@if (Route::has('bo.password.request'))
<a class="btn btn-link" href="{{ route('bo.password.request') }}">
{{ __('Mode de passe oublié ?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,49 @@
@extends('layouts.back')
@section('content')
<div class="container p-2 p-md-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Confirm Password') }}</div>
<div class="card-body">
{{ __('Please confirm your password before continuing.') }}
<form method="POST" action="{{ route('bo.password.confirm') }}">
@csrf
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Confirm Password') }}
</button>
@if (Route::has('bo.password.request'))
<a class="btn btn-link" href="{{ route('bo.password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,47 @@
@extends('layouts.back')
@section('content')
<div class="container p-2 p-md-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('bo.password.email') }}">
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,65 @@
@extends('layouts.back')
@section('content')
<div class="container p-2 p-md-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('bo.password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View file

@ -0,0 +1,68 @@
<header id="topNavBar" class="sticky-top navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContentLeft" aria-controls="navbarSupportedContentLeft" aria-expanded="false" aria-label="{{ __('Afficher/Masquer le menu de navigation') }}">
<i class="fa-solid fa-ellipsis-vertical"></i>
</button>
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Afficher/Masquer le menu d\'utilisateur') }}">
<i class="fa-solid fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
{{-- <li class="nav-item">
<a class="nav-link" href="#" id="navbarDropdownPages" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ __('Pages') }}
</a>
</li> --}}
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@guest
@if (Route::has('bo.login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Connexion') }}</a>
</li>
@endif
@if (Route::has('bo.register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Créer un compte') }}</a>
</li>
@endif
@else
@if(Auth::check() and !Auth::user()->role->equals(\App\Enums\UserRole::user()))
<li class="nav-item">
<span class="nav-link fw-bold {{ Auth::user()->role->equals(\App\Enums\UserRole::admin()) ? 'text-primary' : 'text-success'}}">{{
Auth::user()->role->label
}}</span>
</li>
@endif
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ __('Bonjour') }}&nbsp;{{ Auth::user()->firstname }}&nbsp;{{ Auth::user()->lastname}} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('bo.logout') }}"
onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
{{ __('Déconnexion') }}
</a>
<form id="logout-form" action="{{ route('bo.logout') }}" method="POST" class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</header>

View file

@ -1,10 +1,6 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
@auth('backend')
<nav id="navbarSupportedContentLeft" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse overflow-auto">
<div class="position-sticky pt-3">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="{{route('home')}}" target="_front">{{ __('Voir le site') }}</a>
</li>
</ul>
<div class="accordion" id="accordionMenu">
<div class="accordion-item">
<h2 class="accordion-header" id="headingSliders">
@ -23,11 +19,11 @@
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingPages">
<button class="accordion-button {{ ( request()->routeIs('bo.static_page') || request()->routeIs('bo.faq') ) ? '' : 'collapsed' }}" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePages" aria-expanded="true" aria-controls="collapsePages">
<button class="accordion-button {{ ( request()->routeIs('bo.static_page*') ) ? '' : 'collapsed' }}" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePages" aria-expanded="true" aria-controls="collapsePages">
<i class="fa fa-file-word"></i>&nbsp;{{ __('models.classes.static_pages') }}
</button>
</h2>
<div id="collapsePages" class="accordion-collapse collapse {{ ( request()->routeIs('bo.static_page') || request()->routeIs('bo.faq') ) ? 'show' : '' }}" aria-labelledby="headingPages" data-bs-parent="#accordionMenu">
<div id="collapsePages" class="accordion-collapse collapse {{ ( request()->routeIs('bo.static_page*') ) ? 'show' : '' }}" aria-labelledby="headingPages" data-bs-parent="#accordionMenu">
<div class="accordion-body">
<ul class="nav flex-column mb-2">
@foreach(App\Enums\StaticPageType::toArray() as $pageType)
@ -88,8 +84,8 @@
<div class="accordion-body">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="{{route('bo.orders.index')}}">{{ __('models.classes.orders') }}</a>
<a class="nav-link" href="{{route('bo.orders.today')}}">{{ __('Commandes à retirer aujourd\'hui') }}</a>
<a class="nav-link {{ request()->routeIs('bo.orders.index') ? 'active' : '' }}" href="{{route('bo.orders.index')}}">{{ __('models.classes.orders') }}</a>
<a class="nav-link {{ request()->routeIs('bo.orders.today') ? 'active' : '' }}" href="{{route('bo.orders.today')}}">{{ __('Commandes à retirer aujourd\'hui') }}</a>
</li>
</ul>
</div>
@ -98,7 +94,7 @@
<!-- Points de Collecte -->
<div class="accordion-item">
<h2 class="accordion-header" id="headingCollecte">
<button class="accordion-button {{ ( request()->routeIs('bo.magasins*') || request()->routeIs('bo.creneaux*') ) ? '' : 'collapsed' }}" type="button" data-bs-toggle="collapse" data-bs-target="#collapseCollecte" aria-expanded="true" aria-controls="collapseCollecte">
<button class="accordion-button {{ ( request()->routeIs('bo.shops*') || request()->routeIs('bo.creneaux*') ) ? '' : 'collapsed' }}" type="button" data-bs-toggle="collapse" data-bs-target="#collapseCollecte" aria-expanded="true" aria-controls="collapseCollecte">
<i class="fas fa-store"></i>&nbsp;{{ __('Points de retrait') }}
</button>
</h2>
@ -106,7 +102,7 @@
<div class="accordion-body">
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link {{ request()->routeIs('bo.magasins*') ? 'active' : '' }}" href="{{route('bo.shops.index')}}">{{ __('models.classes.shops') }}</a>
<a class="nav-link {{ request()->routeIs('bo.shops*') ? 'active' : '' }}" href="{{route('bo.shops.index')}}">{{ __('models.classes.shops') }}</a>
</li>
</ul>
</div>
@ -136,3 +132,4 @@
</div>
</div>
</nav>
@endauth

View file

@ -23,7 +23,7 @@
</div>
<div class="row mb-3">
<div class="form-group">
<div class="word-counter" data-json='@json(['id' => $k])'></div>
<div class="word-counter" data-json='@json(['id' => "string{$k}"])'></div>
<input id="string{{$k}}" type="text" class="form-control" name="data[{{$k}}][value]"
aria-describedby="StringHelp{{$k}}"
placeholder="{{ $input['label'] }}" value="{{ old("data.{$k}.value") ?? ($input['value'] ?? '') }}" required>

View file

@ -1,7 +1,7 @@
<div class="input-group mt-1">
<label class="input-group-text bg-white text-danger font-weight-bold">{{ __('Inco') }}</label>
<input type="text" disabled="disabled" class="form-control" value="{{ $value }}">
<button class="btn btn-outline-secondary inco-copy-value" data-input-id={{ $inputId }} type="button">
<button class="btn btn-outline-secondary inco-copy-value" data-input-id={{ "{$inputId}" }} type="button">
<i class="fa-regular fa-copy"></i>
<i class="fa-solid fa-arrow-right"></i>
</button>

View file

@ -123,7 +123,7 @@
<div class="form-group">
<label for="productWeight"><b>{{ __('Poids') }}</b></label>
<div class="input-group">
<input name="weight" type="number" min="0" step="0.001" value="{{ number_format(floatval(old('weight', optional($productModel ?? null)->weight)), 3, '.', '') }}" class="form-control"
<input name="weight" type="number" min="0" step="0.001" value="{{ number_format(floatval(old('weight', round(intval(optional($productModel ?? null)->weight) / \App\Enums\WeightUnit::kg()->conversion, 3))), 3, '.', '') }}" class="form-control"
id="productWeight" aria-describedby="productWeightHelp">
<label class="input-group-text">Kg</label>
</div>
@ -395,9 +395,9 @@ $minicrudModel = $productModel ?? (object)['data' => $minicrudData];
<fieldset class="border mb-2 p-3">
<legend><span class="h3">{{ __("Valeurs Inco") }}</span></legend>
@php $incoValues = $productModel->xmlInco->translateToProductIncoInputs(); @endphp
@foreach ($productModel->data as $k => $input)
<label><b>{{ $input['label'] }}</b></label>
@include('back.products.form-xml-attribute', ['inputId' => $k,'value' => $incoValues[$k]])
@foreach ($productModel->data_object as $k => $input)
<label><b>{{ $input->label }}</b></label>
@include('back.products.form-xml-attribute', ['inputId' => $input->inputType->label.$k,'value' => $incoValues[$k]])
@endforeach
</fieldset>
@endif

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