Compare commits

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

69 commits

Author SHA1 Message Date
Jérémy Munsch
3c80fe8c4c
Fix weekday translation 2023-02-02 18:13:29 +01:00
Jérémy Munsch
c2aa74e1ce
Fix product tax rate 2023-02-02 18:03:50 +01:00
Jérémy Munsch
1f9bc0f04c
theme config merge fix 2023-02-02 16:57:12 +01:00
Jérémy Munsch
7b54a77d7d
fix contact mail model event 2023-02-02 16:23:51 +01:00
Jérémy Munsch
ff5bf2418e
change weekday enum value for mairet 2023-02-02 16:01:02 +01:00
Jérémy Munsch
9816bb93d5
changed submodule url 2023-02-02 15:51:05 +01:00
Jérémy Munsch
237a17d815
fixed theme sliders 2023-02-02 15:09:42 +01:00
Jérémy Munsch
2ebcf417c0
fix migration for mairet dev 2023-02-02 14:34:31 +01:00
Jérémy Munsch
74f88ffc0e
change git submodules remotes to https 2023-02-02 14:13:49 +01:00
Jérémy Munsch
3d3f79cbdd
fix product form 2023-02-02 12:14:34 +01:00
Olivier Legat
a1a93195ac
fix backend extend 2023-02-02 12:06:50 +01:00
Olivier Legat
8349bf5ae7
fix modules list, fix blade extension in login blade, update package lock 2023-02-02 11:54:12 +01:00
Jérémy Munsch
29f07ad69a
added forgottent submodule theme 2023-02-02 11:26:29 +01:00
Jérémy Munsch
c23659c600
fix migration script 2023-01-31 09:04:20 +01:00
Jérémy Munsch
c944141342
SYNC 25
Theme Hudge files and foder reorder
2023-01-30 17:46:30 +01:00
Jérémy Munsch
04178116c9
bump composer 2023-01-30 12:33:36 +01:00
Jérémy Munsch
d2272a7a13
bump npm 2023-01-30 12:32:46 +01:00
Jérémy Munsch
3b243d6cca
SYNC A10
Static page + crud bo + fix Filemanager wysiwyg
2023-01-26 11:21:48 +01:00
Jérémy Munsch
75b33bc116
add ltr attr on blade html layout 2023-01-25 14:31:21 +01:00
Jérémy Munsch
f7ea8d0004
ORder Validation SYNC 23 2022-12-20 16:52:44 +01:00
Jérémy Munsch
b0a72941b5
Front customer backoffice SYNC 22 2022-12-20 13:02:55 +01:00
Jérémy Munsch
d681d55d68
Order validated Sync 21 2022-12-20 11:32:06 +01:00
Jérémy Munsch
529651895f
Order up to final click SYNC 20 2022-12-19 16:20:00 +01:00
Jérémy Munsch
d18a589869
fix bo timeslot closing 2022-12-07 12:29:26 +01:00
Jérémy Munsch
fb45dd4ace
general settings labels migration update 2022-12-07 10:38:55 +01:00
Jérémy Munsch
6869c289f5
Products filter bo 2022-12-06 16:50:32 +01:00
Jérémy Munsch
37725e43fe
Contact message system 2022-12-06 12:59:34 +01:00
Jérémy Munsch
51bb5be052
RGPD remove old customers and remove old ORders 2022-12-06 11:53:41 +01:00
Jérémy Munsch
e7fbe920e1
Remove order line + shipping point select 2022-12-05 17:51:18 +01:00
Jérémy Munsch
5aa0a479fd
Add Delivery Mode to orders 2022-12-05 15:21:54 +01:00
Jérémy Munsch
7d31eb4c72
Show has discount on product.edit 2022-12-05 14:29:24 +01:00
Jérémy Munsch
2d4f9111f4
Product starred fix 2022-12-05 14:14:51 +01:00
Jérémy Munsch
1b0a9d2a18
Handle policies SYNC 15 2022-12-02 14:55:16 +01:00
Jérémy Munsch
b2221b4fe8
Delivery Mode 2022-12-02 11:46:31 +01:00
Jérémy Munsch
a3fa02cfd5
Rangement enums + start delivery mode SYNC 14 2022-12-02 10:37:01 +01:00
Jérémy Munsch
950ff85cbd
single optin route triage 2022-11-30 17:05:37 +01:00
Jérémy Munsch
539f02539d
tunnel stepper SYNC 13 2022-11-30 17:03:43 +01:00
Jérémy Munsch
9fefc205fd
Basket filter unavailable products 2022-11-30 16:36:27 +01:00
Jérémy Munsch
94d84e4251
console.log on debug 2022-11-30 15:40:52 +01:00
Jérémy Munsch
3edc643bb3
Add Inco values display on BO 2022-11-30 11:43:05 +01:00
Jérémy Munsch
fdf548fd0f
Suppression setting orders_display_prices_for_auth SYNC 12 2022-11-29 16:47:22 +01:00
Jérémy Munsch
c8e21e7df8
Calulcator Basktet OK 2022-11-29 15:47:46 +01:00
Jérémy Munsch
e0718decca
Basket Calculation OK SYNC 11 2022-11-29 15:02:38 +01:00
Jérémy Munsch
a243880c58
remove fancybox 2022-11-28 10:33:42 +01:00
Jérémy Munsch
fc1f071262
fix nested view load pathes SYNC 10 2022-11-24 15:30:22 +01:00
Jérémy Munsch
4be962e01d
Change Product <-> Discount association, A product can now have only one discount associated. 2022-11-24 11:21:37 +01:00
Jérémy Munsch
b3f79cb114
git lab ci update 2022-11-24 11:17:43 +01:00
Jérémy Munsch
33b523ff82
Order Tunnel + Basket unit prices SYNC 9 2022-11-23 15:48:07 +01:00
Jérémy Munsch
7f456569b2
Rename products an ranges routes + fix add to basket actions SYNC 8 2022-11-23 13:00:44 +01:00
Jérémy Munsch
efe25f958a
Fix route orders 2022-11-23 11:59:25 +01:00
Jérémy Munsch
d3a963343c
remove quick orders SYNC 7 2022-11-23 11:48:47 +01:00
Jérémy Munsch
ac032cd6e6
fix git submodule add ckeditor plugins 2022-11-23 11:15:47 +01:00
Jérémy Munsch
94ae431d55
BASKET OK SYNC 6 2022-11-23 11:10:40 +01:00
Jérémy Munsch
f043eeaa8d
Stylelint upgrade add sccs base rules 2022-11-22 15:49:33 +01:00
Jérémy Munsch
001691e342
npm update 2022-11-21 11:53:35 +01:00
Jérémy Munsch
ee17d3c445
CART TEMP SYNC 5 2022-11-18 18:21:47 +01:00
Jérémy Munsch
7564d9eba3
Contact page ok SYNC 4 2022-11-17 17:39:54 +01:00
Jérémy Munsch
bde869120e
Sliders SYNC3 2022-11-17 14:55:03 +01:00
Jérémy Munsch
e28970ba13
Remove basic theme from boutique engine
Theme is now a submodule on boutique engine on resources/views/front
2022-11-16 18:42:20 +01:00
Jérémy Munsch
149b54f3ed
Swiper slider test 2022-11-16 17:41:13 +01:00
Jérémy Munsch
f99e259bd5
Tons of fixes Refonte SYNC 1
- Authentification System OK
2022-11-16 12:47:41 +01:00
Jérémy Munsch
ffe82091fb
bo layout navbar redirect to front 2022-11-08 16:05:45 +01:00
Jérémy Munsch
7b48bb605a
yield title on bo layout 2022-11-08 16:04:16 +01:00
Jérémy Munsch
527d0eb828
phpcbf pass 2022-11-08 15:59:16 +01:00
Jérémy Munsch
aa98594f20
gitlab ci update 2022-11-08 15:49:59 +01:00
Jérémy Munsch
e8773a9f38
Crud StaticPage(Content) 2022-11-08 15:41:10 +01:00
Jérémy Munsch
899bc41db5
Composer bump 2022-10-03 11:18:14 +02:00
Jérémy Munsch
3659beb7b5
Remove Regate stuff + Remove salaries stuff WIP 2022-10-03 11:03:30 +02:00
Jérémy Munsch
397434d8db
Upgrade CreateAdmin command 2022-09-30 15:11:52 +02:00
609 changed files with 48992 additions and 57538 deletions

View file

@ -11,5 +11,8 @@ trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.vue]
indent_size = 2
[*.{yml,yaml}]
indent_size = 2

View file

@ -3,7 +3,6 @@ APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=
APP_THEME=basic
LOG_CHANNEL=daily
LOG_LEVEL=debug

41
.eslintrc.json Normal file
View file

@ -0,0 +1,41 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:vue/vue3-strongly-recommended",
"@vue/eslint-config-typescript"
],
"overrides": [
{
"files": ["*.ts", "*.vue"],
"rules": {
"no-undef": "off"
}
},
{
"files": ["*.vue"],
"rules": {
"indent": "off"
}
}
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["vue", "@typescript-eslint"],
"ignorePatterns": ["webpack.mix.js", "**/vendor/*.js", "**/vendor/**/*.js"],
"rules": {
"indent": ["error", 4, { "SwitchCase": 1 }],
"linebreak-style": ["error", "unix"],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"vue/script-indent": ["error", 2, { "baseIndent": 0, "switchCase": 1 }],
"vue/one-component-per-file": "off"
}
}

21
.gitignore vendored
View file

@ -1,22 +1,29 @@
/node_modules
/public/hot
/public/storage
/public/images/content
/public/images/gammes
/public/images/produits
/public/images/slides
/public/b-o/
/public/mix-manifest.json
/public/assets/*
/public/js/*
/public/css/*
/public/fonts/*
/public/content/*
/public/images/*
/public/cache/*
/public/uploads/*
mix-manifest.json
/storage/*.key
/storage/framework/laravel-excel/*
/vendor
.env
.env.backup
.env.dev
.env.prod
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
auth.json
workspace.code-workspace
.vscode
# IGNORE THEME SUBMODULES CHANGES
resources/theme

View file

@ -1,11 +1,11 @@
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
stages:
- Security
- CodingStandards
- Builds
composer-security:
stage: Security
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
script:
- local-php-security-checker --format=json --path=./composer.lock > composer-security-checker-report.json
artifacts:
@ -14,9 +14,13 @@ composer-security:
paths:
- composer-security-checker-report.json
allow_failure: true
needs: []
npm-security:
stage: Security
image: node:17.9.1
before_script:
- npm install --location=global audit-ci@^6
script:
- audit-ci -p npm -o json > npm-security-checker-report.json
artifacts:
@ -25,12 +29,14 @@ npm-security:
paths:
- npm-security-checker-report.json
allow_failure: true
needs: []
phpcs:
stage: CodingStandards
image: $CI_REGISTRY/service-web-lab/ldc-web-images-docker/ldcweb-qa:php7.3-1.0
before_script:
- composer config gitlab-token.gitlab.com $CI_DEPLOY_USER $CI_DEPLOY_PASSWORD
- composer install
- composer install --no-scripts
cache:
paths:
- vendor/
@ -43,4 +49,70 @@ phpcs:
- phpcs-report.xml
reports:
junit: phpcs-report.xml
allow_failure: false
allow_failure: false
needs:
- composer-security
- npm-security
eslint:
stage: CodingStandards
image: node:17.9.1
before_script:
- npm install
cache:
paths:
- node_modules/
script:
- npm run eslint-ci --silent 2>/dev/null 1> eslint-report.xml
artifacts:
when: on_failure
expose_as: 'eslint'
paths:
- eslint-report.xml
reports:
junit: eslint-report.xml
allow_failure: false
needs:
- composer-security
- npm-security
stylelint:
stage: CodingStandards
image: node:17.9.1
before_script:
- npm install
cache:
paths:
- node_modules/
script:
- npm run stylelint-ci --silent 2>/dev/null 1> stylelint-report.json
artifacts:
when: on_failure
expose_as: 'stylelint'
paths:
- stylelint-report.json
allow_failure: false
needs:
- composer-security
- npm-security
front-build:
stage: Builds
image: node:17.9.1
before_script:
- npm install
cache:
paths:
- node_modules/
script:
- npm run prod > front-build-report.json
artifacts:
when: on_failure
expose_as: 'front-build-report'
paths:
- front-build-report.json
allow_failure: false
needs:
- phpcs
- eslint
- stylelint

9
.gitmodules vendored Normal file
View file

@ -0,0 +1,9 @@
[submodule "resources/js/vendor/ckeditor/github/ckeditor5-fullscreen"]
path = resources/js/vendor/ckeditor/github/ckeditor5-fullscreen
url = https://github.com/leknoppix/ckeditor5-fullscreen.git
[submodule "resources/js/vendor/ckeditor/github/ckeditor5-heading-with-id"]
path = resources/js/vendor/ckeditor/github/ckeditor5-heading-with-id
url = https://github.com/archonic/ckeditor5-heading-with-id.git
[submodule "resources/theme"]
path = resources/theme
url = git@gitlab.com:service-web-lab/boutique-cnc-themes.git

39
.stylelintrc.json Normal file
View file

@ -0,0 +1,39 @@
{
"extends": [
"stylelint-config-standard-scss",
"stylelint-config-sass-guidelines",
"stylelint-config-idiomatic-order"
],
"plugins": ["stylelint-scss"],
"files": ["**/*.css", "**/*.scss"],
"ignoreFiles": [
"**/vendor/*.css",
"**/vendor/**/*.css",
"**/vendor/*.scss",
"**/vendor/**/*.scss",
"**/vendor/*.sass",
"**/vendor/**/*.sass",
"**/vendor/*.less",
"**/vendor/**/*.less"
],
"customSyntax": "postcss-scss",
"rules": {
"indentation": 4,
"string-quotes": "double",
"selector-max-id": null,
"color-no-invalid-hex": true,
"order/properties-alphabetical-order": null,
"max-nesting-depth": 5,
"selector-max-compound-selectors": 5,
"scss/double-slash-comment-empty-line-before": [
"always",
{
"ignore": [
"between-comments",
"stylelint-commands",
"inside-block"
]
}
]
}
}

View file

@ -1,3 +1,8 @@
## 2.0.0
- Removed Regate stuff
- Removed Salaries stuff
## 1.0.1
- Change LDC CRM Client repository
- composer update

View file

@ -33,6 +33,12 @@ Project root :
```
npm install && npm run prod
```
## Theme config
- To reload theme config on engine `php artisan config:sync`
- Theme config can be dynamic in `theme.config.php` inserting `MiniCrud`inputs inside, these muste be tested.
## Back-Office
Création de compte

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;
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Support\Carbon;
class IntToEpochCast implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function get($model, string $key, $value, array $attributes)
{
// phpcs:enable
if (\is_null($value)) {
return null;
}
$epoch = Carbon::createFromTimestamp(0)->timezone('utc');
$time = Carbon::now()->timezone('utc')->parse(\intval($value));
$minutesDiff = $time->diffInMinutes($epoch);
$hoursDiff = str_pad(floor($minutesDiff / 60), 2, '0', STR_PAD_LEFT);
$minutesDiff = str_pad($minutesDiff % 60, 2, '0', STR_PAD_LEFT);
return "{$hoursDiff}:{$minutesDiff}";
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
* @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceBeforeLastUsed
*/
public function set($model, string $key, $value, array $attributes)
{
// phpcs:enable
if (\is_null($value) or !\is_string($value)) {
return null;
}
$time = explode(':', $value);
$time = $time ? $time : [];
return Carbon::createFromTimestamp(0)->timezone('utc')
->setTime($time[0] ?? 0, $time[1] ?? 0)->timestamp;
}
}

View file

@ -4,7 +4,8 @@ namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateAdmin extends Command
{
@ -13,14 +14,14 @@ class CreateAdmin extends Command
*
* @var string
*/
protected $signature = 'create:admin';
protected $signature = 'user:create';
/**
* The console command description.
*
* @var string
*/
protected $description = 'creates an admin user upon password entry';
protected $description = 'Create a backend admin user';
/**
* Execute the console command.
@ -29,20 +30,71 @@ class CreateAdmin extends Command
*/
public function handle()
{
$firstname = $this->ask('What is the firstname?', 'Equipe');
$lastname = $this->ask('What is the lastname?', 'Web');
$email = $this->ask('What is the email?', 'equipe.web@ldc.fr');
$pwd = $this->secret('What is the password?');
$email = null;
while (is_null($email)) {
try {
$tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr');
$validator = Validator::make(['email' => $tmp], [
'email' => 'required|unique:users,email|email:rfc,dns'
]);
$validator->validate();
$email = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid email %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$firstname = null;
while (is_null($firstname)) {
try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:5,max:255'
]);
$validator->validate();
$firstname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid firstname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$lastname = null;
while (is_null($lastname)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:5,max:255'
]);
$validator->validate();
$lastname = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
\implode(',', collect($e->errors())->flatten()->all())
));
continue;
}
}
$password = $this->secret('Type the wanted user password');
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->date_of_birth = now();
$user->email = $email;
$user->password = Hash::make($pwd);
$user->password = $password;
$user->email_verified_at = now();
$user->role = 1;
$user->save();
// FIXME: Select role, Enum ?
$user->role = 1;
$user->saveOrFail();
$this->info('User created.');
}

View file

@ -1,37 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Jobs\CsvSalariesReader as JobCsvSalarieReader;
class ImportSalaries extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import:salaries';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Import Salaries, pour provision des clients';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->info('Running the Job, this could take some time.');
$job = new JobCsvSalarieReader();
$job->console = $this;
$job->handle();
$this->info('Job completed');
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use App\Enums\GeneralConfigType;
use App\Models\GeneralConfig;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class SyncThemeSettings extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'config:sync_theme';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Synchronize theme config with database.';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
// * Force config clearance.
Artisan::call('config:clear');
// * Force cache to be cleared ( reload GeneralConfigs )
Artisan::call('cache:clear');
// * Fetch or create theme config
/** @var \App\Models\GeneralConfig */
$config = GeneralConfig::query()->where('type', GeneralConfigType::theme())->firstOrNew();
// * Fetch Bo Theme Settings.
$data = config('theme.bo_theme_settings');
$config->data = $data;
$config->saveOrFail();
$this->info('Theme config is overrided with the latest theme.config.php content.');
}
}

View file

@ -2,6 +2,13 @@
namespace App\Console;
use App\Jobs\DeleteOlds\DeleteOldCollectionException;
use App\Jobs\DeleteOlds\DeleteOldCustomers;
use App\Jobs\DeleteOlds\DeleteOldOrders;
use App\Jobs\DeleteOlds\DeleteOldTimeslots;
use App\Jobs\ProductsOrderableUpdate;
use App\Jobs\ProductsStatusUpdate;
use App\Jobs\SlidesStatusUpdate;
use App\Jobs\XmlIncoReader as JobsXmlIncoReader;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -24,9 +31,19 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// * Update models planified booleans changes
$schedule->job(SlidesStatusUpdate::class)->everyMinute();
$schedule->job(ProductsStatusUpdate::class)->everyMinute();
$schedule->job(ProductsOrderableUpdate::class)->everyMinute();
$schedule->command('sitemap:refresh')->dailyAt('16:13');
$schedule->command('ldcweb:statuschecker')->everyFifteenMinutes();
$schedule->job(JobsXmlIncoReader::class)->name('xml-inco-reader')->onOneServer()->dailyAt('15:00');
$schedule->job(DeleteOldOrders::class)->dailyAt('23:00');
$schedule->job(DeleteOldCustomers::class)->dailyAt('22:50');
$schedule->job(DeleteOldCollectionException::class)->dailyAt('10:42');
$schedule->job(DeleteOldTimeslots::class)->everyMinute();
}
/**

View file

@ -0,0 +1,35 @@
<?php
namespace App\Enums\DeliveryMode;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self inShop()
*/
final class DeliveryModeEnum extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'inShop' => 1
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'inShop' => __('À la boutique')
];
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace App\Enums\Discount;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self amount()
* @method static self percent()
*/
final class DiscountType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'amount' => 0,
'percent' => 1,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'amount' => trans('Montant'),
'percent' => trans('Pourcentage'),
];
}
/**
* Enum units
*
* @return array<float>
*/
protected static function units(): array
{
return [
'amount' => '€',
'percent' => '%'
];
}
/**
* Enum steps
*
* @return array<float>
*/
protected static function steps(): array
{
return [
'amount' => 0.01,
'percent' => 1
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'unit') {
return $this->units()[$this->getDefinition()] ?? null;
}
if ($attribute === 'step') {
return $this->steps()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self global()
* @method static self orders()
* @method static self theme()
*/
final class GeneralConfigType extends BaseEnumRoutable
{
/**
* Values
*
* @return array
*/
protected static function values(): array
{
return [
'global' => 1,
'orders' => 2,
'theme' => 3,
];
}
/**
* Labels
*
* @return array
*/
protected static function labels(): array
{
return [
'global' => __('Configuration du site'),
'orders' => __('Configuration des commandes'),
'theme' => __('Configuration du theme'),
];
}
}

48
app/Enums/InputType.php Normal file
View file

@ -0,0 +1,48 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self boolean()
* @method static self image()
* @method static self string()
* @method static self text()
* html autorisé
* @method static self url()
* @method static self imageArray()
* @method static self labelArray()
* @method static self dropdownArray()
* html autorisé
* @method static self miniblocArray()
* html autorisé
* @method static self videoVimeo()
* @method static self number()
* @method static self email()
*/
final class InputType extends BaseEnumRoutable
{
/**
* Values
*
* @return array
*/
protected static function values(): array
{
return [
'boolean' => 0,
'image' => 1,
'string' => 2,
'text' => 3,
'url' => 4,
'imageArray' => 5,
'labelArray' => 6,
'dropdownArray' => 7,
'miniblocArray' => 8,
'videoVimeo' => 9,
'number' => 10,
'email' => 11
];
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self pce()
* @method static self kgm()
* @method static self colis()
*/
final class InvoicingUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pce' => 'PCE',
'kgm' => 'KGM',
'colis' => 'COLIS',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pce' => __('Pièce'),
'kgm' => __('Kg'),
'colis' => __('Colis'),
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* Aucune action n'a été entreprise.
* @method static self preparation()
* La commande est en préparation
* @method static self ready()
* La commande est prête a être récupérée
* @method static self recovered()
* La commande à été récupérée par le client
*/
final class DeliveryStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'preparation' => 1,
'ready' => 2,
'recovered' => 3
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('À faire'),
'preparation' => __('En préparation'),
'sent' => __('Envoyé'),
'recovered' => __('Récupéré'),
];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self reviewBasket()
* @method static self shippment()
* @method static self shippingMethod()
* @method static self paymentReview()
*/
final class OrderProcess extends BaseEnumRoutable
{
/**
* Values
*
* @return array
*/
protected static function values(): array
{
return [
'reviewBasket' => 0,
'shippingMethod' => 1,
'shippment' => 2,
'paymentReview' => 3
];
}
/**
* Labels
*
* @return array
*/
protected static function labels(): array
{
return [
'reviewBasket' => trans('Panier'),
'shippingMethod' => trans('Expédition - mode'),
'shippment' => trans('Expédition - détails'),
'paymentReview' => trans('Paiement')
];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* Payment en attente
* @method static self failed()
* Payment à échoué
* @method static self payed()
* Le règlement à bien été validé
*/
final class PaymentStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'failed' => 1,
'payed' => 3
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('En attente'),
'failed' => __('Échec'),
'payed' => __('Réglée')
];
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Enums\Order;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self pending()
* La commande n'est pas encore évaluée
* @method static self cancelled()
* La commande est annulée
* @method static self validated()
* La commande est validée (pour préparation)
*/
final class ValidationStatus extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pending' => 0,
'cancelled' => 1,
'validated' => 2,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pending' => __('En attente de validation'),
'cancelled' => __('Annulée'),
'validated' => __('Validée'),
];
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self pce()
* @method static self kgm()
* @method static self colis()
*/
final class OrderUnit extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'pce' => 'PCE',
'kgm' => 'KGM',
'colis' => 'COLIS',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'pce' => __('Pièce'),
'kgm' => __('Kg'),
'colis' => __('Colis'),
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace App\Enums\Product;
use App\Enums\InputType;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self default()
* @method static self inco()
*/
final class DefaultProductType extends BaseEnumRoutable
{
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'default' => 'De base',
'inco' => 'Alimentaire lié a Inco',
];
}
/**
* Get Inco minicrud inputs for specific product type inco
* ! Attention toute modification après la mise en
* ! production doit faire l'objet d'une migration
* ! pour réaligner le contenu du champ data
* ! pour chaque type de produit
* ! sous peine de de tout casser
*
* @return array
*/
public static function incoInputs(): array
{
return [
'ingredients' => [
'label' => 'Ingrédients',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'preparation_advices' => [
'label' => 'Conseils de préparation',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'allergens' => [
'label' => 'Ingrédients allèrgenes',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'selling_arguments' => [
'label' => 'Arguments de vente',
'inputType' => InputType::text(),
'text' => '',
'validate' => 'nullable|string'
],
'servings' => [
'label' => 'Portions',
'inputType' => InputType::number(),
'value' => '',
'validate' => 'numeric|min:0'
],
'servings_type' => [
'label' => 'Type de portions',
'inputType' => InputType::string(),
'value' => '',
'validate' => 'nullable|string|max:150'
],
];
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Enums;
namespace App\Enums\Product;
use Kwaadpepper\Enum\BaseEnum;

View file

@ -0,0 +1,71 @@
<?php
namespace App\Enums\Product;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* Calcul par unité de produits
*
* @method static self twenty()
* @method static self ten()
* @method static self five()
*
* @property-read float $rate The TVA rate.
*/
final class TaxRate extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'twenty' => 0,
'ten' => 1,
'five' => 2
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'twenty' => trans('Taux normal'),
'ten' => trans('Taux reduit'),
'five' => trans('Taux très reduit')
];
}
/**
* Enum rates
*
* @return array<float>
*/
protected static function rates(): array
{
return [
'twenty' => 20,
'ten' => 10,
'five' => 5.5,
];
}
/**
* @param string $attribute
* @return integer|string
*/
public function __get(string $attribute)
{
if ($attribute === 'rate') {
return $this->rates()[$this->getDefinition()] ?? null;
}
return parent::__get($attribute);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace App\Enums\Product;
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

@ -1,47 +0,0 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self public()
* @method static self salaries()
*/
final class SettingRegisterMode extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'public' => 'public',
'salaries' => 'salaries',
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'public' => __('Public'),
'salaries' => __('Salaries'),
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* @method static self home()
* Page d'accueil
* @method static self contact()
* Page de contact
* @method static self legals()
* Page mentions légales
* @method static self sellconditions()
* Page conditions de ventes
* @method static self confidentiality()
* Page politique de confidentialitée
* @method static self faq()
* Page faq
* @method static self crud()
* Page de contenus libre en CRUD.
*/
final class StaticPageType extends BaseEnumRoutable
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'home' => 0,
'contact' => 1,
'legals' => 2,
'sellconditions' => 3,
'confidentiality' => 4,
'faq' => 5,
'crud' => 6
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'home' => trans('Accueil'),
'contact' => trans('Contact'),
'legals' => trans('Mentions Légales'),
'sellconditions' => trans('CGV'),
'confidentiality' => trans('Politique de confidentialité'),
'faq' => trans('FAQ'),
'crud' => trans('Pages libres'),
];
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Enums\TimeSlot;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self monday()
* @method static self tuesday()
* @method static self wednesday()
* @method static self thursday()
* @method static self friday()
* @method static self saturday()
* @method static self sunday()
*/
final class WeekDay extends BaseEnum
{
/**
* Enum values
*
* @return array
*/
protected static function values(): array
{
return [
'monday' => 1,
'tuesday' => 2,
'wednesday' => 3,
'thursday' => 4,
'friday' => 5,
'saturday' => 6,
'sunday' => 7,
];
}
/**
* Enum labels
*
* @return array
*/
protected static function labels(): array
{
return [
'monday' => trans('Lundi'),
'tuesday' => trans('Mardi'),
'wednesday' => trans('Mercredi'),
'thursday' => trans('Jeudi'),
'friday' => trans('Vendredi'),
'saturday' => trans('Samedi'),
'sunday' => trans('Dimanche')
];
}
}

View file

@ -5,14 +5,10 @@ namespace App\Enums;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This is an enumeration class,
* the following methods are its possible values
*
* @method static self authenticated()
* @method static self guest()
*
* @method static self admin()
* @method static self user()
*/
class PriceShowingMode extends BaseEnumRoutable
final class UserRole extends BaseEnumRoutable
{
/**
* Enum values
@ -22,8 +18,8 @@ class PriceShowingMode extends BaseEnumRoutable
protected static function values(): array
{
return [
'authenticated' => 'authenticated',
'guest' => 'guest',
'admin' => 1,
'user' => 0,
];
}
@ -35,8 +31,8 @@ class PriceShowingMode extends BaseEnumRoutable
protected static function labels(): array
{
return [
'authenticated' => __('Client Connecté'),
'guest' => __('Public'),
'admin' => trans('Administrateur'),
'user' => trans('Utilisateur'),
];
}
}

View file

@ -1,65 +0,0 @@
<?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' => __('Gramme'),
'kg' => __('Kg'),
'ton' => __('Tonne'),
];
}
/**
* conversion values
* base kg()
*
* @return array
*/
public static function convert(): array
{
return [
'G' => 0.001,
'KG' => 1,
'T' => 1000,
];
}
/**
* Output the value as a string
* @return string
*/
public function __toString(): string
{
return (string)$this->label;
}
}

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

@ -2,10 +2,7 @@
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Facades\Mail;
use Throwable;
use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
class Handler extends ExceptionHandler
{
@ -26,72 +23,31 @@ class Handler extends ExceptionHandler
'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));
}
/**
* Report or log an exception.
*
* @param \Throwable $e
* @param \Throwable $e
* @return void
*
* @phpcs:ignore Squiz.Commenting.FunctionComment.EmptyThrows
* @throws \Exception
*/
public function report(Throwable $e)
public function report(\Throwable $e)
{
if (get_class($e) == RedirectTo::class) {
abort(redirect($e->url));
}
if (config('app.debug') === false) {
if ($this->shouldntReport($e)) {
return;
}
Mail::send([
'html' => 'emails.exception',
'text' => 'emails.exception-plain'
], [
'exception' => $e,
'appName' => config('app.name')
], function ($m) {
$m->to(
config('mail.contactsList.serviceweb'),
sprintf('%s Exception Handler', config('app.name'))
)->subject('Need help !, an error occured in your application, please check Logs ASAP');
});
if ($this->shouldntReport($e)) {
return;
}
parent::report($e);
}
public function register()
{
$this->reportable(function (Throwable $e) {
Integration::captureUnhandledException($e);
});
}
}

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

@ -1,164 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Support\Facades\Auth;
class BasketController extends Controller
{
/**
* ajout de produit au panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function addToCart(Request $request)
{
$instance = $request->instance ?? "panier";
$product = Product::find($request->id);
Cart::instance($instance);
Cart::add($product, $request->qty);
return $this->getCart($instance);
}
/**
* ajout de plusieurs produits au panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function addToCartLot(Request $request)
{
$instance = $request->instance ?? "panier";
Cart::instance($instance);
$lot = $request->items;
foreach ($lot as $item) {
$product = Product::find($item["cartItem"]["id"]);
Cart::add($product, $item["cartItem"]["qty"]);
}
return $this->getCart($instance);
}
/**
* suppression de produit du panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function removeFromCart(Request $request)
{
$instance = $request->instance ?? "panier";
$rowId = $request->rowId;
Cart::instance($instance);
Cart::remove($rowId);
return $this->getCart($instance);
}
/**
* vidage de panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function emptyCart(Request $request)
{
$instance = $request->instance ?? "panier";
Cart::instance($instance);
Cart::destroy();
return $this->getCart($instance);
}
/**
* récupération du panier
*
* @param \Illuminate\Http\Request $request
*
* @return array
*/
public function getCartRequest(Request $request)
{
$instance = $request->instance ?? "panier";
Cart::instance($instance);
return $this->getCart($instance);
}
/**
* return cart data array
*
* @param string $instance
* @return array
*/
public function getCart(string $instance = "panier")
{
Cart::instance($instance);
$dbpanier = new Collection();
foreach (Cart::content() as $row) {
$product = $row->model;
$product->calculatedPrices = $product->getCalcPrices();
$row->setTaxRate($product->tax->rate);
$row->setDiscountRate($product->discountRate);
$cartextendedItem = array(
'cartItem' => $row,
'product' => $product
);
$dbpanier->add($cartextendedItem);
}
$cartInfos = array(
"totalTTC" => Cart::total(),
// Total des taxes.
'totalTVA' => Cart::tax(),
// Total sans les taxes.
'totalHT' => Cart::subtotal(),
// Poids total des items.
'totalweight' => Cart::weight(),
// Total avant taxes et remises.
'totalHTbefore' => Cart::priceTotal(),
// Total des remises.
'totaldiscount' => Cart::discount(),
// Nb de produits dans le cart.
'countItems' => Cart::count(),
);
$cartInfosNoPrice = array(
"totalTTC" => "-.--",
// Total des taxes.
'totalTVA' => "-.--",
// Total sans les taxes.
'totalHT' => "-.--",
// Poids total des items.
'totalweight' => Cart::weight(),
// Total avant taxes et remises.
'totalHTbefore' => "-.--",
// Total des remises.
'totaldiscount' => "-.--",
// Nb de produits dans le cart.
'countItems' => Cart::count(),
);
$cartReturn = array($dbpanier,$cartInfosNoPrice);
if (config('bosettings.show_prices_authenticated') == "authenticated") {
{
if (Auth::guard('customer')->check()) {
$cartReturn = array($dbpanier,$cartInfos);
}
}
}
if (config('bosettings.show_prices_guest') == "guest") {
$cartReturn = array($dbpanier,$cartInfos);
}
return $cartReturn;
}
}

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:backend')->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

@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Contact;
use Illuminate\Http\Request;
class ContactMessagesController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $contactMessages */
$contactMessages = Contact::query();
if ($request->search) {
$this->searchQuery(
$contactMessages,
$request->search,
'order_number',
'firstname',
'lastname',
'subject',
'email',
);
}
$this->sortQuery($contactMessages);
$searchFields = trans('Réf, Nom, Prénom, Objet, E-mail');
$contactMessages = $contactMessages->orderBy('done', 'ASC')
->orderBy('created_at', 'DESC')->paginate();
return view('back::pages.contacts.index', compact('contactMessages', 'searchFields'));
}
/**
* Show contact message
*
* @param Contact $contact
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Contact $contact)
{
return view('back::pages.contacts.show', [
'contactMessage' => $contact
]);
}
/**
* Show contact message
*
* @param Contact $contact
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function done(Contact $contact)
{
$contact->done = true;
if ($contact->saveOrFail()) {
return response()->redirectToRoute('bo.contacts.index')->with(
'success',
trans('Le message à été marqué comme traité')
);
}
return back();
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Contact $contact
* @return \Illuminate\Http\Response
*/
public function destroy(Contact $contact)
{
if ($contact->deleteOrFail()) {
return redirect()
->route('bo.contacts.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => __('Message de contact')]
));
}
return back();
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Content;
use Illuminate\Http\Request;
class ContentController extends Controller
{
/**
* Manage content page
*
* @param Request $request
* @param string $key
* @return \Illuminate\Http\Response
*
*/
public function manage(Request $request, string $key)
{
$content = Content::where('key', $key)->first();
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$banner = $request->banner;
$title = $request->title;
$chapeau = $request->chapeau;
$maintext = $request->value;
$request->validate([
'banner' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max_width=' .
config('theme.content_banner_maxwidth') .
',max_height=' . config('theme.content_banner_maxheight'),
'value' => 'required',
]);
if ($banner !== null) {
if ($content->banner !== null && file_exists(public_path('images/content/') . $content->banner)) {
unlink(public_path('images/content/') . $content->banner);
}
$imageName = $content->id . '.' . $banner->extension();
$banner->move(public_path('images/content'), $imageName);
$content->banner = $imageName;
}
$content->title = $title;
$content->chapeau = $chapeau;
$content->value = $maintext;
$content->save();
return redirect()->route('bo.content', ['key' => $content->key, 'content' => $content])
->with('success', 'Modifications enregistrés !');
}//end if
return view('bo.content.manage', compact('content'));
}
}

View file

@ -1,80 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Magasin;
use App\Models\TimeslotSetting;
use Illuminate\Http\Request;
class CreneauxController extends Controller
{
/**
* Display creneaux settings form
*
* @param Request $request
* @param integer $id
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function settings(Request $request, int $id)
{
// @phpcs:enable
$magasin = Magasin::findOrFail($id);
$timeslotSettings = $magasin->timeslotSettings;
if ($timeslotSettings->count() === 0) {
// Aucun settings pour ce magasin.
$timeslotSettings = $this->makeCreneauxSettings($id);
}
return view('bo.creneaux.settings', compact('timeslotSettings', 'magasin'));
}
/**
* Retrieve creneaux settings
*
* @param integer $id
* @return mixed
*/
public function makeCreneauxSettings(int $id)
{
for ($i = 0; $i < 7; $i++) {
$dayset = new TimeslotSetting();
$dayset->magasin_id = $id;
$dayset->day_index = '' . $i;
$dayset->save();
}
return Magasin::findOrFail($id)->timeslotSettings;
}
/**
* save settings
*
* @param Request $request
* @param integer $id
* @return \Illuminate\Http\RedirectResponse
*/
public function updateSettings(Request $request, int $id)
{
$plage = $request->plage;
$magasin_id = $id;
foreach ($plage as $index => $day) {
$setting = TimeslotSetting::where('day_index', '' . $index)
->where('magasin_id', $magasin_id)
->firstOrFail();
// $setting->magasin_id = $magasin_id; ne doit pas changer
$setting->morning_start = $day['morning_start'];
$setting->morning_end = $day['morning_end'];
$setting->afternoon_start = $day['afternoon_start'];
$setting->afternoon_end = $day['afternoon_end'];
$setting->interval = $day['interval'];
$setting->max_orders_per_interval = $day['max_orders_per_interval'];
$setting->min_order_preparation = $day['min_order_preparation'];
$setting->save();
}
return redirect()->route('bo.creneaux.settings', $magasin_id)->with('success', 'Modifications enregistrées !');
}
}

View file

@ -13,6 +13,6 @@ class DashboardController extends Controller
*/
public function index()
{
return view('bo.dashboard');
return view('back::pages.dashboard');
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\DeliveryMode\UpdateDeliveryModeRequest;
use App\Models\DeliveryMode;
class DeliveryModeController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$deliveryModes = DeliveryMode::paginate();
return view('back::pages.deliveryModes.index', compact('deliveryModes'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\DeliveryMode $deliveryMode
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function edit(DeliveryMode $deliveryMode)
{
$this->authorize('update', DeliveryMode::class);
return view('back::pages.deliveryModes.edit', compact(
'deliveryMode'
));
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\UpdateDeliveryModeRequest $request
* @param \App\Models\DeliveryMode $deliveryMode
* @return \Illuminate\Http\Response
*/
public function update(UpdateDeliveryModeRequest $request, DeliveryMode $deliveryMode)
{
$this->authorize('update', DeliveryMode::class);
$deliveryMode->fill($request->validated());
if ($deliveryMode->saveOrFail()) {
return redirect()->back()
->with('success', trans(
':model a été mis à jour',
['model' => trans('Mode de livraison')]
));
}
return back();
}
/**
* Change model is_available
*
* @param DeliveryMode $model
* @return \Illuminate\Http\RedirectResponse
*/
public function changeAvailable(DeliveryMode $model)
{
$this->authorize('update', DeliveryMode::class);
$model->is_available = !$model->is_available;
$model->saveOrFail();
return redirect()->back();
}
}

View file

@ -0,0 +1,170 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Discount\DiscountType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Discounts\StoreDiscountRequest;
use App\Http\Requests\Bo\Discounts\UpdateDiscountRequest;
use App\Models\Discount;
use App\Models\Product;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class DiscountController extends Controller
{
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $discounts */
$discounts = Discount::query();
if ($request->search) {
$this->searchQuery(
$discounts,
$request->search,
// * Search in ranges
function (Builder $q) use ($request) {
$types = \collect(DiscountType::toArray())->filter(function (DiscountType $type) use ($request) {
$test = Str::contains(
Str::lower($type->label),
\explode(' ', Str::lower(\trim($request->search)))
);
return Str::contains(
Str::lower($type->label),
\explode(' ', Str::lower(\trim($request->search)))
);
});
$q->orWhereIn('type', $types);
},
'discount_value'
);
} //end if
$this->sortQuery($discounts);
$searchFields = trans('Type');
$discounts->orderBy('updated_at', 'DESC');
$discounts = $discounts->paginate();
return view('back::pages.discounts.index', compact('discounts', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$existingProds = Product::all();
return view('back::pages.discounts.create', \compact(
'existingProds'
));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Discounts\StoreDiscountRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreDiscountRequest $request)
{
return DB::transaction(function () use ($request) {
$discount = new Discount();
$discount->fill($request->validated());
if ($discount->saveOrFail()) {
$discount->associateProducts($request->prods);
$discount->saveOrFail();
return redirect()
->route('bo.discounts.edit', $discount)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.discounts'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Discount $discount
* @return \Illuminate\Http\Response
*/
public function edit(Discount $discount)
{
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Product> */
$selectedProds = $discount->products;
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Product> */
$existingProds = Product::query()->whereNotIn('id', $selectedProds->pluck('id'))->get();
return view('back::pages.discounts.edit', [
'discountModel' => $discount,
'existingProds' => $existingProds,
'selectedProds' => $selectedProds
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Discounts\UpdateDiscountRequest $request
* @param \App\Models\Discount $discount
* @return \Illuminate\Http\Response
*/
public function update(UpdateDiscountRequest $request, Discount $discount)
{
return DB::transaction(function () use ($request, $discount) {
// * Minicrud fill
$discount->fill($request->validated());
$discount->associateProducts($request->prods);
if ($discount->saveOrFail()) {
return redirect()
->route('bo.discounts.edit', $discount)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.discounts'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Discount $discount
* @return \Illuminate\Http\Response
*/
public function destroy(Discount $discount)
{
if ($discount->deleteOrFail()) {
return redirect()
->route('bo.discounts.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.discounts'))]
));
}
return back();
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
class DownloadController extends Controller
{
/**
* Download a file from upload using control access
* Must be authenticated.
*
* @param string $filePath
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function download(string $filePath): StreamedResponse
{
if (!\auth('backend')->check()) {
abort(404);
}
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('uploads');
if (!$disk->exists($filePath)) {
abort(404);
}
return response()->stream(function () use ($disk, $filePath) {
$stream = $disk->readStream($filePath);
\fpassthru($stream);
\fclose($stream);
}, 200, [
'Content-Type' => File::mimeType($disk->path($filePath)),
'Content-Length' => $disk->size($filePath),
'Content-Disposition' => 'inline',
]);
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Faq;
use Illuminate\Http\Request;
class FaqController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$faq = Faq::all();
return view('bo.faq.index', compact('faq'));
}
/**
* Update faq data.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function saveDatas(Request $request)
{
$faq = $request->faq;
$looplimit = count($faq);
for ($i = 0; $i < $looplimit; $i++) {
$faqToInsert = !empty($faq[$i]["question"]) && !empty($faq[$i]["answer"]);
if (isset($faq[$i]["id"]) && $faq[$i]["id"] !== null && $faqToInsert) {
$f = Faq::findOrFail($faq[$i]["id"]);
$f->question = $faq[$i]["question"];
$f->answer = $faq[$i]["answer"];
$f->save();
} else {
if ($faqToInsert) {
$c = Faq::updateOrCreate([
'question' => $faq[$i]["question"],
'answer' => $faq[$i]["answer"]
]);
$faq[$i]["id"] = $c->id;
} else {
unset($faq[$i]);
$i--;
}
}
}
return $faq;
}
/**
* Remove the specified resource from storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function destroy(Request $request)
{
$faq = Faq::findOrFail($request->id);
$faq->delete();
return "Suppression terminée !";
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\UpdateGeneralConfigRequest;
use App\Models\GeneralConfig;
class GeneralConfigController extends Controller
{
/**
* General config index
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
$configs = GeneralConfig::paginate();
return view('back::pages.general-configs.index', compact('configs'));
}
/**
* Edit general config
*
* @param \App\Models\GeneralConfig $config
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factor
*/
public function edit(GeneralConfig $config)
{
return view('back::pages.general-configs.edit', ['config' => $config]);
}
/**
* Update General
*
* @param \App\Http\Requests\UpdateGeneralConfigRequest $request
* @param \App\Models\GeneralConfig $config
* @return voi\Illuminate\Http\RedirectResponse
*/
public function update(UpdateGeneralConfigRequest $request, GeneralConfig $config)
{
$config->fill(array_replace_recursive(
\json_decode(\json_encode($config->toArray()), true),
$request->validated()
));
$config->saveOrFail();
return redirect()->route('bo.general_configs.edit', $config)
->with('success', 'Enregistré !');
}
}

View file

@ -1,204 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Magasin;
use App\Models\CollecteException;
use Illuminate\Http\Request;
class MagasinsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$magasins = Magasin::all();
return view('bo.magasins.index', compact('magasins'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.magasins.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'address' => 'required|string|max:255',
'postcode' => 'required',
'city' => 'required|string|max:255',
]);
$shop = Magasin::create($request->except('_token'));
return redirect()->route('bo.magasins.edit', $shop->id)->with('success', 'Nouveau Magasin enregistré !');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$magasin = Magasin::findOrFail($id);
$exceptionsCollecte = $magasin->collecteExceptions()->orderBy('date')->get();
return view('bo.magasins.edit', compact('magasin', 'exceptionsCollecte'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$request->validate([
'name' => 'required|string|max:255',
'address' => 'required|string|max:255',
'postcode' => 'required',
'city' => 'required|string|max:255',
]);
$shop = Magasin::findOrFail($id);
$shop->fill($request->except('_token'))->save();
return redirect()->route('bo.magasins.edit', $shop->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$shop = Magasin::findOrFail($id);
$count = Magasin::count();
if ($count === 1) {
return redirect()->back()
->withErrors("Suppression impossible : Vous devez conserver au moins 1 point de retrait.");
}
$shop->delete();
return redirect()->route('bo.magasins.index');
}
/**
* bo
* ajx : affichage d'infos sur un magasin
*
* @param integer $id
* @param string|null $mode
* @return \Illuminate\Contracts\View\View
*/
public function getMagasinInfo(int $id, string $mode = null)
{
$magasin = Magasin::findOrFail($id);
if (!is_null($mode)) {
if ($mode == "tippy") {
return view('bo.magasins.tippy-card', compact('magasin'));
}
if ($mode == "json") {
return $magasin;
}
return view('bo.magasins.info-card', compact('magasin'));
}
return view('bo.magasins.info-popup', compact('magasin'));
}
/**
* Save collect exceptions
*
* @param Request $request
* @return array
*/
public function saveExceptions(Request $request)
{
$collecteException = $request->collecte_exception;
$looplimit = count($collecteException);
for ($i = 0; $i < $looplimit; $i++) {
$collecteExceptionToInsert = !empty($collecteException[$i]["date"]);
if (
isset($collecteException[$i]["id"]) &&
$collecteException[$i]["id"] !== null &&
$collecteExceptionToInsert
) {
$ce = CollecteException::findOrFail($collecteException[$i]["id"]);
$ce->date = $collecteException[$i]["date"];
$ce->comment = $collecteException[$i]["comment"];
$ce->magasin_id = $collecteException[$i]["magasin_id"];
$ce->save();
} else {
if ($collecteExceptionToInsert) {
$ce = CollecteException::updateOrCreate([
'date' => $collecteException[$i]["date"],
'comment' => $collecteException[$i]["comment"],
'magasin_id' => $collecteException[$i]["magasin_id"]
]);
$collecteException[$i]["id"] = $ce->id;
} else {
unset($collecteException[$i]);
$i--;
}
}
}//end for
// Remet dans l'ordre avant de renvoyer.
$this->arraySortByColumn($collecteException, "date");
return $collecteException;
}
/**
* helper function
*
* @param array $array
* @param string $column
* @param integer $direction
* @return void
*/
public function arraySortByColumn(array &$array, string $column, int $direction = SORT_ASC)
{
$reference_array = array();
foreach ($array as $key => $row) {
$reference_array[$key] = $row[$column];
}
array_multisort($reference_array, $direction, $array);
}
/**
* delete collecte exception
*
* @param Request $request
* @return string
*/
public function destroyExceptions(Request $request)
{
$collecteException = CollecteException::findOrFail($request->id);
$collecteException->delete();
return "Suppression terminée !";
}
}

View file

@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Order\ValidationStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Orders\ChangeOrderValidationStatusRequest;
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
/**
* If true the index action will only display today orders
* @var false
*/
private $indexModeTodayOnly = false;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $orders */
$orders = Order::query();
if ($request->search) {
$this->searchQuery(
$orders,
$request->search,
null,
'label'
);
}
$this->sortQuery($orders);
$searchFields = trans('Réference');
if ($this->indexModeTodayOnly) {
$orders->whereHas('timeslot', function (Builder $q) {
$q->whereDate('slot', \now()->today());
});
}
$orders->orderBy('updated_at', 'DESC');
$orders = $orders->paginate(10);
return view('back::pages.orders.index', compact('orders', 'searchFields'));
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function indexToday(Request $request)
{
$this->indexModeTodayOnly = true;
return $this->callAction('index', ['request' => $request]);
}
/**
* Display the specified resource.
*
* @param \App\Models\Order $order
* @return \Illuminate\Http\Response
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function show(Order $order)
{
// phpcs:enable
return view('back::pages.orders.show', compact('order'));
}
/**
* Mettre a jour le status de validation d'une commande
*
* @param \App\Http\Requests\Bo\Orders\ChangeOrderValidationStatusRequest $request
* @param \App\Models\Order $order
* @return \Illuminate\Http\RedirectResponse
*/
public function changeValidationStatus(ChangeOrderValidationStatusRequest $request, Order $order)
{
return DB::transaction(function () use ($request, $order) {
$order->validation_status = $request->validation_status;
switch ($request->validation_status) {
case ValidationStatus::validated()->value:
$order->saveOrFail();
return \back()->with('success', \trans(
'La commande viens d\'être validée, le client recevra une notification.'
));
case ValidationStatus::cancelled()->value:
$order->saveOrFail();
return \back()->with('warning', \trans(
'La commande à été annulée, le client recevra une notification.'
));
case ValidationStatus::pending()->value:
return \back()->with('errors', \trans('Il faut choisir un statut pour cette commande.'));
default:
throw new \RuntimeException("Unhandled status `{$request->validation_status}`");
}
});
}
}

View file

@ -1,610 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\OrderLine;
use App\Models\OrdersProducts;
use App\Models\Product;
use App\Models\Timeslot;
use App\Models\Magasin;
use App\Http\Requests\SortOrders;
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderStatusMail;
use App\Mail\OrderConfirmMail;
use App\Mail\OrderConfirmAdminMail;
use App\Jobs\CsvOrderWrite;
use App\Models\Customers;
use App\Models\Setting;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterval;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class OrdersController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \App\Http\Requests\SortOrders $request
* @return \Illuminate\Contracts\View\View
*/
public function index(SortOrders $request)
{
$orders = $this->filterOrders($request);
$orders = $orders->paginate(10);
return view('bo.orders.index', compact('orders'));
}
/**
* orders that ship today
*
* @param SortOrders $request
* @return \Illuminate\Contracts\View\View
*/
public function today(SortOrders $request)
{
$orders = $this->filterOrders($request);
// Filter today.
$today = CarbonImmutable::now()->startOfDay();
$orders = $orders->whereHas('timeslot', function ($q) use ($today) {
$q->where('day', '=', $today->format('d/m/Y'));
});
$orders = $orders->get();
return view('bo.orders.index', compact('orders', 'today'));
}
/**
* Filter orders
*
* @param Request $request
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function filterOrders(Request $request)
{
$orders = Order::query();
if ($request->rst) {
Session::remove('orders.sort_col');
Session::remove('orders.sort_way');
}
if ($request->sort_col) {
Session::put('orders.sort_col', $request->sort_col);
Session::save();
}
if ($request->sort_way) {
Session::put('orders.sort_way', $request->sort_way);
Session::save();
}
if (Session::has('orders.sort_col') and Session::has('orders.sort_way')) {
switch (Session::get('orders.sort_col')) {
case 'date':
$orders = $orders->orderBy('date', Session::get('orders.sort_way'));
break;
case 'status_id':
$orders = $orders->orderBy('status_id', Session::get('orders.sort_way'));
break;
case 'magasin_id':
$orders = $orders->select('orders.*');
$orders = $orders->leftJoin('timeslots', 'timeslots.order_id', '=', 'orders.id');
$orders = $orders->leftJoin('magasins', 'timeslots.magasin_id', '=', 'magasins.id');
$orders = $orders->orderBy('magasins.id', Session::get('orders.sort_way'));
break;
case 'date_retrait':
$orders = $orders->select('orders.*');
$orders = $orders->leftJoin('timeslots', 'timeslots.order_id', '=', 'orders.id');
$orders = $orders->orderBy('timeslots.day', Session::get('orders.sort_way'));
$orders = $orders->orderBy('timeslots.time', Session::get('orders.sort_way'));
break;
case 'customer_id':
$orders = $orders->orderBy('customer_id', Session::get('orders.sort_way'));
break;
}//end switch
}//end if
return $orders;
}
/**
* action de changement de statut vers l'avant
* envoi de mails transctionnels
*
* @param string $reference Reference de la commande.
* @return \Illuminate\Http\RedirectResponse
*/
public function changeStatus(string $reference)
{
$order = Order::where('reference', $reference)->first();
$order->status_id = $order->status_id + 1;
$order->save();
// Send confirm mail.
Mail::to($order->customer->email)->send(new OrderStatusMail($order));
return back()->with('success', "Changement de status enregistré pour la commande " . $order->reference . ".");
}
/**
* mise au statut annulé d'une commande
*
* @param string $reference
* @return \Illuminate\Http\RedirectResponse
*/
public function cancelOrder(string $reference)
{
$order = Order::where('reference', $reference)->first();
$order->status_id = 7;
$order->save();
return back()->with('success', "La commande " . $order->reference . " à été annulée.");
}
/**
* @deprecated Supprimer cette méthode qui repose sur un modèle de donnée osolète.
* ajout de produits à la commande
*
* @param string $orderReference
* @param integer $productId
* @param integer $quantity
* @return \Illuminate\Http\RedirectResponse
*/
public function addProduct(string $orderReference, int $productId, int $quantity)
{
$product = Product::findOrFail($productId);
$order = Order::where('reference', $orderReference)->first();
$order->total = $order->total + (($product->price - $product->price_buy) * $quantity);
$orderProduct = new OrdersProducts();
$orderProduct->product_id = $product->id;
$orderProduct->order_id = $order->id;
$orderProduct->quantity = $quantity;
$orderProduct->save();
return redirect()->route('bo.orders')->with('success', "Le produit à été ajouté.");
}
/**
* front
* récap panier du tunnel de commande
*
* @return \Illuminate\Contracts\View\View
*/
public function recapOrder()
{
$activestep = 2;
Cart::instance('panier');
$cart = Cart::content();
$cartInfos = array(
"totalTTC" => Cart::total(),
// Total des taxes.
'totalTVA' => Cart::tax(),
// Total sans les taxes.
'totalHT' => Cart::subtotal(),
// Poids total des items.
'totalweight' => Cart::weight(),
// Total avant taxes et remises.
'totalHTbefore' => Cart::priceTotal(),
// Total des remises.
'totaldiscount' => Cart::discount(),
// Nb de produits dans le cart.
'countItems' => Cart::count(),
);
return view('sales.recap', compact('activestep', 'cart', 'cartInfos'));
}
/**
* front
* Storing order from sales tunnel
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store()
{
// Save.
Cart::instance('panier');
$order = new Order();
// Generate order ref.
$order->reference = $this->generateOrderRef();
$order->date = Carbon::now();
$order->status_id = 1;
$order->total = str_replace(',', '.', str_replace(' ', '', Cart::total()));
$order->customer_id = Auth::user()->id;
$order->save();
// Order_id.
Cart::instance('panier');
$cart = Cart::content();
foreach ($cart as $cartItem) {
$order_line = new OrderLine();
$order_line->order_id = $order->id;
$order_line->product_id = $cartItem->model->id;
$order_line->quantity = $cartItem->qty;
// Gross unit price.
$order_line->unit_price = $cartItem->model->priceHt;
// Discount per unit.
$order_line->unit_discount = $cartItem->model->discountAmount;
// Tax rate for this line.
$order_line->tax_rate = $cartItem->taxRate;
// Tax amount par unit.
$order_line->unit_tax = round(
(($order_line->unit_price - $order_line->unit_discount) * ($order_line->tax_rate / 100)) * 100
) / 100;
// Discount amount for the line.
$order_line->total_discount = $order_line->unit_discount * $order_line->quantity;
// Gross amount for the line.
$order_line->total_ht = ($order_line->unit_price - $order_line->unit_discount) * $order_line->quantity;
// Tax amount for the line.
$order_line->total_tax = $order_line->unit_tax * $order_line->quantity;
// Total TTC avec remises de la ligne.
$order_line->total_ttc = $order_line->total_ht + $order_line->total_tax;
$order_line->save();
}//end foreach
// Empty cart.
Cart::destroy();
// Then redirect.
return redirect()->route('sales.pick-slot', ['reference' => $order->reference]);
}
/**
* generate order Reference
* for a new order
*
* @return string
*/
public function generateOrderRef()
{
// Format : YYYYMMDDHHMM000.
$now = Carbon::now();
// Count todays orders.
$countorders = Order::whereDate('created_at', Carbon::today())->count();
// Add one.
$countorders++;
$ref = $now->isoFormat('YYYYMMDDHHmm') . str_pad($countorders, 3, "0", STR_PAD_LEFT);
return $ref;
}
/**
* front tunnel de commande étape 3 : choix de créneau
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function timeSlotPick(string $reference)
{
$activestep = 3;
if (config('theme.multi_relay')) {
$magasin = Magasin::all();
} else {
$magasin = Magasin::first();
}
$availCreneaux = array();
$order = Order::where('reference', '=', $reference)->firstOrFail();
return view('sales.pick-slot', compact('activestep', 'availCreneaux', 'order', 'magasin'));
}
/**
* add timeslot by interval between start and end hour
*
* @param array $plage
* @param integer $weekDay
* @param CarbonImmutable $current
* @param Magasin $magasin
* @param CarbonImmutable $now
* @param CarbonImmutable $today
* @param CarbonInterval $delaiPrepa
* @param CarbonInterval $interval
* @param Carbon $currentTime
* @param Carbon $endTime
* @return array
*/
private function addCreneaux(
array $plage,
int $weekDay,
CarbonImmutable $current,
Magasin $magasin,
CarbonImmutable $now,
CarbonImmutable $today,
CarbonInterval $delaiPrepa,
CarbonInterval $interval,
Carbon $currentTime,
Carbon $endTime
) {
$retArray = array();
do {
$creneau = $currentTime->format('H:i');
$nbtakens = $magasin->timeslots()
->where('day', $current->format('d/m/Y'))
->where('time', $creneau)->count();
if ($nbtakens >= $plage[$weekDay]['max_orders_per_interval']) {
$isDispo = false;
} else {
$isDispo = true;
}
if (
$current->addHours($currentTime->format('H'))->addMinutes($currentTime->format('i')) <
$now->add($delaiPrepa)
) {
$isDispo = false;
}
// Attention si la date est passée (today mais avant maintenant) : pas de créneau.
if ($current > $today || $currentTime >= $now) {
$retArray[] = array($creneau, $isDispo);
}
$currentTime->add($interval);
} while ($currentTime <= $endTime);
return $retArray;
}
/**
* give array of available crenaux
*
* @param integer $id
* @return array
*/
public function getAvailableCreneauxByShop(int $id)
{
// Setting à stocker et réutiliser.
$plage_days = Setting::where('key', 'order_withdrawal_period')->firstOrFail()->value;
$magasin = Magasin::findOrFail($id);
$timeslotSettings = $magasin->timeslotSettings;
// $timeslotSettings = TimeslotSetting::all();
// Fabriquons un tableau recap.
$plage = array();
foreach ($timeslotSettings as $day) {
$plage[$day->day_index] = array(
'morning_start' => $day->morning_start,
'morning_end' => $day->morning_end,
'afternoon_start' => $day->afternoon_start,
'afternoon_end' => $day->afternoon_end,
'interval' => $day->interval,
'min_order_preparation' => $day->min_order_preparation,
'max_orders_per_interval' => $day->max_orders_per_interval
);
}
// Génération des plages.
// Start today.
$today = CarbonImmutable::now()->startOfDay();
$weekDay = $today->weekday();
// Final boundary (permet de limiter la liste des jours d'exception).
$finalday = $today->add($plage_days, 'day');
$now = CarbonImmutable::now();
// Jours exceptions.
$noCollectDays = $magasin->collecteExceptions()
->whereDate('date', '>', $now)
->whereDate('date', '<=', $finalday)
->orderBy('date')->get();
$delaiPrepa = CarbonInterval::createFromFormat('h:i', $plage[$weekDay]['min_order_preparation'] ?? '01:00');
$availCreneaux = array();
for ($i = 0; $i < $plage_days; $i++) {
$current = $today->add($i, 'day');
$weekDay = $current->weekday();
$interval = CarbonInterval::createFromFormat('h:i', $plage[$weekDay]['interval'] ?? '00:15');
// Si c'est un jour exception skip.
$skip = false;
foreach ($noCollectDays as $not_today) {
if ($current->isSameDay($not_today->date)) {
$skip = true;
}
}
if ($skip) {
continue;
}
$availCreneaux[$current->format('d/m/Y')] = array();
// Matinée.
if (!empty($plage[$weekDay]['morning_start'])) {
$currentTime = Carbon::createFromTimeString($plage[$weekDay]['morning_start']);
$endTime = Carbon::createFromTimeString($plage[$weekDay]['morning_end']);
$morningCreneaux = $this->addCreneaux(
$plage,
$weekDay,
$current,
$magasin,
$now,
$today,
$delaiPrepa,
$interval,
$currentTime,
$endTime
);
$availCreneaux[$current->format('d/m/Y')] = array_merge(
$availCreneaux[$current->format('d/m/Y')],
$morningCreneaux
);
} //end if
// Aprem.
if (!empty($plage[$weekDay]['afternoon_start'])) {
$currentTime = Carbon::createFromTimeString($plage[$weekDay]['afternoon_start']);
$endTime = Carbon::createFromTimeString($plage[$weekDay]['afternoon_end']);
$afternoonCreneaux = $this->addCreneaux(
$plage,
$weekDay,
$current,
$magasin,
$now,
$today,
$delaiPrepa,
$interval,
$currentTime,
$endTime
);
$availCreneaux[$current->format('d/m/Y')] = array_merge(
$availCreneaux[$current->format('d/m/Y')],
$afternoonCreneaux
);
} //end if
}//end for
return $availCreneaux;
}
/**
* Call ajax pour le composant vue de timeslots.
*
* @param Request $request
* @return array
*/
public function getAvailableCreneaux(Request $request)
{
return $this->getAvailableCreneauxByShop($request->id);
}
/**
* front
* enregistrement du créneau de collect
* et validation de la commande
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function setTimeSlot(Request $request)
{
$request->validate([
'selectedmagasin' => 'required',
'selectedcreneau' => 'required',
'ok_cgv' => 'accepted',
]);
$time_slot = Carbon::createFromFormat('d/m/Y-H:i', $request->selectedcreneau);
$tsday = explode('-', $request->selectedcreneau)[0];
$tstime = explode('-', $request->selectedcreneau)[1];
$magasin_id = $request->selectedmagasin;
$reference = $request->order_ref;
$order = Order::where('reference', '=', $reference)->firstOrFail();
if ($order->status_id == 1) {
// On ne traite l'enregistrement que pour les commandes en attente venant de l'étape de récap.
// Save timeslot.
$timeslot = Timeslot::firstOrNew(['order_id' => $order->id]);
$timeslot->order_id = $order->id;
$timeslot->magasin_id = $magasin_id;
$timeslot->day = $tsday;
$timeslot->time = $tstime;
$timeslot->save();
// Save order status.
$order->status_id = 3;
$order->save();
// Send confirm mail.
Mail::to($order->customer->email)->queue(new OrderConfirmMail($order));
// Envoi du mail de confirmation aux admins site.
$toMailSetting = Setting::where('key', 'send_order_confirm_mail_to')->first();
if (empty($toMailSetting->value)) {
$toMail = config('mail.from.address');
} else {
$toMail = $toMailSetting->value;
}
Mail::to($toMail)->queue(new OrderConfirmAdminMail($order));
// Export commande.
if (config('bosettings.regate_orders')) {
CsvOrderWrite::dispatch($order);
}
}//end if
// Redirect to final confirmation.
return redirect()->route('sales.confirm', ['reference' => $order->reference]);
}
/**
* generate Order
* Test method
*
* @param string $reference
* @return void
*/
public function generateOrder(string $reference)
{
$order = Order::where('reference', '=', $reference)->with('lines')->firstOrFail();
$job = new CsvOrderWrite($order);
$job->handle();
dd('end');
}
/**
* front
* page de confirmation de commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function confirmOrder(string $reference)
{
$activestep = 4;
$idcommande = Session::get('idcommande');
$order = Order::where('reference', '=', $reference)->firstOrFail();
// 1 seul magasin en base pour le moment
$magasin = Magasin::find($order->timeslot->magasin->id);
return view('sales.confirm', compact('activestep', 'order', 'magasin'));
}
/**
* bo
* ajx : affichage d'infos sur un client
*
* @param integer $id
* @param string|null $mode
* @return \Illuminate\Contracts\View\View
*/
public function getCustomerInfo(int $id, string $mode = null)
{
$cust = Customers::findOrFail($id);
if (!is_null($mode)) {
if ($mode == "tippy") {
return view('bo.customer.tippy-card', compact('cust'));
}
if ($mode == "json") {
return $cust->apiData;
}
return view('bo.customer.info-card', compact('cust'));
}
return view('bo.customer.info-popup', compact('cust'));
}
/**
* bo : page détail commande
*
* @param string $reference
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(string $reference)
{
$order = Order::where('reference', '=', $reference)->firstOrFail();
return view('bo.orders.detail', compact('order'));
}
}

View file

@ -0,0 +1,377 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\Product\DefaultProductType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Products\CreateProductRequest;
use App\Http\Requests\Bo\Products\StoreProductRequest;
use App\Http\Requests\Bo\Products\UpdateProductRequest;
use App\Models\Product;
use App\Models\Range;
use App\Models\Tag;
use App\Models\XmlInco;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Kwaadpepper\ImageResizer\ImageResizer;
class ProductController extends Controller
{
/**
* If true the index action will only display starred products
* @var false
*/
private $indexModeStarredOnly = false;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $products */
$products = Product::query();
if ($request->search) {
$this->searchQuery(
$products,
$request->search,
null,
'name',
'description',
'reference'
);
} //end if
if ($request->range) {
$products->whereHas('range', function (Builder $q) use ($request) {
$q->where('id', $request->range);
});
}
if ($request->type) {
$products->where('type', $request->type);
}
if ($this->indexModeStarredOnly) {
$products->where('star', true);
}
if ($request->orderable) {
$products->where('orderable', true);
}
if ($request->star) {
$products->where('star', true);
}
$this->sortQuery($products);
$searchFields = trans('Nom, Description, Réf');
$products->orderBy('updated_at', 'DESC');
$products = $products->paginate();
$ranges = Range::all();
$productTypes = Product::fetchTypes();
return view('back::pages.products.index', compact(
'products',
'ranges',
'searchFields',
'productTypes'
));
}
/**
* Display a listing of the resource.
*
* * Index des produits mis en avant
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function starred(Request $request)
{
$this->indexModeStarredOnly = true;
return $this->callAction('index', [$request]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Http\Requests\Bo\Products\CreateProductRequest $request
* @return \Illuminate\Http\Response
*/
public function create(CreateProductRequest $request)
{
/** @var array product type Minicrud config */
$minicrudData = Product::fetchTypesInputs()[$request->get('type')];
$ranges = Range::all();
$tags = Tag::all();
$existingProds = Product::all();
$productTypes = Product::fetchTypes();
return view('back::pages.products.create', \compact(
'minicrudData',
'productTypes',
'existingProds',
'ranges',
'tags'
));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Products\StoreProductRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreProductRequest $request)
{
return DB::transaction(function () use ($request) {
$product = new Product();
// * Minicrud fill
/** @var array product type Minicrud config */
$minicrudData = Product::fetchTypesInputs()[$request->type];
$product->fill(array_replace_recursive(
\json_decode(\json_encode((object)[
'data' => $minicrudData
]), true),
$request->validated()
));
if ($product->saveOrFail()) {
$product->associatedProducts()->sync($request->prods);
$product->saveOrFail();
return redirect()
->route('bo.products.edit', $product)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.products'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function edit(Product $product)
{
$ranges = Range::all();
$tags = Tag::all();
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Product> */
$selectedProds = $product->associatedProducts;
/** @var \Illuminate\Database\Eloquent\Collection<mixed,\App\Models\Product> */
$existingProds = Product::query()->whereNotIn('id', $selectedProds->pluck('id'))->get();
$productTypes = Product::fetchTypes();
return view('back::pages.products.edit', [
'productModel' => $product,
'ranges' => $ranges,
'tags' => $tags,
'productTypes' => $productTypes,
'existingProds' => $existingProds,
'selectedProds' => $selectedProds
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Products\UpdateProductRequest $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function update(UpdateProductRequest $request, Product $product)
{
return DB::transaction(function () use ($request, $product) {
// * Minicrud fill
$product->fill(array_replace_recursive(
\json_decode(\json_encode($product->toArray()), true),
$request->validated()
));
$product->associatedProducts()->sync($request->prods);
if ($product->saveOrFail()) {
return redirect()
->route('bo.products.edit', $product)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.products'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
if ($product->deleteOrFail()) {
return redirect()
->route('bo.products.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.products'))]
));
}
return back();
}
/**
* Associate XmlInco with product
*
* @param \App\Models\Product $product
* @param \App\Models\XmlInco $xml_inco
* @return \Illuminate\Http\RedirectResponse
*/
public function associateXmlInco(Product $product, XmlInco $xml_inco)
{
// * Allow if auth, request post and product type is inco
Gate::allowIf(function ($user) use ($product) {
return $user and request()->isMethod('post') and
$product->type->value === DefaultProductType::inco()->value;
});
$product->xmlInco()->associate($xml_inco);
$product->saveOrFail();
return redirect()->route('bo.products.edit', $product)
->with('success', trans(
':model est associé avec fiche inco \':incoId\' \':descriptif\'',
[
'model' => Str::singular(trans('models.classes.slides')),
'incoId' => $xml_inco->id,
'descriptif' => Str::limit($xml_inco->descriptif)
]
));
}
/**
* Resize an inco picture and save to to the concerned product
*
* @param \App\Models\Product $product
* @param \App\Models\XmlInco $xml_inco
* @param integer $picture_index
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException If anthing goes wrong managing disks files.
* @throws \Kwaadpepper\ImageResizer\Exceptions\ImageResizerException If resizing the picture fails.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function copyXmlIncoPicture(Product $product, XmlInco $xml_inco, int $picture_index): RedirectResponse
{
// * Check product is associated.
if (!$product->xmlInco) {
redirect()->route('bo.products.edit', $product)
->withErrors(['xmlInco' => 'Vous devez d\'abord associer une fiche Inco']);
}
// * Check product inco is the same as requested.
if ($product->xmlInco->id !== $xml_inco->id) {
redirect()->route('bo.products.edit', $product)
->withErrors(['xmlInco' => 'La fiche inco associé ne correspond pas à l\'image']);
}
// * Allow if auth, request post and product type is inco and product has inco media on wanted index.
Gate::allowIf(function ($user) use ($product, $xml_inco, $picture_index) {
return $user and request()->isMethod('post') and
$product->hasAndIsInco() and $xml_inco->mediaList->has($picture_index);
});
/** @var array<string,string> Indexes are filename and imgdata */
$picture = $xml_inco->mediaList->get($picture_index);
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storagePublicDisk = Storage::disk('public');
$pictureBaseName = $picture['filename'];
$pictureExtension = \pathinfo($picture['filename'], \PATHINFO_EXTENSION);
/**
* We create one file in tmp to be resized
* then use resize image to get cached file that we will copy.
*/
// * Make sys tmp file.
if (!($file = tmpfile())) {
throw new \RuntimeException('Failed to create tmp file handle');
}
// * Wrap in try catch to always close tmp file
try {
// * Write picutre on tmp file.
$path = stream_get_meta_data($file)['uri'];
if (!File::put($path, $picture['imgdata'])) {
throw new \RuntimeException('Failed to write content on tmp file');
}
// * Assert theme config is present.
if (
!config()->has('theme.models.product.picture_maxheight') or
!config()->has('theme.models.product.picture_maxwidth')
) {
throw new \RuntimeException('config theme.models.product needs picture_maxheight and picture_maxwidth');
}
// * Dynamic push config.
\config()->set('image-resizer.templates.incoPictureResizeForProduct', [
'height' => config()->get('theme.models.product.picture_maxheight', 235),
'width' => config()->get('theme.models.product.picture_maxwidth', 235),
'format' => $pictureExtension,
'inCanvas' => true,
'resize' => true,
'keepRatio' => true
]);
// * Resize picture using kwaadpepper/image-resizer.
$resizedPath = ImageResizer::resizeImageOrIgnore(
$path,
'incoPictureResizeForProduct'
);
if (!$storagePublicDisk->has($resizedPath)) {
throw new \RuntimeException("Resized picture '$resizedPath' does not exists on 'public' disk");
}
// * Append unique prefix to File original basename
$pictureBaseNewName = Str::uuid() . '__' . $pictureBaseName;
// * Rename picture.
$newPathName = \pathinfo($resizedPath, \PATHINFO_DIRNAME) . "/{$pictureBaseNewName}";
if (!$storagePublicDisk->move($resizedPath, $newPathName)) {
throw new \RuntimeException("Failed to rename '$resizedPath' into '$newPathName'");
}
$product->picture = new \SplFileInfo($storagePublicDisk->path($newPathName));
$product->saveOrFail();
// * remove tmp cached file.
$storagePublicDisk->delete($newPathName);
} finally {
fclose($file);
} //end try
return redirect()->route('bo.products.edit', $product);
}
}

View file

@ -1,838 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\ProductRequest;
use App\Models\OldSlug;
use App\Models\FoodProduct;
use App\Models\ToolProduct;
use App\Models\AnifoodProduct;
use App\Models\GarmentProduct;
use App\Models\Product;
use App\Models\Range;
use App\Models\Tag;
use App\Models\TaxRate;
use App\Models\XmlInco;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
class ProductsController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$ranges = Range::all();
$filter_range = $request->query('filter_range');
$filter_name = $request->query('filter_name');
$products = new Product();
if (!empty($filter_name)) {
$products = $products->where('name', 'like', '%' . $filter_name . '%');
}
if (!empty($filter_range)) {
$products = $products->where('range_id', $filter_range);
}
$products = $products->paginate(10);
// Ici récupération des infos additionnelles pour affichage dans la liste.
foreach ($products as &$product) {
if (!empty($product->product_type)) {
$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
if ($product->additionnalProductInfo === null) {
// N'existe pas on renseigne un vide.
$product_type = $product->product_type;
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product->$product_type = new $modelClass();
}
}
}
unset($product);
return view('bo.products.index', compact('products', 'ranges', 'filter_name', 'filter_range'));
}
/**
* Show the form for creating a new resource.
* @param string $product_type
* @return \Illuminate\Http\Response
*/
public function create(string $product_type = null)
{
if (!empty($product_type) && !array_key_exists($product_type, config('theme.enum_product_types'))) {
abort(404, "Cette page n'existe pas.");
}
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product = new Product();
if (!empty($product_type)) {
$product->$product_type = new $modelClass();
}
$ranges = Range::all();
$taxes = TaxRate::all();
return view('bo.products.create', compact('product_type', 'product', 'ranges', 'taxes'));
}
/**
* store a new product
*
* @param ProductRequest $request
* @return \Illuminate\Http\Response
*/
public function store(ProductRequest $request)
{
// Products fields.
$name = $request->name;
$description = $request->description;
$picture = $request->picture;
$picture_large = $request->picture_large;
$price = $request->price;
$invoicing_unit = $request->invoicing_unit;
$priceBuy = $request->price_buy;
$order_unit = $request->order_unit;
$stock = $request->stock;
$reference = $request->reference;
$sell_unit = $request->sell_unit;
$weight = $request->weight;
$weight_unit = $request->weight_unit;
$status = $request->status === null ? 0 : 1;
$orderable_start = $request->orderable_start_date === null ? null
: $request->orderable_start_date . " " . $request->orderable_start_time;
$orderable_end = $request->orderable_end_date === null ? null
: $request->orderable_end_date . " " . $request->orderable_end_time;
$star = $request->star === null ? 0 : 1;
$slug = Str::slug($name);
$rangeId = $request->range_id;
$taxRateId = $request->tax_rate_id;
// Check si le slug existe déjà dans cette gamme de produits.
$slugExist = Product::where([
['range_id', $rangeId],
['slug', $slug]
])->first();
if ($slugExist) {
return redirect()->back()
->withInput($request->input())
->withErrors("Erreur : un produit avec le slug d'accès \"" . $slug .
"\" éxiste déjà. Veuillez changer le nom du produit.");
}
$product = new Product();
$product->name = $name;
$product->description = $description;
$product->price = $price;
$product->invoicing_unit = $invoicing_unit;
$product->price_buy = $priceBuy;
$product->order_unit = $order_unit;
$product->stock = $stock;
$product->reference = $reference;
$product->sell_unit = $sell_unit;
$product->weight = $weight;
$product->weight_unit = $weight_unit;
$product->slug = $slug;
$product->status = $status;
$product->orderable_start = $orderable_start;
$product->orderable_end = $orderable_end;
$product->star = $star;
$product->range_id = $rangeId;
$product->tax_rate_id = $taxRateId;
$product->save();
// Check si le slug existe dans la table old_slugs, auquel cas il est attribué a ce nouveau produit.
$oldSlug = OldSlug::where('slug', $slug)->first();
if ($oldSlug && $oldSlug->product_id !== $product->id) {
$oldSlug->product_id = $product->id;
$oldSlug->save();
}
$str_random = Str::random(5);
if ($picture !== null) {
$imageName = $product->id . '_' . $str_random . '.' . $picture->extension();
$picture->move(public_path('images/produits'), $imageName);
$product->picture = $imageName;
$product->save();
}
if ($picture_large !== null) {
$imageName = $product->id . '_' . $str_random . '_large.' . $picture_large->extension();
$picture_large->move(public_path('images/produits'), $imageName);
$product->picture_large = $imageName;
$product->save();
}
// Ajout des infos additionnelles.
$product_type = $request->product_type;
if ($product_type !== null) {
$product->product_type = $product_type;
$product->save();
$methodName = "save" . ucfirst($product_type);
$this->$methodName($request, $product->id);
}
// Additionnal product info (foodProduct).
return redirect()->route('bo.products.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$product = Product::findOrFail($id);
if (!empty($product->product_type)) {
$product->additionnalProductInfo = $product->additionnalProductInfo($product->product_type);
/**
* Above lines equivalent to
* $product = Product::with('additionnalProductInfo')->findOrFail($id);
*/
if ($product->additionnalProductInfo === null) {
// N'existe pas on renseigne un vide.
$product_type = $product->product_type;
$modelClass = 'App\Models\\' . ucfirst($product_type);
$product->$product_type = new $modelClass();
}
}
$ranges = Range::all();
$taxes = TaxRate::all();
$existingTags = Tag::all();
$selectedtags = $product->tags;
$existingProds = Product::all();
$selectedProds = $product->products;
$existingProds = $existingProds->diff($selectedProds);
return view('bo.products.edit', compact(
'product',
'ranges',
'taxes',
'existingTags',
'selectedtags',
'existingProds',
'selectedProds'
));
}
/**
* Update the specified resource.
*
* @param \App\Models\Product $product
* @param ProductRequest $request
* @return \Illuminate\Http\Response
*/
public function update(Product $product, ProductRequest $request)
{
$orderable_end_time = $request->orderable_end_time === null ? "23:59" : $request->orderable_end_time;
$name = $request->name;
$description = $request->description;
$price = $request->price;
$invoicing_unit = $request->invoicing_unit;
$priceBuy = $request->price_buy;
$order_unit = $request->order_unit;
$stock = $request->stock;
$reference = $request->reference;
$sell_unit = $request->sell_unit;
$weight = $request->weight;
$weight_unit = $request->weight_unit;
$status = $request->status === null ? 0 : 1;
$orderable_start = $request->orderable_start_date === null ? null
: $request->orderable_start_date . " " . $request->orderable_start_time;
$orderable_end = $request->orderable_end_date === null ? null
: $request->orderable_end_date . " " . $orderable_end_time;
$star = $request->star === null ? 0 : 1;
$slug = Str::slug($name);
$rangeId = $request->range_id;
$taxRateId = $request->tax_rate_id;
$this->checkSlugExistsWithinRange($request, $rangeId, $slug, $product->id);
$this->manageProductImages($request->picture, $product, $request->picture_large);
$product->name = $name;
$this->saveNewSlugForProduct($product, $slug);
$product->description = $description;
$product->price = $price;
$product->invoicing_unit = $invoicing_unit;
$product->price_buy = $priceBuy;
$product->order_unit = $order_unit;
$product->stock = $stock;
$product->reference = $reference;
$product->sell_unit = $sell_unit;
$product->weight = $weight;
$product->weight_unit = $weight_unit;
$product->slug = $slug;
$product->status = $status;
$product->orderable_start = $orderable_start;
$product->orderable_end = $orderable_end;
$product->star = $star;
$product->range_id = $rangeId;
$product->tax_rate_id = $taxRateId;
$product->save();
// Check si le slug existe dans la table old_slug, auquel cas il est attribué a ce nouveau produit.
$this->cleanOldSlug($slug);
// Ajout des infos additionnelles.
$product_type = $request->product_type;
// Save additionnal.
if ($product_type !== null) {
$methodName = "save" . ucfirst($product_type);
$this->$methodName($request, $product->id);
}
// Tags associés.
$this->syncTags($request->tags, $product);
// Produits associés.
$this->syncProduitsAssocies($request->prods, $product);
return redirect()->route('bo.products.edit', $product->id)->with('success', 'Modifications enregistrés !');
}
/**
* Check if slug already exists, if so redirect
*
* @param Request $request
* @param integer $rangeId
* @param string $slug
* @param integer|null $productId
* @return \Illuminate\Http\RedirectResponse|void
*/
private function checkSlugExistsWithinRange(Request $request, int $rangeId, string $slug, int $productId = null)
{
// Check si le slug existe déjà dans cette gamme de produits.
$slugExist = Product::where([
['range_id', $rangeId],
['slug', $slug],
['id', '<>', $productId]
])->first();
if ($slugExist) {
return redirect()->back()
->withInput($request->input())
->withErrors("Erreur : un produit avec le slug d'accès \"" . $slug .
"\" éxiste déjà. Veuillez changer le nom du produit.");
}
}
/**
* If new slug for product then save oldslug
*
* @param Product $product
* @param string $slug
* @return void
*/
private function saveNewSlugForProduct(Product $product, string $slug)
{
if ($product->slug != $slug) {
$exist = OldSlug::where('slug', $product->slug)->first();
if ($exist === null) {
$oldSlug = new OldSlug();
$oldSlug->product_id = $product->id;
$oldSlug->slug = $product->slug;
$oldSlug->save();
}
}
}
/**
* clean oldslug if exists
*
* @param string $slug
* @return void
*/
private function cleanOldSlug(string $slug)
{
// Check si le slug existe dans la table old_slug, auquel cas il est attribué a ce nouveau produit.
$oldSlug = OldSlug::where('slug', $slug)->first();
if ($oldSlug) {
$oldSlug->delete();
}
}
/**
* Store and update saved images on product
*
* @param UploadedFile|null $picture
* @param Product $product
* @param UploadedFile|null $picture_large
* @return void
*/
private function manageProductImages($picture, Product &$product, $picture_large)
{
$str_random = Str::random(5);
if ($picture !== null) {
if (
$product->picture !== null &&
file_exists(public_path('images/produits/') . $product->picture)
) {
unlink(public_path('images/produits/') . $product->picture);
}
$imageName = $product->id . '_' . $str_random . '.' . $picture->extension();
$picture->move(public_path('images/produits'), $imageName);
$product->picture = $imageName;
}
if ($picture_large !== null) {
if (
$product->picture_large !== null &&
file_exists(public_path('images/produits/') . $product->picture_large)
) {
unlink(public_path('images/produits/') . $product->picture_large);
}
$imageName = $product->id . '_' . $str_random . '_large.' . $picture_large->extension();
$picture_large->move(public_path('images/produits'), $imageName);
$product->picture_large = $imageName;
}
}
/**
* sync tags to product
*
* @param array|null $tags
* @param Product $product
* @return void
*/
private function syncTags(array $tags = null, Product &$product)
{
if (!empty($tags)) {
$arTagIds = array();
foreach ($tags as $tag) {
$oTag = json_decode($tag);
if (empty($oTag->id)) {
// We need a new tag.
$arTagIds[] = $this->storeTag($oTag);
} else {
$arTagIds[] = $oTag->id;
}
}
// Prise en charge de la liaison avec les produits.
$product->tags()->sync($arTagIds);
} else {
$product->tags()->sync(array());
}
}
/**
* sync associated products to product
*
* @param string|null $prods
* @param Product $product
* @return void
*/
private function syncProduitsAssocies(string $prods = null, Product $product)
{
if (!empty($prods)) {
$productIds = explode(",", $prods);
// Prise en charge de la liaison avec les produits.
$product->products()->sync($productIds);
} else {
$product->products()->sync(array());
}
}
/**
* Helper method to store a new tag if necessary
* Otherwise retreive an existing tag to associate its id
*
* @param \App\Models\Tag $oTag
* @return integer
*/
public function storeTag(Tag $oTag)
{
$slug = Str::slug($oTag->label);
$tag = Tag::where('slug', $slug)->first();
if ($tag === null) {
// Si on pas encore ce tag on le crée.
$tag = new Tag();
$tag->label = $oTag->label;
$tag->slug = Str::slug($oTag->label);
// A new tag is published by default.
$tag->status = 1;
$tag->order = TagsController::getLastOrder();
$tag->save();
}
return $tag->id;
}
/**
* destroy a product (soft delete)
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$product = Product::findOrFail($id);
if ($product->picture !== null && file_exists(public_path('images/produits/') . $product->picture)) {
unlink(public_path('images/produits/') . $product->picture);
}
if (
$product->picture_large !== null &&
file_exists(public_path('images/produits/') . $product->picture_large)
) {
unlink(public_path('images/produits/') . $product->picture_large);
}
OldSlug::where('product_id', $product->id)->delete();
$product->delete();
return redirect()->route('bo.products.index');
}
/**
* check if inco data exists
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function checkIncoData(Request $request)
{
$id = $request->id;
$xmlInco = XmlInco::find($id);
if (!empty($xmlInco)) {
// We have data.
$hasInco = __("Des données Inco sont disponibles, souhaitez vous les associer à ce produit ?");
$preparedImages = $this->prepareIncoImages($xmlInco->picture);
return view('bo.products.load-inco', compact('hasInco', 'xmlInco', 'preparedImages'));
} else {
return array("data" => array());
}
}
/**
* Show prepared images
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*/
public function getPrepareIncoImages(Request $request)
{
$picture = $request->picture;
$preparedImages = $this->prepareIncoImages($picture);
return view('bo.products.inco-images', compact('preparedImages'));
}
/**
* prepared images
*
* @param array $picture
* @return \Illuminate\Contracts\View\View
*/
public function prepareIncoImages(array $picture)
{
$preparedImages = array();
foreach ($picture as $incoImage) {
// Charge.
if (File::exists(storage_path($incoImage))) {
$imagePath = storage_path($incoImage);
// Redimensionne.
// Create mask.
// Resize original image into mask.
$this->resizeImg(
$imagePath,
config('theme.product_image_large_maxwidth'),
config('theme.product_image_large_maxheight'),
File::name($incoImage) . "_large.png"
);
$this->resizeImg(
$imagePath,
config('theme.product_image_maxwidth'),
config('theme.product_image_maxheight'),
File::name($incoImage) . "_small.png"
);
$preparedImages[] = "inco/images/" . File::name($incoImage) . "_large.png";
}//end if
}//end foreach
return $preparedImages;
}
/**
* redimensionne une image dans un masque
*/
/**
* redimensionne une image dans un masque
*
* @param string $imagePath
* @param integer $maskWidth
* @param integer $maskHeight
* @param string $resizedName
* @return boolean|void
*/
private function resizeImg(string $imagePath, int $maskWidth, int $maskHeight, string $resizedName)
{
// ---- création du masque
$mask = imagecreatetruecolor($maskWidth, $maskHeight);
imagesavealpha($mask, true);
$color = imagecolorallocatealpha($mask, 0, 0, 0, 127);
imagefill($mask, 0, 0, $color);
// ---- Récupère le mime type de l'image
$info = getimagesize($imagePath);
$mime = $info['mime'];
// ---- Défini les fonctions à appeler selon le type de fichier
switch ($mime) {
case 'image/jpeg':
$image_create_func = 'imagecreatefromjpeg';
break;
case 'image/png':
$image_create_func = 'imagecreatefrompng';
break;
default:
return false;
}
// ---- Chargement de l'image source
$img = $image_create_func($imagePath);
list($imgWidth, $imgHeight) = getimagesize($imagePath);
// ---- Calcul les nouvelles dimensions de l'image
if ($imgWidth > $imgHeight) {
$finalWidth = $maskWidth;
$finalHeight = ($finalWidth * $imgHeight) / $imgWidth;
} else {
$finalHeight = $maskHeight;
$finalWidth = ($finalHeight * $imgWidth) / $imgHeight;
}
// ---- Redimensionne l'image source
$resizeImg = imagecreatetruecolor($finalWidth, $finalHeight);
imagealphablending($resizeImg, false);
imagesavealpha($resizeImg, true);
imagecopyresampled($resizeImg, $img, 0, 0, 0, 0, $finalWidth, $finalHeight, $imgWidth, $imgHeight);
// Copie l'image redimensionnée dans le mask.
imagecopy(
$mask,
$resizeImg,
($maskWidth - $finalWidth) / 2,
($maskHeight - $finalHeight) / 2,
0,
0,
imagesx($resizeImg),
imagesy($resizeImg)
);
imagealphablending($mask, false);
imagesavealpha($mask, true);
// Ecrit dans storage/app/public.
// Créer le fichier final.
imagepng($mask, storage_path('app/public/inco/images/' . $resizedName), 5);
// Libère la mémoire associée aux images manipulées.
imagedestroy($img);
imagedestroy($resizeImg);
imagedestroy($mask);
}
/**
* set une image préparée comme image du produit
*/
/**
* set une image préparée comme image du produit
*
* @param Request $request
* @return \App\Models\Product
*/
public function setIncoImage(Request $request)
{
$imagePath = $request->picture;
$imagePath = storage_path("app/public/" . $imagePath);
$product_id = $request->product;
$product = Product::findOrFail($product_id);
$str_random = Str::random(5);
// Default is large image.
$finalImageName = $product->id . '_' . $str_random . '_large.' . File::extension($imagePath);
$productimagespath = public_path('images/produits');
if (!File::isDirectory($productimagespath)) {
File::makeDirectory($productimagespath, 0644, true);
}
File::copy($imagePath, $productimagespath . "/" . $finalImageName);
// Suppression de l'ancien fichier.
if (
$product->picture_large !== null &&
file_exists(public_path('images/produits/') . $product->picture_large)
) {
unlink(public_path('images/produits/') . $product->picture_large);
}
$product->picture_large = $finalImageName;
// Now set small image.
$imagePath = str_replace("_large", "_small", $imagePath);
$finalImageName = $product->id . '_' . $str_random . '.' . File::extension($imagePath);
File::copy($imagePath, $productimagespath . "/" . $finalImageName);
// Suppression de l'ancien fichier.
if ($product->picture !== null && file_exists(public_path('images/produits/') . $product->picture)) {
unlink(public_path('images/produits/') . $product->picture);
}
$product->picture = $finalImageName;
$product->save();
return $product;
}
/**
* helper method to update additionnal product info
*
* @param Request $request
* @param integer $product_id
* @return void
*/
public function saveFoodProduct(Request $request, int $product_id)
{
$foodProduct = FoodProduct::firstOrNew([
'product_id' => $product_id,
]);
if (!empty($request->no_art)) {
$foodProduct->no_art = $request->no_art;
}
$foodProduct->ingredients = $request->ingredients;
$foodProduct->preparation = $request->preparation;
$foodProduct->servings = $request->servings;
$foodProduct->allergens = $request->allergens;
$foodProduct->selling_argument = $request->selling_argument;
$foodProduct->nv_ref_weight_g = $request->nv_ref_weight_g;
$foodProduct->nv_kj = $request->nv_kj;
$foodProduct->nv_kcal = $request->nv_kcal;
$foodProduct->nv_fat_g = $request->nv_fat_g;
$foodProduct->nv_sat_fat_g = $request->nv_sat_fat_g;
$foodProduct->nv_carb_g = $request->nv_carb_g;
$foodProduct->nv_sugar_g = $request->nv_sugar_g;
$foodProduct->nv_fiber_g = $request->nv_fiber_g;
$foodProduct->nv_protein_g = $request->nv_protein_g;
$foodProduct->nv_salt_g = $request->nv_salt_g;
$foodProduct->nv_serving_size_g = $request->nv_serving_size_g;
$foodProduct->nv_serving_kj = $request->nv_serving_kj;
$foodProduct->nv_serving_kcal = $request->nv_serving_kcal;
$foodProduct->nv_serving_fat_g = $request->nv_serving_fat_g;
$foodProduct->nv_serving_sat_fat_g = $request->nv_serving_sat_fat_g;
$foodProduct->nv_serving_carb_g = $request->nv_serving_carb_g;
$foodProduct->nv_serving_sugar_g = $request->nv_serving_sugar_g;
$foodProduct->nv_serving_fiber_g = $request->nv_serving_fiber_g;
$foodProduct->nv_serving_protein_g = $request->nv_serving_protein_g;
$foodProduct->nv_serving_salt_g = $request->nv_serving_salt_g;
$foodProduct->nv_daily_energy = $request->nv_daily_energy;
$foodProduct->nv_daily_fat = $request->nv_daily_fat;
$foodProduct->nv_daily_sat_fat = $request->nv_daily_sat_fat;
$foodProduct->nv_daily_carb = $request->nv_daily_carb;
$foodProduct->nv_daily_sugar = $request->nv_daily_sugar;
$foodProduct->nv_daily_fiber = $request->nv_daily_fiber;
$foodProduct->nv_daily_protein = $request->nv_daily_protein;
$foodProduct->nv_daily_salt = $request->nv_daily_salt;
$foodProduct->nutriscore = $request->nutriscore;
$foodProduct->nutriscore_args = $request->nutriscore_args;
$foodProduct->product_id = $product_id;
$foodProduct->save();
}
/**
* helper method to update toolproduct info
*
* @param Request $request
* @param integer $product_id
* @return void
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
*/
public function saveToolProduct(Request $request, int $product_id)
{
// phpcs:enable
$toolProduct = ToolProduct::firstOrNew([
'product_id' => $product_id,
]);
$toolProduct->save();
}
/**
* helper method to update anifoodProduct info
*
* @param Request $request
* @param integer $product_id
* @return void
*/
public function saveAnifoodProduct(Request $request, int $product_id)
{
$anifoodProduct = AnifoodProduct::firstOrNew([
'product_id' => $product_id,
]);
$anifoodProduct->composition = $request->composition;
$anifoodProduct->servings = $request->servings;
$anifoodProduct->save();
}
/**
* helper method to update garmentProduct info
*
* @param Request $request
* @param integer $product_id
* @return void
*/
public function saveGarmentProduct(Request $request, int $product_id)
{
$garmentProduct = GarmentProduct::firstOrNew([
'product_id' => $product_id,
]);
$garmentProduct->size = $request->size;
$garmentProduct->color = $request->color;
$garmentProduct->material = $request->material;
$garmentProduct->save();
}
/**
* index des produits mis en avant
*
* @return * @return \Illuminate\Contracts\View\View
*/
public function starred()
{
$products = Product::where('star', 1)->paginate(10);
return view('bo.products.starred', compact('products'));
}
}

View file

@ -1,139 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\DiscountType;
use App\Models\Promotion;
use App\Models\Product;
use Illuminate\Http\Request;
class PromotionsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$promotions = Promotion::all();
return view('bo.promotions.index', compact('promotions'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$discTypes = DiscountType::all();
$existingProds = Product::all();
$promotion = new Promotion();
$selectedProds = $promotion->products;
return view('bo.promotions.create', compact('discTypes', 'existingProds', 'selectedProds'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'product_id' => 'required',
]);
$productIds = explode(",", $request->product_id);
$discType = $request->discount_type_id;
$value = $request->discount_value;
$date_start = $request->date_start_date . " " . $request->date_start_time;
$date_end = $request->date_end_date . " " . $request->date_end_time;
$promotion = new Promotion();
$promotion->discount_type_id = $discType;
$promotion->discount_value = $value;
$promotion->date_start = $date_start;
$promotion->date_end = $date_end;
$promotion->save();
// Prise en charge de la liaison avec les produits.
$promotion->products()->sync($productIds);
return redirect()->route('bo.promotions.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$promotion = Promotion::findOrFail($id);
$discTypes = DiscountType::all();
$existingProds = Product::all();
$selectedProds = $promotion->products;
$existingProds = $existingProds->diff($selectedProds);
return view('bo.promotions.edit', compact('promotion', 'discTypes', 'existingProds', 'selectedProds'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$promotion = Promotion::findOrFail($id);
$request->validate([
'product_id' => 'required',
]);
$productIds = explode(",", $request->product_id);
$discType = $request->discount_type_id;
$value = $request->discount_value;
$date_start = $request->date_start_date . " " . $request->date_start_time;
$date_end = $request->date_end_date . " " . $request->date_end_time;
$promotion->discount_type_id = $discType;
$promotion->discount_value = $value;
$promotion->date_start = $date_start;
$promotion->date_end = $date_end;
$promotion->save();
// Prise en charge de la liaison avec les produits.
$promotion->products()->sync($productIds);
return redirect()->route('bo.promotions.edit', $promotion->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$p = Promotion::findOrFail($id);
// Prise en charge de la liaison avec les produits.
$p->products()->detach();
$p->delete();
return redirect()->route('bo.promotions.index');
}
}

View file

@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ranges\StoreRangeRequest;
use App\Http\Requests\Bo\Ranges\UpdateRangeRequest;
use App\Models\Range;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class RangeController extends Controller
{
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $ranges */
$ranges = Range::query();
if ($request->search) {
$this->searchQuery(
$ranges,
$request->search,
null,
'name',
'description'
);
}
$this->sortQuery($ranges);
$searchFields = trans('Nom, Description');
$ranges->orderBy('updated_at', 'DESC');
$ranges = $ranges->paginate();
return view('back::pages.ranges.index', compact('ranges', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.ranges.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\StoreRangeRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreRangeRequest $request)
{
return DB::transaction(function () use ($request) {
$range = new Range();
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
*/
public function edit(Range $range)
{
return view('back::pages.ranges.edit', [
'rangeModel' => $range
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Ranges\UpdateRangeRequest $request
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
*/
public function update(UpdateRangeRequest $request, Range $range)
{
return DB::transaction(function () use ($request, $range) {
$range->fill($request->validated());
if ($range->saveOrFail()) {
return redirect()
->route('bo.ranges.edit', $range)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.ranges'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Range $range
* @return \Illuminate\Http\Response
*/
public function destroy(Range $range)
{
if ($range->products()->count() > 0) {
return redirect()->back()
->withErrors("Suppression impossible : {$range->id} {$range->name} contient encore des produits.");
}
if ($range->deleteOrFail()) {
return redirect()
->route('bo.ranges.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.ranges'))]
));
}
return back();
}
}

View file

@ -1,169 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\OldSlug;
use App\Models\Product;
use App\Models\Range;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class RangesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$ranges = Range::all();
return view('bo.ranges.index', compact('ranges'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.ranges.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$name = $request->name;
$slug = Str::slug($name);
$description = $request->description;
$picture = $request->picture;
$status = $request->status === null ? 0 : 1;
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.range_image_maxwidth') . ',max-height=' .
config('theme.range_image_maxheight'),
]);
$range = new Range();
$range->name = $name;
$range->slug = $slug;
$range->description = $description;
$range->status = $status;
$range->save();
if ($picture !== null) {
$imageName = $range->id . '_' . $slug . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/gammes'), $imageName);
$range->picture = $imageName;
$range->save();
}
return redirect()->route('bo.ranges.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$range = Range::findOrFail($id);
return view('bo.ranges.edit', compact('range'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$name = $request->name;
$slug = Str::slug($name);
$description = $request->description;
$picture = $request->picture;
$status = $request->status === null ? 0 : 1;
$request->validate([
'name' => 'required|string|max:255',
'description' => 'required',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.range_image_maxwidth') . ',max-height=' .
config('theme.range_image_maxheight'),
]);
$range = Range::findOrFail($id);
if ($picture !== null) {
if ($range->picture !== null && file_exists(public_path('images/gammes/') . $range->picture)) {
unlink(public_path('images/gammes/') . $range->picture);
}
$imageName = $range->id . '_' . $slug . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/gammes'), $imageName);
}
$range->name = $name;
if ($range->slug != $slug) {
$exist = OldSlug::where('slug', $range->slug)->first();
if ($exist === null) {
$oldSlug = new OldSlug();
$oldSlug->range_id = $range->id;
$oldSlug->slug = $range->slug;
$oldSlug->save();
}
}
$range->slug = $slug;
$range->description = $description;
if ($picture !== null) {
$range->picture = $imageName;
}
$range->status = $status;
$range->save();
return redirect()->route('bo.ranges.edit', $range->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$range = Range::findOrFail($id);
$product = Product::where('range_id', $range->id)->count();
if ($product !== 0) {
return redirect()->back()
->withErrors("Suppression impossible : \"" . $range->name . "\" contient encore des produits.");
}
if ($range->picture !== null) {
if (file_exists(public_path('images/gammes/') . $range->picture)) {
unlink(public_path('images/gammes/') . $range->picture);
}
}
OldSlug::where('range_id', $range->id)->delete();
$range->delete();
return redirect()->route('bo.ranges.index');
}
}

View file

@ -1,112 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Config;
class SettingsController extends Controller
{
/**
* Manage settings page
*
* @param Request $request
* @return \Illuminate\Http\Response
*
*/
public function manage(Request $request)
{
$settings = Setting::all();
$settingsToPrint = [];
foreach ($settings as $setting) {
$settingsToPrint[$setting->key] = $setting->value;
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$input = $request->all();
// Comparer les settingsToPrint (en base) avec les inputs (pour trouver un nouveau).
foreach ($input as $key => $value) {
if (
!isset($settingsToPrint[$key])
&& $key != "_method"
&& $key != "_token"
) {
// New setting.
$setting = new Setting();
$setting->key = $key;
$setting->value = $value ?? 0;
$setting->save();
}
}
$looplimit = count($settings);
for ($i = 0; $i < $looplimit; $i++) {
if (is_array($request->input($settings[$i]->key))) {
$settings[$i]->value = implode(',', $request->input($settings[$i]->key));
} else {
$settings[$i]->value = $request->input($settings[$i]->key) === null ? 0
: $request->input($settings[$i]->key);
}
$settings[$i]->save();
}
// Store redirector to keep flash message from being wipe pby artisan call below.
$redirector = redirect()->route('bo.settings')->with('success', 'Modifications enregistrés !');
// All settings saved : cache routes.
Artisan::call('route:clear');
// Programmer un php artisan optimize ? (une fois par jour la nuit par exemple).
return $redirector;
}//end if
return view('bo.settings', compact('settingsToPrint'));
}
/**
* Manage Theme settings page
* Management des settings dont la clé est préfixée theme_
*
* @param Request $request
* @return \Illuminate\Http\Response
*
*/
public function manageTheme(Request $request)
{
$settings = Setting::where('key', 'LIKE', 'theme_%')->get();
$settingsToPrint = [];
foreach ($settings as $setting) {
$settingsToPrint[$setting->key] = $setting->value;
}
$config_settings = Config::get(('theme.bo_theme_settings'));
// Check if, for each of the theme defined settings we have a database setting.
foreach ($config_settings as $onekey => $params) {
if (!isset($settingsToPrint['theme_' . $onekey])) {
$dbsetting = new Setting();
$dbsetting->key = 'theme_' . $onekey;
$dbsetting->value = $params['default_value'];
$dbsetting->save();
// Injection dans settings to print.
$settingsToPrint[$dbsetting->key] = $dbsetting->value;
}
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$looplimit = count($settings);
for ($i = 0; $i < $looplimit; $i++) {
// Attention pour les booléens/checkbox on enregistre 'on'.
$settings[$i]->value = $request->input($settings[$i]->key) === null ? 0
: $request->input($settings[$i]->key);
$settings[$i]->save();
}
return redirect()->route('bo.themesettings')->with('success', 'Modifications enregistrés !');
}
return view('bo.settings.theme', compact('settingsToPrint', 'config_settings'));
}
}

View file

@ -0,0 +1,197 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Ajax\TimeslotExceptions\DeleteTimeslotExceptionsRequest;
use App\Http\Requests\Bo\Ajax\TimeslotExceptions\SaveTimeslotExceptionsRequest;
use App\Http\Requests\Bo\Shops\StoreShopRequest;
use App\Http\Requests\Bo\Shops\UpdateShopRequest;
use App\Models\CollectionException;
use App\Models\Shop;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ShopController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $shops */
$shops = Shop::query();
if ($request->search) {
$this->searchQuery(
$shops,
$request->search,
null,
'name',
'address',
'city',
'postal_code'
);
}
$this->sortQuery($shops);
$searchFields = trans('Nom, adresse, ville, cp');
$shops->orderBy('updated_at', 'DESC');
$shops = $shops->paginate();
return view('back::pages.shops.index', compact('shops', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.shops.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Shops \StoreShopRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreShopRequest $request)
{
return DB::transaction(function () use ($request) {
$shop = new Shop();
$shop->fill($request->validated());
if ($shop->saveOrFail()) {
return redirect()
->route('bo.shops.edit', $shop)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.shops'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\Response
*/
public function edit(Shop $shop)
{
$exceptionsCollecte = $shop->collectionExceptions()->orderBy('date')->get();
return view('back::pages.shops.edit', [
'shopModel' => $shop,
'exceptionsCollecte' => $exceptionsCollecte
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Shops\UpdateShopRequest $request
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\Response
*/
public function update(UpdateShopRequest $request, Shop $shop)
{
return DB::transaction(function () use ($request, $shop) {
$shop->fill($request->validated());
if ($shop->saveOrFail()) {
return redirect()
->route('bo.shops.edit', $shop)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.shops'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\Response
*/
public function destroy(Shop $shop)
{
if (Shop::count() <= 1) {
return redirect()->back()
->withErrors('Suppression impossible : Vous devez conserver au moins 1 point de retrait.');
}
if ($shop->deleteOrFail()) {
return redirect()
->route('bo.shops.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.shops'))]
));
}
return back();
}
/**
* Save collect exceptions
*
* @param \App\Http\Requests\Bo\Ajax\TimeslotExceptions\SaveTimeslotExceptionsRequest $request
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\JsonResponse
*/
public function saveExceptions(SaveTimeslotExceptionsRequest $request, Shop $shop): JsonResponse
{
return DB::transaction(function () use ($request, $shop) {
$collecteExceptionDates = \collect($request->dates)->keyBy('date');
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\CollectionException> */
$collectionExceptions = $shop->collectionExceptions
->filter(function (CollectionException $collectionException) use ($collecteExceptionDates) {
if (!$collecteExceptionDates->has($collectionException->date->format('Y-m-d'))) {
$collectionException->deleteOrFail();
return false;
}
$collecteExceptionDates->pull($collectionException->date->format('Y-m-d'));
return true;
});
$collecteExceptionDates->map(function (array $ajaxCollectionexception) use ($shop, $collectionExceptions) {
$collectionException = new CollectionException($ajaxCollectionexception);
$collectionException->shop()->associate($shop);
$collectionException->saveOrFail();
$collectionException->refresh();
$collectionExceptions->push($collectionException);
});
return \response()->json($collectionExceptions);
});
}
/**
* delete collecte exception
*
* @param \App\Http\Requests\Bo\Ajax\TimeslotExceptions\DeleteTimeslotExceptionsRequest $request
* @param \App\Models\Shop $shop
* @return string
*/
public function destroyExceptions(DeleteTimeslotExceptionsRequest $request, Shop $shop): JsonResponse
{
return DB::transaction(function () use ($request, $shop) {
CollectionException::whereHas('shop', function (Builder $q) use ($shop) {
$q->where('id', $shop->id);
})->whereId($request->id)->firstOrFail()->deleteOrFail();
return \response()->json($shop->collectionExceptions);
});
}
}

View file

@ -0,0 +1,180 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Slides\StoreSlideRequest;
use App\Http\Requests\Bo\Slides\UpdateSlideRequest;
use App\Models\Slide;
use App\Models\Slider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SlideController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $slides */
$slides = Slide::query();
if ($request->search) {
$this->searchQuery(
$slides,
$request->search,
null,
'label'
);
}
$this->sortQuery($slides);
$searchFields = trans('Label');
$slides->orderBy('updated_at', 'DESC');
$slides = $slides->paginate();
return view('back::pages.slides.index', compact('slides', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$sliders = Slider::all();
return view('back::pages.slides.create', compact('sliders'));
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Slides\StoreSlideRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreSlideRequest $request)
{
return DB::transaction(function () use ($request) {
$slide = new Slide();
$slide->fill($request->validated());
$slide->order = $this->getLastOrder();
if ($slide->saveOrFail()) {
return redirect()
->route('bo.slides.edit', $slide)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.slides'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Slide $slide
* @return \Illuminate\Http\Response
*/
public function edit(Slide $slide)
{
$sliders = Slider::all();
return view('back::pages.slides.edit', [
'slideModel' => $slide, 'sliders' => $sliders
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Slides\UpdateSlideRequest $request
* @param \App\Models\Slide $slide
* @return \Illuminate\Http\Response
*/
public function update(UpdateSlideRequest $request, Slide $slide)
{
return DB::transaction(function () use ($request, $slide) {
$slide->fill($request->validated());
if ($slide->saveOrFail()) {
return redirect()
->route('bo.slides.edit', $slide)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.slides'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Slide $slide
* @return \Illuminate\Http\Response
*/
public function destroy(Slide $slide)
{
if ($slide->deleteOrFail()) {
return redirect()
->route('bo.slides.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.slides'))]
));
}
return back();
}
/**
* Change slide order
*
* @param \App\Models\Slide $slide
* @param string $action
* @return \Illuminate\Http\RedirectResponse
*/
public function changeOrder(Slide $slide, string $action)
{
if ($action === 'up') {
$tmp = Slide::where([
['order', '<', $slide->order],
])->orderBy('order', 'DESC')->first();
} else {
$tmp = Slide::where([
['order', '>', $slide->order],
])->orderBy('order', 'ASC')->first();
}
$newOrder = $tmp->order;
$tmp->order = $slide->order;
$slide->order = $newOrder;
DB::transaction(function () use ($tmp, $slide) {
$tmp->saveOrFail();
$slide->saveOrFail();
});
return redirect()->back();
}
/**
* Get Last order
*
* @return integer
*/
private function getLastOrder(): int
{
$order = Slide::select('order')
->orderBy('order', 'DESC')->first();
if ($order === null) {
return 1;
}
return $order->order + 1;
}
}

View file

@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Sliders\StoreSliderRequest;
use App\Http\Requests\Bo\Sliders\UpdateSliderRequest;
use App\Models\Slider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SliderController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $sliders */
$sliders = Slider::query();
if ($request->search) {
$this->searchQuery(
$sliders,
$request->search,
null,
'label'
);
}
$this->sortQuery($sliders);
$searchFields = trans('Label');
$sliders->orderBy('updated_at', 'DESC');
$sliders = $sliders->paginate();
return view('back::pages.sliders.index', compact('sliders', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.sliders.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Sliders\StoreSliderRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreSliderRequest $request)
{
return DB::transaction(function () use ($request) {
$slider = new Slider();
$slider->fill($request->validated());
if ($slider->saveOrFail()) {
return redirect()
->route('bo.sliders.edit', $slider)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.sliders'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Slider $slider
* @return \Illuminate\Http\Response
*/
public function edit(Slider $slider)
{
return view('back::pages.sliders.edit', ['sliderModel' => $slider]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Sliders\UpdateSliderRequest $request
* @param \App\Models\Slider $slider
* @return \Illuminate\Http\Response
*/
public function update(UpdateSliderRequest $request, Slider $slider)
{
return DB::transaction(function () use ($request, $slider) {
$slider->fill($request->validated());
if ($slider->saveOrFail()) {
return redirect()
->route('bo.sliders.edit', $slider)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.sliders'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Slider $slider
* @return \Illuminate\Http\Response
*/
public function destroy(Slider $slider)
{
if (Slider::count() <= 1) {
return redirect()->back()->withErrors("Suppression impossible : Vous devez conserver au moins 1 Slider");
}
if ($slider->deleteOrFail()) {
return redirect()
->route('bo.sliders.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.sliders'))]
));
}
return back();
}
}

View file

@ -1,120 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Slider;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class SlidersController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$sliders = Slider::all();
return view('bo.sliders.index', compact('sliders'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.sliders.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$label = $request->label;
$slug = $request->slug;
if (empty($slug)) {
$slug = Str::slug($label);
}
$commentaire = $request->commentaire;
$request->validate([
'label' => 'required|string|max:255',
'slug' => 'sometimes|unique:sliders',
]);
$slider = new Slider();
$slider->label = $label;
$slider->slug = $slug;
$slider->commentaire = $commentaire;
$slider->save();
return redirect()->route('bo.sliders.edit', $slider->id)->with('success', 'Slider créé !');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$slider = Slider::findOrFail($id);
return view('bo.sliders.edit', compact('slider'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$slider = Slider::findOrFail($id);
$label = $request->label;
$slug = $request->slug;
$commentaire = $request->commentaire;
$request->validate([
'label' => 'required|string|max:255',
'slug' => 'required|unique:sliders,slug,' . $slider->id,
]);
$slider->label = $label;
$slider->slug = $slug;
$slider->commentaire = $commentaire;
$slider->save();
return redirect()->route('bo.sliders.edit', $slider->id)->with('success', 'Modifications enregistrées !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$slider = Slider::findOrFail($id);
$count = Slider::count();
if ($count === 1) {
return redirect()->back()->withErrors("Suppression impossible : Vous devez conserver au moins 1 Slider");
}
$slider->delete();
return redirect()->route('bo.sliders.index');
}
}

View file

@ -1,241 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Slide;
use App\Models\Slider;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class SlidesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$slides = Slide::orderBy('order', 'ASC')->get();
return view('bo.slides.index', compact('slides'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$sliders = Slider::all();
return view('bo.slides.create', compact('sliders'));
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$title = $request->title;
$picture = $request->picture;
$text = $request->text;
$linkLabel = $request->link_label;
$link = $request->link;
$newTab = $request->link_new_tab === null ? 0 : 1;
$sliderId = $request->slider_id;
if (empty($request->date_start_date)) {
$date_start = null;
} else {
if (empty($request->date_start_time)) {
$date_start = $request->date_start_date . " 00:00";
} else {
$date_start = $request->date_start_date . " " . $request->date_start_time;
}
}
if (empty($request->date_end_date)) {
$date_end = null;
} else {
if (empty($request->date_end_time)) {
$date_end = $request->date_end_date . " 23:59";
} else {
$date_end = $request->date_end_date . " " . $request->date_end_time;
}
}
$request->validate([
'title' => 'required|max:255',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.slide_image_maxwidth') . ',max-height=' .
config('theme.slide_image_maxheight'),
'link_label' => 'sometimes|max:255',
'link' => 'sometimes|max:255',
]);
$slide = new Slide();
$slide->title = $title;
$slide->text = $text;
$slide->link_label = $linkLabel;
$slide->link = $link;
$slide->link_new_tab = $newTab;
$slide->date_start = $date_start;
$slide->date_end = $date_end;
$slide->slider_id = $sliderId;
$slide->order = $this->getLastOrder();
$slide->save();
if ($picture !== null) {
$imageName = $slide->id . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/slides'), $imageName);
$slide->picture = $imageName;
$slide->save();
}
return redirect()->route('bo.slides.edit', $slide->id)->with('success', 'Slide créé !');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$slide = Slide::findOrFail($id);
$sliders = Slider::all();
return view('bo.slides.edit', compact('slide', 'sliders'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$title = $request->title;
$picture = $request->picture;
$text = $request->text;
$linkLabel = $request->link_label;
$link = $request->link;
$newTab = $request->link_new_tab === null ? 0 : 1;
$sliderId = $request->slider_id;
if (empty($request->date_start_date)) {
$date_start = null;
} else {
if (empty($request->date_start_time)) {
$date_start = $request->date_start_date . " 00:00";
} else {
$date_start = $request->date_start_date . " " . $request->date_start_time;
}
}
if (empty($request->date_end_date)) {
$date_end = null;
} else {
if (empty($request->date_end_time)) {
$date_end = $request->date_end_date . " 23:59";
} else {
$date_end = $request->date_end_date . " " . $request->date_end_time;
}
}
$request->validate([
'title' => 'required|max:255',
'picture' => 'image|mimes:jpeg,png,jpg|max:2048|dimensions:max-width=' .
config('theme.slide_image_maxwidth') . ',max-height=' .
config('theme.slide_image_maxheight'),
'link_label' => 'sometimes|max:255',
'link' => 'sometimes|max:255',
]);
$slide = Slide::findOrFail($id);
$slide->title = $title;
$slide->text = $text;
$slide->link_label = $linkLabel;
$slide->link = $link;
$slide->link_new_tab = $newTab;
$slide->date_start = $date_start;
$slide->date_end = $date_end;
$slide->slider_id = $sliderId;
$slide->save();
if ($picture !== null) {
if (file_exists(public_path() . 'images/slides/' . $slide->picture)) {
unlink(public_path() . 'images/slides/' . $slide->picture);
}
$imageName = $slide->id . '_' . Str::random(5) . '.' . $picture->extension();
$picture->move(public_path('images/slides'), $imageName);
$slide->picture = $imageName;
$slide->save();
}
return redirect()->route('bo.slides.edit', $slide->id)->with('success', 'Modifications enregistrées !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$s = Slide::findOrFail($id);
$s->delete();
return redirect()->route('bo.slides.index');
}
/**
* Change Slide Order
*
* @param integer $id
* @param string $action
* @return \Illuminate\Http\Response
*/
public function changeOrder(int $id, string $action)
{
$current = Slide::findOrFail($id);
if ($action === 'up') {
$tmp = Slide::where('order', '<', $current->order)->orderBy('order', 'DESC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
} else {
$tmp = Slide::where('order', '>', $current->order)->orderBy('order', 'ASC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
}
$tmp->save();
$current->save();
return back();
}
/**
* retreive order position to put a new slide to
*
* @return integer
*/
private function getLastOrder()
{
$order = Slide::select('order')->orderBy('order', 'DESC')->first();
if ($order === null) {
return 1;
}
return $order->order + 1;
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\StaticPages\StoreStaticPageRequest;
use App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest;
use App\Models\StaticPage;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class StaticPageController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $static_pages */
$static_pages = StaticPage::query()->where('type', '=', StaticPageType::crud());
if ($request->search) {
$this->searchQuery(
$static_pages,
$request->search,
null,
'title',
'value'
);
} //end if
$this->sortQuery($static_pages);
$searchFields = trans('Titre, contenu');
$static_pages->orderBy('updated_at', 'DESC');
$static_pages = $static_pages->paginate();
return view('back::pages.static_pages.index', compact('static_pages', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.static_pages.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\StaticPages\StoreStaticPageRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreStaticPageRequest $request)
{
return DB::transaction(function () use ($request) {
$static_page = new StaticPage();
// * Only crud pages are allowed to be created.
$static_page->type = StaticPageType::crud();
$static_page->fill($request->validated());
if ($static_page->saveOrFail()) {
return redirect()
->route('bo.static_pages.edit', $static_page)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.static_pages'))]
));
}
return back();
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Http\Response
*/
public function edit(StaticPage $static_page)
{
return view('back::pages.static_pages.edit', ['static_pageModel' => $static_page]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\StaticPages\UpdateStaticPageRequest $request
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Http\Response
*/
public function update(UpdateStaticPageRequest $request, StaticPage $static_page)
{
return DB::transaction(function () use ($request, $static_page) {
try {
$static_page->fill($request->validated());
} catch (\ErrorException $e) {
return back()
->with('danger', trans(
':model n\'a pas pu être enregistré car l\'image n\'a pu être transférée.',
['model' => Str::singular(trans('models.classes.pages'))]
));
}
if ($static_page->saveOrFail()) {
return redirect()
->to(
\App\Models\StaticPage::getBoRouteForType($static_page->type, 'edit') ??
route('bo.static_pages.edit', $static_page->id)
)
->with('success', trans(
':model a été créé',
['model' => trans('Page')]
));
}
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Http\Response
*/
public function destroy(StaticPage $static_page)
{
if ($static_page->deleteOrFail()) {
return redirect()
->route('bo.static_pages.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.static_pages'))]
));
}
return back();
}
}

View file

@ -0,0 +1,151 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tags\StoreTagRequest;
use App\Http\Requests\Bo\Tags\UpdateTagRequest;
use App\Models\Tag;
use App\Traits\Controllers\ChangesModelOrder;
use App\Traits\Controllers\UpdateModelStatus;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TagController extends Controller
{
use ChangesModelOrder;
use UpdateModelStatus;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $tags */
$tags = Tag::query();
if ($request->search) {
$this->searchQuery(
$tags,
$request->search,
null,
'label'
);
}
$this->sortQuery($tags);
$searchFields = trans('Label');
$tags = $tags->paginate();
return view('back::pages.tags.index', compact('tags', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('back::pages.tags.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreTagRequest $request)
{
return DB::transaction(function () use ($request) {
$tag = new Tag();
$tag->fill($request->validated());
if ($tag->saveOrFail()) {
return redirect()
->route('bo.tags.edit', $tag)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.tags'))]
));
}
return back();
});
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\StoreTagRequest $request
* @return \Illuminate\Http\Response
*/
public function jsonStore(StoreTagRequest $request)
{
return DB::transaction(function () use ($request) {
/** @var \App\Models\Tag */
$tag = Tag::where('slug', Str::slug($request->name))->firstOrNew();
$tag->fill($request->validated());
$saved = $tag->saveOrFail();
return \response()->json($saved ? $tag->toArray() : [], $saved ? 200 : 500);
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response
*/
public function edit(Tag $tag)
{
return view('back::pages.tags.edit', ['tagModel' => $tag]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tags\UpdateTagRequest $request
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response
*/
public function update(UpdateTagRequest $request, Tag $tag)
{
return DB::transaction(function () use ($request, $tag) {
$tag->fill($request->validated());
if ($tag->saveOrFail()) {
return redirect()
->route('bo.tags.edit', $tag)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))]
));
} //end if
return back();
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Tag $tag
* @return \Illuminate\Http\Response
*/
public function destroy(Tag $tag)
{
if ($tag->deleteOrFail()) {
return redirect()
->route('bo.tags.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.tags'))]
));
}
return back();
}
}

View file

@ -1,174 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class TagsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$tags = Tag::orderBy('order', 'ASC')->get();
return view('bo.tags.index', compact('tags'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.tags.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$label = $request->label;
$slug = Str::slug($label);
$status = $request->status === null ? 0 : 1;
$request->validate([
'label' => 'required|string|max:255',
]);
$tag = new Tag();
$tag->label = $label;
$tag->slug = $slug;
$tag->status = $status;
$tag->order = $this->getLastOrder();
$tag->save();
return redirect()->route('bo.tags.index');
}
/**
* Show the form for editing the specified resource.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function edit(int $id)
{
$tag = Tag::findOrFail($id);
return view('bo.tags.edit', compact('tag'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, int $id)
{
$label = $request->label;
$slug = Str::slug($label);
$status = $request->status === null ? 0 : 1;
$request->validate([
'label' => 'required|string|max:255',
]);
$tag = Tag::findOrFail($id);
$tag->label = $label;
$tag->slug = $slug;
$tag->status = $status;
$tag->save();
return redirect()->route('bo.tags.edit', $tag->id)->with('success', 'Modifications enregistrés !');
}
/**
* Remove the specified resource from storage.
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function destroy(int $id)
{
$tag = Tag::findOrFail($id);
$products = $tag->products()->count();
if ($products !== 0) {
return redirect()->back()
->withErrors("Suppression impossible : \"" . $tag->label . "\" est associé à des produits.");
}
$tag->delete();
return redirect()->route('bo.tags.index');
}
/**
* Change Tag Order
*
* @param integer $id
* @param string $action
* @return \Illuminate\Http\Response
*/
public function changeOrder(int $id, string $action)
{
$current = Tag::findOrFail($id);
if ($action === 'up') {
$tmp = Tag::where('order', '<', $current->order)->orderBy('order', 'DESC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
} else {
$tmp = Tag::where('order', '>', $current->order)->orderBy('order', 'ASC')->first();
$newOrder = $tmp->order;
$tmp->order = $current->order;
$current->order = $newOrder;
}
$tmp->save();
$current->save();
return back();
}
/**
* Toggle tag status
*
* @param integer $id
* @return \Illuminate\Http\Response
*/
public function toggleStatus(int $id)
{
$tag = Tag::findOrFail($id);
$tag->status = !($tag->status);
$tag->save();
return back();
}
/**
* retreive order position to put a new slide to
*
* @return integer
*/
public static function getLastOrder()
{
$order = Tag::select('order')->orderBy('order', 'DESC')->first();
if ($order === null) {
return 1;
}
return $order->order + 1;
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Enums\TimeSlot\WeekDay;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Timeslots\UpdateTimeslotRequest;
use App\Models\Shop;
use App\Models\TimeslotSetting;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class TimeslotController extends Controller
{
/**
* Display timeslot settings form
*
* @param \App\Models\Shop $shop
* @return \Illuminate\Contracts\View\View
*/
public function settings(Shop $shop)
{
// @phpcs:enable
$timeslotSettings = $shop->timeslotSettings;
if ($timeslotSettings->count() === 0) {
// Aucun settings pour ce magasin.
$timeslotSettings = $this->makeCreneauxSettings($shop);
}
return view('back::pages.timeslots.settings', [
'timeslotSettings' => $timeslotSettings,
'shopModel' => $shop
]);
}
/**
* Retrieve creneaux settings
*
* @param \App\Models\Shop $shop
* @return mixed
*/
private function makeCreneauxSettings(Shop $shop)
{
return DB::transaction(function () use ($shop) {
for ($i = 0; $i < 7; $i++) {
$dayset = new TimeslotSetting();
$dayset->day_index = \strval($i);
$dayset->shop()->associate($shop);
$dayset->saveOrFail();
}
return $shop->timeslotSettings;
});
}
/**
* save settings
*
* @param \App\Http\Requests\Bo\Timeslots\UpdateTimeslotRequest $request
* @param \App\Models\Shop $shop
* @return \Illuminate\Http\RedirectResponse
*/
public function updateSettings(UpdateTimeslotRequest $request, Shop $shop)
{
return DB::transaction(function () use ($request, $shop) {
$validated = $request->validated()['plages'];
foreach (WeekDay::toArray() as $weekDay) {
$setting = TimeslotSetting::where('day', $weekDay)
->whereHas('shop', function (Builder $q) use ($shop) {
$q->where('id', $shop->id);
})->firstOrFail();
$setting->fill($validated[$weekDay->value]);
$setting->saveOrFail();
}
return redirect()->route('bo.timeslots.settings', $shop)
->with('success', 'Modifications enregistrées !');
});
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\XmlIncos\SearchXmlInco;
use App\Http\Resources\XmlIncoCollection;
use App\Models\XmlInco;
use Illuminate\Support\Facades\Storage;
use Kwaadpepper\ImageResizer\ImageResizer;
use Symfony\Component\HttpFoundation\StreamedResponse;
class XmlIncoController extends Controller
{
/**
* Download a file from inco storage.
*
* @param string $inco_picture
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function getXmlIncoPicture(string $inco_picture): StreamedResponse
{
if (!($picturePath = XmlInco::getXmlIncoPicturePath($inco_picture))) {
abort(404);
}
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = XmlInco::getDisk();
// * Resized inco picture if wanted.
if (request()->get('resize')) {
$resizedPath = ImageResizer::resizeImageOrIgnore(
$disk->path($picturePath),
'boIncoPicture'
);
$picturePath = $resizedPath ? $resizedPath : $picturePath;
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$disk = Storage::disk('public');
}
return \response()->streamDownload(function () use ($disk, $picturePath) {
$stream = $disk->readStream($picturePath);
\fpassthru($stream);
\fclose($stream);
}, $inco_picture, [
'Content-Type' => $disk->mimeType($picturePath),
'Content-Length' => $disk->size($picturePath),
'Content-Disposition' => 'inline',
]);
}
/**
* Search for xml inco
*
* @param \App\Http\Requests\Bo\XmlIncos\SearchXmlInco $request
* @return \App\Http\Resources\XmlIncoCollection
*/
public function getXmlIncos(SearchXmlInco $request)
{
return new XmlIncoCollection(XmlInco::where('id', 'LIKE', "%$request->term%")
->orWhere('descriptif', 'LIKE', "%$request->term%")
->get());
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactFormRequest;
use App\Mail\SendContactMail;
use App\Models\Magasin;
use App\Models\Setting;
use App\Models\Content;
use Illuminate\Support\Facades\Mail;
use Illuminate\Http\Request;
class ContactController extends Controller
{
/**
* legal notice page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function contact(Request $request)
{
// @phpcs:enable
$status = $request->session()->get('status');
// 1er magasin en base pour le moment
$magasin = Magasin::first();
$content = Content::where('key', 'contact')->first();
return view('contact', compact('status', 'magasin', 'content'));
}
/**
* submit contact form
*
* @param ContactFormRequest $request
* @return \Illuminate\Http\Response
*/
public function contactSubmit(ContactFormRequest $request)
{
$data = $request->all();
$toMailSetting = Setting::where('key', 'send_contact_mail_to')->first();
if (empty($toMailSetting->value)) {
$toMail = config('mail.from.address');
} else {
$toMail = $toMailSetting->value;
}
// Envoi du mail d'alerte.
Mail::to($toMail)->send(new SendContactMail($request));
return back()->with('status', true);
}
}

View file

@ -2,14 +2,163 @@
namespace App\Http\Controllers;
use Gloudemans\Shoppingcart\Cart;
use Gloudemans\Shoppingcart\CartItem;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
class Controller extends BaseController
{
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
/**
* Build Search query on specified fields
* splitting search into words or using whole sentence
*
* @param Builder $query The eloquent query builder.
* @param string $search The query string.
* @param callable $searchQuery The query string.
* @param string ...$fields Either a simple array or a string
* in case of a string the two fields would be concatenate to make the search.
* @return Builder
*/
protected function searchQuery(
Builder $query,
string $search,
callable $searchQuery = null,
string ...$fields
): Builder {
if (count($fields)) {
// Search using words.
$words = explode(' ', $search);
$query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
foreach ($words as $word) {
if (!strlen($word)) {
continue;
}
$this->searchOnFields($query, $word, $fields);
} //end foreach
// Search whole using whole sentence.
$this->searchOnFields($query, $search, $fields);
if ($searchQuery) {
$searchQuery($query);
}
});
} //end if
return $query;
}
/**
* Build query search on all fields
*
* @param Builder $query
* @param string $search
* @param array $fields
* @return Builder
*/
private function searchOnFields(Builder $query, string $search, array $fields): Builder
{
$table = $query->getModel()->getTable();
foreach ($fields as $f) {
$f = str_replace('?', '\?', $f);
if (is_array($f)) {
$query->orWhereRaw(
sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
return "`$table`.`$q`";
})->implode(', \' \', ')),
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
} else {
$query->orWhereRaw(
"UPPER(`$table`.`$f`) LIKE UPPER(?)",
['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
);
}
}
return $query;
}
/**
* Sort columns with a query
*
* @param Builder $query
* @param array $ignore
* @return Builder
*/
protected function sortQuery(Builder $query, array $ignore = []): Builder
{
$rName = request()->route()->getName();
$table = $query->getModel()->getTable();
if (request()->sort_col) {
Session::put("$rName.sort_col", request()->sort_col);
}
if (request()->sort_way) {
Session::put("$rName.sort_way", request()->sort_way);
}
if (request()->rst) {
Session::remove("$rName.sort_col");
Session::remove("$rName.sort_way");
}
if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
if (
!isset($ignore) or
(isset($ignore) and !in_array(request()->sort_col, $ignore))
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
}
return $query;
}
/**
* Filter basket items to keep only the orderable ones
*
* ! Assert Model found, status true and orderable true.
*
* @param \Gloudemans\Shoppingcart\Cart $basket
* @param array<string> $messages Will be filled with messages on warnings.
* @return \Gloudemans\Shoppingcart\Cart
*/
protected function filterBasketKeepOnlyOrderableItems(Cart $basket, array &$messages = []): Cart
{
\collect($basket->content())->each(function (CartItem $item) use (&$basket, &$messages) {
/** @var \App\Models\Product|null */
$product = $item->model;
if (!$product or !$product->status or !$product->orderable) {
$basket->remove($item->rowId);
$messages[] = \trans(
'Le produit :name a été retiré du panier car il n\'est plus disponible',
[
'name' => $item->name
]
);
}
});
return $basket;
}
/**
* Get basket as product list
*
* @param \Gloudemans\Shoppingcart\Cart $basket
* @return \Illuminate\Support\Collection
*/
protected function basketToList(Cart $basket): Collection
{
return $basket->content()->map(function (CartItem $cartItem) {
return \json_decode(\json_encode($cartItem->toArray()));
});
}
}

View file

@ -1,606 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CustomerRegisterRequest;
use App\Http\Requests\LoginRequest;
use App\Http\Requests\UpdateInfosRequest;
use App\Mail\PasswordResetMail;
use App\Mail\RegisterConfirmMail;
use App\Models\Customers;
use App\Models\Magasin;
use App\Models\Order;
use App\Models\PasswordReset;
use App\Models\Salarie;
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\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;
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 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();
// * liaison éventuelle avec la table salaries
if ($request->matricule && $request->code) {
/** @var \App\Models\Salarie $salarie */
$salarie = Salarie::where('matricule', '=', $request->matricule)
->where('code', '=', $request->code)
->firstOrFail();
$salarie->customer()->associate($customer);
$salarie->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 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 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) {
$magasin = null;
} else {
$magasin = Magasin::find($order->timeslot->magasin->id);
}
return view('customer.order-detail', compact('order', 'magasin'));
}
/**
* 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 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;
}
}

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('theme::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('theme::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('theme::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('theme::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('theme::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('theme::auth.verify');
}
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
return response()->redirectToRoute('home')->getTargetUrl();
}
}

View file

@ -0,0 +1,152 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\Product\WeightUnit;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Gloudemans\Shoppingcart\Facades\Cart as FacadesCart;
use Illuminate\Validation\ValidationException;
class BasketController extends Controller
{
/**
* Get Basket
*
* @return \Illuminate\Http\JsonResponse
*/
public function getBasket()
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
return \response()->json($this->basketToList($basket));
}
/**
* Ajout d'un produit au panier
*
* ! Assert Model found, status true and orderable true.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Validation\ValidationException Si le produit n'est pas disponible (orderable).
*/
public function addToBasket(Product $product)
{
$this->authorize('useBasketSystem', Product::class);
$this->authorize('addToCart', $product);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$basket = $this->filterBasketKeepOnlyOrderableItems($basket);
if (!$product->status or !$product->orderable) {
throw ValidationException::withMessages([
'error' => \trans('Ce produit n\'est plus disponible')
]);
}
// ! Use cart facade here because of lib bug
// ! https://github.com/bumbummen99/LaravelShoppingcart/pull/181
// * Note Lib is is actually refactoring for laravel 9 php8 only
// * https://github.com/bumbummen99/LaravelShoppingcart/pull/145
/** @var \Gloudemans\Shoppingcart\CartItem */
$cartItem = FacadesCart::add($product, 1, [
'slug' => $product->slug,
'picture' => $product->picture,
'sell_unit' => $product->sell_unit,
'weight_with_unit' => WeightUnit::getDisplayableWeight($product->weight),
'discount_type' => \optional(\optional($product->discount)->type)->value,
// * Prix ttc du produit
'price_ttc' => $product->price_ttc,
// * Prix ttc du produit remisé
'undiscounted_price_ttc' => $product->undiscounted_price_ttc,
]);
$cartItem->setTaxRate($product->tax_rate->rate / 100);
$cartItem->setDiscountRate($product->discount_rate);
return \response()->json($this->basketToList($basket));
}
/**
* Retrait d'un produit du panier (1 quantité d'1 type)
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\JsonResponse
*/
public function removeFromBasket(Product $product)
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
/** @var \Gloudemans\Shoppingcart\CartItem|null */
$basketItem = $basket->content()->where('id', $product->id)->first();
/** @var string|null */
$rowId = \optional($basketItem)->rowId;
/** @var integer|null */
$qty = \optional($basketItem)->qty;
if ($rowId and !\is_null($qty)) {
$basket->update($rowId, $qty - 1);
}
$basket = $this->filterBasketKeepOnlyOrderableItems($basket);
return \response()->json($this->basketToList($basket));
}
/**
* Retrait d'un type produit du panier (toute les quantités d'1 type)
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\JsonResponse
*/
public function removeItemTypeFromBasket(Product $product)
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
/** @var \Gloudemans\Shoppingcart\CartItem|null */
$basketItem = $basket->content()->where('id', $product->id)->first();
/** @var string|null */
$rowId = \optional($basketItem)->rowId;
if ($rowId) {
$basket->remove($rowId);
}
$basket = $this->filterBasketKeepOnlyOrderableItems($basket);
return \response()->json($this->basketToList($basket));
}
/**
* Retrait d'un type produit du panier (toute les quantités d'1 type)
*
* @return \Illuminate\Http\JsonResponse
*/
public function emptyBasket()
{
$this->authorize('useBasketSystem', Product::class);
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$basket->destroy();
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
return \response()->json($this->basketToList($basket));
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\ContactFormRequest;
use App\Models\Contact;
use App\Models\StaticPage;
use Illuminate\Http\Request;
class ContactController extends Controller
{
/**
* Display contact from
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function contact(Request $request)
{
$status = $request->session()->get('status');
$content = StaticPage::getPageOfType(StaticPageType::contact());
return view('theme::pages.contact', compact('status', 'content'));
}
/**
* Submit contact form
*
* @param \App\Http\Requests\Fo\ContactFormRequest $request
* @return \Illuminate\Http\Response
*/
public function contactSubmit(ContactFormRequest $request)
{
$contact = new Contact($request->validated());
if ($contact->saveOrFail()) {
return redirect()
->back()
->with('status', true)
->with('success', trans('Votre message à été envoyé'));
}
return back();
}
}

View file

@ -0,0 +1,263 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Ajax\SubscribeNewsletterRequest;
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\Order;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
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('theme::pages.customer.dashboard', compact('customer', 'crmInfos'));
}
/**
* Show update customer informations form
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function email()
{
return view('theme::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('theme::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('theme::pages.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
});
}
/**
* Affichage des commandes du customer connecté
*
* @return \Illuminate\Contracts\View\View
*/
public function myOrders()
{
/** @var \App\Models\Customer */
$customer = auth('frontend')->user();
$orders = $customer->orders;
return view('theme::pages.customer.my-orders', compact('orders'));
}
/**
* Show detail commande
*
* @param \App\Models\Order $order
* @return \Illuminate\Contracts\View\View
*/
public function orderDetail(Order $order)
{
$order->load('timeslot.shop');
return view('theme::pages.customer.order-detail', compact('order'));
}
// * --- END Backoffice ---
/**
* Call the CRM to add a single optin
*
* @param \App\Http\Requests\Fo\Ajax\SubscribeNewsletterRequest $request
* @return \Illuminate\Http\JsonResponse
*
*/
public function addOptIn(SubscribeNewsletterRequest $request)
{
try {
SingleOptin::setStatus($request->email, true);
return \response()->json();
} catch (ApiErrorException $e) {
// * Il y a deja un customerInfo avec optin a true pour ce customer
if ($e->getApiError()->equals(ApiError::singleOptinCustomerExists())) {
return \response()->json([
'errors' => [
'email' => [\trans('Vous êtes déjà inscrit avec cette adresse email')]
]
], 422);
}
report($e);
return \response()->json([], 500);
} catch (CrmException $e) {
report($e);
return \response()->json([], 500);
}
}
}

View file

@ -0,0 +1,536 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\DeliveryMode\DeliveryModeEnum;
use App\Enums\GeneralConfigType;
use App\Enums\Order\DeliveryStatus;
use App\Enums\Order\OrderProcess;
use App\Enums\Order\PaymentStatus;
use App\Enums\Order\ValidationStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\Fo\Orders\CheckoutRequest;
use App\Http\Requests\Fo\Orders\SetOrderTimeSlotRequest;
use App\Http\Requests\Fo\Orders\ShippingFormRequest;
use App\Http\Requests\Fo\Orders\ShippingModeFormRequest;
use App\Http\Resources\ShopResource;
use App\Mail\NewOrderIncome;
use App\Models\Customer;
use App\Models\DeliveryMode;
use App\Models\GeneralConfig;
use App\Models\Order;
use App\Models\Shop;
use App\Models\Timeslot;
use App\Notifications\OrderCreated;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use LDC\LDCCRM\Enums\Country;
class OrderController extends Controller
{
// * --- TUNNEL DE COMMANDE ---
/**
* Récapitualtif du panier
*
* * --- 1 ---
*
* @return \Illuminate\Contracts\View\View
*/
public function basketRecap()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
// * On peut passer commande ?
$this->authorize('orderThings', Order::class);
$this->setOrderProcessStep(OrderProcess::reviewBasket());
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$messages = [];
$basket = $this->filterBasketKeepOnlyOrderableItems($basket, $messages);
if (!empty($messages)) {
Session::flash('warning', \collect($messages)->implode(",\n"));
}
$basketItems = $this->basketToList($basket);
return view('theme::pages.order-tunnel.basket-recap', compact(
'basket',
'basketItems'
));
}
/**
* Select shipping Mode And create Session Order
*
* * --- 2 ---
*
* @return \Illuminate\Contracts\View\View
*/
public function shippingMode()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->setOrderProcessStep(OrderProcess::shippingMethod(), OrderProcess::reviewBasket());
// * CREATE ORDER
/** @var \App\Models\Customer $customer */
$customer = auth('frontend')->user();
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
/** @var \App\Models\Order|null $order */
$order = Order::getCurrentOrder();
// * If there was a previous order that was not ended use the ID.
/** @var \App\Models\Order */
$order = $order ?? new Order();
$order->mail_sent = false;
$order->delivery_status = DeliveryStatus::pending();
$order->payment_status = PaymentStatus::pending();
$order->validation_status = ValidationStatus::pending();
$order->assignBasket($basket);
$order->customer()->associate($customer);
// * Save Session Order
Session::put('currentOrder', $order);
/** @var \Illuminate\Database\Eloquent\Collection<\App\Models\DeliveryMode> */
$deliveryModes = app('DeliveryModesGlobal');
return view('theme::pages.order-tunnel.shipping-mode', \compact('deliveryModes'));
}
/**
* Save shipping Mode
*
* * --- 2 ---
*
* @param \App\Http\Requests\Fo\Orders\ShippingModeFormRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function shippingModeSubmit(ShippingModeFormRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::shippingMethod(), OrderProcess::reviewBasket());
/** @var \App\Models\DeliveryMode $mode */
if (!($mode = DeliveryMode::availables()->whereId($request->mode)->first())) {
$e = new \RuntimeException('Mode de livraison non disponible', 1);
report($e);
Session::flash('error', trans('Mode de livraison non disponible'));
return \back()->withException($e);
}
/** @var \App\Models\Order $order */
$order = Order::getCurrentOrder();
$order->deliveryMode()->associate($mode);
Session::put('currentOrder', $order);
return \response()->redirectToRoute('order-tunnel.shipping');
}
/**
* Shipping informations
*
* * --- 3 ---
*
* ! Pour - Click and collect
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If delivery mode is unhandled.
*/
public function shipping()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::shippment(), OrderProcess::shippingMethod());
/** @var \App\Models\Order $order */
$order = Order::getCurrentOrder();
// * Switch Delivery modes
// * Pour afficher formulaire qui devra retourner une adresse 'shipping'
/**
* * Switch Delivery modes
* - Pour afficher formulaire qui devra retourner une adresse 'shipping'.
* - Qui devra fournir un timeslot dans le cas de clickAndCollect.
*/
/** @var \App\Enums\DeliveryMode\DeliveryModeEnum|null */
$delivery_mode = \optional($order->deliveryMode)->mode;
switch ($delivery_mode) {
case DeliveryModeEnum::inShop():
// * 1 Select timeslot if not already done.
if (!$order->timeslot()->first() or !Session::get('deliveryModePickSlot')) {
$shops = Shop::all()->map(function (Shop $shop) {
return new ShopResource($shop);
});
Session::put('deliveryModePickSlot', true);
return \view('theme::pages.order-tunnel.pick-slot', \compact('order', 'shops'));
}
return \view('theme::pages.order-tunnel.shipping', \compact('order'));
default:
throw new \RuntimeException('Unhandled delivery mode ');
}
throw new \RuntimeException('This should never happen');
}
/**
* Save and validate timeslot
*
* * --- 3 ---
*
* @param \App\Http\Requests\Fo\Orders\SetOrderTimeSlotRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Validation\ValidationException If Creneau is not available anomore.
*/
public function validateSelectedTimeSlot(SetOrderTimeSlotRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::shippment(), OrderProcess::shippingMethod());
/** @var \App\Models\Shop */
$shop = Shop::query()->where('id', $request->shop_id)->firstOrFail();
$creneau = Carbon::createFromFormat('d/m/Y H:i', "{$request->selected_day} {$request->selected_time}");
if (!$shop->isCreneauAvailable($creneau)) {
throw ValidationException::withMessages([
'error' => 'Ce créneau n\'est plus disponible'
]);
}
DB::transaction(function () use ($shop, $creneau) {
/** @var \App\Models\Order $order */
$order = Order::getCurrentOrder();
// * Assign order uuid we would use, if none already.
if (!$order->uuid) {
$order->uuid = Str::uuid();
}
Session::put('currentOrder', $order);
/** @var \App\Models\Timeslot */
$timeSlot = Timeslot::query()->where('order_uuid', $order->uuid)->firstOrNew();
$timeSlot->order()->associate($order);
$timeSlot->shop()->associate($shop);
$timeSlot->slot = $creneau;
$timeSlot->saveOrFail();
});
return response()->redirectToRoute('order-tunnel.shipping');
}
/**
* SaveShippingAdress
*
* * --- 3 ---
*
* @param \App\Http\Requests\Fo\Orders\ShippingFormRequest $request
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException If somehow no email can be found.
*/
public function shippingSubmit(ShippingFormRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertDoesHaveCurrentOrder();
$this->assertBasketDidNotChange();
$this->setOrderProcessStep(OrderProcess::shippment(), OrderProcess::shippingMethod());
/** @var \App\Models\Order $order */
$order = Order::getCurrentOrder();
$order->fill($request->validated());
/** @var object */
$billing = $order->billing;
$billing->country = Country::france();
$order->billing = $billing;
Session::put('deliveryModePickSlot', false);
// Fetch right email to send in.
if ($order->billing->email) {
$order->email = $order->billing->email;
} elseif ($order->shipping->email) {
$order->email = $order->shipping->email;
} else {
$order->email = $order->customer->api_data->email;
}
if (!$order->email) {
throw new \RuntimeException('Somehow no email was provided');
}
$this->fillShippingFromShopInfo($order);
Session::put('currentOrder', $order);
return \response()->redirectToRoute('order-tunnel.review');
}
/**
* Valider le panier avant achat.
*
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function review()
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::paymentReview(), OrderProcess::shippment());
/** @var \App\Models\Customer $customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Order $order */
$order = Order::getCurrentOrder();
// * Order completed email already sent.
if ($order->mail_sent) {
self::detroyCurrentBasket();
return \view('theme::pages.order-tunnel.done', \compact('order'));
}
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
return view('theme::pages.order-tunnel.review', \compact('basket', 'order'));
}
/**
* Setup Order for stripe submit
*
* @param \App\Http\Requests\Fo\Orders\CheckoutRequest $request
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If payment method is unhandled somehow.
*/
public function reviewSubmit(CheckoutRequest $request)
{
$this->assertDoesNotHaveEmptyBasket();
$this->assertBasketDidNotChange();
$this->assertDoesHaveCurrentOrder();
$this->setOrderProcessStep(OrderProcess::paymentReview(), OrderProcess::shippment());
/** @var \App\Models\Customer $customer */
$customer = auth('frontend')->user();
/** @var \App\Models\Order $order */
$order = Order::getCurrentOrder();
// * Order completed email already sent.
if ($order->mail_sent) {
self::detroyCurrentBasket();
return \view('theme::pages.order-tunnel.done', \compact('order'));
}
if ($request->method === 'cnc') {
return $this->checkoutCnC($customer, $order);
}
throw new \RuntimeException('Unhandled method payment');
}
/**
* Checkout click and collect
*
* @param \App\Models\Customer $customer
* @param \App\Models\Order $order
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \Illuminate\Validation\ValidationException If saving order fails.
*/
private function checkoutCnC(Customer $customer, Order $order)
{
return DB::transaction(function () use ($customer, $order) {
// * Notification has been sent
$order->mail_sent = true;
// * Enregistrement commande + envoi de mail.
if (!$order->save()) {
report(new \RuntimeException("Order saving error happened."));
throw ValidationException::withMessages([
'error' => trans('Une erreur est survenue, veuillez réssayer')
]);
}
// * Notifiy customer
$customer->notify(new OrderCreated($order));
/** @var object|null */
$notificationMail = \optional(\optional(GeneralConfig::query()->where('type', GeneralConfigType::global())
->first())->data)->confirmed_orders_destination_email;
if (!\optional($notificationMail)->value) {
throw new \RuntimeException('Could not get new order email notification setting');
}
Mail::to($notificationMail->value)->send(new NewOrderIncome($order));
self::detroyCurrentBasket();
return \view('theme::pages.order-tunnel.done', \compact('order'));
});
}
// * --- FIN TUNNEL DE COMMANDE ---
// * PRIVATE METHODS
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertDoesHaveCurrentOrder()
{
$order = Order::getCurrentOrder();
if (
$order = Order::getCurrentOrder() and
$order->payment_status and
$order->payment_status->equals(PaymentStatus::payed())
) {
self::detroyCurrentBasket();
abort(\response()->view('theme::pages.order-tunnel.done', \compact('order')));
}
if (!$order) {
\redirect()->route('order-tunnel.basket.recap')->throwResponse();
}
}
/**
* Delete order steps, removes basket, ends tunnel transaction.
*
* @return void
*/
public static function detroyCurrentBasket()
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$basket->destroy();
Session::remove('currentOrder');
Session::remove('PreviousOrderStep');
Session::remove('OrderStep');
Session::remove('deliveryModePickSlot');
Session::remove('basketFingerPrint');
}
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertDoesNotHaveEmptyBasket(): void
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
if (!$basket->count()) {
\redirect()->route('home')
->with('warning', trans('Vous n\'avez aucun produit dans votre panier'))
->throwResponse();
}
}
/**
* Assert user does not have an empty basket
* or redirect with warning
*
* @return void
*/
private function assertBasketDidNotChange(): void
{
/** @var \Gloudemans\Shoppingcart\Cart */
$basket = app('basket');
$actual_finger_print = hash('sha256', \json_encode($basket->content()->jsonSerialize()));
$session_finger_print = Session::get('basketFingerPrint');
Session::put('basketFingerPrint', $actual_finger_print);
if ($session_finger_print !== $actual_finger_print) {
Session::remove('deliveryModePickSlot');
\redirect()->route('order-tunnel.basket.recap')
->with('warning', trans('Votre panier à été modifié'))
->throwResponse();
}
}
/**
* Validate Process step
*
* @param \App\Enums\Order\OrderProcess $step
* @param \App\Enums\Order\OrderProcess|null $assertDid
* @return void
* @throws \Illuminate\Validation\ValidationException If previous was not done.
*/
private function setOrderProcessStep(OrderProcess $step, OrderProcess $assertDid = null): void
{
if ($assertDid) {
if (!Session::get('PreviousOrderStep')) {
Session::put('PreviousOrderStep', collect());
}
/** @var \Illuminate\Support\Collection */
$previousSteps = Session::get('PreviousOrderStep');
if (Session::get('OrderStep') < $step) {
/** @var \App\Enums\OrderProcess */
$previousStep = Session::get('OrderStep');
$previousSteps->put($previousStep->value, $previousStep);
Session::put('PreviousOrderStep', $previousSteps);
}
if (!$previousSteps->count()) {
\redirect()->route('home')
->with('warning', trans('Vous devez d\'abord valider votre panier'))
->throwResponse();
}
if (!$previousSteps->contains($assertDid)) {
throw ValidationException::withMessages(['Étape précédente requise']);
}
} //end if
Session::put('OrderStep', $step);
}
/**
* Fill order shipping with in shop infos
*
* @param \App\Models\Order $order
* @return void
* @throws \Illuminate\Validation\ValidationException Si le créneau de reservation n'est plus trouvable.
*/
private function fillShippingFromShopInfo(Order $order)
{
/** @var \App\Models\Customer */
$user = auth('frontend')->user();
if ($order->deliveryMode->mode->equals(DeliveryModeEnum::inShop())) {
/** @var \App\Models\Timeslot|null */
if (!($timeslot = $order->timeslot()->first())) {
throw ValidationException::withMessages([
'error' => trans('Créneau de réservation introuvable')
]);
}
$order->shipping = [
'firstname' => $timeslot->shop->name,
'lastname' => '',
'email' => null,
'phone' => $timeslot->shop->phone,
'line1' => $timeslot->shop->address,
'postal_code' => $timeslot->shop->postal_code,
'city' => $timeslot->shop->city,
'country' => $timeslot->shop->country
];
}
}
}

View file

@ -1,13 +1,14 @@
<?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;
use Illuminate\Http\Request;
class ProductsController extends Controller
class ProductController extends Controller
{
/**
* Ranges page
@ -21,7 +22,7 @@ class ProductsController extends Controller
{
// @phpcs:enable
// $ranges shared in AppServiceProvider.
return view('ranges');
return view('theme::pages.ranges');
}
/**
@ -43,10 +44,10 @@ class ProductsController 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('promotions')
->with('promotions')->get();
return view('range', compact('range', 'products', 'offres'));
->where('status', 1)
->has('discount')
->with('discount')->get();
return view('theme::pages.range', compact('range', 'products', 'offres'));
}
/**
@ -65,17 +66,8 @@ class ProductsController 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('theme::pages.product', ['productModel' => $product]);
}
/**
@ -101,7 +93,7 @@ class ProductsController extends Controller
$search_title = 'Recherche : ' . $filter;
return view('search', compact('products', 'search_title', 'filter'));
return view('theme::pages.search', compact('products', 'search_title', 'filter'));
}
/**
@ -126,6 +118,6 @@ class ProductsController extends Controller
$tag = Tag::where('slug', $tag_slug)->firstOrFail();
$search_title = $tag->label;
return view('search', compact('products', 'search_title', 'tag'));
return view('theme::pages.search', compact('products', 'search_title', 'tag'));
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Enums\StaticPageType;
use App\Http\Controllers\Controller;
use App\Models\Product;
use App\Models\Slider;
use App\Models\StaticPage;
use Illuminate\Http\Request;
class StaticPageController extends Controller
{
/**
* Display static page type home
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function home(Request $request)
{
// @phpcs:enable
$content = StaticPage::getPageOfType(StaticPageType::home());
// Star products.
$products = Product::where('status', true)->where('star', true)->get();
$slider = Slider::where('slug', 'home')->first();
$slides = $slider->showable_slides;
return view(
'theme::pages.home',
compact('content', 'products', 'slides')
);
}
/**
* Display a static page
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\StaticPage $static_page_front
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If the static page type is not handled.
*/
public function displayStaticPage(Request $request, StaticPage $static_page_front)
{
$static_page = $static_page_front;
switch ($static_page->type) {
case StaticPageType::contact():
return app(ContactController::class)->contact($request);
case StaticPageType::home():
return $this->callAction('home', [$request]);
case StaticPageType::legals():
case StaticPageType::sellconditions():
case StaticPageType::confidentiality():
case StaticPageType::faq():
return view('theme::pages.static-page', compact('static_page'));
case StaticPageType::crud():
default:
throw new \RuntimeException("Unhandled static page {$static_page->type->label}");
}
}
/**
* Display a crud static page
*
* @param \App\Models\StaticPage $static_page
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
* @throws \RuntimeException If the static page type is not handled.
*/
public function displayCrudStaticPage(StaticPage $static_page)
{
if (!$static_page->type->equals(StaticPageType::crud())) {
throw new \RuntimeException("Unhandled static page {$static_page->type->label}");
}
return view('theme::pages.static-page', compact('static_page'));
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Fo;
use App\Models\Shop;
class TimeSlotController
{
/**
* Récupérer tout les créneaux disponible à partir de
* maintenant pour un magasin sur les jours à venir.
*
* @param \App\Models\Shop $shop Le magasin concerné.
* @param integer $days_to_come Sur quelle période proposer des créneaux à partir de maintenant.
* @return array
* @throws \RuntimeException Si la période demandé est incohérente.
*/
public function getAvailableCreneauxByShop(Shop $shop, int $days_to_come = 1)
{
return $shop->getAvailableCreneaux($days_to_come);
}
}

View file

@ -1,76 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Content;
use App\Models\Product;
use App\Models\Slider;
use App\Models\Range;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use LDC\LDCCRM\Enums\ApiError;
use LDC\LDCCRM\Exceptions\ApiErrorException;
use LDC\LDCCRM\Exceptions\CrmException;
use LDC\LDCCRM\Models\SingleOptin;
class HomeController extends Controller
{
/**
* Display homepage
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
$content = Content::where('key', 'accueil')->first();
// Star products.
$products = Product::where('status', 1)->where('star', 1)->get();
$slider = Slider::where('slug', 'home')->first();
$slides = $slider->showableSlides;
$quickorder_ranges = Range::with('products')->get();
foreach ($quickorder_ranges as $range) {
foreach ($range->products as $product) {
$product->calculatedPrices = $product->getCalcPrices();
}
}
return view('home', compact('content', 'products', 'slides', 'quickorder_ranges'));
}
/**
* 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,71 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Content;
use App\Models\Faq;
use Illuminate\Http\Request;
class IndexController extends Controller
{
/**
* legal notice page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function legalNotice(Request $request)
{
// @phpcs:enable
$content = Content::where('key', 'legals')->first();
return view('legal-notice', compact('content'));
}
/**
* terms and conditions page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function terms(Request $request)
{
// @phpcs:enable
$content = Content::where('key', 'cgv')->first();
return view('terms', compact('content'));
}
/**
* privacy page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function privacy(Request $request)
{
// @phpcs:enable
$content = Content::where('key', 'confidentialite')->first();
return view('privacy', compact('content'));
}
/**
* faq page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function faq(Request $request)
{
// @phpcs:enable
$content = Content::where('key', 'faq')->first();
$faq = Faq::all();
return view('faq', compact('faq', 'content'));
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Range;
use Illuminate\Http\Request;
class QuickOrderController extends Controller
{
/**
* Quickorder page
*
* @param Request $request
* @return \Illuminate\Contracts\View\View
*
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
*/
public function index(Request $request)
{
// @phpcs:enable
$quickorder_ranges = Range::with('products')->get();
foreach ($quickorder_ranges as $range) {
foreach ($range->products as $product) {
$product->calculatedPrices = $product->getCalcPrices();
}
}
return view('quickorder', compact('quickorder_ranges'));
}
}

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
@ -30,12 +29,14 @@ class Kernel extends HttpKernel
*/
protected $middlewareGroups = [
'web' => [
'htmlsanitize',
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\SecureHeaders::class
],
'api' => [
@ -62,6 +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

@ -0,0 +1,53 @@
<?php
namespace App\Http\Middleware;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Http\Request;
class HtmlSanitizer
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param mixed ...$params
* @return mixed
*/
public function handle(Request $request, \Closure $next, ...$params)
{
$inputs = $request->all();
/** @var callable|null */
$cleanInput = null;
$cleanInput = function (&$input, $name, $rootName = null) use ($params, &$cleanInput) {
// * Nested element array
if (is_array($input)) {
array_walk_recursive($input, $cleanInput, $name);
}
if (!is_string($input)) {
return;
}
if (in_array($name, $params) or in_array($rootName ?? $name, $params)) {
// ? Is a json input ?
if ($isJson = \json_decode($input, true) and is_array($isJson)) {
// ! Decode each json field recursive.
\array_walk_recursive($isJson, function (&$item) {
if (\is_string($item)) {
$item = clean($item);
$item = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($item);
}
});
} else {
$input = clean($input);
$input = ToolboxHelper::stripCkEditorSchemesOnMediaInputs($input);
}
} else {
$input = strip_tags($input);
}
};
\array_walk($inputs, $cleanInput);
$request->merge($inputs);
return $next($request);
}
}

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

@ -16,6 +16,7 @@ class RedirectIfAuthenticated
* @param \Closure $next
* @param string|null ...$guards
* @return mixed
* @throws \RuntimeException If guard is unhandled.
*/
public function handle(Request $request, Closure $next, ...$guards)
{
@ -23,7 +24,14 @@ class RedirectIfAuthenticated
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
switch ($guard) {
case 'frontend':
return \redirect()->route('home');
case 'backend':
return \redirect()->route('bo.dashboard');
default:
throw new \RuntimeException("Unhandled guard `{$guard}` .");
}
}
}

View file

@ -2,7 +2,7 @@
namespace App\Http\Middleware;
use App\Models\OldSlug;
use App\Models\OldSlugable;
use App\Models\Product;
use App\Models\Range;
use Closure;
@ -25,10 +25,10 @@ class RedirectOldSlugs
if ($productSlug !== null) {
$product = Product::where('slug', $productSlug)->first();
if (!$product) {
$productOldSlug = OldSlug::where('slug', $productSlug)->first();
$productOldSlug = OldSlugable::where('slug', $productSlug)->first();
if ($productOldSlug) {
$product = Product::where('id', $productOldSlug->product_id)->first();
return redirect()->route('product', $product->slug);
return redirect()->route('products.show', $product->slug);
}
}
}
@ -36,10 +36,10 @@ class RedirectOldSlugs
if ($rangeSlug !== null) {
$range = Range::where('slug', $rangeSlug)->first();
if (!$range) {
$rangeOldSlug = OldSlug::where('slug', $rangeSlug)->first();
$rangeOldSlug = OldSlugable::where('slug', $rangeSlug)->first();
if ($rangeOldSlug) {
$range = Range::where('id', $rangeOldSlug->range_id)->first();
return redirect()->route('range', $range->slug);
return redirect()->route('ranges.show', $range->slug);
}
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\View;
class SecureHeaders
{
/**
* Enumerate headers which you do not want in your application's responses.
* Great starting point would be to go check out @Scott_Helme's:
* https://securityheaders.com/
*
* @var array
*/
private $unwantedHeaderList = [
'X-Powered-By',
'Server',
];
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
View::share('nonce', $nonce = \uniqid());
// * For VueJS dev tool to execute
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-$nonce'";
$scheme = config('app.env') === 'local' ? 'http' : 'https';
$reportUri = Route::has('cspReportUri') ?
\sprintf(' report-uri %s ;', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
$this->removeUnwantedHeaders($this->unwantedHeaderList);
$response = $next($request);
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
$cspPolicies = [
// * Unsafe inline ck-editor requires unsafe-inline
"style-src 'self' 'unsafe-inline' " . \implode(' ', [
'https://cdnjs.cloudflare.com',
// * File Manager
'https://fonts.googleapis.com',
'https://cdn.jsdelivr.net',
'https://use.fontawesome.com',
'https://fonts.googleapis.com'
]) . ';',
"script-src 'self' $jsDev " . \implode(' ', [
'https://www.googletagmanager.com',
'https://cdnjs.cloudflare.com',
"$scheme://www.google-analytics.com",
]) . ';'
];
$response->headers->set(
'Content-Security-Policy',
implode(' ', $cspPolicies) . $reportUri
);
$response->headers->set('Feature-Policy', 'fullscreen \'self\';');
return $response;
}
/**
* Remove unwanted headers
*
* @param array $headerList
* @return void
*/
private function removeUnwantedHeaders(array $headerList)
{
foreach ($headerList as $header) {
header_remove($header);
}
}
}

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

@ -1,10 +1,10 @@
<?php
namespace App\Http\Requests;
namespace App\Http\Requests\Bo\Ajax\TimeslotExceptions;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
class DeleteTimeslotExceptionsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
@ -13,10 +13,7 @@ class LoginRequest extends FormRequest
*/
public function authorize()
{
$this->merge([
'remember' => $this->remember ? true : false
]);
return true;
return \auth('backend')->check();
}
/**
@ -27,9 +24,7 @@ class LoginRequest extends FormRequest
public function rules()
{
return [
'email' => 'required|email:rfc,filter',
'password' => 'required',
'remember' => 'required|boolean'
'id' => 'required|exists:collection_exceptions,id'
];
}
}

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