Merge branch 'v2' into develop

This commit is contained in:
Jérémy Munsch 2021-08-12 14:57:05 +02:00
commit f59deac5fc
170 changed files with 15756 additions and 749 deletions

56
.env.testing.example Normal file
View file

@ -0,0 +1,56 @@
APP_NAME=Laravel
APP_ENV=testing
APP_KEY=base64:UypKzhOMAs2aDu3ovgw/lINvWZQWicbz9nfUXDpDzeQ=
APP_DEBUG=true
APP_URL="http://127.0.0.25:8888"
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=testing_ldccrm_12_2020
DB_USERNAME=root
DB_PASSWORD=DBPASSWORD
DB_CONNECTION_OLD=mysql
DB_HOST_OLD=127.0.0.1
DB_PORT_OLD=3306
DB_DATABASE_OLD="ldc-crm"
DB_USERNAME_OLD=root
DB_PASSWORD_OLD=
MIGRATIONPAGINATION=20000
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=8a51f2fab34b77
MAIL_PASSWORD=49de71fa995df4
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=crm@ldc.fr
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "clients/V2"]
path = clients/V2
url = https://ldc-web:phaeZ6ej@voight001.lesidc.com/crm-client

7
CRM V2.code-workspace Normal file
View file

@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
]
}

View file

@ -9,6 +9,7 @@ use Illuminate\Console\Command;
class DeleteUnconfirmedCustomers extends Command
{
/**
* The name and signature of the console command.
*
@ -21,17 +22,8 @@ class DeleteUnconfirmedCustomers extends Command
*
* @var string
*/
protected $description = "Delete the customers that haven't confirmed their acocunt within 24 hours after the registeration.";
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
protected $description = 'Delete the customers that haven\'t
confirmed their acocunt within 24 hours after the registeration.';
/**
* Execute the console command.
@ -41,7 +33,7 @@ class DeleteUnconfirmedCustomers extends Command
public function handle()
{
$customersLogins = CustomerLogin::where([['status', 0], ['created_at', '<', Carbon::now()->subDay(1)]])->get();
foreach($customersLogins as $customerLogin) {
foreach ($customersLogins as $customerLogin) {
// Si non activé et créer il y a plus d'un jour : Suppression
$customerLogin->customer->infos()->where('brand_id', $customerLogin->brand_id)->first()->delete();
// On récupère son id pour le cas ou on doit le supprimer complètement après

View file

@ -0,0 +1,166 @@
<?php
namespace App\Console\Commands;
use App\Enums\ApiError;
use App\Enums\ApiStatus;
use App\Enums\ArcadiaMailType;
use App\Enums\ArcadiaOrderType;
use App\Enums\Country;
use App\Enums\QuestionType;
use App\Exceptions\Api\V2\ApiErrorException;
use App\Exceptions\Api\V2\CrmException;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
class GenerateCrmClient extends Command
{
private const TARGET = 'clients/V2/src';
private const CLIENTNAMESPACE = 'LDC\\LDCCRM';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'client:generate';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Generate the crm v2 client in clients/V2 folder';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->copyEnums();
$this->copyExceptions();
$this->copyTranslations();
$this->copyRoutesList();
$this->copyApiConfigInfo();
}
private function copyApiConfigInfo()
{
$this->info('Copy Api Config');
$rootTarget = app_path('../' . self::TARGET);
if (File::exists("$rootTarget/../apiConfig.readonly.json")) {
File::delete("$rootTarget/../apiConfig.readonly.json");
}
File::put(
"$rootTarget/../apiConfig.readonly.json",
\json_encode(config('api'), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
}
private function copyRoutesList()
{
$this->info('Copy Routes');
$routes = [];
/** @var \Illuminate\Routing\Route */
foreach (Route::getRoutes() as $route) {
if (\strpos($route->getPrefix(), 'v2') !== false) {
$routes[$route->getName()] = [
'uri' => $route->uri(),
'methods' => $route->methods()
];
}
}
$rootTarget = app_path('../' . self::TARGET);
if (File::exists("$rootTarget/routes.json")) {
File::delete("$rootTarget/routes.json");
}
File::put(
"$rootTarget/routes.json",
\json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
}
private function copyExceptions(): void
{
$this->info('Copy Exceptions');
$rootTarget = app_path('../' . self::TARGET);
$this->copyToFolder([
CrmException::class,
ApiErrorException::class
], 'Exceptions');
$this->replaceInPhpFile(
"$rootTarget/Exceptions/ApiErrorException.php",
'App\Enums\ApiError',
'LDC\LDCCRM\Enums\ApiError'
);
}
private function copyEnums(): void
{
$this->info('Copy Enums');
$this->copyToFolder([
ApiError::class,
ApiStatus::class,
ArcadiaMailType::class,
ArcadiaOrderType::class,
Country::class,
QuestionType::class
], 'Enums');
}
private function copyTranslations()
{
$this->info('Copy Translations');
$rootTarget = app_path('../' . self::TARGET . '/..');
$source = \resource_path('lang/en/validation.php');
$dest = "$rootTarget/resources/lang/en/validation.php";
if (File::exists($dest)) {
File::delete($dest);
}
File::copy($source, $dest);
}
/**
* Copy Classes to specified folder
*
* @param array $classes
* @param string $folder
* @return void
*/
private function copyToFolder(array $classes, string $folder): void
{
$rootTarget = app_path('../' . self::TARGET);
foreach ($classes as $class) {
$classFileName = \str_replace("App\\$folder\\", '', sprintf('%s.php', $class));
$classFileName = \str_replace('\\', '/', $classFileName);
$classBasename = class_basename($class);
$source = \app_path("$folder/$classFileName");
$dest = "$rootTarget/$folder/$classBasename.php";
if (File::exists($dest)) {
File::delete($dest);
}
File::copy($source, $dest);
$this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE);
}
}
private function replaceInPhpFile(string $source, string $search, string $replace): void
{
$fContent = File::get($source);
// remove trailing \Api\V2
if ($pos = strpos($fContent, '\Api\V2')) {
$fContent = substr_replace($fContent, '', $pos, strlen('\Api\V2'));
}
// replace namespace top level
$pos = strpos($fContent, $search);
if ($pos !== false) {
File::put($source, \str_replace($search, $replace, $fContent));
}
}
}

View file

@ -6,13 +6,11 @@ use App\Lib\MigrateOldData;
use App\Models\Brand;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class migrateCrmUsers extends Command
class MigrateCrmUsers extends Command
{
/**
* The name and signature of the console command.
*
@ -36,11 +34,7 @@ class migrateCrmUsers extends Command
public function handle()
{
try {
$date = null;
// $table = $this->ask('What is the tmp table name with users data ?');
$table = 'legaulois_migration';
$table = $this->ask('What is the tmp table name with users data ?');
try {
DB::connection(config('database.oldDB'))->table($table)->exists();
@ -50,14 +44,14 @@ class migrateCrmUsers extends Command
}
$brandname = $this->ask('What is the brand name ?');
$brand = Brand::where('name', 'LIKE', "%$brandname%")->first();
$brand = Brand::where('name', 'LIKE', "%$brandname%")->first();
if (!$brand) {
if (!$this->confirm(sprintf('Would you like to use %s ? ', $brandname))) {
$this->info('Command cancelled');
return 0;
}
if ($this->confirm('Would you like to create a new one ?')) {
$brand = new Brand();
$brand = new Brand();
$brand->name = $brandname;
$brand->save();
} else {

View file

@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Hash;
class UserCreate extends Command
{
/**
* The name and signature of the console command.
*
@ -22,16 +23,6 @@ class UserCreate extends Command
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
@ -42,15 +33,15 @@ class UserCreate extends Command
$admin = false;
$firstname = $this->ask('What is the firstname?');
$lastname = $this->ask('What is the lastname?');
$email = $this->ask('What is the email?');
$pwd = $this->secret('What is the password?');
$lastname = $this->ask('What is the lastname?');
$email = $this->ask('What is the email?');
$pwd = $this->secret('What is the password?');
$user = new User();
$user = new User();
$user->firstname = $firstname;
$user->lastname = $lastname;
$user->email = $email;
$user->password = Hash::make($pwd);
$user->lastname = $lastname;
$user->email = $email;
$user->password = Hash::make($pwd);
$user->save();
$this->info('User created.');

View file

@ -7,14 +7,13 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
protected $commands = [];
/**
* Define the application's command schedule.
@ -24,7 +23,6 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')->hourly();
}
/**
@ -34,7 +32,7 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}

103
app/Enums/ApiError.php Normal file
View file

@ -0,0 +1,103 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* This describes an API error.
*
* @method static self modelNotFound()
* The request model was not found (typically the customer)
*
* @method static self validationFailed()
* This means some data could not be validated,
* you may have to look into message to get the details
*
* @method static self customerExists()
* The customer already exists
*
* @method static self customerNotActivated()
* The customer account is not activated and cannot perform
* this action
*
* @method static self customerAlreadyActive()
* You have requested to activate a customer account,
* but it is already active
*
* @method static self singleOptinCustomerExists()
* You have requested to create a single optin for an email,
* but it already exists
*
* @method static self apiAuthFailed()
* The authentification to the api failed. You may have to
* check into config or .env file for your brand credentials
*
* @method static self authFailed()
* The authentification for this customer failed because of wrong credentials
*
* @method static self needAuth()
* This action requires to be authenticated
*
* @method static self methodNotAllowed()
* This http method is not allowed
*
* @method static self wrongTimeStamp()
* The timestamp given is wrong. This would happen in case if
* a replay attack scenario. Timestamps are provided in headers
* and in the http body
*
* @method static self throttleRequestsException()
* Too many requests have been attempted. The is a throttle security.
* Normally you should not trigger this.
* If this happens fill a ticket on https://supportweb.ldc.fr
* with some technical details
*
* @method static self authorizationException()
* You are maybe trying to access data that is out of your range.
* This should not happen normally, it is here to allow you to handle this case.
* If this happens fill a ticket on https://supportweb.ldc.fr
* with some technical details
*
* @package LDCCrmClient
* @subpackage enums
*/
class ApiError extends BaseEnum
{
public static function values(): array
{
return [
'modelNotFound' => 101,
'validationFailed' => 102,
'customerExists' => 103,
'customerAlreadyActive' => 110,
'customerNotActivated' => 116,
'authFailed' => 117,
'needAuth' => 118,
'apiAuthFailed' => 119,
'singleOptinCustomerExists' => 120,
'methodNotAllowed' => 150,
'wrongTimeStamp' => 151,
'authorizationException' => 403,
'throttleRequestsException' => 429
];
}
public static function labels(): array
{
return [
'modelNotFound' => trans('%s not found.'),
'validationFailed' => trans('Could not validate data.'),
'customerExists' => trans('User already registered on this site.'),
'customerNotActivated' => trans('Account is not activated.'),
'customerAlreadyActive' => trans('User already active.'),
'singleOptinCustomerExists' => trans('SingleOptin wont be added because customer exists.'),
'authFailed' => trans('Authentication failed.'),
'needAuth' => trans('You need to be authenticated'),
'methodNotAllowed' => trans('This method is not allowed for this request'),
'wrongTimeStamp' => trans('Your request is malformed, please check timestamp header and parameter'),
'authorizationException' => trans('You are not autorized to access this'),
'throttleRequestsException' => trans('Throttle: too many request have been attempted, please retry later')
];
}
}

16
app/Enums/ApiStatus.php Normal file
View file

@ -0,0 +1,16 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self OK()
* @method static self KO()
*
* @package LDCCrmClient
* @subpackage enums
*/
class ApiStatus extends BaseEnum
{
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self default()
* @method static self game()
*
* @package LDCCrmClient
* @subpackage enums
*/
class ArcadiaMailType extends BaseEnum
{
public static function values(): array
{
return [
'default' => 1,
'game' => 2
];
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self cb()
* @method static self check()
*
* @package LDCCrmClient
* @subpackage enums
*/
class ArcadiaOrderType extends BaseEnum
{
public static function values(): array
{
return [
'cb' => 0,
'check' => 1
];
}
}

774
app/Enums/Country.php Normal file
View file

@ -0,0 +1,774 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* Liste de Pays
* @source https://datahub.io/core/country-list Data Hub country list
* @method static self afghanistan()
* @method static self aland-islands()
* @method static self albania()
* @method static self algeria()
* @method static self american-samoa()
* @method static self andorra()
* @method static self angola()
* @method static self anguilla()
* @method static self antarctica()
* @method static self antigua-and-barbuda()
* @method static self argentina()
* @method static self armenia()
* @method static self aruba()
* @method static self australia()
* @method static self austria()
* @method static self azerbaijan()
* @method static self bahrain()
* @method static self bahamas()
* @method static self bangladesh()
* @method static self barbados()
* @method static self belarus()
* @method static self belgium()
* @method static self belize()
* @method static self benin()
* @method static self bermuda()
* @method static self bhutan()
* @method static self bolivia-plurinational-state-of()
* @method static self bonaire-sint-eustatius-and-saba()
* @method static self bosnia-and-herzegovina()
* @method static self botswana()
* @method static self bouvet-island()
* @method static self brazil()
* @method static self british-indian-ocean-territory()
* @method static self brunei-darussalam()
* @method static self bulgaria()
* @method static self burkina-faso()
* @method static self burundi()
* @method static self cambodia()
* @method static self cameroon()
* @method static self canada()
* @method static self cape-verde()
* @method static self cayman-islands()
* @method static self central-african-republic()
* @method static self chad()
* @method static self chile()
* @method static self china()
* @method static self christmas-island()
* @method static self cocos-keeling-islands()
* @method static self colombia()
* @method static self comoros()
* @method static self congo()
* @method static self congo-the-democratic-republic-of-the()
* @method static self cook-islands()
* @method static self costa-rica()
* @method static self cote-divoire()
* @method static self croatia()
* @method static self cuba()
* @method static self curacao()
* @method static self cyprus()
* @method static self czech-republic()
* @method static self denmark()
* @method static self djibouti()
* @method static self dominica()
* @method static self dominican-republic()
* @method static self ecuador()
* @method static self egypt()
* @method static self el-salvador()
* @method static self equatorial-guinea()
* @method static self eritrea()
* @method static self estonia()
* @method static self ethiopia()
* @method static self falkland-islands-malvinas()
* @method static self faroe-islands()
* @method static self fiji()
* @method static self finland()
* @method static self france()
* @method static self french-guiana()
* @method static self french-polynesia()
* @method static self french-southern-territories()
* @method static self gabon()
* @method static self gambia()
* @method static self georgia()
* @method static self germany()
* @method static self ghana()
* @method static self gibraltar()
* @method static self greece()
* @method static self greenland()
* @method static self grenada()
* @method static self guadeloupe()
* @method static self guam()
* @method static self guatemala()
* @method static self guernsey()
* @method static self guinea()
* @method static self guinea-bissau()
* @method static self guyana()
* @method static self haiti()
* @method static self heard-island-and-mcdonald-islands()
* @method static self holy-see-vatican-city-state()
* @method static self honduras()
* @method static self hong-kong()
* @method static self hungary()
* @method static self iceland()
* @method static self india()
* @method static self indonesia()
* @method static self iran-islamic-republic-of()
* @method static self iraq()
* @method static self ireland()
* @method static self isle-of-man()
* @method static self israel()
* @method static self italy()
* @method static self jamaica()
* @method static self japan()
* @method static self jersey()
* @method static self jordan()
* @method static self kazakhstan()
* @method static self kenya()
* @method static self kiribati()
* @method static self korea-democratic-peoples-republic-of()
* @method static self korea-republic-of()
* @method static self kuwait()
* @method static self kyrgyzstan()
* @method static self lao-peoples-democratic-republic()
* @method static self latvia()
* @method static self lebanon()
* @method static self lesotho()
* @method static self liberia()
* @method static self libya()
* @method static self liechtenstein()
* @method static self lithuania()
* @method static self luxembourg()
* @method static self macao()
* @method static self macedonia-the-former-yugoslav-republic-of()
* @method static self madagascar()
* @method static self malawi()
* @method static self malaysia()
* @method static self maldives()
* @method static self mali()
* @method static self malta()
* @method static self marshall-islands()
* @method static self martinique()
* @method static self mauritania()
* @method static self mauritius()
* @method static self mayotte()
* @method static self mexico()
* @method static self micronesia-federated-states-of()
* @method static self moldova-republic-of()
* @method static self monaco()
* @method static self mongolia()
* @method static self montenegro()
* @method static self montserrat()
* @method static self morocco()
* @method static self mozambique()
* @method static self myanmar()
* @method static self namibia()
* @method static self nauru()
* @method static self nepal()
* @method static self netherlands()
* @method static self new-caledonia()
* @method static self new-zealand()
* @method static self nicaragua()
* @method static self niger()
* @method static self nigeria()
* @method static self niue()
* @method static self norfolk-island()
* @method static self northern-mariana-islands()
* @method static self norway()
* @method static self oman()
* @method static self pakistan()
* @method static self palau()
* @method static self palestine-state-of()
* @method static self panama()
* @method static self papua-new-guinea()
* @method static self paraguay()
* @method static self peru()
* @method static self philippines()
* @method static self pitcairn()
* @method static self poland()
* @method static self portugal()
* @method static self puerto-rico()
* @method static self qatar()
* @method static self reunion()
* @method static self romania()
* @method static self russian-federation()
* @method static self rwanda()
* @method static self saint-barthelemy()
* @method static self saint-helena-ascension-and-tristan-da-cunha()
* @method static self saint-kitts-and-nevis()
* @method static self saint-lucia()
* @method static self saint-martin-french-part()
* @method static self saint-pierre-and-miquelon()
* @method static self saint-vincent-and-the-grenadines()
* @method static self samoa()
* @method static self san-marino()
* @method static self sao-tome-and-principe()
* @method static self saudi-arabia()
* @method static self senegal()
* @method static self serbia()
* @method static self seychelles()
* @method static self sierra-leone()
* @method static self singapore()
* @method static self sint-maarten-dutch-part()
* @method static self slovakia()
* @method static self slovenia()
* @method static self solomon-islands()
* @method static self somalia()
* @method static self south-africa()
* @method static self south-georgia-and-the-south-sandwich-islands()
* @method static self south-sudan()
* @method static self spain()
* @method static self sri-lanka()
* @method static self sudan()
* @method static self suriname()
* @method static self svalbard-and-jan-mayen()
* @method static self swaziland()
* @method static self sweden()
* @method static self switzerland()
* @method static self syrian-arab-republic()
* @method static self taiwan-province-of-china()
* @method static self tajikistan()
* @method static self tanzania-united-republic-of()
* @method static self thailand()
* @method static self timor-leste()
* @method static self togo()
* @method static self tokelau()
* @method static self tonga()
* @method static self trinidad-and-tobago()
* @method static self tunisia()
* @method static self turkey()
* @method static self turkmenistan()
* @method static self turks-and-caicos-islands()
* @method static self tuvalu()
* @method static self uganda()
* @method static self ukraine()
* @method static self united-arab-emirates()
* @method static self united-kingdom()
* @method static self united-states()
* @method static self united-states-minor-outlying-islands()
* @method static self uruguay()
* @method static self uzbekistan()
* @method static self vanuatu()
* @method static self venezuela-bolivarian-republic-of()
* @method static self viet-nam()
* @method static self virgin-islands-british()
* @method static self virgin-islands-us()
* @method static self wallis-and-futuna()
* @method static self western-sahara()
* @method static self yemen()
* @method static self zambia()
* @method static self zimbabwe()
*
* @package LDCCrmClient
* @subpackage enums
*/
class Country extends BaseEnum
{
public static function values(): array
{
return [
'afghanistan' => 1,
'aland-islands' => 2,
'albania' => 3,
'algeria' => 4,
'american-samoa' => 5,
'andorra' => 6,
'angola' => 7,
'anguilla' => 8,
'antarctica' => 9,
'antigua-and-barbuda' => 10,
'argentina' => 11,
'armenia' => 12,
'aruba' => 13,
'australia' => 14,
'austria' => 15,
'azerbaijan' => 16,
'bahrain' => 17,
'bahamas' => 18,
'bangladesh' => 19,
'barbados' => 20,
'belarus' => 21,
'belgium' => 22,
'belize' => 23,
'benin' => 24,
'bermuda' => 25,
'bhutan' => 26,
'bolivia-plurinational-state-of' => 27,
'bonaire-sint-eustatius-and-saba' => 28,
'bosnia-and-herzegovina' => 29,
'botswana' => 30,
'bouvet-island' => 31,
'brazil' => 32,
'british-indian-ocean-territory' => 33,
'brunei-darussalam' => 34,
'bulgaria' => 35,
'burkina-faso' => 36,
'burundi' => 37,
'cambodia' => 38,
'cameroon' => 39,
'canada' => 40,
'cape-verde' => 41,
'cayman-islands' => 42,
'central-african-republic' => 43,
'chad' => 44,
'chile' => 45,
'china' => 46,
'christmas-island' => 47,
'cocos-keeling-islands' => 48,
'colombia' => 49,
'comoros' => 50,
'congo' => 51,
'congo-the-democratic-republic-of-the' => 52,
'cook-islands' => 53,
'costa-rica' => 54,
'cote-divoire' => 55,
'croatia' => 56,
'cuba' => 57,
'curacao' => 58,
'cyprus' => 59,
'czech-republic' => 60,
'denmark' => 61,
'djibouti' => 62,
'dominica' => 63,
'dominican-republic' => 64,
'ecuador' => 65,
'egypt' => 66,
'el-salvador' => 67,
'equatorial-guinea' => 68,
'eritrea' => 69,
'estonia' => 70,
'ethiopia' => 71,
'falkland-islands-malvinas' => 72,
'faroe-islands' => 73,
'fiji' => 74,
'finland' => 75,
'france' => 76,
'french-guiana' => 77,
'french-polynesia' => 78,
'french-southern-territories' => 79,
'gabon' => 80,
'gambia' => 81,
'georgia' => 82,
'germany' => 83,
'ghana' => 84,
'gibraltar' => 85,
'greece' => 86,
'greenland' => 87,
'grenada' => 88,
'guadeloupe' => 89,
'guam' => 90,
'guatemala' => 91,
'guernsey' => 92,
'guinea' => 93,
'guinea-bissau' => 94,
'guyana' => 95,
'haiti' => 96,
'heard-island-and-mcdonald-islands' => 97,
'holy-see-vatican-city-state' => 98,
'honduras' => 99,
'hong-kong' => 100,
'hungary' => 101,
'iceland' => 102,
'india' => 103,
'indonesia' => 104,
'iran-islamic-republic-of' => 105,
'iraq' => 106,
'ireland' => 107,
'isle-of-man' => 108,
'israel' => 109,
'italy' => 110,
'jamaica' => 111,
'japan' => 112,
'jersey' => 113,
'jordan' => 114,
'kazakhstan' => 115,
'kenya' => 116,
'kiribati' => 117,
'korea-democratic-peoples-republic-of' => 118,
'korea-republic-of' => 119,
'kuwait' => 120,
'kyrgyzstan' => 121,
'lao-peoples-democratic-republic' => 122,
'latvia' => 123,
'lebanon' => 124,
'lesotho' => 125,
'liberia' => 126,
'libya' => 127,
'liechtenstein' => 128,
'lithuania' => 129,
'luxembourg' => 130,
'macao' => 131,
'macedonia-the-former-yugoslav-republic-of' => 132,
'madagascar' => 133,
'malawi' => 134,
'malaysia' => 135,
'maldives' => 136,
'mali' => 137,
'malta' => 138,
'marshall-islands' => 139,
'martinique' => 140,
'mauritania' => 141,
'mauritius' => 142,
'mayotte' => 143,
'mexico' => 144,
'micronesia-federated-states-of' => 145,
'moldova-republic-of' => 146,
'monaco' => 147,
'mongolia' => 148,
'montenegro' => 149,
'montserrat' => 150,
'morocco' => 151,
'mozambique' => 152,
'myanmar' => 153,
'namibia' => 154,
'nauru' => 155,
'nepal' => 156,
'netherlands' => 157,
'new-caledonia' => 158,
'new-zealand' => 159,
'nicaragua' => 160,
'niger' => 161,
'nigeria' => 162,
'niue' => 163,
'norfolk-island' => 164,
'northern-mariana-islands' => 165,
'norway' => 166,
'oman' => 167,
'pakistan' => 168,
'palau' => 169,
'palestine-state-of' => 170,
'panama' => 171,
'papua-new-guinea' => 172,
'paraguay' => 173,
'peru' => 174,
'philippines' => 175,
'pitcairn' => 176,
'poland' => 177,
'portugal' => 178,
'puerto-rico' => 179,
'qatar' => 180,
'reunion' => 181,
'romania' => 182,
'russian-federation' => 183,
'rwanda' => 184,
'saint-barthelemy' => 185,
'saint-helena-ascension-and-tristan-da-cunha' => 186,
'saint-kitts-and-nevis' => 187,
'saint-lucia' => 188,
'saint-martin-french-part' => 189,
'saint-pierre-and-miquelon' => 190,
'saint-vincent-and-the-grenadines' => 191,
'samoa' => 192,
'san-marino' => 193,
'sao-tome-and-principe' => 194,
'saudi-arabia' => 195,
'senegal' => 196,
'serbia' => 197,
'seychelles' => 198,
'sierra-leone' => 199,
'singapore' => 200,
'sint-maarten-dutch-part' => 201,
'slovakia' => 202,
'slovenia' => 203,
'solomon-islands' => 204,
'somalia' => 205,
'south-africa' => 206,
'south-georgia-and-the-south-sandwich-islands' => 207,
'south-sudan' => 208,
'spain' => 209,
'sri-lanka' => 210,
'sudan' => 211,
'suriname' => 212,
'svalbard-and-jan-mayen' => 213,
'swaziland' => 214,
'sweden' => 215,
'switzerland' => 216,
'syrian-arab-republic' => 217,
'taiwan-province-of-china' => 218,
'tajikistan' => 219,
'tanzania-united-republic-of' => 220,
'thailand' => 221,
'timor-leste' => 222,
'togo' => 223,
'tokelau' => 224,
'tonga' => 225,
'trinidad-and-tobago' => 226,
'tunisia' => 227,
'turkey' => 228,
'turkmenistan' => 229,
'turks-and-caicos-islands' => 230,
'tuvalu' => 231,
'uganda' => 232,
'ukraine' => 233,
'united-arab-emirates' => 234,
'united-kingdom' => 235,
'united-states' => 236,
'united-states-minor-outlying-islands' => 237,
'uruguay' => 238,
'uzbekistan' => 239,
'vanuatu' => 240,
'venezuela-bolivarian-republic-of' => 241,
'viet-nam' => 242,
'virgin-islands-british' => 243,
'virgin-islands-us' => 244,
'wallis-and-futuna' => 245,
'western-sahara' => 246,
'yemen' => 247,
'zambia' => 248,
'zimbabwe' => 249
];
}
public static function labels(): array
{
return [
'afghanistan' => 'Afghanistan',
'aland-islands' => 'Åland Islands',
'albania' => 'Albania',
'algeria' => 'Algeria',
'american-samoa' => 'American Samoa',
'andorra' => 'Andorra',
'angola' => 'Angola',
'anguilla' => 'Anguilla',
'antarctica' => 'Antarctica',
'antigua-and-barbuda' => 'Antigua and Barbuda',
'argentina' => 'Argentina',
'armenia' => 'Armenia',
'aruba' => 'Aruba',
'australia' => 'Australia',
'austria' => 'Austria',
'azerbaijan' => 'Azerbaijan',
'bahrain' => 'Bahrain',
'bahamas' => 'Bahamas',
'bangladesh' => 'Bangladesh',
'barbados' => 'Barbados',
'belarus' => 'Belarus',
'belgium' => 'Belgium',
'belize' => 'Belize',
'benin' => 'Benin',
'bermuda' => 'Bermuda',
'bhutan' => 'Bhutan',
'bolivia-plurinational-state-of' => 'Bolivia, Plurinational State of',
'bonaire-sint-eustatius-and-saba' => 'Bonaire, Sint Eustatius and Saba',
'bosnia-and-herzegovina' => 'Bosnia and Herzegovina',
'botswana' => 'Botswana',
'bouvet-island' => 'Bouvet Island',
'brazil' => 'Brazil',
'british-indian-ocean-territory' => 'British Indian Ocean Territory',
'brunei-darussalam' => 'Brunei Darussalam',
'bulgaria' => 'Bulgaria',
'burkina-faso' => 'Burkina Faso',
'burundi' => 'Burundi',
'cambodia' => 'Cambodia',
'cameroon' => 'Cameroon',
'canada' => 'Canada',
'cape-verde' => 'Cape Verde',
'cayman-islands' => 'Cayman Islands',
'central-african-republic' => 'Central African Republic',
'chad' => 'Chad',
'chile' => 'Chile',
'china' => 'China',
'christmas-island' => 'Christmas Island',
'cocos-keeling-islands' => 'Cocos (Keeling) Islands',
'colombia' => 'Colombia',
'comoros' => 'Comoros',
'congo' => 'Congo',
'congo-the-democratic-republic-of-the' => 'Congo, the Democratic Republic of the',
'cook-islands' => 'Cook Islands',
'costa-rica' => 'Costa Rica',
'cote-divoire' => 'Côte d\'Ivoire',
'croatia' => 'Croatia',
'cuba' => 'Cuba',
'curacao' => 'Curaçao',
'cyprus' => 'Cyprus',
'czech-republic' => 'Czech Republic',
'denmark' => 'Denmark',
'djibouti' => 'Djibouti',
'dominica' => 'Dominica',
'dominican-republic' => 'Dominican Republic',
'ecuador' => 'Ecuador',
'egypt' => 'Egypt',
'el-salvador' => 'El Salvador',
'equatorial-guinea' => 'Equatorial Guinea',
'eritrea' => 'Eritrea',
'estonia' => 'Estonia',
'ethiopia' => 'Ethiopia',
'falkland-islands-malvinas' => 'Falkland Islands (Malvinas)',
'faroe-islands' => 'Faroe Islands',
'fiji' => 'Fiji',
'finland' => 'Finland',
'france' => 'France',
'french-guiana' => 'French Guiana',
'french-polynesia' => 'French Polynesia',
'french-southern-territories' => 'French Southern Territories',
'gabon' => 'Gabon',
'gambia' => 'Gambia',
'georgia' => 'Georgia',
'germany' => 'Germany',
'ghana' => 'Ghana',
'gibraltar' => 'Gibraltar',
'greece' => 'Greece',
'greenland' => 'Greenland',
'grenada' => 'Grenada',
'guadeloupe' => 'Guadeloupe',
'guam' => 'Guam',
'guatemala' => 'Guatemala',
'guernsey' => 'Guernsey',
'guinea' => 'Guinea',
'guinea-bissau' => 'Guinea-Bissau',
'guyana' => 'Guyana',
'haiti' => 'Haiti',
'heard-island-and-mcdonald-islands' => 'Heard Island and McDonald Islands',
'holy-see-vatican-city-state' => 'Holy See (Vatican City State)',
'honduras' => 'Honduras',
'hong-kong' => 'Hong Kong',
'hungary' => 'Hungary',
'iceland' => 'Iceland',
'india' => 'India',
'indonesia' => 'Indonesia',
'iran-islamic-republic-of' => 'Iran, Islamic Republic of',
'iraq' => 'Iraq',
'ireland' => 'Ireland',
'isle-of-man' => 'Isle of Man',
'israel' => 'Israel',
'italy' => 'Italy',
'jamaica' => 'Jamaica',
'japan' => 'Japan',
'jersey' => 'Jersey',
'jordan' => 'Jordan',
'kazakhstan' => 'Kazakhstan',
'kenya' => 'Kenya',
'kiribati' => 'Kiribati',
'korea-democratic-peoples-republic-of' => 'Korea, Democratic People\'s Republic of',
'korea-republic-of' => 'Korea, Republic of',
'kuwait' => 'Kuwait',
'kyrgyzstan' => 'Kyrgyzstan',
'lao-peoples-democratic-republic' => 'Lao People\'s Democratic Republic',
'latvia' => 'Latvia',
'lebanon' => 'Lebanon',
'lesotho' => 'Lesotho',
'liberia' => 'Liberia',
'libya' => 'Libya',
'liechtenstein' => 'Liechtenstein',
'lithuania' => 'Lithuania',
'luxembourg' => 'Luxembourg',
'macao' => 'Macao',
'macedonia-the-former-yugoslav-republic-of' => 'Macedonia, the Former Yugoslav Republic of',
'madagascar' => 'Madagascar',
'malawi' => 'Malawi',
'malaysia' => 'Malaysia',
'maldives' => 'Maldives',
'mali' => 'Mali',
'malta' => 'Malta',
'marshall-islands' => 'Marshall Islands',
'martinique' => 'Martinique',
'mauritania' => 'Mauritania',
'mauritius' => 'Mauritius',
'mayotte' => 'Mayotte',
'mexico' => 'Mexico',
'micronesia-federated-states-of' => 'Micronesia, Federated States of',
'moldova-republic-of' => 'Moldova, Republic of',
'monaco' => 'Monaco',
'mongolia' => 'Mongolia',
'montenegro' => 'Montenegro',
'montserrat' => 'Montserrat',
'morocco' => 'Morocco',
'mozambique' => 'Mozambique',
'myanmar' => 'Myanmar',
'namibia' => 'Namibia',
'nauru' => 'Nauru',
'nepal' => 'Nepal',
'netherlands' => 'Netherlands',
'new-caledonia' => 'New Caledonia',
'new-zealand' => 'New Zealand',
'nicaragua' => 'Nicaragua',
'niger' => 'Niger',
'nigeria' => 'Nigeria',
'niue' => 'Niue',
'norfolk-island' => 'Norfolk Island',
'northern-mariana-islands' => 'Northern Mariana Islands',
'norway' => 'Norway',
'oman' => 'Oman',
'pakistan' => 'Pakistan',
'palau' => 'Palau',
'palestine-state-of' => 'Palestine, State of',
'panama' => 'Panama',
'papua-new-guinea' => 'Papua New Guinea',
'paraguay' => 'Paraguay',
'peru' => 'Peru',
'philippines' => 'Philippines',
'pitcairn' => 'Pitcairn',
'poland' => 'Poland',
'portugal' => 'Portugal',
'puerto-rico' => 'Puerto Rico',
'qatar' => 'Qatar',
'reunion' => 'Réunion',
'romania' => 'Romania',
'russian-federation' => 'Russian Federation',
'rwanda' => 'Rwanda',
'saint-barthelemy' => 'Saint Barthélemy',
'saint-helena-ascension-and-tristan-da-cunha' => 'Saint Helena, Ascension and Tristan da Cunha',
'saint-kitts-and-nevis' => 'Saint Kitts and Nevis',
'saint-lucia' => 'Saint Lucia',
'saint-martin-french-part' => 'Saint Martin (French part)',
'saint-pierre-and-miquelon' => 'Saint Pierre and Miquelon',
'saint-vincent-and-the-grenadines' => 'Saint Vincent and the Grenadines',
'samoa' => 'Samoa',
'san-marino' => 'San Marino',
'sao-tome-and-principe' => 'Sao Tome and Principe',
'saudi-arabia' => 'Saudi Arabia',
'senegal' => 'Senegal',
'serbia' => 'Serbia',
'seychelles' => 'Seychelles',
'sierra-leone' => 'Sierra Leone',
'singapore' => 'Singapore',
'sint-maarten-dutch-part' => 'Sint Maarten (Dutch part)',
'slovakia' => 'Slovakia',
'slovenia' => 'Slovenia',
'solomon-islands' => 'Solomon Islands',
'somalia' => 'Somalia',
'south-africa' => 'South Africa',
'south-georgia-and-the-south-sandwich-islands' => 'South Georgia and the South Sandwich Islands',
'south-sudan' => 'South Sudan',
'spain' => 'Spain',
'sri-lanka' => 'Sri Lanka',
'sudan' => 'Sudan',
'suriname' => 'Suriname',
'svalbard-and-jan-mayen' => 'Svalbard and Jan Mayen',
'swaziland' => 'Swaziland',
'sweden' => 'Sweden',
'switzerland' => 'Switzerland',
'syrian-arab-republic' => 'Syrian Arab Republic',
'taiwan-province-of-china' => 'Taiwan, Province of China',
'tajikistan' => 'Tajikistan',
'tanzania-united-republic-of' => 'Tanzania, United Republic of',
'thailand' => 'Thailand',
'timor-leste' => 'Timor-Leste',
'togo' => 'Togo',
'tokelau' => 'Tokelau',
'tonga' => 'Tonga',
'trinidad-and-tobago' => 'Trinidad and Tobago',
'tunisia' => 'Tunisia',
'turkey' => 'Turkey',
'turkmenistan' => 'Turkmenistan',
'turks-and-caicos-islands' => 'Turks and Caicos Islands',
'tuvalu' => 'Tuvalu',
'uganda' => 'Uganda',
'ukraine' => 'Ukraine',
'united-arab-emirates' => 'United Arab Emirates',
'united-kingdom' => 'United Kingdom',
'united-states' => 'United States',
'united-states-minor-outlying-islands' => 'United States Minor Outlying Islands',
'uruguay' => 'Uruguay',
'uzbekistan' => 'Uzbekistan',
'vanuatu' => 'Vanuatu',
'venezuela-bolivarian-republic-of' => 'Venezuela, Bolivarian Republic of',
'viet-nam' => 'Viet Nam',
'virgin-islands-british' => 'Virgin Islands, British',
'virgin-islands-us' => 'Virgin Islands, U.S.',
'wallis-and-futuna' => 'Wallis and Futuna',
'western-sahara' => 'Western Sahara',
'yemen' => 'Yemen',
'zambia' => 'Zambia',
'zimbabwe' => 'Zimbabwe'
];
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self select()
* @method static self radio()
* @method static self checkbox()
*
* @package LDCCrmClient
* @subpackage enums
*/
class QuestionType extends BaseEnum
{
public static function values(): array
{
return [
'select' => 1,
'radio' => 2,
'checkbox' => 3
];
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace App\Exceptions\Api\V2;
use App\Enums\ApiError;
use Exception;
use Illuminate\Support\Facades\Log;
use Throwable;
/**
* This would be thrown if any of the case
* referenced in \App\Enums\ApiError would
* occur within the crm
*
* On the client an ApiErrorException would
* also be throw with the ApiError corresponding to
* the situation
*
* @package LDCCrmClient
* @subpackage exceptions
*/
class ApiErrorException extends Exception
{
/**
* Api Error
* This value makes the error more explicit.
* Handling these errors are normal as inputs or
* request may be undesired or refused by the crm.
*
* @var \App\Enums\ApiError
*/
protected $error;
/**
* This is an explicit error message,
* ou may print this to the front or not depending
* on $error value
*
* @var string
*/
protected $message;
/**
* This is the http code returned by the crm.
* This value could be used to handle errors.
* But please take in consideration $error first
*
* @var int
*/
protected $httpCode;
public function __construct(ApiError $error, int $httpCode = 400, string $message = '', Throwable $previous = null)
{
$this->error = $error;
$this->message = $message;
$this->httpCode = $httpCode;
parent::__construct(
strlen($this->message) ? $this->message : $error->label,
$error->value,
$previous
);
}
/**
* Get the Api Error
*
* @return ApiError
*/
public function getApiError(): ApiError
{
return $this->error;
}
/**
* Get the http code
*
* @return int
*/
public function getStatusCode(): int
{
return $this->httpCode;
}
public function report()
{
Log::debug(sprintf('CRM API: %s', $this->getMessage()));
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Exceptions\Api\V2;
use Exception;
use Illuminate\Support\Facades\Log;
/**
* This is a critical error.
* The request could not be handled. You may dump any value you can
* get before submitting a ticker to https://supportweb.ldc.fr
*
* @package LDCCrmClient
* @subpackage exceptions
*/
class CrmException extends Exception
{
public function report()
{
Log::critical(sprintf('CRM API: %s', $this->getMessage()));
}
}

View file

@ -2,17 +2,35 @@
namespace App\Exceptions;
use App\Enums\ApiError;
use App\Enums\ApiStatus;
use App\Exceptions\Api\V2\ApiErrorException;
use App\Exceptions\Api\V2\CrmException;
use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse;
use App\Lib\Helper;
use App\Mail\ExceptionAlert;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
ApiErrorException::class
];
/**
@ -34,4 +52,143 @@ class Handler extends ExceptionHandler
{
//
}
/**
* Report or log an exception.
*
* @param \Throwable $exception
* @return void
*
* @throws \Exception
*/
public function report(Throwable $e)
{
if (get_class($e) == RedirectTo::class) {
abort(redirect($e->url));
}
try {
if (!config('app.debug')) {
if ($this->shouldntReport($e)) {
return;
}
Mail::send(new ExceptionAlert($e));
}
} finally {
parent::report($e);
}
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Throwable $e
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Throwable
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
public function render($request, Throwable $e)
{
if (Helper::isRequestForApiV2($request) and $request->isJson()) {
switch (get_class($e)) {
case AuthenticationException::class:
/** @var AuthenticationException $e */
return $this->getReponse(
$request,
407,
ApiError::needAuth()->value,
sprintf('%s, %s', ApiError::needAuth()->label, $e->getMessage())
);
case MethodNotAllowedHttpException::class:
/** @var MethodNotAllowedHttpException $e */
return $this->getReponse(
$request,
$e->getStatusCode(),
ApiError::methodNotAllowed()->value,
sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage())
);
case ValidationException::class:
/** @var ValidationException $e */
return $this->getReponse(
$request,
$e->status,
ApiError::validationFailed()->value,
sprintf(ApiError::validationFailed()->label, $e->getMessage()),
['errors' => $e->errors()]
);
case ModelNotFoundException::class:
/** @var ModelNotFoundException $e */
return $this->getReponse(
$request,
404,
ApiError::modelNotFound()->value,
sprintf(
ApiError::modelNotFound()->label,
\array_slice(explode('\\', $e->getModel()), -1)[0]
)
);
case ApiErrorException::class:
/** @var ApiErrorException $e */
return $this->getReponse(
$request,
$e->getStatusCode(),
$e->getCode(),
$e->getMessage()
);
case ThrottleRequestsException::class:
/** @var ThrottleRequestsException $e */
return $this->getReponse(
$request,
429,
ApiError::throttleRequestsException()->value,
ApiError::throttleRequestsException()->label
);
case AuthorizationException::class:
/** @var AuthorizationException $e */
return $this->getReponse(
$request,
403,
ApiError::authorizationException()->value,
ApiError::authorizationException()->label
);
case CrmException::class:
default:
if (config('app.env') === 'testing') {
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
print_r((string)$e);
}
return $this->getReponse($request, 500, $e->getCode(), $e->getMessage());
}
}
return parent::render($request, $e);
}
/**
* Generate an ApiV2 json response
*
* @param Request $request
* @param integer $httpCode
* @param integer $apiCode
* @param string $message
* @return JsonResponse
*/
private static function getReponse(
Request $request,
int $httpCode,
int $apiCode,
string $message,
array $data = []
): JsonResponse {
return (new V2ApiResponse(
(object)array_merge(
$data,
[
'message' => $message,
'code' => $apiCode
]
),
ApiStatus::KO()
))->toResponse($request)->setStatusCode($httpCode);
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api\v1;
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\StatusResource;
@ -24,45 +24,58 @@ class ArcadiaController extends Controller
'shipping' => 'required_if:type,default|json'
]);
if ($validator->fails())
return new StatusResource(['status' => 'KO', 'code' => 201, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO',
'code' => 201,
'message' => 'Error on required fields.',
'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$crmId = $request->crm_id;
$fields = json_decode($request->fields);
$type = $request->type;
$brandId = $request->brand_id;
$crmId = $request->crm_id;
$fields = json_decode($request->fields);
$type = $request->type;
$shipping = $type === 'default' ? json_decode($request->shipping) : false;
$customer = Customer::where('id', $crmId)->first();
if (!$customer)
if (!$customer) {
return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']);
}
$customerInfos = $customer->infos()->where('brand_id', $brandId)->first();
$CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
if (!$customerInfos)
if (!$CustomerInfo) {
return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']);
}
$check = $this->checkRequiredFields($fields, $type);
if ($check['status'] !== 'OK')
if ($check['status'] !== 'OK') {
return new StatusResource($check);
}
if ($type === 'default') {
$check = $this->checkShippingRequiredFields($shipping);
if ($check['status'] !== 'OK')
if ($check['status'] !== 'OK') {
return new StatusResource($check);
}
}
$xml = $this->makeXML($fields, $type, $customer, $customerInfos, $shipping);
$xml = $this->makeXml($fields, $type, $customer, $CustomerInfo, $shipping);
try {
Storage::disk('arcadia')->put(date ("Ymd_His") . microtime(true) . ".xml", $xml);
Storage::disk('arcadia')->put(date("Ymd_His") . microtime(true) . ".xml", $xml);
return new StatusResource(['status' => 'OK']);
}catch (\Exception $e){
} catch (\Exception $e) {
Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage());
Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError("ARCADIA XML CREATION FAILED", "Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(), $xml));
Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError(
"ARCADIA XML CREATION FAILED",
"Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(),
$xml
));
return new StatusResource(['status' => 'KO', 'code' => 204, 'message' => 'Arcadia XML Creation failed.']);
}
}
private function checkShippingRequiredFields($fields)
@ -76,91 +89,112 @@ class ArcadiaController extends Controller
];
foreach ($test as $t) {
if (!array_key_exists($t, $fields)){
if (!array_key_exists($t, $fields)) {
$errors[] = ['field' => $t, 'error' => 'required'];
}
}
if (!empty($errors))
if (!empty($errors)) {
return ['status' => 'KO', 'code' => 203, 'message' => $errors];
}
return ['status' => 'OK'];
}
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
private function checkRequiredFields($fields, $type)
{
$errors = [];
$key = array_search('operation', array_column($fields->root, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'operation', 'error' => 'required'];
}
$key = array_search('support_code', array_column($fields->orderLine, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'support_code', 'error' => 'required'];
}
$key = array_search('label_type_support', array_column($fields->orderLine, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'label_type_support', 'error' => 'required'];
}
$key = array_search('order_date', array_column($fields->root, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'order_date', 'error' => 'required'];
}
if (!preg_match("/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/", $fields->root[$key]->fieldValue)) {
$regex = join('', [
'/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|',
'((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))',
'|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]',
'|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/'
]);
if (!preg_match($regex, $fields->root[$key]->fieldValue)) {
$errors[] = ['field' => 'order_date', 'error' => 'format must be DD/MM/YYYY'];
}
if ($type === 'default'){
if ($type === 'default') {
$key = array_search('order_num', array_column($fields->root, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'order_num', 'error' => 'required for default type'];
}
if (!is_numeric($fields->root[$key]->fieldValue) || strlen($fields->root[$key]->fieldValue) !== 9)
if (!is_numeric($fields->root[$key]->fieldValue) || strlen($fields->root[$key]->fieldValue) !== 9) {
$errors[] = ['field' => 'order_num', 'error' => 'format must be 9 numeric characters'];
}
$key = array_search('state_order_cb', array_column($fields->root, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'state_order_cb', 'error' => 'required for default type'];
}
if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null')
if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') {
$errors[] = ['field' => 'state_order_cb', 'error' => 'value must be 1 or Null (Yes Null not NULL)'];
}
$orderCB = $fields->root[$key]->fieldValue;
$key = array_search('state_order_check', array_column($fields->root, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'state_order_check', 'error' => 'required for default type'];
}
if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null')
if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') {
$errors[] = ['field' => 'state_order_check', 'error' => 'value must be 1 or Null (Yes Null not NULL)'];
}
$orderCheck = $fields->root[$key]->fieldValue;
if ($orderCB === $orderCheck)
$errors[] = ['field' => 'state_order', 'error' => 'state_order_cb and state_order_check values must be different'];
if ($orderCB === $orderCheck) {
$errors[] = [
'field' => 'state_order',
'error' => 'state_order_cb and state_order_check values must be different'
];
}
$key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'package_unit_weight', 'error' => 'required for default type'];
}elseif ($type === 'game'){
}
} elseif ($type === 'game') {
$key = array_search('code_unique', array_column($fields->orderLine, 'fieldName'));
if ($key === false)
if ($key === false) {
$errors[] = ['field' => 'code_unique', 'error' => 'required for game type'];
}
}
if (!empty($errors))
if (!empty($errors)) {
return ['status' => 'KO', 'code' => 203, 'message' => $errors];
}
return ['status' => 'OK'];
}
private function makeXML($fields, $type, $customer, $customerInfos, $shipping)
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
private function makeXml($fields, $type, $customer, $customerInfos, $shipping)
{
$pivot = [
'order_num' => 'Num_Commande', // required if type = default - format 9 numeric characters
@ -172,12 +206,14 @@ class ArcadiaController extends Controller
'qty' => 'Quantite',
'code_on_pack' => 'Code_on_Pack',
'code_unique' => 'Code_Unique', // required if type = game
'state_order_cb' => 'Etat_Commande_CB', // required if type = default - format 1 ou Null (oui Null et pas NULL)
'state_order_check' => 'Etat_Commande_Cheque', // required if type = default - format 1 ou Null (oui Null et pas NULL)
// required if type = default - format 1 ou Null (oui Null et pas NULL)
'state_order_cb' => 'Etat_Commande_CB',
// required if type = default - format 1 ou Null (oui Null et pas NULL)
'state_order_check' => 'Etat_Commande_Cheque',
];
$dom = new DOMDocument('1.0','UTF-8');
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$root = $dom->createElement('export_arcadia');
@ -185,74 +221,109 @@ class ArcadiaController extends Controller
$order = $root->appendChild($dom->createElement('Commande'));
$key = array_search('order_num', array_column($fields->root, 'fieldName'));
if ($key === false)
if ($key === false) {
$order->appendChild($dom->createElement('Num_Commande'));
else
} else {
$order->appendChild($dom->createElement('Num_Commande', $fields->root[$key]->fieldValue));
}
foreach ($fields->root as $value){
if (isset($pivot[$value->fieldName]) && $value->fieldName !== 'state_order_cb' && $value->fieldName !== 'state_order_check' && $value->fieldName !== 'order_num')
foreach ($fields->root as $value) {
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
isset($pivot[$value->fieldName]) && $value->fieldName !== 'state_order_cb' &&
$value->fieldName !== 'state_order_check' && $value->fieldName !== 'order_num'
) {
$order->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue));
}
}
if ($type === 'default') {
$key = array_search('state_order_cb', array_column($fields->root, 'fieldName'));
if ($key)
if ($key) {
$order->appendChild($dom->createElement('Etat_Commande_CB', $fields->root[$key]->fieldValue));
}
$key = array_search('state_order_check', array_column($fields->root, 'fieldName'));
if ($key)
if ($key) {
$order->appendChild($dom->createElement('Etat_Commande_Cheque', $fields->root[$key]->fieldValue));
}
}
$order->appendChild($dom->createElement('Email_conso'))->appendChild($dom->createCDATASection( "$customer->email"));
$order->appendChild($dom->createElement('Email_conso'))
->appendChild($dom->createCDATASection("$customer->email"));
$order->appendChild($dom->createElement('Civilite'));
if ($type === 'default') {
$order->appendChild($dom->createElement('Nom'))->appendChild($dom->createCDATASection("$shipping->lastname"));
$order->appendChild($dom->createElement('Prenom'))->appendChild($dom->createCDATASection("$shipping->firstname"));
$order->appendChild($dom->createElement('Adresse'))->appendChild($dom->createCDATASection("$shipping->address"));
$order->appendChild($dom->createElement('Nom'))
->appendChild($dom->createCDATASection("$shipping->lastname"));
$order->appendChild($dom->createElement('Prenom'))
->appendChild($dom->createCDATASection("$shipping->firstname"));
$order->appendChild($dom->createElement('Adresse'))
->appendChild($dom->createCDATASection("$shipping->address"));
$order->appendChild($dom->createElement('Adresse_Complementaire'));
$order->appendChild($dom->createElement('CP'))->appendChild($dom->createCDATASection("$shipping->postcode"));
$order->appendChild($dom->createElement('CP'))
->appendChild($dom->createCDATASection("$shipping->postcode"));
$order->appendChild($dom->createElement('Ville'))->appendChild($dom->createCDATASection("$shipping->city"));
if (isset($shipping->phone) && !empty($shipping->phone)) {
$order->appendChild($dom->createElement('Telephone'))->appendChild($dom->createCDATASection("$shipping->phone"));
}else{
$order->appendChild($dom->createElement('Telephone'))
->appendChild($dom->createCDATASection("$shipping->phone"));
} else {
$order->appendChild($dom->createElement('Telephone'));
}
} else {
$order->appendChild($dom->createElement('Nom'))->appendChild($dom->createCDATASection( "$customerInfos->lastname"));
$order->appendChild($dom->createElement('Prenom'))->appendChild($dom->createCDATASection( "$customerInfos->firstname"));
$order->appendChild($dom->createElement('Adresse'))->appendChild($dom->createCDATASection( "$customerInfos->address"));
$order->appendChild($dom->createElement('Adresse_Complementaire'))->appendChild($dom->createCDATASection( "$customerInfos->address2"));
$order->appendChild($dom->createElement('CP'))->appendChild($dom->createCDATASection( "$customerInfos->postcode"));
$order->appendChild($dom->createElement('Ville'))->appendChild($dom->createCDATASection( "$customerInfos->city"));
$order->appendChild($dom->createElement('Telephone'))->appendChild($dom->createCDATASection( "$customerInfos->phone"));
$order->appendChild($dom->createElement('Nom'))
->appendChild($dom->createCDATASection("$customerInfos->lastname"));
$order->appendChild($dom->createElement('Prenom'))
->appendChild($dom->createCDATASection("$customerInfos->firstname"));
$order->appendChild($dom->createElement('Adresse'))
->appendChild($dom->createCDATASection("$customerInfos->address"));
$order->appendChild($dom->createElement('Adresse_Complementaire'))
->appendChild($dom->createCDATASection("$customerInfos->address2"));
$order->appendChild($dom->createElement('CP'))
->appendChild($dom->createCDATASection("$customerInfos->postcode"));
$order->appendChild($dom->createElement('Ville'))
->appendChild($dom->createCDATASection("$customerInfos->city"));
$order->appendChild($dom->createElement('Telephone'))
->appendChild($dom->createCDATASection("$customerInfos->phone"));
}
$orderLine = $order->appendChild($dom->createElement('Ligne_de_Commande'));
$key = array_search('qty', array_column($fields->orderLine, 'fieldName'));
if ($key === false)
if ($key === false) {
$orderLine->appendChild($dom->createElement('Quantite', '1'));
}
$key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName'));
if ($key === false)
if ($key === false) {
$orderLine->appendChild($dom->createElement('Poids_Unitaire_Colis'));
}
foreach ($fields->orderLine as $value){
if (isset($pivot[$value->fieldName]) && $value->fieldName !== 'code_on_pack' && $value->fieldName !== 'code_unique')
foreach ($fields->orderLine as $value) {
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
isset($pivot[$value->fieldName]) &&
$value->fieldName !== 'code_on_pack' &&
$value->fieldName !== 'code_unique'
) {
$orderLine->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue));
}
}
if ($type === 'game') {
$key = array_search('code_on_pack', array_column($fields->orderLine, 'fieldName'));
if ($key)
$orderLine->appendChild($dom->createElement('Code_on_Pack'))->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
if ($key) {
$orderLine->appendChild(
$dom->createElement('Code_on_Pack')
)->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
}
$key = array_search('code_unique', array_column($fields->orderLine, 'fieldName'));
if ($key)
$orderLine->appendChild($dom->createElement('Code_Unique'))->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
if ($key) {
$orderLine->appendChild(
$dom->createElement('Code_Unique')
)->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
}
}
return $dom->saveXML();

View file

@ -1,12 +1,12 @@
<?php
namespace App\Http\Controllers\Api\v1;
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Resources\CustomerResource;
use App\Http\Resources\StatusResource;
use App\Models\Customer;
use App\Models\CustomerInfos;
use App\Models\CustomerInfo;
use App\Models\CustomerLogin;
use App\Models\CustomerOptInHistory;
use App\Models\SingleOptin;
@ -35,68 +35,84 @@ class CustomerController extends Controller
'birth_date' => 'date_format:Y-m-d'
]);
if ($validator->fails())
return new StatusResource(['status' => 'KO', 'code' => 102, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO', 'code' => 102,
'message' => 'Error on required fields.', 'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$brandId = $request->brand_id;
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING);
$lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING);
$optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING);
$hashcode = Str::uuid();
$lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING);
$optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING);
$hashcode = Str::uuid();
// Récupère le customer s'il existe déjà
$customer = Customer::where('email', $email)->first();
// Si le customer n'existe pas déjà
if (!$customer) {
$customer = new Customer();
$customer = new Customer();
$customer->email = strtolower($email);
$customer->save();
$singleOptin = SingleOptin::where([['email', $email], ['brand_id', $brandId]])->first();
if ($singleOptin)
if ($singleOptin) {
$singleOptin->delete();
}
} else {
// Récupère les infos s'ils existent déjà
$customerInfos = CustomerInfos::where([['customer_id', $customer->id], ['brand_id', $brandId]])->first();
if ($customerInfos)
return new StatusResource(['status' => 'KO', 'code' => 103, 'message' => 'User already registered on this site.']);
$CustomerInfo = CustomerInfo::where([['customer_id', $customer->id], ['brand_id', $brandId]])->first();
if ($CustomerInfo) {
return new StatusResource([
'status' => 'KO', 'code' => 103,
'message' => 'User already registered on this site.'
]);
}
}
$customerInfos = new CustomerInfos();
$customerInfos->firstname = ucfirst(strtolower($firstname));
$customerInfos->lastname = ucfirst(strtolower($lastname));
$customerInfos->birth_date = !empty($request->birth_date) ? filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null;
$customerInfos->address = !empty($request->address) ? filter_var($request->address, FILTER_SANITIZE_STRING) : null;
$customerInfos->address2 = !empty($request->address2) ? filter_var($request->address2, FILTER_SANITIZE_STRING) : null;
$customerInfos->phone = !empty($request->phone) ? filter_var($request->phone, FILTER_SANITIZE_STRING) : null;
$customerInfos->country = !empty($request->country) ? filter_var($request->country, FILTER_SANITIZE_STRING) : null;
$customerInfos->city = !empty($request->city) ? filter_var($request->city, FILTER_SANITIZE_STRING) : null;
$customerInfos->postcode = !empty($request->postcode) ? filter_var($request->postcode, FILTER_SANITIZE_STRING) : null;
$customerInfos->opt_in = $optIn;
$customerInfos->hashcode = $hashcode;
$customerInfos->brand_id = $brandId;
$customerInfos = new CustomerInfo();
$customerInfos->firstname = ucfirst(strtolower($firstname));
$customerInfos->lastname = ucfirst(strtolower($lastname));
$customerInfos->birth_date = !empty($request->birth_date) ?
filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null;
$customerInfos->address = !empty($request->address) ?
filter_var($request->address, FILTER_SANITIZE_STRING) : null;
$customerInfos->address2 = !empty($request->address2) ?
filter_var($request->address2, FILTER_SANITIZE_STRING) : null;
$customerInfos->phone = !empty($request->phone) ?
filter_var($request->phone, FILTER_SANITIZE_STRING) : null;
$customerInfos->country = !empty($request->country) ?
filter_var($request->country, FILTER_SANITIZE_STRING) : null;
$customerInfos->city = !empty($request->city) ?
filter_var($request->city, FILTER_SANITIZE_STRING) : null;
$customerInfos->postcode = !empty($request->postcode) ?
filter_var($request->postcode, FILTER_SANITIZE_STRING) : null;
$customerInfos->opt_in = $optIn;
$customerInfos->hashcode = $hashcode;
$customerInfos->brand_id = $brandId;
$customerInfos->customer_id = $customer->id;
$customer->infos()->save($customerInfos);
// Création du login pour cet utilisateur sur ce service
$customerLogin = new CustomerLogin();
$customerLogin->customer_id = $customer->id;
$customerLogin->password = Hash::make(trim($request->password));
$customerLogin = new CustomerLogin();
$customerLogin->customer_id = $customer->id;
$customerLogin->password = Hash::make(trim($request->password));
$customerLogin->last_connection = now();
$customerLogin->brand_id = $brandId;
$customerLogin->status = 0;
$customerLogin->brand_id = $brandId;
$customerLogin->status = 0;
// Par défaut à 0 pour activation par mail
//$customerLogin->status = $request->status !== null ? 1 : 0;
$customer->logins()->save($customerLogin);
// Ajout dans la table d'historique des opt-in de la nouvelle valeur
$optInHistory = new CustomerOptInHistory();
$optInHistory = new CustomerOptInHistory();
$optInHistory->customer_id = $customer->id;
$optInHistory->brand_id = $brandId;
$optInHistory->new_value = $optIn;
$optInHistory->brand_id = $brandId;
$optInHistory->new_value = $optIn;
$optInHistory->save();
return new CustomerResource($customer, $brandId);
@ -112,20 +128,23 @@ class CustomerController extends Controller
{
$brandId = $request->brand_id;
if ($type === 'id'){
if ($type === 'id') {
$customer = Customer::find($value);
if (!$customer)
if (!$customer) {
return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
}else if($type === 'email'){
}
} elseif ($type === 'email') {
$customer = Customer::where('email', $value)->first();
if (!$customer)
if (!$customer) {
return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
}
$customerInfos = $customer->infos()->where('brand_id', $brandId)->first();
$CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
if (!$customerInfos)
if (!$CustomerInfo) {
return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
}else{
}
} else {
return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
}
@ -139,103 +158,112 @@ class CustomerController extends Controller
* @param int $id
* @return CustomerResource|StatusResource
*/
// phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
public function update(Request $request, $id)
{
$validator = Validator::make($request->all(), [
'birth_date' => 'date_format:Y-m-d'
]);
if ($validator->fails())
return new StatusResource(['status' => 'KO', 'code' => 104, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO', 'code' => 104,
'message' => 'Error on required fields.', 'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$brandId = $request->brand_id;
$customer = Customer::find($id);
if (!$customer)
if (!$customer) {
return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']);
}
$customerInfos = $customer->infos()->where('brand_id', $brandId)->first();
if (!$customerInfos)
$CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
if (!$CustomerInfo) {
return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']);
}
if ($request->has('firstname'))
$customerInfos->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING);
if ($request->has('firstname')) {
$CustomerInfo->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING);
}
if ($request->has('lastname'))
$customerInfos->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING);
if ($request->has('lastname')) {
$CustomerInfo->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING);
}
if ($request->has('birth_date')) {
if (!empty($request->birth_date)){
$customerInfos->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING);
}else{
$customerInfos->birth_date = null;
if (!empty($request->birth_date)) {
$CustomerInfo->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->birth_date = null;
}
}
if ($request->has('address')) {
if (!empty($request->address)){
$customerInfos->address = filter_var($request->address, FILTER_SANITIZE_STRING);
}else{
$customerInfos->address = null;
if (!empty($request->address)) {
$CustomerInfo->address = filter_var($request->address, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->address = null;
}
}
if ($request->has('address2')) {
if (!empty($request->address2)){
$customerInfos->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING);
}else{
$customerInfos->address2 = null;
if (!empty($request->address2)) {
$CustomerInfo->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->address2 = null;
}
}
if ($request->has('phone')) {
if (!empty($request->phone)){
$customerInfos->phone = filter_var($request->phone, FILTER_SANITIZE_STRING);
}else{
$customerInfos->phone = null;
if (!empty($request->phone)) {
$CustomerInfo->phone = filter_var($request->phone, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->phone = null;
}
}
if ($request->has('country')) {
if (!empty($request->country)){
$customerInfos->country = filter_var($request->country, FILTER_SANITIZE_STRING);
}else{
$customerInfos->country = null;
if (!empty($request->country)) {
$CustomerInfo->country = filter_var($request->country, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->country = null;
}
}
if ($request->has('city')) {
if (!empty($request->city)){
$customerInfos->city = filter_var($request->city, FILTER_SANITIZE_STRING);
}else{
$customerInfos->city = null;
if (!empty($request->city)) {
$CustomerInfo->city = filter_var($request->city, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->city = null;
}
}
if ($request->has('postcode')) {
if (!empty($request->postcode)){
$customerInfos->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING);
}else{
$customerInfos->postcode = null;
if (!empty($request->postcode)) {
$CustomerInfo->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING);
} else {
$CustomerInfo->postcode = null;
}
}
if ($request->has('opt_in')) {
$optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING);
if ($customerInfos->opt_in != $optIn) {
if ($CustomerInfo->opt_in != $optIn) {
// Ajout dans la table d'historique des opt-in de la nouvelle valeur
$optInHistory = new CustomerOptInHistory();
$optInHistory = new CustomerOptInHistory();
$optInHistory->customer_id = $customer->id;
$optInHistory->brand_id = $brandId;
$optInHistory->new_value = $optIn;
$optInHistory->brand_id = $brandId;
$optInHistory->new_value = $optIn;
$optInHistory->save();
}
$customerInfos->opt_in = $optIn;
$CustomerInfo->opt_in = $optIn;
}
$customerInfos->save();
$CustomerInfo->save();
return new CustomerResource($customer, $brandId);
}
@ -245,17 +273,22 @@ class CustomerController extends Controller
* @param Request $request
* @return CustomerResource|StatusResource
*/
public function authenticate(Request $request) {
public function authenticate(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required',
]);
if ($validator->fails())
return new StatusResource(['status' => 'KO', 'code' => 115, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO', 'code' => 115,
'message' => 'Error on required fields.', 'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$brandId = $request->brand_id;
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$password = trim($request->password);
$customer = Customer::where('email', $email)->first();
if ($customer) {
@ -286,7 +319,8 @@ class CustomerController extends Controller
'hashcode' => $infos->hashcode,
'status' => $login->status,
]
]);
]
);
}
$login->last_connection = now();
@ -310,15 +344,20 @@ class CustomerController extends Controller
* @param Request $request
* @param $id
*/
public function changePassword(Request $request, $id) {
public function changePassword(Request $request, $id)
{
$validator = Validator::make($request->all(), [
'password' => 'required',
]);
if ($validator->fails())
return new StatusResource(['status' => 'KO', 'code' => 107, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO', 'code' => 107,
'message' => 'Error on required fields.', 'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$brandId = $request->brand_id;
$customer = Customer::find($id);
$password = trim($request->password);
@ -326,7 +365,7 @@ class CustomerController extends Controller
$customerLogin = $customer->logins()->where('brand_id', $brandId)->first();
if ($customerLogin) {
// Check if same password
if (Hash::check($password, $customerLogin->password)){
if (Hash::check($password, $customerLogin->password)) {
return new StatusResource(['status' => 'KO', 'code' => 123, 'message' => 'Same password.']);
}
@ -348,7 +387,10 @@ class CustomerController extends Controller
* @param Request $request
* @param $id
*/
public function activateAccount(Request $request, $id) {
public function activateAccount(
Request $request,
$id
) {
$brandId = $request->brand_id;
$customer = Customer::find($id);
@ -379,7 +421,9 @@ class CustomerController extends Controller
* @param Request $request
* @param $id
*/
public function delete(Request $request, $id) {
// phpcs:disable Squiz.PHP.NonExecutableCode.Unreachable
public function delete(Request $request, $id)
{
die("Not implemented yet");
// TODO : A voir pour les stats, obuton dans le bo qui permet de faire les stats sur les customer puis supprimer
// TODO : Gestion avec mesBR
@ -409,7 +453,8 @@ class CustomerController extends Controller
* @param Request $request
* @param $id
*/
public function toggleOptin(Request $request, $id) {
public function toggleOptin(Request $request, $id)
{
// TODO : Liaison des opt-in avec MailJet
$brandId = $request->brand_id;
@ -420,10 +465,10 @@ class CustomerController extends Controller
$infos->opt_in = $infos->opt_in === 0 ? 1 : 0;
// On enregistre l'historique du changement d'optin
$optinHistory = new CustomerOptInHistory();
$optinHistory = new CustomerOptInHistory();
$optinHistory->customer_id = $customer->id;
$optinHistory->new_value = $infos->opt_in;
$optinHistory->brand_id = $brandId;
$optinHistory->new_value = $infos->opt_in;
$optinHistory->brand_id = $brandId;
$optinHistory->save();
$infos->save();
@ -444,11 +489,12 @@ class CustomerController extends Controller
* @param Request $request
* @param $hashcode
*/
public function removeOptin(Request $request, $hashcode) {
public function removeOptin(Request $request, $hashcode)
{
// TODO : Liaison des opt-in avec MailJet
$brandId = $request->brand_id;
$infos = CustomerInfos::where([
$infos = CustomerInfo::where([
['brand_id', $brandId],
['hashcode', $hashcode]
])->first();
@ -457,10 +503,10 @@ class CustomerController extends Controller
$infos->opt_in = 0;
// On enregistre l'historique du changement d'optin
$optinHistory = new CustomerOptInHistory();
$optinHistory = new CustomerOptInHistory();
$optinHistory->customer_id = $infos->customer_id;
$optinHistory->new_value = 0;
$optinHistory->brand_id = $brandId;
$optinHistory->new_value = 0;
$optinHistory->brand_id = $brandId;
$optinHistory->save();
$infos->save();
@ -472,28 +518,41 @@ class CustomerController extends Controller
}
}
public function addSingleOptin(Request $request) {
public function addSingleOptin(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|email',
]);
if ($validator->fails())
return new StatusResource(['status' => 'KO', 'code' => 120, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]);
if ($validator->fails()) {
return new StatusResource([
'status' => 'KO', 'code' => 120,
'message' => 'Error on required fields.', 'errors' => $validator->errors()
]);
}
$brandId = $request->brand_id;
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
$email = filter_var($request->email, FILTER_SANITIZE_EMAIL);
// Check si le customer ou l'optin simple existe avant de l'ajouter en optin simple
if (!SingleOptin::where([['email', $email], ['brand_id', $brandId]])->exists() &&
!CustomerInfos::join("customers", "customer_infos.customer_id", "=", "customers.id")->where([['brand_id', $brandId], ['email', $email]])->exists()) {
$singleOptin = new SingleOptin();
$singleOptin->email = $email;
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
!SingleOptin::where([['email', $email], ['brand_id', $brandId]])->exists() &&
!CustomerInfo::join("customers", "customer_infos.customer_id", "=", "customers.id")->where([
['brand_id', $brandId], ['email', $email]
])->exists()
) {
$singleOptin = new SingleOptin();
$singleOptin->email = $email;
$singleOptin->brand_id = $brandId;
$singleOptin->save();
} else {
// "Cet utilisateur est déjà inscrit à la newsletter pour cette marque"
return new StatusResource(['status' => 'KO', 'code' => 121, 'message' => 'This user is already subscribed to the newsletter for this brand.']);
return new StatusResource([
'status' => 'KO', 'code' => 121,
'message' => 'This user is already subscribed to the newsletter for this brand.'
]);
}
return new StatusResource(['status' => 'OK' , 'message' => 'Optin added.']);
return new StatusResource(['status' => 'OK', 'message' => 'Optin added.']);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Controllers\Api\V2;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V2\StoreCustomerAnswers;
use App\Models\Answer;
use App\Models\Customer;
use App\Models\Service;
use Illuminate\Http\JsonResponse;
class AnswerController extends Controller
{
/**
* Store the customer answers
*
* @param \App\Http\Requests\Api\V2\StoreCustomerAnswers $request
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\JsonResponse
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
public function storeCustomerAnswers(StoreCustomerAnswers $request, Service $service, Customer $customer): JsonResponse
{
$this->authorize('customerBelongsToService', $customer);
$answersID = Answer::whereIn('id', $request->answers)->pluck('id')->toArray();
$customer->answers()->sync($answersID);
return \response()->json();
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Api\V2;
use App\Enums\ApiError;
use App\Exceptions\Api\V2\ApiErrorException;
use App\Http\Controllers\Controller;
use App\Models\Service;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class ApiController extends Controller
{
public function ping()
{
return response()->json();
}
public function authenticate(Request $request)
{
try {
/** @var \App\Models\Service */
$service = Service::getFromBearerToken($request->bearerToken());
} catch (ModelNotFoundException $e) {
throw new ApiErrorException(ApiError::authFailed());
}
if (!Hash::check($request->bearer_pwd, $service->bearer_pwd)) {
throw new ApiErrorException(ApiError::authFailed());
}
$apiToken = $service->generateApiToken();
$service->saveOrFail();
return response()->json(['api_token' => $apiToken]);
}
}

View file

@ -0,0 +1,115 @@
<?php
namespace App\Http\Controllers\Api\V2;
use App\Enums\ArcadiaMailType;
use App\Enums\ArcadiaOrderType;
use App\Exceptions\Api\V2\CrmException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V2\BaseApiRequest;
use App\Http\Requests\Api\V2\SendArchadiaDefaultMailRequest;
use App\Http\Requests\Api\V2\SendArchadiaGameMailRequest;
use App\Mail\CourrierArcadiaError;
use App\Models\Customer;
use App\Models\CustomerInfo;
use App\Models\Service;
use DOMDocument;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
/**
* TODO: séparer ça quand coté arcadia ils auront branché le cerveau
* Ici c'est du sale à cause d'arcadia
* Ils ont fait le choix de rajouter une fonctionnalité
* sur un methode existant en rajoutant des conditions
* sales
*
* Donc un séparation est faite sur les endpoints CRM et dans le client
* pour éviter une confusion de noes utilisateurs
*/
class ArcadiaController extends Controller
{
public function outgoingMailDefault(SendArchadiaDefaultMailRequest $request, Customer $customer, Service $service)
{
$this->authorize('customerBelongsToService', $customer);
return $this->handleArcadiaMail($request, ArcadiaMailType::default(), $customer, $service);
}
public function outgoingMailGame(SendArchadiaGameMailRequest $request, Customer $customer, Service $service)
{
$this->authorize('customerBelongsToService', $customer);
return $this->handleArcadiaMail($request, ArcadiaMailType::game(), $customer, $service);
}
private function handleArcadiaMail(
BaseApiRequest $request,
ArcadiaMailType $type,
Customer $customer,
Service $service
) {
$request = $request->merge([
'order_number' => str_pad(\intval($request->order_number), 9, '0', STR_PAD_LEFT)
]);
$xml = 'none';
try {
/** @var \App\Models\CustomerInfo $customerInfo */
$customerInfo = $customer->info($service->brand)->firstOrFail();
$orderType = ArcadiaOrderType::make(intval($request->state_order));
$xml = $this->makeXml($request, $type, $orderType, $customer, $customerInfo);
// Format xml
$doc = new DOMDocument('1.0', 'utf-8');
$doc->preserveWhiteSpace = true;
$doc->formatOutput = true;
$doc->loadXML($xml);
$xml = $doc->saveXML();
Storage::disk('arcadia')->put(date("Ymd_His") . ".xml", $xml);
return response()->json();
} catch (\Exception $e) {
Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage());
if (!config('app.debug')) {
dispatch(function () use ($xml, $e) {
Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError(
"ARCADIA XML CREATION FAILED",
"Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(),
$xml
));
})->afterResponse();
} else {
throw $e;
}
throw new CrmException('Failed to create the arcadia xml file', 0, $e);
}
}
/**
* render Arcadia xml for order
*
* @param SendArchadiaMailRequest $request
* @param ArcadiaMailType $type
* @param ArcadiaOrderType $orderType
* @param Customer $customer
* @param CustomerInfo $customerInfo
* @return string
*/
private function makeXml(
BaseApiRequest $request,
ArcadiaMailType $type,
ArcadiaOrderType $orderType,
Customer $customer,
CustomerInfo $customerInfo
): string {
return view(
'mail.arcadiaOrders.default',
[
'request' => $request,
'type' => $type,
'orderType' => $orderType,
'customer' => $customer,
'customerInfo' => $customerInfo
]
)->render();
}
}

View file

@ -0,0 +1,211 @@
<?php
namespace App\Http\Controllers\Api\V2;
use App\Enums\ApiError;
use App\Exceptions\Api\V2\ApiErrorException;
use App\Exceptions\Api\V2\CrmException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V2\AuthenticateRequest;
use App\Http\Requests\Api\V2\StoreCustomerRequest;
use App\Http\Requests\Api\V2\UpdateCustomerRequest;
use App\Http\Resources\Api\V2\CustomerResource;
use App\Models\Customer;
use App\Models\Service;
use App\Models\SingleOptin;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\JsonResource;
class CustomerController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \App\Request\StoreCustomerRequest $request
* @param \App\Models\Service $service
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
public function store(StoreCustomerRequest $request, Service $service): JsonResource
{
$data = (object)$request->validated();
/** @var \App\Models\Customer */
$customer = Customer::where('email', $data->email)->firstOrCreate([
'email' => $data->email
]);
$customer->saveOrFail();
$customer->setOptIn($service->brand, $data->opt_in);
// Delete Single optin if exists
if (
$singleOptin = SingleOptin::where('email', $data->email)
->where('brand_id', $service->brand->id)->first()
) {
$singleOptin->delete();
}
/** @var \App\Models\CustomerInfo */
$customerInfo = $customer->info($service->brand)->firstOrNew();
if ($customerInfo->id) {
throw new ApiErrorException(ApiError::customerExists());
}
$customerInfo->fill($request->validated());
$customerInfo->brand()->associate($service->brand);
$customerInfo->customer()->associate($customer);
$customerInfo->saveOrFail();
/** @var \App\Models\CustomerLogin */
$customerLogin = $customer->login($service->brand)->firstOrNew();
$customerLogin->fill($request->validated());
$customerLogin->brand()->associate($service->brand);
$customerLogin->customer()->associate($customer);
$customerLogin->saveOrFail();
return new CustomerResource($service->brand, $customer);
}
/**
* Display the specified resource.
*
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
public function show(Customer $customer, Service $service): JsonResource
{
$this->authorize('customerBelongsToService', $customer);
return new CustomerResource($service->brand, $customer);
}
/**
* Update the specified resource in storage.
*
* @param \App\Request\UpdateCustomerRequest $request
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
public function update(UpdateCustomerRequest $request, Customer $customer, Service $service): JsonResource
{
$this->authorize('customerBelongsToService', $customer);
// To correct with create
$customer->fill($request->all());
if ($customer->saveOrFail() === false) {
throw new CrmException(sprintf('Could not update customer %d', $customer));
}
if ($request->has('opt_in')) {
$customer->setOptIn($service->brand, $request->opt_in);
}
/** @var \App\Models\CustomerInfo */
$customerInfo = $customer->info($service->brand)->firstOrFail();
$customerInfo->fill($request->validated());
$customerInfo->saveOrFail();
/** @var \App\Models\CustomerLogin */
$customerLogin = $customer->login($service->brand)->firstOrFail();
$customerLogin->fill($request->validated());
$customerLogin->saveOrFail();
return new CustomerResource($service->brand, $customer);
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Customer $customer, Service $service): JsonResponse
{
$this->authorize('customerBelongsToService', $customer);
// TODO : A voir pour les stats, obuton dans le bo qui permet de faire les stats sur les customer puis supprimer
// TODO : Gestion avec mesBR
/** @var \App\Models\CustomerInfo */
$customerInfo = $customer->info($service->brand)->firstOrFail();
/** @var \App\Models\CustomerLogin */
$customerLogin = $customer->login($service->brand)->firstOrFail();
if (
!$customerInfo->delete() ||
!$customerLogin->delete() ||
$customer->optInHistory()->whereHas('brand', function ($q) use ($service) {
$q->where('id', $service->brand_id);
})->delete() === false
) {
throw new CrmException(sprintf(
'Could not delete customer %d for brand %d',
$customer->id,
$service->brand->id
));
};
$logins = $customer->logins()->count();
$infos = $customer->infos()->count();
$history = $customer->optInHistory()->count();
// If customer is not associated with any brand then delete
if (!$logins and !$infos and !$history) {
if ($customer->delete() === false) {
throw new CrmException(sprintf(
'Could not delete customer %d',
$customer->id
));
}
}
return response()->json();
}
/**
* Authenticate the user
* @param \App\Models\Service $service
* @param \App\Request\AuthenticateRequest $request
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
public function authenticate(AuthenticateRequest $request, Service $service): JsonResource
{
try {
/** @var \App\Models\Customer */
$customer = Customer::where('email', $request->email)->firstOrFail();
/** @var \App\Models\CustomerLogin */
$customerLogin = $customer->login($service->brand)->firstOrFail();
if (!$customerLogin->passwordMatch($request->password)) {
throw new ApiErrorException(ApiError::authFailed());
}
if ($customerLogin->status === 0) {
throw new ApiErrorException(ApiError::customerNotActivated());
}
$customerLogin->last_connection = now();
$customer->logins()->save($customerLogin);
return new CustomerResource($service->brand, $customer);
} catch (ModelNotFoundException $e) {
throw new ApiErrorException(ApiError::authFailed());
}
}
/**
* Activate the account of the user
* @param Customer $customer
* @param Service $service
* @return \Illuminate\Http\JsonResponse
*/
public function activateAccount(Customer $customer, Service $service)
{
$this->authorize('customerBelongsToService', $customer);
$login = $customer->login($service->brand)->firstOrFail();
if ($login->status === 0) {
$login->status = 1;
$login->save();
return response()->json();
} else {
throw new ApiErrorException(ApiError::customerAlreadyActive());
}
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Api\V2;
use App\Enums\ApiError;
use App\Exceptions\Api\V2\ApiErrorException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V2\SingleOptinRequest;
use App\Models\CustomerInfo;
use App\Models\Service;
use App\Models\SingleOptin;
class OptinController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \App\Request\SingleOptinRequest $request
* @param \App\Models\Service $service
* @return \Illuminate\Http\JsonResponse
*/
public function optin(SingleOptinRequest $request, Service $service)
{
if (CustomerInfo::getFromEmail($service->brand, $request->email)->exists()) {
throw new ApiErrorException(ApiError::singleOptinCustomerExists());
}
/** @var \App\Models\SingleOptin */
$singleOptin = SingleOptin::where('email', $request->email)->firstOrNew();
if ($request->opt_in) {
$singleOptin->fill($request->validated());
$singleOptin->brand()->associate($service->brand);
$singleOptin->saveOrFail();
} else {
$singleOptin->delete();
}
return response()->json();
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Api\V2;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\V2\QuestionsResource;
use App\Models\Answer;
use App\Models\Customer;
use App\Models\Question;
use App\Models\Service;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
class QuestionController extends Controller
{
/**
* Display all specified resources.
*
* @param \App\Models\Service $service
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
public function index(Service $service): ResourceCollection
{
return new QuestionsResource($service->brand->questions);
}
/**
* Display all specified resources.
*
* @param \App\Models\Service $service
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
public function indexWithResponse(Service $service, Customer $customer): ResourceCollection
{
$this->authorize('customerBelongsToService', $customer);
$customerAnswers = Answer::whereHas('customers', function ($q) use ($customer) {
$q->whereId($customer->id);
})->get();
$customerAnswers = Answer::whereHas('customers', function ($q) use ($customer) {
$q->whereId($customer->id);
})->whereHas('question', function ($q) use ($service) {
$q->whereHas('brand', function ($q) use ($service) {
$q->whereId($service->brand->id);
});
})->get();
return new QuestionsResource($service->brand->questions, $customerAnswers);
}
/**
* Display all specified resource by customer.
*
* @param \App\Models\Service $service
* @param \App\Models\Customer $customer
* @return \Illuminate\Http\Resources\Json\JsonResource
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed
public function unAnsweredBy(Service $service, Customer $customer): JsonResource
{
$this->authorize('customerBelongsToService', $customer);
$questions = Question::whereHas('brand', function ($q) use ($service) {
$q->where('id', $service->brand->id);
})->whereDoesntHave('answers', function ($q) use ($customer) {
$q->whereIn('id', Answer::whereHas('customers', function ($q) use ($customer) {
$q->where('id', $customer->id);
})->pluck('id'));
})->with('answers')->get();
return new QuestionsResource($questions);
}
}

View file

@ -3,35 +3,27 @@
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Http\Requests\ManageBrandRequest;
use App\Models\Brand;
use Illuminate\Http\Request;
class BrandController extends Controller
{
public function index() {
public function index()
{
$brands = Brand::all();
return view('brands.index', compact('brands'));
}
public function manage(Request $request, $id = null) {
$brand = null;
if ($id !== null)
$brand = Brand::find($id);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$request->validate([
'name' => 'required|max:255',
]);
public function manage(ManageBrandRequest $request, Brand $brand)
{
if ($request->isMethod('post')) {
if (!$brand) {
$brand = new Brand();
$brand = new Brand($request->validated());
} else {
$brand->fill($request->validated());
}
$brand->name = filter_var($request->name, FILTER_SANITIZE_STRING);
$brand->save();
return redirect()->route('brands')->with('success', 'Modifications enregistrées !');
return redirect()->route('brands')->with('success', trans('Modifications enregistrées !'));
}
return view('brands.manage', compact('brand'));

View file

@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests;
use DispatchesJobs;
use ValidatesRequests;
}

View file

@ -5,42 +5,62 @@ namespace App\Http\Controllers;
use App\Http\Requests\CustomerDeleteRequest;
use App\Models\Brand;
use App\Models\Customer;
use App\Models\CustomerInfos;
use App\Models\CustomerInfo;
use App\Models\CustomerLogin;
class CustomerController extends Controller
{
public function index() {
public function index()
{
$brands = Brand::select('id', 'name')->get();
return view('customers.index', compact('brands'));
}
public function show($id, $brandId) {
$customerInfos = CustomerInfos::where([['customer_id', $id], ['brand_id', $brandId]])->first();
$customerLogin = CustomerLogin::where([['customer_id', $id], ['brand_id', $brandId]])->first();
if (!$customerInfos)
abort(404);
public function show(Customer $customer, Brand $brand)
{
$customerInfos = CustomerInfo::where('customer_id', $customer->id)
->where('brand_id', $brand->id)->firstOrFail();
$customerLogin = CustomerLogin::where('customer_id', $customer->id)
->where('brand_id', $brand->id)->first();
return view('customers.show', compact('customerInfos', 'customerLogin'));
}
public function filter($texte, $brandId = null) {
if ($brandId !== null) {
public function filter($texte, Brand $brand = null)
{
if ($brand) {
$customers = Customer::join('customer_infos', 'customers.id', '=', 'customer_infos.customer_id')
->join('customer_login', 'customers.id', '=', 'customer_login.customer_id')
->select('customer_infos.customer_id', 'customer_infos.firstname', 'customer_infos.lastname', 'customer_infos.opt_in', 'customers.email', 'customer_infos.brand_id', 'customer_login.last_connection')
->where([['lastname', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brandId], ['customer_login.brand_id', $brandId]])
->orWhere([['email', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brandId], ['customer_login.brand_id', $brandId]])
->get();
->join('customer_logins', 'customers.id', '=', 'customer_logins.customer_id')->select(
'customer_infos.customer_id',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customers.email',
'customer_infos.brand_id',
'customer_logins.last_connection'
)->where([
['lastname', 'like', '%' . $texte . '%'],
['customer_infos.brand_id', $brand->id],
['customer_logins.brand_id', $brand->id]
])->orWhere([
['email', 'like', '%' . $texte . '%'],
['customer_infos.brand_id', $brand->id],
['customer_logins.brand_id', $brand->id]
])->get();
} else {
$customers = CustomerInfos::join('customers', 'customers.id', '=', 'customer_infos.customer_id')
->join('customer_login', function($join) {
$join->on('customer_login.customer_id', '=', 'customer_infos.customer_id');
$join->on('customer_login.brand_id', '=', 'customer_infos.brand_id');
})
->select('customer_infos.customer_id', 'customer_infos.firstname', 'customer_infos.lastname', 'customer_infos.opt_in', 'customers.email', 'customer_infos.brand_id', 'customer_login.last_connection')
->where('lastname', 'like', '%' . $texte . '%')
->orWhere('email', 'like', '%' . $texte . '%')
->get();
$customers = CustomerInfo::join('customers', 'customers.id', '=', 'customer_infos.customer_id')
->join('customer_logins', function ($join) {
$join->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
$join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id');
})->select(
'customer_infos.customer_id',
'customer_infos.firstname',
'customer_infos.lastname',
'customer_infos.opt_in',
'customers.email',
'customer_infos.brand_id',
'customer_logins.last_connection'
)->where('lastname', 'like', '%' . $texte . '%')
->orWhere('email', 'like', '%' . $texte . '%')->get();
}
return response()->json($customers);

View file

@ -7,19 +7,29 @@ use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function index() {
public function index()
{
return view('login');
}
public function authenticate(Request $request) {
if (Auth::attempt(["email" => $request->email, "password" => $request->password], $request->remember !== null ? true : false)) {
public function authenticate(Request $request)
{
// phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
if (
Auth::attempt(
["email" => $request->email, "password" => $request->password],
$request->remember !== null ? true : false
)
) {
return redirect()->route('customers');
} else {
return back()->withInput($request->except("password"))->withErrors("Les identifiants entrés sont incorrects, veuillez réessayer.");
return back()->withInput($request->except("password"))
->withErrors("Les identifiants entrés sont incorrects, veuillez réessayer.");
}
}
public function logout() {
public function logout()
{
Auth::logout();
return redirect()->route('login');
}

View file

@ -2,62 +2,49 @@
namespace App\Http\Controllers;
use App\Http\Requests\ManageServiceRequest;
use App\Models\Brand;
use App\Models\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ServiceController extends Controller
{
public function index() {
public function index()
{
$services = Service::all();
return view('services.index', compact('services'));
}
public function manage(Request $request, $id = null) {
$service = null;
public function manage(ManageServiceRequest $request, Service $service = null)
{
$brands = Brand::all();
$edit = false;
if ($id !== null) {
$service = Service::find($id);
$edit = true;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$request->validate([
'name' => 'required|max:255',
]);
$edit = $service ? true : false;
if ($request->isMethod('post')) {
if (!$service) {
$service = new Service();
$apiToken = Str::random(80);
$service->api_token = hash('sha256', $apiToken);
$service->api_token_date = now();
$service = new Service($request->validated());
} else {
$service->fill($request->validated());
}
$service->name = filter_var($request->name, FILTER_SANITIZE_STRING);
$service->brand_id = filter_var($request->brand_id, FILTER_SANITIZE_NUMBER_INT);
$brand = Brand::findOrFail($request->brand_id);
$service->brand()->associate($brand);
$service->save();
return redirect()->route('services')->with('success', $edit ? 'Modifications enregistrées !' : 'Token du service : ' . $apiToken);
return redirect()->route('services')->with('success', $edit ? 'Modifications enregistrées !' : sprintf(
'Token du service : \'%s\'',
$service->bearer_token
));
}
return view('services.manage', compact('service', 'brands', 'edit'));
}
public function generateApiToken($serviceId) {
$service = Service::find($serviceId);
if (!$service)
return back()->with('error', "Génération impossible : Service introuvable.");
$apiToken = Str::random(80);
$service->api_token = hash('sha256', $apiToken);
$service->api_token_date = now();
$service->save();
return back()->with('success', "Nouveau token d'API de " . $service->name . " : " . $apiToken);
public function generateApiToken(Service $service)
{
$bearerToken = $service->generateBearerToken();
$bearerPwd = $service->bearer_pwd;
$service->saveOrFail();
return back()
// phpcs:ignore Generic.Files.LineLength.TooLong
->with('success', "Nouveau token d'API de $service->name : '$bearerToken' et mot de passe '$bearerPwd'");
}
}

View file

@ -8,12 +8,14 @@ use App\Models\SingleOptin;
class SingleOptinsController extends Controller
{
public function index() {
public function index()
{
$brands = Brand::select('id', 'name')->get();
return view('single-optins.index', compact('brands'));
}
public function filter($texte, $brandId = null) {
public function filter($texte, $brandId = null)
{
if ($brandId !== null) {
$customers = SingleOptin::where([['email', 'like', '%' . $texte . '%'], ['brand_id', $brandId]])->get();
} else {
@ -23,11 +25,13 @@ class SingleOptinsController extends Controller
return response()->json($customers);
}
public function delete($id) {
public function delete($id)
{
// TODO : Gestion avec les stats
$optin = SingleOptin::find($id);
if ($optin)
if ($optin) {
$optin->delete();
}
return new StatusResource(['status' => "OK"]);
}
}

View file

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
@ -14,13 +15,13 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\XssSanitizer::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class
];
/**
@ -33,7 +34,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
@ -41,7 +41,10 @@ class Kernel extends HttpKernel
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\JsonOnly::class,
\App\Http\Middleware\ApiResponse::class,
\App\Http\Middleware\ApiCheckTimeStamps::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class
],
];

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ApiError;
use App\Exceptions\Api\V2\ApiErrorException;
use App\Lib\Helper;
use Closure;
use Illuminate\Http\Request;
/**
* Check Http Header Timestamp with request timestamp
* to prevent replay attacks
* https://restfulapi.net/security-essentials/
*/
class ApiCheckTimeStamps
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if (Helper::isRequestForApiV2($request)) {
if (!$request->hasHeader('X-API-TIMESTAMP') or !is_numeric($request->header('X-API-TIMESTAMP'))) {
throw new ApiErrorException(ApiError::wrongTimeStamp());
}
if (!$request->timestamp or !is_numeric($request->timestamp)) {
throw new ApiErrorException(ApiError::wrongTimeStamp());
}
$diff = intval($request->timestamp) - intval($request->header('X-API-TIMESTAMP'));
$maxDiff = 120;
if (abs($diff) > $maxDiff) {
throw new ApiErrorException(ApiError::wrongTimeStamp());
}
}
return $next($request);
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace App\Http\Middleware;
use App\Enums\ApiStatus;
use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse;
use App\Lib\Helper;
use App\Models\Service;
use Closure;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ApiResponse
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
/** @var \Illuminate\Http\JsonResponse $response */
$response = $next($request);
$isApiV1Request = Helper::isRequestForApiV1($request);
$isApiV2Request = Helper::isRequestForApiV2($request);
try {
if ($isApiV1Request) {
// Use patched v1 Auth mecanism (without bearer reimplementation)
$bearerToken = $request->bearerToken();
$service = Service::getFromBearerToken($bearerToken);
$service->incrementStat();
}
if ($isApiV2Request) {
$bearerToken = $request->bearerToken();
if ($bearerToken) {
$service = Service::getFromBearerToken($bearerToken);
}
if ($request->api_token) {
$service = Service::getFromApiToken($request->api_token);
}
if (isset($service)) {
$service->incrementStat();
}
}
} catch (ModelNotFoundException $e) {
Log::error(sprintf('Could not increment stats %s', $e->getMessage()));
}
if ($isApiV1Request) {
return $response;
}
$data = $response->getData();
// Is reponse is already a V2ApiResponse then just return it
if (
isset($data->status) and
isset($data->data) and
isset($data->version) and
in_array($data->status, [ApiStatus::OK()->value, ApiStatus::KO()->value])
) {
return $response;
}
/** @ var \App\Enums\ApiStatus */
$status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO();
return $response->setData(new V2ApiResponse((object)$response->getData(), $status));
}
}

View file

@ -2,10 +2,45 @@
namespace App\Http\Middleware;
use App\Lib\Helper;
use App\Models\Service;
use Closure;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
/**
* Updated for ApiV2
*/
class Authenticate extends Middleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string[] ...$guards
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$guards)
{
// TODO: check this works
if (Helper::isRequestForApiV1($request)) {
// Use patched v1 Auth mecanism (without bearer reimplementation)
$bearerToken = $request->bearerToken() ?? $request->api_token;
$brand = Service::getFromBearerToken($bearerToken)->brand;
$request->request->add(['brand_id' => $brand->id]);
$response = $next($request);
} else {
// Use Laravel auth check mecanism
$response = \call_user_func_array(
[$this, 'parent::' . __FUNCTION__],
array_merge([$request, $next], $guards)
);
}
return $response;
}
/**
* Get the path the user should be redirected to when they are not authenticated.
*

View file

@ -2,17 +2,13 @@
namespace App\Http\Middleware;
use App\Models\Brand;
use App\Models\Service;
use App\Models\ServiceStat;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
class CheckBrand
{
private $apiToken;
/**
* Handle an incoming request.
*
@ -22,25 +18,17 @@ class CheckBrand
*/
public function handle(Request $request, Closure $next)
{
if ($request->bearerToken() !== null)
$this->apiToken = $request->bearerToken();
else
$this->apiToken = ($request->api_token !== null ? $request->api_token : null);
$apiToken = $request->bearerToken() ?? $request->api_token;
if ($this->apiToken === null)
die("Le token d'authentification est incorrect");
$brand = Brand::whereHas('services', function (Builder $query) {
$query->where("api_token", hash('sha256', $this->apiToken));
})->first();
if ($apiToken === null) {
abort(401, 'Le token d\'authentification est incorrect dfd' . \json_encode($request->all()));
}
if (!$brand)
die("Le token d'authentification est incorrect");
$serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]);
$serviceStat->brand_id = $brand->id;
$serviceStat->service_id = Service::where('api_token', hash('sha256', $this->apiToken))->first()->id;
$serviceStat->nb_calls = $serviceStat->nb_calls === 0 ? 1 : $serviceStat->nb_calls + 1;
$serviceStat->save();
try {
$brand = Service::getFromBearerToken($apiToken)->brand;
} catch (ModelNotFoundException $e) {
abort(401, 'Le token d\'authentification est incorrect ' . $e->getMessage());
}
$request->request->add(['brand_id' => $brand->id]);
return $next($request);

View file

@ -6,6 +6,7 @@ use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*

View file

@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use App\Lib\Helper;
use Closure;
use Illuminate\Http\Request;
class JsonOnly
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// TODO reve v1 check when dropped
if (!Helper::isRequestForApiV1($request) and !$request->isJson()) {
abort(400);
}
return $next($request);
}
}

View file

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Mi
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*

View file

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*

View file

@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*

View file

@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
class XssSanitizer
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$input = $request->all();
array_walk_recursive($input, function (&$input) {
$input = strip_tags($input);
});
$request->merge($input);
return $next($request);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Api\V2;
class AuthenticateRequest extends BaseApiRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'email' => 'required|email:rfc|exists:customers,email',
'password' => 'required'
];
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Http\Requests\Api\V2;
use ArondeParon\RequestSanitizer\Sanitizers\Lowercase;
use ArondeParon\RequestSanitizer\Sanitizers\RemoveNonNumeric;
use ArondeParon\RequestSanitizer\Sanitizers\Trim;
use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
use Illuminate\Foundation\Http\FormRequest;
abstract class BaseApiRequest extends FormRequest
{
use SanitizesInputs;
protected $sanitizers = [
'password' => Trim::class,
'email' => [Lowercase::class, Trim::class],
'postcode' => RemoveNonNumeric::class
];
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
abstract public function rules(): array;
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Requests\Api\V2;
use App\Enums\ArcadiaOrderType;
use ArondeParon\RequestSanitizer\Sanitizers\RemoveNonNumeric;
use ArondeParon\RequestSanitizer\Sanitizers\Trim;
use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
class SendArchadiaDefaultMailRequest extends BaseApiRequest
{
use SanitizesInputs;
protected $sanitizers = [
'label_type_support' => Trim::class,
'operation' => Trim::class,
'order_date' => Trim::class,
'order_num' => RemoveNonNumeric::class,
'package_unit_weight' => RemoveNonNumeric::class,
'support_code' => Trim::class,
];
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'label_type_support' => 'required|string',
'operation' => 'required|int',
'order_date' => 'required|date',
'order_num' => 'required|numeric',
'package_unit_weight' => 'required|integer',
'state_order' => [
'required',
new EnumIsValidRule(ArcadiaOrderType::class)
],
'support_code' => 'required|int',
];
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace App\Http\Requests\Api\V2;
use ArondeParon\RequestSanitizer\Sanitizers\Trim;
use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
class SendArchadiaGameMailRequest extends BaseApiRequest
{
use SanitizesInputs;
protected $sanitizers = [
'code_on_pack' => Trim::class,
'code_unique' => Trim::class,
'label_type_support' => Trim::class,
'operation' => Trim::class,
'order_date' => Trim::class,
'support_code' => Trim::class,
'type' => Trim::class
];
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'code_on_pack' => 'sometimes|string',
'code_unique' => 'required|string',
'label_type_support' => 'required|string',
'operation' => 'required|int',
'order_date' => 'required|date',
'support_code' => 'required|int'
];
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Http\Requests\Api\V2;
class SingleOptinRequest extends BaseApiRequest
{
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof,filter',
'opt_in' => 'required|boolean'
];
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Api\V2;
use App\Models\Answer;
use App\Models\Question;
class StoreCustomerAnswers extends BaseApiRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
// resolve Service
$service = app()->make(\App\Models\Service::class);
$brandId = $service->brand->id;
$question = Question::findOrFail($this->question);
$questionTableName = (new Question())->getTable();
$answerTableName = (new Answer())->getTable();
return [
'question' => [
'required',
"exists:$questionTableName,id",
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) use ($question, $brandId) {
if ($question->brand->id !== $brandId) {
$fail("$attribute does not belong to brand $brandId");
}
}
],
'answers' => 'required|array',
'answers.*' => [
'int',
'distinct',
"exists:$answerTableName,id",
function ($attribute, $value, $fail) use ($question) {
if (Answer::findOrFail($value)->question->id !== $question->id) {
$fail("$attribute does not belong to question $question->id");
}
}
]
];
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace App\Http\Requests\Api\V2;
use App\Enums\Country;
use Illuminate\Validation\Rules\Password;
class StoreCustomerRequest extends BaseApiRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'firstname' => 'required|string|min:2',
'lastname' => 'required|string|min:2',
'email' => 'required|email:rfc,dns,spoof,filter',
'password' => [
'required',
Password::min(8)
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised()
],
'birth_date' => 'required|date_format:Y-m-d',
'address' => 'required|string',
'address2' => 'sometimes|string',
'phone' => 'sometimes|string',
'country' => [
'sometimes',
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!\in_array($value, Country::toValues())) {
$fail('notInList');
}
}
],
'city' => 'required|string',
'postcode' => 'required|string',
'opt_in' => 'required|boolean',
];
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Http\Requests\Api\V2;
class StoreSingleOptinRequest extends BaseApiRequest
{
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof,filter'
];
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Http\Requests\Api\V2;
use App\Enums\Country;
use Illuminate\Validation\Rules\Password;
class UpdateCustomerRequest extends BaseApiRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$fields = [
'firstname', 'lastname', 'email', 'password',
'birth_date', 'address', 'phone', 'country',
'city', 'postcode', 'opt_in'
];
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
$atLeast = function ($attribute, $value, $fail) use ($fields) {
if (!\in_array($attribute, $fields)) {
$fail(trans('require_at_least_field', [
'attributes' => rtrim(\implode(',', $fields), ',')
]));
}
};
return [
'firstname' => [$atLeast, 'string', 'min:2'],
'lastname' => [$atLeast, 'string', 'min:2'],
'email' => [$atLeast, 'email:rfc,dns,spoof,filter'],
'password' => [
$atLeast, Password::min(8)
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised()
],
'birth_date' => [$atLeast, 'date_format:Y-m-d'],
'address' => [$atLeast, 'required_with:address2', 'string'],
'address2' => ['sometimes', 'string'],
'phone' => [$atLeast, 'string'],
'country' => [
$atLeast,
'numeric',
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!\in_array($value, Country::toValues())) {
$fail(trans('not_in'));
}
}
],
'city' => [$atLeast, 'string'],
'postcode' => [$atLeast, 'string'],
'opt_in' => [$atLeast, 'boolean'],
];
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Http\Requests\Api\V2;
class UpdateSingleOptinRequest extends BaseApiRequest
{
public function rules(): array
{
return [
'email' => 'required|email:rfc,dns,spoof,filter',
'optin' => 'required|boolean'
];
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Http\Requests;
use ArondeParon\RequestSanitizer\Sanitizers\FilterVars;
use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
use Illuminate\Foundation\Http\FormRequest;
class ManageBrandRequest extends FormRequest
{
use SanitizesInputs;
protected $sanitizers = [
'name' => [
FilterVars::class => [
'filter' => FILTER_SANITIZE_STRING
]
],
];
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
if ($this->isMethod('post')) {
return [
'name' => 'required|string|min:5|max:255'
];
}
return [];
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests;
use ArondeParon\RequestSanitizer\Sanitizers\FilterVars;
use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
use Illuminate\Foundation\Http\FormRequest;
class ManageServiceRequest extends FormRequest
{
use SanitizesInputs;
protected $sanitizers = [
'name' => [
FilterVars::class => [
'filter' => FILTER_SANITIZE_STRING
]
],
];
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
if ($this->isMethod('post')) {
return [
'name' => 'required|string|min:5|max:255',
'brand_id' => 'required|exists:brands,id'
];
}
return [];
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Http\Resources\Api\V2;
use App\Models\Answer;
use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection;
class AnswersResource extends JsonResourceCollection
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request)
{
return $this->collection->map(function (Answer $answer) {
return [
'id' => $answer->id,
'answer' => $answer->answer
];
});
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Resources\Api\V2;
use App\Enums\ApiStatus;
use Illuminate\Http\Resources\Json\JsonResource;
use stdClass;
class ApiResponse extends JsonResource
{
/** @var stdClass */
protected $data;
/** @var \App\Enums\ApiStatus */
protected $status;
/**
* @param stdClass $data
* @param ApiStatus $status
*/
public function __construct(stdClass $data = null, ApiStatus $status)
{
$this->status = $status;
$this->data = $data;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request): array
{
return [
'status' => $this->status->value,
'data' => (array)$this->data,
'version' => config('api.version')
];
}
}

View file

@ -0,0 +1,86 @@
<?php
namespace App\Http\Resources\Api\V2;
use App\Models\Brand;
use App\Models\Customer;
use Illuminate\Http\Resources\Json\JsonResource;
class CustomerResource extends JsonResource
{
/** @var Customer */
private $customer;
/** @var CustomerInfo */
private $info;
/** @var CustomerLogin */
private $login;
private static $skeleton = [
'hashcode' => '',
'firstname' => '',
'lastname' => '',
'email' => '',
'birth_date' => '',
'address' => '',
'address2' => '',
'phone' => '',
'country' => '',
'city' => '',
'postcode' => '',
'opt_in' => '',
'status' => '',
'last_connection' => ''
];
public function __construct(Brand $brand, Customer $customer)
{
$this->customer = $customer;
$this->info = $customer->info($brand)->firstOrFail();
$this->login = $customer->login($brand)->firstOrFail();
}
/**
* Transform the resource into an array.
*
* @return array
* + firstname string
* + lastname string
* + birth_date datetime
* + address string
* + address2 string
* + phone string
* + country string
* + city string
* + postcode string
* + opt_in boolean
* + hashcode string
* + status boolean
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request = null)
{
// patch country TODO remove this when all crm are migrated to v2
$data = array_intersect_key(
array_merge(
[
'email' => $this->customer->email
],
$this->login->toArray(),
$this->info->toArray(),
['status' => $this->login->status]
),
$this->getSkeleton()
);
$data['country'] = $this->info->newcountry->value;
return $data;
}
public static function getSkeleton(): array
{
return self::$skeleton;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Resources\Api\V2;
use App\Models\Question;
use Illuminate\Http\Resources\Json\JsonResource;
class QuestionResource extends JsonResource
{
/** @var Question */
private $question;
public function __construct(Question $question)
{
$this->question = $question;
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request)
{
return [
'id' => $this->question->id,
'question' => $this->question->question,
'answers' => new AnswersResource($this->question->answers)
];
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Http\Resources\Api\V2;
use App\Models\Question;
use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection;
use Illuminate\Support\Collection;
class QuestionsResource extends JsonResourceCollection
{
/** @var \Illuminate\Support\Collection */
protected $userAnwsers = null;
/**
* Create a new resource instance.
*
* @param mixed $resource
* @return void
*/
public function __construct($resource, Collection $userAnwsers = null)
{
$this->userAnwsers = $userAnwsers;
parent::__construct($resource);
}
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request)
{
return $this->collection->map(function (Question $question) {
$question = [
'id' => $question->id,
'type' => $question->type->value,
'question' => $question->question,
'answers' => new AnswersResource($question->answers)
];
if ($this->userAnwsers) {
$question['customerAnswers'] = new AnswersResource($this->userAnwsers);
}
return $question;
});
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Http\Resources\Api\V2;
use Illuminate\Http\Resources\Json\JsonResource;
class SingleOptinResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'email' => $request->email
];
}
}

View file

@ -6,9 +6,11 @@ use Illuminate\Http\Resources\Json\JsonResource;
class CustomerResource extends JsonResource
{
private $brandId;
public function __construct($resource, $brandId) {
public function __construct($resource, $brandId)
{
parent::__construct($resource);
$this->brandId = $brandId;
}
@ -19,6 +21,7 @@ class CustomerResource extends JsonResource
* @param \Illuminate\Http\Request $request
* @return array
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function toArray($request)
{
$infos = $this->infos()->where('brand_id', $this->brandId)->first();

View file

@ -6,14 +6,4 @@ use Illuminate\Http\Resources\Json\JsonResource;
class StatusResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

95
app/Lib/Helper.php Normal file
View file

@ -0,0 +1,95 @@
<?php
namespace App\Lib;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/**
* Helper Class
*/
class Helper
{
/**
* Package infos
*
* @var array
*/
private static $packageInfos;
/**
* Tells if the request is within apiV2 routes
*
* @param Request $request
* @return boolean
*/
public static function isRequestForApiV2(Request $request)
{
// Small hack to ensure to know if route is From APIv2
$routeName = static::getRouteName($request) ??
(strpos($request->path(), 'api/v2') === 0 ? 'apiV2.' : null) ?? 'none';
return strpos($routeName, 'apiV2.') === 0;
}
/**
* Tells if the request is within apiV1 routes
*
* @param Request $request
* @return boolean
*/
public static function isRequestForApiV1(Request $request): bool
{
// Small hack to ensure to know if route is From APIv1
$routePrefix = (strpos(static::getRoutePrefix($request), 'api/v1') ? 'v1' : null) ??
(strpos($request->path(), 'api/v1') === 0 ? 'v1' : null) ?? 'none';
return strpos($routePrefix, 'v1') === 0;
}
/**
* Try to get current route name
*
* @param Request $request
* @return string|null
*/
public static function getRouteName(Request $request)
{
return Route::currentRouteName() ?? ($request->route() ? $request->route()->getName() : null);
}
/**
* Try to get current route prefix
*
* @param Request $request
* @return string|null
*/
public static function getRoutePrefix(Request $request)
{
return (Route::getCurrentRoute() ? Route::getCurrentRoute()->getPrefix() : null) ??
($request->route() ? $request->route()->getPrefix() : null);
}
/**
* Try to get route from request
*
* @param Request $request
* @return \Illuminate\Routing\Route|null
*/
public static function getRoute(Request $request)
{
return Route::getRoutes()->getByName(static::getRouteName($request));
}
/**
* Get composer.json infos
*
* @return array
*/
public static function getPackageInfo(): array
{
if (!static::$packageInfos) {
static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../../composer.json'), true);
}
return static::$packageInfos;
}
}

View file

@ -5,7 +5,7 @@ namespace App\Lib;
use App\Exceptions\AbortRowImportException;
use App\Models\Brand;
use App\Models\Customer;
use App\Models\CustomerInfos;
use App\Models\CustomerInfo;
use App\Models\CustomerLogin;
use App\Models\SingleOptin;
use Carbon\Exceptions\InvalidFormatException;
@ -48,28 +48,28 @@ class MigrateOldData extends LeGaulois2016Migration
'bind' => [
'email' => [
'apply' => function ($v, $model) use ($cleanDegeu) {
$v = \mb_convert_encoding($v, "UTF-8");
$v = explode('#|#', $v);
$oldId = $v[0];
$first_name = $cleanDegeu($v[1]);
$last_name = $cleanDegeu($v[2]);
$birth_date = $v[3];
$address = $cleanDegeu($v[4]);
$address2 = $cleanDegeu($v[5]);
$city = $cleanDegeu($v[6]);
$postcode = $v[7];
$v = \mb_convert_encoding($v, "UTF-8");
$v = explode('#|#', $v);
$oldId = $v[0];
$first_name = $cleanDegeu($v[1]);
$last_name = $cleanDegeu($v[2]);
$birth_date = $v[3];
$address = $cleanDegeu($v[4]);
$address2 = $cleanDegeu($v[5]);
$city = $cleanDegeu($v[6]);
$postcode = $v[7];
$user_inscritNewsletter = $v[8];
$email = strtolower($v[9]);
$pwd = $v[10];
$created_at = $v[11];
$updated_at = $v[12];
$last_cnx = $v[13];
$email = strtolower($v[9]);
$pwd = $v[10];
$created_at = $v[11];
$updated_at = $v[12];
$last_cnx = $v[13];
// Just save Single optin
if (empty($first_name) && empty($last_name)) {
$sglOptin = SingleOptin::where('email', strtolower($email))
$sglOptin = SingleOptin::where('email', strtolower($email))
->where('brand_id', $this->brand->id)->firstOrNew();
$sglOptin->email = strtolower($email);
$sglOptin->email = strtolower($email);
$sglOptin->brand_id = $this->brand->id;
$sglOptin->save();
} else {
@ -109,8 +109,8 @@ class MigrateOldData extends LeGaulois2016Migration
}
}
$model = Customer::where('email', $email)->firstOrNew();
$model->email = $email;
$model = Customer::where('email', $email)->firstOrNew();
$model->email = $email;
$model->created_at = $created_at;
$model->updated_at = $updated_at;
$model->save();
@ -118,47 +118,54 @@ class MigrateOldData extends LeGaulois2016Migration
// filtre birthdate puisque c'est du sale dans la vielle crm
try {
$expl = explode('-', $birth_date);
$d = Carbon::parse($birth_date);
if ($d->toDateString() === sprintf('%s-%s-%s', $expl[2] ?? '', $expl[1] ?? '', $expl[0] ?? '')) {
$d = Carbon::parse($birth_date);
if (
$d->toDateString() === sprintf(
'%s-%s-%s',
$expl[2] ?? '',
$expl[1] ?? '',
$expl[0] ?? ''
)
) {
$birth_date = $d->toDateString();
}
} catch (InvalidFormatException $e) {
$birth_date = null;
$birth_date = '';
}
if ($birth_date === '0000-00-00') {
$birth_date = null;
$birth_date = '';
}
$city = !empty($city) ? $city : null;
$city = !empty($city) ? $city : null;
$postcode = !empty($postcode) ? $postcode : null;
$address = !empty($address) ? $address : null;
$address = !empty($address) ? $address : null;
$address2 = !empty($address2) ? $address2 : null;
$cInfo = new CustomerInfos();
$cInfo->hashcode = Str::uuid();
$cInfo->firstname = ucfirst(strtolower($first_name));
$cInfo->lastname = ucfirst(strtolower($last_name));
$cInfo->birth_date = strlen($birth_date) ? $birth_date : null;
$cInfo->address = $address;
$cInfo->address2 = $address2;
$cInfo->city = $city;
$cInfo->postcode = $postcode;
$cInfo->opt_in = $user_inscritNewsletter ? true : false;
$cInfo->brand_id = $this->brand->id;
$cInfo = new CustomerInfo();
$cInfo->hashcode = Str::uuid();
$cInfo->firstname = ucfirst(strtolower($first_name));
$cInfo->lastname = ucfirst(strtolower($last_name));
$cInfo->birth_date = !strlen($birth_date) ? null : $birth_date;
$cInfo->address = $address;
$cInfo->address2 = $address2;
$cInfo->city = $city;
$cInfo->postcode = $postcode;
$cInfo->opt_in = $user_inscritNewsletter ? true : false;
$cInfo->brand_id = $this->brand->id;
$cInfo->customer_id = $model->id;
$cInfo->created_at = $created_at;
$cInfo->updated_at = $updated_at;
$cInfo->created_at = $created_at;
$cInfo->updated_at = $updated_at;
$cInfo->save();
$cLogin = new CustomerLogin();
$cLogin->password = Hash::make(trim($pwd));
$cLogin->brand_id = $this->brand->id;
$cLogin->customer_id = $model->id;
$cLogin->status = true;
$cLogin->created_at = $created_at;
$cLogin->updated_at = $updated_at;
$cLogin = new CustomerLogin();
$cLogin->password = Hash::make(trim($pwd));
$cLogin->brand_id = $this->brand->id;
$cLogin->customer_id = $model->id;
$cLogin->status = true;
$cLogin->created_at = $created_at;
$cLogin->updated_at = $updated_at;
$cLogin->last_connection = $last_cnx;
$cLogin->save();

View file

@ -9,9 +9,14 @@ use Illuminate\Queue\SerializesModels;
class CourrierArcadiaError extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
use Queueable;
use SerializesModels;
public $subject, $msg, $xml;
public $subject;
public $msg;
public $xml;
/**
* Create a new message instance.
@ -21,8 +26,8 @@ class CourrierArcadiaError extends Mailable implements ShouldQueue
public function __construct($subject, $msg, $xml)
{
$this->subject = $subject;
$this->msg = $msg;
$this->xml = $xml;
$this->msg = $msg;
$this->xml = $xml;
}
/**
@ -33,6 +38,18 @@ class CourrierArcadiaError extends Mailable implements ShouldQueue
public function build()
{
$msg = $this->msg;
return $this->subject("[LDC CRM] ". $this->subject)->view('mail.arcadia-error', compact('msg'))->attachData($this->xml, date ("Ymd_His") . '.xml', ['mime' => 'application/xml']);
return $this->subject(
sprintf(
"[LDC CRM] %s",
$this->subject
)
)->view(
'mail.arcadia-error',
compact('msg')
)->attachData(
$this->xml,
date("Ymd_His") . '.xml',
['mime' => 'application/xml']
);
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Mail;
use Exception;
use Illuminate\Mail\Mailable;
class ExceptionAlert extends Mailable
{
/**
* Undocumented variable
*
* @var Exception
*/
public $exception;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Exception $exception)
{
$this->exception = $exception;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('mail.exception')
->subject('Need help !, an error occured in your application, please check Logs ASAP')
->text('mail.exception-plain')
->with('appName', config('app.name'))
->with('appUrl', config('app.url'))
->with('exception', $this->exception)
->with('url', app()->runningInConsole() ? 'CONSOLE' : (
(request() and request()->fullUrl()) ?
request()->fullUrl() : 'none'))
->to(
config('mail.contactsList.serviceweb', 'informatique.internet@ldc.fr'),
sprintf('%s Exception Handler', config('app.name'))
);
}
}

35
app/Models/Answer.php Normal file
View file

@ -0,0 +1,35 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Answer extends Model
{
use HasFactory;
protected $fillable = [
'answer'
];
/**
* Question BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function question()
{
return $this->belongsTo(Question::class);
}
/**
* Answer belongsToMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function customers()
{
return $this->belongsToMany(Customer::class, 'customer_answer');
}
}

View file

@ -9,29 +9,67 @@ class Brand extends Model
{
use HasFactory;
protected $table = "brands";
protected $fillable = [
'name'
];
public function services() {
return $this->hasMany('App\Models\Service', 'brand_id');
/**
* Service hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function services()
{
return $this->hasMany(Service::class);
}
public function customer_infos() {
return $this->hasMany('App\Models\CustomerInfos', 'brand_id');
/**
* Question hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function questions()
{
return $this->hasMany(Question::class);
}
public function services_stats() {
return $this->hasMany('App\Models\ServiceStat', 'brand_id');
/**
* CustomerInfo hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function customerInfos()
{
return $this->hasMany(CustomerInfo::class);
}
public function single_optins() {
return $this->hasMany('App\Models\SingleOptin', 'brand_id');
/**
* ServiceStat hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servicesStats()
{
return $this->hasMany(ServiceStat::class);
}
public function getAccountsAttribute(){
return $this->customer_infos()->count();
/**
* SingleOptin hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function singleOptins()
{
return $this->hasMany(SingleOptin::class);
}
public function getTotalOptinsAttribute(){
return $this->customer_infos()->where('opt_in', 1)->count() + $this->single_optins()->count();
public function getAccountsAttribute()
{
return $this->customerInfos()->count();
}
public function getTotalOptinsAttribute()
{
return $this->customerInfos()->where('opt_in', 1)->count() + $this->singleOptins()->count();
}
}

View file

@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -9,17 +10,97 @@ class Customer extends Model
{
use HasFactory;
protected $table = "customers";
protected $fillable = [
'email'
];
public function infos() {
return $this->hasMany('App\Models\CustomerInfos', 'customer_id');
/**
* CustomerInfo hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function infos()
{
return $this->hasMany(CustomerInfo::class);
}
public function logins() {
return $this->hasMany('App\Models\CustomerLogin', 'customer_id');
/**
* Get the CustomerInfo from a specific brand
*
* @param Brand $brand
* @return \Illuminate\Database\Eloquent\Builder
*/
public function info(Brand $brand)
{
return CustomerInfo::whereHas('brand', function (Builder $query) use ($brand) {
$query->where('id', $brand->id);
})->whereHas('customer', function (Builder $query) use ($brand) {
$query->where('id', $this->id);
});
}
public function optInHistory() {
return $this->hasMany('App\Models\CustomerOptInHistory', 'customer_id');
/**
* CustomerLogin hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function logins()
{
return $this->hasMany(CustomerLogin::class);
}
/**
* Get the CustomerLogin from a specific brand
*
* @param Brand $brand
* @return \Illuminate\Database\Eloquent\Builder
*/
public function login(Brand $brand)
{
return CustomerLogin::whereHas('brand', function (Builder $query) use ($brand) {
$query->where('id', $brand->id);
})->whereHas('customer', function (Builder $query) use ($brand) {
$query->where('id', $this->id);
});
}
/**
* CustomerOptInHistory hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function optInHistory()
{
return $this->hasMany(CustomerOptInHistory::class);
}
/**
* Customer Brand belongsToMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function brands()
{
return $this->belongsToMany(Brand::class, CustomerInfo::class);
}
/**
* Answers belongsToMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function answers()
{
return $this->belongsToMany(Answer::class, 'customer_answer');
}
public function setOptIn(Brand $brand, bool $optIn)
{
// Ajout dans la table d'historique des opt-in de la nouvelle valeur
$optInHistory = new CustomerOptInHistory();
$optInHistory->customer_id = $this->id;
$optInHistory->brand_id = $brand->id;
$optInHistory->new_value = $optIn;
$optInHistory->saveOrFail();
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class CustomerAnswer extends Pivot
{
//
}

122
app/Models/CustomerInfo.php Normal file
View file

@ -0,0 +1,122 @@
<?php
namespace App\Models;
use App\Enums\Country;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Traits\CastsEnums;
class CustomerInfo extends Model
{
use HasFactory;
use CastsEnums;
protected $enumCasts = [
'newcountry' => Country::class
];
protected $fillable = [
'firstname',
'lastname',
'birth_date',
'address',
'address2',
'phone',
'newcountry',
'country',
'city',
'postcode',
'opt_in'
];
protected static function booted()
{
static::creating(function ($customerInfo) {
$customerInfo->hashcode = Str::uuid();
if (ctype_digit($customerInfo->country)) {
try {
$customerInfo->newcountry = Country::make((int)$customerInfo->country);
} catch (\TypeError $e) {
// TODO change this when country column is updated
// Set afghanistan as default country for now
$customerInfo->newcountry = Country::afghanistan();
}
}
if (\is_object($customerInfo->country) and \get_class($customerInfo->country) === Country::class) {
$customerInfo->newcountry = $customerInfo->country;
$customerInfo->country = $customerInfo->newcountry->label;
}
if (!$customerInfo->newcountry) {
// TODO change this when country column is updated
// Set afghanistan as default country for now
$customerInfo->newcountry = Country::afghanistan();
}
if (!$customerInfo->newcountry->equals(Country::afghanistan())) {
$customerInfo->country = $customerInfo->newcountry->label;
}
});
static::updating(function ($customerInfo) {
if (ctype_digit($customerInfo->country)) {
try {
$customerInfo->newcountry = Country::make((int)$customerInfo->country);
} catch (\TypeError $e) {
// TODO change this when country column is updated
// Set afghanistan as default country for now
$customerInfo->newcountry = Country::afghanistan();
}
}
if (\is_object($customerInfo->country) and \get_class($customerInfo->country) === Country::class) {
$customerInfo->newcountry = $customerInfo->country;
$customerInfo->country = $customerInfo->newcountry->label;
}
if (!$customerInfo->newcountry) {
// TODO change this when country column is updated
// Set afghanistan as default country for now
$customerInfo->newcountry = Country::afghanistan();
}
if (!$customerInfo->newcountry->equals(Country::afghanistan())) {
$customerInfo->country = $customerInfo->newcountry->label;
}
});
}
/**
* Customer BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
/**
* Get customer info from email
*
* @param Brand $brand
* @param string $email
* @return \Illuminate\Database\Eloquent\Builder
*/
public static function getFromEmail(Brand $brand, string $email)
{
return CustomerInfo::whereHas('brand', function ($q) use ($brand) {
$q->where('id', $brand->id);
})->whereHas('customer', function ($q) use ($email) {
$q->where('email', $email);
});
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class CustomerInfos extends Model
{
use HasFactory;
protected $table = "customer_infos";
public function customer() {
return $this->belongsTo('App\Models\Customer', 'customer_id');
}
public function brand() {
return $this->belongsTo('App\Models\Brand', 'brand_id');
}
}

View file

@ -4,14 +4,52 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
class CustomerLogin extends Model
{
use HasFactory;
protected $table = "customer_login";
protected $fillable = [
'password'
];
public function customer() {
return $this->belongsTo('App\Models\Customer', 'customer_id');
protected static function booted()
{
static::creating(function ($customerLogin) {
if (Hash::needsRehash($customerLogin->password)) {
$customerLogin->password = Hash::make(trim($customerLogin->password));
}
});
static::updating(function ($customerLogin) {
if (Hash::needsRehash($customerLogin->password)) {
$customerLogin->password = Hash::make(trim($customerLogin->password));
}
});
}
public function passwordMatch(string $password)
{
return Hash::check($password, $this->password);
}
/**
* Customer BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
}

View file

@ -11,7 +11,23 @@ class CustomerOptInHistory extends Model
protected $table = "customer_opt_in_history";
public function customer() {
return $this->belongsTo('App\Models\Customer', 'customer_id');
/**
* Customer BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
}

43
app/Models/Question.php Normal file
View file

@ -0,0 +1,43 @@
<?php
namespace App\Models;
use App\Enums\QuestionType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Question extends Model
{
use CastsEnums;
use HasFactory;
protected $enumCasts = [
'type' => QuestionType::class
];
protected $fillable = [
'question',
'type'
];
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
/**
* Answer BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function answers()
{
return $this->hasMany(Answer::class);
}
}

View file

@ -4,18 +4,152 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class Service extends Model
{
use HasFactory;
protected $table = "services";
protected $fillable = [
'name'
];
public function brand() {
return $this->belongsTo('App\Models\Brand', 'brand_id');
protected static function booted()
{
static::creating(function (self $service) {
$service->generateBearerToken();
if (Hash::needsRehash($service->bearer_token)) {
$service->bearer_token = Hash::make($service->bearer_token);
}
if (Hash::needsRehash($service->bearer_pwd)) {
$service->bearer_pwd = Hash::make($service->bearer_pwd);
}
if (Hash::needsRehash($service->api_token)) {
$service->api_token = Hash::make($service->api_token);
}
});
static::updating(function (self $service) {
if (Hash::needsRehash($service->bearer_token)) {
$service->bearer_token = Hash::make($service->bearer_token);
}
if (Hash::needsRehash($service->bearer_pwd)) {
$service->bearer_pwd = Hash::make($service->bearer_pwd);
}
if (Hash::needsRehash($service->api_token)) {
$service->api_token = Hash::make($service->api_token);
}
});
}
public function services_stats() {
return $this->hasMany('App\Models\ServiceStat', 'service_id');
/**
* Generates a new Api Token
*
* @return string
*/
public function generateApiToken(): string
{
$this->api_token = hash('sha256', Str::uuid());
$this->api_token_date = now();
return $this->api_token;
}
/**
* Generates new bearer credentials
*
* @return string
*/
public function generateBearerToken(): string
{
$this->bearer_token = hash('sha256', Str::uuid());
$this->bearer_token_date = now();
$this->bearer_pwd = hash('ripemd160', Str::uuid());
return $this->bearer_token;
}
/**
* Increment API CRM Stat
*
* @return void
*/
public function incrementStat(): void
{
$serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]);
if (!$serviceStat->exists) {
$serviceStat->service()->associate($this);
$serviceStat->brand()->associate($this->brand);
}
$serviceStat->nb_calls += 1;
$serviceStat->saveOrFail();
}
/**
* Get Service from apiToken with its brand
*
* @param string $apiToken
* @return \App\Models\Service
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getFromApiToken(string $apiToken): self
{
$services = static::all();
foreach ($services as $service) {
if (Hash::check($apiToken, $service->api_token)) {
return $service;
}
}
throw new ModelNotFoundException('Could not find service from Api Token');
}
/**
* Get Service from bearerToken with its brand
*
* @param string $bearerToken
* @return \App\Models\Service
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getFromBearerToken(string $bearerToken): self
{
$services = static::all();
foreach ($services as $service) {
if (Hash::check($bearerToken, $service->bearer_token)) {
return $service;
}
}
throw new ModelNotFoundException('Could not find service from Service Token');
}
/**
* Authenticate from the api token string
*
* @param array $guards
* @param string $apiToken
* @return bool
*/
public function authenticateApi(string $apiToken): bool
{
return Hash::check($apiToken, $this->api_token) and
((abs(strtotime($this->api_token_date) - time()) * 60) < (config('api.tokenTimeout') * 60));
}
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
/**
* ServiceStat hasMany relation
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function servicesStats()
{
return $this->hasMany(ServiceStat::class);
}
}

View file

@ -11,13 +11,27 @@ class ServiceStat extends Model
protected $fillable = ['date'];
protected $table = "services_stats";
protected $attributes = [
'nb_calls' => 0
];
public function brand() {
return $this->belongsTo('App\Models\Brand', 'brand_id');
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function service() {
return $this->belongsTo('App\Models\Service', 'service_id');
/**
* Service BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function service()
{
return $this->belongsTo(Service::class);
}
}

View file

@ -9,9 +9,17 @@ class SingleOptin extends Model
{
use HasFactory;
protected $table = "single_optin";
protected $fillable = [
'email'
];
public function brand() {
return $this->belongsTo('App\Models\Brand', 'brand_id');
/**
* Brand BelongsTo relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
}

View file

@ -2,14 +2,14 @@
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
use HasFactory;
use Notifiable;
/**
* The attributes that are mass assignable.

View file

@ -0,0 +1,17 @@
<?php
namespace App\Policies;
use App\Models\Customer;
use App\Models\Service;
use Illuminate\Auth\Access\HandlesAuthorization;
class CustomerPolicy
{
use HandlesAuthorization;
public function customerBelongsToService(Service $service, Customer $customer)
{
return $customer->login($service->brand)->exists();
}
}

View file

@ -3,6 +3,7 @@
namespace App\Providers;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -25,8 +26,8 @@ class AppServiceProvider extends ServiceProvider
public function boot()
{
JsonResource::withoutWrapping();
if(config('app.env') !== 'local') {
\URL::forceScheme('https');
if (!in_array(config('app.env'), ['local', 'testing'])) {
URL::forceScheme('https');
}
}
}

View file

@ -2,19 +2,22 @@
namespace App\Providers;
use App\Lib\Helper;
use App\Models\Service;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
protected $policies = [];
/**
* Register any authentication / authorization services.
@ -25,6 +28,39 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
//
Auth::viaRequest('api-token', function (Request $request) {
// https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly/25843058
/**
* Un token devrais être fourni pour chaque requete apres authentification
* api_token param est requis par Auth
*/
$apiToken = $request->api_token;
if (is_null($apiToken)) {
return null;
}
/**
** All ApiV2 routes must pass Brand model to controller action since a Brand owns
** a service token, otherwise the api cannot be called.
** This hack is a custom binder instead of passing
** this to route as a parameter
*/
if (!($route = Helper::getRoute($request))) {
return null;
}
try {
$service = Service::getFromApiToken($apiToken);
$service = $service->load('brand');
app()->bind(Service::class, function () use ($service) {
return $service;
});
$test = $service->authenticateApi($apiToken);
return $test ? $service : null;
} catch (ModelNotFoundException $e) {
return null;
}
});
}
}

View file

@ -5,10 +5,10 @@ namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*

View file

@ -2,6 +2,7 @@
namespace App\Providers;
use App\Models\Customer;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
@ -19,15 +20,6 @@ class RouteServiceProvider extends ServiceProvider
*/
public const HOME = '/customers';
/**
* The controller namespace for the application.
*
* When present, controller route declarations will automatically be prefixed with this namespace.
*
* @var string|null
*/
// protected $namespace = 'App\\Http\\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
@ -38,14 +30,16 @@ class RouteServiceProvider extends ServiceProvider
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::prefix('api')->middleware('api')->namespace($this->namespace)->group(base_path('routes/api.php'));
Route::middleware('web')->namespace($this->namespace)->group(base_path('routes/web.php'));
});
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
Route::bind('customer', function ($value) {
$test = Customer::where('email', '=', $value);
return Customer::whereHas('infos', function ($q) use ($value) {
$q->where('customer_infos.hashcode', $value);
})->first() ?? (\ctype_digit($value) ? Customer::where('id', '=', $value)->first() : null) ??
Customer::where('email', '=', $value)->firstOrFail();
});
}
@ -57,7 +51,8 @@ class RouteServiceProvider extends ServiceProvider
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip());
return Limit::perMinute(config('api.apiRequestRatePerMinute'))
->by(optional($request->user())->id ?: $request->ip());
});
}
}

View file

@ -13,15 +13,18 @@ class ApiV1
$this->api_key = $api_key;
}
public function getCustomer($id){
public function getCustomer($id)
{
return $this->callAPI('GET', "/customer/id/$id");
}
public function getCustomerByEmail($email){
public function getCustomerByEmail($email)
{
return $this->callAPI('GET', "/customer/email/$email");
}
public function createCustomer($email, $password, $firstname, $lastname, $optin, $birthdate = null, $phone = null, $address = null, $address2 = null, $city = null, $postcode = null, $country = null){
public function createCustomer($email, $password, $firstname, $lastname, $optin, $birthdate = null, $phone = null, $address = null, $address2 = null, $city = null, $postcode = null, $country = null)
{
$user = [
'email' => $email,
'password' => $password,
@ -39,7 +42,8 @@ class ApiV1
return $this->callAPI('POST', '/customer/create', $user);
}
public function updateCustomer($crmId, $firstname = null, $lastname = null, $optin = null, $birthdate = null, $phone = null, $address = null, $address2 = null, $city = null, $postcode = null, $country = null){
public function updateCustomer($crmId, $firstname = null, $lastname = null, $optin = null, $birthdate = null, $phone = null, $address = null, $address2 = null, $city = null, $postcode = null, $country = null)
{
$user = [];
if ($firstname !== null)
@ -76,55 +80,64 @@ class ApiV1
return $this->callAPI('PUT', "/customer/$crmId", $user);
}
public function deleteCustomer($id){
public function deleteCustomer($id)
{
return $this->callAPI('DELETE', "/customer/$id");
}
public function changePwdCustomer($id, $password){
public function changePwdCustomer($id, $password)
{
return $this->callAPI('POST', "/customer/$id/change-password", ['password' => $password]);
}
public function activateCustomer($id){
public function activateCustomer($id)
{
return $this->callAPI('POST', "/customer/$id/activate");
}
public function toggleOptInCustomer($id){
public function toggleOptInCustomer($id)
{
return $this->callAPI('POST', "/customer/$id/toggle-optin");
}
public function removeOptInCustomer($hashcode){
public function removeOptInCustomer($hashcode)
{
return $this->callAPI('POST', "/customer/$hashcode/remove-optin");
}
public function loginCustomer($email, $password){
public function loginCustomer($email, $password)
{
return $this->callAPI('POST', "/customer/login", ['email' => $email, 'password' => $password]);
}
public function createSingleOptin($email){
public function createSingleOptin($email)
{
return $this->callAPI('POST', '/single-optin', ['email' => $email]);
}
public function ArcadiaOutgoingMail($crm_id, $fields, $type, $shipping = false){
public function ArcadiaOutgoingMail($crm_id, $fields, $type, $shipping = false)
{
if ($shipping !== false && $type === 'default') {
return $this->callAPI('POST', '/arcadia/outgoing-mail', ['crm_id' => $crm_id, 'fields' => json_encode($fields), 'type' => $type, 'shipping' => json_encode($shipping)]);
}else{
} else {
return $this->callAPI('POST', '/arcadia/outgoing-mail', ['crm_id' => $crm_id, 'fields' => json_encode($fields), 'type' => $type]);
}
}
private function callAPI($method, $url, $data = false){
private function callAPI($method, $url, $data = false)
{
if (strpos($this->endpoint, "dev")){
if ($data !== false){
if (strpos($this->endpoint, "dev")) {
if ($data !== false) {
$data = array_merge($data, ['api_token' => $this->api_key]);
}else{
} else {
$data = ['api_token' => $this->api_key];
}
}
$url = $this->endpoint . '/api/v1' . $url;
$curl = curl_init();
switch ($method){
switch ($method) {
case "POST":
curl_setopt($curl, CURLOPT_POST, 1);
if ($data)
@ -159,9 +172,9 @@ class ApiV1
if (!curl_errno($curl)) {
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (!in_array($http_code, [200, 201]))
return (object) ['status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed - Code HTTP : '. $http_code];
return (object) ['status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed - Code HTTP : ' . $http_code];
} else {
return (object) ['status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed : '. curl_error($curl)];
return (object) ['status' => 'KO', 'code' => 500, 'message' => 'Connection to API failed : ' . curl_error($curl)];
}
curl_close($curl);

1
clients/V2 Submodule

@ -0,0 +1 @@
Subproject commit 73a9d09ecdb17833e41a8868436938ab12629601

View file

@ -16,6 +16,7 @@ namespace LDCCRM;
use Exception;
// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
// TABLE LDC CUSTOMERS
// CREATE TABLE `ldc_wp_customers` (
// `crm_id` BIGINT NOT NULL COMMENT 'Le CRM ID',
@ -40,9 +41,9 @@ use Exception;
class WPCrm extends ApiV1
{
private static $_sessionKey = 'LDC_CRM_USER';
private static $sessionKey = 'LDC_CRM_USER';
private static $_sessionDestroyed = false;
private static $sessionDestroyed = false;
/**
* The user object
@ -69,8 +70,8 @@ class WPCrm extends ApiV1
*/
public static function login($email, $password)
{
self::_bootSessionIfNeeded();
return self::_loginUserWithApi($email, $password);
self::bootSessionIfNeeded();
return self::loginUserWithApi($email, $password);
}
/**
@ -81,7 +82,7 @@ class WPCrm extends ApiV1
*/
public static function getUser()
{
self::_bootSessionIfNeeded();
self::bootSessionIfNeeded();
return self::$user;
}
@ -95,7 +96,7 @@ class WPCrm extends ApiV1
*/
public static function getUserFromApi($email)
{
self::_initLDCApi();
self::initLdcApi();
/**
* Object response
*
@ -103,6 +104,7 @@ class WPCrm extends ApiV1
*/
$response = self::$api->getCustomerByEmail($email);
if ($response->status === 'KO') {
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
error_log(
sprintf(
'LDC CRM: returned KO from CRM => %s',
@ -111,7 +113,7 @@ class WPCrm extends ApiV1
);
return $response->code;
}
self::_setUser(
self::setUser(
$response->user->crm_id,
$response->user->hashcode,
$response->user->email,
@ -129,9 +131,9 @@ class WPCrm extends ApiV1
*
* @return \LDCCRM\WPCrmUser|int
*/
private static function _loginUserWithApi($email, $password)
private static function loginUserWithApi($email, $password)
{
self::_initLDCApi();
self::initLdcApi();
/**
* Object response
*
@ -139,6 +141,7 @@ class WPCrm extends ApiV1
*/
$response = self::$api->loginCustomer($email, $password);
if ($response->status === 'KO') {
// phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
error_log(
sprintf(
'LDC CRM: returned KO from CRM => %s',
@ -147,13 +150,13 @@ class WPCrm extends ApiV1
);
return $response->code;
}
self::_setUser(
self::setUser(
$response->user->crm_id,
$response->user->hashcode,
$response->user->email,
$response
);
self::_saveOrUpdateUserToTable();
self::saveOrUpdateUserToTable();
return self::$user;
}
@ -165,10 +168,10 @@ class WPCrm extends ApiV1
*/
public static function logout()
{
self::_bootSessionIfNeeded();
unset($_SESSION[self::$_sessionKey]);
self::bootSessionIfNeeded();
unset($_SESSION[self::$sessionKey]);
session_destroy();
self::$_sessionDestroyed = true;
self::$sessionDestroyed = true;
}
/**
@ -178,8 +181,8 @@ class WPCrm extends ApiV1
*/
public static function check()
{
self::_bootSessionIfNeeded();
return (bool) self::getUser();
self::bootSessionIfNeeded();
return (bool)self::getUser();
}
/**
@ -194,14 +197,14 @@ class WPCrm extends ApiV1
*
* @return void
*/
private static function _setUser($crm_id, $hashcode, $email, $response)
private static function setUser($crm_id, $hashcode, $email, $response)
{
self::_bootSessionIfNeeded();
self::bootSessionIfNeeded();
if (!self::$user) {
self::$user = new WPCrmUser($crm_id, $hashcode, $email);
}
self::$user->setInfo($response->user);
$_SESSION[self::$_sessionKey] = json_encode(self::$user);
$_SESSION[self::$sessionKey] = json_encode(self::$user);
}
/**
@ -209,7 +212,7 @@ class WPCrm extends ApiV1
*
* @return \LDCCRM\ApiV1
*/
private static function _initLDCApi()
private static function initLdcApi()
{
if (!self::$api) {
if (!extension_loaded('curl')) {
@ -218,7 +221,7 @@ class WPCrm extends ApiV1
);
}
$endpoint = getenv('CRM_ENDPOINT');
$api_key = getenv('CRM_API_TOKEN');
$api_key = getenv('CRM_API_TOKEN');
if ($endpoint === false or $api_key === false) {
throw new Exception(
'LDC CRM require .env to have CRM_ENDPOINT and CRM_API_TOKEN'
@ -234,19 +237,19 @@ class WPCrm extends ApiV1
*
* @return void
*/
private static function _bootSessionIfNeeded()
private static function bootSessionIfNeeded()
{
if (!session_id() && !self::$_sessionDestroyed) {
if (!session_id() && !self::$sessionDestroyed) {
session_start();
}
if (!self::$user and array_key_exists(self::$_sessionKey, $_SESSION)) {
if (!self::$user and array_key_exists(self::$sessionKey, $_SESSION)) {
/**
* The user object
*
* @var \LDCCRM\WPCrmUser
*/
self::$user = WPCrmUser::parseFromJson(
$_SESSION[self::$_sessionKey] ?? null
$_SESSION[self::$sessionKey] ?? null
);
}
}
@ -260,7 +263,7 @@ class WPCrm extends ApiV1
*/
public static function getUserFromTable($crm_id)
{
return self::_getWpDB()->get_row(
return self::getWpDb()->get_row(
'SELECT * FROM `ldc_wp_customers` WHERE `crm_id` = ' . $crm_id
);
}
@ -274,7 +277,7 @@ class WPCrm extends ApiV1
*/
public static function getUserFromTableByEmail($email)
{
return self::_getWpDB()->get_row(
return self::getWpDb()->get_row(
'SELECT * FROM `ldc_wp_customers` WHERE `email` = "' . $email . '"'
);
}
@ -282,13 +285,11 @@ class WPCrm extends ApiV1
/**
* Get a customer from table
*
* @param int $crm_id The customer crm id
*
* @return object|null
*/
public static function getUserToTable($crm_id)
public static function getUserToTable()
{
return self::_getWpDB()->get_row(
return self::getWpDb()->get_row(
'SELECT * FROM `ldc_wp_customers` WHERE `crm_id` = ' .
self::$user->crm_id
);
@ -299,12 +300,11 @@ class WPCrm extends ApiV1
*
* @return void
*/
private static function _saveOrUpdateUserToTable()
private static function saveOrUpdateUserToTable()
{
if (self::getUserToTable(self::$user->crm_id)) {
self::_query(
'UPDATE `ldc_wp_customers` SET `hashcode` = %d, `email` = %d' .
' WHERE `crm_id` = %d',
if (self::getUserToTable()) {
self::query(
'UPDATE `ldc_wp_customers` SET `hashcode` = %d, `email` = %d WHERE `crm_id` = %d',
[
'crm_id' => self::$user->crm_id,
'hashcode' => self::$user->hashcode,
@ -312,7 +312,7 @@ class WPCrm extends ApiV1
]
);
} else {
self::_query(
self::query(
'INSERT INTO `ldc_wp_customers` VALUES (%d, %s, %s)',
[
'crm_id' => self::$user->crm_id,
@ -331,10 +331,10 @@ class WPCrm extends ApiV1
*
* @return object Containing the row columns
*/
private static function _query($query, array $params = [])
private static function query($query, array $params = [])
{
return self::_getWpDB()->query(
self::_getWpDB()->prepare($query, $params)
return self::getWpDb()->query(
self::getWpDb()->prepare($query, $params)
);
}
@ -343,8 +343,9 @@ class WPCrm extends ApiV1
*
* @return object The $wpdb variable
*/
private static function _getWpDB()
private static function getWpDb()
{
// phpcs:ignore Squiz.PHP.GlobalKeyword.NotAllowed
global $wpdb;
return $wpdb;
}

View file

@ -29,6 +29,7 @@ use JsonSerializable;
*/
class WPCrmUser implements JsonSerializable
{
/**
* The CRM ID
*
@ -66,9 +67,9 @@ class WPCrmUser implements JsonSerializable
*/
public function __construct($crm_id, $hashcode, $email)
{
$this->crm_id = $crm_id;
$this->crm_id = $crm_id;
$this->hashcode = $hashcode;
$this->email = $email;
$this->email = $email;
}
/**
@ -107,9 +108,9 @@ class WPCrmUser implements JsonSerializable
if (is_null($info) or is_null($info->email)) {
throw new Exception('Info cannot be null');
}
$this->info = $info;
$this->crm_id = $info->crm_id;
$this->email = $info->email;
$this->info = $info;
$this->crm_id = $info->crm_id;
$this->email = $info->email;
$this->hashcode = $info->hashcode;
}

View file

@ -1,65 +1,99 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The Laravel Framework.",
"keywords": [
"framework",
"laravel"
],
"license": "MIT",
"require": {
"php": "^7.3|^8.0",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"laravel/framework": "^8.12",
"laravel/tinker": "^2.5",
"ext-curl": "*",
"ext-json": "*",
"ext-dom": "*"
},
"require-dev": {
"facade/ignition": "^2.5",
"fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.4.2",
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.3.3"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\migrations\\": "database/migrations/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
"name": "ldc/ldccrmv2client",
"type": "project",
"version": "2.0.12",
"description": "LDC CRM V2",
"authors": [
{
"name": "Romain Bonhomme",
"email": "romain.bonhomme@ldc.fr"
},
{
"name": "Vincent Bridier",
"email": "vicent.bridier@ldc.fr",
"homepage": "https://bridier.me"
},
{
"name": "Jérémy Munsch",
"email": "jeremy.munsch@ldc.fr",
"homepage": "https://jeremydev.ovh"
}
],
"license": "MIT",
"require": {
"php": "^7",
"ext-curl": "*",
"ext-dom": "*",
"ext-json": "*",
"arondeparon/laravel-request-sanitizer": "^3.1",
"doctrine/dbal": "^3.0",
"fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^7.0.1",
"kwaadpepper/enum": "^0.1",
"laravel-lang/lang": "~7.0",
"laravel/framework": "^8.39",
"laravel/tinker": "^2.5"
},
"require-dev": {
"facade/ignition": "^2.5",
"fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.4.2",
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.3.3",
"slevomat/coding-standard": "^7.0",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^5.3"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"platform": {
"php": "7.3.26"
}
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\migrations\\": "database/migrations/",
"Database\\Seeders\\": "database/seeders/",
"LDC\\LDCCRM\\" : "clients/V2/src/",
"LDCCRM\\" : "clients/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"test": [
"@configclear",
"./test.sh"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
}
"configclear": "@php artisan config:clear",
"runtestmigration": "@php artisan migrate:fresh --env=testing --seed",
"runtest": "@php artisan test",
"runserve": "@php artisan serve --host=127.0.0.25 --port=8888 --env=testing&",
"killserve": "kill $(ps -f | grep '[1]27.0.0.25:8888' | awk '{print $2}')",
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
}
}

8255
composer.lock generated Normal file

File diff suppressed because it is too large Load diff

22
config/api.php Normal file
View file

@ -0,0 +1,22 @@
<?php
use App\Lib\Helper;
return [
/**
* Api versions
** this value is sent in response
*/
'version' => Helper::getPackageInfo()['version'],
/**
* Api Token timeout in minutes
*/
'tokenTimeout' => 60,
/**
* The number of request
* per user or ip per minutes
*/
'apiRequestRatePerMinute' => 256
];

View file

@ -39,7 +39,7 @@ return [
|
*/
'debug' => (bool) env('APP_DEBUG', false),
'debug' => (bool)env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
@ -171,7 +171,6 @@ return [
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
@ -226,7 +225,9 @@ return [
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'Carbon' => Illuminate\Support\Carbon::class,
'ArcadiaMailType' => App\Enums\ArcadiaMailType::class,
'ArcadiaOrderType' => App\Enums\ArcadiaOrderType::class
],
];

View file

@ -42,9 +42,7 @@ return [
],
'api' => [
'driver' => 'token',
'provider' => 'services',
'hash' => true,
'driver' => 'api-token'
],
],
@ -69,17 +67,7 @@ return [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'services' => [
'driver' => 'eloquent',
'model' => App\Models\Service::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
]
],
/*

View file

@ -60,9 +60,7 @@ return [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'options' => [],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
@ -99,6 +97,6 @@ return [
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
];

View file

@ -41,7 +41,7 @@ return [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'database' => env('DB_DATABASE', database_path('testing.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
@ -146,7 +146,7 @@ return [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [

View file

@ -51,7 +51,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],

View file

@ -30,6 +30,7 @@ return [
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 10),
'verify' => true
],
/*

View file

@ -90,6 +90,10 @@ return [
'MAIL_ADMIN' => env('MAIL_ADMIN'),
'contactsList' => [
'serviceweb' => env('MAIL_LIST_SERVICEWEB', 'informatique.internet@ldc.fr')
],
/*
|--------------------------------------------------------------------------
| Markdown Mail Settings

View file

@ -128,7 +128,7 @@ return [
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
),
/*

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