From 2e99623428c236b1aba8d92e69e6870bbedda821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sun, 28 Feb 2021 12:04:49 +0100 Subject: [PATCH 001/103] Normalize - add phpcs - fix php standards - fix Laravel standards - normalize tables names --- .../Commands/DeleteUnconfirmedCustomers.php | 5 +- app/Console/Kernel.php | 2 +- .../Controllers/Api/v1/ArcadiaController.php | 218 +- .../Controllers/Api/v1/CustomerController.php | 207 +- app/Http/Controllers/BrandController.php | 9 +- app/Http/Controllers/Controller.php | 4 +- app/Http/Controllers/CustomerController.php | 54 +- app/Http/Controllers/LoginController.php | 20 +- app/Http/Controllers/ServiceController.php | 15 +- .../Controllers/SingleOptinsController.php | 12 +- app/Http/Middleware/CheckBrand.php | 11 +- app/Http/Resources/CustomerResource.php | 3 +- app/Mail/CourrierArcadiaError.php | 11 +- app/Models/Brand.php | 42 +- app/Models/Customer.php | 32 +- app/Models/CustomerInfo.php | 31 + app/Models/CustomerInfos.php | 21 - app/Models/CustomerLogin.php | 12 +- app/Models/CustomerOptInHistory.php | 10 +- app/Models/Service.php | 22 +- app/Models/ServiceStat.php | 22 +- app/Models/SingleOptin.php | 12 +- app/Models/User.php | 3 +- app/Providers/AppServiceProvider.php | 5 +- composer.json | 19 +- composer.lock | 7746 +++++++++++++++++ config/cache.php | 2 +- config/database.php | 2 +- config/filesystems.php | 2 +- config/session.php | 2 +- .../2014_10_12_000000_create_users_table.php | 1 + ..._08_19_000000_create_failed_jobs_table.php | 1 + .../2020_11_26_152638_create_customers.php | 1 + .../2020_11_26_152901_create_brands.php | 1 + ...020_11_26_152941_create_customer_infos.php | 1 + ...020_11_26_153521_create_customer_login.php | 1 + .../2020_11_26_153929_create_single_optin.php | 1 + ..._154226_create_customer_opt_in_history.php | 1 + .../2020_11_27_122842_create_services.php | 1 + ...020_12_01_145010_create_services_stats.php | 1 + ..._services_stats_table_to_service_stats.php | 29 + ...2_rename_single_optin_to_single_optins.php | 29 + ...omer_login_to_optin_to_customer_logins.php | 29 + phpcs.xml | 13 + routes/api.php | 4 +- routes/web.php | 21 +- tests/CreatesApplication.php | 2 +- 47 files changed, 8423 insertions(+), 270 deletions(-) create mode 100644 app/Models/CustomerInfo.php delete mode 100644 app/Models/CustomerInfos.php create mode 100644 composer.lock create mode 100644 database/migrations/2021_02_28_101342_rename_services_stats_table_to_service_stats.php create mode 100644 database/migrations/2021_02_28_101522_rename_single_optin_to_single_optins.php create mode 100644 database/migrations/2021_02_28_101638_rename_customer_login_to_optin_to_customer_logins.php create mode 100644 phpcs.xml diff --git a/app/Console/Commands/DeleteUnconfirmedCustomers.php b/app/Console/Commands/DeleteUnconfirmedCustomers.php index 5393156..30aaea5 100644 --- a/app/Console/Commands/DeleteUnconfirmedCustomers.php +++ b/app/Console/Commands/DeleteUnconfirmedCustomers.php @@ -21,7 +21,8 @@ class DeleteUnconfirmedCustomers extends Command * * @var string */ - protected $description = "Delete the customers that haven't confirmed their acocunt within 24 hours after the registeration."; + protected $description = "Delete the customers that haven't confirmed " . + "their acocunt within 24 hours after the registeration."; /** * Create a new command instance. @@ -41,7 +42,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 diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 69914e9..31f4b24 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -34,7 +34,7 @@ class Kernel extends ConsoleKernel */ protected function commands() { - $this->load(__DIR__.'/Commands'); + $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } diff --git a/app/Http/Controllers/Api/v1/ArcadiaController.php b/app/Http/Controllers/Api/v1/ArcadiaController.php index 09205db..04f9c5c 100644 --- a/app/Http/Controllers/Api/v1/ArcadiaController.php +++ b/app/Http/Controllers/Api/v1/ArcadiaController.php @@ -23,8 +23,14 @@ class ArcadiaController extends Controller 'type' => 'required', ]); - 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; @@ -32,104 +38,132 @@ class ArcadiaController extends Controller $type = $request->type; $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(); - - if (!$customerInfos) - return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']); - - $check = $this->checkRequiredFields($fields, $type); - if ($check['status'] !== 'OK') - return new StatusResource($check); - - $xml = $this->makeXML($fields, $type, $customer, $customerInfos); - - try { - Storage::disk('arcadia')->put(date ("Ymd_His") . ".xml", $xml); - return new StatusResource(['status' => 'OK']); - }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)); - return new StatusResource(['status' => 'KO', 'code' => 204, 'message' => 'Arcadia XML Creation failed.']); } + $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first(); + + if (!$CustomerInfo) { + return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']); + } + + $check = $this->checkRequiredFields($fields, $type); + if ($check['status'] !== 'OK') { + return new StatusResource($check); + } + + $xml = $this->makeXML($fields, $type, $customer, $CustomerInfo); + + try { + Storage::disk('arcadia')->put(date("Ymd_His") . ".xml", $xml); + return new StatusResource(['status' => 'OK']); + } 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 + )); + return new StatusResource(['status' => 'KO', 'code' => 204, 'message' => 'Arcadia XML Creation failed.']); + } } + + 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' => 'NOK', 'code' => 203, 'message' => $errors]; + } return ['status' => 'OK']; } - private function makeXML($fields, $type, $customer, $customerInfos) + private function makeXML($fields, $type, $customer, $CustomerInfo) { $pivot = [ 'order_num' => 'Num_Commande', // required if type = default - format 9 numeric characters @@ -141,12 +175,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'); @@ -154,59 +190,99 @@ 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('Civilite')); - $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('Email_conso') + )->appendChild($dom->createCDATASection("$customer->email")); + $order->appendChild( + $dom->createElement('Civilite') + ); + $order->appendChild( + $dom->createElement('Nom') + )->appendChild($dom->createCDATASection("$CustomerInfo->lastname")); + $order->appendChild( + $dom->createElement('Prenom') + )->appendChild($dom->createCDATASection("$CustomerInfo->firstname")); + $order->appendChild( + $dom->createElement('Adresse') + )->appendChild($dom->createCDATASection("$CustomerInfo->address")); + $order->appendChild( + $dom->createElement('Adresse_Complementaire') + )->appendChild($dom->createCDATASection("$CustomerInfo->address2")); + $order->appendChild( + $dom->createElement('CP') + )->appendChild($dom->createCDATASection("$CustomerInfo->postcode")); + $order->appendChild( + $dom->createElement('Ville') + )->appendChild($dom->createCDATASection("$CustomerInfo->city")); + $order->appendChild( + $dom->createElement('Telephone') + )->appendChild($dom->createCDATASection("$CustomerInfo->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(); diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/v1/CustomerController.php index ef10b5b..f5d7302 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/v1/CustomerController.php @@ -6,7 +6,7 @@ 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,8 +35,12 @@ 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); @@ -54,31 +58,43 @@ class CustomerController extends Controller $customer->email = $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 = $firstname; - $customerInfos->lastname = $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; + $CustomerInfo = new CustomerInfo(); + $CustomerInfo->firstname = $firstname; + $CustomerInfo->lastname = $lastname; + $CustomerInfo->birth_date = !empty($request->birth_date) ? + filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->address = !empty($request->address) ? + filter_var($request->address, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->address2 = !empty($request->address2) ? + filter_var($request->address2, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->phone = !empty($request->phone) ? + filter_var($request->phone, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->country = !empty($request->country) ? + filter_var($request->country, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->city = !empty($request->city) ? + filter_var($request->city, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->postcode = !empty($request->postcode) ? + filter_var($request->postcode, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->opt_in = $optIn; + $CustomerInfo->hashcode = $hashcode; + $CustomerInfo->brand_id = $brandId; + $CustomerInfo->customer_id = $customer->id; - $customer->infos()->save($customerInfos); + $customer->infos()->save($CustomerInfo); // Création du login pour cet utilisateur sur ce service $customerLogin = new CustomerLogin(); @@ -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.']); } @@ -145,50 +164,65 @@ class CustomerController extends Controller '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; $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 (isset($request->firstname)) - $customerInfos->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING); + if (isset($request->firstname)) { + $CustomerInfo->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING); + } - if (isset($request->lastname)) - $customerInfos->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); + if (isset($request->lastname)) { + $CustomerInfo->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); + } - if (isset($request->birth_date)) - $customerInfos->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING); + if (isset($request->birth_date)) { + $CustomerInfo->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING); + } - if (isset($request->address)) - $customerInfos->address = filter_var($request->address, FILTER_SANITIZE_STRING); + if (isset($request->address)) { + $CustomerInfo->address = filter_var($request->address, FILTER_SANITIZE_STRING); + } - if (isset($request->address2)) - $customerInfos->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING); + if (isset($request->address2)) { + $CustomerInfo->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING); + } - if (isset($request->phone)) - $customerInfos->phone = filter_var($request->phone, FILTER_SANITIZE_STRING); + if (isset($request->phone)) { + $CustomerInfo->phone = filter_var($request->phone, FILTER_SANITIZE_STRING); + } - if (isset($request->country)) - $customerInfos->country = filter_var($request->country, FILTER_SANITIZE_STRING); + if (isset($request->country)) { + $CustomerInfo->country = filter_var($request->country, FILTER_SANITIZE_STRING); + } - if (isset($request->city)) - $customerInfos->city = filter_var($request->city, FILTER_SANITIZE_STRING); + if (isset($request->city)) { + $CustomerInfo->city = filter_var($request->city, FILTER_SANITIZE_STRING); + } - if (isset($request->postcode)) - $customerInfos->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING); + if (isset($request->postcode)) { + $CustomerInfo->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING); + } if (isset($request->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->customer_id = $customer->id; @@ -197,10 +231,10 @@ class CustomerController extends Controller $optInHistory->save(); } - $customerInfos->opt_in = $optIn; + $CustomerInfo->opt_in = $optIn; } - $customerInfos->save(); + $CustomerInfo->save(); return new CustomerResource($customer, $brandId); } @@ -210,14 +244,19 @@ 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); @@ -250,7 +289,8 @@ class CustomerController extends Controller 'hashcode' => $infos->hashcode, 'status' => $login->status, ] - ]); + ] + ); } $login->last_connection = now(); @@ -274,13 +314,18 @@ 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; $customer = Customer::find($id); @@ -307,7 +352,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); @@ -338,7 +386,8 @@ class CustomerController extends Controller * @param Request $request * @param $id */ - public function delete(Request $request, $id) { + 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 @@ -368,7 +417,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; @@ -398,28 +448,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); // 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()) { + // 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.']); } } diff --git a/app/Http/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php index 6043d9e..61a2e4d 100644 --- a/app/Http/Controllers/BrandController.php +++ b/app/Http/Controllers/BrandController.php @@ -8,16 +8,19 @@ 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) { + public function manage(Request $request, $id = null) + { $brand = null; - if ($id !== null) + if ($id !== null) { $brand = Brand::find($id); + } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $request->validate([ diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index a0a2a8a..ce1176d 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -9,5 +9,7 @@ use Illuminate\Routing\Controller as BaseController; class Controller extends BaseController { - use AuthorizesRequests, DispatchesJobs, ValidatesRequests; + use AuthorizesRequests; + use DispatchesJobs; + use ValidatesRequests; } diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 620dc7e..83d12d7 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -4,39 +4,67 @@ namespace App\Http\Controllers; 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(); + public function show($id, $brandId) + { + $CustomerInfo = CustomerInfo::where([['customer_id', $id], ['brand_id', $brandId]])->first(); $customerLogin = CustomerLogin::where([['customer_id', $id], ['brand_id', $brandId]])->first(); - if (!$customerInfos) + if (!$CustomerInfo) { abort(404); - return view('customers.show', compact('customerInfos', 'customerLogin')); + } + return view('customers.show', compact('CustomerInfo', 'customerLogin')); } - public function filter($texte, $brandId = null) { + public function filter($texte, $brandId = null) + { if ($brandId !== null) { $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]]) + ->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(); } else { - $customers = CustomerInfos::join('customers', 'customers.id', '=', 'customer_infos.customer_id') - ->join('customer_login', function($join) { + $customers = CustomerInfo::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') + ->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(); diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index c2ec613..f7033f7 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -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'); } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 6c5c0d0..976e7d8 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -9,12 +9,14 @@ 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) { + public function manage(Request $request, $id = null) + { $service = null; $brands = Brand::all(); $edit = false; @@ -40,17 +42,20 @@ class ServiceController extends Controller $service->brand_id = filter_var($request->brand_id, FILTER_SANITIZE_NUMBER_INT); $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 !' : 'Token du service : ' . $apiToken); } return view('services.manage', compact('service', 'brands', 'edit')); } - public function generateApiToken($serviceId) { + public function generateApiToken($serviceId) + { $service = Service::find($serviceId); - if (!$service) + if (!$service) { return back()->with('error', "Génération impossible : Service introuvable."); + } $apiToken = Str::random(80); $service->api_token = hash('sha256', $apiToken); diff --git a/app/Http/Controllers/SingleOptinsController.php b/app/Http/Controllers/SingleOptinsController.php index a4c6388..303585d 100644 --- a/app/Http/Controllers/SingleOptinsController.php +++ b/app/Http/Controllers/SingleOptinsController.php @@ -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"]); } } diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index 0fda77b..7db2566 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -22,19 +22,22 @@ class CheckBrand */ public function handle(Request $request, Closure $next) { - if ($request->bearerToken() !== null) + if ($request->bearerToken() !== null) { $this->apiToken = $request->bearerToken(); - else + } else { $this->apiToken = ($request->api_token !== null ? $request->api_token : null); + } - if ($this->apiToken === null) + 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 (!$brand) + if (!$brand) { die("Le token d'authentification est incorrect"); + } $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); $serviceStat->brand_id = $brand->id; diff --git a/app/Http/Resources/CustomerResource.php b/app/Http/Resources/CustomerResource.php index 2539016..9bfda5f 100644 --- a/app/Http/Resources/CustomerResource.php +++ b/app/Http/Resources/CustomerResource.php @@ -8,7 +8,8 @@ class CustomerResource extends JsonResource { private $brandId; - public function __construct($resource, $brandId) { + public function __construct($resource, $brandId) + { parent::__construct($resource); $this->brandId = $brandId; } diff --git a/app/Mail/CourrierArcadiaError.php b/app/Mail/CourrierArcadiaError.php index 889a893..bf6bb5f 100644 --- a/app/Mail/CourrierArcadiaError.php +++ b/app/Mail/CourrierArcadiaError.php @@ -9,9 +9,12 @@ 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. @@ -33,6 +36,8 @@ 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("[LDC CRM] " . $this->subject) + ->view('mail.arcadia-error', compact('msg')) + ->attachData($this->xml, date("Ymd_His") . '.xml', ['mime' => 'application/xml']); } } diff --git a/app/Models/Brand.php b/app/Models/Brand.php index 6e7c660..f4c2494 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -9,21 +9,43 @@ class Brand extends Model { use HasFactory; - protected $table = "brands"; - - 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'); + /** + * CustomerInfo hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function customerInfos() + { + return $this->hasMany(CustomerInfo::class); } - public function services_stats() { - return $this->hasMany('App\Models\ServiceStat', 'brand_id'); + /** + * ServiceStat hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servicesStats() + { + return $this->hasMany(ServiceStat::class); } - public function single_optins() { - return $this->hasMany('App\Models\SingleOptin', 'brand_id'); + /** + * SingleOptin hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function singleOptins() + { + return $this->hasMany(SingleOptin::class); } } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 903a462..70fcfed 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -9,17 +9,33 @@ class Customer extends Model { use HasFactory; - protected $table = "customers"; - - 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'); + /** + * CustomerLogin hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function logins() + { + return $this->hasMany(CustomerLogin::class); } - public function optInHistory() { - return $this->hasMany('App\Models\CustomerOptInHistory', 'customer_id'); + /** + * CustomerOptInHistory hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function optInHistory() + { + return $this->hasMany(CustomerOptInHistory::class); } } diff --git a/app/Models/CustomerInfo.php b/app/Models/CustomerInfo.php new file mode 100644 index 0000000..4b4d0cb --- /dev/null +++ b/app/Models/CustomerInfo.php @@ -0,0 +1,31 @@ +belongsTo(Customer::class); + } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); + } +} diff --git a/app/Models/CustomerInfos.php b/app/Models/CustomerInfos.php deleted file mode 100644 index 7050d57..0000000 --- a/app/Models/CustomerInfos.php +++ /dev/null @@ -1,21 +0,0 @@ -belongsTo('App\Models\Customer', 'customer_id'); - } - - public function brand() { - return $this->belongsTo('App\Models\Brand', 'brand_id'); - } -} diff --git a/app/Models/CustomerLogin.php b/app/Models/CustomerLogin.php index 98d04d1..ac056ef 100644 --- a/app/Models/CustomerLogin.php +++ b/app/Models/CustomerLogin.php @@ -9,9 +9,13 @@ class CustomerLogin extends Model { use HasFactory; - protected $table = "customer_login"; - - 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); } } diff --git a/app/Models/CustomerOptInHistory.php b/app/Models/CustomerOptInHistory.php index 28d069d..5228320 100644 --- a/app/Models/CustomerOptInHistory.php +++ b/app/Models/CustomerOptInHistory.php @@ -11,7 +11,13 @@ 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); } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 7a213dd..b8df6fc 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -9,13 +9,23 @@ class Service extends Model { use HasFactory; - protected $table = "services"; - - 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 services_stats() { - return $this->hasMany('App\Models\ServiceStat', 'service_id'); + /** + * ServiceStat hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servicesStats() + { + return $this->hasMany(ServiceStat::class); } } diff --git a/app/Models/ServiceStat.php b/app/Models/ServiceStat.php index 6495715..c08112f 100644 --- a/app/Models/ServiceStat.php +++ b/app/Models/ServiceStat.php @@ -11,13 +11,23 @@ class ServiceStat extends Model protected $fillable = ['date']; - protected $table = "services_stats"; - - 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); } } diff --git a/app/Models/SingleOptin.php b/app/Models/SingleOptin.php index 29f8465..311424b 100644 --- a/app/Models/SingleOptin.php +++ b/app/Models/SingleOptin.php @@ -9,9 +9,13 @@ class SingleOptin extends Model { use HasFactory; - protected $table = "single_optin"; - - 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); } } diff --git a/app/Models/User.php b/app/Models/User.php index 804799b..95af624 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,7 +9,8 @@ use Illuminate\Notifications\Notifiable; class User extends Authenticatable { - use HasFactory, Notifiable; + use HasFactory; + use Notifiable; /** * The attributes that are mass assignable. diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c8242a1..54fc163 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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 (config('app.env') !== 'local') { + URL::forceScheme('https'); } } } diff --git a/composer.json b/composer.json index f298c95..4244354 100644 --- a/composer.json +++ b/composer.json @@ -8,27 +8,32 @@ ], "license": "MIT", "require": { - "php": "^7.3|^8.0", + "php": "^7", + "ext-curl": "*", + "ext-dom": "*", + "ext-json": "*", + "doctrine/dbal": "^3.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": "*" + "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" + "phpunit/phpunit": "^9.3.3", + "squizlabs/php_codesniffer": "^3.5" }, "config": { "optimize-autoloader": true, "preferred-install": "dist", - "sort-packages": true + "sort-packages": true, + "platform": { + "php": "7.3.26" + } }, "extra": { "laravel": { diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2d19508 --- /dev/null +++ b/composer.lock @@ -0,0 +1,7746 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "a8f4b1ed32617cbb217584fcdbc66fac", + "packages": [ + { + "name": "asm89/stack-cors", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/asm89/stack-cors.git", + "reference": "8d8f88b3b3830916be94292c1fbce84433efb1aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/asm89/stack-cors/zipball/8d8f88b3b3830916be94292c1fbce84433efb1aa", + "reference": "8d8f88b3b3830916be94292c1fbce84433efb1aa", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0", + "symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Asm89\\Stack\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander", + "email": "iam.asm89@gmail.com" + } + ], + "description": "Cross-origin resource sharing library and stack middleware", + "homepage": "https://github.com/asm89/stack-cors", + "keywords": [ + "cors", + "stack" + ], + "support": { + "issues": "https://github.com/asm89/stack-cors/issues", + "source": "https://github.com/asm89/stack-cors/tree/v2.0.2" + }, + "time": "2020-10-29T16:03:21+00:00" + }, + { + "name": "brick/math", + "version": "0.9.2", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", + "reference": "dff976c2f3487d42c1db75a3b180e2b9f0e72ce0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", + "vimeo/psalm": "4.3.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.9.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" + } + ], + "time": "2021-01-20T22:51:39+00:00" + }, + { + "name": "composer/package-versions-deprecated", + "version": "1.11.99.1", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" + }, + "require-dev": { + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "support": { + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-11-11T10:22:58+00:00" + }, + { + "name": "dnoegel/php-xdg-base-dir", + "version": "v0.1.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "support": { + "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", + "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" + }, + "time": "2019-12-04T15:06:13+00:00" + }, + { + "name": "doctrine/cache", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "13e3381b25847283a91948d04640543941309727" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", + "reference": "13e3381b25847283a91948d04640543941309727", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "doctrine/coding-standard": "^6.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0", + "predis/predis": "~1.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/1.10.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2020-07-07T18:54:01+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ee6d1260d5cc20ec506455a585945d7bdb98662c", + "reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c", + "shasum": "" + }, + "require": { + "composer/package-versions-deprecated": "^1.11.99", + "doctrine/cache": "^1.0", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.1", + "jetbrains/phpstorm-stubs": "^2019.1", + "phpstan/phpstan": "^0.12.40", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpunit/phpunit": "^9.4", + "psalm/plugin-phpunit": "^0.10.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.17.2" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-11-15T18:20:41+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-29T15:13:26+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "webmozart/assert": "^1.7.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-webmozart-assert": "^0.12.7", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2020-11-24T19:55:57+00:00" + }, + { + "name": "egulias/email-validator", + "version": "2.1.25", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "reference": "0dbf5d78455d4d6a41d186da50adc1122ec066f4", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.10" + }, + "require-dev": { + "dominicsayers/isemail": "^3.0.7", + "phpunit/phpunit": "^4.8.36|^7.5.15", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/2.1.25" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2020-12-29T14:50:06+00:00" + }, + { + "name": "fideloper/proxy", + "version": "4.4.1", + "source": { + "type": "git", + "url": "https://github.com/fideloper/TrustedProxy.git", + "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/c073b2bd04d1c90e04dc1b787662b558dd65ade0", + "reference": "c073b2bd04d1c90e04dc1b787662b558dd65ade0", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0", + "php": ">=5.4.0" + }, + "require-dev": { + "illuminate/http": "^5.0|^6.0|^7.0|^8.0|^9.0", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Fideloper\\Proxy\\TrustedProxyServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fideloper\\Proxy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Fidao", + "email": "fideloper@gmail.com" + } + ], + "description": "Set trusted proxies for Laravel", + "keywords": [ + "load balancing", + "proxy", + "trusted proxy" + ], + "support": { + "issues": "https://github.com/fideloper/TrustedProxy/issues", + "source": "https://github.com/fideloper/TrustedProxy/tree/4.4.1" + }, + "time": "2020-10-22T13:48:01+00:00" + }, + { + "name": "fruitcake/laravel-cors", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/laravel-cors.git", + "reference": "01de0fe5f71c70d1930ee9a80385f9cc28e0f63a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/01de0fe5f71c70d1930ee9a80385f9cc28e0f63a", + "reference": "01de0fe5f71c70d1930ee9a80385f9cc28e0f63a", + "shasum": "" + }, + "require": { + "asm89/stack-cors": "^2.0.1", + "illuminate/contracts": "^6|^7|^8|^9", + "illuminate/support": "^6|^7|^8|^9", + "php": ">=7.2", + "symfony/http-foundation": "^4|^5", + "symfony/http-kernel": "^4.3.4|^5" + }, + "require-dev": { + "laravel/framework": "^6|^7|^8", + "orchestra/testbench-dusk": "^4|^5|^6", + "phpunit/phpunit": "^6|^7|^8", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "laravel": { + "providers": [ + "Fruitcake\\Cors\\CorsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Adds CORS (Cross-Origin Resource Sharing) headers support in your Laravel application", + "keywords": [ + "api", + "cors", + "crossdomain", + "laravel" + ], + "support": { + "issues": "https://github.com/fruitcake/laravel-cors/issues", + "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.3" + }, + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-10-22T13:57:20+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "phpoption/phpoption": "^1.7.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2020-04-13T13:17:36+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79", + "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2020-10-10T11:47:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "60d379c243457e073cff02bc323a2a86cb355631" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", + "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.0" + }, + "time": "2020-09-30T07:37:28+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", + "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.7.0" + }, + "time": "2020-09-30T07:37:11+00:00" + }, + { + "name": "laravel/framework", + "version": "v8.29.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "d2eba352b3b3a3c515b18c5726b373fe5026733e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/d2eba352b3b3a3c515b18c5726b373fe5026733e", + "reference": "d2eba352b3b3a3c515b18c5726b373fe5026733e", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "dragonmantank/cron-expression": "^3.0.2", + "egulias/email-validator": "^2.1.10", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "league/commonmark": "^1.3", + "league/flysystem": "^1.1", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.31", + "opis/closure": "^3.6", + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0", + "ramsey/uuid": "^4.0", + "swiftmailer/swiftmailer": "^6.0", + "symfony/console": "^5.1.4", + "symfony/error-handler": "^5.1.4", + "symfony/finder": "^5.1.4", + "symfony/http-foundation": "^5.1.4", + "symfony/http-kernel": "^5.1.4", + "symfony/mime": "^5.1.4", + "symfony/process": "^5.1.4", + "symfony/routing": "^5.1.4", + "symfony/var-dumper": "^5.1.4", + "tijsverkoyen/css-to-inline-styles": "^2.2.2", + "vlucas/phpdotenv": "^5.2", + "voku/portable-ascii": "^1.4.8" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.155", + "doctrine/dbal": "^2.6|^3.0", + "filp/whoops": "^2.8", + "guzzlehttp/guzzle": "^6.5.5|^7.0.1", + "league/flysystem-cached-adapter": "^1.0", + "mockery/mockery": "^1.4.2", + "orchestra/testbench-core": "^6.8", + "pda/pheanstalk": "^4.0", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "predis/predis": "^1.1.1", + "symfony/cache": "^5.1.4" + }, + "suggest": { + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.8).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", + "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", + "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "mockery/mockery": "Required to use mocking (^1.4.2).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", + "predis/predis": "Required to use the predis connector (^1.1.2).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", + "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-02-23T14:27:41+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.6.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "daae1c43f1300fe88c05d83db6f3d8f76677ad88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/daae1c43f1300fe88c05d83db6f3d8f76677ad88", + "reference": "daae1c43f1300fe88c05d83db6f3d8f76677ad88", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/contracts": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.10.4", + "symfony/var-dumper": "^4.3.4|^5.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpunit/phpunit": "^8.5.8|^9.3.3" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.6.0" + }, + "time": "2021-01-26T20:35:18+00:00" + }, + { + "name": "league/commonmark", + "version": "1.5.7", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/11df9b36fd4f1d2b727a73bf14931d81373b9a54", + "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "scrutinizer/ocular": "1.7.*" + }, + "require-dev": { + "cebe/markdown": "~1.0", + "commonmark/commonmark.js": "0.29.2", + "erusev/parsedown": "~1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "~1.4", + "mikehaertl/php-shellcommand": "^1.4", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", + "scrutinizer/ocular": "^1.5", + "symfony/finder": "^4.2" + }, + "bin": [ + "bin/commonmark" + ], + "type": "library", + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", + "type": "custom" + }, + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2020-10-31T13:49:32+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", + "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.x" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2020-08-23T07:39:11+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-01-18T20:58:21+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90 <7.0.1", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-12-14T13:15:25+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.45.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "528783b188bdb853eb21239b1722831e0f000a8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/528783b188bdb853eb21239b1722831e0f000a8d", + "reference": "528783b188bdb853eb21239b1722831e0f000a8d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^3.4 || ^4.0 || ^5.0" + }, + "require-dev": { + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", + "kylekatarnls/multi-tester": "^2.0", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.54", + "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2021-02-11T18:30:17+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.10.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + }, + "time": "2020-12-20T10:01:03+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.1", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" + } + ], + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", + "keywords": [ + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/opis/closure/issues", + "source": "https://github.com/opis/closure/tree/3.6.1" + }, + "time": "2020-11-07T02:01:34+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.7.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/994ecccd8f3283ecf5ac33254543eb0ac946d525", + "reference": "994ecccd8f3283ecf5ac33254543eb0ac946d525", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.7.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2020-07-20T17:29:33+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/master" + }, + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.3" + }, + "time": "2020-03-23T09:12:05+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.10.6", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6f990c19f91729de8b31e639d6e204ea59f19cf3", + "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1.*", + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", + "php": "^8.0 || ^7.0 || ^5.5.9", + "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", + "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "hoa/console": "3.17.*" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.10.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.10.6" + }, + "time": "2021-01-18T15:53:43+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8" + }, + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP 7.2+ library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.1.3" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-01-21T17:40:04+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "cd4032040a750077205918c86049aa0f43d22947" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/cd4032040a750077205918c86049aa0f43d22947", + "reference": "cd4032040a750077205918c86049aa0f43d22947", + "shasum": "" + }, + "require": { + "brick/math": "^0.8 || ^0.9", + "ext-json": "*", + "php": "^7.2 || ^8", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7.0", + "doctrine/annotations": "^1.8", + "goaop/framework": "^2", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-mockery": "^1.3", + "php-mock/php-mock-phpunit": "^2.5", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^0.17.1", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "psy/psysh": "^0.10.0", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "3.9.4" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "rss": "https://github.com/ramsey/uuid/releases.atom", + "source": "https://github.com/ramsey/uuid" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + } + ], + "time": "2020-08-18T17:17:46+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.2.5", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/698a6a9f54d7eb321274de3ad19863802c879fb7", + "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.0", + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "symfony/phpunit-bridge": "^4.4|^5.0" + }, + "suggest": { + "ext-intl": "Needed to support internationalized email addresses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "https://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "support": { + "issues": "https://github.com/swiftmailer/swiftmailer/issues", + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.5" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", + "type": "tidelift" + } + ], + "time": "2021-01-12T09:35:59+00:00" + }, + { + "name": "symfony/console", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" + }, + "conflict": { + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-28T22:06:19+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f65f217b3314504a1ec99c2d6ef69016bb13490f", + "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:01:46+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/master" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "48f18b3609e120ea66d59142c23dc53e9562c26d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/48f18b3609e120ea66d59142c23dc53e9562c26d", + "reference": "48f18b3609e120ea66d59142c23dc53e9562c26d", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1.0", + "symfony/polyfill-php80": "^1.15", + "symfony/var-dumper": "^4.4|^5.0" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.1", + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-28T22:06:19+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367", + "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/error-handler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^4.4|^5.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:36:42+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2", + "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "4adc8d172d602008c204c2e16956f99257248e03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03", + "reference": "4adc8d172d602008c204c2e16956f99257248e03", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-28T22:06:19+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "41db680a15018f9c1d4b23516059633ce280ca33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41db680a15018f9c1d4b23516059633ce280ca33", + "reference": "41db680a15018f9c1d4b23516059633ce280ca33", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-version": "2.3", + "branch-alias": { + "dev-main": "2.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v2.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-14T17:08:19+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "20c554c0f03f7cde5ce230ed248470cccbc34c36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/20c554c0f03f7cde5ce230ed248470cccbc34c36", + "reference": "20c554c0f03f7cde5ce230ed248470cccbc34c36", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-03T04:42:09+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05", + "reference": "89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "~1.0", + "symfony/deprecation-contracts": "^2.1", + "symfony/error-handler": "^4.4|^5.0", + "symfony/event-dispatcher": "^5.0", + "symfony/http-client-contracts": "^1.1|^2", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/browser-kit": "<4.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<5.1.8", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0" + }, + "require-dev": { + "psr/cache": "~1.0", + "symfony/browser-kit": "^4.4|^5.0", + "symfony/config": "^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/css-selector": "^4.4|^5.0", + "symfony/dependency-injection": "^5.1.8", + "symfony/dom-crawler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/routing": "^4.4|^5.0", + "symfony/stopwatch": "^4.4|^5.0", + "symfony/translation": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-03T04:51:58+00:00" + }, + { + "name": "symfony/mime", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/7dee6a43493f39b51ff6c5bb2bd576fe40a76c86", + "reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<4.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/property-access": "^4.4|^5.1", + "symfony/property-info": "^4.4|^5.1", + "symfony/serializer": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-02T06:10:15+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/06fb361659649bcfd6a208a0f1fcaf4e827ad342", + "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Iconv\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Iconv extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "iconv", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", + "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "2d63434d922daf7da8dd863e7907e67ee3031483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483", + "reference": "2d63434d922daf7da8dd863e7907e67ee3031483", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", + "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/process", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/routing", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/348b5917e56546c6d96adbf21d7f92c9ef563661", + "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/config": "<5.0", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.10.4", + "psr/log": "~1.0", + "symfony/config": "^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/master" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-07T11:33:47+00:00" + }, + { + "name": "symfony/string", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "c95468897f408dd0aca2ff582074423dd0455122" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", + "reference": "c95468897f408dd0aca2ff582074423dd0455122", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-25T15:14:59+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/c021864d4354ee55160ddcfd31dc477a1bc77949", + "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/http-kernel": "^5.0", + "symfony/intl": "^4.4|^5.0", + "symfony/service-contracts": "^1.1.2|^2", + "symfony/yaml": "^4.4|^5.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-09-28T13:05:58+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "72ca213014a92223a5d18651ce79ef441c12b694" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72ca213014a92223a5d18651ce79ef441c12b694", + "reference": "72ca213014a92223a5d18651ce79ef441c12b694", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v5.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-27T10:15:41+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5", + "reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0 || ^8.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.3" + }, + "time": "2020-07-13T06:12:54+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.1", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.7.4", + "symfony/polyfill-ctype": "^1.17", + "symfony/polyfill-mbstring": "^1.17", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "homepage": "https://gjcampbell.co.uk/" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://vancelucas.com/" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2021-01-20T15:23:13+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "1.5.6", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "80953678b19901e5165c56752d087fc11526017c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/80953678b19901e5165c56752d087fc11526017c", + "reference": "80953678b19901e5165c56752d087fc11526017c", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/1.5.6" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2020-11-12T00:07:28+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, + "time": "2020-07-08T17:02:28+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "facade/flare-client-php", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/facade/flare-client-php.git", + "reference": "ef0f5bce23b30b32d98fd9bb49c6fa37b40eb546" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/ef0f5bce23b30b32d98fd9bb49c6fa37b40eb546", + "reference": "ef0f5bce23b30b32d98fd9bb49c6fa37b40eb546", + "shasum": "" + }, + "require": { + "facade/ignition-contracts": "~1.0", + "illuminate/pipeline": "^5.5|^6.0|^7.0|^8.0", + "php": "^7.1|^8.0", + "symfony/http-foundation": "^3.3|^4.1|^5.0", + "symfony/mime": "^3.4|^4.0|^5.1", + "symfony/var-dumper": "^3.4|^4.0|^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14", + "phpunit/phpunit": "^7.5.16", + "spatie/phpunit-snapshot-assertions": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Facade\\FlareClient\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Send PHP errors to Flare", + "homepage": "https://github.com/facade/flare-client-php", + "keywords": [ + "exception", + "facade", + "flare", + "reporting" + ], + "support": { + "issues": "https://github.com/facade/flare-client-php/issues", + "source": "https://github.com/facade/flare-client-php/tree/1.4.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-02-16T12:42:06+00:00" + }, + { + "name": "facade/ignition", + "version": "2.5.13", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition.git", + "reference": "5e9ef386aaad9985cee2ac23281a27568d083b7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition/zipball/5e9ef386aaad9985cee2ac23281a27568d083b7e", + "reference": "5e9ef386aaad9985cee2ac23281a27568d083b7e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "facade/flare-client-php": "^1.3.7", + "facade/ignition-contracts": "^1.0.2", + "filp/whoops": "^2.4", + "illuminate/support": "^7.0|^8.0", + "monolog/monolog": "^2.0", + "php": "^7.2.5|^8.0", + "symfony/console": "^5.0", + "symfony/var-dumper": "^5.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.14", + "mockery/mockery": "^1.3", + "orchestra/testbench": "^5.0|^6.0", + "psalm/plugin-laravel": "^1.2" + }, + "suggest": { + "laravel/telescope": "^3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Facade\\Ignition\\IgnitionServiceProvider" + ], + "aliases": { + "Flare": "Facade\\Ignition\\Facades\\Flare" + } + } + }, + "autoload": { + "psr-4": { + "Facade\\Ignition\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A beautiful error page for Laravel applications.", + "homepage": "https://github.com/facade/ignition", + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], + "support": { + "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", + "forum": "https://twitter.com/flareappio", + "issues": "https://github.com/facade/ignition/issues", + "source": "https://github.com/facade/ignition" + }, + "time": "2021-02-16T12:46:19+00:00" + }, + { + "name": "facade/ignition-contracts", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition-contracts.git", + "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition-contracts/zipball/3c921a1cdba35b68a7f0ccffc6dffc1995b18267", + "reference": "3c921a1cdba35b68a7f0ccffc6dffc1995b18267", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v2.15.8", + "phpunit/phpunit": "^9.3.11", + "vimeo/psalm": "^3.17.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Facade\\IgnitionContracts\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://flareapp.io", + "role": "Developer" + } + ], + "description": "Solution contracts for Ignition", + "homepage": "https://github.com/facade/ignition-contracts", + "keywords": [ + "contracts", + "flare", + "ignition" + ], + "support": { + "issues": "https://github.com/facade/ignition-contracts/issues", + "source": "https://github.com/facade/ignition-contracts/tree/1.0.2" + }, + "time": "2020-10-16T08:27:54+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "ab3f5364d01f2c2c16113442fb987d26e4004913" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ab3f5364d01f2c2c16113442fb987d26e4004913", + "reference": "ab3f5364d01f2c2c16113442fb987d26e4004913", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-intl": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.4.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.13.0" + }, + "time": "2020-12-18T16:50:48+00:00" + }, + { + "name": "filp/whoops", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "df7933820090489623ce0be5e85c7e693638e536" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/df7933820090489623ce0be5e85c7e693638e536", + "reference": "df7933820090489623ce0be5e85c7e693638e536", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.9.2" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2021-01-24T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea", + "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.4.3" + }, + "time": "2021-02-24T09:51:49+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v5.3.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "aca63581f380f63a492b1e3114604e411e39133a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca63581f380f63a492b1e3114604e411e39133a", + "reference": "aca63581f380f63a492b1e3114604e411e39133a", + "shasum": "" + }, + "require": { + "facade/ignition-contracts": "^1.0", + "filp/whoops": "^2.7.2", + "php": "^7.3 || ^8.0", + "symfony/console": "^5.0" + }, + "require-dev": { + "brianium/paratest": "^6.1", + "fideloper/proxy": "^4.4.1", + "friendsofphp/php-cs-fixer": "^2.17.3", + "fruitcake/laravel-cors": "^2.0.3", + "laravel/framework": "^9.0", + "nunomaduro/larastan": "^0.6.2", + "nunomaduro/mock-final-classes": "^1.0", + "orchestra/testbench": "^7.0", + "phpstan/phpstan": "^0.12.64", + "phpunit/phpunit": "^9.5.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2021-01-25T15:34:13+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/master" + }, + "time": "2020-06-27T14:33:11+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" + }, + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.12.2", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "245710e971a030f42e08f4912863805570f23d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", + "reference": "245710e971a030f42e08f4912863805570f23d39", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + }, + "time": "2020-12-19T10:15:11+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", + "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.10.2", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:44:49+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f661659747f2f87f9e72095bb207bceb0f151cb4", + "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.1", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.3", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3", + "sebastian/version": "^3.0.2" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-02-02T14:45:58+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", + "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:55:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:18:59+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.5.8", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", + "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards" + ], + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2020-10-23T02:01:07+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "75a63c33a8577608444246075ea0af0d052e452a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", + "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/master" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2020-07-12T23:59:07+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^7", + "ext-curl": "*", + "ext-dom": "*", + "ext-json": "*" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.3.26" + }, + "plugin-api-version": "2.0.0" +} diff --git a/config/cache.php b/config/cache.php index 4f41fdf..a8eaf93 100644 --- a/config/cache.php +++ b/config/cache.php @@ -99,6 +99,6 @@ return [ | */ - 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'), ]; diff --git a/config/database.php b/config/database.php index b42d9b3..3bfc47a 100644 --- a/config/database.php +++ b/config/database.php @@ -123,7 +123,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' => [ diff --git a/config/filesystems.php b/config/filesystems.php index fad67bc..f7e7243 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -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', ], diff --git a/config/session.php b/config/session.php index 4e0f66c..c0e798b 100644 --- a/config/session.php +++ b/config/session.php @@ -128,7 +128,7 @@ return [ 'cookie' => env( 'SESSION_COOKIE', - Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + Str::slug(env('APP_NAME', 'laravel'), '_') . '_session' ), /* diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 51015b6..f6aae68 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateUsersTable extends Migration { /** diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php index 6aa6d74..69b2335 100644 --- a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php +++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateFailedJobsTable extends Migration { /** diff --git a/database/migrations/2020_11_26_152638_create_customers.php b/database/migrations/2020_11_26_152638_create_customers.php index 6313abf..015ce55 100644 --- a/database/migrations/2020_11_26_152638_create_customers.php +++ b/database/migrations/2020_11_26_152638_create_customers.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateCustomers extends Migration { /** diff --git a/database/migrations/2020_11_26_152901_create_brands.php b/database/migrations/2020_11_26_152901_create_brands.php index ef7e073..caa88c6 100644 --- a/database/migrations/2020_11_26_152901_create_brands.php +++ b/database/migrations/2020_11_26_152901_create_brands.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateBrands extends Migration { /** diff --git a/database/migrations/2020_11_26_152941_create_customer_infos.php b/database/migrations/2020_11_26_152941_create_customer_infos.php index b8bcba9..8c69232 100644 --- a/database/migrations/2020_11_26_152941_create_customer_infos.php +++ b/database/migrations/2020_11_26_152941_create_customer_infos.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateCustomerInfos extends Migration { /** diff --git a/database/migrations/2020_11_26_153521_create_customer_login.php b/database/migrations/2020_11_26_153521_create_customer_login.php index 5238c46..1b95593 100644 --- a/database/migrations/2020_11_26_153521_create_customer_login.php +++ b/database/migrations/2020_11_26_153521_create_customer_login.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateCustomerLogin extends Migration { /** diff --git a/database/migrations/2020_11_26_153929_create_single_optin.php b/database/migrations/2020_11_26_153929_create_single_optin.php index 2f709c4..cbbbc44 100644 --- a/database/migrations/2020_11_26_153929_create_single_optin.php +++ b/database/migrations/2020_11_26_153929_create_single_optin.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateSingleOptin extends Migration { /** diff --git a/database/migrations/2020_11_26_154226_create_customer_opt_in_history.php b/database/migrations/2020_11_26_154226_create_customer_opt_in_history.php index 038643a..2f8a5c4 100644 --- a/database/migrations/2020_11_26_154226_create_customer_opt_in_history.php +++ b/database/migrations/2020_11_26_154226_create_customer_opt_in_history.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateCustomerOptInHistory extends Migration { /** diff --git a/database/migrations/2020_11_27_122842_create_services.php b/database/migrations/2020_11_27_122842_create_services.php index 4ce5e0f..ec8e280 100644 --- a/database/migrations/2020_11_27_122842_create_services.php +++ b/database/migrations/2020_11_27_122842_create_services.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateServices extends Migration { /** diff --git a/database/migrations/2020_12_01_145010_create_services_stats.php b/database/migrations/2020_12_01_145010_create_services_stats.php index fc8b0ca..c61231d 100644 --- a/database/migrations/2020_12_01_145010_create_services_stats.php +++ b/database/migrations/2020_12_01_145010_create_services_stats.php @@ -4,6 +4,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateServicesStats extends Migration { /** diff --git a/database/migrations/2021_02_28_101342_rename_services_stats_table_to_service_stats.php b/database/migrations/2021_02_28_101342_rename_services_stats_table_to_service_stats.php new file mode 100644 index 0000000..d1fb129 --- /dev/null +++ b/database/migrations/2021_02_28_101342_rename_services_stats_table_to_service_stats.php @@ -0,0 +1,29 @@ + + + + + app/ + config/ + routes/ + tests/ + + + + + diff --git a/routes/api.php b/routes/api.php index f56474f..8f5c368 100644 --- a/routes/api.php +++ b/routes/api.php @@ -15,9 +15,9 @@ use App\Http\Controllers\Api\v1\ArcadiaController; | */ -Route::prefix('v1')->middleware(['auth:api', 'check-brand'])->group(function() { +Route::prefix('v1')->middleware(['auth:api', 'check-brand'])->group(function () { // Clients - Route::prefix('customer')->group(function (){ + Route::prefix('customer')->group(function () { Route::get('/{type}/{value}', [CustomerController::class, 'show']); Route::post('/create', [CustomerController::class, 'store']); Route::put('/{id}', [CustomerController::class, 'update']); diff --git a/routes/web.php b/routes/web.php index aa3628e..0d7d182 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,26 +17,33 @@ use Illuminate\Support\Facades\Route; | contains the "web" middleware group. Now create something great! | */ + Route::get('/login', [LoginController::class, 'index'])->name('login'); Route::post('/login', [LoginController::class, 'authenticate'])->name('authenticate'); Route::middleware(['auth'])->group(function () { - Route::get('/', function() { + Route::get('/', function () { return redirect()->route('customers'); }); Route::get('/logout', [LoginController::class, 'logout'])->name('logout'); // Marques Route::get('/brands', [BrandController::class, 'index'])->name('brands'); - Route::match(['get', 'post'], '/brand/manage/{id?}', [BrandController::class, 'manage'])->name('manage-brand'); + Route::match(['get', 'post'], '/brand/manage/{id?}', [BrandController::class, 'manage']) + ->name('manage-brand'); // Services Route::get('/services', [ServiceController::class, 'index'])->name('services'); - Route::match(['get', 'post'], '/service/manage/{id?}', [ServiceController::class, 'manage'])->name('manage-service'); - Route::get('/services/{id}/token/generate', [ServiceController::class, 'generateApiToken'])->name('generate-token-service'); + Route::match(['get', 'post'], '/service/manage/{id?}', [ServiceController::class, 'manage']) + ->name('manage-service'); + Route::get('/services/{id}/token/generate', [ServiceController::class, 'generateApiToken']) + ->name('generate-token-service'); // Clients (Customer) Route::get('/customers', [CustomerController::class, 'index'])->name('customers'); - Route::get('/customers/filter/{texte}/{brandId?}', [CustomerController::class, 'filter'])->name('filter-customer'); + Route::get('/customers/filter/{texte}/{brandId?}', [CustomerController::class, 'filter']) + ->name('filter-customer'); Route::get('/customer/{id}/{brandId}', [CustomerController::class, 'show'])->name('show-customer'); // Single optins Route::get('/single-optins', [SingleOptinsController::class, 'index'])->name('single-optins'); - Route::get('/single-optins/filter/{texte}/{brandId?}', [SingleOptinsController::class, 'filter'])->name('filter-single-optins'); - Route::post('/single-optins/{id}/delete', [SingleOptinsController::class, 'delete'])->name('single-optins-delete'); + Route::get('/single-optins/filter/{texte}/{brandId?}', [SingleOptinsController::class, 'filter']) + ->name('filter-single-optins'); + Route::post('/single-optins/{id}/delete', [SingleOptinsController::class, 'delete']) + ->name('single-optins-delete'); }); diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index 547152f..ab92402 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -13,7 +13,7 @@ trait CreatesApplication */ public function createApplication() { - $app = require __DIR__.'/../bootstrap/app.php'; + $app = require __DIR__ . '/../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); From 6987501a98c79860a4a6cc9f1e4f41ad736fe051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Sun, 14 Mar 2021 10:45:42 +0100 Subject: [PATCH 002/103] CRM API V2 --- .../Commands/DeleteUnconfirmedCustomers.php | 15 +- app/Console/Commands/UserCreate.php | 25 +- app/Console/Kernel.php | 1 + app/Enums/ApiError.php | 56 ++ app/Enums/ApiStatus.php | 13 + app/Enums/ArcadiaMailType.php | 20 + app/Enums/ArcadiaOrderType.php | 20 + app/Enums/Country.php | 771 ++++++++++++++++++ app/Exceptions/Api/v2/ApiErrorException.php | 30 + app/Exceptions/Api/v2/CrmException.php | 14 + app/Exceptions/Handler.php | 99 ++- .../Controllers/Api/v1/ArcadiaController.php | 15 +- .../Controllers/Api/v1/CustomerController.php | 103 ++- app/Http/Controllers/Api/v2/ApiController.php | 33 + .../Controllers/Api/v2/ArcadiaController.php | 86 ++ .../Controllers/Api/v2/CustomerController.php | 180 ++++ .../Controllers/Api/v2/OptinController.php | 39 + app/Http/Controllers/BrandController.php | 24 +- app/Http/Controllers/CustomerController.php | 54 +- app/Http/Controllers/LoginController.php | 3 +- app/Http/Controllers/ServiceController.php | 53 +- app/Http/Kernel.php | 4 + app/Http/Middleware/ApiCheckTimeStamps.php | 43 + app/Http/Middleware/ApiResponse.php | 30 + app/Http/Middleware/Authenticate.php | 77 ++ app/Http/Middleware/CheckBrand.php | 7 +- app/Http/Middleware/EncryptCookies.php | 1 + .../PreventRequestsDuringMaintenance.php | 1 + app/Http/Middleware/TrimStrings.php | 1 + app/Http/Middleware/TrustProxies.php | 1 + app/Http/Middleware/VerifyCsrfToken.php | 1 + app/Http/Middleware/XssSanitizer.php | 25 + .../Requests/Api/v2/AuthenticateRequest.php | 29 + app/Http/Requests/Api/v2/BaseApiRequest.php | 37 + .../Api/v2/SendArchadiaMailRequest.php | 68 ++ .../Requests/Api/v2/SingleOptinRequest.php | 14 + .../Requests/Api/v2/StoreCustomerRequest.php | 78 ++ .../Api/v2/StoreSingleOptinRequest.php | 13 + .../Requests/Api/v2/UpdateCustomerRequest.php | 54 ++ .../Api/v2/UpdateSingleOptinRequest.php | 14 + app/Http/Requests/ManageBrandRequest.php | 45 + app/Http/Requests/ManageServiceRequest.php | 46 ++ app/Http/Resources/Api/v2/ApiResponse.php | 47 ++ .../Resources/Api/v2/CustomerResource.php | 87 ++ .../Resources/Api/v2/SingleOptinResource.php | 21 + app/Http/Resources/CustomerResource.php | 2 + app/Http/Resources/StatusResource.php | 10 - app/Lib/Helper.php | 74 ++ app/Mail/CourrierArcadiaError.php | 22 +- app/Models/Brand.php | 14 +- app/Models/Customer.php | 56 ++ app/Models/CustomerInfo.php | 44 + app/Models/CustomerLogin.php | 30 + app/Models/Service.php | 91 +++ app/Models/ServiceStat.php | 4 + app/Models/SingleOptin.php | 4 + app/Providers/AuthServiceProvider.php | 1 + app/Providers/EventServiceProvider.php | 1 + app/Providers/RouteServiceProvider.php | 14 +- composer.json | 6 +- config/api.php | 14 + config/app.php | 6 +- config/auth.php | 2 +- config/hashing.php | 1 + database/factories/BrandFactory.php | 29 + database/factories/CustomerFactory.php | 29 + database/factories/CustomerInfoFactory.php | 43 + database/factories/CustomerLoginFactory.php | 32 + database/factories/ServiceFactory.php | 29 + database/factories/UserFactory.php | 1 + .../2014_10_12_000000_create_users_table.php | 1 - ...ign_keys_to_cascade_on_customer_delete.php | 59 ++ ...249_rename_api_token_on_services_table.php | 35 + ...250_re_add_api_token_to_services_table.php | 35 + database/seeders/BrandSeeder.php | 29 + database/seeders/CustomerSeeder.php | 42 + database/seeders/DatabaseSeeder.php | 7 +- phpcs.xml | 90 +- phpunit.xml | 2 +- resources/lang/en/validation.php | 14 +- resources/lang/fr/auth.php | 18 + resources/lang/fr/pagination.php | 17 + resources/lang/fr/passwords.php | 20 + resources/lang/fr/validation-inline.php | 177 ++++ resources/lang/fr/validation.php | 182 +++++ resources/views/brands/index.blade.php | 16 +- resources/views/brands/manage.blade.php | 19 +- .../mail/arcadiaOrders/default.blade.php | 41 + resources/views/mail/exception.blade.php | 17 + resources/views/services/index.blade.php | 22 +- resources/views/services/manage.blade.php | 19 +- routes/api.php | 22 + routes/channels.php | 2 +- routes/web.php | 17 +- tests/Feature/ApiV2/ApiBase.php | 84 ++ tests/Feature/ApiV2/ApiTest.php | 53 ++ tests/Feature/ApiV2/ArcadiaTest.php | 65 ++ tests/Feature/ApiV2/CustomerTest.php | 272 ++++++ tests/Feature/ApiV2/SingleOptinTest.php | 74 ++ tests/Feature/ExampleTest.php | 21 - tests/Unit/ExampleTest.php | 18 - tests/Unit/HelperTest.php | 53 ++ 102 files changed, 4194 insertions(+), 306 deletions(-) create mode 100644 app/Enums/ApiError.php create mode 100644 app/Enums/ApiStatus.php create mode 100644 app/Enums/ArcadiaMailType.php create mode 100644 app/Enums/ArcadiaOrderType.php create mode 100644 app/Enums/Country.php create mode 100644 app/Exceptions/Api/v2/ApiErrorException.php create mode 100644 app/Exceptions/Api/v2/CrmException.php create mode 100644 app/Http/Controllers/Api/v2/ApiController.php create mode 100644 app/Http/Controllers/Api/v2/ArcadiaController.php create mode 100644 app/Http/Controllers/Api/v2/CustomerController.php create mode 100644 app/Http/Controllers/Api/v2/OptinController.php create mode 100644 app/Http/Middleware/ApiCheckTimeStamps.php create mode 100644 app/Http/Middleware/ApiResponse.php create mode 100644 app/Http/Middleware/XssSanitizer.php create mode 100644 app/Http/Requests/Api/v2/AuthenticateRequest.php create mode 100644 app/Http/Requests/Api/v2/BaseApiRequest.php create mode 100644 app/Http/Requests/Api/v2/SendArchadiaMailRequest.php create mode 100644 app/Http/Requests/Api/v2/SingleOptinRequest.php create mode 100644 app/Http/Requests/Api/v2/StoreCustomerRequest.php create mode 100644 app/Http/Requests/Api/v2/StoreSingleOptinRequest.php create mode 100644 app/Http/Requests/Api/v2/UpdateCustomerRequest.php create mode 100644 app/Http/Requests/Api/v2/UpdateSingleOptinRequest.php create mode 100644 app/Http/Requests/ManageBrandRequest.php create mode 100644 app/Http/Requests/ManageServiceRequest.php create mode 100644 app/Http/Resources/Api/v2/ApiResponse.php create mode 100644 app/Http/Resources/Api/v2/CustomerResource.php create mode 100644 app/Http/Resources/Api/v2/SingleOptinResource.php create mode 100644 app/Lib/Helper.php create mode 100644 config/api.php create mode 100644 database/factories/BrandFactory.php create mode 100644 database/factories/CustomerFactory.php create mode 100644 database/factories/CustomerInfoFactory.php create mode 100644 database/factories/CustomerLoginFactory.php create mode 100644 database/factories/ServiceFactory.php create mode 100644 database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php create mode 100644 database/migrations/2021_03_15_073249_rename_api_token_on_services_table.php create mode 100644 database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php create mode 100644 database/seeders/BrandSeeder.php create mode 100644 database/seeders/CustomerSeeder.php create mode 100644 resources/lang/fr/auth.php create mode 100644 resources/lang/fr/pagination.php create mode 100644 resources/lang/fr/passwords.php create mode 100644 resources/lang/fr/validation-inline.php create mode 100644 resources/lang/fr/validation.php create mode 100644 resources/views/mail/arcadiaOrders/default.blade.php create mode 100644 resources/views/mail/exception.blade.php create mode 100644 tests/Feature/ApiV2/ApiBase.php create mode 100644 tests/Feature/ApiV2/ApiTest.php create mode 100644 tests/Feature/ApiV2/ArcadiaTest.php create mode 100644 tests/Feature/ApiV2/CustomerTest.php create mode 100644 tests/Feature/ApiV2/SingleOptinTest.php delete mode 100644 tests/Feature/ExampleTest.php delete mode 100644 tests/Unit/ExampleTest.php create mode 100644 tests/Unit/HelperTest.php diff --git a/app/Console/Commands/DeleteUnconfirmedCustomers.php b/app/Console/Commands/DeleteUnconfirmedCustomers.php index 30aaea5..421fa39 100644 --- a/app/Console/Commands/DeleteUnconfirmedCustomers.php +++ b/app/Console/Commands/DeleteUnconfirmedCustomers.php @@ -9,6 +9,7 @@ use Illuminate\Console\Command; class DeleteUnconfirmedCustomers extends Command { + /** * The name and signature of the console command. * @@ -21,18 +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. diff --git a/app/Console/Commands/UserCreate.php b/app/Console/Commands/UserCreate.php index 683b670..ef5fd5b 100644 --- a/app/Console/Commands/UserCreate.php +++ b/app/Console/Commands/UserCreate.php @@ -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.'); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 31f4b24..17408e5 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { + /** * The Artisan commands provided by your application. * diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php new file mode 100644 index 0000000..8a0fd98 --- /dev/null +++ b/app/Enums/ApiError.php @@ -0,0 +1,56 @@ + 101, + 'validationFailed' => 102, + 'customerExists' => 103, + 'customerAlreadyActive' => 110, + 'customerNotActivated' => 116, + 'authFailed' => 117, + 'needAuth' => 118, + 'apiAuthFailed' => 119, + 'singleOptinCustomerExists' => 120, + 'methodNotAllowed' => 150, + 'wrongTimeStamp' => 151, + 'arcardiaXmlMailFailed' => 204 + ]; + } + + public static function labels(): array + { + return [ + 'modelNotFound' => trans('%s not found.'), + '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'), + 'arcardiaXmlMailFailed' => trans('Arcadia XML Creation failed.') + ]; + } +} diff --git a/app/Enums/ApiStatus.php b/app/Enums/ApiStatus.php new file mode 100644 index 0000000..942f490 --- /dev/null +++ b/app/Enums/ApiStatus.php @@ -0,0 +1,13 @@ + 1, + 'game' => 2 + ]; + } +} diff --git a/app/Enums/ArcadiaOrderType.php b/app/Enums/ArcadiaOrderType.php new file mode 100644 index 0000000..c408d08 --- /dev/null +++ b/app/Enums/ArcadiaOrderType.php @@ -0,0 +1,20 @@ + 0, + 'check' => 1 + ]; + } +} diff --git a/app/Enums/Country.php b/app/Enums/Country.php new file mode 100644 index 0000000..bd6a505 --- /dev/null +++ b/app/Enums/Country.php @@ -0,0 +1,771 @@ + 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' + ]; + } +} diff --git a/app/Exceptions/Api/v2/ApiErrorException.php b/app/Exceptions/Api/v2/ApiErrorException.php new file mode 100644 index 0000000..9077e08 --- /dev/null +++ b/app/Exceptions/Api/v2/ApiErrorException.php @@ -0,0 +1,30 @@ +statusCode = $statusCode; + parent::__construct($error->label, $error->value, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function report() + { + Log::debug(sprintf('CRM API: %s', $this->getMessage())); + } +} diff --git a/app/Exceptions/Api/v2/CrmException.php b/app/Exceptions/Api/v2/CrmException.php new file mode 100644 index 0000000..aab59f4 --- /dev/null +++ b/app/Exceptions/Api/v2/CrmException.php @@ -0,0 +1,14 @@ +getMessage())); + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 7e40d73..4199b4f 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,17 +2,28 @@ namespace App\Exceptions; +use App\Enums\ApiError; +use App\Exceptions\Api\v2\ApiErrorException; +use App\Exceptions\Api\v2\CrmException; +use App\Lib\Helper; +use Illuminate\Auth\AuthenticationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +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 +45,90 @@ class Handler extends ExceptionHandler { // } + + /** + * Report or log an exception. + * + * @param \Throwable $exception + * @return void + * + * @throws \Exception + */ + public function report(Throwable $e) + { + try { + if (!config('app.debug')) { + if ($this->shouldntReport($e)) { + return; + } + Mail::send('mails.exception', ['exception' => $e], function ($m) { + $m->to( + config('mail.contactsList.serviceweb'), + sprintf('%s Exception Handler', config('app.name')) + )->subject('Need help !, an error occured in your application, please check Logs ASAP'); + }); + } + } 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 + */ + public function render($request, Throwable $e) + { + + if (Helper::isRequestForApiV2($request)) { + switch (get_class($e)) { + case AuthenticationException::class: + /** @var AuthenticationException $e */ + return response()->json([ + 'message' => sprintf('%s, %s', ApiError::needAuth()->label, $e->getMessage()), + 'code' => ApiError::needAuth()->value, + ], 407); + case MethodNotAllowedHttpException::class: + /** @var MethodNotAllowedHttpException $e */ + return response()->json([ + 'message' => sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage()), + 'code' => ApiError::methodNotAllowed()->value, + ], $e->getStatusCode()); + case ValidationException::class: + /** @var ValidationException $e */ + return response()->json([ + 'message' => sprintf('%s, %s', ApiError::validationFailed()->label, $e->getMessage()), + 'code' => ApiError::validationFailed()->value, + 'errors' => $e->errors() + ], $e->status); + case ModelNotFoundException::class: + /** @var ModelNotFoundException $e */ + return response()->json([ + 'message' => sprintf( + ApiError::modelNotFound()->label, + \array_slice(explode('\\', $e->getModel()), -1)[0] + ), + 'code' => ApiError::modelNotFound()->value + ], 404); + case ApiErrorException::class: + /** @var ApiErrorException $e */ + return response()->json([ + 'message' => $e->getMessage(), + 'code' => $e->getCode() + ], $e->getStatusCode()); + case CrmException::class: + default: + return response()->json([ + 'message' => $e->getMessage(), + 'code' => $e->getCode() + ], 500); + } + } + return parent::render($request, $e); + } } diff --git a/app/Http/Controllers/Api/v1/ArcadiaController.php b/app/Http/Controllers/Api/v1/ArcadiaController.php index 04f9c5c..c5be303 100644 --- a/app/Http/Controllers/Api/v1/ArcadiaController.php +++ b/app/Http/Controllers/Api/v1/ArcadiaController.php @@ -33,9 +33,9 @@ class ArcadiaController extends Controller } $brandId = $request->brand_id; - $crmId = $request->crm_id; - $fields = json_decode($request->fields); - $type = $request->type; + $crmId = $request->crm_id; + $fields = json_decode($request->fields); + $type = $request->type; $customer = Customer::where('id', $crmId)->first(); if (!$customer) { @@ -53,15 +53,14 @@ class ArcadiaController extends Controller return new StatusResource($check); } - $xml = $this->makeXML($fields, $type, $customer, $CustomerInfo); + $xml = $this->makeXml($fields, $type, $customer, $CustomerInfo); try { Storage::disk('arcadia')->put(date("Ymd_His") . ".xml", $xml); return new StatusResource(['status' => 'OK']); } catch (\Exception $e) { Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage()); - Mail::to(config('mail.MAIL_ADMIN')) - ->send(new CourrierArcadiaError( + 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 @@ -163,7 +162,7 @@ class ArcadiaController extends Controller return ['status' => 'OK']; } - private function makeXML($fields, $type, $customer, $CustomerInfo) + private function makeXml($fields, $type, $customer, $CustomerInfo) { $pivot = [ 'order_num' => 'Num_Commande', // required if type = default - format 9 numeric characters @@ -182,7 +181,7 @@ class ArcadiaController extends Controller ]; - $dom = new DOMDocument('1.0', 'UTF-8'); + $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElement('export_arcadia'); diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/v1/CustomerController.php index 7acd931..5e5ff77 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/v1/CustomerController.php @@ -42,19 +42,19 @@ class CustomerController extends Controller ]); } - $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 = $email; $customer->save(); $singleOptin = SingleOptin::where([['email', $email], ['brand_id', $brandId]])->first(); @@ -72,47 +72,40 @@ class CustomerController extends Controller } } - $CustomerInfo = new CustomerInfo(); - $CustomerInfo->firstname = $firstname; - $CustomerInfo->lastname = $lastname; - $CustomerInfo->birth_date = !empty($request->birth_date) ? - filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->address = !empty($request->address) ? - filter_var($request->address, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->address2 = !empty($request->address2) ? - filter_var($request->address2, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->phone = !empty($request->phone) ? - filter_var($request->phone, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->country = !empty($request->country) ? - filter_var($request->country, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->city = !empty($request->city) ? - filter_var($request->city, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->postcode = !empty($request->postcode) ? - filter_var($request->postcode, FILTER_SANITIZE_STRING) : null; - $CustomerInfo->opt_in = $optIn; - $CustomerInfo->hashcode = $hashcode; - $CustomerInfo->brand_id = $brandId; + $CustomerInfo = new CustomerInfo(); + $CustomerInfo->firstname = $firstname; + $CustomerInfo->lastname = $lastname; + $CustomerInfo->birth_date = !empty($request->birth_date) ? filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->address = !empty($request->address) ? filter_var($request->address, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->address2 = !empty($request->address2) ? filter_var($request->address2, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->phone = !empty($request->phone) ? filter_var($request->phone, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->country = !empty($request->country) ? filter_var($request->country, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->city = !empty($request->city) ? filter_var($request->city, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->postcode = !empty($request->postcode) ? filter_var($request->postcode, FILTER_SANITIZE_STRING) : null; + $CustomerInfo->opt_in = $optIn; + $CustomerInfo->hashcode = $hashcode; + $CustomerInfo->brand_id = $brandId; $CustomerInfo->customer_id = $customer->id; $customer->infos()->save($CustomerInfo); // 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); @@ -171,7 +164,7 @@ class CustomerController extends Controller ]); } - $brandId = $request->brand_id; + $brandId = $request->brand_id; $customer = Customer::find($id); if (!$customer) { @@ -224,10 +217,10 @@ class CustomerController extends Controller 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(); } @@ -258,8 +251,8 @@ class CustomerController extends Controller ]); } - $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) { @@ -327,7 +320,7 @@ class CustomerController extends Controller ]); } - $brandId = $request->brand_id; + $brandId = $request->brand_id; $customer = Customer::find($id); $password = trim($request->password); @@ -335,7 +328,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.']); } @@ -434,10 +427,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(); @@ -458,11 +451,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(); @@ -471,10 +465,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(); @@ -486,7 +480,8 @@ class CustomerController extends Controller } } - public function addSingleOptin(Request $request) { + public function addSingleOptin(Request $request) + { $validator = Validator::make($request->all(), [ 'email' => 'required|email', ]); @@ -499,7 +494,7 @@ class CustomerController extends Controller } $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 // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace @@ -509,8 +504,8 @@ class CustomerController extends Controller ['brand_id', $brandId], ['email', $email] ])->exists() ) { - $singleOptin = new SingleOptin(); - $singleOptin->email = $email; + $singleOptin = new SingleOptin(); + $singleOptin->email = $email; $singleOptin->brand_id = $brandId; $singleOptin->save(); } else { diff --git a/app/Http/Controllers/Api/v2/ApiController.php b/app/Http/Controllers/Api/v2/ApiController.php new file mode 100644 index 0000000..8e76c5d --- /dev/null +++ b/app/Http/Controllers/Api/v2/ApiController.php @@ -0,0 +1,33 @@ +json(); + } + + public function authenticate(Request $request) + { + try { + /** @var \App\Models\Service */ + $service = Service::getFromBearerToken($request->bearerToken()); + $service->generateApiToken(); + $service->saveOrFail(); + return response()->json([ + 'api_token' => $service->api_token + ]); + } catch (ModelNotFoundException $e) { + throw new ApiErrorException(ApiError::authFailed()); + } + } +} diff --git a/app/Http/Controllers/Api/v2/ArcadiaController.php b/app/Http/Controllers/Api/v2/ArcadiaController.php new file mode 100644 index 0000000..38b67c0 --- /dev/null +++ b/app/Http/Controllers/Api/v2/ArcadiaController.php @@ -0,0 +1,86 @@ +info($service->brand)->firstOrFail(); + $type = ArcadiaMailType::make(intval($request->type)); + $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 ApiErrorException(ApiError::arcardiaXmlMailFailed()); + } + } + + /** + * 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( + SendArchadiaMailRequest $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(); + } +} diff --git a/app/Http/Controllers/Api/v2/CustomerController.php b/app/Http/Controllers/Api/v2/CustomerController.php new file mode 100644 index 0000000..c09d944 --- /dev/null +++ b/app/Http/Controllers/Api/v2/CustomerController.php @@ -0,0 +1,180 @@ +validated(); + /** @var \App\Models\Customer */ + $customer = Customer::where('email', $data->email)->firstOrCreate([ + 'email' => $data->email + ]); + $customer->saveOrFail(); + $customer->setOptIn($service->brand, $data->opt_in); + + /** @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 + { + 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 + { + // 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 + { + // 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()) { + throw new CrmException(sprintf('Could not delete customer %d for brand %d', $customer, $service->brand)); + } + + if (!$customer->logins()->count() and !$customer->infos()->count()) { + if ($customer->delete() === false) { + throw new CrmException(sprintf('Could not delete customer %d', $customer)); + } + } + + 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) + { + $login = $customer->login($service->brand)->firstOrFail(); + + if ($login->status === 0) { + $login->status = 1; + $login->save(); + return response()->json(); + } else { + throw new ApiErrorException(ApiError::customerAlreadyActive()); + } + } +} diff --git a/app/Http/Controllers/Api/v2/OptinController.php b/app/Http/Controllers/Api/v2/OptinController.php new file mode 100644 index 0000000..c6605ac --- /dev/null +++ b/app/Http/Controllers/Api/v2/OptinController.php @@ -0,0 +1,39 @@ +email)->firstOrNew(); + + if ($request->optin) { + if (CustomerInfo::getFromEmail($service->brand, $request->email)->where('opt_in', true)->exists()) { + throw new ApiErrorException(ApiError::singleOptinCustomerExists()); + } + $singleOptin->fill($request->validated()); + $singleOptin->brand()->associate($service->brand); + $singleOptin->saveOrFail(); + } else { + $singleOptin->delete(); + } + return response()->json(); + } +} diff --git a/app/Http/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php index 61a2e4d..eeecf63 100644 --- a/app/Http/Controllers/BrandController.php +++ b/app/Http/Controllers/BrandController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Http\Controllers\Controller; +use App\Http\Requests\ManageBrandRequest; use App\Models\Brand; use Illuminate\Http\Request; @@ -14,27 +15,16 @@ class BrandController extends Controller return view('brands.index', compact('brands')); } - public function manage(Request $request, $id = null) + public function manage(ManageBrandRequest $request, Brand $brand) { - $brand = null; - - if ($id !== null) { - $brand = Brand::find($id); - } - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $request->validate([ - 'name' => 'required|max:255', - ]); - + 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')); diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 83d12d7..888da63 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -17,7 +17,7 @@ class CustomerController extends Controller public function show($id, $brandId) { - $CustomerInfo = CustomerInfo::where([['customer_id', $id], ['brand_id', $brandId]])->first(); + $CustomerInfo = CustomerInfo::where([['customer_id', $id], ['brand_id', $brandId]])->first(); $customerLogin = CustomerLogin::where([['customer_id', $id], ['brand_id', $brandId]])->first(); if (!$CustomerInfo) { abort(404); @@ -28,46 +28,36 @@ class CustomerController extends Controller public function filter($texte, $brandId = null) { if ($brandId !== null) { - $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([ + $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([ + ])->orWhere([ ['email', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brandId], ['customer_login.brand_id', $brandId] - ]) - ->get(); + ])->get(); } else { - $customers = CustomerInfo::join('customers', 'customers.id', '=', 'customer_infos.customer_id') - ->join('customer_login', function ($join) { + $customers = CustomerInfo::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(); + })->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(); } return response()->json($customers); diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index f7033f7..e779f47 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -23,8 +23,7 @@ class LoginController extends Controller ) { 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."); } } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 976e7d8..14f5675 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -2,10 +2,9 @@ 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 { @@ -15,54 +14,34 @@ class ServiceController extends Controller return view('services.index', compact('services')); } - public function manage(Request $request, $id = null) + public function manage(ManageServiceRequest $request, Service $service = null) { - $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) + public function generateApiToken(Service $service) { - $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->generateBearerToken(); $service->save(); - - return back()->with('success', "Nouveau token d'API de " . $service->name . " : " . $apiToken); + return back()->with('success', "Nouveau token d'API de " . $service->name . " : " . $service->bearer_token); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index f500585..3bd80bf 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -6,6 +6,7 @@ use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { + /** * The application's global HTTP middleware stack. * @@ -20,7 +21,10 @@ class Kernel extends HttpKernel \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, + \App\Http\Middleware\XssSanitizer::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \App\Http\Middleware\ApiResponse::class, + \App\Http\Middleware\ApiCheckTimeStamps::class ]; /** diff --git a/app/Http/Middleware/ApiCheckTimeStamps.php b/app/Http/Middleware/ApiCheckTimeStamps.php new file mode 100644 index 0000000..1937357 --- /dev/null +++ b/app/Http/Middleware/ApiCheckTimeStamps.php @@ -0,0 +1,43 @@ +hasHeader('X-API-TIMESTAMP') or !is_numeric($request->header('X-API-TIMESTAMP'))) { + throw $badRequest; + } + if (!$request->timestamp or !is_numeric($request->timestamp)) { + throw $badRequest; + } + $diff = intval($request->timestamp) - intval($request->header('X-API-TIMESTAMP')); + $maxDiff = 120; + if (abs($diff) > $maxDiff) { + throw $badRequest; + } + } + return $next($request); + } +} diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php new file mode 100644 index 0000000..b202432 --- /dev/null +++ b/app/Http/Middleware/ApiResponse.php @@ -0,0 +1,30 @@ +getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO(); + $response->setData(new V2ApiResponse((object)$response->getData(), $status)); + } + return $response; + } +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 704089a..1c843e5 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -2,10 +2,87 @@ namespace App\Http\Middleware; +use App\Lib\Helper; +use App\Models\Service; +use Closure; +use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Middleware\Authenticate as Middleware; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Facades\Route; +/** + * 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) + { + $isApiV1Request = $request->isJson() && Helper::isRequestForApiV1($request); + $isApiV2Request = $request->isJson() && Helper::isRequestForApiV2($request); + + if ($isApiV2Request) { + //! Is Bearer Token needed ? + // 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)) { + $this->unauthenticated($request, $guards); + } + + /** + ** 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))) { + $this->unauthenticated($request, $guards); + } + + try { + $service = Service::getFromApiToken($apiToken); + $service->authenticate($guards, $apiToken); + $route->setParameter('service', $service); + } catch (ModelNotFoundException $e) { + throw new AuthenticationException('Unauthenticated.', $guards); + } + } + + // TODO: check this works + if ($isApiV1Request) { + // Use patched v1 Auth mecanism (without bearer reimplementation) + $bearerToken = $request->bearerToken(); + $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) + ); + } + + if ($isApiV2Request || $isApiV1Request) { + Service::incrementStat($apiToken); + } + + return $response; + } /** * Get the path the user should be redirected to when they are not authenticated. * diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index 7db2566..b21ce83 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -11,6 +11,7 @@ use Illuminate\Http\Request; class CheckBrand { + private $apiToken; /** @@ -39,10 +40,10 @@ class CheckBrand die("Le token d'authentification est incorrect"); } - $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); - $serviceStat->brand_id = $brand->id; + $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->nb_calls = $serviceStat->nb_calls === 0 ? 1 : $serviceStat->nb_calls + 1; $serviceStat->save(); $request->request->add(['brand_id' => $brand->id]); diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 033136a..5cf30b3 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -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. * diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php index e4956d0..2a8072e 100644 --- a/app/Http/Middleware/PreventRequestsDuringMaintenance.php +++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -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. * diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index 5a50e7b..7eb899a 100644 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -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. * diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 14befce..919aec9 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -7,6 +7,7 @@ use Illuminate\Http\Request; class TrustProxies extends Middleware { + /** * The trusted proxies for this application. * diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 0c13b85..680465e 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -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. * diff --git a/app/Http/Middleware/XssSanitizer.php b/app/Http/Middleware/XssSanitizer.php new file mode 100644 index 0000000..5285138 --- /dev/null +++ b/app/Http/Middleware/XssSanitizer.php @@ -0,0 +1,25 @@ +all(); + array_walk_recursive($input, function (&$input) { + $input = strip_tags($input); + }); + $request->merge($input); + return $next($request); + } +} diff --git a/app/Http/Requests/Api/v2/AuthenticateRequest.php b/app/Http/Requests/Api/v2/AuthenticateRequest.php new file mode 100644 index 0000000..309798e --- /dev/null +++ b/app/Http/Requests/Api/v2/AuthenticateRequest.php @@ -0,0 +1,29 @@ + 'required|email:rfc|exists:customers,email', + 'password' => 'required' + ]; + } +} diff --git a/app/Http/Requests/Api/v2/BaseApiRequest.php b/app/Http/Requests/Api/v2/BaseApiRequest.php new file mode 100644 index 0000000..ac681da --- /dev/null +++ b/app/Http/Requests/Api/v2/BaseApiRequest.php @@ -0,0 +1,37 @@ + 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; +} diff --git a/app/Http/Requests/Api/v2/SendArchadiaMailRequest.php b/app/Http/Requests/Api/v2/SendArchadiaMailRequest.php new file mode 100644 index 0000000..fdb564b --- /dev/null +++ b/app/Http/Requests/Api/v2/SendArchadiaMailRequest.php @@ -0,0 +1,68 @@ + Trim::class, + 'state_order' => Trim::class, + 'operation' => Trim::class, + 'support_code' => Trim::class, + 'order_date' => Trim::class, + 'order_num' => RemoveNonNumeric::class, + 'package_unit_weight' => RemoveNonNumeric::class, + 'code_unique' => Trim::class + ]; + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'type' => [ + 'required', + new EnumIsValidRule(ArcadiaMailType::class) + ], + 'operation' => 'required|int', + 'support_code' => 'required|int', + 'label_type_support' => 'required|string', + 'order_date' => 'required|date', + // IF default + 'order_num' => [ + 'required_if:type,' . ArcadiaMailType::default()->value, + 'numeric', + 'digits:9' + ], + 'state_order' => [ + 'required_if:type,' . ArcadiaMailType::default()->value, + new EnumIsValidRule(ArcadiaOrderType::class) + ], + 'package_unit_weight' => [ + 'required_if:type,' . ArcadiaMailType::default()->value, + 'integer' + ], + // IF game + 'code_unique' => [ + 'required_if:type,' . ArcadiaMailType::game()->value, + 'string' + ], + 'code_on_pack' => [ + 'sometimes', + 'string' + ] + ]; + } +} diff --git a/app/Http/Requests/Api/v2/SingleOptinRequest.php b/app/Http/Requests/Api/v2/SingleOptinRequest.php new file mode 100644 index 0000000..432c4f6 --- /dev/null +++ b/app/Http/Requests/Api/v2/SingleOptinRequest.php @@ -0,0 +1,14 @@ + 'required|email:rfc,dns,spoof,filter', + 'optin' => 'required|boolean' + ]; + } +} diff --git a/app/Http/Requests/Api/v2/StoreCustomerRequest.php b/app/Http/Requests/Api/v2/StoreCustomerRequest.php new file mode 100644 index 0000000..28039ac --- /dev/null +++ b/app/Http/Requests/Api/v2/StoreCustomerRequest.php @@ -0,0 +1,78 @@ + [ + 'required', + 'string', + 'min:2' + ], + 'lastname' => [ + 'required', + 'string', + 'min:2' + ], + 'email' => [ + 'required', + 'email:rfc,dns,spoof,filter' + ], + 'password' => [ + 'required', + 'case_diff', + 'numbers', + 'letters', + 'symbols', + 'min:10' + ], + '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' + ], + ]; + } +} diff --git a/app/Http/Requests/Api/v2/StoreSingleOptinRequest.php b/app/Http/Requests/Api/v2/StoreSingleOptinRequest.php new file mode 100644 index 0000000..3cdc1cc --- /dev/null +++ b/app/Http/Requests/Api/v2/StoreSingleOptinRequest.php @@ -0,0 +1,13 @@ + 'required|email:rfc,dns,spoof,filter' + ]; + } +} diff --git a/app/Http/Requests/Api/v2/UpdateCustomerRequest.php b/app/Http/Requests/Api/v2/UpdateCustomerRequest.php new file mode 100644 index 0000000..11547c6 --- /dev/null +++ b/app/Http/Requests/Api/v2/UpdateCustomerRequest.php @@ -0,0 +1,54 @@ + rtrim(\implode(',', $fields), ',') + ])); + } + }; + return [ + 'firstname' => [$atLeast, 'string', 'min:2'], + 'lastname' => [$atLeast, 'string', 'min:2'], + 'email' => [$atLeast, 'email:rfc,dns,spoof,filter'], + 'password' => $atLeast, + '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'], + ]; + } +} diff --git a/app/Http/Requests/Api/v2/UpdateSingleOptinRequest.php b/app/Http/Requests/Api/v2/UpdateSingleOptinRequest.php new file mode 100644 index 0000000..53d1648 --- /dev/null +++ b/app/Http/Requests/Api/v2/UpdateSingleOptinRequest.php @@ -0,0 +1,14 @@ + 'required|email:rfc,dns,spoof,filter', + 'optin' => 'required|boolean' + ]; + } +} diff --git a/app/Http/Requests/ManageBrandRequest.php b/app/Http/Requests/ManageBrandRequest.php new file mode 100644 index 0000000..c12b7ea --- /dev/null +++ b/app/Http/Requests/ManageBrandRequest.php @@ -0,0 +1,45 @@ + [ + 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 []; + } +} diff --git a/app/Http/Requests/ManageServiceRequest.php b/app/Http/Requests/ManageServiceRequest.php new file mode 100644 index 0000000..250a0d1 --- /dev/null +++ b/app/Http/Requests/ManageServiceRequest.php @@ -0,0 +1,46 @@ + [ + 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 []; + } +} diff --git a/app/Http/Resources/Api/v2/ApiResponse.php b/app/Http/Resources/Api/v2/ApiResponse.php new file mode 100644 index 0000000..b201c12 --- /dev/null +++ b/app/Http/Resources/Api/v2/ApiResponse.php @@ -0,0 +1,47 @@ +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') + ]; + } +} diff --git a/app/Http/Resources/Api/v2/CustomerResource.php b/app/Http/Resources/Api/v2/CustomerResource.php new file mode 100644 index 0000000..2c55fc9 --- /dev/null +++ b/app/Http/Resources/Api/v2/CustomerResource.php @@ -0,0 +1,87 @@ + '', + 'firstname' => '', + 'lastname' => '', + 'email' => '', + 'birth_date' => '', + 'address' => '', + 'address2' => '', + 'phone' => '', + 'country' => '', + 'city' => '', + 'postcode' => '', + 'opt_in' => '', + 'hashcode' => '', + '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 + * + crm_id int + * + 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) + { + return array_intersect_key( + array_merge( + [ + 'crm_id' => $this->customer->id, + 'email' => $this->customer->email + ], + $this->login->toArray(), + $this->info->toArray(), + ['status' => $this->login->status] + ), + $this->getSkeleton() + ); + } + + public static function getSkeleton(): array + { + return self::$skeleton; + } +} diff --git a/app/Http/Resources/Api/v2/SingleOptinResource.php b/app/Http/Resources/Api/v2/SingleOptinResource.php new file mode 100644 index 0000000..e89231f --- /dev/null +++ b/app/Http/Resources/Api/v2/SingleOptinResource.php @@ -0,0 +1,21 @@ + $request->email + ]; + } +} diff --git a/app/Http/Resources/CustomerResource.php b/app/Http/Resources/CustomerResource.php index f01f773..66102dd 100644 --- a/app/Http/Resources/CustomerResource.php +++ b/app/Http/Resources/CustomerResource.php @@ -6,6 +6,7 @@ use Illuminate\Http\Resources\Json\JsonResource; class CustomerResource extends JsonResource { + private $brandId; public function __construct($resource, $brandId) @@ -20,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(); diff --git a/app/Http/Resources/StatusResource.php b/app/Http/Resources/StatusResource.php index 60f87ae..effa264 100644 --- a/app/Http/Resources/StatusResource.php +++ b/app/Http/Resources/StatusResource.php @@ -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); - } } diff --git a/app/Lib/Helper.php b/app/Lib/Helper.php new file mode 100644 index 0000000..327f9f4 --- /dev/null +++ b/app/Lib/Helper.php @@ -0,0 +1,74 @@ +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)); + } +} diff --git a/app/Mail/CourrierArcadiaError.php b/app/Mail/CourrierArcadiaError.php index bf6bb5f..52a6255 100644 --- a/app/Mail/CourrierArcadiaError.php +++ b/app/Mail/CourrierArcadiaError.php @@ -13,7 +13,9 @@ class CourrierArcadiaError extends Mailable implements ShouldQueue use SerializesModels; public $subject; + public $msg; + public $xml; /** @@ -24,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; } /** @@ -36,8 +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'] + ); } } diff --git a/app/Models/Brand.php b/app/Models/Brand.php index 2140143..8900982 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -9,6 +9,10 @@ class Brand extends Model { use HasFactory; + protected $fillable = [ + 'name' + ]; + /** * Service hasMany relation * @@ -49,11 +53,13 @@ class Brand extends Model return $this->hasMany(SingleOptin::class); } - public function getAccountsAttribute(){ - return $this->customer_infos()->count(); + public function getAccountsAttribute() + { + return $this->customerInfos()->count(); } - public function getTotalOptinsAttribute(){ - return $this->customer_infos()->where('opt_in', 1)->count() + $this->single_optins()->count(); + public function getTotalOptinsAttribute() + { + return $this->customerInfos()->where('opt_in', 1)->count() + $this->singleOptins()->count(); } } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 70fcfed..ffeee64 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -2,13 +2,19 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Hash; class Customer extends Model { use HasFactory; + protected $fillable = [ + 'email' + ]; + /** * CustomerInfo hasMany relation * @@ -19,6 +25,21 @@ class Customer extends Model return $this->hasMany(CustomerInfo::class); } + /** + * 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); + }); + } + /** * CustomerLogin hasMany relation * @@ -29,6 +50,21 @@ class Customer extends Model 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 * @@ -38,4 +74,24 @@ class Customer extends Model { 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); + } + + 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(); + } } diff --git a/app/Models/CustomerInfo.php b/app/Models/CustomerInfo.php index 4b4d0cb..3b3fdc3 100644 --- a/app/Models/CustomerInfo.php +++ b/app/Models/CustomerInfo.php @@ -2,12 +2,40 @@ 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 = [ + 'country' => Country::class + ]; + + protected $fillable = [ + 'firstname', + 'lastname', + 'birth_date', + 'address', + 'address2', + 'phone', + 'country', + 'city', + 'postcode', + 'opt_in' + ]; + + protected static function booted() + { + static::creating(function ($customerInfo) { + $customerInfo->hashcode = Str::uuid(); + }); + } /** * Customer BelongsTo relation @@ -28,4 +56,20 @@ class CustomerInfo extends Model { 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); + }); + } } diff --git a/app/Models/CustomerLogin.php b/app/Models/CustomerLogin.php index ac056ef..8b7bfd6 100644 --- a/app/Models/CustomerLogin.php +++ b/app/Models/CustomerLogin.php @@ -4,11 +4,31 @@ 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 $fillable = [ + 'password' + ]; + + protected static function booted() + { + static::creating(function ($customerLogin) { + $customerLogin->password = Hash::make(trim($customerLogin->password)); + }); + static::updating(function ($customerLogin) { + $customerLogin->password = Hash::make(trim($customerLogin->password)); + }); + } + + public function passwordMatch(string $password) + { + return Hash::check($password, $this->password); + } + /** * Customer BelongsTo relation * @@ -18,4 +38,14 @@ class CustomerLogin extends Model { return $this->belongsTo(Customer::class); } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index b8df6fc..b8c740d 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,13 +2,104 @@ namespace App\Models; +use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Support\Str; class Service extends Model { use HasFactory; + protected $fillable = [ + 'name' + ]; + + protected static function booted() + { + static::creating(function ($service) { + $service->generateBearerToken(); + }); + } + + public function generateApiToken() + { + $this->api_token = hash('sha256', Str::random(80)); + $this->api_token_date = now(); + return $this->api_token; + } + + public function generateBearerToken() + { + $this->bearer_token = hash('sha256', Str::random(80)); + $this->bearer_token_date = now(); + return $this->bearer_token; + } + + /** + * Undocumented function + * + * @param string $apiToken + * @return boolean + */ + public static function incrementStat(string $apiToken): bool + { + try { + $service = self::getFromApiToken($apiToken); + $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); + $serviceStat->nb_calls += 1; + $serviceStat->service()->associate($service); + $serviceStat->brand()->associate($service->brand); + $serviceStat->saveOrFail(); + return true; + } catch (ModelNotFoundException $e) { + return false; + } + } + + /** + * Get Service from apiToken with its brand + * + * @param string $apiToken + * @return \App\Models\Service|null + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public static function getFromApiToken(string $apiToken) + { + return static::where('api_token', $apiToken)->with('brand')->firstOrFail(); + } + + /** + * Get Service from bearerToken with its brand + * + * @param string $bearerToken + * @return \App\Models\Service|null + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public static function getFromBearerToken(string $bearerToken) + { + return static::where('bearer_token', $bearerToken)->with('brand')->firstOrFail(); + } + + /** + * Authenticate from a token string + * + * @param array $guards + * @param string $apiToken + * @return void + */ + public function authenticate(array $guards, string $apiToken) + { + + if ($this->api_token !== $apiToken) { + throw new AuthenticationException('Unauthenticated.', $guards); + } + if ((abs(strtotime($this->api_token_date) - time()) * 60) > (config('api.tokenTimeout') * 60)) { + throw new AuthenticationException('Unauthenticated.', $guards); + } + } + /** * Brand BelongsTo relation * diff --git a/app/Models/ServiceStat.php b/app/Models/ServiceStat.php index c08112f..9750b84 100644 --- a/app/Models/ServiceStat.php +++ b/app/Models/ServiceStat.php @@ -11,6 +11,10 @@ class ServiceStat extends Model protected $fillable = ['date']; + protected $attributes = [ + 'nb_calls' => 0 + ]; + /** * Brand BelongsTo relation * diff --git a/app/Models/SingleOptin.php b/app/Models/SingleOptin.php index 311424b..7776219 100644 --- a/app/Models/SingleOptin.php +++ b/app/Models/SingleOptin.php @@ -9,6 +9,10 @@ class SingleOptin extends Model { use HasFactory; + protected $fillable = [ + 'email' + ]; + /** * Brand BelongsTo relation * diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index ce74491..551e307 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { + /** * The policy mappings for the application. * diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a9f10a6..cf1e2ab 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { + /** * The event listener mappings for the application. * diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f3b67e5..5d93da2 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -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; @@ -38,14 +39,13 @@ 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) { + return Customer::where('id', $value)->orWhere('email', $value)->firstOrFail(); + }); }); } diff --git a/composer.json b/composer.json index 4244354..cbfda4c 100644 --- a/composer.json +++ b/composer.json @@ -12,12 +12,16 @@ "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.12", - "laravel/tinker": "^2.5" + "laravel/tinker": "^2.5", + "schuppo/password-strength": "~2.0" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/config/api.php b/config/api.php new file mode 100644 index 0000000..0055c7d --- /dev/null +++ b/config/api.php @@ -0,0 +1,14 @@ + '2.0.0', + + /** + * Api Token timeout in minutes + */ + 'tokenTimeout' => 60 +]; diff --git a/config/app.php b/config/app.php index bc1a0c6..5fc14cf 100644 --- a/config/app.php +++ b/config/app.php @@ -39,7 +39,7 @@ return [ | */ - 'debug' => (bool) env('APP_DEBUG', false), + 'debug' => (bool)env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- @@ -226,7 +226,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 ], ]; diff --git a/config/auth.php b/config/auth.php index 73f4486..62110d7 100644 --- a/config/auth.php +++ b/config/auth.php @@ -44,7 +44,7 @@ return [ 'api' => [ 'driver' => 'token', 'provider' => 'services', - 'hash' => true, + 'hash' => false, // Says that tokens are already hashed in DB ], ], diff --git a/config/hashing.php b/config/hashing.php index 8425770..108db6a 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -30,6 +30,7 @@ return [ 'bcrypt' => [ 'rounds' => env('BCRYPT_ROUNDS', 10), + 'verify' => true ], /* diff --git a/database/factories/BrandFactory.php b/database/factories/BrandFactory.php new file mode 100644 index 0000000..d8b08aa --- /dev/null +++ b/database/factories/BrandFactory.php @@ -0,0 +1,29 @@ + $this->faker->company, + ]; + } +} diff --git a/database/factories/CustomerFactory.php b/database/factories/CustomerFactory.php new file mode 100644 index 0000000..fe11a7d --- /dev/null +++ b/database/factories/CustomerFactory.php @@ -0,0 +1,29 @@ + $this->faker->unique()->safeEmail, + ]; + } +} diff --git a/database/factories/CustomerInfoFactory.php b/database/factories/CustomerInfoFactory.php new file mode 100644 index 0000000..81d4845 --- /dev/null +++ b/database/factories/CustomerInfoFactory.php @@ -0,0 +1,43 @@ +faker->addProvider(new \Faker\Provider\fr_FR\Address($this->faker)); + $this->faker->addProvider(new \Faker\Provider\fr_FR\PhoneNumber($this->faker)); + $countries = Country::toValues(); + return [ + 'firstname' => $this->faker->firstName, + 'lastname' => $this->faker->lastName, + 'birth_date' => $this->faker->dateTimeBetween('-60 years', '-18 years'), + 'address' => $this->faker->address, + 'address2' => '', + 'phone' => $this->faker->phoneNumber, + 'country' => $countries[\array_rand($countries)], + 'city' => $this->faker->city, + 'postcode' => $this->faker->postcode, + 'opt_in' => $this->faker->boolean, + 'hashcode' => $this->faker->sha1 + ]; + } +} diff --git a/database/factories/CustomerLoginFactory.php b/database/factories/CustomerLoginFactory.php new file mode 100644 index 0000000..e7749b4 --- /dev/null +++ b/database/factories/CustomerLoginFactory.php @@ -0,0 +1,32 @@ + 'testPassword', + 'last_connection' => $this->faker->date, + 'status' => $this->faker->boolean + ]; + } +} diff --git a/database/factories/ServiceFactory.php b/database/factories/ServiceFactory.php new file mode 100644 index 0000000..5d18187 --- /dev/null +++ b/database/factories/ServiceFactory.php @@ -0,0 +1,29 @@ + $this->faker->lexify('?????????') + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index bdea1a3..af316d5 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -8,6 +8,7 @@ use Illuminate\Support\Str; class UserFactory extends Factory { + /** * The name of the factory's corresponding model. * diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index f6aae68..51015b6 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -4,7 +4,6 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -// phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace class CreateUsersTable extends Migration { /** diff --git a/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php b/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php new file mode 100644 index 0000000..a6b3e3c --- /dev/null +++ b/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php @@ -0,0 +1,59 @@ +dropForeign(['customer_id']); + $table->foreign('customer_id')->references('id') + ->on('customers')->onDelete('cascade'); + $table->dropForeign(['brand_id']); + $table->foreign('brand_id')->references('id') + ->on('brands')->onDelete('cascade'); + }); + Schema::table('customer_logins', function (Blueprint $table) { + $table->dropForeign('customer_login_customer_id_foreign'); + $table->foreign('customer_id')->references('id') + ->on('customers')->onDelete('cascade'); + $table->dropForeign('customer_login_brand_id_foreign'); + $table->foreign('brand_id')->references('id') + ->on('brands')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customer_infos', function (Blueprint $table) { + $table->dropForeign(['customer_id']); + $table->foreign('customer_id')->references('id') + ->on('brands')->onUpdate('restrict')->onDelete('restrict'); + $table->dropForeign(['brand_id']); + $table->foreign('brand_id')->references('id') + ->on('brands')->onUpdate('restrict')->onDelete('restrict'); + }); + Schema::table('customer_logins', function (Blueprint $table) { + $table->dropForeign(['customer_id']); + $table->foreign('customer_id', 'customer_login_customer_id_foreign')->references('id') + ->on('brands')->onUpdate('restrict')->onDelete('restrict'); + $table->dropForeign(['brand_id']); + $table->foreign('brand_id', 'customer_login_brand_id_foreign')->references('id') + ->on('brands')->onUpdate('restrict')->onDelete('restrict'); + }); + } +} diff --git a/database/migrations/2021_03_15_073249_rename_api_token_on_services_table.php b/database/migrations/2021_03_15_073249_rename_api_token_on_services_table.php new file mode 100644 index 0000000..3ce1bd1 --- /dev/null +++ b/database/migrations/2021_03_15_073249_rename_api_token_on_services_table.php @@ -0,0 +1,35 @@ +renameColumn('api_token', 'bearer_token'); + $table->renameColumn('api_token_date', 'bearer_token_date'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->renameColumn('bearer_token', 'api_token'); + $table->renameColumn('bearer_token_date', 'api_token_date'); + }); + } +} diff --git a/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php b/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php new file mode 100644 index 0000000..0c8f373 --- /dev/null +++ b/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php @@ -0,0 +1,35 @@ +string('api_token')->nullable()->after('bearer_token_date'); + $table->timestamp('api_token_date')->after('api_token')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('api_token'); + $table->dropColumn('api_token_date'); + }); + } +} diff --git a/database/seeders/BrandSeeder.php b/database/seeders/BrandSeeder.php new file mode 100644 index 0000000..f4ed875 --- /dev/null +++ b/database/seeders/BrandSeeder.php @@ -0,0 +1,29 @@ +create()->each(function ($brand) { + Service::factory(1)->make()->each( + /** @var \App\Models\Service $service*/ + function ($service) use ($brand) { + $service->brand()->associate($brand); + $service->save(); + } + ); + }); + } +} diff --git a/database/seeders/CustomerSeeder.php b/database/seeders/CustomerSeeder.php new file mode 100644 index 0000000..a9933a2 --- /dev/null +++ b/database/seeders/CustomerSeeder.php @@ -0,0 +1,42 @@ +create()->each( + function ($customer) { + /** @var \App\Models\Brand */ + foreach (Brand::all() as $brand) { + CustomerInfo::factory(1)->make()->each( + function ($customerInfo) use ($customer, $brand) { + $customerInfo->customer()->associate($customer); + $customerInfo->brand()->associate($brand); + $customerInfo->save(); + } + ); + CustomerLogin::factory(1)->make()->each( + function ($customerLogin) use ($customer, $brand) { + $customerLogin->customer()->associate($customer); + $customerLogin->brand()->associate($brand); + $customerLogin->save(); + } + ); + } + } + ); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 57b73b5..e584dcf 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,6 +2,8 @@ namespace Database\Seeders; +use App\Models\Brand; +use App\Models\Customer; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder @@ -13,6 +15,9 @@ class DatabaseSeeder extends Seeder */ public function run() { - // \App\Models\User::factory(10)->create(); + $this->call([ + BrandSeeder::class, + CustomerSeeder::class + ]); } } diff --git a/phpcs.xml b/phpcs.xml index f1e70de..3601517 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,13 +1,91 @@ - - + + app/ config/ + database/factories + database/seeders routes/ tests/ - - - - + + + + + Created with the PHP Coding Standard Generator. http://edorian.github.com/php-coding-standard-generator/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */database/migrations/* + diff --git a/phpunit.xml b/phpunit.xml index 4ae4d97..5a77d2a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,7 +9,7 @@ ./tests/Unit - ./tests/Feature + ./tests/Feature/ApiV2 diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 2e2820b..9296ce2 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -130,11 +130,7 @@ return [ | */ - 'custom' => [ - 'attribute-name' => [ - 'rule-name' => 'custom-message', - ], - ], + 'require_at_least_field' => 'At least one of these fields :attributes must be present.', /* |-------------------------------------------------------------------------- @@ -147,6 +143,12 @@ return [ | */ - 'attributes' => [], + 'attributes' => [ + // Rajouté + 'postcode' => 'post code', + 'birth_date' => 'birth date', + 'lastname' => 'last name', + 'firstname' => 'first name' + ], ]; diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php new file mode 100644 index 0000000..bf72ee2 --- /dev/null +++ b/resources/lang/fr/auth.php @@ -0,0 +1,18 @@ + 'Ces identifiants ne correspondent pas à nos enregistrements', + 'password' => 'Le mot de passe fourni est incorrect.', + 'throttle' => 'Tentatives de connexion trop nombreuses. Veuillez essayer de nouveau dans :seconds secondes.', +]; diff --git a/resources/lang/fr/pagination.php b/resources/lang/fr/pagination.php new file mode 100644 index 0000000..8c1080e --- /dev/null +++ b/resources/lang/fr/pagination.php @@ -0,0 +1,17 @@ + '« Précédent', + 'next' => 'Suivant »', +]; diff --git a/resources/lang/fr/passwords.php b/resources/lang/fr/passwords.php new file mode 100644 index 0000000..2f898d3 --- /dev/null +++ b/resources/lang/fr/passwords.php @@ -0,0 +1,20 @@ + 'Votre mot de passe a été réinitialisé !', + 'sent' => 'Nous vous avons envoyé par email le lien de réinitialisation du mot de passe !', + 'throttled' => 'Veuillez patienter avant de réessayer.', + 'token' => "Ce jeton de réinitialisation du mot de passe n'est pas valide.", + 'user' => "Aucun utilisateur n'a été trouvé avec cette adresse email.", +]; diff --git a/resources/lang/fr/validation-inline.php b/resources/lang/fr/validation-inline.php new file mode 100644 index 0000000..5ec7269 --- /dev/null +++ b/resources/lang/fr/validation-inline.php @@ -0,0 +1,177 @@ + 'Ce champ doit être accepté.', + 'active_url' => "Ce n'est pas une URL valide", + 'after' => 'La date doit être postérieure au :date.', + 'after_or_equal' => 'La date doit être postérieure ou égale au :date.', + 'alpha' => 'Ce champ doit contenir uniquement des lettres', + 'alpha_dash' => 'Ce champ doit contenir uniquement des lettres, des chiffres et des tirets.', + 'alpha_num' => 'Ce champ doit contenir uniquement des chiffres et des lettres.', + 'array' => 'Ce champ doit être un tableau.', + 'before' => 'Ce champ doit être une date antérieure au :date.', + 'before_or_equal' => 'Ce champ doit être une date antérieure ou égale au :date.', + 'between' => [ + 'numeric' => 'La valeur doit être comprise entre :min et :max.', + 'file' => 'La taille du fichier doit être comprise entre :min et :max kilo-octets.', + 'string' => 'Le texte doit contenir entre :min et :max caractères.', + 'array' => 'Le tableau doit contenir entre :min et :max éléments.', + ], + 'boolean' => 'Ce champ doit être vrai ou faux.', + 'confirmed' => 'Le champ de confirmation ne correspond pas.', + 'date' => "Ce n'est pas une date valide.", + 'date_equals' => 'La date doit être égale à :date.', + 'date_format' => 'Ce champ ne correspond pas au format :format.', + 'different' => 'Cette valeur doit être différente de :other.', + 'digits' => 'Ce champ doit contenir :digits chiffres.', + 'digits_between' => 'Ce champ doit contenir entre :min et :max chiffres.', + 'dimensions' => "La taille de l'image n'est pas conforme.", + 'distinct' => 'Ce champ a une valeur en double.', + 'email' => 'Ce champ doit être une adresse email valide.', + 'ends_with' => 'Ce champ doit se terminer par une des valeurs suivantes : :values', + 'exists' => 'Ce champ sélectionné est invalide.', + 'file' => 'Ce champ doit être un fichier.', + 'filled' => 'Ce champ doit avoir une valeur.', + 'gt' => [ + 'numeric' => 'La valeur doit être supérieure à :value.', + 'file' => 'La taille du fichier doit être supérieure à :value kilo-octets.', + 'string' => 'Le texte doit contenir plus de :value caractères.', + 'array' => 'Le tableau doit contenir plus de :value éléments.', + ], + 'gte' => [ + 'numeric' => 'La valeur doit être supérieure ou égale à :value.', + 'file' => 'La taille du fichier doit être supérieure ou égale à :value kilo-octets.', + 'string' => 'Le texte doit contenir au moins :value caractères.', + 'array' => 'Le tableau doit contenir au moins :value éléments.', + ], + 'image' => 'Ce champ doit être une image.', + 'in' => 'Ce champ est invalide.', + 'in_array' => "Ce champ n'existe pas dans :other.", + 'integer' => 'Ce champ doit être un entier.', + 'ip' => 'Ce champ doit être une adresse IP valide.', + 'ipv4' => 'Ce champ doit être une adresse IPv4 valide.', + 'ipv6' => 'Ce champ doit être une adresse IPv6 valide.', + 'json' => 'Ce champ doit être un document JSON valide.', + 'lt' => [ + 'numeric' => 'La valeur doit être inférieure à :value.', + 'file' => 'La taille du fichier doit être inférieure à :value kilo-octets.', + 'string' => 'Le texte doit contenir moins de :value caractères.', + 'array' => 'Le tableau doit contenir moins de :value éléments.', + ], + 'lte' => [ + 'numeric' => 'La valeur doit être inférieure ou égale à :value.', + 'file' => 'La taille du fichier doit être inférieure ou égale à :value kilo-octets.', + 'string' => 'Le texte doit contenir au plus :value caractères.', + 'array' => 'Le tableau doit contenir au plus :value éléments.', + ], + 'max' => [ + 'numeric' => 'La valeur ne peut être supérieure à :max.', + 'file' => 'La taille du fichier ne peut pas dépasser :max kilo-octets.', + 'string' => 'Le texte ne peut contenir plus de :max caractères.', + 'array' => 'Le tableau ne peut contenir plus de :max éléments.', + ], + 'mimes' => 'Le fichier doit être de type : :values.', + 'mimetypes' => 'Le fichier doit être de type : :values.', + 'min' => [ + 'numeric' => 'La valeur doit être supérieure ou égale à :min.', + 'file' => 'La taille du fichier doit être supérieure à :min kilo-octets.', + 'string' => 'Le texte doit contenir au moins :min caractères.', + 'array' => 'Le tableau doit contenir au moins :min éléments.', + ], + 'multiple_of' => 'La valeur doit être un multiple de :value', + 'not_in' => "Le champ sélectionné n'est pas valide.", + 'not_regex' => "Le format du champ n'est pas valide.", + 'numeric' => 'Ce champ doit contenir un nombre.', + 'password' => 'Le mot de passe est incorrect', + 'present' => 'Ce champ doit être présent.', + 'regex' => 'Le format du champ est invalide.', + 'required' => 'Ce champ est obligatoire.', + 'required_if' => 'Ce champ est obligatoire quand la valeur de :other est :value.', + 'required_unless' => 'Ce champ est obligatoire sauf si :other est :values.', + 'required_with' => 'Ce champ est obligatoire quand :values est présent.', + 'required_with_all' => 'Ce champ est obligatoire quand :values sont présents.', + 'required_without' => "Ce champ est obligatoire quand :values n'est pas présent.", + 'required_without_all' => "Ce champ est requis quand aucun de :values n'est présent.", + 'same' => 'Ce champ doit être identique à :other.', + 'size' => [ + 'numeric' => 'La valeur doit être :size.', + 'file' => 'La taille du fichier doit être de :size kilo-octets.', + 'string' => 'Le texte doit contenir :size caractères.', + 'array' => 'Le tableau doit contenir :size éléments.', + ], + 'starts_with' => 'Ce champ doit commencer avec une des valeurs suivantes : :values', + 'string' => 'Ce champ doit être une chaîne de caractères.', + 'timezone' => 'Ce champ doit être un fuseau horaire valide.', + 'unique' => 'La valeur est déjà utilisée.', + 'uploaded' => "Le fichier n'a pu être téléversé.", + 'url' => "Le format de l'URL n'est pas valide.", + 'uuid' => 'Ce champ doit être un UUID valide', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'require_at_least_field' => 'Au moins un des champs :attributes doit être présent.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => [ + 'name' => 'nom', + 'username' => "nom d'utilisateur", + 'email' => 'adresse email', + 'first_name' => 'prénom', + 'last_name' => 'nom', + 'password' => 'mot de passe', + 'password_confirmation' => 'confirmation du mot de passe', + 'city' => 'ville', + 'country' => 'pays', + 'address' => 'adresse', + 'phone' => 'téléphone', + 'mobile' => 'portable', + 'age' => 'âge', + 'sex' => 'sexe', + 'gender' => 'genre', + 'day' => 'jour', + 'month' => 'mois', + 'year' => 'année', + 'hour' => 'heure', + 'minute' => 'minute', + 'second' => 'seconde', + 'title' => 'titre', + 'content' => 'contenu', + 'description' => 'description', + 'excerpt' => 'extrait', + 'date' => 'date', + 'time' => 'heure', + 'available' => 'disponible', + 'size' => 'taille', + ], +]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php new file mode 100644 index 0000000..8133d52 --- /dev/null +++ b/resources/lang/fr/validation.php @@ -0,0 +1,182 @@ + 'Le champ :attribute doit être accepté.', + 'active_url' => "Le champ :attribute n'est pas une URL valide.", + 'after' => 'Le champ :attribute doit être une date postérieure au :date.', + 'after_or_equal' => 'Le champ :attribute doit être une date postérieure ou égale au :date.', + 'alpha' => 'Le champ :attribute doit contenir uniquement des lettres.', + 'alpha_dash' => 'Le champ :attribute doit contenir uniquement des lettres, des chiffres et des tirets.', + 'alpha_num' => 'Le champ :attribute doit contenir uniquement des chiffres et des lettres.', + 'array' => 'Le champ :attribute doit être un tableau.', + 'before' => 'Le champ :attribute doit être une date antérieure au :date.', + 'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.', + 'between' => [ + 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', + 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', + 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', + 'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.', + ], + 'boolean' => 'Le champ :attribute doit être vrai ou faux.', + 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', + 'date' => "Le champ :attribute n'est pas une date valide.", + 'date_equals' => 'Le champ :attribute doit être une date égale à :date.', + 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', + 'different' => 'Les champs :attribute et :other doivent être différents.', + 'digits' => 'Le champ :attribute doit contenir :digits chiffres.', + 'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.', + 'dimensions' => "La taille de l'image :attribute n'est pas conforme.", + 'distinct' => 'Le champ :attribute a une valeur en double.', + 'email' => 'Le champ :attribute doit être une adresse email valide.', + 'ends_with' => 'Le champ :attribute doit se terminer par une des valeurs suivantes : :values', + 'exists' => 'Le champ :attribute sélectionné est invalide.', + 'file' => 'Le champ :attribute doit être un fichier.', + 'filled' => 'Le champ :attribute doit avoir une valeur.', + 'gt' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure à :value.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir plus de :value caractères.', + 'array' => 'Le tableau :attribute doit contenir plus de :value éléments.', + ], + 'gte' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.', + 'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au moins :value caractères.', + 'array' => 'Le tableau :attribute doit contenir au moins :value éléments.', + ], + 'image' => 'Le champ :attribute doit être une image.', + 'in' => 'Le champ :attribute est invalide.', + 'in_array' => "Le champ :attribute n'existe pas dans :other.", + 'integer' => 'Le champ :attribute doit être un entier.', + 'ip' => 'Le champ :attribute doit être une adresse IP valide.', + 'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.', + 'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.', + 'json' => 'Le champ :attribute doit être un document JSON valide.', + 'lt' => [ + 'numeric' => 'La valeur de :attribute doit être inférieure à :value.', + 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir moins de :value caractères.', + 'array' => 'Le tableau :attribute doit contenir moins de :value éléments.', + ], + 'lte' => [ + 'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.', + 'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au plus :value caractères.', + 'array' => 'Le tableau :attribute doit contenir au plus :value éléments.', + ], + 'max' => [ + 'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', + 'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.', + 'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', + 'array' => 'Le tableau :attribute ne peut contenir plus de :max éléments.', + ], + 'mimes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'mimetypes' => 'Le champ :attribute doit être un fichier de type : :values.', + 'min' => [ + 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.', + 'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.', + 'string' => 'Le texte :attribute doit contenir au moins :min caractères.', + 'array' => 'Le tableau :attribute doit contenir au moins :min éléments.', + ], + 'multiple_of' => 'La valeur de :attribute doit être un multiple de :value', + 'not_in' => "Le champ :attribute sélectionné n'est pas valide.", + 'not_regex' => "Le format du champ :attribute n'est pas valide.", + 'numeric' => 'Le champ :attribute doit contenir un nombre.', + 'password' => 'Le mot de passe est incorrect', + 'present' => 'Le champ :attribute doit être présent.', + 'regex' => 'Le format du champ :attribute est invalide.', + 'required' => 'Le champ :attribute est obligatoire.', + 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', + 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', + 'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.', + 'required_with_all' => 'Le champ :attribute est obligatoire quand :values sont présents.', + 'required_without' => "Le champ :attribute est obligatoire quand :values n'est pas présent.", + 'required_without_all' => "Le champ :attribute est requis quand aucun de :values n'est présent.", + 'same' => 'Les champs :attribute et :other doivent être identiques.', + 'size' => [ + 'numeric' => 'La valeur de :attribute doit être :size.', + 'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', + 'string' => 'Le texte de :attribute doit contenir :size caractères.', + 'array' => 'Le tableau :attribute doit contenir :size éléments.', + ], + 'starts_with' => 'Le champ :attribute doit commencer avec une des valeurs suivantes : :values', + 'string' => 'Le champ :attribute doit être une chaîne de caractères.', + 'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.', + 'unique' => 'La valeur du champ :attribute est déjà utilisée.', + 'uploaded' => "Le fichier du champ :attribute n'a pu être téléversé.", + 'url' => "Le format de l'URL de :attribute n'est pas valide.", + 'uuid' => 'Le champ :attribute doit être un UUID valide', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'require_at_least_field' => 'Au moins un des champs :attributes doit être présent.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => [ + 'name' => 'nom', + 'username' => "nom d'utilisateur", + 'email' => 'adresse email', + 'first_name' => 'prénom', + 'last_name' => 'nom', + 'password' => 'mot de passe', + 'password_confirmation' => 'confirmation du mot de passe', + 'city' => 'ville', + 'country' => 'pays', + 'address' => 'adresse', + 'phone' => 'téléphone', + 'mobile' => 'portable', + 'age' => 'âge', + 'sex' => 'sexe', + 'gender' => 'genre', + 'day' => 'jour', + 'month' => 'mois', + 'year' => 'année', + 'hour' => 'heure', + 'minute' => 'minute', + 'second' => 'seconde', + 'title' => 'titre', + 'content' => 'contenu', + 'description' => 'description', + 'excerpt' => 'extrait', + 'date' => 'date', + 'time' => 'heure', + 'available' => 'disponible', + 'size' => 'taille', + + // Rajouté + 'postcode' => 'code postal', + 'birth_date' => 'année de naissance', + 'lastname' => 'nom', + 'firstname' => 'prénom' + ], +]; diff --git a/resources/views/brands/index.blade.php b/resources/views/brands/index.blade.php index 24965ab..1c63dbd 100644 --- a/resources/views/brands/index.blade.php +++ b/resources/views/brands/index.blade.php @@ -2,15 +2,15 @@ @section('content')
-

Gestion des marques Liste

- Ajouter +

{{ __('Gestion des marques') }} {{ __('Liste') }}

+ {{ __('Ajouter') }}
- - - - + + + + @@ -19,8 +19,8 @@ - - + + @endforeach diff --git a/resources/views/brands/manage.blade.php b/resources/views/brands/manage.blade.php index a2aa29a..036e0f0 100644 --- a/resources/views/brands/manage.blade.php +++ b/resources/views/brands/manage.blade.php @@ -2,21 +2,28 @@ @section('content')
-

Gestion des marques Gestion

+

{{ __('Gestion des marques') }} {{ __('Gestion') }}

+ @if ($errors->any()) +
    + @foreach(session('errors')->all(':message') as $error) +
  • {{ $error }}
  • + @endforeach +
+ @endif @if (session('success')) {!! session('success') !!} @endif - Retour + {{ __('Retour') }}
-
+ @csrf
- - + +
- +
diff --git a/resources/views/mail/arcadiaOrders/default.blade.php b/resources/views/mail/arcadiaOrders/default.blade.php new file mode 100644 index 0000000..5b55cbe --- /dev/null +++ b/resources/views/mail/arcadiaOrders/default.blade.php @@ -0,0 +1,41 @@ + + + + email }}]]> + + lastname }}]]> + firstname }}]]> + address }}]]> + address2 }}]]> + postcode }}]]> + city }}]]> + phone }}]]> +@if($type->equals(ArcadiaMailType::default())) +@if($orderType->equals(ArcadiaOrderType::cb())) + 1 + Null@elseif($orderType->equals(ArcadiaOrderType::check())) + Null + 1@endif @endif + + {{ $request->order_num }} + {{ Carbon::parse($request->order_date)->format('d/m/Y') }} + {{ $request->operation }} + {{ $request->support_code }} + {{ $request->label_type_support }} + {{ $request->package_unit_weight }} +@if(!is_null($request->qty)) + {{-- {{ $request->qty }} --}} + {{-- Locked by Arcadia to quantity = 1 --}} + 1 +@endif + +@if($type->equals(ArcadiaMailType::game())) +@if(!is_null($request->code_on_pack)) + code_on_pack }}]]> +@endif +@if(!is_null($request->code_unique)) + code_unique }}]]> +@endif +@endif + + diff --git a/resources/views/mail/exception.blade.php b/resources/views/mail/exception.blade.php new file mode 100644 index 0000000..5d0a7ab --- /dev/null +++ b/resources/views/mail/exception.blade.php @@ -0,0 +1,17 @@ + + + + {{ config('app.name') }} Exception Handler + + + +

Une erreur est survenue sur {{ config('app.name') config('app.url') }}

+

{{ $exception->getMessage() }}

+

date : {{ date('Y-m-d H:i:s') }}

+

code : {{$exception->getCode() }}

+

file : {{$exception->getFile() }}

+

line : {{$exception->getLine() }}

+

Veuillez vérifier les logs pour en voir la trace.

+ + + diff --git a/resources/views/services/index.blade.php b/resources/views/services/index.blade.php index 38fb450..372f0de 100644 --- a/resources/views/services/index.blade.php +++ b/resources/views/services/index.blade.php @@ -2,22 +2,22 @@ @section('content')
-

Gestion des services Liste

+

{{ __('Gestion des services') }} {{ __('Liste') }}

@if (session('success')) {!! session('success') !!} @endif @if (session('error')) {!! session('error') !!} @endif - Ajouter + {{ __('Ajouter') }}
Nom de la marqueNombre de comptesNombre d'optinsDate d'ajout en base{{ __('Nom de la marque') }}{{ __('Nombre de comptes') }}{{ __('Nombre d\'optins') }}{{ __('Date d\'ajout en base') }}
{{ $brand->name }} {{ $brand->accounts }} {{ $brand->total_optins }}{{ \Carbon\Carbon::parse($brand->created_at)->format('d/m/Y à H:i') }}Modifier{{ Carbon::parse($brand->created_at)->format('d/m/Y à H:i') }}{{ __('Modifier') }}
- - - - - + + + + + @@ -25,10 +25,10 @@ - - - - + + + + @endforeach diff --git a/resources/views/services/manage.blade.php b/resources/views/services/manage.blade.php index ff7ff7c..f888168 100644 --- a/resources/views/services/manage.blade.php +++ b/resources/views/services/manage.blade.php @@ -2,31 +2,40 @@ @section('content')
-

Gestion des services Gestion

+

{{ __('Gestion des services') }} {{ __('Gestion') }}

+ @if ($errors->any()) +
    + @foreach(session('errors')->all(':message') as $error) +
  • {{ $error }}
  • + @endforeach +
+ @endif @if (session('success')) {!! session('success') !!} @endif - Retour + {{ __('Retour') }}
@csrf
- +
@if (!$edit)
- +
+ @else + @endif
- +
diff --git a/routes/api.php b/routes/api.php index 4136a0d..66cc312 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,6 +3,10 @@ use Illuminate\Support\Facades\Route; use App\Http\Controllers\Api\v1\CustomerController; use App\Http\Controllers\Api\v1\ArcadiaController; +use App\Http\Controllers\Api\v2\ApiController as V2ApiController; +use App\Http\Controllers\Api\v2\ArcadiaController as V2ArcadiaController; +use App\Http\Controllers\Api\v2\CustomerController as V2CustomerController; +use App\Http\Controllers\Api\v2\OptinController as V2OptinController; /* |-------------------------------------------------------------------------- @@ -33,3 +37,21 @@ Route::prefix('v1')->middleware(['auth:api', 'check-brand'])->group(function () // Arcadia Route::post('/arcadia/outgoing-mail', [ArcadiaController::class, 'outgoingMail']); }); + +Route::prefix('v2')->name('apiV2.')->group(function () { + Route::get('/ping', [V2ApiController::class, 'ping'])->name('ping'); + Route::post('/auth', [V2ApiController::class, 'authenticate'])->name('authenticate'); + Route::middleware('auth:api')->group(function () { + Route::post('/optin', [V2OptinController::class, 'optin'])->name('optin'); + Route::apiResource('customer', V2CustomerController::class)->except(['index']); + Route::prefix('customer')->name('customer.')->group(function () { + Route::post('/authenticate', [V2CustomerController::class, 'authenticate'])->name('authenticate'); + Route::prefix('{customer}')->group(function () { + Route::prefix('arcadia')->name('arcadia.')->group(function () { + Route::post('/outgoing-mail', [V2ArcadiaController::class, 'outgoingMail'])->name('outgoingMail'); + }); + Route::post('/activate', [V2CustomerController::class, 'activateAccount'])->name('activate'); + }); + }); + }); +}); diff --git a/routes/channels.php b/routes/channels.php index 5d451e1..28b4539 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -14,5 +14,5 @@ use Illuminate\Support\Facades\Broadcast; */ Broadcast::channel('App.Models.User.{id}', function ($user, $id) { - return (int) $user->id === (int) $id; + return (int)$user->id === (int)$id; }); diff --git a/routes/web.php b/routes/web.php index 0d7d182..6921e96 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,25 +25,26 @@ Route::middleware(['auth'])->group(function () { return redirect()->route('customers'); }); Route::get('/logout', [LoginController::class, 'logout'])->name('logout'); + // Marques Route::get('/brands', [BrandController::class, 'index'])->name('brands'); - Route::match(['get', 'post'], '/brand/manage/{id?}', [BrandController::class, 'manage']) - ->name('manage-brand'); + Route::match(['get', 'post'], '/brand/manage/{brand?}', [BrandController::class, 'manage'])->name('manage-brand'); + // Services Route::get('/services', [ServiceController::class, 'index'])->name('services'); - Route::match(['get', 'post'], '/service/manage/{id?}', [ServiceController::class, 'manage']) + Route::match(['get', 'post'], '/service/manage/{service?}', [ServiceController::class, 'manage']) ->name('manage-service'); - Route::get('/services/{id}/token/generate', [ServiceController::class, 'generateApiToken']) + Route::get('/services/{service}/token/generate', [ServiceController::class, 'generateApiToken']) ->name('generate-token-service'); + // Clients (Customer) Route::get('/customers', [CustomerController::class, 'index'])->name('customers'); - Route::get('/customers/filter/{texte}/{brandId?}', [CustomerController::class, 'filter']) - ->name('filter-customer'); + Route::get('/customers/filter/{texte}/{brandId?}', [CustomerController::class, 'filter'])->name('filter-customer'); Route::get('/customer/{id}/{brandId}', [CustomerController::class, 'show'])->name('show-customer'); + // Single optins Route::get('/single-optins', [SingleOptinsController::class, 'index'])->name('single-optins'); Route::get('/single-optins/filter/{texte}/{brandId?}', [SingleOptinsController::class, 'filter']) ->name('filter-single-optins'); - Route::post('/single-optins/{id}/delete', [SingleOptinsController::class, 'delete']) - ->name('single-optins-delete'); + Route::post('/single-optins/{id}/delete', [SingleOptinsController::class, 'delete'])->name('single-optins-delete'); }); diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php new file mode 100644 index 0000000..ed0d559 --- /dev/null +++ b/tests/Feature/ApiV2/ApiBase.php @@ -0,0 +1,84 @@ +assertFragment(['status' => $status->value]); + } + + /** + * Raw Function to call api + * + * @param string $method + * @param string $uri + * @param array $data + * @param array $headers + * @return \Illuminate\Testing\TestResponse + */ + protected function callApi(string $method, string $uri, array $data = [], array $headers = []): TestResponse + { + $time = \time(); + $headers['X-API-TIMESTAMP'] = $time; + $data['timestamp'] = $time; + return $this->json($method, $uri, $data, $headers); + } + + /** + * Raw Function to call api with Token + * + * @param Customer $customer + * @param Service $service + * @param string $method + * @param string $uri + * @param array $data + * @param array $headers + * @return \Illuminate\Testing\TestResponse + */ + protected function callApiWithToken( + Service $service, + string $method, + string $uri, + array $data = [], + array $headers = [] + ): TestResponse { + $apiToken = $service->api_token; + if (!$apiToken) { + $apiToken = $service->generateApiToken(); + $service->saveOrFail(); + } + $data['api_token'] = $apiToken; + return $this->callApi($method, $uri, $data, $headers); + } + + /** + * @return array + */ + protected function getCustomer() + { + /** @var \App\Models\Costumer $customer */ + $customer = Customer::whereHas('logins', function ($q) { + $q->where('status', true); + })->inRandomOrder()->firstOrFail(); + /** @var \App\Models\Brand $brand */ + $brand = $customer->logins()->where('status', true)->firstOrFail()->brand; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + + return compact('customer', 'service', 'brand'); + } +} diff --git a/tests/Feature/ApiV2/ApiTest.php b/tests/Feature/ApiV2/ApiTest.php new file mode 100644 index 0000000..5b286f2 --- /dev/null +++ b/tests/Feature/ApiV2/ApiTest.php @@ -0,0 +1,53 @@ +callApi('get', route('apiV2.ping')); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + + $response = $this->json('get', route('apiV2.ping'), ['timestamp' => time()], [ + 'X-API-TIMESTAMP' => time() + 119 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $response = $this->json('get', route('apiV2.ping'), ['timestamp' => time()], [ + 'X-API-TIMESTAMP' => time() + 121 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + } + + /** + * route: apiV2.authenticate + */ + public function testApiAuthenticate() + { + extract($this->getCustomer()); + $response = $this->callApi( + 'post', + route('apiV2.authenticate'), + [], + ['Authorization' => sprintf('Bearer %s', $service->bearer_token)] + ); + + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertNotEmpty($json->json('data')); + $this->assertNotEmpty($json->json('data.api_token')); + $this->assertEquals(Service::findOrFail($service->id)->api_token, $json->json('data.api_token')); + } +} diff --git a/tests/Feature/ApiV2/ArcadiaTest.php b/tests/Feature/ApiV2/ArcadiaTest.php new file mode 100644 index 0000000..6c102da --- /dev/null +++ b/tests/Feature/ApiV2/ArcadiaTest.php @@ -0,0 +1,65 @@ +getCustomer()); + $data = [ + 'type' => ArcadiaMailType::default()->value, + 'operation' => 16, + 'label_type_support' => 'Send Departaimants', + 'order_date' => \date('Y/m/d H:i:s'), + 'state_order' => ArcadiaOrderType::cb()->value, + 'support_code' => 32, + 'label_type_support' => 'Fond carte Aimanté', + 'order_num' => str_pad('23', 9, '0', STR_PAD_LEFT), + 'qty' => 1, + 'package_unit_weight' => 200, + ]; + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.customer.arcadia.outgoingMail', + compact('customer') + ), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } + + public function testArcadiaSendOrderGame() + { + extract($this->getCustomer()); + $data = [ + 'type' => ArcadiaMailType::game()->value, + 'operation' => 16, + 'order_date' => date('Y/m/d H:i:s'), + 'support_code' => 32, + 'label_type_support' => 'Jeux Reghalal', + 'code_unique' => Str::uuid(), + 'code_on_pack' => 'anystring' + ]; + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.customer.arcadia.outgoingMail', + compact('customer') + ), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } +} diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php new file mode 100644 index 0000000..3b0629c --- /dev/null +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -0,0 +1,272 @@ +getCustomer()); + $infos = $customer->infos; + $logins = $customer->logins; + $brands = $customer->brands; + $optInHistory = $customer->optInHistory; + $this->assertInstanceOf(Collection::class, $infos); + $this->assertInstanceOf(Collection::class, $logins); + $this->assertInstanceOf(Collection::class, $brands); + $this->assertInstanceOf(Collection::class, $optInHistory); + $this->assertInstanceOf(CustomerInfo::class, $customer->info($brands->first())->first()); + $this->assertInstanceOf(CustomerLogin::class, $customer->login($brands->first())->first()); + } + + /** + * route: apiV2.customer.authenticate + */ + public function testApiAuthenticateCustomer() + { + extract($this->getCustomer()); + $response = $this->callApiWithToken( + $service, + 'post', + route('apiV2.customer.authenticate', compact('customer')), + [ + 'email' => $customer->email, + 'password' => 'testPassword' + ] + ); + + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertNotEmpty($json->json('data')); + $this->assertNotEmpty($json->json('data.crm_id')); + $this->assertNotEmpty($json->json('data.firstname')); + $this->assertNotEmpty($json->json('data.lastname')); + $this->assertNotEmpty($json->json('data.birth_date')); + $this->assertNotEmpty($json->json('data.address')); + $this->assertNotEmpty($json->json('data.phone')); + $this->assertNotEmpty($json->json('data.country')); + $this->assertNotEmpty($json->json('data.city')); + $this->assertNotEmpty($json->json('data.postcode')); + $this->assertNotEmpty($json->json('data.hashcode')); + } + + /** + * route: apiV2.customer.store + */ + public function testApiStoreCustomer() + { + extract($this->getCustomer()); + $data = [ + 'firstname' => 'Jean', + 'lastname' => 'Bond', + 'birth_date' => \date('1992-03-08'), + 'address' => '10 impasse Maryse Bastié', + 'email' => 'warthogdj@hotmail.fr', + 'password' => '123.Mot.De.P4sse', + 'phone' => '+33654821562', + 'country' => (string)Country::france(), + 'city' => 'Connerré', + 'postcode' => '72160', + 'opt_in' => true + ]; + $response = $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertNotEmpty($json->json('data.crm_id')); + $this->assertNotEmpty($json->json('data.firstname')); + $this->assertNotEmpty($json->json('data.lastname')); + $this->assertNotEmpty($json->json('data.birth_date')); + $this->assertNotEmpty($json->json('data.address')); + $this->assertNotEmpty($json->json('data.phone')); + $this->assertNotEmpty($json->json('data.country')); + $this->assertNotEmpty($json->json('data.city')); + $this->assertNotEmpty($json->json('data.postcode')); + $this->assertNotEmpty($json->json('data.hashcode')); + + $this->assertEquals(\strip_tags($data['firstname']), $json->json('data.firstname')); + $this->assertEquals($data['lastname'], $json->json('data.lastname')); + $this->assertEquals($data['birth_date'], $json->json('data.birth_date')); + $this->assertEquals($data['address'], $json->json('data.address')); + $this->assertEquals($data['phone'], $json->json('data.phone')); + $this->assertEquals($data['country'], $json->json('data.country')); + $this->assertEquals($data['city'], $json->json('data.city')); + $this->assertEquals($data['postcode'], $json->json('data.postcode')); + + $response = $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::customerExists()->value, $json->json('data.code')); + $this->assertEquals(ApiError::customerExists()->label, $json->json('data.message')); + + + $data['password'] = 'password'; + $response = $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); + $this->assertEquals( + ApiError::validationFailed()->label . ', The given data was invalid.', + $json->json('data.message') + ); + } + + /** + * route: apiV2.customer.activate + */ + public function testActivateCustomer() + { + /** @var \App\Models\Costumer $customer */ + $customer = Customer::whereHas('logins', function ($q) { + $q->where('status', false); + })->inRandomOrder()->firstOrFail(); + /** @var \App\Models\Brand $brand */ + $brand = $customer->logins()->where('status', false)->firstOrFail()->brand; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.customer.activate', + compact('customer') + ), []); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.customer.activate', + compact('customer') + ), []); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::customerAlreadyActive()->value, $json->json('data.code')); + $this->assertEquals(ApiError::customerAlreadyActive()->label, $json->json('data.message')); + } + + /** + * route: apiV2.customer.update + */ + public function testApiUpdateCustomer() + { + extract($this->getCustomer()); + $data = [ + 'firstname' => 'Jean2', + 'lastname' => 'Bond2', + 'birth_date' => \date('1992-03-09'), + 'address' => '10 impasse Maryse Bastié2', + 'email' => 'warthogdj2@hotmail.fr', + 'password' => '123.Mot.De.P4sseNew', + 'phone' => '+33654821563', + 'country' => (string)Country::bahamas(), + 'city' => 'Connerré2', + 'postcode' => '72161', + 'opt_in' => 0 + ]; + + foreach ($data as $rowName => $row) { + $response = $this->callApiWithToken($service, 'patch', route( + 'apiV2.customer.update', + compact('customer') + ), [$rowName => $row]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + if ($rowName != 'password') { + $this->assertNotNull($json->json('data.' . $rowName), "Failed asserting $rowName is not null"); + $this->assertEquals( + $row, + $json->json('data.' . $rowName), + "Failed asserting $rowName is equal to $row" + ); + } else { + $this->assertNull($json->json('data.' . $rowName), "Failed asserting $rowName is null"); + $this->assertEquals( + null, + $json->json('data.' . $rowName), + "Failed asserting $rowName is equal to $row" + ); + } + } + $response = $this->callApiWithToken($service, 'patch', route('apiV2.customer.update', compact('customer')), [ + 'address2' => 'anything' + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); + $this->assertEquals( + ApiError::validationFailed()->label . ', The given data was invalid.', + $json->json('data.message') + ); + } + + /** + * route: apiV2.customer.show + */ + public function testApiGetCustomer() + { + extract($this->getCustomer()); + + $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', compact('customer'))); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertNotEmpty($json->json('data.crm_id')); + $this->assertNotEmpty($json->json('data.firstname')); + $this->assertNotEmpty($json->json('data.lastname')); + $this->assertNotEmpty($json->json('data.birth_date')); + $this->assertNotEmpty($json->json('data.address')); + $this->assertNotEmpty($json->json('data.phone')); + $this->assertNotEmpty($json->json('data.country')); + $this->assertNotEmpty($json->json('data.city')); + $this->assertNotEmpty($json->json('data.postcode')); + $this->assertNotEmpty($json->json('data.hashcode')); + + $csClassName = \array_slice(explode('\\', get_class($customer)), -1)[0]; + $customer = 9999999; + + $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', compact('customer'))); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::modelNotFound()->value, $json->json('data.code')); + $this->assertEquals( + sprintf(ApiError::modelNotFound()->label, $csClassName), + $json->json('data.message') + ); + } + + /** + * route: apiV2.customer.destroy + */ + public function testApiDestroyCustomer() + { + extract($this->getCustomer()); + $brands = $customer->brands()->get(); + foreach ($brands as $brand) { + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken($service, 'delete', route( + 'apiV2.customer.destroy', + compact('customer') + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertEmpty($json->json('data')); + } + } +} diff --git a/tests/Feature/ApiV2/SingleOptinTest.php b/tests/Feature/ApiV2/SingleOptinTest.php new file mode 100644 index 0000000..30e52f8 --- /dev/null +++ b/tests/Feature/ApiV2/SingleOptinTest.php @@ -0,0 +1,74 @@ +callApiWithToken($service, 'post', route('apiV2.optin'), [ + 'email' => 'warthogdj@hotmail.fr', + 'optin' => 1 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + + $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ + 'email' => 'warthogdj@hotmail.fr', + 'optin' => 0 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertNull(SingleOptin::where('email', 'warthogdj@hotmail.fr')->first()); + } + + public function testSingleOptinOnCustomerExsits() + { + $service = Service::firstOrFail(); + $this->storeCustomer($service); + $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ + 'email' => 'warthogdj@hotmail.fr', + 'optin' => 1 + ]); + + $json = $response->decodeResponseJson(); + // dd($json); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::singleOptinCustomerExists()->value, $json->json('data.code')); + $this->assertEquals(ApiError::singleOptinCustomerExists()->label, $json->json('data.message')); + } + + private function storeCustomer(Service $service) + { + $data = [ + 'firstname' => 'Jean', + 'lastname' => 'Bond', + 'birth_date' => \date('1992-03-08'), + 'address' => '10 impasse Maryse Bastié', + 'email' => 'warthogdj@hotmail.fr', + 'password' => '123.Mot.De.P4sse', + 'phone' => '+33654821562', + 'country' => (string)Country::france(), + 'city' => 'Connerré', + 'postcode' => '72160', + 'opt_in' => true + ]; + $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php deleted file mode 100644 index cdb5111..0000000 --- a/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,21 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index 358cfc8..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue(true); - } -} diff --git a/tests/Unit/HelperTest.php b/tests/Unit/HelperTest.php new file mode 100644 index 0000000..6010166 --- /dev/null +++ b/tests/Unit/HelperTest.php @@ -0,0 +1,53 @@ +makeRequest('apiV2.ping'); + $this->assertInstanceOf(\Illuminate\Routing\Route::class, Helper::getRoute($request)); + } + + public function testGetRoutePrefix() + { + $request = $this->makeRequest('apiV2.ping'); + $this->assertEquals($request->route()->getPrefix(), Helper::getRoutePrefix($request)); + } + + public function testGetRouteName() + { + $request = $this->makeRequest('apiV2.ping'); + $this->assertEquals($request->route()->getName(), Helper::getRouteName($request)); + } + + public function testIsRequestForApiV1() + { + $request = $this->makeRequest('App\Http\Controllers\Api\v1\CustomerController@activateAccount'); + $this->assertTrue(Helper::isRequestForApiV1($request)); + } + + public function testIsRequestForApiV2() + { + $request = $this->makeRequest('apiV2.ping'); + $this->assertTrue(Helper::isRequestForApiV2($request)); + } + + private function makeRequest(string $nameOrAction) + { + $route = Route::getRoutes()->getByName($nameOrAction) ?? Route::getRoutes()->getByAction($nameOrAction); + $request = new Request([], [], [], [], [], ['REQUEST_URI' => $route->uri()]); + return $request->setRouteResolver(function () use ($route, $request) { + return ($route)->bind($request); + }); + } +} From c0d515e85d30e1c3959d92e257688ab2ca2a165b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 22 Jul 2021 14:47:40 +0200 Subject: [PATCH 003/103] Bump composer deps --- composer.lock | 1281 ++++++++++++++++++++++++++++++------------------- 1 file changed, 794 insertions(+), 487 deletions(-) diff --git a/composer.lock b/composer.lock index 2d19508..83e2f7e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,84 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a8f4b1ed32617cbb217584fcdbc66fac", + "content-hash": "7b72242184eebf977895b2352f204a39", "packages": [ { - "name": "asm89/stack-cors", - "version": "v2.0.2", + "name": "arondeparon/laravel-request-sanitizer", + "version": "3.1.0", "source": { "type": "git", - "url": "https://github.com/asm89/stack-cors.git", - "reference": "8d8f88b3b3830916be94292c1fbce84433efb1aa" + "url": "https://github.com/ArondeParon/laravel-request-sanitizer.git", + "reference": "025d3e45cddf19d2488e420201e8b34e8007b94d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/asm89/stack-cors/zipball/8d8f88b3b3830916be94292c1fbce84433efb1aa", - "reference": "8d8f88b3b3830916be94292c1fbce84433efb1aa", + "url": "https://api.github.com/repos/ArondeParon/laravel-request-sanitizer/zipball/025d3e45cddf19d2488e420201e8b34e8007b94d", + "reference": "025d3e45cddf19d2488e420201e8b34e8007b94d", + "shasum": "" + }, + "require": { + "illuminate/http": "^6.0|^7.0|^8.0", + "php": "^7.3|^8.0", + "symfony/http-foundation": "^4.3.4|>=5.1.3" + }, + "require-dev": { + "mockery/mockery": "^1.4", + "orchestra/testbench": "^4.0|^5.0|^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ArondeParon\\RequestSanitizer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aron Rotteveel", + "email": "hi@aron.codes", + "homepage": "https://github.com/arondeparon", + "role": "Developer" + } + ], + "description": "An easy to use request sanitizer that allows you to sanitize your form data before validating it.", + "homepage": "https://github.com/arondeparon/laravel-request-sanitizer", + "keywords": [ + "arondeparon", + "form", + "laravel", + "request", + "sanitizer", + "validation" + ], + "support": { + "issues": "https://github.com/ArondeParon/laravel-request-sanitizer/issues", + "source": "https://github.com/ArondeParon/laravel-request-sanitizer/tree/3.1.0" + }, + "funding": [ + { + "url": "https://github.com/arondeparon", + "type": "github" + } + ], + "time": "2021-02-02T09:03:04+00:00" + }, + { + "name": "asm89/stack-cors", + "version": "v2.0.3", + "source": { + "type": "git", + "url": "https://github.com/asm89/stack-cors.git", + "reference": "9cb795bf30988e8c96dd3c40623c48a877bc6714" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/asm89/stack-cors/zipball/9cb795bf30988e8c96dd3c40623c48a877bc6714", + "reference": "9cb795bf30988e8c96dd3c40623c48a877bc6714", "shasum": "" }, "require": { @@ -58,9 +122,9 @@ ], "support": { "issues": "https://github.com/asm89/stack-cors/issues", - "source": "https://github.com/asm89/stack-cors/tree/v2.0.2" + "source": "https://github.com/asm89/stack-cors/tree/v2.0.3" }, - "time": "2020-10-29T16:03:21+00:00" + "time": "2021-03-11T06:42:03+00:00" }, { "name": "brick/math", @@ -120,16 +184,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.1", + "version": "1.11.99.2", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6" + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/7413f0b55a051e89485c5cb9f765fe24bb02a7b6", - "reference": "7413f0b55a051e89485c5cb9f765fe24bb02a7b6", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", "shasum": "" }, "require": { @@ -173,7 +237,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.1" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.2" }, "funding": [ { @@ -189,57 +253,20 @@ "type": "tidelift" } ], - "time": "2020-11-11T10:22:58+00:00" - }, - { - "name": "dnoegel/php-xdg-base-dir", - "version": "v0.1.1", - "source": { - "type": "git", - "url": "https://github.com/dnoegel/php-xdg-base-dir.git", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "reference": "8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "require-dev": { - "phpunit/phpunit": "~7.0|~6.0|~5.0|~4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "XdgBaseDir\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "implementation of xdg base directory specification for php", - "support": { - "issues": "https://github.com/dnoegel/php-xdg-base-dir/issues", - "source": "https://github.com/dnoegel/php-xdg-base-dir/tree/v0.1.1" - }, - "time": "2019-12-04T15:06:13+00:00" + "time": "2021-05-24T07:46:03+00:00" }, { "name": "doctrine/cache", - "version": "1.10.2", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "13e3381b25847283a91948d04640543941309727" + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/13e3381b25847283a91948d04640543941309727", - "reference": "13e3381b25847283a91948d04640543941309727", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", "shasum": "" }, "require": { @@ -250,20 +277,19 @@ }, "require-dev": { "alcaeus/mongo-php-adapter": "^1.1", - "doctrine/coding-standard": "^6.0", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0", - "predis/predis": "~1.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" }, "suggest": { "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" @@ -310,7 +336,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.10.x" + "source": "https://github.com/doctrine/cache/tree/2.1.1" }, "funding": [ { @@ -326,37 +352,40 @@ "type": "tidelift" } ], - "time": "2020-07-07T18:54:01+00:00" + "time": "2021-07-17T14:49:29+00:00" }, { "name": "doctrine/dbal", - "version": "3.0.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c" + "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/ee6d1260d5cc20ec506455a585945d7bdb98662c", - "reference": "ee6d1260d5cc20ec506455a585945d7bdb98662c", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/8e0fde2b90e3f61361013d1e928621beeea07bc0", + "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0", "shasum": "" }, "require": { "composer/package-versions-deprecated": "^1.11.99", - "doctrine/cache": "^1.0", + "doctrine/cache": "^1.0|^2.0", + "doctrine/deprecations": "^0.5.3", "doctrine/event-manager": "^1.0", "php": "^7.3 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.1", - "jetbrains/phpstorm-stubs": "^2019.1", - "phpstan/phpstan": "^0.12.40", + "doctrine/coding-standard": "9.0.0", + "jetbrains/phpstorm-stubs": "2020.2", + "phpstan/phpstan": "0.12.81", "phpstan/phpstan-strict-rules": "^0.12.2", - "phpunit/phpunit": "^9.4", - "psalm/plugin-phpunit": "^0.10.0", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", - "vimeo/psalm": "^3.17.2" + "phpunit/phpunit": "9.5.5", + "psalm/plugin-phpunit": "0.13.0", + "squizlabs/php_codesniffer": "3.6.0", + "symfony/cache": "^5.2|^6.0", + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0|^6.0", + "vimeo/psalm": "4.6.4" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -365,11 +394,6 @@ "bin/doctrine-dbal" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, "autoload": { "psr-4": { "Doctrine\\DBAL\\": "src" @@ -421,7 +445,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.0.0" + "source": "https://github.com/doctrine/dbal/tree/3.1.1" }, "funding": [ { @@ -437,7 +461,50 @@ "type": "tidelift" } ], - "time": "2020-11-15T18:20:41+00:00" + "time": "2021-06-19T17:59:55+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" }, { "name": "doctrine/event-manager", @@ -897,16 +964,16 @@ }, { "name": "fruitcake/laravel-cors", - "version": "v2.0.3", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/fruitcake/laravel-cors.git", - "reference": "01de0fe5f71c70d1930ee9a80385f9cc28e0f63a" + "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/01de0fe5f71c70d1930ee9a80385f9cc28e0f63a", - "reference": "01de0fe5f71c70d1930ee9a80385f9cc28e0f63a", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/a8ccedc7ca95189ead0e407c43b530dc17791d6a", + "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a", "shasum": "" }, "require": { @@ -919,8 +986,8 @@ }, "require-dev": { "laravel/framework": "^6|^7|^8", - "orchestra/testbench-dusk": "^4|^5|^6", - "phpunit/phpunit": "^6|^7|^8", + "orchestra/testbench-dusk": "^4|^5|^6|^7", + "phpunit/phpunit": "^6|^7|^8|^9", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", @@ -962,7 +1029,7 @@ ], "support": { "issues": "https://github.com/fruitcake/laravel-cors/issues", - "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.3" + "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.4" }, "funding": [ { @@ -970,7 +1037,7 @@ "type": "github" } ], - "time": "2020-10-22T13:57:20+00:00" + "time": "2021-04-26T11:24:25+00:00" }, { "name": "graham-campbell/result-type", @@ -1040,22 +1107,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.2.0", + "version": "7.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79" + "reference": "7008573787b430c1c1f650e3722d9bba59967628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79", - "reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.4", - "guzzlehttp/psr7": "^1.7", + "guzzlehttp/psr7": "^1.7 || ^2.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0" }, @@ -1063,6 +1130,7 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", "ext-curl": "*", "php-http/client-integration-tests": "^3.0", "phpunit/phpunit": "^8.5.5 || ^9.3.5", @@ -1076,7 +1144,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.1-dev" + "dev-master": "7.3-dev" } }, "autoload": { @@ -1118,7 +1186,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.2.0" + "source": "https://github.com/guzzle/guzzle/tree/7.3.0" }, "funding": [ { @@ -1138,20 +1206,20 @@ "type": "github" } ], - "time": "2020-10-10T11:47:56+00:00" + "time": "2021-03-23T11:33:13+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "60d379c243457e073cff02bc323a2a86cb355631" + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", - "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", "shasum": "" }, "require": { @@ -1191,35 +1259,38 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.0" + "source": "https://github.com/guzzle/promises/tree/1.4.1" }, - "time": "2020-09-30T07:37:28+00:00" + "time": "2021-03-07T09:25:29+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.7.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", - "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", "shasum": "" }, "require": { - "php": ">=5.4.0", - "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { - "ext-zlib": "*", - "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + "bamarni/composer-bin-plugin": "^1.4.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.8 || ^9.3.10" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1227,16 +1298,13 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "2.0-dev" } }, "autoload": { "psr-4": { "GuzzleHttp\\Psr7\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1251,6 +1319,11 @@ { "name": "Tobias Schultze", "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "PSR-7 message implementation that also provides common utility methods", @@ -1266,22 +1339,126 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.0.0" }, - "time": "2020-09-30T07:37:11+00:00" + "time": "2021-06-30T20:03:07+00:00" }, { - "name": "laravel/framework", - "version": "v8.29.0", + "name": "kwaadpepper/enum", + "version": "0.1.4", "source": { "type": "git", - "url": "https://github.com/laravel/framework.git", - "reference": "d2eba352b3b3a3c515b18c5726b373fe5026733e" + "url": "https://github.com/Kwaadpepper/enum.git", + "reference": "f9e2b960d86ceea45c905f11c0c5699529e32848" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d2eba352b3b3a3c515b18c5726b373fe5026733e", - "reference": "d2eba352b3b3a3c515b18c5726b373fe5026733e", + "url": "https://api.github.com/repos/Kwaadpepper/enum/zipball/f9e2b960d86ceea45c905f11c0c5699529e32848", + "reference": "f9e2b960d86ceea45c905f11c0c5699529e32848", + "shasum": "" + }, + "require": { + "illuminate/support": "~7|~8", + "laravel/framework": "~7|~8" + }, + "require-dev": { + "orchestra/testbench": "^6.13", + "phpunit/phpunit": "~9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Kwaadpepper\\Enum\\EnumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Kwaadpepper\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jérémy Munsch", + "email": "github@jeremydev.ovh", + "homepage": "https://jeremydev.ovh" + } + ], + "description": "Enums to use for any project", + "homepage": "https://github.com/kwaadpepper/enum", + "keywords": [ + "enum", + "laravel" + ], + "support": { + "issues": "https://github.com/Kwaadpepper/enum/issues", + "source": "https://github.com/Kwaadpepper/enum/tree/0.1.4" + }, + "time": "2021-04-06T14:35:59+00:00" + }, + { + "name": "laravel-lang/lang", + "version": "7.0.9", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/lang.git", + "reference": "679a65755db37b35acd36a5e0ca51e055815a00a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/679a65755db37b35acd36a5e0ca51e055815a00a", + "reference": "679a65755db37b35acd36a5e0ca51e055815a00a", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "suggest": { + "andrey-helldar/laravel-lang-publisher": "Easy installation and update of translation files for your project", + "arcanedev/laravel-lang": "Translations manager and checker for Laravel 5", + "overtrue/laravel-lang": "Command to add languages in your project" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laravel-Lang Team" + } + ], + "description": "Languages for Laravel", + "keywords": [ + "lang", + "languages", + "laravel", + "lpm" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/lang/issues", + "source": "https://github.com/Laravel-Lang/lang/tree/7.0.9" + }, + "time": "2020-11-30T20:35:41+00:00" + }, + { + "name": "laravel/framework", + "version": "v8.51.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "208d9c0043b4c192a9bb9b15782cc4ec37f28bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/208d9c0043b4c192a9bb9b15782cc4ec37f28bb0", + "reference": "208d9c0043b4c192a9bb9b15782cc4ec37f28bb0", "shasum": "" }, "require": { @@ -1291,7 +1468,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "league/commonmark": "^1.3", + "league/commonmark": "^1.3|^2.0", "league/flysystem": "^1.1", "monolog/monolog": "^2.0", "nesbot/carbon": "^2.31", @@ -1360,10 +1537,10 @@ "guzzlehttp/guzzle": "^6.5.5|^7.0.1", "league/flysystem-cached-adapter": "^1.0", "mockery/mockery": "^1.4.2", - "orchestra/testbench-core": "^6.8", + "orchestra/testbench-core": "^6.23", "pda/pheanstalk": "^4.0", "phpunit/phpunit": "^8.5.8|^9.3.3", - "predis/predis": "^1.1.1", + "predis/predis": "^1.1.2", "symfony/cache": "^5.1.4" }, "suggest": { @@ -1389,7 +1566,7 @@ "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", "predis/predis": "Required to use the predis connector (^1.1.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0|^6.0).", "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", @@ -1436,20 +1613,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-02-23T14:27:41+00:00" + "time": "2021-07-20T14:38:36+00:00" }, { "name": "laravel/tinker", - "version": "v2.6.0", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "daae1c43f1300fe88c05d83db6f3d8f76677ad88" + "reference": "04ad32c1a3328081097a181875733fa51f402083" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/daae1c43f1300fe88c05d83db6f3d8f76677ad88", - "reference": "daae1c43f1300fe88c05d83db6f3d8f76677ad88", + "url": "https://api.github.com/repos/laravel/tinker/zipball/04ad32c1a3328081097a181875733fa51f402083", + "reference": "04ad32c1a3328081097a181875733fa51f402083", "shasum": "" }, "require": { @@ -1502,22 +1679,22 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.6.0" + "source": "https://github.com/laravel/tinker/tree/v2.6.1" }, - "time": "2021-01-26T20:35:18+00:00" + "time": "2021-03-02T16:53:12+00:00" }, { "name": "league/commonmark", - "version": "1.5.7", + "version": "1.6.6", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54" + "reference": "c4228d11e30d7493c6836d20872f9582d8ba6dcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/11df9b36fd4f1d2b727a73bf14931d81373b9a54", - "reference": "11df9b36fd4f1d2b727a73bf14931d81373b9a54", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4228d11e30d7493c6836d20872f9582d8ba6dcf", + "reference": "c4228d11e30d7493c6836d20872f9582d8ba6dcf", "shasum": "" }, "require": { @@ -1535,7 +1712,7 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "~1.4", "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "^0.12.90", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", "scrutinizer/ocular": "^1.5", "symfony/finder": "^4.2" @@ -1605,20 +1782,20 @@ "type": "tidelift" } ], - "time": "2020-10-31T13:49:32+00:00" + "time": "2021-07-17T17:13:23+00:00" }, { "name": "league/flysystem", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", - "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", "shasum": "" }, "require": { @@ -1634,7 +1811,6 @@ "phpunit/phpunit": "^8.5.8" }, "suggest": { - "ext-fileinfo": "Required for MimeType", "ext-ftp": "Allows you to use FTP server storage", "ext-openssl": "Allows you to use FTPS server storage", "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", @@ -1692,7 +1868,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/1.x" + "source": "https://github.com/thephpleague/flysystem/tree/1.1.4" }, "funding": [ { @@ -1700,7 +1876,7 @@ "type": "other" } ], - "time": "2020-08-23T07:39:11+00:00" + "time": "2021-06-23T21:56:05+00:00" }, { "name": "league/mime-type-detection", @@ -1760,16 +1936,16 @@ }, { "name": "monolog/monolog", - "version": "2.2.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084" + "reference": "9738e495f288eec0b187e310b7cdbbb285777dbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1cb1cde8e8dd0f70cc0fe51354a59acad9302084", - "reference": "1cb1cde8e8dd0f70cc0fe51354a59acad9302084", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/9738e495f288eec0b187e310b7cdbbb285777dbe", + "reference": "9738e495f288eec0b187e310b7cdbbb285777dbe", "shasum": "" }, "require": { @@ -1788,7 +1964,7 @@ "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", "phpspec/prophecy": "^1.6.1", - "phpstan/phpstan": "^0.12.59", + "phpstan/phpstan": "^0.12.91", "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", "rollbar/rollbar": "^1.3", @@ -1840,7 +2016,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.2.0" + "source": "https://github.com/Seldaek/monolog/tree/2.3.1" }, "funding": [ { @@ -1852,20 +2028,20 @@ "type": "tidelift" } ], - "time": "2020-12-14T13:15:25+00:00" + "time": "2021-07-14T11:56:39+00:00" }, { "name": "nesbot/carbon", - "version": "2.45.1", + "version": "2.50.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "528783b188bdb853eb21239b1722831e0f000a8d" + "reference": "f47f17d17602b2243414a44ad53d9f8b9ada5fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/528783b188bdb853eb21239b1722831e0f000a8d", - "reference": "528783b188bdb853eb21239b1722831e0f000a8d", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f47f17d17602b2243414a44ad53d9f8b9ada5fdb", + "reference": "f47f17d17602b2243414a44ad53d9f8b9ada5fdb", "shasum": "" }, "require": { @@ -1917,15 +2093,15 @@ { "name": "Brian Nesbitt", "email": "brian@nesbot.com", - "homepage": "http://nesbot.com" + "homepage": "https://markido.com" }, { "name": "kylekatarnls", - "homepage": "http://github.com/kylekatarnls" + "homepage": "https://github.com/kylekatarnls" } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "http://carbon.nesbot.com", + "homepage": "https://carbon.nesbot.com", "keywords": [ "date", "datetime", @@ -1945,20 +2121,20 @@ "type": "tidelift" } ], - "time": "2021-02-11T18:30:17+00:00" + "time": "2021-06-28T22:38:45+00:00" }, { "name": "nikic/php-parser", - "version": "v4.10.4", + "version": "v4.12.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e" + "reference": "6608f01670c3cc5079e18c1dab1104e002579143" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e", - "reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143", "shasum": "" }, "require": { @@ -1999,22 +2175,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.12.0" }, - "time": "2020-12-20T10:01:03+00:00" + "time": "2021-07-21T10:44:31+00:00" }, { "name": "opis/closure", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5" + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", - "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5", + "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", "shasum": "" }, "require": { @@ -2064,9 +2240,9 @@ ], "support": { "issues": "https://github.com/opis/closure/issues", - "source": "https://github.com/opis/closure/tree/3.6.1" + "source": "https://github.com/opis/closure/tree/3.6.2" }, - "time": "2020-11-07T02:01:34+00:00" + "time": "2021-04-09T13:42:10+00:00" }, { "name": "phpoption/phpoption", @@ -2139,27 +2315,22 @@ }, { "name": "psr/container", - "version": "1.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -2172,7 +2343,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -2186,9 +2357,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/master" + "source": "https://github.com/php-fig/container/tree/1.1.1" }, - "time": "2017-02-14T16:28:37+00:00" + "time": "2021-03-05T17:36:06+00:00" }, { "name": "psr/event-dispatcher", @@ -2292,6 +2463,61 @@ }, "time": "2020-06-29T06:28:15+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -2347,16 +2573,16 @@ }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -2380,7 +2606,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -2391,9 +2617,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "psr/simple-cache", @@ -2448,20 +2674,19 @@ }, { "name": "psy/psysh", - "version": "v0.10.6", + "version": "v0.10.8", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3" + "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/6f990c19f91729de8b31e639d6e204ea59f19cf3", - "reference": "6f990c19f91729de8b31e639d6e204ea59f19cf3", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3", "shasum": "" }, "require": { - "dnoegel/php-xdg-base-dir": "0.1.*", "ext-json": "*", "ext-tokenizer": "*", "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", @@ -2518,9 +2743,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.10.6" + "source": "https://github.com/bobthecow/psysh/tree/v0.10.8" }, - "time": "2021-01-18T15:53:43+00:00" + "time": "2021-04-10T16:23:39+00:00" }, { "name": "ralouphie/getallheaders", @@ -2736,21 +2961,83 @@ "time": "2020-08-18T17:17:46+00:00" }, { - "name": "swiftmailer/swiftmailer", - "version": "v6.2.5", + "name": "schuppo/password-strength", + "version": "v2.4", "source": { "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7" + "url": "https://github.com/schuppo/PasswordStrengthPackage.git", + "reference": "d25211547b77bc220832da9cafe066ebc51bd228" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/698a6a9f54d7eb321274de3ad19863802c879fb7", - "reference": "698a6a9f54d7eb321274de3ad19863802c879fb7", + "url": "https://api.github.com/repos/schuppo/PasswordStrengthPackage/zipball/d25211547b77bc220832da9cafe066ebc51bd228", + "reference": "d25211547b77bc220832da9cafe066ebc51bd228", "shasum": "" }, "require": { - "egulias/email-validator": "^2.0", + "illuminate/support": "^5.1|^6.0|^7.0|^8.0", + "illuminate/translation": "^5.1|6.*|7.*|8.*", + "php": ">=7.1.0" + }, + "require-dev": { + "illuminate/validation": "~5.1|6.*|7.*|8.*", + "orchestra/testbench": "^3.5|^4.0|^5.0|^6.0", + "phpunit/phpunit": "^6.0|^7.0|^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Schuppo\\PasswordStrength\\PasswordStrengthServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Schuppo\\PasswordStrength\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Schupp", + "email": "oliver.schupp@yahoo.de" + } + ], + "description": "This package provides a validator for ensuring strong passwords in Laravel 4 applications.", + "keywords": [ + "laravel", + "laravel 5", + "laravel5", + "password", + "password strength", + "validation" + ], + "support": { + "issues": "https://github.com/schuppo/PasswordStrengthPackage/issues", + "source": "https://github.com/schuppo/PasswordStrengthPackage/tree/v2.4" + }, + "time": "2021-02-25T15:17:02+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "15f7faf8508e04471f666633addacf54c0ab5933" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/15f7faf8508e04471f666633addacf54c0ab5933", + "reference": "15f7faf8508e04471f666633addacf54c0ab5933", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.0|^3.1", "php": ">=7.0.0", "symfony/polyfill-iconv": "^1.0", "symfony/polyfill-intl-idn": "^1.10", @@ -2796,7 +3083,7 @@ ], "support": { "issues": "https://github.com/swiftmailer/swiftmailer/issues", - "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.5" + "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.2.7" }, "funding": [ { @@ -2808,24 +3095,25 @@ "type": "tidelift" } ], - "time": "2021-01-12T09:35:59+00:00" + "time": "2021-03-09T12:30:35+00:00" }, { "name": "symfony/console", - "version": "v5.2.3", + "version": "v5.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a" + "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a", - "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a", + "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", "symfony/polyfill-php80": "^1.15", @@ -2889,7 +3177,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.2.3" + "source": "https://github.com/symfony/console/tree/v5.3.2" }, "funding": [ { @@ -2905,20 +3193,20 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-06-12T09:42:48+00:00" }, { "name": "symfony/css-selector", - "version": "v5.2.3", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f" + "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/f65f217b3314504a1ec99c2d6ef69016bb13490f", - "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", "shasum": "" }, "require": { @@ -2954,7 +3242,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.2.3" + "source": "https://github.com/symfony/css-selector/tree/v5.3.0" }, "funding": [ { @@ -2970,20 +3258,20 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:01:46+00:00" + "time": "2021-05-26T17:40:38+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665" + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5fa56b4074d1ae755beb55617ddafe6f5d78f665", - "reference": "5fa56b4074d1ae755beb55617ddafe6f5d78f665", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", "shasum": "" }, "require": { @@ -2992,7 +3280,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3021,7 +3309,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/master" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" }, "funding": [ { @@ -3037,20 +3325,20 @@ "type": "tidelift" } ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2021-03-23T23:28:01+00:00" }, { "name": "symfony/error-handler", - "version": "v5.2.3", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48f18b3609e120ea66d59142c23dc53e9562c26d" + "reference": "43323e79c80719e8a4674e33484bca98270d223f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48f18b3609e120ea66d59142c23dc53e9562c26d", - "reference": "48f18b3609e120ea66d59142c23dc53e9562c26d", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/43323e79c80719e8a4674e33484bca98270d223f", + "reference": "43323e79c80719e8a4674e33484bca98270d223f", "shasum": "" }, "require": { @@ -3090,7 +3378,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.2.3" + "source": "https://github.com/symfony/error-handler/tree/v5.3.3" }, "funding": [ { @@ -3106,20 +3394,20 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-06-24T08:13:00+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.2.3", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367" + "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367", - "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce", "shasum": "" }, "require": { @@ -3175,7 +3463,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0" }, "funding": [ { @@ -3191,20 +3479,20 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:36:42+00:00" + "time": "2021-05-26T17:43:10+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2" + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ba7d54483095a198fa51781bc608d17e84dffa2", - "reference": "0ba7d54483095a198fa51781bc608d17e84dffa2", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", "shasum": "" }, "require": { @@ -3217,7 +3505,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3254,7 +3542,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.2.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.4.0" }, "funding": [ { @@ -3270,20 +3558,20 @@ "type": "tidelift" } ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2021-03-23T23:28:01+00:00" }, { "name": "symfony/finder", - "version": "v5.2.3", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "4adc8d172d602008c204c2e16956f99257248e03" + "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03", - "reference": "4adc8d172d602008c204c2e16956f99257248e03", + "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", "shasum": "" }, "require": { @@ -3315,7 +3603,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.2.3" + "source": "https://github.com/symfony/finder/tree/v5.3.0" }, "funding": [ { @@ -3331,20 +3619,20 @@ "type": "tidelift" } ], - "time": "2021-01-28T22:06:19+00:00" + "time": "2021-05-26T12:52:38+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v2.3.1", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "41db680a15018f9c1d4b23516059633ce280ca33" + "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/41db680a15018f9c1d4b23516059633ce280ca33", - "reference": "41db680a15018f9c1d4b23516059633ce280ca33", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", "shasum": "" }, "require": { @@ -3355,9 +3643,8 @@ }, "type": "library", "extra": { - "branch-version": "2.3", "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -3394,7 +3681,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.3.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v2.4.0" }, "funding": [ { @@ -3410,20 +3697,20 @@ "type": "tidelift" } ], - "time": "2020-10-14T17:08:19+00:00" + "time": "2021-04-11T23:07:08+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.2.3", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "20c554c0f03f7cde5ce230ed248470cccbc34c36" + "reference": "0e45ab1574caa0460d9190871a8ce47539e40ccf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/20c554c0f03f7cde5ce230ed248470cccbc34c36", - "reference": "20c554c0f03f7cde5ce230ed248470cccbc34c36", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0e45ab1574caa0460d9190871a8ce47539e40ccf", + "reference": "0e45ab1574caa0460d9190871a8ce47539e40ccf", "shasum": "" }, "require": { @@ -3467,7 +3754,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.2.3" + "source": "https://github.com/symfony/http-foundation/tree/v5.3.3" }, "funding": [ { @@ -3483,20 +3770,20 @@ "type": "tidelift" } ], - "time": "2021-02-03T04:42:09+00:00" + "time": "2021-06-27T09:19:40+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.2.3", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05" + "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05", - "reference": "89bac04f29e7b0b52f9fa6a4288ca7a8f90a1a05", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8", + "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8", "shasum": "" }, "require": { @@ -3506,7 +3793,7 @@ "symfony/error-handler": "^4.4|^5.0", "symfony/event-dispatcher": "^5.0", "symfony/http-client-contracts": "^1.1|^2", - "symfony/http-foundation": "^4.4|^5.0", + "symfony/http-foundation": "^5.3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9", "symfony/polyfill-php80": "^1.15" @@ -3516,7 +3803,7 @@ "symfony/cache": "<5.0", "symfony/config": "<5.0", "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.1.8", + "symfony/dependency-injection": "<5.3", "symfony/doctrine-bridge": "<5.0", "symfony/form": "<5.0", "symfony/http-client": "<5.0", @@ -3531,12 +3818,12 @@ "psr/log-implementation": "1.0" }, "require-dev": { - "psr/cache": "~1.0", + "psr/cache": "^1.0|^2.0|^3.0", "symfony/browser-kit": "^4.4|^5.0", "symfony/config": "^5.0", "symfony/console": "^4.4|^5.0", "symfony/css-selector": "^4.4|^5.0", - "symfony/dependency-injection": "^5.1.8", + "symfony/dependency-injection": "^5.3", "symfony/dom-crawler": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", @@ -3579,7 +3866,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.2.3" + "source": "https://github.com/symfony/http-kernel/tree/v5.3.3" }, "funding": [ { @@ -3595,20 +3882,20 @@ "type": "tidelift" } ], - "time": "2021-02-03T04:51:58+00:00" + "time": "2021-06-30T08:27:49+00:00" }, { "name": "symfony/mime", - "version": "v5.2.3", + "version": "v5.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86" + "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7dee6a43493f39b51ff6c5bb2bd576fe40a76c86", - "reference": "7dee6a43493f39b51ff6c5bb2bd576fe40a76c86", + "url": "https://api.github.com/repos/symfony/mime/zipball/47dd7912152b82d0d4c8d9040dbc93d6232d472a", + "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a", "shasum": "" }, "require": { @@ -3619,12 +3906,13 @@ "symfony/polyfill-php80": "^1.15" }, "conflict": { + "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<4.4" }, "require-dev": { - "egulias/email-validator": "^2.1.10", + "egulias/email-validator": "^2.1.10|^3.1", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/property-access": "^4.4|^5.1", @@ -3661,7 +3949,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.2.3" + "source": "https://github.com/symfony/mime/tree/v5.3.2" }, "funding": [ { @@ -3677,20 +3965,20 @@ "type": "tidelift" } ], - "time": "2021-02-02T06:10:15+00:00" + "time": "2021-06-09T10:58:01+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", - "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { @@ -3702,7 +3990,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3740,7 +4028,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -3756,20 +4044,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342" + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/06fb361659649bcfd6a208a0f1fcaf4e827ad342", - "reference": "06fb361659649bcfd6a208a0f1fcaf4e827ad342", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", "shasum": "" }, "require": { @@ -3781,7 +4069,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3820,7 +4108,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0" }, "funding": [ { @@ -3836,20 +4124,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170" + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170", - "reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", + "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", "shasum": "" }, "require": { @@ -3861,7 +4149,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3901,7 +4189,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" }, "funding": [ { @@ -3917,20 +4205,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "2d63434d922daf7da8dd863e7907e67ee3031483" + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483", - "reference": "2d63434d922daf7da8dd863e7907e67ee3031483", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", "shasum": "" }, "require": { @@ -3944,7 +4232,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -3988,7 +4276,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.23.0" }, "funding": [ { @@ -4004,20 +4292,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248" + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248", - "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", "shasum": "" }, "require": { @@ -4029,7 +4317,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4072,7 +4360,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0" }, "funding": [ { @@ -4088,20 +4376,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", - "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", "shasum": "" }, "require": { @@ -4113,7 +4401,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4152,7 +4440,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" }, "funding": [ { @@ -4168,20 +4456,20 @@ "type": "tidelift" } ], - "time": "2021-01-22T09:19:47+00:00" + "time": "2021-05-27T09:27:20+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", - "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", "shasum": "" }, "require": { @@ -4190,7 +4478,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4228,7 +4516,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" }, "funding": [ { @@ -4244,20 +4532,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-05-27T09:17:38+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2" + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", - "reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", "shasum": "" }, "require": { @@ -4266,7 +4554,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4307,7 +4595,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.23.0" }, "funding": [ { @@ -4323,20 +4611,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.22.1", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", - "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", "shasum": "" }, "require": { @@ -4345,7 +4633,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.22-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4390,7 +4678,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" }, "funding": [ { @@ -4406,20 +4694,20 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { "name": "symfony/process", - "version": "v5.2.3", + "version": "v5.3.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f" + "reference": "714b47f9196de61a196d86c4bad5f09201b307df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f", - "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f", + "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df", + "reference": "714b47f9196de61a196d86c4bad5f09201b307df", "shasum": "" }, "require": { @@ -4452,7 +4740,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.2.3" + "source": "https://github.com/symfony/process/tree/v5.3.2" }, "funding": [ { @@ -4468,20 +4756,20 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-06-12T10:15:01+00:00" }, { "name": "symfony/routing", - "version": "v5.2.3", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661" + "reference": "368e81376a8e049c37cb80ae87dbfbf411279199" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/348b5917e56546c6d96adbf21d7f92c9ef563661", - "reference": "348b5917e56546c6d96adbf21d7f92c9ef563661", + "url": "https://api.github.com/repos/symfony/routing/zipball/368e81376a8e049c37cb80ae87dbfbf411279199", + "reference": "368e81376a8e049c37cb80ae87dbfbf411279199", "shasum": "" }, "require": { @@ -4490,21 +4778,21 @@ "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/config": "<5.0", + "doctrine/annotations": "<1.12", + "symfony/config": "<5.3", "symfony/dependency-injection": "<4.4", "symfony/yaml": "<4.4" }, "require-dev": { - "doctrine/annotations": "^1.10.4", + "doctrine/annotations": "^1.12", "psr/log": "~1.0", - "symfony/config": "^5.0", + "symfony/config": "^5.3", "symfony/dependency-injection": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", "symfony/yaml": "^4.4|^5.0" }, "suggest": { - "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", @@ -4542,7 +4830,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.2.3" + "source": "https://github.com/symfony/routing/tree/v5.3.0" }, "funding": [ { @@ -4558,25 +4846,25 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-05-26T17:43:10+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.2.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1" + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d15da7ba4957ffb8f1747218be9e1a121fd298a1", - "reference": "d15da7ba4957ffb8f1747218be9e1a121fd298a1", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.0" + "psr/container": "^1.1" }, "suggest": { "symfony/service-implementation": "" @@ -4584,7 +4872,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -4621,7 +4909,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/master" + "source": "https://github.com/symfony/service-contracts/tree/v2.4.0" }, "funding": [ { @@ -4637,20 +4925,20 @@ "type": "tidelift" } ], - "time": "2020-09-07T11:33:47+00:00" + "time": "2021-04-01T10:43:52+00:00" }, { "name": "symfony/string", - "version": "v5.2.3", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "c95468897f408dd0aca2ff582074423dd0455122" + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122", - "reference": "c95468897f408dd0aca2ff582074423dd0455122", + "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", "shasum": "" }, "require": { @@ -4704,7 +4992,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.2.3" + "source": "https://github.com/symfony/string/tree/v5.3.3" }, "funding": [ { @@ -4720,24 +5008,25 @@ "type": "tidelift" } ], - "time": "2021-01-25T15:14:59+00:00" + "time": "2021-06-27T11:44:38+00:00" }, { "name": "symfony/translation", - "version": "v5.2.3", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949" + "reference": "380b8c9e944d0e364b25f28e8e555241eb49c01c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/c021864d4354ee55160ddcfd31dc477a1bc77949", - "reference": "c021864d4354ee55160ddcfd31dc477a1bc77949", + "url": "https://api.github.com/repos/symfony/translation/zipball/380b8c9e944d0e364b25f28e8e555241eb49c01c", + "reference": "380b8c9e944d0e364b25f28e8e555241eb49c01c", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.15", "symfony/translation-contracts": "^2.3" @@ -4750,7 +5039,7 @@ "symfony/yaml": "<4.4" }, "provide": { - "symfony/translation-implementation": "2.0" + "symfony/translation-implementation": "2.3" }, "require-dev": { "psr/log": "~1.0", @@ -4760,6 +5049,7 @@ "symfony/finder": "^4.4|^5.0", "symfony/http-kernel": "^5.0", "symfony/intl": "^4.4|^5.0", + "symfony/polyfill-intl-icu": "^1.21", "symfony/service-contracts": "^1.1.2|^2", "symfony/yaml": "^4.4|^5.0" }, @@ -4797,7 +5087,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.2.3" + "source": "https://github.com/symfony/translation/tree/v5.3.3" }, "funding": [ { @@ -4813,20 +5103,20 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-06-27T12:22:47+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.3.0", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105" + "reference": "95c812666f3e91db75385749fe219c5e494c7f95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e2eaa60b558f26a4b0354e1bbb25636efaaad105", - "reference": "e2eaa60b558f26a4b0354e1bbb25636efaaad105", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", + "reference": "95c812666f3e91db75385749fe219c5e494c7f95", "shasum": "" }, "require": { @@ -4838,7 +5128,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -4875,7 +5165,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.3.0" + "source": "https://github.com/symfony/translation-contracts/tree/v2.4.0" }, "funding": [ { @@ -4891,20 +5181,20 @@ "type": "tidelift" } ], - "time": "2020-09-28T13:05:58+00:00" + "time": "2021-03-23T23:28:01+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.2.3", + "version": "v5.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "72ca213014a92223a5d18651ce79ef441c12b694" + "reference": "46aa709affb9ad3355bd7a810f9662d71025c384" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/72ca213014a92223a5d18651ce79ef441c12b694", - "reference": "72ca213014a92223a5d18651ce79ef441c12b694", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46aa709affb9ad3355bd7a810f9662d71025c384", + "reference": "46aa709affb9ad3355bd7a810f9662d71025c384", "shasum": "" }, "require": { @@ -4963,7 +5253,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v5.3.3" }, "funding": [ { @@ -4979,7 +5269,7 @@ "type": "tidelift" } ], - "time": "2021-01-27T10:15:41+00:00" + "time": "2021-06-24T08:13:00+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -5190,30 +5480,35 @@ }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", + "php": "^7.2 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -5237,9 +5532,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" + "source": "https://github.com/webmozarts/assert/tree/1.10.0" }, - "time": "2020-07-08T17:02:28+00:00" + "time": "2021-03-09T10:59:23+00:00" } ], "packages-dev": [ @@ -5314,16 +5609,16 @@ }, { "name": "facade/flare-client-php", - "version": "1.4.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/facade/flare-client-php.git", - "reference": "ef0f5bce23b30b32d98fd9bb49c6fa37b40eb546" + "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/flare-client-php/zipball/ef0f5bce23b30b32d98fd9bb49c6fa37b40eb546", - "reference": "ef0f5bce23b30b32d98fd9bb49c6fa37b40eb546", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f", + "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f", "shasum": "" }, "require": { @@ -5367,7 +5662,7 @@ ], "support": { "issues": "https://github.com/facade/flare-client-php/issues", - "source": "https://github.com/facade/flare-client-php/tree/1.4.0" + "source": "https://github.com/facade/flare-client-php/tree/1.8.1" }, "funding": [ { @@ -5375,28 +5670,27 @@ "type": "github" } ], - "time": "2021-02-16T12:42:06+00:00" + "time": "2021-05-31T19:23:29+00:00" }, { "name": "facade/ignition", - "version": "2.5.13", + "version": "2.11.2", "source": { "type": "git", "url": "https://github.com/facade/ignition.git", - "reference": "5e9ef386aaad9985cee2ac23281a27568d083b7e" + "reference": "7c4e7a7da184cd00c7ce6eacc590200bb9672de7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/5e9ef386aaad9985cee2ac23281a27568d083b7e", - "reference": "5e9ef386aaad9985cee2ac23281a27568d083b7e", + "url": "https://api.github.com/repos/facade/ignition/zipball/7c4e7a7da184cd00c7ce6eacc590200bb9672de7", + "reference": "7c4e7a7da184cd00c7ce6eacc590200bb9672de7", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "facade/flare-client-php": "^1.3.7", + "facade/flare-client-php": "^1.6", "facade/ignition-contracts": "^1.0.2", - "filp/whoops": "^2.4", "illuminate/support": "^7.0|^8.0", "monolog/monolog": "^2.0", "php": "^7.2.5|^8.0", @@ -5452,7 +5746,7 @@ "issues": "https://github.com/facade/ignition/issues", "source": "https://github.com/facade/ignition" }, - "time": "2021-02-16T12:46:19+00:00" + "time": "2021-07-20T14:01:22+00:00" }, { "name": "facade/ignition-contracts", @@ -5509,20 +5803,22 @@ }, { "name": "fakerphp/faker", - "version": "v1.13.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "ab3f5364d01f2c2c16113442fb987d26e4004913" + "reference": "89c6201c74db25fa759ff16e78a4d8f32547770e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/ab3f5364d01f2c2c16113442fb987d26e4004913", - "reference": "ab3f5364d01f2c2c16113442fb987d26e4004913", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/89c6201c74db25fa759ff16e78a4d8f32547770e", + "reference": "89c6201c74db25fa759ff16e78a4d8f32547770e", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.1 || ^8.0", + "psr/container": "^1.0", + "symfony/deprecation-contracts": "^2.2" }, "conflict": { "fzaninotto/faker": "*" @@ -5530,9 +5826,20 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", "ext-intl": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.4.2" + "symfony/phpunit-bridge": "^4.4 || ^5.2" + }, + "suggest": { + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "v1.15-dev" + } + }, "autoload": { "psr-4": { "Faker\\": "src/Faker/" @@ -5555,22 +5862,22 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.13.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.15.0" }, - "time": "2020-12-18T16:50:48+00:00" + "time": "2021-07-06T20:39:40+00:00" }, { "name": "filp/whoops", - "version": "2.9.2", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "df7933820090489623ce0be5e85c7e693638e536" + "reference": "fdf92f03e150ed84d5967a833ae93abffac0315b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/df7933820090489623ce0be5e85c7e693638e536", - "reference": "df7933820090489623ce0be5e85c7e693638e536", + "url": "https://api.github.com/repos/filp/whoops/zipball/fdf92f03e150ed84d5967a833ae93abffac0315b", + "reference": "fdf92f03e150ed84d5967a833ae93abffac0315b", "shasum": "" }, "require": { @@ -5620,7 +5927,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.9.2" + "source": "https://github.com/filp/whoops/tree/2.14.0" }, "funding": [ { @@ -5628,7 +5935,7 @@ "type": "github" } ], - "time": "2021-01-24T12:00:00+00:00" + "time": "2021-07-13T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -5813,16 +6120,16 @@ }, { "name": "nunomaduro/collision", - "version": "v5.3.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "aca63581f380f63a492b1e3114604e411e39133a" + "reference": "b5cb36122f1c142c3c3ee20a0ae778439ef0244b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/aca63581f380f63a492b1e3114604e411e39133a", - "reference": "aca63581f380f63a492b1e3114604e411e39133a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b5cb36122f1c142c3c3ee20a0ae778439ef0244b", + "reference": "b5cb36122f1c142c3c3ee20a0ae778439ef0244b", "shasum": "" }, "require": { @@ -5897,20 +6204,20 @@ "type": "patreon" } ], - "time": "2021-01-25T15:34:13+00:00" + "time": "2021-06-22T20:47:22+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133" + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", - "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { @@ -5955,9 +6262,9 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/master" + "source": "https://github.com/phar-io/manifest/tree/2.0.3" }, - "time": "2020-06-27T14:33:11+00:00" + "time": "2021-07-20T11:28:43+00:00" }, { "name": "phar-io/version", @@ -6170,16 +6477,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.12.2", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "245710e971a030f42e08f4912863805570f23d39" + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/245710e971a030f42e08f4912863805570f23d39", - "reference": "245710e971a030f42e08f4912863805570f23d39", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", "shasum": "" }, "require": { @@ -6231,22 +6538,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/1.12.2" + "source": "https://github.com/phpspec/prophecy/tree/1.13.0" }, - "time": "2020-12-19T10:15:11+00:00" + "time": "2021-03-17T13:42:18+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.5", + "version": "9.2.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1" + "reference": "f6293e1b30a2354e8428e004689671b83871edde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f3e026641cc91909d421802dd3ac7827ebfd97e1", - "reference": "f3e026641cc91909d421802dd3ac7827ebfd97e1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", "shasum": "" }, "require": { @@ -6302,7 +6609,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.6" }, "funding": [ { @@ -6310,7 +6617,7 @@ "type": "github" } ], - "time": "2020-11-28T06:44:49+00:00" + "time": "2021-03-28T07:26:59+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6555,16 +6862,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.2", + "version": "9.5.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4" + "reference": "d0dc8b6999c937616df4fb046792004b33fd31c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f661659747f2f87f9e72095bb207bceb0f151cb4", - "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0dc8b6999c937616df4fb046792004b33fd31c5", + "reference": "d0dc8b6999c937616df4fb046792004b33fd31c5", "shasum": "" }, "require": { @@ -6594,7 +6901,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3", + "sebastian/type": "^2.3.4", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -6642,7 +6949,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.7" }, "funding": [ { @@ -6654,7 +6961,7 @@ "type": "github" } ], - "time": "2021-02-02T14:45:58+00:00" + "time": "2021-07-19T06:14:47+00:00" }, { "name": "sebastian/cli-parser", @@ -7162,16 +7469,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455" + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455", - "reference": "a90ccbddffa067b51f574dea6eb25d5680839455", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", "shasum": "" }, "require": { @@ -7214,7 +7521,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" }, "funding": [ { @@ -7222,7 +7529,7 @@ "type": "github" } ], - "time": "2020-10-26T15:55:19+00:00" + "time": "2021-06-11T13:31:12+00:00" }, { "name": "sebastian/lines-of-code", @@ -7513,16 +7820,16 @@ }, { "name": "sebastian/type", - "version": "2.3.1", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2" + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2", - "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", "shasum": "" }, "require": { @@ -7557,7 +7864,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.1" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" }, "funding": [ { @@ -7565,7 +7872,7 @@ "type": "github" } ], - "time": "2020-10-26T13:18:59+00:00" + "time": "2021-06-15T12:49:02+00:00" }, { "name": "sebastian/version", @@ -7622,16 +7929,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.8", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "9d583721a7157ee997f235f327de038e7ea6dac4" + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/9d583721a7157ee997f235f327de038e7ea6dac4", - "reference": "9d583721a7157ee997f235f327de038e7ea6dac4", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", "shasum": "" }, "require": { @@ -7674,7 +7981,7 @@ "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2020-10-23T02:01:07+00:00" + "time": "2021-04-09T00:54:41+00:00" }, { "name": "theseer/tokenizer", From 6b5b6c8cfdecf934dd52062cead92f0ea73d0fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 22 Jul 2021 14:47:53 +0200 Subject: [PATCH 004/103] add VSCODE workspace file --- app/Http/Controllers/Api/v1/CRM V2.code-workspace | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/Http/Controllers/Api/v1/CRM V2.code-workspace diff --git a/app/Http/Controllers/Api/v1/CRM V2.code-workspace b/app/Http/Controllers/Api/v1/CRM V2.code-workspace new file mode 100644 index 0000000..fc95750 --- /dev/null +++ b/app/Http/Controllers/Api/v1/CRM V2.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": "../../../../.." + } + ], + "settings": {} +} From aebbdeff993721c197234518491d2082d3ac1066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 22 Jul 2021 15:14:07 +0200 Subject: [PATCH 005/103] phpcs conformity --- ...igrateCrmUsers.php => MigrateCrmUsers.php} | 16 +-- app/Console/Kernel.php | 5 +- .../Controllers/Api/v1/ArcadiaController.php | 69 ++++++++----- .../Controllers/Api/v1/CustomerController.php | 71 +++++++------ app/Http/Controllers/CustomerController.php | 48 ++++----- app/Http/Controllers/LoginController.php | 3 +- app/Http/Kernel.php | 2 - app/Lib/MigrateOldData.php | 99 ++++++++++--------- app/Providers/AuthServiceProvider.php | 6 +- app/Providers/RouteServiceProvider.php | 15 +-- config/app.php | 1 - config/auth.php | 5 - config/cache.php | 4 +- routes/web.php | 6 +- tests/Feature/ApiV2/SingleOptinTest.php | 5 +- 15 files changed, 182 insertions(+), 173 deletions(-) rename app/Console/Commands/{migrateCrmUsers.php => MigrateCrmUsers.php} (79%) diff --git a/app/Console/Commands/migrateCrmUsers.php b/app/Console/Commands/MigrateCrmUsers.php similarity index 79% rename from app/Console/Commands/migrateCrmUsers.php rename to app/Console/Commands/MigrateCrmUsers.php index da55ab4..8e50097 100644 --- a/app/Console/Commands/migrateCrmUsers.php +++ b/app/Console/Commands/MigrateCrmUsers.php @@ -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 { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 17408e5..6d23f9c 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -13,9 +13,7 @@ class Kernel extends ConsoleKernel * * @var array */ - protected $commands = [ - // - ]; + protected $commands = []; /** * Define the application's command schedule. @@ -25,7 +23,6 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - // $schedule->command('inspire')->hourly(); } /** diff --git a/app/Http/Controllers/Api/v1/ArcadiaController.php b/app/Http/Controllers/Api/v1/ArcadiaController.php index ffd1fac..757343e 100644 --- a/app/Http/Controllers/Api/v1/ArcadiaController.php +++ b/app/Http/Controllers/Api/v1/ArcadiaController.php @@ -33,10 +33,10 @@ class ArcadiaController extends Controller ]); } - $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(); @@ -57,14 +57,15 @@ class ArcadiaController extends Controller 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, $CustomerInfo, $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) { Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage()); @@ -88,18 +89,19 @@ 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 = []; @@ -184,13 +186,15 @@ class ArcadiaController extends Controller } } - 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 @@ -245,29 +249,42 @@ class ArcadiaController extends Controller } } - $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')); diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/v1/CustomerController.php index 82a7d24..4167a26 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/v1/CustomerController.php @@ -54,7 +54,7 @@ class CustomerController extends Controller // 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(); @@ -72,19 +72,26 @@ class CustomerController extends Controller } } - $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 = 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); @@ -151,6 +158,7 @@ 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(), [ @@ -176,64 +184,66 @@ class CustomerController extends Controller return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']); } - if ($request->has('firstname')) + if ($request->has('firstname')) { $CustomerInfo->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING); + } - if ($request->has('lastname')) + if ($request->has('lastname')) { $CustomerInfo->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); + } if ($request->has('birth_date')) { - if (!empty($request->birth_date)){ + if (!empty($request->birth_date)) { $CustomerInfo->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->birth_date = null; } } if ($request->has('address')) { - if (!empty($request->address)){ + if (!empty($request->address)) { $CustomerInfo->address = filter_var($request->address, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->address = null; } } if ($request->has('address2')) { - if (!empty($request->address2)){ + if (!empty($request->address2)) { $CustomerInfo->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->address2 = null; } } if ($request->has('phone')) { - if (!empty($request->phone)){ + if (!empty($request->phone)) { $CustomerInfo->phone = filter_var($request->phone, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->phone = null; } } if ($request->has('country')) { - if (!empty($request->country)){ + if (!empty($request->country)) { $CustomerInfo->country = filter_var($request->country, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->country = null; } } if ($request->has('city')) { - if (!empty($request->city)){ + if (!empty($request->city)) { $CustomerInfo->city = filter_var($request->city, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->city = null; } } if ($request->has('postcode')) { - if (!empty($request->postcode)){ + if (!empty($request->postcode)) { $CustomerInfo->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING); - }else{ + } else { $CustomerInfo->postcode = null; } } @@ -411,6 +421,7 @@ class CustomerController extends Controller * @param Request $request * @param $id */ + // phpcs:disable Squiz.PHP.NonExecutableCode.Unreachable public function delete(Request $request, $id) { die("Not implemented yet"); diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 888da63..b1830fd 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -15,20 +15,20 @@ class CustomerController extends Controller return view('customers.index', compact('brands')); } - public function show($id, $brandId) + public function show(Customer $customer, Brand $brand) { - $CustomerInfo = CustomerInfo::where([['customer_id', $id], ['brand_id', $brandId]])->first(); - $customerLogin = CustomerLogin::where([['customer_id', $id], ['brand_id', $brandId]])->first(); - if (!$CustomerInfo) { - abort(404); - } + $CustomerInfo = 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('CustomerInfo', 'customerLogin')); } - public function filter($texte, $brandId = null) + public function filter($texte, Brand $brand = null) { - if ($brandId !== null) { - $customers = Customer::join('customer_infos', 'customers.id', '=', 'customer_infos.customer_id')->join('customer_login', 'customers.id', '=', 'customer_login.customer_id')->select( + 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', @@ -38,26 +38,28 @@ class CustomerController extends Controller 'customer_login.last_connection' )->where([ ['lastname', 'like', '%' . $texte . '%'], - ['customer_infos.brand_id', $brandId], - ['customer_login.brand_id', $brandId] + ['customer_infos.brand_id', $brand->id], + ['customer_login.brand_id', $brand->id] ])->orWhere([ ['email', 'like', '%' . $texte . '%'], - ['customer_infos.brand_id', $brandId], - ['customer_login.brand_id', $brandId] + ['customer_infos.brand_id', $brand->id], + ['customer_login.brand_id', $brand->id] ])->get(); } else { - $customers = CustomerInfo::join('customers', 'customers.id', '=', 'customer_infos.customer_id')->join('customer_login', function ($join) { + $customers = CustomerInfo::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(); + })->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(); } return response()->json($customers); diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index e779f47..f7033f7 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -23,7 +23,8 @@ class LoginController extends Controller ) { 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."); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3bd80bf..4317844 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -15,7 +15,6 @@ 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, @@ -37,7 +36,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, diff --git a/app/Lib/MigrateOldData.php b/app/Lib/MigrateOldData.php index 38eb886..e76649f 100644 --- a/app/Lib/MigrateOldData.php +++ b/app/Lib/MigrateOldData.php @@ -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(); diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 551e307..7cebb24 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -13,9 +13,7 @@ class AuthServiceProvider extends ServiceProvider * * @var array */ - protected $policies = [ - // 'App\Models\Model' => 'App\Policies\ModelPolicy', - ]; + protected $policies = []; /** * Register any authentication / authorization services. @@ -25,7 +23,5 @@ class AuthServiceProvider extends ServiceProvider public function boot() { $this->registerPolicies(); - - // } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 5d93da2..5585e8d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Models\Brand; use App\Models\Customer; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; @@ -20,15 +21,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. * @@ -44,8 +36,11 @@ class RouteServiceProvider extends ServiceProvider Route::middleware('web')->namespace($this->namespace)->group(base_path('routes/web.php')); Route::bind('customer', function ($value) { - return Customer::where('id', $value)->orWhere('email', $value)->firstOrFail(); + return Customer::where('id', $value) + ->orWhere('email', $value) + ->firstOrFail(); }); + Route::model('brand', Brand::class); }); } diff --git a/config/app.php b/config/app.php index 5fc14cf..f3a82ab 100644 --- a/config/app.php +++ b/config/app.php @@ -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, diff --git a/config/auth.php b/config/auth.php index 62110d7..b153c26 100644 --- a/config/auth.php +++ b/config/auth.php @@ -75,11 +75,6 @@ return [ 'driver' => 'eloquent', 'model' => App\Models\Service::class, ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], ], /* diff --git a/config/cache.php b/config/cache.php index a8eaf93..9b4d7ab 100644 --- a/config/cache.php +++ b/config/cache.php @@ -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'), diff --git a/routes/web.php b/routes/web.php index 6921e96..3ee1c53 100644 --- a/routes/web.php +++ b/routes/web.php @@ -37,10 +37,10 @@ Route::middleware(['auth'])->group(function () { Route::get('/services/{service}/token/generate', [ServiceController::class, 'generateApiToken']) ->name('generate-token-service'); - // Clients (Customer) + // Clients Route::get('/customers', [CustomerController::class, 'index'])->name('customers'); - Route::get('/customers/filter/{texte}/{brandId?}', [CustomerController::class, 'filter'])->name('filter-customer'); - Route::get('/customer/{id}/{brandId}', [CustomerController::class, 'show'])->name('show-customer'); + Route::get('/customers/filter/{texte}/{brand?}', [CustomerController::class, 'filter'])->name('filter-customer'); + Route::get('/customer/{customer}/{brand}', [CustomerController::class, 'show'])->name('show-customer'); // Single optins Route::get('/single-optins', [SingleOptinsController::class, 'index'])->name('single-optins'); diff --git a/tests/Feature/ApiV2/SingleOptinTest.php b/tests/Feature/ApiV2/SingleOptinTest.php index 30e52f8..3225592 100644 --- a/tests/Feature/ApiV2/SingleOptinTest.php +++ b/tests/Feature/ApiV2/SingleOptinTest.php @@ -26,14 +26,14 @@ class SingleOptinTest extends ApiBase 'email' => 'warthogdj@hotmail.fr', 'optin' => 1 ]); - $json = $response->decodeResponseJson(); + $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ 'email' => 'warthogdj@hotmail.fr', 'optin' => 0 ]); - $json = $response->decodeResponseJson(); + $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $this->assertNull(SingleOptin::where('email', 'warthogdj@hotmail.fr')->first()); } @@ -48,7 +48,6 @@ class SingleOptinTest extends ApiBase ]); $json = $response->decodeResponseJson(); - // dd($json); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); $this->assertEquals(ApiError::singleOptinCustomerExists()->value, $json->json('data.code')); $this->assertEquals(ApiError::singleOptinCustomerExists()->label, $json->json('data.message')); From c04d8e37bb06413211e010221cf2c67c054171d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 22 Jul 2021 16:54:28 +0200 Subject: [PATCH 006/103] add crm-client as submodule --- .gitmodules | 3 +++ clients/V2 | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 clients/V2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..05894ad --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "clients/V2"] + path = clients/V2 + url = https://ldc-web:phaeZ6ej@voight001.lesidc.com/crm-client diff --git a/clients/V2 b/clients/V2 new file mode 160000 index 0000000..c9de856 --- /dev/null +++ b/clients/V2 @@ -0,0 +1 @@ +Subproject commit c9de85693e56511fc8774b051523dcd9911a908f From 01b3659a36e2edab4981f181f526014188071049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 28 Jul 2021 14:17:45 +0200 Subject: [PATCH 007/103] fix migration --- ...0637_change_foreign_keys_to_cascade_on_customer_delete.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php b/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php index a6b3e3c..c8da5d9 100644 --- a/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php +++ b/database/migrations/2021_03_04_210637_change_foreign_keys_to_cascade_on_customer_delete.php @@ -14,6 +14,7 @@ class ChangeForeignKeysToCascadeOnCustomerDelete extends Migration */ public function up() { + Schema::disableForeignKeyConstraints(); Schema::table('customer_infos', function (Blueprint $table) { $table->dropForeign(['customer_id']); $table->foreign('customer_id')->references('id') @@ -30,6 +31,7 @@ class ChangeForeignKeysToCascadeOnCustomerDelete extends Migration $table->foreign('brand_id')->references('id') ->on('brands')->onDelete('cascade'); }); + Schema::enableForeignKeyConstraints(); } /** @@ -39,6 +41,7 @@ class ChangeForeignKeysToCascadeOnCustomerDelete extends Migration */ public function down() { + Schema::disableForeignKeyConstraints(); Schema::table('customer_infos', function (Blueprint $table) { $table->dropForeign(['customer_id']); $table->foreign('customer_id')->references('id') @@ -55,5 +58,6 @@ class ChangeForeignKeysToCascadeOnCustomerDelete extends Migration $table->foreign('brand_id', 'customer_login_brand_id_foreign')->references('id') ->on('brands')->onUpdate('restrict')->onDelete('restrict'); }); + Schema::enableForeignKeyConstraints(); } } From 19e1a022a2e09251ac8e87691d097d0b01c50dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 28 Jul 2021 14:18:47 +0200 Subject: [PATCH 008/103] =?UTF-8?q?mise=20=C3=A0=20plat,=20renomage=20v1?= =?UTF-8?q?=20et=20v2=20en=20V1=20et=20V2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CRM V2.code-workspace | 7 ++ app/Console/Commands/GenerateCrmClient.php | 97 +++++++++++++++++++ .../Api/{v2 => V2}/ApiErrorException.php | 2 +- .../Api/{v2 => V2}/CrmException.php | 2 +- app/Exceptions/Handler.php | 4 +- .../Api/{v1 => V1}/ArcadiaController.php | 2 +- .../Api/{v1 => V1}/CustomerController.php | 2 +- .../Api/{v2 => V2}/ApiController.php | 4 +- .../Api/{v2 => V2}/ArcadiaController.php | 6 +- .../Api/{v2 => V2}/CustomerController.php | 14 +-- .../Api/{v2 => V2}/OptinController.php | 6 +- .../Controllers/Api/v1/CRM V2.code-workspace | 8 -- app/Http/Middleware/ApiCheckTimeStamps.php | 2 +- app/Http/Middleware/ApiResponse.php | 2 +- app/Http/Middleware/Authenticate.php | 4 +- .../Api/{v2 => V2}/AuthenticateRequest.php | 2 +- .../Api/{v2 => V2}/BaseApiRequest.php | 2 +- .../{v2 => V2}/SendArchadiaMailRequest.php | 2 +- .../Api/{v2 => V2}/SingleOptinRequest.php | 2 +- .../Api/{v2 => V2}/StoreCustomerRequest.php | 2 +- .../{v2 => V2}/StoreSingleOptinRequest.php | 2 +- .../Api/{v2 => V2}/UpdateCustomerRequest.php | 2 +- .../{v2 => V2}/UpdateSingleOptinRequest.php | 2 +- .../Resources/Api/{v2 => V2}/ApiResponse.php | 4 +- .../Api/{v2 => V2}/CustomerResource.php | 2 +- .../Api/{v2 => V2}/SingleOptinResource.php | 2 +- routes/api.php | 12 +-- tests/Feature/ApiV2/ArcadiaTest.php | 1 - tests/Feature/ApiV2/CustomerTest.php | 3 - tests/Unit/HelperTest.php | 2 +- 30 files changed, 149 insertions(+), 55 deletions(-) create mode 100644 CRM V2.code-workspace create mode 100644 app/Console/Commands/GenerateCrmClient.php rename app/Exceptions/Api/{v2 => V2}/ApiErrorException.php (94%) rename app/Exceptions/Api/{v2 => V2}/CrmException.php (86%) rename app/Http/Controllers/Api/{v1 => V1}/ArcadiaController.php (99%) rename app/Http/Controllers/Api/{v1 => V1}/CustomerController.php (99%) rename app/Http/Controllers/Api/{v2 => V2}/ApiController.php (90%) rename app/Http/Controllers/Api/{v2 => V2}/ArcadiaController.php (95%) rename app/Http/Controllers/Api/{v2 => V2}/CustomerController.php (95%) rename app/Http/Controllers/Api/{v2 => V2}/OptinController.php (89%) delete mode 100644 app/Http/Controllers/Api/v1/CRM V2.code-workspace rename app/Http/Requests/Api/{v2 => V2}/AuthenticateRequest.php (93%) rename app/Http/Requests/Api/{v2 => V2}/BaseApiRequest.php (95%) rename app/Http/Requests/Api/{v2 => V2}/SendArchadiaMailRequest.php (98%) rename app/Http/Requests/Api/{v2 => V2}/SingleOptinRequest.php (86%) rename app/Http/Requests/Api/{v2 => V2}/StoreCustomerRequest.php (98%) rename app/Http/Requests/Api/{v2 => V2}/StoreSingleOptinRequest.php (84%) rename app/Http/Requests/Api/{v2 => V2}/UpdateCustomerRequest.php (98%) rename app/Http/Requests/Api/{v2 => V2}/UpdateSingleOptinRequest.php (87%) rename app/Http/Resources/Api/{v2 => V2}/ApiResponse.php (93%) rename app/Http/Resources/Api/{v2 => V2}/CustomerResource.php (98%) rename app/Http/Resources/Api/{v2 => V2}/SingleOptinResource.php (90%) diff --git a/CRM V2.code-workspace b/CRM V2.code-workspace new file mode 100644 index 0000000..b51044b --- /dev/null +++ b/CRM V2.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php new file mode 100644 index 0000000..5e7a818 --- /dev/null +++ b/app/Console/Commands/GenerateCrmClient.php @@ -0,0 +1,97 @@ +copyEnums(); + $this->copyExceptions(); + } + + private function copyExceptions(): void + { + $this->info('Copy Exceptions'); + $this->copyToFolder([ + CrmException::class + ], 'Exceptions'); + } + + private function copyEnums(): void + { + $this->info('Copy Enums'); + $this->copyToFolder([ + ApiError::class, + ApiStatus::class, + ArcadiaMailType::class, + ArcadiaOrderType::class + ], 'Enums'); + } + + /** + * 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->replaceNamespaceinPhpFile($dest); + } + } + + private function replaceNamespaceinPhpFile(string $source): void + { + $search = 'namespace App'; + $replace = 'namespace ' . self::CLIENTNAMESPACE; + $fContent = File::get($source); + $pos = strpos($fContent, $search); + if ($pos !== false) { + File::put($source, substr_replace($fContent, $replace, $pos, strlen($search))); + } + } +} diff --git a/app/Exceptions/Api/v2/ApiErrorException.php b/app/Exceptions/Api/V2/ApiErrorException.php similarity index 94% rename from app/Exceptions/Api/v2/ApiErrorException.php rename to app/Exceptions/Api/V2/ApiErrorException.php index 9077e08..6b10654 100644 --- a/app/Exceptions/Api/v2/ApiErrorException.php +++ b/app/Exceptions/Api/V2/ApiErrorException.php @@ -1,6 +1,6 @@ authenticate($guards, $apiToken); - $route->setParameter('service', $service); + app()->bind(Service::class, function () use ($service) { + return $service; + }); } catch (ModelNotFoundException $e) { throw new AuthenticationException('Unauthenticated.', $guards); } diff --git a/app/Http/Requests/Api/v2/AuthenticateRequest.php b/app/Http/Requests/Api/V2/AuthenticateRequest.php similarity index 93% rename from app/Http/Requests/Api/v2/AuthenticateRequest.php rename to app/Http/Requests/Api/V2/AuthenticateRequest.php index 309798e..4f6a710 100644 --- a/app/Http/Requests/Api/v2/AuthenticateRequest.php +++ b/app/Http/Requests/Api/V2/AuthenticateRequest.php @@ -1,6 +1,6 @@ makeRequest('App\Http\Controllers\Api\v1\CustomerController@activateAccount'); + $request = $this->makeRequest('App\Http\Controllers\Api\V1\CustomerController@activateAccount'); $this->assertTrue(Helper::isRequestForApiV1($request)); } From 39beb54b19171d4d625ca03c15338db2f03cb06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 2 Aug 2021 11:46:08 +0200 Subject: [PATCH 009/103] Add bearer_pwd to api auth --- app/Http/Controllers/Api/V2/ApiController.php | 15 ++++++--- app/Http/Controllers/ServiceController.php | 8 +++-- app/Http/Middleware/Authenticate.php | 3 +- app/Models/Service.php | 33 +++++++++++++++---- ...03828_add_bearer_pwd_to_services_table.php | 32 ++++++++++++++++++ tests/Feature/ApiV2/ApiBase.php | 6 ++++ tests/Feature/ApiV2/ApiTest.php | 2 +- 7 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 database/migrations/2021_08_02_103828_add_bearer_pwd_to_services_table.php diff --git a/app/Http/Controllers/Api/V2/ApiController.php b/app/Http/Controllers/Api/V2/ApiController.php index 009d927..ee0c622 100644 --- a/app/Http/Controllers/Api/V2/ApiController.php +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -8,6 +8,7 @@ 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 { @@ -21,13 +22,17 @@ class ApiController extends Controller try { /** @var \App\Models\Service */ $service = Service::getFromBearerToken($request->bearerToken()); - $service->generateApiToken(); - $service->saveOrFail(); - return response()->json([ - 'api_token' => $service->api_token - ]); } catch (ModelNotFoundException $e) { throw new ApiErrorException(ApiError::authFailed()); } + if (!Hash::check($request->bearer_pwd, $service->bearer_pwd)) { + throw new ApiErrorException(ApiError::authFailed()); + } + + $service->generateApiToken(); + $service->saveOrFail(); + return response()->json([ + 'api_token' => $service->api_token + ]); } } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 14f5675..11a4eab 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -30,7 +30,7 @@ class ServiceController extends Controller $service->save(); return redirect()->route('services')->with('success', $edit ? 'Modifications enregistrées !' : sprintf( - 'Token du service : %s', + 'Token du service : \'%s\'', $service->bearer_token )); } @@ -41,7 +41,9 @@ class ServiceController extends Controller public function generateApiToken(Service $service) { $service->generateBearerToken(); - $service->save(); - return back()->with('success', "Nouveau token d'API de " . $service->name . " : " . $service->bearer_token); + $bearerPwd = $service->bearer_pwd; + $service->saveOrFail(); + return back() + ->with('success', "Nouveau token d'API de $service->name : '$service->bearer_token' et mot de passe '$bearerPwd'"); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 7dc9bd1..a04d10d 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -31,7 +31,6 @@ class Authenticate extends Middleware $isApiV2Request = $request->isJson() && Helper::isRequestForApiV2($request); if ($isApiV2Request) { - //! Is Bearer Token needed ? // 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 @@ -55,7 +54,7 @@ class Authenticate extends Middleware try { $service = Service::getFromApiToken($apiToken); - $service->authenticate($guards, $apiToken); + $service->authenticateApi($guards, $apiToken); app()->bind(Service::class, function () use ($service) { return $service; }); diff --git a/app/Models/Service.php b/app/Models/Service.php index b8c740d..01b8ee2 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -6,6 +6,7 @@ use Illuminate\Auth\AuthenticationException; 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 @@ -20,20 +21,39 @@ class Service extends Model { static::creating(function ($service) { $service->generateBearerToken(); + if (Hash::needsRehash($service->bearer_pwd)) { + $service->bearer_pwd = Hash::make($service->bearer_pwd); + } + }); + static::updating(function ($service) { + if (Hash::needsRehash($service->bearer_pwd)) { + $service->bearer_pwd = Hash::make($service->bearer_pwd); + } }); } - public function generateApiToken() + /** + * Generates a new Api Token + * + * @return string + */ + public function generateApiToken(): string { - $this->api_token = hash('sha256', Str::random(80)); + $this->api_token = hash('sha256', Str::uuid()); $this->api_token_date = now(); return $this->api_token; } - public function generateBearerToken() + /** + * Generates new bearer credentials + * + * @return string + */ + public function generateBearerToken(): string { - $this->bearer_token = hash('sha256', Str::random(80)); + $this->bearer_token = hash('sha256', Str::uuid()); $this->bearer_token_date = now(); + $this->bearer_pwd = hash('ripemd160', Str::uuid()); return $this->bearer_token; } @@ -83,13 +103,14 @@ class Service extends Model } /** - * Authenticate from a token string + * Authenticate from the api token string * * @param array $guards * @param string $apiToken * @return void + * @throws \Illuminate\Auth\AuthenticationException */ - public function authenticate(array $guards, string $apiToken) + public function authenticateApi(array $guards, string $apiToken) { if ($this->api_token !== $apiToken) { diff --git a/database/migrations/2021_08_02_103828_add_bearer_pwd_to_services_table.php b/database/migrations/2021_08_02_103828_add_bearer_pwd_to_services_table.php new file mode 100644 index 0000000..70ff424 --- /dev/null +++ b/database/migrations/2021_08_02_103828_add_bearer_pwd_to_services_table.php @@ -0,0 +1,32 @@ +string('bearer_pwd')->after('bearer_token'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('bearer_pwd'); + }); + } +} diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index ed0d559..3e3ec26 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -79,6 +79,12 @@ class ApiBase extends BaseTestCase /** @var \App\Models\Service $service */ $service = $brand->services()->firstOrFail(); + // regen bearer token to get the pwd in clear + $service->generateBearerToken(); + $bearer_pwd = $service->bearer_pwd; + $service->saveOrFail(); + $service->bearer_pwd = $bearer_pwd; + return compact('customer', 'service', 'brand'); } } diff --git a/tests/Feature/ApiV2/ApiTest.php b/tests/Feature/ApiV2/ApiTest.php index 5b286f2..0e79ad8 100644 --- a/tests/Feature/ApiV2/ApiTest.php +++ b/tests/Feature/ApiV2/ApiTest.php @@ -39,7 +39,7 @@ class ApiTest extends ApiBase $response = $this->callApi( 'post', route('apiV2.authenticate'), - [], + ['bearer_pwd' => $service->bearer_pwd], ['Authorization' => sprintf('Bearer %s', $service->bearer_token)] ); From 873d57d8c10a0a4cc533726b8fe216159c4588a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 2 Aug 2021 19:06:01 +0200 Subject: [PATCH 010/103] Exception handler --- app/Exceptions/Handler.php | 14 +++--- app/Mail/ExceptionAlert.php | 49 +++++++++++++++++++ config/mail.php | 4 ++ .../views/mail/exception-plain.blade.php | 13 +++++ resources/views/mail/exception.blade.php | 3 +- 5 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 app/Mail/ExceptionAlert.php create mode 100644 resources/views/mail/exception-plain.blade.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 458261d..27363a2 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -6,6 +6,7 @@ use App\Enums\ApiError; use App\Exceptions\Api\V2\ApiErrorException; use App\Exceptions\Api\V2\CrmException; use App\Lib\Helper; +use App\Mail\ExceptionAlert; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; @@ -56,17 +57,15 @@ class Handler extends ExceptionHandler */ 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('mails.exception', ['exception' => $e], function ($m) { - $m->to( - config('mail.contactsList.serviceweb'), - sprintf('%s Exception Handler', config('app.name')) - )->subject('Need help !, an error occured in your application, please check Logs ASAP'); - }); + Mail::send(new ExceptionAlert($e)); } } finally { parent::report($e); @@ -84,8 +83,7 @@ class Handler extends ExceptionHandler */ public function render($request, Throwable $e) { - - if (Helper::isRequestForApiV2($request)) { + if (Helper::isRequestForApiV2($request) and $request->ajax()) { switch (get_class($e)) { case AuthenticationException::class: /** @var AuthenticationException $e */ diff --git a/app/Mail/ExceptionAlert.php b/app/Mail/ExceptionAlert.php new file mode 100644 index 0000000..5753f2d --- /dev/null +++ b/app/Mail/ExceptionAlert.php @@ -0,0 +1,49 @@ +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'), + sprintf('%s Exception Handler', config('app.name')) + ); + } +} diff --git a/config/mail.php b/config/mail.php index e58d776..b3d48a7 100644 --- a/config/mail.php +++ b/config/mail.php @@ -90,6 +90,10 @@ return [ 'MAIL_ADMIN' => env('MAIL_ADMIN'), + 'contactsList' => [ + 'serviceweb' => env('MAIL_LIST_SERVICEWEB', 'informatique.internet@ldc.fr') + ], + /* |-------------------------------------------------------------------------- | Markdown Mail Settings diff --git a/resources/views/mail/exception-plain.blade.php b/resources/views/mail/exception-plain.blade.php new file mode 100644 index 0000000..966ae73 --- /dev/null +++ b/resources/views/mail/exception-plain.blade.php @@ -0,0 +1,13 @@ +{{ $appName }} Exception Handler + +Une erreur est survenue sur {{ $appName }} {{ $appUrl }} + +{{ $exception->getMessage() }} + +url : {{ $url ?? 'none' }} +date : {{ date('Y-m-d H:i:s') }} +code : {{$exception->getCode() }} +file : {{$exception->getFile() }} +line : {{$exception->getLine() }} + +Veuillez vérifier les logs pour en voir la trace. diff --git a/resources/views/mail/exception.blade.php b/resources/views/mail/exception.blade.php index 5d0a7ab..18e4298 100644 --- a/resources/views/mail/exception.blade.php +++ b/resources/views/mail/exception.blade.php @@ -5,8 +5,9 @@ -

Une erreur est survenue sur {{ config('app.name') config('app.url') }}

+

Une erreur est survenue sur {{ $appName }} {{ $appUrl }}

{{ $exception->getMessage() }}

+

url : {{ $url ?? 'none' }}

date : {{ date('Y-m-d H:i:s') }}

code : {{$exception->getCode() }}

file : {{$exception->getFile() }}

From d62e3a18e1aa25dc207387faba66688b3e788be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 09:33:28 +0200 Subject: [PATCH 011/103] Add throttle handle --- app/Enums/ApiError.php | 7 +++++-- app/Exceptions/Handler.php | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 8a0fd98..9e84968 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -17,6 +17,7 @@ use Kwaadpepper\Enum\BaseEnum; * @method static self methodNotAllowed() * @method static self wrongTimeStamp() * @method static self arcardiaXmlMailFailed() + * @method static self throttleRequestsException() */ class ApiError extends BaseEnum { @@ -34,7 +35,8 @@ class ApiError extends BaseEnum 'singleOptinCustomerExists' => 120, 'methodNotAllowed' => 150, 'wrongTimeStamp' => 151, - 'arcardiaXmlMailFailed' => 204 + 'arcardiaXmlMailFailed' => 204, + 'throttleRequestsException' => 429 ]; } @@ -50,7 +52,8 @@ class ApiError extends BaseEnum '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'), - 'arcardiaXmlMailFailed' => trans('Arcadia XML Creation failed.') + 'arcardiaXmlMailFailed' => trans('Arcadia XML Creation failed.'), + 'throttleRequestsException' => trans('Throttle: too many request have been attempted, please retry later') ]; } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 27363a2..0f2d67d 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -10,6 +10,7 @@ use App\Mail\ExceptionAlert; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Http\Exceptions\ThrottleRequestsException; use Illuminate\Support\Facades\Mail; use Illuminate\Validation\ValidationException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; @@ -83,7 +84,7 @@ class Handler extends ExceptionHandler */ public function render($request, Throwable $e) { - if (Helper::isRequestForApiV2($request) and $request->ajax()) { + if (Helper::isRequestForApiV2($request) and $request->isJson()) { switch (get_class($e)) { case AuthenticationException::class: /** @var AuthenticationException $e */ @@ -119,6 +120,12 @@ class Handler extends ExceptionHandler 'message' => $e->getMessage(), 'code' => $e->getCode() ], $e->getStatusCode()); + case ThrottleRequestsException::class: + /** @var ThrottleRequestsException $e */ + return response()->json([ + 'message' => ApiError::throttleRequestsException()->label, + 'code' => ApiError::throttleRequestsException()->value + ], 429); case CrmException::class: default: return response()->json([ From 3619593485a00dfff97660bfad18e975049b8392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 09:42:48 +0200 Subject: [PATCH 012/103] =?UTF-8?q?Mise=20=C3=A0=20plat=20CRM=20v2=20-=20a?= =?UTF-8?q?jout=20une=20colonne=20new=20country=20pour=20enum=20-=20fix=20?= =?UTF-8?q?des=20test=20v2=20-=20fix=20du=20handler=20-=20fix=20des=20midd?= =?UTF-8?q?leware=20-=20fix=20du=20timestamp=20checker=20-=20fix=20des=20s?= =?UTF-8?q?tats=20fix=20du=20api=20handler=20-=20ajout=20du=20middleware?= =?UTF-8?q?=20json=20only=20-=20remove=20crm-id=20du=20client=20en=20faveu?= =?UTF-8?q?r=20du=20hashcode=20seulement=20-=20fix=20du=20route=20service?= =?UTF-8?q?=20provider=20-=20d=C3=A9placement=20de=20la=20route=20arcradia?= =?UTF-8?q?=20en=20dehors=20du=20customer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/GenerateCrmClient.php | 53 +++++++++- app/Http/Controllers/CustomerController.php | 20 ++-- app/Http/Kernel.php | 9 +- app/Http/Middleware/ApiCheckTimeStamps.php | 7 +- app/Http/Middleware/ApiResponse.php | 39 +++++++- app/Http/Middleware/Authenticate.php | 4 - app/Http/Middleware/JsonOnly.php | 25 +++++ .../Resources/Api/V2/CustomerResource.php | 11 ++- app/Models/CustomerInfo.php | 49 +++++++++- app/Models/Service.php | 26 ++--- app/Providers/AppServiceProvider.php | 2 +- app/Providers/RouteServiceProvider.php | 15 +-- composer.json | 3 +- config/api.php | 8 +- database/factories/CustomerInfoFactory.php | 6 +- ...country_column_to_customer_infos_table.php | 32 ++++++ routes/api.php | 7 +- tests/Feature/ApiV2/ApiBase.php | 11 ++- tests/Feature/ApiV2/ApiTest.php | 6 ++ tests/Feature/ApiV2/ArcadiaTest.php | 4 +- tests/Feature/ApiV2/CustomerTest.php | 35 +++---- tests/Unit/CrmClientTester.php | 76 +++++++++++++++ tests/Unit/CrmClientV2ClientTest.php | 39 ++++++++ tests/Unit/CrmClientV2ConfigTest.php | 13 +++ tests/Unit/CrmClientV2CustomerTest.php | 97 +++++++++++++++++++ tests/Unit/CrmClientV2RouterTest.php | 45 +++++++++ 26 files changed, 545 insertions(+), 97 deletions(-) create mode 100644 app/Http/Middleware/JsonOnly.php create mode 100644 database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php create mode 100644 tests/Unit/CrmClientTester.php create mode 100644 tests/Unit/CrmClientV2ClientTest.php create mode 100644 tests/Unit/CrmClientV2ConfigTest.php create mode 100644 tests/Unit/CrmClientV2CustomerTest.php create mode 100644 tests/Unit/CrmClientV2RouterTest.php diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index 5e7a818..c22a060 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -6,9 +6,11 @@ use App\Enums\ApiError; use App\Enums\ApiStatus; use App\Enums\ArcadiaMailType; use App\Enums\ArcadiaOrderType; +use App\Enums\Country; use App\Exceptions\Api\V2\CrmException; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Route; class GenerateCrmClient extends Command { @@ -39,6 +41,31 @@ class GenerateCrmClient extends Command { $this->copyEnums(); $this->copyExceptions(); + $this->copyTranslations(); + $this->copyRoutesList(); + } + + 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 @@ -56,10 +83,23 @@ class GenerateCrmClient extends Command ApiError::class, ApiStatus::class, ArcadiaMailType::class, - ArcadiaOrderType::class + ArcadiaOrderType::class, + Country::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 * @@ -87,9 +127,16 @@ class GenerateCrmClient extends Command private function replaceNamespaceinPhpFile(string $source): void { $search = 'namespace App'; - $replace = 'namespace ' . self::CLIENTNAMESPACE; + $replace = 'namespace ' . self::CLIENTNAMESPACE; $fContent = File::get($source); - $pos = strpos($fContent, $search); + + // 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, substr_replace($fContent, $replace, $pos, strlen($search))); } diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index b1830fd..55387ed 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -17,39 +17,39 @@ class CustomerController extends Controller public function show(Customer $customer, Brand $brand) { - $CustomerInfo = CustomerInfo::where('customer_id', $customer->id) + $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('CustomerInfo', 'customerLogin')); + return view('customers.show', compact('customerInfos', 'customerLogin')); } 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( + ->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_login.last_connection' + 'customer_logins.last_connection' )->where([ ['lastname', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brand->id], - ['customer_login.brand_id', $brand->id] + ['customer_logins.brand_id', $brand->id] ])->orWhere([ ['email', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brand->id], - ['customer_login.brand_id', $brand->id] + ['customer_logins.brand_id', $brand->id] ])->get(); } else { $customers = CustomerInfo::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'); + ->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', @@ -57,7 +57,7 @@ class CustomerController extends Controller 'customer_infos.opt_in', 'customers.email', 'customer_infos.brand_id', - 'customer_login.last_connection' + 'customer_logins.last_connection' )->where('lastname', 'like', '%' . $texte . '%') ->orWhere('email', 'like', '%' . $texte . '%')->get(); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 4317844..ccf3d9e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -21,9 +21,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \App\Http\Middleware\XssSanitizer::class, - \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, - \App\Http\Middleware\ApiResponse::class, - \App\Http\Middleware\ApiCheckTimeStamps::class + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class ]; /** @@ -43,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 ], ]; diff --git a/app/Http/Middleware/ApiCheckTimeStamps.php b/app/Http/Middleware/ApiCheckTimeStamps.php index b146626..ddca7ef 100644 --- a/app/Http/Middleware/ApiCheckTimeStamps.php +++ b/app/Http/Middleware/ApiCheckTimeStamps.php @@ -25,17 +25,16 @@ class ApiCheckTimeStamps public function handle(Request $request, Closure $next) { if (Helper::isRequestForApiV2($request)) { - $badRequest = new ApiErrorException(ApiError::wrongTimeStamp(), 400); if (!$request->hasHeader('X-API-TIMESTAMP') or !is_numeric($request->header('X-API-TIMESTAMP'))) { - throw $badRequest; + throw new ApiErrorException(ApiError::wrongTimeStamp(), 400); } if (!$request->timestamp or !is_numeric($request->timestamp)) { - throw $badRequest; + throw new ApiErrorException(ApiError::wrongTimeStamp(), 400); } $diff = intval($request->timestamp) - intval($request->header('X-API-TIMESTAMP')); $maxDiff = 120; if (abs($diff) > $maxDiff) { - throw $badRequest; + throw new ApiErrorException(ApiError::wrongTimeStamp(), 400); } } return $next($request); diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index cdef34c..1a9eb8e 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -4,9 +4,13 @@ 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\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; class ApiResponse { @@ -20,11 +24,38 @@ class ApiResponse public function handle(Request $request, Closure $next) { $response = $next($request); - if (get_class($response) === JsonResponse::class) { - /** @ var \App\Enums\ApiStatus */ - $status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO(); - $response->setData(new V2ApiResponse((object)$response->getData(), $status)); + + $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())); } + + /** @ var \App\Enums\ApiStatus */ + $status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO(); + $response->setData(new V2ApiResponse((object)$response->getData(), $status)); + return $response; } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index a04d10d..70d5318 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -78,10 +78,6 @@ class Authenticate extends Middleware ); } - if ($isApiV2Request || $isApiV1Request) { - Service::incrementStat($apiToken); - } - return $response; } /** diff --git a/app/Http/Middleware/JsonOnly.php b/app/Http/Middleware/JsonOnly.php new file mode 100644 index 0000000..5e1aa19 --- /dev/null +++ b/app/Http/Middleware/JsonOnly.php @@ -0,0 +1,25 @@ +isJson()) { + abort(400); + } + + return $next($request); + } +} diff --git a/app/Http/Resources/Api/V2/CustomerResource.php b/app/Http/Resources/Api/V2/CustomerResource.php index 446c37b..fe7ee4a 100644 --- a/app/Http/Resources/Api/V2/CustomerResource.php +++ b/app/Http/Resources/Api/V2/CustomerResource.php @@ -21,7 +21,7 @@ class CustomerResource extends JsonResource private $login; private static $skeleton = [ - 'crm_id' => '', + 'hashcode' => '', 'firstname' => '', 'lastname' => '', 'email' => '', @@ -33,7 +33,6 @@ class CustomerResource extends JsonResource 'city' => '', 'postcode' => '', 'opt_in' => '', - 'hashcode' => '', 'status' => '', 'last_connection' => '' ]; @@ -49,7 +48,6 @@ class CustomerResource extends JsonResource * Transform the resource into an array. * * @return array - * + crm_id int * + firstname string * + lastname string * + birth_date datetime @@ -66,10 +64,10 @@ class CustomerResource extends JsonResource //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass public function toArray($request = null) { - return array_intersect_key( + // patch country TODO remove this when all crm are migrated to v2 + $data = array_intersect_key( array_merge( [ - 'crm_id' => $this->customer->id, 'email' => $this->customer->email ], $this->login->toArray(), @@ -78,6 +76,9 @@ class CustomerResource extends JsonResource ), $this->getSkeleton() ); + + $data['country'] = $this->info->newcountry->value; + return $data; } public static function getSkeleton(): array diff --git a/app/Models/CustomerInfo.php b/app/Models/CustomerInfo.php index 3b3fdc3..9557943 100644 --- a/app/Models/CustomerInfo.php +++ b/app/Models/CustomerInfo.php @@ -14,7 +14,7 @@ class CustomerInfo extends Model use CastsEnums; protected $enumCasts = [ - 'country' => Country::class + 'newcountry' => Country::class ]; protected $fillable = [ @@ -24,6 +24,7 @@ class CustomerInfo extends Model 'address', 'address2', 'phone', + 'newcountry', 'country', 'city', 'postcode', @@ -34,6 +35,52 @@ class CustomerInfo extends Model { 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; + } }); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 01b8ee2..61744a4 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -58,24 +58,17 @@ class Service extends Model } /** - * Undocumented function + * Increment API CRM Stat * - * @param string $apiToken - * @return boolean + * @return void */ - public static function incrementStat(string $apiToken): bool + public function incrementStat(): void { - try { - $service = self::getFromApiToken($apiToken); - $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); - $serviceStat->nb_calls += 1; - $serviceStat->service()->associate($service); - $serviceStat->brand()->associate($service->brand); - $serviceStat->saveOrFail(); - return true; - } catch (ModelNotFoundException $e) { - return false; - } + $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); + $serviceStat->nb_calls += 1; + $serviceStat->service()->associate($this); + $serviceStat->brand()->associate($this->brand); + $serviceStat->saveOrFail(); } /** @@ -94,7 +87,7 @@ class Service extends Model * Get Service from bearerToken with its brand * * @param string $bearerToken - * @return \App\Models\Service|null + * @return \App\Models\Service * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ public static function getFromBearerToken(string $bearerToken) @@ -112,7 +105,6 @@ class Service extends Model */ public function authenticateApi(array $guards, string $apiToken) { - if ($this->api_token !== $apiToken) { throw new AuthenticationException('Unauthenticated.', $guards); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 54fc163..7c7e915 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -26,7 +26,7 @@ class AppServiceProvider extends ServiceProvider public function boot() { JsonResource::withoutWrapping(); - if (config('app.env') !== 'local') { + if (!in_array(config('app.env'), ['local', 'testing'])) { URL::forceScheme('https'); } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 5585e8d..b9f1b72 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -32,15 +32,16 @@ class RouteServiceProvider extends ServiceProvider $this->routes(function () { 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::bind('customer', function ($value) { - return Customer::where('id', $value) - ->orWhere('email', $value) - ->firstOrFail(); - }); - Route::model('brand', Brand::class); + Route::bind('customer', function ($value) { + return Customer::whereHas('infos', function ($q) use ($value) { + $q->where('customer_infos.hashcode', $value); + }) + ->orWhere('id', $value) + ->orWhere('email', $value)->firstOrFail() + ->firstOrFail(); }); } diff --git a/composer.json b/composer.json index 196472d..21a1584 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\migrations\\": "database/migrations/", - "Database\\Seeders\\": "database/seeders/" + "Database\\Seeders\\": "database/seeders/", + "LDC\\LDCCRM\\" : "clients/V2/src/" } }, "autoload-dev": { diff --git a/config/api.php b/config/api.php index 0055c7d..c0641e1 100644 --- a/config/api.php +++ b/config/api.php @@ -10,5 +10,11 @@ return [ /** * Api Token timeout in minutes */ - 'tokenTimeout' => 60 + 'tokenTimeout' => 60, + + /** + * The number of request + * per user or ip per minutes + */ + 'apiRequestRatePerMinute' => 256 ]; diff --git a/database/factories/CustomerInfoFactory.php b/database/factories/CustomerInfoFactory.php index 81d4845..9642e6c 100644 --- a/database/factories/CustomerInfoFactory.php +++ b/database/factories/CustomerInfoFactory.php @@ -25,7 +25,8 @@ class CustomerInfoFactory extends Factory { $this->faker->addProvider(new \Faker\Provider\fr_FR\Address($this->faker)); $this->faker->addProvider(new \Faker\Provider\fr_FR\PhoneNumber($this->faker)); - $countries = Country::toValues(); + $countries = Country::toArray(); + $country = $countries[\array_rand($countries)]; return [ 'firstname' => $this->faker->firstName, 'lastname' => $this->faker->lastName, @@ -33,7 +34,8 @@ class CustomerInfoFactory extends Factory 'address' => $this->faker->address, 'address2' => '', 'phone' => $this->faker->phoneNumber, - 'country' => $countries[\array_rand($countries)], + 'country' => $country, + 'newcountry' => $country, 'city' => $this->faker->city, 'postcode' => $this->faker->postcode, 'opt_in' => $this->faker->boolean, diff --git a/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php b/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php new file mode 100644 index 0000000..27e496e --- /dev/null +++ b/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php @@ -0,0 +1,32 @@ +smallInteger('newcountry')->after('country'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customer_infos', function (Blueprint $table) { + $table->dropColumn('newcountry'); + }); + } +} diff --git a/routes/api.php b/routes/api.php index 1714175..2030614 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,11 +47,12 @@ Route::prefix('v2')->name('apiV2.')->group(function () { Route::prefix('customer')->name('customer.')->group(function () { Route::post('/authenticate', [V2CustomerController::class, 'authenticate'])->name('authenticate'); Route::prefix('{customer}')->group(function () { - Route::prefix('arcadia')->name('arcadia.')->group(function () { - Route::post('/outgoing-mail', [V2ArcadiaController::class, 'outgoingMail'])->name('outgoingMail'); - }); Route::post('/activate', [V2CustomerController::class, 'activateAccount'])->name('activate'); }); }); + Route::prefix('arcadia')->name('arcadia.')->group(function () { + Route::post('/outgoing-mail/{customer}', [V2ArcadiaController::class, 'outgoingMail']) + ->name('outgoingMail'); + }); }); }); diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index 3e3ec26..5b1fdbb 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -57,10 +57,8 @@ class ApiBase extends BaseTestCase array $headers = [] ): TestResponse { $apiToken = $service->api_token; - if (!$apiToken) { - $apiToken = $service->generateApiToken(); - $service->saveOrFail(); - } + $apiToken = $service->generateApiToken(); + $service->saveOrFail(); $data['api_token'] = $apiToken; return $this->callApi($method, $uri, $data, $headers); } @@ -85,6 +83,9 @@ class ApiBase extends BaseTestCase $service->saveOrFail(); $service->bearer_pwd = $bearer_pwd; - return compact('customer', 'service', 'brand'); + // get customer info + $info = $customer->info($brand)->firstOrFail(); + + return compact('customer', 'service', 'brand', 'info'); } } diff --git a/tests/Feature/ApiV2/ApiTest.php b/tests/Feature/ApiV2/ApiTest.php index 0e79ad8..7044f88 100644 --- a/tests/Feature/ApiV2/ApiTest.php +++ b/tests/Feature/ApiV2/ApiTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\ApiV2; +use App\Enums\ApiError; use App\Enums\ApiStatus; use App\Models\Service; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -23,11 +24,16 @@ class ApiTest extends ApiBase ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $response = $this->json('get', route('apiV2.ping'), ['timestamp' => time()], [ 'X-API-TIMESTAMP' => time() + 121 ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $json->assertFragment(['data' => [ + 'code' => ApiError::wrongTimeStamp()->value, + 'message' => ApiError::wrongTimeStamp()->label + ]]); } /** diff --git a/tests/Feature/ApiV2/ArcadiaTest.php b/tests/Feature/ApiV2/ArcadiaTest.php index 4aa4afb..bb32644 100644 --- a/tests/Feature/ApiV2/ArcadiaTest.php +++ b/tests/Feature/ApiV2/ArcadiaTest.php @@ -33,7 +33,7 @@ class ArcadiaTest extends ApiBase 'package_unit_weight' => 200, ]; $response = $this->callApiWithToken($service, 'post', route( - 'apiV2.customer.arcadia.outgoingMail', + 'apiV2.arcadia.outgoingMail', compact('customer') ), $data); $json = $response->decodeResponseJson(); @@ -54,7 +54,7 @@ class ArcadiaTest extends ApiBase 'code_on_pack' => 'anystring' ]; $response = $this->callApiWithToken($service, 'post', route( - 'apiV2.customer.arcadia.outgoingMail', + 'apiV2.arcadia.outgoingMail', compact('customer') ), $data); $json = $response->decodeResponseJson(); diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index 16a1b80..c27f907 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -5,11 +5,11 @@ namespace Tests\Feature\ApiV2; use App\Enums\ApiError; use App\Enums\ApiStatus; use App\Enums\Country; -use App\Models\Customer; use App\Models\CustomerInfo; use App\Models\CustomerLogin; use Illuminate\Database\Eloquent\Collection; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Support\Str; use Tests\CreatesApplication; class CustomerTest extends ApiBase @@ -41,7 +41,7 @@ class CustomerTest extends ApiBase $response = $this->callApiWithToken( $service, 'post', - route('apiV2.customer.authenticate', compact('customer')), + route('apiV2.customer.authenticate', ['customer' => $info->hashcode]), [ 'email' => $customer->email, 'password' => 'testPassword' @@ -52,7 +52,6 @@ class CustomerTest extends ApiBase $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $this->assertNotEmpty($json->json('data')); - $this->assertNotEmpty($json->json('data.crm_id')); $this->assertNotEmpty($json->json('data.firstname')); $this->assertNotEmpty($json->json('data.lastname')); $this->assertNotEmpty($json->json('data.birth_date')); @@ -78,7 +77,7 @@ class CustomerTest extends ApiBase 'email' => 'warthogdj@hotmail.fr', 'password' => '123.Mot.De.P4sse', 'phone' => '+33654821562', - 'country' => (string)Country::france(), + 'country' => Country::france()->value, 'city' => 'Connerré', 'postcode' => '72160', 'opt_in' => true @@ -87,7 +86,6 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); - $this->assertNotEmpty($json->json('data.crm_id')); $this->assertNotEmpty($json->json('data.firstname')); $this->assertNotEmpty($json->json('data.lastname')); $this->assertNotEmpty($json->json('data.birth_date')); @@ -131,25 +129,18 @@ class CustomerTest extends ApiBase */ public function testActivateCustomer() { - /** @var \App\Models\Costumer $customer */ - $customer = Customer::whereHas('logins', function ($q) { - $q->where('status', false); - })->inRandomOrder()->firstOrFail(); - /** @var \App\Models\Brand $brand */ - $brand = $customer->logins()->where('status', false)->firstOrFail()->brand; - /** @var \App\Models\Service $service */ - $service = $brand->services()->firstOrFail(); + extract($this->getCustomer()); $response = $this->callApiWithToken($service, 'post', route( 'apiV2.customer.activate', - compact('customer') + ['customer' => $info->hashcode] ), []); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $response = $this->callApiWithToken($service, 'post', route( 'apiV2.customer.activate', - compact('customer') + ['customer' => $info->hashcode] ), []); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); @@ -171,7 +162,7 @@ class CustomerTest extends ApiBase 'email' => 'warthogdj2@hotmail.fr', 'password' => '123.Mot.De.P4sseNew', 'phone' => '+33654821563', - 'country' => (string)Country::bahamas(), + 'country' => Country::bahamas()->value, 'city' => 'Connerré2', 'postcode' => '72161', 'opt_in' => 0 @@ -180,7 +171,7 @@ class CustomerTest extends ApiBase foreach ($data as $rowName => $row) { $response = $this->callApiWithToken($service, 'patch', route( 'apiV2.customer.update', - compact('customer') + ['customer' => $info->hashcode] ), [$rowName => $row]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); @@ -200,7 +191,7 @@ class CustomerTest extends ApiBase ); } } - $response = $this->callApiWithToken($service, 'patch', route('apiV2.customer.update', compact('customer')), [ + $response = $this->callApiWithToken($service, 'patch', route('apiV2.customer.update', ['customer' => $info->hashcode]), [ 'address2' => 'anything' ]); $json = $response->decodeResponseJson(); @@ -219,11 +210,10 @@ class CustomerTest extends ApiBase { extract($this->getCustomer()); - $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', compact('customer'))); + $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', ['customer' => $info->hashcode])); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); - $this->assertNotEmpty($json->json('data.crm_id')); $this->assertNotEmpty($json->json('data.firstname')); $this->assertNotEmpty($json->json('data.lastname')); $this->assertNotEmpty($json->json('data.birth_date')); @@ -235,9 +225,8 @@ class CustomerTest extends ApiBase $this->assertNotEmpty($json->json('data.hashcode')); $csClassName = \array_slice(explode('\\', get_class($customer)), -1)[0]; - $customer = 9999999; - $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', compact('customer'))); + $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', ['customer' => Str::uuid()])); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); @@ -259,7 +248,7 @@ class CustomerTest extends ApiBase $service = $brand->services()->firstOrFail(); $response = $this->callApiWithToken($service, 'delete', route( 'apiV2.customer.destroy', - compact('customer') + ['customer' => $info->hashcode] )); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php new file mode 100644 index 0000000..e4a1b62 --- /dev/null +++ b/tests/Unit/CrmClientTester.php @@ -0,0 +1,76 @@ + self::$service->bearer_token]); + config(['crm.password' => self::$service->bearer_pwd]); + config(['crm.endpoint' => app()->make('url')->to('/')]); + } + } + + /** + * @return array + */ + protected static function getCustomer() + { + /** @var \App\Models\Costumer $customer */ + $customer = Customer::whereHas('logins', function ($q) { + $q->where('status', true); + })->inRandomOrder()->firstOrFail(); + /** @var \App\Models\Brand $brand */ + $brand = $customer->logins()->where('status', true)->firstOrFail()->brand; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + + // regen bearer token to get the pwd in clear + $service->generateBearerToken(); + $bearer_pwd = $service->bearer_pwd; + $service->saveOrFail(); + $service->bearer_pwd = $bearer_pwd; + + return compact('customer', 'service', 'brand'); + } + + /** + * Parse routes.json and return routes list + * + * @return array + */ + protected function getClientRoutes(): array + { + return \json_decode(\file_get_contents(__DIR__ . '/../../clients/V2/src/routes.json'), true); + } +} diff --git a/tests/Unit/CrmClientV2ClientTest.php b/tests/Unit/CrmClientV2ClientTest.php new file mode 100644 index 0000000..f71dc4c --- /dev/null +++ b/tests/Unit/CrmClientV2ClientTest.php @@ -0,0 +1,39 @@ +getClientRoutes(); + $interface = new ApiInterface(); + + $request = new Request( + HttpMethods::make(\strtolower($routes['apiV2.ping']['methods'][0])), + $routes['apiV2.ping']['uri'] + ); + + $response = $interface->call($request); + + $this->assertEquals(ApiStatus::OK(), $response->status); + } + + public function testClientPing() + { + $api = new CrmApiV2(); + $this->assertTrue($api->ping()); + } + + public function testApiAuthenticate() + { + $api = new CrmApiV2(); + $this->assertTrue($api->authenticate()); + } +} diff --git a/tests/Unit/CrmClientV2ConfigTest.php b/tests/Unit/CrmClientV2ConfigTest.php new file mode 100644 index 0000000..a6b867c --- /dev/null +++ b/tests/Unit/CrmClientV2ConfigTest.php @@ -0,0 +1,13 @@ +assertEquals(null, Utils::getConfig('totot')); + } +} diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php new file mode 100644 index 0000000..7336e64 --- /dev/null +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -0,0 +1,97 @@ +expectNotToPerformAssertions(); + $client = new Customer(); + $client->email; + } + + public function testSetter() + { + $this->expectNotToPerformAssertions(); + $client = new Customer(); + + $client->firstname = 'toto'; + $client->firstname; + } + + public function testSetterFailWrongType() + { + $this->expectException(TypeError::class); + $client = new Customer(); + + $client->optin = 'toto'; + } + + public function testSetterFailWrongTypeCountry() + { + $this->expectException(TypeError::class); + $client = new Customer(); + + $client->country = 'toto'; + } + + public function testPropsAllSetOk() + { + $this->expectNotToPerformAssertions(); + $client = new Customer(); + + $propTypes = [ + 'hashcode' => (string)Str::uuid(), + 'email' => 'toto@gmail.com', + 'firstname' => 'string', + 'lastname' => 'string', + 'password' => 'StrongPassword!7', + 'birthdate' => '1992-02-03', + 'phone' => '0673891630', + 'address' => 'string', + 'address2' => 'string', + 'city' => 'string', + 'postcode' => 'string', + 'country' => Country::afghanistan(), + 'optin' => true, + 'status' => true, + ]; + + foreach ($propTypes as $prop => $type) { + $client->{$prop} = $type; + } + } + + public function testPropsAllSetNotOk() + { + $this->expectException(TypeError::class); + $client = new Customer(); + + $propTypes = [ + 'hashcode' => (string)Str::uuid(), + 'email' => 'toto@gmail.com', + 'firstname' => 'string', + 'lastname' => 'string', + 'password' => 'StrongPassword!7', + 'birthdate' => '1992-02-03', + 'phone' => '0673891630', + 'address' => 'string', + 'address2' => 'string', + 'city' => 'string', + 'postcode' => 'string', + 'country' => Country::afghanistan()->value, + 'optin' => true, + 'status' => true, + ]; + + foreach ($propTypes as $prop => $type) { + $client->{$prop} = $type; + } + } +} diff --git a/tests/Unit/CrmClientV2RouterTest.php b/tests/Unit/CrmClientV2RouterTest.php new file mode 100644 index 0000000..7059f01 --- /dev/null +++ b/tests/Unit/CrmClientV2RouterTest.php @@ -0,0 +1,45 @@ +getClientRoutes(); + + /** @var \Illuminate\Routing\Route */ + foreach (Route::getRoutes() as $route) { + if (\strpos($route->getPrefix(), 'v2') !== false) { + $this->assertArrayHasKey($route->getName(), $routes); + $this->assertArrayHasKey('uri', $routes[$route->getName()]); + $this->assertArrayHasKey('methods', $routes[$route->getName()]); + $this->assertEquals($route->methods(), $routes[$route->getName()]['methods']); + $this->assertEquals($route->uri(), $routes[$route->getName()]['uri']); + } else { + $this->assertArrayNotHasKey($route->getName(), $routes); + } + } + } + + public function testGenRequest() + { + $this->expectNotToPerformAssertions(); + $routes = $this->getClientRoutes(); + + foreach ($routes as $name => $route) { + $data = []; + $query = []; + if (\strpos($route['uri'], '{customer}') !== false) { + $query['customer'] = 1; + } + $request = Utils::getResquest($name, $query, $data); + $request->uri; + $request->method; + $request->data; + } + } +} From 89c5f11dc44ba88d89cfb483d0ec478215e24399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 13:00:23 +0200 Subject: [PATCH 013/103] fix customer bind route fix customer test --- app/Providers/RouteServiceProvider.php | 8 ++--- phpunit.xml | 10 +++--- tests/Feature/ApiV2/ApiBase.php | 15 ++++++--- tests/Feature/ApiV2/CustomerTest.php | 46 ++++++++++++++++++-------- 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index b9f1b72..67d2595 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -38,10 +38,8 @@ class RouteServiceProvider extends ServiceProvider Route::bind('customer', function ($value) { return Customer::whereHas('infos', function ($q) use ($value) { $q->where('customer_infos.hashcode', $value); - }) - ->orWhere('id', $value) - ->orWhere('email', $value)->firstOrFail() - ->firstOrFail(); + })->first() ?? (\ctype_digit($value) ? Customer::where('id', '=', $value)->first() : null) ?? + Customer::where('email', '=', $value)->firstOrFail(); }); } @@ -53,7 +51,7 @@ 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()); }); } } diff --git a/phpunit.xml b/phpunit.xml index 5a77d2a..b754467 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,8 +1,8 @@ @@ -21,8 +21,8 @@ - - + + diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index 5b1fdbb..493d74b 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -64,16 +64,21 @@ class ApiBase extends BaseTestCase } /** + * Get Customer models with given status (active or not) + * + * @param boolean $status the customer status for a brand * @return array */ - protected function getCustomer() + protected function getCustomer(bool $status = true): array { /** @var \App\Models\Costumer $customer */ - $customer = Customer::whereHas('logins', function ($q) { - $q->where('status', true); + $customer = Customer::whereHas('logins', function ($q) use ($status) { + $q->where('status', $status); })->inRandomOrder()->firstOrFail(); + /** @var \App\Models\Login $login */ + $login = $customer->logins()->where('status', $status)->firstOrFail(); /** @var \App\Models\Brand $brand */ - $brand = $customer->logins()->where('status', true)->firstOrFail()->brand; + $brand = $login->brand; /** @var \App\Models\Service $service */ $service = $brand->services()->firstOrFail(); @@ -86,6 +91,6 @@ class ApiBase extends BaseTestCase // get customer info $info = $customer->info($brand)->firstOrFail(); - return compact('customer', 'service', 'brand', 'info'); + return compact('customer', 'service', 'login', 'brand', 'info'); } } diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index c27f907..ed36117 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature\ApiV2; use App\Enums\ApiError; use App\Enums\ApiStatus; use App\Enums\Country; +use App\Models\Customer; use App\Models\CustomerInfo; use App\Models\CustomerLogin; use Illuminate\Database\Eloquent\Collection; @@ -129,7 +130,7 @@ class CustomerTest extends ApiBase */ public function testActivateCustomer() { - extract($this->getCustomer()); + extract($this->getCustomer(false)); $response = $this->callApiWithToken($service, 'post', route( 'apiV2.customer.activate', @@ -210,7 +211,10 @@ class CustomerTest extends ApiBase { extract($this->getCustomer()); - $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', ['customer' => $info->hashcode])); + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.customer.show', + ['customer' => $info->hashcode] + )); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); @@ -226,9 +230,19 @@ class CustomerTest extends ApiBase $csClassName = \array_slice(explode('\\', get_class($customer)), -1)[0]; - $response = $this->callApiWithToken($service, 'get', route('apiV2.customer.show', ['customer' => Str::uuid()])); + $uuid = Str::uuid(); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.customer.show', + ['customer' => $uuid] + )); $json = $response->decodeResponseJson(); + /** @var \App\Models\Customer */ + $customer = Customer::whereHas('infos', function ($q) use ($uuid) { + $q->where('hashcode', $uuid); + })->first(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); $this->assertEquals(ApiError::modelNotFound()->value, $json->json('data.code')); $this->assertEquals( @@ -243,16 +257,20 @@ class CustomerTest extends ApiBase public function testApiDestroyCustomer() { extract($this->getCustomer()); - $brands = $customer->brands()->get(); - foreach ($brands as $brand) { - $service = $brand->services()->firstOrFail(); - $response = $this->callApiWithToken($service, 'delete', route( - 'apiV2.customer.destroy', - ['customer' => $info->hashcode] - )); - $json = $response->decodeResponseJson(); - $this->assertApiResponseStatusIs($json, ApiStatus::OK()); - $this->assertEmpty($json->json('data')); - } + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken($service, 'delete', route( + 'apiV2.customer.destroy', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertEmpty($json->json('data')); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.customer.show', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); } } From a30db50f38a8442befe58d18a77764dd1cb36569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 13:13:19 +0200 Subject: [PATCH 014/103] Complete composer.json info --- composer.json | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 21a1584..b7346f0 100644 --- a/composer.json +++ b/composer.json @@ -1,10 +1,23 @@ { - "name": "laravel/laravel", + "name": "ldc/ldccrmv2client", "type": "project", - "description": "The Laravel Framework.", - "keywords": [ - "framework", - "laravel" + "version": "2.0.0", + "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": { From eb08131df005a0f20c38a3c8ee433f24cdc3cfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 13:13:30 +0200 Subject: [PATCH 015/103] fix default alert mail --- app/Mail/ExceptionAlert.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Mail/ExceptionAlert.php b/app/Mail/ExceptionAlert.php index 5753f2d..48ca654 100644 --- a/app/Mail/ExceptionAlert.php +++ b/app/Mail/ExceptionAlert.php @@ -42,7 +42,7 @@ class ExceptionAlert extends Mailable (request() and request()->fullUrl()) ? request()->fullUrl() : 'none')) ->to( - config('mail.contactsList.serviceweb'), + config('mail.contactsList.serviceweb', 'informatique.internet@ldc.fr'), sprintf('%s Exception Handler', config('app.name')) ); } From 022507a72c4135c794fe5263e625be79eae51fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 13:13:51 +0200 Subject: [PATCH 016/103] api config get composer.json version number --- app/Lib/Helper.php | 21 +++++++++++++++++++++ config/api.php | 4 +++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/Lib/Helper.php b/app/Lib/Helper.php index 327f9f4..889a26e 100644 --- a/app/Lib/Helper.php +++ b/app/Lib/Helper.php @@ -10,6 +10,14 @@ use Illuminate\Support\Facades\Route; */ class Helper { + + /** + * Package infos + * + * @var array + */ + private static $packageInfos; + /** * Tells if the request is within apiV2 routes * @@ -71,4 +79,17 @@ class Helper { 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; + } } diff --git a/config/api.php b/config/api.php index c0641e1..1f6a224 100644 --- a/config/api.php +++ b/config/api.php @@ -1,11 +1,13 @@ '2.0.0', + 'version' => Helper::getPackageInfo()['version'], /** * Api Token timeout in minutes From 856e708e466f55f02fb7df0dbe08ba4927a37d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 13:17:00 +0200 Subject: [PATCH 017/103] Client generate add api config json --- app/Console/Commands/GenerateCrmClient.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index c22a060..9d72381 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -43,6 +43,20 @@ class GenerateCrmClient extends Command $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() From 00639792c4c529fc794374734c987b021a823257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 15:07:25 +0200 Subject: [PATCH 018/103] - Fix Handler reponse coherence with api response - Add ApiErrorException to the client --- app/Console/Commands/GenerateCrmClient.php | 16 +++- app/Exceptions/Api/V2/ApiErrorException.php | 41 +++++++- app/Exceptions/Handler.php | 101 ++++++++++++++------ app/Http/Middleware/ApiCheckTimeStamps.php | 6 +- 4 files changed, 121 insertions(+), 43 deletions(-) diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index 9d72381..4e01f29 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -7,6 +7,7 @@ use App\Enums\ApiStatus; use App\Enums\ArcadiaMailType; use App\Enums\ArcadiaOrderType; use App\Enums\Country; +use App\Exceptions\Api\V2\ApiErrorException; use App\Exceptions\Api\V2\CrmException; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; @@ -85,9 +86,16 @@ class GenerateCrmClient extends Command private function copyExceptions(): void { $this->info('Copy Exceptions'); + $rootTarget = app_path('../' . self::TARGET); $this->copyToFolder([ - CrmException::class + CrmException::class, + ApiErrorException::class ], 'Exceptions'); + $this->replaceInPhpFile( + "$rootTarget/Exceptions/ApiErrorException.php", + 'App\Enums\ApiError', + 'LDC\LDCCRM\Enums\ApiError' + ); } private function copyEnums(): void @@ -134,14 +142,12 @@ class GenerateCrmClient extends Command File::delete($dest); } File::copy($source, $dest); - $this->replaceNamespaceinPhpFile($dest); + $this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE); } } - private function replaceNamespaceinPhpFile(string $source): void + private function replaceInPhpFile(string $source, string $search, string $replace): void { - $search = 'namespace App'; - $replace = 'namespace ' . self::CLIENTNAMESPACE; $fContent = File::get($source); // remove trailing \Api\V2 diff --git a/app/Exceptions/Api/V2/ApiErrorException.php b/app/Exceptions/Api/V2/ApiErrorException.php index 6b10654..37c3914 100644 --- a/app/Exceptions/Api/V2/ApiErrorException.php +++ b/app/Exceptions/Api/V2/ApiErrorException.php @@ -7,20 +7,51 @@ 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 + */ class ApiErrorException extends Exception { - protected $statusCode; + /** + * Api Error + * + * @var \App\Enums\ApiError + */ + protected $error; - public function __construct(ApiError $error, int $statusCode = 400, Throwable $previous = null) + protected $httpCode; + + public function __construct(ApiError $error, int $httpCode = 400, Throwable $previous = null) { - $this->statusCode = $statusCode; + $this->httpCode = $httpCode; parent::__construct($error->label, $error->value, $previous); } - public function getStatusCode() + /** + * Get the Api Error + * + * @return ApiError + */ + public function getApiError(): ApiError { - return $this->statusCode; + return $this->error; + } + + /** + * Get the http code + * + * @return int + */ + public function getStatusCode(): int + { + return $this->httpCode; } public function report() diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 0f2d67d..3da0a8d 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,14 +3,18 @@ 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\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; @@ -88,52 +92,89 @@ class Handler extends ExceptionHandler switch (get_class($e)) { case AuthenticationException::class: /** @var AuthenticationException $e */ - return response()->json([ - 'message' => sprintf('%s, %s', ApiError::needAuth()->label, $e->getMessage()), - 'code' => ApiError::needAuth()->value, - ], 407); + return $this->getReponse( + $request, + 407, + ApiError::needAuth()->value, + sprintf('%s, %s', ApiError::needAuth()->label, $e->getMessage()) + ); case MethodNotAllowedHttpException::class: /** @var MethodNotAllowedHttpException $e */ - return response()->json([ - 'message' => sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage()), - 'code' => ApiError::methodNotAllowed()->value, - ], $e->getStatusCode()); + return $this->getReponse( + $request, + $e->getStatusCode(), + ApiError::methodNotAllowed()->value, + sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage()) + ); case ValidationException::class: /** @var ValidationException $e */ - return response()->json([ - 'message' => sprintf('%s, %s', ApiError::validationFailed()->label, $e->getMessage()), - 'code' => ApiError::validationFailed()->value, - 'errors' => $e->errors() - ], $e->status); + return $this->getReponse( + $request, + $e->status, + ApiError::validationFailed()->value, + sprintf('%s, %s', ApiError::validationFailed()->label, $e->getMessage()), + ['errors' => $e->errors()] + ); case ModelNotFoundException::class: /** @var ModelNotFoundException $e */ - return response()->json([ - 'message' => sprintf( + return $this->getReponse( + $request, + 404, + ApiError::modelNotFound()->value, + sprintf( ApiError::modelNotFound()->label, \array_slice(explode('\\', $e->getModel()), -1)[0] - ), - 'code' => ApiError::modelNotFound()->value - ], 404); + ) + ); case ApiErrorException::class: /** @var ApiErrorException $e */ - return response()->json([ - 'message' => $e->getMessage(), - 'code' => $e->getCode() - ], $e->getStatusCode()); + return $this->getReponse( + $request, + $e->getStatusCode(), + $e->getCode(), + $e->getMessage() + ); case ThrottleRequestsException::class: /** @var ThrottleRequestsException $e */ - return response()->json([ - 'message' => ApiError::throttleRequestsException()->label, - 'code' => ApiError::throttleRequestsException()->value - ], 429); + return $this->getReponse( + $request, + 429, + ApiError::throttleRequestsException()->value, + ApiError::throttleRequestsException()->label + ); case CrmException::class: default: - return response()->json([ - 'message' => $e->getMessage(), - 'code' => $e->getCode() - ], 500); + 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); + } } diff --git a/app/Http/Middleware/ApiCheckTimeStamps.php b/app/Http/Middleware/ApiCheckTimeStamps.php index ddca7ef..09d61fc 100644 --- a/app/Http/Middleware/ApiCheckTimeStamps.php +++ b/app/Http/Middleware/ApiCheckTimeStamps.php @@ -26,15 +26,15 @@ class ApiCheckTimeStamps { if (Helper::isRequestForApiV2($request)) { if (!$request->hasHeader('X-API-TIMESTAMP') or !is_numeric($request->header('X-API-TIMESTAMP'))) { - throw new ApiErrorException(ApiError::wrongTimeStamp(), 400); + throw new ApiErrorException(ApiError::wrongTimeStamp()); } if (!$request->timestamp or !is_numeric($request->timestamp)) { - throw new ApiErrorException(ApiError::wrongTimeStamp(), 400); + 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(), 400); + throw new ApiErrorException(ApiError::wrongTimeStamp()); } } return $next($request); From 9118e43452f404fcee07dbe9c0b23eb3c0352977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 15:31:53 +0200 Subject: [PATCH 019/103] Fix client generate replace of occurence in php files --- app/Console/Commands/GenerateCrmClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index 4e01f29..35e1618 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -158,7 +158,7 @@ class GenerateCrmClient extends Command // replace namespace top level $pos = strpos($fContent, $search); if ($pos !== false) { - File::put($source, substr_replace($fContent, $replace, $pos, strlen($search))); + File::put($source, \str_replace($search, $replace, $fContent)); } } } From 6b56929222acaa35dca5eda5a0830ee2bd1905df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 17:15:06 +0200 Subject: [PATCH 020/103] Api error and Api response fix to prevent imbricate ApiV2 Responses --- app/Exceptions/Api/V2/ApiErrorException.php | 14 ++++++++++++-- app/Http/Middleware/ApiResponse.php | 17 ++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/Exceptions/Api/V2/ApiErrorException.php b/app/Exceptions/Api/V2/ApiErrorException.php index 37c3914..24c0179 100644 --- a/app/Exceptions/Api/V2/ApiErrorException.php +++ b/app/Exceptions/Api/V2/ApiErrorException.php @@ -26,12 +26,22 @@ class ApiErrorException extends Exception */ protected $error; + /** @var string */ + protected $message; + + /** @var int */ protected $httpCode; - public function __construct(ApiError $error, int $httpCode = 400, Throwable $previous = null) + public function __construct(ApiError $error, int $httpCode = 400, string $message = '', Throwable $previous = null) { + $this->error = $error; + $this->message = $message; $this->httpCode = $httpCode; - parent::__construct($error->label, $error->value, $previous); + parent::__construct( + strlen($this->message) ? $this->message : $error->label, + $error->value, + $previous + ); } /** diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index 1a9eb8e..b127bba 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -23,6 +23,7 @@ class ApiResponse */ public function handle(Request $request, Closure $next) { + /** @var \Illuminate\Http\JsonResponse $response */ $response = $next($request); $isApiV1Request = Helper::isRequestForApiV1($request); @@ -52,10 +53,20 @@ class ApiResponse Log::error(sprintf('Could not increment stats %s', $e->getMessage())); } + $data = $response->getData(); + + // Is reponse is already a V2ApiResponse then just return it + if ( + isset($data->status) and + isset($data->data) and + isset($data->message) 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(); - $response->setData(new V2ApiResponse((object)$response->getData(), $status)); - - return $response; + return $response->setData(new V2ApiResponse((object)$response->getData(), $status)); } } From a1dc1eefe517962410f9cf1585169b5ca85b0239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 17:15:38 +0200 Subject: [PATCH 021/103] Fix client v2 ping test --- tests/Unit/CrmClientV2ClientTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/Unit/CrmClientV2ClientTest.php b/tests/Unit/CrmClientV2ClientTest.php index f71dc4c..67406ff 100644 --- a/tests/Unit/CrmClientV2ClientTest.php +++ b/tests/Unit/CrmClientV2ClientTest.php @@ -7,6 +7,7 @@ use LDC\LDCCRM\Enums\ApiStatus; use LDC\LDCCRM\Enums\HttpMethods; use LDC\LDCCRM\Transport\ApiInterface; use LDC\LDCCRM\Transport\Request; +use ReflectionClass; class CrmClientV2ClientTest extends CrmClientTester { @@ -33,7 +34,9 @@ class CrmClientV2ClientTest extends CrmClientTester public function testApiAuthenticate() { - $api = new CrmApiV2(); - $this->assertTrue($api->authenticate()); + $refClass = new ReflectionClass(ApiInterface::class); + $refMeth = $refClass->getMethod('authenticate'); + $refMeth->setAccessible(true); + $this->assertTrue($refMeth->invoke(new ApiInterface())); } } From 4af12017c7c2e3e98ae23103578b93d52a9a42b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 17:16:05 +0200 Subject: [PATCH 022/103] Customer client get test --- tests/Unit/CrmClientTester.php | 17 +++++++++++++++-- tests/Unit/CrmClientV2CustomerTest.php | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index e4a1b62..7e1c648 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -19,6 +19,12 @@ abstract class CrmClientTester extends TestCase /** @var App\Models\Service */ protected static $service; + /** @var App\Models\Info */ + protected static $info; + + /** @var App\Models\Login */ + protected static $login; + public static function setUpBeforeClass(): void { fwrite(STDERR, "\n \e[0;30;43m WARNING \e[0m This test cannot be done on another envirronement than local or testing as it modifies the first Service credentials\n"); @@ -35,6 +41,8 @@ abstract class CrmClientTester extends TestCase self::$customer = $customer; self::$brand = $brand; self::$service = $service; + self::$login = $login; + self::$info = $info; config(['crm.token' => self::$service->bearer_token]); config(['crm.password' => self::$service->bearer_pwd]); config(['crm.endpoint' => app()->make('url')->to('/')]); @@ -51,17 +59,22 @@ abstract class CrmClientTester extends TestCase $q->where('status', true); })->inRandomOrder()->firstOrFail(); /** @var \App\Models\Brand $brand */ - $brand = $customer->logins()->where('status', true)->firstOrFail()->brand; + $login = $customer->logins()->where('status', true)->firstOrFail(); + /** @var \App\Models\Login $login */ + $brand = $login->brand; /** @var \App\Models\Service $service */ $service = $brand->services()->firstOrFail(); + // get customer info + $info = $customer->info($brand)->firstOrFail(); + // regen bearer token to get the pwd in clear $service->generateBearerToken(); $bearer_pwd = $service->bearer_pwd; $service->saveOrFail(); $service->bearer_pwd = $bearer_pwd; - return compact('customer', 'service', 'brand'); + return compact('customer', 'service', 'brand', 'login', 'info'); } /** diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 7336e64..9ec04d0 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -3,6 +3,7 @@ namespace Tests\Unit; use Illuminate\Support\Str; +use LDC\LDCCRM\CrmApiV2; use LDC\LDCCRM\Enums\Country; use LDC\LDCCRM\Models\Customer; use TypeError; @@ -94,4 +95,24 @@ class CrmClientV2CustomerTest extends CrmClientTester $client->{$prop} = $type; } } + + public function testCustomerGet() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + + $this->assertEquals(Customer::class, get_class($customer)); + $this->assertEquals($customer->firstname, static::$info->firstname); + $this->assertEquals($customer->lastname, static::$info->lastname); + $this->assertEquals($customer->birth_date, static::$info->birth_date); + $this->assertEquals($customer->address, static::$info->address); + $this->assertEquals($customer->address2, static::$info->address2); + $this->assertEquals($customer->phone, static::$info->phone); + $this->assertEquals($customer->city, static::$info->city); + $this->assertEquals($customer->postcode, static::$info->postcode); + $this->assertEquals($customer->opt_in, static::$info->opt_in); + $this->assertEquals($customer->hashcode, static::$info->hashcode); + $this->assertEquals($customer->country->label, static::$info->country); + $this->assertEquals($customer->country->value, static::$info->newcountry->value); + } } From 64e905d34c40b45724b9d0ba2976b6c90cec14d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 17:17:13 +0200 Subject: [PATCH 023/103] fix testSetterFailWrongType --- tests/Unit/CrmClientV2CustomerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 9ec04d0..7781855 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -31,7 +31,7 @@ class CrmClientV2CustomerTest extends CrmClientTester $this->expectException(TypeError::class); $client = new Customer(); - $client->optin = 'toto'; + $client->opt_in = 'toto'; } public function testSetterFailWrongTypeCountry() From 484f7b6f1b74b7a12b35b30ada823553e01ccf8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 4 Aug 2021 17:23:57 +0200 Subject: [PATCH 024/103] Fix api response middleware --- app/Http/Middleware/ApiResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index b127bba..f74005f 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -59,7 +59,7 @@ class ApiResponse if ( isset($data->status) and isset($data->data) and - isset($data->message) and + isset($data->version) and in_array($data->status, [ApiStatus::OK()->value, ApiStatus::KO()->value]) ) { return $response; From 6131b1a171d5868d76952550cb7535007d999a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 5 Aug 2021 15:00:28 +0200 Subject: [PATCH 025/103] - working testing env - use php artisan serve to run test + autokill - autodebug if envirronement is testing --- .env.testing.example | 56 ++++++ app/Exceptions/Handler.php | 4 + app/Http/Controllers/LoginController.php | 4 + composer.json | 186 ++++++++++-------- composer.lock | 16 +- config/database.php | 2 +- database/factories/CustomerInfoFactory.php | 2 +- database/factories/CustomerLoginFactory.php | 12 +- ...250_re_add_api_token_to_services_table.php | 3 + phpunit.xml | 3 +- test.sh | 2 + tests/Unit/CrmClientTester.php | 20 +- tests/Unit/CrmClientV2CustomerTest.php | 22 +++ 13 files changed, 221 insertions(+), 111 deletions(-) create mode 100644 .env.testing.example create mode 100644 test.sh diff --git a/.env.testing.example b/.env.testing.example new file mode 100644 index 0000000..4b39a14 --- /dev/null +++ b/.env.testing.example @@ -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}" diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 3da0a8d..b13c0e9 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -144,6 +144,10 @@ class Handler extends ExceptionHandler ); 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()); } } diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index f7033f7..5031dbd 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers; +use App\Models\Customer; +use App\Models\Service; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -9,6 +11,8 @@ class LoginController extends Controller { public function index() { + dump(\config('app.env')); + dd(Service::pluck('bearer_token')->toArray()); return view('login'); } diff --git a/composer.json b/composer.json index b7346f0..d5b572b 100644 --- a/composer.json +++ b/composer.json @@ -1,88 +1,102 @@ { - "name": "ldc/ldccrmv2client", - "type": "project", - "version": "2.0.0", - "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.12", - "laravel/tinker": "^2.5", - "schuppo/password-strength": "~2.0" - }, - "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", - "squizlabs/php_codesniffer": "^3.5" - }, - "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/" - } - }, - "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" - ], - "post-root-package-install": [ - "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" - ], - "post-create-project-cmd": [ - "@php artisan key:generate --ansi" - ] - } + "name": "ldc/ldccrmv2client", + "type": "project", + "version": "2.0.0", + "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.12", + "laravel/tinker": "^2.5", + "schuppo/password-strength": "~2.0" + }, + "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", + "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/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "test": "./test.sh", + "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" + ] + } } diff --git a/composer.lock b/composer.lock index 83e2f7e..9daf9de 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7b72242184eebf977895b2352f204a39", + "content-hash": "2ebadc06386d0d4db3eb629fe92d91fc", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -4698,21 +4698,21 @@ }, { "name": "symfony/process", - "version": "v5.3.2", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df" + "reference": "d16634ee55b895bd85ec714dadc58e4428ecf030" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/714b47f9196de61a196d86c4bad5f09201b307df", - "reference": "714b47f9196de61a196d86c4bad5f09201b307df", + "url": "https://api.github.com/repos/symfony/process/zipball/d16634ee55b895bd85ec714dadc58e4428ecf030", + "reference": "d16634ee55b895bd85ec714dadc58e4428ecf030", "shasum": "" }, "require": { "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -4740,7 +4740,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.3.2" + "source": "https://github.com/symfony/process/tree/v5.3.4" }, "funding": [ { @@ -4756,7 +4756,7 @@ "type": "tidelift" } ], - "time": "2021-06-12T10:15:01+00:00" + "time": "2021-07-23T15:54:19+00:00" }, { "name": "symfony/routing", diff --git a/config/database.php b/config/database.php index ffac77d..e811670 100644 --- a/config/database.php +++ b/config/database.php @@ -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), ], diff --git a/database/factories/CustomerInfoFactory.php b/database/factories/CustomerInfoFactory.php index 9642e6c..e1b7525 100644 --- a/database/factories/CustomerInfoFactory.php +++ b/database/factories/CustomerInfoFactory.php @@ -26,7 +26,7 @@ class CustomerInfoFactory extends Factory $this->faker->addProvider(new \Faker\Provider\fr_FR\Address($this->faker)); $this->faker->addProvider(new \Faker\Provider\fr_FR\PhoneNumber($this->faker)); $countries = Country::toArray(); - $country = $countries[\array_rand($countries)]; + $country = $countries[\array_rand($countries)]; return [ 'firstname' => $this->faker->firstName, 'lastname' => $this->faker->lastName, diff --git a/database/factories/CustomerLoginFactory.php b/database/factories/CustomerLoginFactory.php index e7749b4..ceffc7f 100644 --- a/database/factories/CustomerLoginFactory.php +++ b/database/factories/CustomerLoginFactory.php @@ -9,6 +9,8 @@ use Illuminate\Support\Facades\Hash; class CustomerLoginFactory extends Factory { + protected static $first = false; + /** * The name of the factory's corresponding model. * @@ -23,10 +25,16 @@ class CustomerLoginFactory extends Factory */ public function definition() { + $bool = $this->faker->boolean; + // Force first to be active + if (!self::$first) { + self::$first = true; + $bool = true; + } return [ 'password' => 'testPassword', - 'last_connection' => $this->faker->date, - 'status' => $this->faker->boolean + 'last_connection' => $this->faker->date(), + 'status' => $bool ]; } } diff --git a/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php b/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php index 0c8f373..e603114 100644 --- a/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php +++ b/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php @@ -2,6 +2,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; // phpcs:ignore PSR1.Classes.ClassDeclaration.MissingNamespace @@ -29,6 +30,8 @@ class ReAddApiTokenToServicesTable extends Migration { Schema::table('services', function (Blueprint $table) { $table->dropColumn('api_token'); + }); + Schema::table('services', function (Blueprint $table) { $table->dropColumn('api_token_date'); }); } diff --git a/phpunit.xml b/phpunit.xml index b754467..5f66c8e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -19,10 +19,9 @@ + - - diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..12a786f --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +#!/bin/sh +composer runtestmigration > /dev/null; composer runserve; composer runtest; composer killserve; diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index 7e1c648..b0a3bdd 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -3,12 +3,10 @@ namespace Tests\Unit; use App\Models\Customer; -use Illuminate\Foundation\Testing\TestCase; -use Tests\CreatesApplication; +use Tests\TestCase; abstract class CrmClientTester extends TestCase { - use CreatesApplication; /** @var App\Models\Customer */ protected static $customer; @@ -33,8 +31,8 @@ abstract class CrmClientTester extends TestCase public function setUp(): void { parent::setUp(); - if (!in_array(config('app.env'), ['local', 'testing'])) { - throw new \Exception(); + if (!in_array(config('app.env'), ['testing'])) { + throw new \Exception('Env has to be testing'); } if (!self::$customer) { extract(self::getCustomer()); @@ -45,22 +43,22 @@ abstract class CrmClientTester extends TestCase self::$info = $info; config(['crm.token' => self::$service->bearer_token]); config(['crm.password' => self::$service->bearer_pwd]); - config(['crm.endpoint' => app()->make('url')->to('/')]); + config(['crm.endpoint' => 'http://127.0.0.25:8888']); } } /** * @return array */ - protected static function getCustomer() + protected static function getCustomer(bool $status = true) { /** @var \App\Models\Costumer $customer */ - $customer = Customer::whereHas('logins', function ($q) { - $q->where('status', true); + $customer = Customer::whereHas('logins', function ($q) use ($status) { + $q->where('status', $status); })->inRandomOrder()->firstOrFail(); /** @var \App\Models\Brand $brand */ - $login = $customer->logins()->where('status', true)->firstOrFail(); - /** @var \App\Models\Login $login */ + $login = $customer->logins()->where('status', $status)->firstOrFail(); + /** @var \App\Models\CustomerLogin $login */ $brand = $login->brand; /** @var \App\Models\Service $service */ $service = $brand->services()->firstOrFail(); diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 7781855..67ca725 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit; +use App\Models\Customer as ModelsCustomer; use Illuminate\Support\Str; use LDC\LDCCRM\CrmApiV2; use LDC\LDCCRM\Enums\Country; @@ -115,4 +116,25 @@ class CrmClientV2CustomerTest extends CrmClientTester $this->assertEquals($customer->country->label, static::$info->country); $this->assertEquals($customer->country->value, static::$info->newcountry->value); } + + public function testCustomerActivate() + { + // Get non activated customer + /** @var \App\Models\Customer */ + $customer = ModelsCustomer::whereHas('logins', function ($q) { + $q->where('status', 0)->whereHas('brand', function ($q) { + $q->whereId(static::$brand->id); + }); + })->firstOrFail(); + $login = $customer->logins()->where('status', 0)->first(); + /** @var \App\Models\Brand */ + $brand = $login->brand; + /** @var \App\Models\CustomerInfo */ + $info = $customer->info($brand)->firstOrFail(); + + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew($info->hashcode); + + $this->assertTrue($customer->activate()); + } } From 3163685554e39437cbe689c1caaea26210f18266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 5 Aug 2021 15:01:47 +0200 Subject: [PATCH 026/103] remove useless warning since testing has its own database --- tests/Unit/CrmClientTester.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index b0a3bdd..0be6290 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -23,11 +23,6 @@ abstract class CrmClientTester extends TestCase /** @var App\Models\Login */ protected static $login; - public static function setUpBeforeClass(): void - { - fwrite(STDERR, "\n \e[0;30;43m WARNING \e[0m This test cannot be done on another envirronement than local or testing as it modifies the first Service credentials\n"); - } - public function setUp(): void { parent::setUp(); From f03ae09e6ae63d7ae923a61e0bd0ea6264f61f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 5 Aug 2021 16:18:09 +0200 Subject: [PATCH 027/103] - fixes - customer update prop test --- .../Controllers/Api/V2/OptinController.php | 2 +- .../Requests/Api/V2/SingleOptinRequest.php | 2 +- database/factories/CustomerFactory.php | 6 ++- database/factories/CustomerLoginFactory.php | 2 +- tests/Feature/ApiV2/SingleOptinTest.php | 6 +-- tests/Unit/CrmClientV2CustomerTest.php | 40 +++++++++++++++++++ 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/V2/OptinController.php b/app/Http/Controllers/Api/V2/OptinController.php index 0b2f2cf..7a90014 100644 --- a/app/Http/Controllers/Api/V2/OptinController.php +++ b/app/Http/Controllers/Api/V2/OptinController.php @@ -24,7 +24,7 @@ class OptinController extends Controller /** @var \App\Models\SingleOptin */ $singleOptin = SingleOptin::where('email', $request->email)->firstOrNew(); - if ($request->optin) { + if ($request->opt_in) { if (CustomerInfo::getFromEmail($service->brand, $request->email)->where('opt_in', true)->exists()) { throw new ApiErrorException(ApiError::singleOptinCustomerExists()); } diff --git a/app/Http/Requests/Api/V2/SingleOptinRequest.php b/app/Http/Requests/Api/V2/SingleOptinRequest.php index 48b832b..9a2fda2 100644 --- a/app/Http/Requests/Api/V2/SingleOptinRequest.php +++ b/app/Http/Requests/Api/V2/SingleOptinRequest.php @@ -8,7 +8,7 @@ class SingleOptinRequest extends BaseApiRequest { return [ 'email' => 'required|email:rfc,dns,spoof,filter', - 'optin' => 'required|boolean' + 'opt_in' => 'required|boolean' ]; } } diff --git a/database/factories/CustomerFactory.php b/database/factories/CustomerFactory.php index fe11a7d..851e862 100644 --- a/database/factories/CustomerFactory.php +++ b/database/factories/CustomerFactory.php @@ -23,7 +23,11 @@ class CustomerFactory extends Factory public function definition() { return [ - 'email' => $this->faker->unique()->safeEmail, + 'email' => preg_replace( + '/@example\..*/', + '@gmail.com', + $this->faker->unique()->safeEmail + ), ]; } } diff --git a/database/factories/CustomerLoginFactory.php b/database/factories/CustomerLoginFactory.php index ceffc7f..d5b8c42 100644 --- a/database/factories/CustomerLoginFactory.php +++ b/database/factories/CustomerLoginFactory.php @@ -29,7 +29,7 @@ class CustomerLoginFactory extends Factory // Force first to be active if (!self::$first) { self::$first = true; - $bool = true; + $bool = false; } return [ 'password' => 'testPassword', diff --git a/tests/Feature/ApiV2/SingleOptinTest.php b/tests/Feature/ApiV2/SingleOptinTest.php index 3225592..6ac1e4c 100644 --- a/tests/Feature/ApiV2/SingleOptinTest.php +++ b/tests/Feature/ApiV2/SingleOptinTest.php @@ -24,14 +24,14 @@ class SingleOptinTest extends ApiBase $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ 'email' => 'warthogdj@hotmail.fr', - 'optin' => 1 + 'opt_in' => 1 ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ 'email' => 'warthogdj@hotmail.fr', - 'optin' => 0 + 'opt_in' => 0 ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); @@ -44,7 +44,7 @@ class SingleOptinTest extends ApiBase $this->storeCustomer($service); $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ 'email' => 'warthogdj@hotmail.fr', - 'optin' => 1 + 'opt_in' => 1 ]); $json = $response->decodeResponseJson(); diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 67ca725..1d1252d 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -5,7 +5,9 @@ namespace Tests\Unit; use App\Models\Customer as ModelsCustomer; use Illuminate\Support\Str; use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\Enums\ApiError; use LDC\LDCCRM\Enums\Country; +use LDC\LDCCRM\Exceptions\ApiErrorException; use LDC\LDCCRM\Models\Customer; use TypeError; @@ -136,5 +138,43 @@ class CrmClientV2CustomerTest extends CrmClientTester $customer = $api->getCustomerOrNew($info->hashcode); $this->assertTrue($customer->activate()); + $this->assertTrue($customer->status); + + $this->expectException(ApiErrorException::class); + $this->expectExceptionMessage(ApiError::customerAlreadyActive()->label); + $this->assertTrue($customer->activate()); + } + + public function testCustomerToggleOptin() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + + $this->assertTrue($customer->toggleOptin()); + $this->assertTrue($customer->opt_in); + + $this->assertTrue($customer->toggleOptin()); + $this->assertFalse($customer->opt_in); + + $this->assertTrue($customer->toggleOptin()); + $this->assertTrue($customer->opt_in); + } + + public function testCustomerUpdateProps() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + + $customer->firstname = 'toto'; + $this->assertTrue($customer->save()); + + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $this->assertEquals('toto', $customer->firstname); + + $customer->country = Country::angola(); + $customer->save(); + + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $this->assertEquals(Country::angola(), $customer->country); } } From 31484df0554bee89d5046db5099ef75ffc2a14af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 09:45:30 +0200 Subject: [PATCH 028/103] fix typo in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d5b572b..7f91822 100644 --- a/composer.json +++ b/composer.json @@ -83,7 +83,7 @@ "@php artisan test" ], "runserve": [ - "php artisan serve --host=127.0.0.25 --port=8888 --env=testing&" + "@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}')" From dbf910223d49e49889732d4713c556b4ba6ddc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 09:46:11 +0200 Subject: [PATCH 029/103] add missing validation exception message on enum --- app/Enums/ApiError.php | 1 + app/Exceptions/Handler.php | 2 +- tests/Feature/ApiV2/CustomerTest.php | 8 -------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 9e84968..8031b63 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -44,6 +44,7 @@ class ApiError extends BaseEnum { 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.'), diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index b13c0e9..84eabe1 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -112,7 +112,7 @@ class Handler extends ExceptionHandler $request, $e->status, ApiError::validationFailed()->value, - sprintf('%s, %s', ApiError::validationFailed()->label, $e->getMessage()), + sprintf(ApiError::validationFailed()->label, $e->getMessage()), ['errors' => $e->errors()] ); case ModelNotFoundException::class: diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index ed36117..1bbd64b 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -119,10 +119,6 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); - $this->assertEquals( - ApiError::validationFailed()->label . ', The given data was invalid.', - $json->json('data.message') - ); } /** @@ -198,10 +194,6 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); - $this->assertEquals( - ApiError::validationFailed()->label . ', The given data was invalid.', - $json->json('data.message') - ); } /** From c451d53118f171dd5da9eefb277a9d8ff335f318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 09:46:16 +0200 Subject: [PATCH 030/103] fixed tests --- tests/Unit/CrmClientV2CustomerTest.php | 79 ++++++++++++++++++++------ 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 1d1252d..ba94555 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -87,9 +87,9 @@ class CrmClientV2CustomerTest extends CrmClientTester 'phone' => '0673891630', 'address' => 'string', 'address2' => 'string', - 'city' => 'string', + 'city' => 18, 'postcode' => 'string', - 'country' => Country::afghanistan()->value, + 'country' => Country::afghanistan(), 'optin' => true, 'status' => true, ]; @@ -145,21 +145,6 @@ class CrmClientV2CustomerTest extends CrmClientTester $this->assertTrue($customer->activate()); } - public function testCustomerToggleOptin() - { - $api = new CrmApiV2(); - $customer = $api->getCustomerOrNew(static::$info->hashcode); - - $this->assertTrue($customer->toggleOptin()); - $this->assertTrue($customer->opt_in); - - $this->assertTrue($customer->toggleOptin()); - $this->assertFalse($customer->opt_in); - - $this->assertTrue($customer->toggleOptin()); - $this->assertTrue($customer->opt_in); - } - public function testCustomerUpdateProps() { $api = new CrmApiV2(); @@ -171,10 +156,70 @@ class CrmClientV2CustomerTest extends CrmClientTester $customer = $api->getCustomerOrNew(static::$info->hashcode); $this->assertEquals('toto', $customer->firstname); + $customer->lastname = 'pouet'; + $this->assertTrue($customer->save()); + + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $this->assertEquals('pouet', $customer->lastname); + $customer->country = Country::angola(); $customer->save(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $this->assertEquals(Country::angola(), $customer->country); + + $customer->email = 'pouet@gmail.com'; + $customer->birth_date = '1992-03-08'; + $customer->phone = '0388977213'; + $customer->address = '30 rue des toto'; + $customer->address2 = 'batiment c'; + $customer->city = 'toto ville'; + $customer->postcode = '83160'; + $this->assertTrue($customer->save()); + + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $this->assertEquals('pouet@gmail.com', $customer->email); + $this->assertEquals('1992-03-08', $customer->birth_date); + $this->assertEquals('0388977213', $customer->phone); + $this->assertEquals('30 rue des toto', $customer->address); + $this->assertEquals('batiment c', $customer->address2); + $this->assertEquals('toto ville', $customer->city); + $this->assertEquals('83160', $customer->postcode); } + + public function testCustomerToggleOptin() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + + $customer->opt_in = false; + $this->assertTrue($customer->save()); + + $this->assertTrue($customer->toggleOptin()); + $this->assertTrue($customer->opt_in); + + $this->assertTrue($customer->toggleOptin()); + $this->assertFalse($customer->opt_in); + + $this->assertTrue($customer->toggleOptin()); + $this->assertTrue($customer->opt_in); + } + + // public function testCustomerStoreProps() + // { + // $api = new CrmApiV2(); + // $customer = $api->getCustomerOrNew(static::$info->hashcode); + + // $customer->firstname = 'toto'; + // $this->assertTrue($customer->save()); + + // $customer = $api->getCustomerOrNew(static::$info->hashcode); + // $this->assertEquals('toto', $customer->firstname); + + // $customer->country = Country::angola(); + // $customer->save(); + + // $customer = $api->getCustomerOrNew(static::$info->hashcode); + // $this->assertEquals(Country::angola(), $customer->country); + // } } From ef2b81b500289b3c5eb48059348e3936ad4233ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 15:56:42 +0200 Subject: [PATCH 031/103] bump composer deps --- composer.json | 27 ++- composer.lock | 450 ++++++++++++++++++++++++++------------------------ 2 files changed, 247 insertions(+), 230 deletions(-) diff --git a/composer.json b/composer.json index 7f91822..16c6cd7 100644 --- a/composer.json +++ b/composer.json @@ -32,9 +32,8 @@ "guzzlehttp/guzzle": "^7.0.1", "kwaadpepper/enum": "^0.1", "laravel-lang/lang": "~7.0", - "laravel/framework": "^8.12", - "laravel/tinker": "^2.5", - "schuppo/password-strength": "~2.0" + "laravel/framework": "^8.39", + "laravel/tinker": "^2.5" }, "require-dev": { "facade/ignition": "^2.5", @@ -75,19 +74,15 @@ "minimum-stability": "dev", "prefer-stable": true, "scripts": { - "test": "./test.sh", - "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}')" - ], + "test": [ + "@configclear", + "./test.sh" + ], + "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" diff --git a/composer.lock b/composer.lock index 9daf9de..6977a52 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2ebadc06386d0d4db3eb629fe92d91fc", + "content-hash": "9456db5ed8418e270aa1f66c9438d861", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -1449,16 +1449,16 @@ }, { "name": "laravel/framework", - "version": "v8.51.0", + "version": "v8.53.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "208d9c0043b4c192a9bb9b15782cc4ec37f28bb0" + "reference": "5df423c2790f32b26baee90518bb659572aa404f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/208d9c0043b4c192a9bb9b15782cc4ec37f28bb0", - "reference": "208d9c0043b4c192a9bb9b15782cc4ec37f28bb0", + "url": "https://api.github.com/repos/laravel/framework/zipball/5df423c2790f32b26baee90518bb659572aa404f", + "reference": "5df423c2790f32b26baee90518bb659572aa404f", "shasum": "" }, "require": { @@ -1531,7 +1531,7 @@ "illuminate/view": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^3.155", + "aws/aws-sdk-php": "^3.186.4", "doctrine/dbal": "^2.6|^3.0", "filp/whoops": "^2.8", "guzzlehttp/guzzle": "^6.5.5|^7.0.1", @@ -1544,7 +1544,7 @@ "symfony/cache": "^5.1.4" }, "suggest": { - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.186.4).", "brianium/paratest": "Required to run tests in parallel (^6.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", "ext-ftp": "Required to use the Flysystem FTP driver.", @@ -1613,7 +1613,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-07-20T14:38:36+00:00" + "time": "2021-08-05T14:04:08+00:00" }, { "name": "laravel/tinker", @@ -1936,16 +1936,16 @@ }, { "name": "monolog/monolog", - "version": "2.3.1", + "version": "2.3.2", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "9738e495f288eec0b187e310b7cdbbb285777dbe" + "reference": "71312564759a7db5b789296369c1a264efc43aad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/9738e495f288eec0b187e310b7cdbbb285777dbe", - "reference": "9738e495f288eec0b187e310b7cdbbb285777dbe", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/71312564759a7db5b789296369c1a264efc43aad", + "reference": "71312564759a7db5b789296369c1a264efc43aad", "shasum": "" }, "require": { @@ -2016,7 +2016,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.3.1" + "source": "https://github.com/Seldaek/monolog/tree/2.3.2" }, "funding": [ { @@ -2028,26 +2028,27 @@ "type": "tidelift" } ], - "time": "2021-07-14T11:56:39+00:00" + "time": "2021-07-23T07:42:52+00:00" }, { "name": "nesbot/carbon", - "version": "2.50.0", + "version": "2.51.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "f47f17d17602b2243414a44ad53d9f8b9ada5fdb" + "reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f47f17d17602b2243414a44ad53d9f8b9ada5fdb", - "reference": "f47f17d17602b2243414a44ad53d9f8b9ada5fdb", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922", + "reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", "symfony/translation": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { @@ -2066,8 +2067,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev", - "dev-3.x": "3.x-dev" + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" }, "laravel": { "providers": [ @@ -2121,7 +2122,7 @@ "type": "tidelift" } ], - "time": "2021-06-28T22:38:45+00:00" + "time": "2021-07-28T13:16:28+00:00" }, { "name": "nikic/php-parser", @@ -2793,20 +2794,21 @@ }, { "name": "ramsey/collection", - "version": "1.1.3", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1" + "reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", - "reference": "28a5c4ab2f5111db6a60b2b4ec84057e0f43b9c1", + "url": "https://api.github.com/repos/ramsey/collection/zipball/eaca1dc1054ddd10cbd83c1461907bee6fb528fa", + "reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa", "shasum": "" }, "require": { - "php": "^7.2 || ^8" + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" }, "require-dev": { "captainhook/captainhook": "^5.3", @@ -2816,6 +2818,7 @@ "hamcrest/hamcrest-php": "^2", "jangregor/phpstan-prophecy": "^0.8", "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", "phpstan/extension-installer": "^1", "phpstan/phpstan": "^0.12.32", "phpstan/phpstan-mockery": "^0.12.5", @@ -2843,7 +2846,7 @@ "homepage": "https://benramsey.com" } ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", + "description": "A PHP library for representing and manipulating collections.", "keywords": [ "array", "collection", @@ -2854,7 +2857,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.1.3" + "source": "https://github.com/ramsey/collection/tree/1.2.1" }, "funding": [ { @@ -2866,7 +2869,7 @@ "type": "tidelift" } ], - "time": "2021-01-21T17:40:04+00:00" + "time": "2021-08-06T03:41:06+00:00" }, { "name": "ramsey/uuid", @@ -2960,68 +2963,6 @@ ], "time": "2020-08-18T17:17:46+00:00" }, - { - "name": "schuppo/password-strength", - "version": "v2.4", - "source": { - "type": "git", - "url": "https://github.com/schuppo/PasswordStrengthPackage.git", - "reference": "d25211547b77bc220832da9cafe066ebc51bd228" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schuppo/PasswordStrengthPackage/zipball/d25211547b77bc220832da9cafe066ebc51bd228", - "reference": "d25211547b77bc220832da9cafe066ebc51bd228", - "shasum": "" - }, - "require": { - "illuminate/support": "^5.1|^6.0|^7.0|^8.0", - "illuminate/translation": "^5.1|6.*|7.*|8.*", - "php": ">=7.1.0" - }, - "require-dev": { - "illuminate/validation": "~5.1|6.*|7.*|8.*", - "orchestra/testbench": "^3.5|^4.0|^5.0|^6.0", - "phpunit/phpunit": "^6.0|^7.0|^8.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Schuppo\\PasswordStrength\\PasswordStrengthServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Schuppo\\PasswordStrength\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oliver Schupp", - "email": "oliver.schupp@yahoo.de" - } - ], - "description": "This package provides a validator for ensuring strong passwords in Laravel 4 applications.", - "keywords": [ - "laravel", - "laravel 5", - "laravel5", - "password", - "password strength", - "validation" - ], - "support": { - "issues": "https://github.com/schuppo/PasswordStrengthPackage/issues", - "source": "https://github.com/schuppo/PasswordStrengthPackage/tree/v2.4" - }, - "time": "2021-02-25T15:17:02+00:00" - }, { "name": "swiftmailer/swiftmailer", "version": "v6.2.7", @@ -3099,16 +3040,16 @@ }, { "name": "symfony/console", - "version": "v5.3.2", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1" + "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/649730483885ff2ca99ca0560ef0e5f6b03f2ac1", - "reference": "649730483885ff2ca99ca0560ef0e5f6b03f2ac1", + "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", "shasum": "" }, "require": { @@ -3116,11 +3057,12 @@ "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php80": "^1.16", "symfony/service-contracts": "^1.1|^2", "symfony/string": "^5.1" }, "conflict": { + "psr/log": ">=3", "symfony/dependency-injection": "<4.4", "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", @@ -3128,10 +3070,10 @@ "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2", "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/event-dispatcher": "^4.4|^5.0", @@ -3177,7 +3119,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.3.2" + "source": "https://github.com/symfony/console/tree/v5.3.6" }, "funding": [ { @@ -3193,24 +3135,25 @@ "type": "tidelift" } ], - "time": "2021-06-12T09:42:48+00:00" + "time": "2021-07-27T19:10:22+00:00" }, { "name": "symfony/css-selector", - "version": "v5.3.0", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814" + "reference": "7fb120adc7f600a59027775b224c13a33530dd90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", - "reference": "fcd0b29a7a0b1bb5bfbedc6231583d77fea04814", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -3242,7 +3185,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.3.0" + "source": "https://github.com/symfony/css-selector/tree/v5.3.4" }, "funding": [ { @@ -3258,7 +3201,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:40:38+00:00" + "time": "2021-07-21T12:38:00+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3329,22 +3272,21 @@ }, { "name": "symfony/error-handler", - "version": "v5.3.3", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "43323e79c80719e8a4674e33484bca98270d223f" + "reference": "281f6c4660bcf5844bb0346fe3a4664722fe4c73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/43323e79c80719e8a4674e33484bca98270d223f", - "reference": "43323e79c80719e8a4674e33484bca98270d223f", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/281f6c4660bcf5844bb0346fe3a4664722fe4c73", + "reference": "281f6c4660bcf5844bb0346fe3a4664722fe4c73", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/log": "^1.0", - "symfony/polyfill-php80": "^1.15", + "psr/log": "^1|^2|^3", "symfony/var-dumper": "^4.4|^5.0" }, "require-dev": { @@ -3378,7 +3320,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.3.3" + "source": "https://github.com/symfony/error-handler/tree/v5.3.4" }, "funding": [ { @@ -3394,27 +3336,27 @@ "type": "tidelift" } ], - "time": "2021-06-24T08:13:00+00:00" + "time": "2021-07-23T15:55:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.3.0", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce" + "reference": "f2fd2208157553874560f3645d4594303058c4bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/67a5f354afa8e2f231081b3fa11a5912f933c3ce", - "reference": "67a5f354afa8e2f231081b3fa11a5912f933c3ce", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f2fd2208157553874560f3645d4594303058c4bd", + "reference": "f2fd2208157553874560f3645d4594303058c4bd", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", "symfony/event-dispatcher-contracts": "^2", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "symfony/dependency-injection": "<4.4" @@ -3424,7 +3366,7 @@ "symfony/event-dispatcher-implementation": "2.0" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2|^3", "symfony/config": "^4.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", "symfony/error-handler": "^4.4|^5.0", @@ -3463,7 +3405,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.3.4" }, "funding": [ { @@ -3479,7 +3421,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2021-07-23T15:55:36+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3562,20 +3504,21 @@ }, { "name": "symfony/finder", - "version": "v5.3.0", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6" + "reference": "17f50e06018baec41551a71a15731287dbaab186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", - "reference": "0ae3f047bed4edff6fd35b26a9a6bfdc92c953c6", + "url": "https://api.github.com/repos/symfony/finder/zipball/17f50e06018baec41551a71a15731287dbaab186", + "reference": "17f50e06018baec41551a71a15731287dbaab186", "shasum": "" }, "require": { - "php": ">=7.2.5" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { @@ -3603,7 +3546,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.3.0" + "source": "https://github.com/symfony/finder/tree/v5.3.4" }, "funding": [ { @@ -3619,7 +3562,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T12:52:38+00:00" + "time": "2021-07-23T15:54:19+00:00" }, { "name": "symfony/http-client-contracts", @@ -3701,23 +3644,23 @@ }, { "name": "symfony/http-foundation", - "version": "v5.3.3", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "0e45ab1574caa0460d9190871a8ce47539e40ccf" + "reference": "a8388f7b7054a7401997008ce9cd8c6b0ab7ac75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0e45ab1574caa0460d9190871a8ce47539e40ccf", - "reference": "0e45ab1574caa0460d9190871a8ce47539e40ccf", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a8388f7b7054a7401997008ce9cd8c6b0ab7ac75", + "reference": "a8388f7b7054a7401997008ce9cd8c6b0ab7ac75", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { "predis/predis": "~1.0", @@ -3754,7 +3697,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.3.3" + "source": "https://github.com/symfony/http-foundation/tree/v5.3.6" }, "funding": [ { @@ -3770,25 +3713,25 @@ "type": "tidelift" } ], - "time": "2021-06-27T09:19:40+00:00" + "time": "2021-07-27T17:08:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.3.3", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8" + "reference": "60030f209018356b3b553b9dbd84ad2071c1b7e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8", - "reference": "90ad9f4b21ddcb8ebe9faadfcca54929ad23f9f8", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/60030f209018356b3b553b9dbd84ad2071c1b7e0", + "reference": "60030f209018356b3b553b9dbd84ad2071c1b7e0", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/log": "~1.0", + "psr/log": "^1|^2", "symfony/deprecation-contracts": "^2.1", "symfony/error-handler": "^4.4|^5.0", "symfony/event-dispatcher": "^5.0", @@ -3796,7 +3739,7 @@ "symfony/http-foundation": "^5.3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "symfony/browser-kit": "<4.4", @@ -3815,7 +3758,7 @@ "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", @@ -3866,7 +3809,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.3.3" + "source": "https://github.com/symfony/http-kernel/tree/v5.3.6" }, "funding": [ { @@ -3882,20 +3825,20 @@ "type": "tidelift" } ], - "time": "2021-06-30T08:27:49+00:00" + "time": "2021-07-29T07:06:27+00:00" }, { "name": "symfony/mime", - "version": "v5.3.2", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a" + "reference": "633e4e8afe9e529e5599d71238849a4218dd497b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/47dd7912152b82d0d4c8d9040dbc93d6232d472a", - "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a", + "url": "https://api.github.com/repos/symfony/mime/zipball/633e4e8afe9e529e5599d71238849a4218dd497b", + "reference": "633e4e8afe9e529e5599d71238849a4218dd497b", "shasum": "" }, "require": { @@ -3903,7 +3846,7 @@ "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "egulias/email-validator": "~3.0.0", @@ -3949,7 +3892,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.3.2" + "source": "https://github.com/symfony/mime/tree/v5.3.4" }, "funding": [ { @@ -3965,7 +3908,7 @@ "type": "tidelift" } ], - "time": "2021-06-09T10:58:01+00:00" + "time": "2021-07-21T12:40:44+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4128,16 +4071,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab" + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/24b72c6baa32c746a4d0840147c9715e42bb68ab", - "reference": "24b72c6baa32c746a4d0840147c9715e42bb68ab", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", "shasum": "" }, "require": { @@ -4189,7 +4132,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1" }, "funding": [ { @@ -4205,7 +4148,7 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2021-05-27T12:26:48+00:00" }, { "name": "symfony/polyfill-intl-idn", @@ -4380,16 +4323,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1" + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/2df51500adbaebdc4c38dea4c89a2e131c45c8a1", - "reference": "2df51500adbaebdc4c38dea4c89a2e131c45c8a1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", "shasum": "" }, "require": { @@ -4440,7 +4383,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" }, "funding": [ { @@ -4456,7 +4399,7 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:27:20+00:00" + "time": "2021-05-27T12:26:48+00:00" }, { "name": "symfony/polyfill-php72", @@ -4615,16 +4558,16 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0" + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/eca0bf41ed421bed1b57c4958bab16aa86b757d0", - "reference": "eca0bf41ed421bed1b57c4958bab16aa86b757d0", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "shasum": "" }, "require": { @@ -4678,7 +4621,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" }, "funding": [ { @@ -4694,7 +4637,86 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2021-07-28T13:41:28+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "e66119f3de95efc359483f810c4c3e6436279436" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", + "reference": "e66119f3de95efc359483f810c4c3e6436279436", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-21T13:25:03+00:00" }, { "name": "symfony/process", @@ -4760,22 +4782,22 @@ }, { "name": "symfony/routing", - "version": "v5.3.0", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199" + "reference": "0a35d2f57d73c46ab6d042ced783b81d09a624c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/368e81376a8e049c37cb80ae87dbfbf411279199", - "reference": "368e81376a8e049c37cb80ae87dbfbf411279199", + "url": "https://api.github.com/repos/symfony/routing/zipball/0a35d2f57d73c46ab6d042ced783b81d09a624c4", + "reference": "0a35d2f57d73c46ab6d042ced783b81d09a624c4", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "doctrine/annotations": "<1.12", @@ -4785,7 +4807,7 @@ }, "require-dev": { "doctrine/annotations": "^1.12", - "psr/log": "~1.0", + "psr/log": "^1|^2|^3", "symfony/config": "^5.3", "symfony/dependency-injection": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", @@ -4830,7 +4852,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.3.0" + "source": "https://github.com/symfony/routing/tree/v5.3.4" }, "funding": [ { @@ -4846,7 +4868,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2021-07-23T15:55:36+00:00" }, { "name": "symfony/service-contracts", @@ -5012,23 +5034,23 @@ }, { "name": "symfony/translation", - "version": "v5.3.3", + "version": "v5.3.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "380b8c9e944d0e364b25f28e8e555241eb49c01c" + "reference": "d89ad7292932c2699cbe4af98d72c5c6bbc504c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/380b8c9e944d0e364b25f28e8e555241eb49c01c", - "reference": "380b8c9e944d0e364b25f28e8e555241eb49c01c", + "url": "https://api.github.com/repos/symfony/translation/zipball/d89ad7292932c2699cbe4af98d72c5c6bbc504c1", + "reference": "d89ad7292932c2699cbe4af98d72c5c6bbc504c1", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15", + "symfony/polyfill-php80": "^1.16", "symfony/translation-contracts": "^2.3" }, "conflict": { @@ -5042,7 +5064,7 @@ "symfony/translation-implementation": "2.3" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2|^3", "symfony/config": "^4.4|^5.0", "symfony/console": "^4.4|^5.0", "symfony/dependency-injection": "^5.0", @@ -5087,7 +5109,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.3.3" + "source": "https://github.com/symfony/translation/tree/v5.3.4" }, "funding": [ { @@ -5103,7 +5125,7 @@ "type": "tidelift" } ], - "time": "2021-06-27T12:22:47+00:00" + "time": "2021-07-25T09:39:16+00:00" }, { "name": "symfony/translation-contracts", @@ -5185,22 +5207,22 @@ }, { "name": "symfony/var-dumper", - "version": "v5.3.3", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "46aa709affb9ad3355bd7a810f9662d71025c384" + "reference": "3dd8ddd1e260e58ecc61bb78da3b6584b3bfcba0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46aa709affb9ad3355bd7a810f9662d71025c384", - "reference": "46aa709affb9ad3355bd7a810f9662d71025c384", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3dd8ddd1e260e58ecc61bb78da3b6584b3bfcba0", + "reference": "3dd8ddd1e260e58ecc61bb78da3b6584b3bfcba0", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "conflict": { "phpunit/phpunit": "<5.4.3", @@ -5253,7 +5275,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.3.3" + "source": "https://github.com/symfony/var-dumper/tree/v5.3.6" }, "funding": [ { @@ -5269,7 +5291,7 @@ "type": "tidelift" } ], - "time": "2021-06-24T08:13:00+00:00" + "time": "2021-07-27T01:56:02+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -6120,16 +6142,16 @@ }, { "name": "nunomaduro/collision", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "b5cb36122f1c142c3c3ee20a0ae778439ef0244b" + "reference": "0122ac6b03c75279ef78d1c0ad49725dfc52a8d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b5cb36122f1c142c3c3ee20a0ae778439ef0244b", - "reference": "b5cb36122f1c142c3c3ee20a0ae778439ef0244b", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0122ac6b03c75279ef78d1c0ad49725dfc52a8d2", + "reference": "0122ac6b03c75279ef78d1c0ad49725dfc52a8d2", "shasum": "" }, "require": { @@ -6143,10 +6165,10 @@ "fideloper/proxy": "^4.4.1", "friendsofphp/php-cs-fixer": "^2.17.3", "fruitcake/laravel-cors": "^2.0.3", - "laravel/framework": "^9.0", + "laravel/framework": "^8.0 || ^9.0", "nunomaduro/larastan": "^0.6.2", "nunomaduro/mock-final-classes": "^1.0", - "orchestra/testbench": "^7.0", + "orchestra/testbench": "^6.0 || ^7.0", "phpstan/phpstan": "^0.12.64", "phpunit/phpunit": "^9.5.0" }, @@ -6204,7 +6226,7 @@ "type": "patreon" } ], - "time": "2021-06-22T20:47:22+00:00" + "time": "2021-07-26T20:39:06+00:00" }, { "name": "phar-io/manifest", @@ -6862,16 +6884,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.7", + "version": "9.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d0dc8b6999c937616df4fb046792004b33fd31c5" + "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d0dc8b6999c937616df4fb046792004b33fd31c5", - "reference": "d0dc8b6999c937616df4fb046792004b33fd31c5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/191768ccd5c85513b4068bdbe99bb6390c7d54fb", + "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb", "shasum": "" }, "require": { @@ -6883,7 +6905,7 @@ "ext-xml": "*", "ext-xmlwriter": "*", "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.1", + "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", @@ -6949,7 +6971,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.8" }, "funding": [ { @@ -6961,7 +6983,7 @@ "type": "github" } ], - "time": "2021-07-19T06:14:47+00:00" + "time": "2021-07-31T15:17:34+00:00" }, { "name": "sebastian/cli-parser", @@ -7985,16 +8007,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "75a63c33a8577608444246075ea0af0d052e452a" + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a", - "reference": "75a63c33a8577608444246075ea0af0d052e452a", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { @@ -8023,7 +8045,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/master" + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { @@ -8031,7 +8053,7 @@ "type": "github" } ], - "time": "2020-07-12T23:59:07+00:00" + "time": "2021-07-28T10:34:58+00:00" } ], "aliases": [], From 260d5fd259d8d93c2e4c111889e43ee7419a0add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 15:57:48 +0200 Subject: [PATCH 032/103] - Arcadia separate routes on v2 - fix optin v2 - customer validation password - full working v2 tests --- .../Controllers/Api/V2/ArcadiaController.php | 38 ++++++++++-- .../Controllers/Api/V2/CustomerController.php | 9 +++ .../Controllers/Api/V2/OptinController.php | 7 ++- ...php => SendArchadiaDefaultMailRequest.php} | 40 +++--------- .../Api/V2/SendArchadiaGameMailRequest.php | 38 ++++++++++++ .../Requests/Api/V2/StoreCustomerRequest.php | 12 ++-- .../Requests/Api/V2/UpdateCustomerRequest.php | 10 ++- resources/lang/fr.json | 7 +++ routes/api.php | 6 +- tests/Feature/ApiV2/ArcadiaTest.php | 7 +-- tests/Unit/CrmClientV2ArcadiaTest.php | 45 ++++++++++++++ tests/Unit/CrmClientV2CustomerTest.php | 62 +++++++++++++++---- tests/Unit/CrmClientV2SingleOptinTest.php | 39 ++++++++++++ 13 files changed, 254 insertions(+), 66 deletions(-) rename app/Http/Requests/Api/V2/{SendArchadiaMailRequest.php => SendArchadiaDefaultMailRequest.php} (52%) create mode 100644 app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php create mode 100644 resources/lang/fr.json create mode 100644 tests/Unit/CrmClientV2ArcadiaTest.php create mode 100644 tests/Unit/CrmClientV2SingleOptinTest.php diff --git a/app/Http/Controllers/Api/V2/ArcadiaController.php b/app/Http/Controllers/Api/V2/ArcadiaController.php index 09912a5..91794b4 100644 --- a/app/Http/Controllers/Api/V2/ArcadiaController.php +++ b/app/Http/Controllers/Api/V2/ArcadiaController.php @@ -7,7 +7,9 @@ use App\Enums\ArcadiaMailType; use App\Enums\ArcadiaOrderType; use App\Exceptions\Api\V2\ApiErrorException; use App\Http\Controllers\Controller; -use App\Http\Requests\Api\V2\SendArchadiaMailRequest; +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; @@ -17,15 +19,41 @@ 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 outgoingMail(SendArchadiaMailRequest $request, Customer $customer, Service $service) + public function outgoingMailDefault(SendArchadiaDefaultMailRequest $request, Customer $customer, Service $service) { - $xml = 'none'; + return $this->handleArcadiaMail($request, ArcadiaMailType::default(), $customer, $service); + } + + public function outgoingMailGame(SendArchadiaGameMailRequest $request, Customer $customer, Service $service) + { + 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(); - $type = ArcadiaMailType::make(intval($request->type)); $orderType = ArcadiaOrderType::make(intval($request->state_order)); $xml = $this->makeXml($request, $type, $orderType, $customer, $customerInfo); @@ -66,7 +94,7 @@ class ArcadiaController extends Controller * @return string */ private function makeXml( - SendArchadiaMailRequest $request, + BaseApiRequest $request, ArcadiaMailType $type, ArcadiaOrderType $orderType, Customer $customer, diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index 81b6f49..f8db182 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -12,6 +12,7 @@ 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; @@ -35,6 +36,14 @@ class CustomerController extends Controller $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) { diff --git a/app/Http/Controllers/Api/V2/OptinController.php b/app/Http/Controllers/Api/V2/OptinController.php index 7a90014..37014ee 100644 --- a/app/Http/Controllers/Api/V2/OptinController.php +++ b/app/Http/Controllers/Api/V2/OptinController.php @@ -21,13 +21,14 @@ class OptinController extends Controller */ 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) { - if (CustomerInfo::getFromEmail($service->brand, $request->email)->where('opt_in', true)->exists()) { - throw new ApiErrorException(ApiError::singleOptinCustomerExists()); - } $singleOptin->fill($request->validated()); $singleOptin->brand()->associate($service->brand); $singleOptin->saveOrFail(); diff --git a/app/Http/Requests/Api/V2/SendArchadiaMailRequest.php b/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php similarity index 52% rename from app/Http/Requests/Api/V2/SendArchadiaMailRequest.php rename to app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php index 2b0ccb7..d554900 100644 --- a/app/Http/Requests/Api/V2/SendArchadiaMailRequest.php +++ b/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php @@ -2,26 +2,23 @@ namespace App\Http\Requests\Api\V2; -use App\Enums\ArcadiaMailType; 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 SendArchadiaMailRequest extends BaseApiRequest +class SendArchadiaDefaultMailRequest extends BaseApiRequest { use SanitizesInputs; protected $sanitizers = [ - 'type' => Trim::class, - 'state_order' => Trim::class, + 'label_type_support' => Trim::class, 'operation' => Trim::class, - 'support_code' => Trim::class, 'order_date' => Trim::class, 'order_num' => RemoveNonNumeric::class, 'package_unit_weight' => RemoveNonNumeric::class, - 'code_unique' => Trim::class + 'support_code' => Trim::class, ]; /** @@ -32,37 +29,16 @@ class SendArchadiaMailRequest extends BaseApiRequest public function rules(): array { return [ - 'type' => [ - 'required', - new EnumIsValidRule(ArcadiaMailType::class) - ], - 'operation' => 'required|int', - 'support_code' => 'required|int', 'label_type_support' => 'required|string', + 'operation' => 'required|int', 'order_date' => 'required|date', - // IF default - 'order_num' => [ - 'required_if:type,' . ArcadiaMailType::default()->value, - 'numeric', - 'digits:9' - ], + 'order_num' => 'required|numeric', + 'package_unit_weight' => 'required|integer', 'state_order' => [ - 'required_if:type,' . ArcadiaMailType::default()->value, + 'required', new EnumIsValidRule(ArcadiaOrderType::class) ], - 'package_unit_weight' => [ - 'required_if:type,' . ArcadiaMailType::default()->value, - 'integer' - ], - // IF game - 'code_unique' => [ - 'required_if:type,' . ArcadiaMailType::game()->value, - 'string' - ], - 'code_on_pack' => [ - 'sometimes', - 'string' - ] + 'support_code' => 'required|int', ]; } } diff --git a/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php b/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php new file mode 100644 index 0000000..901c48a --- /dev/null +++ b/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php @@ -0,0 +1,38 @@ + 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' + ]; + } +} diff --git a/app/Http/Requests/Api/V2/StoreCustomerRequest.php b/app/Http/Requests/Api/V2/StoreCustomerRequest.php index 24bb01b..70e4e98 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerRequest.php +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\Api\V2; use App\Enums\Country; +use Illuminate\Validation\Rules\Password; class StoreCustomerRequest extends BaseApiRequest { @@ -30,11 +31,12 @@ class StoreCustomerRequest extends BaseApiRequest ], 'password' => [ 'required', - 'case_diff', - 'numbers', - 'letters', - 'symbols', - 'min:10' + Password::min(10) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() ], 'birth_date' => [ 'required', diff --git a/app/Http/Requests/Api/V2/UpdateCustomerRequest.php b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php index 03bed67..c63a40b 100644 --- a/app/Http/Requests/Api/V2/UpdateCustomerRequest.php +++ b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\Api\V2; use App\Enums\Country; +use Illuminate\Validation\Rules\Password; class UpdateCustomerRequest extends BaseApiRequest { @@ -31,7 +32,14 @@ class UpdateCustomerRequest extends BaseApiRequest 'firstname' => [$atLeast, 'string', 'min:2'], 'lastname' => [$atLeast, 'string', 'min:2'], 'email' => [$atLeast, 'email:rfc,dns,spoof,filter'], - 'password' => $atLeast, + 'password' => [ + $atLeast, Password::min(10) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + ], 'birth_date' => [$atLeast, 'date_format:Y-m-d'], 'address' => [$atLeast, 'required_with:address2', 'string'], 'address2' => ['sometimes', 'string'], diff --git a/resources/lang/fr.json b/resources/lang/fr.json new file mode 100644 index 0000000..0cdc1c7 --- /dev/null +++ b/resources/lang/fr.json @@ -0,0 +1,7 @@ +{ + "The :attribute must contain at least one uppercase and one lowercase letter.": ":attribute doit contenir au moins une lettre majuscule et une lettre minuscule.", + "The :attribute must contain at least one letter.": ":attribute doit contenir au moins une lettre.", + "The :attribute must contain at least one symbol.": ":attribute doit contenir au moins un caractère spécial.", + "The :attribute must contain at least one number.": ":attribute doit contenir au moins un nombre.", + "The given :attribute has appeared in a data leak. Please choose a different :attribute.": ":attribute apparait dans une fuite de données sur le web. Merci de choisir un :attribute différent." +} diff --git a/routes/api.php b/routes/api.php index 2030614..7355450 100644 --- a/routes/api.php +++ b/routes/api.php @@ -51,8 +51,10 @@ Route::prefix('v2')->name('apiV2.')->group(function () { }); }); Route::prefix('arcadia')->name('arcadia.')->group(function () { - Route::post('/outgoing-mail/{customer}', [V2ArcadiaController::class, 'outgoingMail']) - ->name('outgoingMail'); + Route::post('/outgoing-mail/{customer}/default', [V2ArcadiaController::class, 'outgoingMailDefault']) + ->name('outgoingMailDefault'); + Route::post('/outgoing-mail/{customer}/game', [V2ArcadiaController::class, 'outgoingMailGame']) + ->name('outgoingMailGame'); }); }); }); diff --git a/tests/Feature/ApiV2/ArcadiaTest.php b/tests/Feature/ApiV2/ArcadiaTest.php index bb32644..61c885d 100644 --- a/tests/Feature/ApiV2/ArcadiaTest.php +++ b/tests/Feature/ApiV2/ArcadiaTest.php @@ -21,7 +21,6 @@ class ArcadiaTest extends ApiBase { extract($this->getCustomer()); $data = [ - 'type' => ArcadiaMailType::default()->value, 'operation' => 16, 'label_type_support' => 'Send Departaimants', 'order_date' => \date('Y/m/d H:i:s'), @@ -29,11 +28,10 @@ class ArcadiaTest extends ApiBase 'support_code' => 32, 'label_type_support' => 'Fond carte Aimanté', 'order_num' => str_pad('23', 9, '0', STR_PAD_LEFT), - 'qty' => 1, 'package_unit_weight' => 200, ]; $response = $this->callApiWithToken($service, 'post', route( - 'apiV2.arcadia.outgoingMail', + 'apiV2.arcadia.outgoingMailDefault', compact('customer') ), $data); $json = $response->decodeResponseJson(); @@ -45,7 +43,6 @@ class ArcadiaTest extends ApiBase { extract($this->getCustomer()); $data = [ - 'type' => ArcadiaMailType::game()->value, 'operation' => 16, 'order_date' => date('Y/m/d H:i:s'), 'support_code' => 32, @@ -54,7 +51,7 @@ class ArcadiaTest extends ApiBase 'code_on_pack' => 'anystring' ]; $response = $this->callApiWithToken($service, 'post', route( - 'apiV2.arcadia.outgoingMail', + 'apiV2.arcadia.outgoingMailGame', compact('customer') ), $data); $json = $response->decodeResponseJson(); diff --git a/tests/Unit/CrmClientV2ArcadiaTest.php b/tests/Unit/CrmClientV2ArcadiaTest.php new file mode 100644 index 0000000..f5de599 --- /dev/null +++ b/tests/Unit/CrmClientV2ArcadiaTest.php @@ -0,0 +1,45 @@ +getCustomerOrNew(static::$info->hashcode); + + $arcadiaOrderMail = new ArcadiaMailDefault([ + 'operation' => 16, + 'label_type_support' => 'Send Departaimants', + 'order_date' => \date('Y/m/d H:i:s'), + 'state_order' => ArcadiaOrderType::cb()->value, + 'support_code' => 32, + 'label_type_support' => 'Fond carte Aimanté', + 'order_num' => 23, + 'package_unit_weight' => 200, + ]); + $this->assertTrue($arcadiaOrderMail->send($customer)); + } + + public function testArcadiaSendOrderGame() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $arcadiaOrderMail = new ArcadiaMailGame([ + 'operation' => 16, + 'order_date' => date('Y/m/d H:i:s'), + 'support_code' => 32, + 'label_type_support' => 'Jeux Reghalal', + 'code_unique' => (string)Str::uuid(), + 'code_on_pack' => 'anystring' + ]); + $this->assertTrue($arcadiaOrderMail->send($customer)); + } +} diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index ba94555..0bbe892 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -8,6 +8,7 @@ use LDC\LDCCRM\CrmApiV2; use LDC\LDCCRM\Enums\ApiError; use LDC\LDCCRM\Enums\Country; use LDC\LDCCRM\Exceptions\ApiErrorException; +use LDC\LDCCRM\Exceptions\ValidationException; use LDC\LDCCRM\Models\Customer; use TypeError; @@ -205,21 +206,56 @@ class CrmClientV2CustomerTest extends CrmClientTester $this->assertTrue($customer->opt_in); } - // public function testCustomerStoreProps() - // { - // $api = new CrmApiV2(); - // $customer = $api->getCustomerOrNew(static::$info->hashcode); + public function testCustomerStoreProps() + { + $customer = new Customer(); - // $customer->firstname = 'toto'; - // $this->assertTrue($customer->save()); + $customer->firstname = 'toto'; + $customer->lastname = 'delarogerie'; + $customer->email = 'toto.delarogerie@gmail.com'; + $customer->password = 'Complic4tEd!P4assW0rd'; + $customer->birth_date = '1992-03-08'; + $customer->phone = '0673891160'; + $customer->address = 'rue des totos'; + $customer->city = 'Toto ville'; + $customer->postcode = '72160'; + $customer->country = Country::france(); - // $customer = $api->getCustomerOrNew(static::$info->hashcode); - // $this->assertEquals('toto', $customer->firstname); + $this->assertTrue($customer->save()); - // $customer->country = Country::angola(); - // $customer->save(); + $this->assertEquals('toto', $customer->firstname); + $this->assertEquals(null, $customer->password); + } - // $customer = $api->getCustomerOrNew(static::$info->hashcode); - // $this->assertEquals(Country::angola(), $customer->country); - // } + public function testCustomerStorePropsFails() + { + $this->expectException(ValidationException::class); + $customer = new Customer(); + + $customer->firstname = 'toto'; + $customer->lastname = 'delarogerie'; + $customer->email = 'toto.delarogerie@gmail.com'; + $customer->password = 'simplepassword'; + $customer->birth_date = '1992-03-08'; + $customer->phone = '0673891160'; + $customer->address = 'rue des totos'; + $customer->city = 'Toto ville'; + $customer->postcode = '72160'; + $customer->country = Country::france(); + + $this->assertTrue($customer->save()); + + $this->assertEquals('toto', $customer->firstname); + } + + public function testCustomerDelete() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + + $this->assertTrue($customer->delete()); + + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $this->assertEquals(null, $customer->hashcode); + } } diff --git a/tests/Unit/CrmClientV2SingleOptinTest.php b/tests/Unit/CrmClientV2SingleOptinTest.php new file mode 100644 index 0000000..ad69373 --- /dev/null +++ b/tests/Unit/CrmClientV2SingleOptinTest.php @@ -0,0 +1,39 @@ +assertTrue(SingleOptin::setStatus(static::$customer->email, true)); + } + + public function testCreateSingleOptinShouldFailBecauseCustomerExists() + { + $this->expectException(ApiErrorException::class); + $this->expectExceptionMessage(ApiError::singleOptinCustomerExists()->label); + $customer = new Customer(); + + $customer->firstname = 'toto singleoptin'; + $customer->lastname = 'delarogerie'; + $customer->email = 'totosingleoptin.delarogerie@gmail.com'; + $customer->password = 'Complic4tEd!P4assW0rd'; + $customer->birth_date = '1992-03-08'; + $customer->phone = '0673891160'; + $customer->address = 'rue des totos'; + $customer->city = 'Toto ville'; + $customer->postcode = '72160'; + $customer->country = Country::france(); + + $this->assertTrue($customer->save()); + $this->assertFalse(SingleOptin::setStatus($customer->email, true)); + } +} From 8cea640597750984f1704c920183f3447bf6dc9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 16:06:57 +0200 Subject: [PATCH 033/103] remove broken test hack authenticate --- tests/Unit/CrmClientV2ClientTest.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/Unit/CrmClientV2ClientTest.php b/tests/Unit/CrmClientV2ClientTest.php index 67406ff..c83ac31 100644 --- a/tests/Unit/CrmClientV2ClientTest.php +++ b/tests/Unit/CrmClientV2ClientTest.php @@ -31,12 +31,4 @@ class CrmClientV2ClientTest extends CrmClientTester $api = new CrmApiV2(); $this->assertTrue($api->ping()); } - - public function testApiAuthenticate() - { - $refClass = new ReflectionClass(ApiInterface::class); - $refMeth = $refClass->getMethod('authenticate'); - $refMeth->setAccessible(true); - $this->assertTrue($refMeth->invoke(new ApiInterface())); - } } From e6e8b944092aeeb4bf3c76ea42759d9145c4265a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 6 Aug 2021 16:12:41 +0200 Subject: [PATCH 034/103] phpcs fix --- app/Exceptions/Handler.php | 1 + app/Http/Controllers/CustomerController.php | 18 +++++++++--------- app/Http/Controllers/ServiceController.php | 1 + app/Providers/RouteServiceProvider.php | 3 ++- tests/Feature/ApiV2/CustomerTest.php | 7 ++++--- tests/Unit/CrmClientV2ArcadiaTest.php | 4 ++-- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 84eabe1..3b93ee2 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -86,6 +86,7 @@ class Handler extends ExceptionHandler * * @throws \Throwable */ + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh public function render($request, Throwable $e) { if (Helper::isRequestForApiV2($request) and $request->isJson()) { diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 55387ed..3f9831d 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -29,14 +29,14 @@ class CustomerController extends Controller if ($brand) { $customers = Customer::join('customer_infos', 'customers.id', '=', 'customer_infos.customer_id') ->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([ + '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] @@ -57,7 +57,7 @@ class CustomerController extends Controller 'customer_infos.opt_in', 'customers.email', 'customer_infos.brand_id', - 'customer_logins.last_connection' + 'customer_logins.last_connection' )->where('lastname', 'like', '%' . $texte . '%') ->orWhere('email', 'like', '%' . $texte . '%')->get(); } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 11a4eab..52b40a3 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -44,6 +44,7 @@ class ServiceController extends Controller $bearerPwd = $service->bearer_pwd; $service->saveOrFail(); return back() + // phpcs:ignore Generic.Files.LineLength.TooLong ->with('success', "Nouveau token d'API de $service->name : '$service->bearer_token' et mot de passe '$bearerPwd'"); } } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 67d2595..d80193e 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -51,7 +51,8 @@ class RouteServiceProvider extends ServiceProvider protected function configureRateLimiting() { RateLimiter::for('api', function (Request $request) { - return Limit::perMinute(config('api.apiRequestRatePerMinute'))->by(optional($request->user())->id ?: $request->ip()); + return Limit::perMinute(config('api.apiRequestRatePerMinute')) + ->by(optional($request->user())->id ?: $request->ip()); }); } } diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index 1bbd64b..4fe3800 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -188,9 +188,10 @@ class CustomerTest extends ApiBase ); } } - $response = $this->callApiWithToken($service, 'patch', route('apiV2.customer.update', ['customer' => $info->hashcode]), [ - 'address2' => 'anything' - ]); + $response = $this->callApiWithToken($service, 'patch', route( + 'apiV2.customer.update', + ['customer' => $info->hashcode] + ), ['address2' => 'anything']); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); diff --git a/tests/Unit/CrmClientV2ArcadiaTest.php b/tests/Unit/CrmClientV2ArcadiaTest.php index f5de599..f87a21c 100644 --- a/tests/Unit/CrmClientV2ArcadiaTest.php +++ b/tests/Unit/CrmClientV2ArcadiaTest.php @@ -30,8 +30,8 @@ class CrmClientV2ArcadiaTest extends CrmClientTester public function testArcadiaSendOrderGame() { - $api = new CrmApiV2(); - $customer = $api->getCustomerOrNew(static::$info->hashcode); + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); $arcadiaOrderMail = new ArcadiaMailGame([ 'operation' => 16, 'order_date' => date('Y/m/d H:i:s'), From 46a47e822d6a5f96b6de001e796c5dcae6be8fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 9 Aug 2021 08:55:39 +0200 Subject: [PATCH 035/103] Some php doc --- app/Enums/ApiError.php | 40 +++++++++++++++++-- app/Enums/ApiStatus.php | 3 ++ app/Enums/ArcadiaMailType.php | 3 ++ app/Enums/ArcadiaOrderType.php | 3 ++ app/Enums/Country.php | 3 ++ app/Exceptions/Api/V2/ApiErrorException.php | 22 +++++++++- app/Exceptions/Api/V2/CrmException.php | 8 ++++ .../Controllers/Api/V2/ArcadiaController.php | 3 +- 8 files changed, 79 insertions(+), 6 deletions(-) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 8031b63..85e2e91 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -5,19 +5,55 @@ 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() - * @method static self arcardiaXmlMailFailed() + * 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 + * + * @package LDCCrmClient + * @subpackage enums */ class ApiError extends BaseEnum { @@ -35,7 +71,6 @@ class ApiError extends BaseEnum 'singleOptinCustomerExists' => 120, 'methodNotAllowed' => 150, 'wrongTimeStamp' => 151, - 'arcardiaXmlMailFailed' => 204, 'throttleRequestsException' => 429 ]; } @@ -53,7 +88,6 @@ class ApiError extends BaseEnum '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'), - 'arcardiaXmlMailFailed' => trans('Arcadia XML Creation failed.'), 'throttleRequestsException' => trans('Throttle: too many request have been attempted, please retry later') ]; } diff --git a/app/Enums/ApiStatus.php b/app/Enums/ApiStatus.php index 942f490..7b36759 100644 --- a/app/Enums/ApiStatus.php +++ b/app/Enums/ApiStatus.php @@ -7,6 +7,9 @@ use Kwaadpepper\Enum\BaseEnum; /** * @method static self OK() * @method static self KO() + * + * @package LDCCrmClient + * @subpackage enums */ class ApiStatus extends BaseEnum { diff --git a/app/Enums/ArcadiaMailType.php b/app/Enums/ArcadiaMailType.php index 35fda0b..81696a4 100644 --- a/app/Enums/ArcadiaMailType.php +++ b/app/Enums/ArcadiaMailType.php @@ -7,6 +7,9 @@ use Kwaadpepper\Enum\BaseEnum; /** * @method static self default() * @method static self game() + * + * @package LDCCrmClient + * @subpackage enums */ class ArcadiaMailType extends BaseEnum { diff --git a/app/Enums/ArcadiaOrderType.php b/app/Enums/ArcadiaOrderType.php index c408d08..8228846 100644 --- a/app/Enums/ArcadiaOrderType.php +++ b/app/Enums/ArcadiaOrderType.php @@ -7,6 +7,9 @@ use Kwaadpepper\Enum\BaseEnum; /** * @method static self cb() * @method static self check() + * + * @package LDCCrmClient + * @subpackage enums */ class ArcadiaOrderType extends BaseEnum { diff --git a/app/Enums/Country.php b/app/Enums/Country.php index bd6a505..ee91d04 100644 --- a/app/Enums/Country.php +++ b/app/Enums/Country.php @@ -256,6 +256,9 @@ use Kwaadpepper\Enum\BaseEnum; * @method static self yemen() * @method static self zambia() * @method static self zimbabwe() + * + * @package LDCCrmClient + * @subpackage enums */ class Country extends BaseEnum { diff --git a/app/Exceptions/Api/V2/ApiErrorException.php b/app/Exceptions/Api/V2/ApiErrorException.php index 24c0179..63e839a 100644 --- a/app/Exceptions/Api/V2/ApiErrorException.php +++ b/app/Exceptions/Api/V2/ApiErrorException.php @@ -15,21 +15,39 @@ use Throwable; * 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; - /** @var string */ + /** + * This is an explicit error message, + * ou may print this to the front or not depending + * on $error value + * + * @var string + */ protected $message; - /** @var int */ + /** + * 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) diff --git a/app/Exceptions/Api/V2/CrmException.php b/app/Exceptions/Api/V2/CrmException.php index 3698e71..f720c9f 100644 --- a/app/Exceptions/Api/V2/CrmException.php +++ b/app/Exceptions/Api/V2/CrmException.php @@ -5,6 +5,14 @@ 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() diff --git a/app/Http/Controllers/Api/V2/ArcadiaController.php b/app/Http/Controllers/Api/V2/ArcadiaController.php index 91794b4..c39ea82 100644 --- a/app/Http/Controllers/Api/V2/ArcadiaController.php +++ b/app/Http/Controllers/Api/V2/ArcadiaController.php @@ -6,6 +6,7 @@ use App\Enums\ApiError; use App\Enums\ArcadiaMailType; use App\Enums\ArcadiaOrderType; use App\Exceptions\Api\V2\ApiErrorException; +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; @@ -79,7 +80,7 @@ class ArcadiaController extends Controller } else { throw $e; } - throw new ApiErrorException(ApiError::arcardiaXmlMailFailed()); + throw new CrmException('Failed to create the arcadia xml file', 0, $e); } } From b3f56a5329f42fbef3b9a7a789b7537f3c47f385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 9 Aug 2021 11:40:46 +0200 Subject: [PATCH 036/103] Change password requirement 8 char instead of 10 --- .../Requests/Api/V2/StoreCustomerRequest.php | 54 ++++--------------- .../Requests/Api/V2/UpdateCustomerRequest.php | 2 +- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/app/Http/Requests/Api/V2/StoreCustomerRequest.php b/app/Http/Requests/Api/V2/StoreCustomerRequest.php index 70e4e98..b609b6f 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerRequest.php +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -15,45 +15,22 @@ class StoreCustomerRequest extends BaseApiRequest public function rules(): array { return [ - 'firstname' => [ - 'required', - 'string', - 'min:2' - ], - 'lastname' => [ - 'required', - 'string', - 'min:2' - ], - 'email' => [ - 'required', - 'email:rfc,dns,spoof,filter' - ], + 'firstname' => 'required|string|min:2', + 'lastname' => 'required|string|min:2', + 'email' => 'required|email:rfc,dns,spoof,filter', 'password' => [ 'required', - Password::min(10) + 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' - ], + '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 @@ -63,18 +40,9 @@ class StoreCustomerRequest extends BaseApiRequest } } ], - 'city' => [ - 'required', - 'string' - ], - 'postcode' => [ - 'required', - 'string' - ], - 'opt_in' => [ - 'required', - 'boolean' - ], + 'city' => 'required|string', + 'postcode' => 'required|string', + 'opt_in' => 'required|boolean', ]; } } diff --git a/app/Http/Requests/Api/V2/UpdateCustomerRequest.php b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php index c63a40b..c36a319 100644 --- a/app/Http/Requests/Api/V2/UpdateCustomerRequest.php +++ b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php @@ -33,7 +33,7 @@ class UpdateCustomerRequest extends BaseApiRequest 'lastname' => [$atLeast, 'string', 'min:2'], 'email' => [$atLeast, 'email:rfc,dns,spoof,filter'], 'password' => [ - $atLeast, Password::min(10) + $atLeast, Password::min(8) ->mixedCase() ->letters() ->numbers() From 7d0b86d1341b183d3e1fe853ba07b869468a10b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 9 Aug 2021 17:35:28 +0200 Subject: [PATCH 037/103] Store hashed tokens (bearer and api) instead of clear --- app/Http/Controllers/Api/V2/ApiController.php | 6 +-- app/Http/Controllers/ServiceController.php | 8 +-- app/Http/Middleware/Authenticate.php | 34 ------------ app/Http/Middleware/CheckBrand.php | 10 ++-- app/Http/Middleware/JsonOnly.php | 4 +- app/Models/Service.php | 52 +++++++++++++------ app/Providers/AuthServiceProvider.php | 39 ++++++++++++++ clients/V2 | 2 +- config/auth.php | 11 +--- tests/Feature/ApiV2/ApiBase.php | 7 +-- tests/Feature/ApiV2/ApiTest.php | 3 +- tests/Unit/CrmClientTester.php | 7 +-- 12 files changed, 102 insertions(+), 81 deletions(-) diff --git a/app/Http/Controllers/Api/V2/ApiController.php b/app/Http/Controllers/Api/V2/ApiController.php index ee0c622..6be7a29 100644 --- a/app/Http/Controllers/Api/V2/ApiController.php +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -29,10 +29,8 @@ class ApiController extends Controller throw new ApiErrorException(ApiError::authFailed()); } - $service->generateApiToken(); + $apiToken = $service->generateApiToken(); $service->saveOrFail(); - return response()->json([ - 'api_token' => $service->api_token - ]); + return response()->json(['api_token' => $apiToken]); } } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 52b40a3..2d45863 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -40,11 +40,11 @@ class ServiceController extends Controller public function generateApiToken(Service $service) { - $service->generateBearerToken(); - $bearerPwd = $service->bearer_pwd; + $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 : '$service->bearer_token' et mot de passe '$bearerPwd'"); + // phpcs:ignore Generic.Files.LineLength.TooLong + ->with('success', "Nouveau token d'API de $service->name : '$bearerToken' et mot de passe '$bearerPwd'"); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 70d5318..bacf566 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -28,40 +28,6 @@ class Authenticate extends Middleware public function handle($request, Closure $next, ...$guards) { $isApiV1Request = $request->isJson() && Helper::isRequestForApiV1($request); - $isApiV2Request = $request->isJson() && Helper::isRequestForApiV2($request); - - if ($isApiV2Request) { - // 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)) { - $this->unauthenticated($request, $guards); - } - - /** - ** 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))) { - $this->unauthenticated($request, $guards); - } - - try { - $service = Service::getFromApiToken($apiToken); - $service->authenticateApi($guards, $apiToken); - app()->bind(Service::class, function () use ($service) { - return $service; - }); - } catch (ModelNotFoundException $e) { - throw new AuthenticationException('Unauthenticated.', $guards); - } - } // TODO: check this works if ($isApiV1Request) { diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index b21ce83..cd79b38 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -8,6 +8,7 @@ use App\Models\ServiceStat; use Closure; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; class CheckBrand { @@ -28,21 +29,22 @@ class CheckBrand } else { $this->apiToken = ($request->api_token !== null ? $request->api_token : null); } + print_r($request->all()); if ($this->apiToken === null) { - die("Le token d'authentification est incorrect"); + abort(401, 'Le token d\'authentification est incorrect'); } $brand = Brand::whereHas('services', function (Builder $query) { - $query->where("api_token", hash('sha256', $this->apiToken)); + $query->where("bearer_token", Hash::make($this->apiToken)); })->first(); if (!$brand) { - die("Le token d'authentification est incorrect"); + abort(401, '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->service_id = Service::where('bearer_token', Hash::make($this->apiToken))->first()->id; $serviceStat->nb_calls = $serviceStat->nb_calls === 0 ? 1 : $serviceStat->nb_calls + 1; $serviceStat->save(); diff --git a/app/Http/Middleware/JsonOnly.php b/app/Http/Middleware/JsonOnly.php index 5e1aa19..2930d72 100644 --- a/app/Http/Middleware/JsonOnly.php +++ b/app/Http/Middleware/JsonOnly.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Lib\Helper; use Closure; use Illuminate\Http\Request; @@ -16,7 +17,8 @@ class JsonOnly */ public function handle(Request $request, Closure $next) { - if (!$request->isJson()) { + // TODO reve v1 check when dropped + if (!Helper::isRequestForApiV1($request) and !$request->isJson()) { abort(400); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 61744a4..7716f4b 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,7 +2,6 @@ namespace App\Models; -use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -19,16 +18,28 @@ class Service extends Model protected static function booted() { - static::creating(function ($service) { + 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 ($service) { + 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); + } }); } @@ -75,12 +86,18 @@ class Service extends Model * Get Service from apiToken with its brand * * @param string $apiToken - * @return \App\Models\Service|null + * @return \App\Models\Service * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public static function getFromApiToken(string $apiToken) + public static function getFromApiToken(string $apiToken): self { - return static::where('api_token', $apiToken)->with('brand')->firstOrFail(); + $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'); } /** @@ -90,9 +107,15 @@ class Service extends Model * @return \App\Models\Service * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public static function getFromBearerToken(string $bearerToken) + public static function getFromBearerToken(string $bearerToken): self { - return static::where('bearer_token', $bearerToken)->with('brand')->firstOrFail(); + $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'); } /** @@ -100,17 +123,12 @@ class Service extends Model * * @param array $guards * @param string $apiToken - * @return void - * @throws \Illuminate\Auth\AuthenticationException + * @return bool */ - public function authenticateApi(array $guards, string $apiToken) + public function authenticateApi(string $apiToken): bool { - if ($this->api_token !== $apiToken) { - throw new AuthenticationException('Unauthenticated.', $guards); - } - if ((abs(strtotime($this->api_token_date) - time()) * 60) > (config('api.tokenTimeout') * 60)) { - throw new AuthenticationException('Unauthenticated.', $guards); - } + return Hash::check($apiToken, $this->api_token) and + ((abs(strtotime($this->api_token_date) - time()) * 60) < (config('api.tokenTimeout') * 60)); } /** diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 7cebb24..21af851 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,7 +2,12 @@ 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\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider @@ -23,5 +28,39 @@ class AuthServiceProvider extends ServiceProvider public function boot() { $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); + app()->bind(Service::class, function () use ($service) { + return $service; + }); + $test = $service->authenticateApi($apiToken); + return $test ? $service : null; + } catch (ModelNotFoundException $e) { + return null; + } + }); } } diff --git a/clients/V2 b/clients/V2 index c9de856..5427646 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit c9de85693e56511fc8774b051523dcd9911a908f +Subproject commit 5427646967e5c4e02558b564ba645263e2cb1d07 diff --git a/config/auth.php b/config/auth.php index b153c26..46b56f6 100644 --- a/config/auth.php +++ b/config/auth.php @@ -42,9 +42,7 @@ return [ ], 'api' => [ - 'driver' => 'token', - 'provider' => 'services', - 'hash' => false, // Says that tokens are already hashed in DB + 'driver' => 'api-token' ], ], @@ -69,12 +67,7 @@ return [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, - ], - - 'services' => [ - 'driver' => 'eloquent', - 'model' => App\Models\Service::class, - ], + ] ], /* diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index 493d74b..545d5a6 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -83,10 +83,11 @@ class ApiBase extends BaseTestCase $service = $brand->services()->firstOrFail(); // regen bearer token to get the pwd in clear - $service->generateBearerToken(); - $bearer_pwd = $service->bearer_pwd; + $bearer_token = $service->generateBearerToken(); + $bearer_pwd = $service->bearer_pwd; $service->saveOrFail(); - $service->bearer_pwd = $bearer_pwd; + $service->bearer_token = $bearer_token; + $service->bearer_pwd = $bearer_pwd; // get customer info $info = $customer->info($brand)->firstOrFail(); diff --git a/tests/Feature/ApiV2/ApiTest.php b/tests/Feature/ApiV2/ApiTest.php index 7044f88..940f8b3 100644 --- a/tests/Feature/ApiV2/ApiTest.php +++ b/tests/Feature/ApiV2/ApiTest.php @@ -6,6 +6,7 @@ use App\Enums\ApiError; use App\Enums\ApiStatus; use App\Models\Service; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Illuminate\Support\Facades\Hash; use Tests\CreatesApplication; class ApiTest extends ApiBase @@ -54,6 +55,6 @@ class ApiTest extends ApiBase $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $this->assertNotEmpty($json->json('data')); $this->assertNotEmpty($json->json('data.api_token')); - $this->assertEquals(Service::findOrFail($service->id)->api_token, $json->json('data.api_token')); + $this->assertTrue(Hash::check($json->json('data.api_token'), Service::findOrFail($service->id)->api_token)); } } diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index 0be6290..b2ec773 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -62,10 +62,11 @@ abstract class CrmClientTester extends TestCase $info = $customer->info($brand)->firstOrFail(); // regen bearer token to get the pwd in clear - $service->generateBearerToken(); - $bearer_pwd = $service->bearer_pwd; + $bearer_token = $service->generateBearerToken(); + $bearer_pwd = $service->bearer_pwd; $service->saveOrFail(); - $service->bearer_pwd = $bearer_pwd; + $service->bearer_token = $bearer_token; + $service->bearer_pwd = $bearer_pwd; return compact('customer', 'service', 'brand', 'login', 'info'); } From df15ed1fe51462196c94f7cd0486db40f215e045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 10 Aug 2021 10:20:08 +0200 Subject: [PATCH 038/103] REmove dump code on Login Controller --- app/Http/Controllers/LoginController.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index 5031dbd..f7033f7 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -2,8 +2,6 @@ namespace App\Http\Controllers; -use App\Models\Customer; -use App\Models\Service; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -11,8 +9,6 @@ class LoginController extends Controller { public function index() { - dump(\config('app.env')); - dd(Service::pluck('bearer_token')->toArray()); return view('login'); } From 1566b704a7e2518837cb1b3a71485f40e6452f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 10 Aug 2021 10:20:52 +0200 Subject: [PATCH 039/103] API V1 Test ok + All test OK --- app/Http/Middleware/ApiResponse.php | 4 + app/Http/Middleware/Authenticate.php | 6 +- app/Http/Middleware/CheckBrand.php | 32 +-- app/Models/CustomerLogin.php | 8 +- app/Models/Service.php | 8 +- app/Providers/RouteServiceProvider.php | 1 + clients/{LDCCRM_V1.php => ApiV1.php} | 53 ++-- composer.json | 3 +- tests/Unit/CrmClientTester.php | 6 +- tests/Unit/CrmClientV1CustomerTest.php | 360 +++++++++++++++++++++++++ 10 files changed, 425 insertions(+), 56 deletions(-) rename clients/{LDCCRM_V1.php => ApiV1.php} (84%) create mode 100644 tests/Unit/CrmClientV1CustomerTest.php diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index f74005f..3509de8 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -53,6 +53,10 @@ class ApiResponse 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 diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index bacf566..dafed4a 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -27,12 +27,10 @@ class Authenticate extends Middleware */ public function handle($request, Closure $next, ...$guards) { - $isApiV1Request = $request->isJson() && Helper::isRequestForApiV1($request); - // TODO: check this works - if ($isApiV1Request) { + if (Helper::isRequestForApiV1($request)) { // Use patched v1 Auth mecanism (without bearer reimplementation) - $bearerToken = $request->bearerToken(); + $bearerToken = $request->bearerToken() ?? $request->api_token; $brand = Service::getFromBearerToken($bearerToken)->brand; $request->request->add(['brand_id' => $brand->id]); $response = $next($request); diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index cd79b38..cbe94b1 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -7,14 +7,12 @@ use App\Models\Service; use App\Models\ServiceStat; use Closure; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; class CheckBrand { - - private $apiToken; - /** * Handle an incoming request. * @@ -24,29 +22,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); - } - print_r($request->all()); + $apiToken = $request->bearerToken() ?? $request->api_token; - if ($this->apiToken === null) { - abort(401, 'Le token d\'authentification est incorrect'); - } - $brand = Brand::whereHas('services', function (Builder $query) { - $query->where("bearer_token", Hash::make($this->apiToken)); - })->first(); - - if (!$brand) { - abort(401, 'Le token d\'authentification est incorrect'); + if ($apiToken === null) { + abort(401, 'Le token d\'authentification est incorrect dfd' . \json_encode($request->all())); } - $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); - $serviceStat->brand_id = $brand->id; - $serviceStat->service_id = Service::where('bearer_token', Hash::make($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); diff --git a/app/Models/CustomerLogin.php b/app/Models/CustomerLogin.php index 8b7bfd6..6922714 100644 --- a/app/Models/CustomerLogin.php +++ b/app/Models/CustomerLogin.php @@ -17,10 +17,14 @@ class CustomerLogin extends Model protected static function booted() { static::creating(function ($customerLogin) { - $customerLogin->password = Hash::make(trim($customerLogin->password)); + if (Hash::needsRehash($customerLogin->password)) { + $customerLogin->password = Hash::make(trim($customerLogin->password)); + } }); static::updating(function ($customerLogin) { - $customerLogin->password = Hash::make(trim($customerLogin->password)); + if (Hash::needsRehash($customerLogin->password)) { + $customerLogin->password = Hash::make(trim($customerLogin->password)); + } }); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 7716f4b..c11afb0 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -75,10 +75,12 @@ class Service extends Model */ public function incrementStat(): void { - $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); + $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->service()->associate($this); - $serviceStat->brand()->associate($this->brand); $serviceStat->saveOrFail(); } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index d80193e..cba00ca 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -36,6 +36,7 @@ class RouteServiceProvider extends ServiceProvider }); 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) ?? diff --git a/clients/LDCCRM_V1.php b/clients/ApiV1.php similarity index 84% rename from clients/LDCCRM_V1.php rename to clients/ApiV1.php index 54a351d..d8e23b3 100644 --- a/clients/LDCCRM_V1.php +++ b/clients/ApiV1.php @@ -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); diff --git a/composer.json b/composer.json index 16c6cd7..3180222 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,8 @@ "Database\\Factories\\": "database/factories/", "Database\\migrations\\": "database/migrations/", "Database\\Seeders\\": "database/seeders/", - "LDC\\LDCCRM\\" : "clients/V2/src/" + "LDC\\LDCCRM\\" : "clients/V2/src/", + "LDCCRM\\" : "clients/" } }, "autoload-dev": { diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index b2ec773..f76d422 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -36,10 +36,10 @@ abstract class CrmClientTester extends TestCase self::$service = $service; self::$login = $login; self::$info = $info; - config(['crm.token' => self::$service->bearer_token]); - config(['crm.password' => self::$service->bearer_pwd]); - config(['crm.endpoint' => 'http://127.0.0.25:8888']); } + config(['crm.token' => self::$service->bearer_token]); + config(['crm.password' => self::$service->bearer_pwd]); + config(['crm.endpoint' => 'http://127.0.0.25:8888']); } /** diff --git a/tests/Unit/CrmClientV1CustomerTest.php b/tests/Unit/CrmClientV1CustomerTest.php new file mode 100644 index 0000000..c47b006 --- /dev/null +++ b/tests/Unit/CrmClientV1CustomerTest.php @@ -0,0 +1,360 @@ +getCustomer(static::$customer->id); + + $this->assertEquals($reponse->status, 'OK'); + $this->assertEquals($reponse->user->crm_id, static::$customer->id); + $this->assertEquals($reponse->user->email, static::$customer->email); + $this->assertEquals($reponse->user->firstname, static::$info->firstname); + $this->assertEquals($reponse->user->lastname, static::$info->lastname); + $this->assertEquals($reponse->user->birth_date, static::$info->birth_date); + $this->assertEquals($reponse->user->address, static::$info->address); + $this->assertEquals($reponse->user->address2, static::$info->address2); + $this->assertEquals($reponse->user->phone, static::$info->phone); + $this->assertEquals($reponse->user->country, static::$info->country); + $this->assertEquals($reponse->user->city, static::$info->city); + $this->assertEquals($reponse->user->opt_in, static::$info->opt_in); + $this->assertEquals($reponse->user->hashcode, static::$info->hashcode); + $this->assertEquals($reponse->user->status, static::$login->status); + } + + public function testGetCustomerByIdShouldFail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->getCustomer(9999999); + + $this->assertEquals($reponse->status, 'KO'); + $this->assertEquals($reponse->code, 101); + } + + public function testGetCustomerByEmail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->getCustomerByEmail(static::$customer->email); + + $this->assertEquals($reponse->status, 'OK'); + $this->assertEquals($reponse->user->crm_id, static::$customer->id); + $this->assertEquals($reponse->user->email, static::$customer->email); + $this->assertEquals($reponse->user->firstname, static::$info->firstname); + $this->assertEquals($reponse->user->lastname, static::$info->lastname); + $this->assertEquals($reponse->user->birth_date, static::$info->birth_date); + $this->assertEquals($reponse->user->address, static::$info->address); + $this->assertEquals($reponse->user->address2, static::$info->address2); + $this->assertEquals($reponse->user->phone, static::$info->phone); + $this->assertEquals($reponse->user->country, static::$info->country); + $this->assertEquals($reponse->user->city, static::$info->city); + $this->assertEquals($reponse->user->opt_in, static::$info->opt_in); + $this->assertEquals($reponse->user->hashcode, static::$info->hashcode); + $this->assertEquals($reponse->user->status, static::$login->status); + } + + public function testGetCustomerByEmailShouldFail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->getCustomer('totoo@toto.fr'); + + $this->assertEquals($reponse->status, 'KO'); + $this->assertEquals($reponse->code, 101); + } + + public function testCustomerStoreProps() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->createCustomer( + 'v1toto.delarogerie@gmail.com', + 'Complic4tEd!P4assW0rd', + 'toto', + 'delarogerie', + true, + '1992-03-08', + '0673891160', + 'rue des totos', + '', + 'Toto ville', + '72160', + Country::france()->label + ); + + $this->assertEquals($reponse->status, 'OK'); + $this->assertNotEmpty($reponse->user->crm_id); + $this->assertNotEmpty($reponse->user->hashcode); + $this->assertEquals($reponse->user->email, 'v1toto.delarogerie@gmail.com'); + $this->assertEquals($reponse->user->firstname, 'Toto'); + $this->assertEquals($reponse->user->lastname, 'Delarogerie'); + $this->assertEquals($reponse->user->birth_date, '1992-03-08'); + $this->assertEquals($reponse->user->address, 'rue des totos'); + $this->assertEquals($reponse->user->address2, ''); + $this->assertEquals($reponse->user->phone, '0673891160'); + $this->assertEquals($reponse->user->country, Country::france()->label); + $this->assertEquals($reponse->user->city, 'Toto ville'); + $this->assertEquals($reponse->user->opt_in, true); + $this->assertEquals($reponse->user->status, false); + } + + public function testCustomerStorePropsShouldFailBecauseAlreadyExist() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->createCustomer( + 'v1toto.delarogerie@gmail.com', + 'Complic4tEd!P4assW0rd', + 'toto', + 'delarogerie', + true, + '1992-03-08', + '0673891160', + 'rue des totos', + '', + 'Toto ville', + '72160', + Country::france()->label + ); + + $this->assertEquals($reponse->status, 'KO'); + $this->assertEquals($reponse->code, 103); + } + + public function testCustomerStorePropsShouldFail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->createCustomer( + 'v1bistoto.delarogerie@gmail.com', + '123456', + 'toto bis', + 'delarogerie', + true, + '1992-08', + '0673891160', + 'rue des totos', + '', + 'Toto ville', + '72160', + Country::france()->label + ); + + $this->assertEquals($reponse->status, 'KO'); + $this->assertEquals($reponse->code, 102); + } + + public function testCustomerUpdateProps() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->updateCustomer( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com')->firstOrFail()->id, + 'toto new', + 'delarogerie', + true, + '1992-03-08', + '0673891160', + 'rue des totos', + '', + 'Toto ville', + '72160', + Country::france()->label + ); + + $this->assertEquals($reponse->status, 'OK'); + $this->assertNotEmpty($reponse->user->crm_id); + $this->assertNotEmpty($reponse->user->hashcode); + $this->assertEquals($reponse->user->email, 'v1toto.delarogerie@gmail.com'); + $this->assertEquals($reponse->user->firstname, 'toto new'); + $this->assertEquals($reponse->user->lastname, 'delarogerie'); + $this->assertEquals($reponse->user->birth_date, '1992-03-08'); + $this->assertEquals($reponse->user->address, 'rue des totos'); + $this->assertEquals($reponse->user->address2, ''); + $this->assertEquals($reponse->user->phone, '0673891160'); + $this->assertEquals($reponse->user->country, Country::france()->label); + $this->assertEquals($reponse->user->city, 'Toto ville'); + $this->assertEquals($reponse->user->opt_in, true); + $this->assertEquals($reponse->user->status, false); + } + + public function testCustomerLoginShouldFailDontExists() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->loginCustomer('v1totso.delarogerie@gmail.com', 'Complic4tEd!P4assW0rd'); + + $this->assertEquals('KO', $reponse->status); + $this->assertEquals(119, $reponse->code); + } + + public function testCustomerLoginShouldFailNotActivated() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->loginCustomer('v1toto.delarogerie@gmail.com', 'Complic4tEd!P4assW0rd'); + + $this->assertEquals('KO', $reponse->status); + $this->assertEquals(116, $reponse->code); + } + + public function testCustomeActivate() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->activateCustomer( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com')->firstOrFail()->id + ); + + $this->assertEquals('OK', $reponse->status); + } + + public function testCustomerLogin() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->loginCustomer('v1toto.delarogerie@gmail.com', 'Complic4tEd!P4assW0rd'); + + $this->assertEquals('OK', $reponse->status); + } + + public function testCustomeActivateShouldFail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->activateCustomer( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com')->firstOrFail()->id + ); + + $this->assertEquals('KO', $reponse->status); + $this->assertEquals(110, $reponse->code); + } + + public function testCustomeToggleOptin() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->toggleOptInCustomer( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com')->firstOrFail()->id + ); + + $this->assertEquals('OK', $reponse->status); + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->toggleOptInCustomer( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com')->firstOrFail()->id + ); + + $this->assertEquals('OK', $reponse->status); + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->toggleOptInCustomer( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com')->firstOrFail()->id + ); + + $this->assertEquals('OK', $reponse->status); + } + + public function testCustomeRemoveOptin() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $hashcode = $api->getCustomerByEmail('v1toto.delarogerie@gmail.com')->user->hashcode; + $reponse = $api->removeOptInCustomer($hashcode); + + $this->assertEquals('OK', $reponse->status); + } + + public function testCustomerDelete() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->deleteCustomer(ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com') + ->firstOrFail()->id); + + $this->assertNull($reponse); + } + + public function testCreateSingleOptin() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->createSingleOptin('singlev1toto.delarogerie@gmail.com'); + + $this->assertEquals('OK', $reponse->status); + } + + public function testCreateSingleOptinShouldFail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->createSingleOptin('singlev1toto.delarogerie@gmail.com'); + + $this->assertEquals('KO', $reponse->status); + $this->assertEquals(121, $reponse->code); + } + + public function testCreateArcadiaOutgoingMail() + { + $api = new ApiV1(config('crm.endpoint'), config('crm.token')); + $reponse = $api->ArcadiaOutgoingMail( + ModelsCustomer::where('email', 'v1toto.delarogerie@gmail.com') + ->firstOrFail()->id, + [ + 'root' => [ + [ + 'fieldName' => 'order_num', + //format 9 numeric characters + 'fieldValue' => str_pad(12, 9, '0', \STR_PAD_LEFT), + ], + [ + 'fieldName' => 'order_date', + //format : DD/MM/YYYY + 'fieldValue' => '12/05/1992', + ], + [ + 'fieldName' => 'state_order_cb', + // format 1 ou Null (oui Null et pas NULL) + 'fieldValue' => 1, + ], + [ + 'fieldName' => 'state_order_check', + // format 1 ou Null (oui Null et pas NULL) + 'fieldValue' => 'Null' + ], + [ + 'fieldName' => 'operation', + // mettre valeur dans .env + 'fieldValue' => 16 + ] + ], + 'orderLine' => [ + [ + 'fieldName' => 'package_unit_weight', + 'fieldValue' => 200 + ], + [ + 'fieldName' => 'qty', + // forced to 1 faire un appel par produit et pour chaque quantité + 'fieldValue' => '1' + ], + [ + 'fieldName' => 'support_code', + 'fieldValue' => 2 + ], + [ + 'fieldName' => 'label_type_support', + 'fieldValue' => 'label supoport' + ] + ] + ], + 'default', + [ + "firstname" => 'toto', + "lastname" => 'delarogerie', + "address" => 'rue des toto', + "city" => 'toto ville', + "postcode" => '72000', + "phone" => '0673891160' + ] + ); + + $this->assertEquals('OK', $reponse->status); + } +} From a44ebf47fe1b750c5bc3b3568f9dfcfa05600040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 10 Aug 2021 10:22:19 +0200 Subject: [PATCH 040/103] V2 client update --- clients/V2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/V2 b/clients/V2 index 5427646..eb69ef3 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 5427646967e5c4e02558b564ba645263e2cb1d07 +Subproject commit eb69ef30e9d2512d26c9f2718ef4d7d054dd766b From 6ce462f4fab2d6b98635747215e0283ff33a9730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 10 Aug 2021 12:08:30 +0200 Subject: [PATCH 041/103] phpcs unused imports --- composer.json | 1 + composer.lock | 182 +++++++++++++++++++++++++++++++++++++++++++++++++- phpcs.xml | 1 + 3 files changed, 183 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3180222..0205b55 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "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" }, diff --git a/composer.lock b/composer.lock index 6977a52..92cf5a6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9456db5ed8418e270aa1f66c9438d861", + "content-hash": "005972f2df23e24166a99b57016676df", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -5560,6 +5560,76 @@ } ], "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.7.1", + "source": { + "type": "git", + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c", + "reference": "fe390591e0241955f22eb9ba327d137e501c771c", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "phpcompatibility/php-compatibility": "^9.0", + "sensiolabs/security-checker": "^4.1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", + "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + }, + "time": "2020-12-07T18:04:37+00:00" + }, { "name": "doctrine/instantiator", "version": "1.4.0", @@ -6564,6 +6634,55 @@ }, "time": "2021-03-17T13:42:18+00:00" }, + { + "name": "phpstan/phpdoc-parser", + "version": "0.5.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c", + "reference": "ea0b17460ec38e20d7eb64e7ec49b5d44af5d28c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.87", + "phpstan/phpstan-strict-rules": "^0.12.5", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.5-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/0.5.5" + }, + "time": "2021-06-11T13:24:46+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.6", @@ -7949,6 +8068,67 @@ ], "time": "2020-09-28T06:39:44+00:00" }, + { + "name": "slevomat/coding-standard", + "version": "7.0.13", + "source": { + "type": "git", + "url": "https://github.com/slevomat/coding-standard.git", + "reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/122a9bf9a4a2195f74100f47dfb8375982f43cc9", + "reference": "122a9bf9a4a2195f74100f47dfb8375982f43cc9", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "php": "^7.1 || ^8.0", + "phpstan/phpdoc-parser": "0.5.1 - 0.5.5", + "squizlabs/php_codesniffer": "^3.6.0" + }, + "require-dev": { + "phing/phing": "2.16.4", + "php-parallel-lint/php-parallel-lint": "1.3.0", + "phpstan/phpstan": "0.12.93", + "phpstan/phpstan-deprecation-rules": "0.12.6", + "phpstan/phpstan-phpunit": "0.12.21", + "phpstan/phpstan-strict-rules": "0.12.10", + "phpunit/phpunit": "7.5.20|8.5.5|9.5.7" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "SlevomatCodingStandard\\": "SlevomatCodingStandard" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "support": { + "issues": "https://github.com/slevomat/coding-standard/issues", + "source": "https://github.com/slevomat/coding-standard/tree/7.0.13" + }, + "funding": [ + { + "url": "https://github.com/kukulich", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slevomat/coding-standard", + "type": "tidelift" + } + ], + "time": "2021-07-29T14:30:22+00:00" + }, { "name": "squizlabs/php_codesniffer", "version": "3.6.0", diff --git a/phpcs.xml b/phpcs.xml index 3601517..f24a49f 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -82,6 +82,7 @@ + From 642d3cce088c777e92b03c9ea865d7ee4b96442f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 10 Aug 2021 12:09:49 +0200 Subject: [PATCH 042/103] phpcs rmove unused imports --- app/Enums/QuestionType.php | 25 +++++++++++ .../Controllers/Api/V2/AnswerController.php | 10 +++++ .../Controllers/Api/V2/ArcadiaController.php | 2 - .../Controllers/Api/V2/QuestionController.php | 10 +++++ app/Http/Controllers/BrandController.php | 1 - app/Http/Middleware/ApiResponse.php | 1 - app/Http/Middleware/Authenticate.php | 3 -- app/Http/Middleware/CheckBrand.php | 4 -- app/Http/Resources/Api/V2/ApiResponse.php | 3 -- .../Resources/Api/V2/CustomerResource.php | 2 - app/Models/Answer.php | 35 +++++++++++++++ app/Models/Customer.php | 1 - app/Models/CustomerAnswer.php | 10 +++++ app/Models/Question.php | 43 +++++++++++++++++++ app/Models/User.php | 1 - app/Providers/AuthServiceProvider.php | 1 - app/Providers/EventServiceProvider.php | 1 - app/Providers/RouteServiceProvider.php | 1 - database/factories/AnswerFactory.php | 31 +++++++++++++ database/factories/CustomerLoginFactory.php | 1 - database/factories/QuestionFactory.php | 31 +++++++++++++ ...21_08_10_103745_create_questions_table.php | 34 +++++++++++++++ ...2021_08_10_103757_create_answers_table.php | 33 ++++++++++++++ ...10_104859_create_customer_answer_table.php | 32 ++++++++++++++ database/seeders/DatabaseSeeder.php | 2 - tests/Feature/ApiV2/ArcadiaTest.php | 1 - tests/Unit/CrmClientV1CustomerTest.php | 7 --- tests/Unit/CrmClientV2ClientTest.php | 1 - 28 files changed, 294 insertions(+), 33 deletions(-) create mode 100644 app/Enums/QuestionType.php create mode 100644 app/Http/Controllers/Api/V2/AnswerController.php create mode 100644 app/Http/Controllers/Api/V2/QuestionController.php create mode 100644 app/Models/Answer.php create mode 100644 app/Models/CustomerAnswer.php create mode 100644 app/Models/Question.php create mode 100644 database/factories/AnswerFactory.php create mode 100644 database/factories/QuestionFactory.php create mode 100644 database/migrations/2021_08_10_103745_create_questions_table.php create mode 100644 database/migrations/2021_08_10_103757_create_answers_table.php create mode 100644 database/migrations/2021_08_10_104859_create_customer_answer_table.php diff --git a/app/Enums/QuestionType.php b/app/Enums/QuestionType.php new file mode 100644 index 0000000..da7ef21 --- /dev/null +++ b/app/Enums/QuestionType.php @@ -0,0 +1,25 @@ + 1, + 'radio' => 2, + 'checkbox' => 3 + ]; + } +} diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php new file mode 100644 index 0000000..64bca6d --- /dev/null +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -0,0 +1,10 @@ +belongsTo(Question::class); + } + + /** + * Answer hasOneThrough relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + */ + public function customer() + { + return $this->hasOneThrough(Customer::class, 'customer_answer'); + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php index ffeee64..2a72b53 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -5,7 +5,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Hash; class Customer extends Model { diff --git a/app/Models/CustomerAnswer.php b/app/Models/CustomerAnswer.php new file mode 100644 index 0000000..b613fe1 --- /dev/null +++ b/app/Models/CustomerAnswer.php @@ -0,0 +1,10 @@ + 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); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 95af624..6232f42 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,7 +2,6 @@ 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; diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 21af851..797d739 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -8,7 +8,6 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index cf1e2ab..2c03558 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -5,7 +5,6 @@ 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 { diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index cba00ca..b54ffc7 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,7 +2,6 @@ namespace App\Providers; -use App\Models\Brand; use App\Models\Customer; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; diff --git a/database/factories/AnswerFactory.php b/database/factories/AnswerFactory.php new file mode 100644 index 0000000..3564a18 --- /dev/null +++ b/database/factories/AnswerFactory.php @@ -0,0 +1,31 @@ + $this->faker->name, + 'type' => collect(QuestionType::toArray())->random()->value, + ]; + } +} diff --git a/database/factories/CustomerLoginFactory.php b/database/factories/CustomerLoginFactory.php index d5b8c42..056ff0e 100644 --- a/database/factories/CustomerLoginFactory.php +++ b/database/factories/CustomerLoginFactory.php @@ -4,7 +4,6 @@ namespace Database\Factories; use App\Models\CustomerLogin; use Illuminate\Database\Eloquent\Factories\Factory; -use Illuminate\Support\Facades\Hash; class CustomerLoginFactory extends Factory { diff --git a/database/factories/QuestionFactory.php b/database/factories/QuestionFactory.php new file mode 100644 index 0000000..42843a0 --- /dev/null +++ b/database/factories/QuestionFactory.php @@ -0,0 +1,31 @@ + $this->faker->name, + 'type' => collect(QuestionType::toArray())->random()->value, + ]; + } +} diff --git a/database/migrations/2021_08_10_103745_create_questions_table.php b/database/migrations/2021_08_10_103745_create_questions_table.php new file mode 100644 index 0000000..90ec94e --- /dev/null +++ b/database/migrations/2021_08_10_103745_create_questions_table.php @@ -0,0 +1,34 @@ +id(); + $table->text('question'); + $table->tinyInteger('type'); // QuestionType + $table->foreignId('brand_id')->constrained('brands'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('questions'); + } +} diff --git a/database/migrations/2021_08_10_103757_create_answers_table.php b/database/migrations/2021_08_10_103757_create_answers_table.php new file mode 100644 index 0000000..79d038b --- /dev/null +++ b/database/migrations/2021_08_10_103757_create_answers_table.php @@ -0,0 +1,33 @@ +id(); + $table->text('answer'); + $table->foreignId('question_id')->constrained('questions'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('answers'); + } +} diff --git a/database/migrations/2021_08_10_104859_create_customer_answer_table.php b/database/migrations/2021_08_10_104859_create_customer_answer_table.php new file mode 100644 index 0000000..6124faf --- /dev/null +++ b/database/migrations/2021_08_10_104859_create_customer_answer_table.php @@ -0,0 +1,32 @@ +foreignId('question_id')->constrained('questions'); + $table->foreignId('customer_id')->constrained('customers'); + $table->timestamp('created_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('customer_answer'); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index e584dcf..f5a1b5c 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,8 +2,6 @@ namespace Database\Seeders; -use App\Models\Brand; -use App\Models\Customer; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder diff --git a/tests/Feature/ApiV2/ArcadiaTest.php b/tests/Feature/ApiV2/ArcadiaTest.php index 61c885d..38cdc72 100644 --- a/tests/Feature/ApiV2/ArcadiaTest.php +++ b/tests/Feature/ApiV2/ArcadiaTest.php @@ -3,7 +3,6 @@ namespace Tests\Feature\ApiV2; use App\Enums\ApiStatus; -use App\Enums\ArcadiaMailType; use App\Enums\ArcadiaOrderType; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Str; diff --git a/tests/Unit/CrmClientV1CustomerTest.php b/tests/Unit/CrmClientV1CustomerTest.php index c47b006..df98b1a 100644 --- a/tests/Unit/CrmClientV1CustomerTest.php +++ b/tests/Unit/CrmClientV1CustomerTest.php @@ -3,15 +3,8 @@ namespace Tests\Unit; use App\Models\Customer as ModelsCustomer; -use LDC\LDCCRM\CrmApiV2; -use LDC\LDCCRM\Enums\ApiError; -use LDC\LDCCRM\Enums\ArcadiaOrderType; use LDC\LDCCRM\Enums\Country; -use LDC\LDCCRM\Exceptions\ApiErrorException; -use LDC\LDCCRM\Exceptions\ValidationException; -use LDC\LDCCRM\Models\Customer; use LDCCRM\ApiV1; -use TypeError; class CrmClientV1CustomerTest extends CrmClientTester { diff --git a/tests/Unit/CrmClientV2ClientTest.php b/tests/Unit/CrmClientV2ClientTest.php index c83ac31..9db3b96 100644 --- a/tests/Unit/CrmClientV2ClientTest.php +++ b/tests/Unit/CrmClientV2ClientTest.php @@ -7,7 +7,6 @@ use LDC\LDCCRM\Enums\ApiStatus; use LDC\LDCCRM\Enums\HttpMethods; use LDC\LDCCRM\Transport\ApiInterface; use LDC\LDCCRM\Transport\Request; -use ReflectionClass; class CrmClientV2ClientTest extends CrmClientTester { From 787ee5a4c57b5ad2974e6e9ef9874df97f07bf9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 11 Aug 2021 17:25:24 +0200 Subject: [PATCH 043/103] - tag 2.0.1 - Add Questions segementation - Fix Customer delete Tests --- app/Console/Commands/GenerateCrmClient.php | 4 +- .../Controllers/Api/V2/AnswerController.php | 22 +++++- .../Controllers/Api/V2/ArcadiaController.php | 2 + .../Controllers/Api/V2/CustomerController.php | 32 ++++++-- .../Controllers/Api/V2/QuestionController.php | 63 +++++++++++++++- .../Requests/Api/V2/StoreCustomerAnswers.php | 47 ++++++++++++ app/Http/Resources/Api/V2/AnswersResource.php | 26 +++++++ .../Resources/Api/V2/QuestionResource.php | 34 +++++++++ .../Resources/Api/V2/QuestionsResource.php | 49 ++++++++++++ app/Models/Answer.php | 8 +- app/Models/Brand.php | 10 +++ app/Models/Customer.php | 10 +++ app/Models/CustomerOptInHistory.php | 10 +++ app/Policies/CustomerPolicy.php | 17 +++++ app/Providers/AuthServiceProvider.php | 1 + clients/V2 | 2 +- composer.json | 2 +- database/factories/AnswerFactory.php | 8 +- database/factories/QuestionFactory.php | 2 +- ...10_104859_create_customer_answer_table.php | 2 +- database/seeders/BrandSeeder.php | 10 +++ routes/api.php | 15 +++- tests/Feature/ApiV2/ApiBase.php | 8 +- tests/Feature/ApiV2/ArcadiaTest.php | 2 +- tests/Feature/ApiV2/CustomerTest.php | 34 ++++----- tests/Feature/ApiV2/QuestionTest.php | 72 ++++++++++++++++++ tests/Feature/ApiV2/SingleOptinTest.php | 2 +- tests/Unit/CrmClientV2CustomerTest.php | 4 +- tests/Unit/CrmClientV2QuestionTest.php | 75 +++++++++++++++++++ tests/Unit/CrmClientV2RouterTest.php | 6 ++ 30 files changed, 533 insertions(+), 46 deletions(-) create mode 100644 app/Http/Requests/Api/V2/StoreCustomerAnswers.php create mode 100644 app/Http/Resources/Api/V2/AnswersResource.php create mode 100644 app/Http/Resources/Api/V2/QuestionResource.php create mode 100644 app/Http/Resources/Api/V2/QuestionsResource.php create mode 100644 app/Policies/CustomerPolicy.php create mode 100644 tests/Feature/ApiV2/QuestionTest.php create mode 100644 tests/Unit/CrmClientV2QuestionTest.php diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index 35e1618..a19ceed 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -7,6 +7,7 @@ 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; @@ -106,7 +107,8 @@ class GenerateCrmClient extends Command ApiStatus::class, ArcadiaMailType::class, ArcadiaOrderType::class, - Country::class + Country::class, + QuestionType::class ], 'Enums'); } diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php index 64bca6d..86bda2f 100644 --- a/app/Http/Controllers/Api/V2/AnswerController.php +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -3,8 +3,28 @@ 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(); + } } diff --git a/app/Http/Controllers/Api/V2/ArcadiaController.php b/app/Http/Controllers/Api/V2/ArcadiaController.php index d0be251..a30ba1f 100644 --- a/app/Http/Controllers/Api/V2/ArcadiaController.php +++ b/app/Http/Controllers/Api/V2/ArcadiaController.php @@ -32,11 +32,13 @@ 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); } diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index f8db182..f855456 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -73,6 +73,7 @@ class CustomerController extends Controller */ public function show(Customer $customer, Service $service): JsonResource { + $this->authorize('customerBelongsToService', $customer); return new CustomerResource($service->brand, $customer); } @@ -86,6 +87,7 @@ class CustomerController extends Controller */ 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) { @@ -117,6 +119,7 @@ class CustomerController extends Controller */ 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 */ @@ -124,13 +127,31 @@ class CustomerController extends Controller /** @var \App\Models\CustomerLogin */ $customerLogin = $customer->login($service->brand)->firstOrFail(); - if (!$customerInfo->delete() || !$customerLogin->delete()) { - throw new CrmException(sprintf('Could not delete customer %d for brand %d', $customer, $service->brand)); - } + 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 + )); + }; - if (!$customer->logins()->count() and !$customer->infos()->count()) { + $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)); + throw new CrmException(sprintf( + 'Could not delete customer %d', + $customer->id + )); } } @@ -176,6 +197,7 @@ class CustomerController extends Controller */ public function activateAccount(Customer $customer, Service $service) { + $this->authorize('customerBelongsToService', $customer); $login = $customer->login($service->brand)->firstOrFail(); if ($login->status === 0) { diff --git a/app/Http/Controllers/Api/V2/QuestionController.php b/app/Http/Controllers/Api/V2/QuestionController.php index 9b5d7b4..3d53a6e 100644 --- a/app/Http/Controllers/Api/V2/QuestionController.php +++ b/app/Http/Controllers/Api/V2/QuestionController.php @@ -3,8 +3,69 @@ 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); + } } diff --git a/app/Http/Requests/Api/V2/StoreCustomerAnswers.php b/app/Http/Requests/Api/V2/StoreCustomerAnswers.php new file mode 100644 index 0000000..0a96d18 --- /dev/null +++ b/app/Http/Requests/Api/V2/StoreCustomerAnswers.php @@ -0,0 +1,47 @@ +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"); + } + } + ] + ]; + } +} diff --git a/app/Http/Resources/Api/V2/AnswersResource.php b/app/Http/Resources/Api/V2/AnswersResource.php new file mode 100644 index 0000000..07b32b9 --- /dev/null +++ b/app/Http/Resources/Api/V2/AnswersResource.php @@ -0,0 +1,26 @@ +collection->map(function (Answer $answer) { + return [ + 'id' => $answer->id, + 'answer' => $answer->answer + ]; + }); + } +} diff --git a/app/Http/Resources/Api/V2/QuestionResource.php b/app/Http/Resources/Api/V2/QuestionResource.php new file mode 100644 index 0000000..497a633 --- /dev/null +++ b/app/Http/Resources/Api/V2/QuestionResource.php @@ -0,0 +1,34 @@ +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) + ]; + } +} diff --git a/app/Http/Resources/Api/V2/QuestionsResource.php b/app/Http/Resources/Api/V2/QuestionsResource.php new file mode 100644 index 0000000..a3c64e7 --- /dev/null +++ b/app/Http/Resources/Api/V2/QuestionsResource.php @@ -0,0 +1,49 @@ +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; + }); + } +} diff --git a/app/Models/Answer.php b/app/Models/Answer.php index 58df0f8..f85e234 100644 --- a/app/Models/Answer.php +++ b/app/Models/Answer.php @@ -24,12 +24,12 @@ class Answer extends Model } /** - * Answer hasOneThrough relation + * Answer belongsToMany relation * - * @return \Illuminate\Database\Eloquent\Relations\HasOneThrough + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function customer() + public function customers() { - return $this->hasOneThrough(Customer::class, 'customer_answer'); + return $this->belongsToMany(Customer::class, 'customer_answer'); } } diff --git a/app/Models/Brand.php b/app/Models/Brand.php index 8900982..a628480 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -23,6 +23,16 @@ class Brand extends Model return $this->hasMany(Service::class); } + /** + * Question hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function questions() + { + return $this->hasMany(Question::class); + } + /** * CustomerInfo hasMany relation * diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 2a72b53..935da9c 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -84,6 +84,16 @@ class Customer extends Model 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 diff --git a/app/Models/CustomerOptInHistory.php b/app/Models/CustomerOptInHistory.php index 5228320..6d2ca9a 100644 --- a/app/Models/CustomerOptInHistory.php +++ b/app/Models/CustomerOptInHistory.php @@ -20,4 +20,14 @@ class CustomerOptInHistory extends Model { return $this->belongsTo(Customer::class); } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); + } } diff --git a/app/Policies/CustomerPolicy.php b/app/Policies/CustomerPolicy.php new file mode 100644 index 0000000..e9253b2 --- /dev/null +++ b/app/Policies/CustomerPolicy.php @@ -0,0 +1,17 @@ +login($service->brand)->exists(); + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 797d739..aaddf7d 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -52,6 +52,7 @@ class AuthServiceProvider extends ServiceProvider try { $service = Service::getFromApiToken($apiToken); + $service = $service->load('brand'); app()->bind(Service::class, function () use ($service) { return $service; }); diff --git a/clients/V2 b/clients/V2 index eb69ef3..a336b3b 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit eb69ef30e9d2512d26c9f2718ef4d7d054dd766b +Subproject commit a336b3bfca10f6d383e585d68e80cc85b1b7295d diff --git a/composer.json b/composer.json index 0205b55..399d656 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.0", + "version": "2.0.1", "description": "LDC CRM V2", "authors": [ { diff --git a/database/factories/AnswerFactory.php b/database/factories/AnswerFactory.php index 3564a18..b9dcbd0 100644 --- a/database/factories/AnswerFactory.php +++ b/database/factories/AnswerFactory.php @@ -2,8 +2,7 @@ namespace Database\Factories; -use App\Enums\QuestionType; -use App\Models\Question; +use App\Models\Answer; use Illuminate\Database\Eloquent\Factories\Factory; class AnswerFactory extends Factory @@ -14,7 +13,7 @@ class AnswerFactory extends Factory * * @var string */ - protected $model = Question::class; + protected $model = Answer::class; /** * Define the model's default state. @@ -24,8 +23,7 @@ class AnswerFactory extends Factory public function definition() { return [ - 'question' => $this->faker->name, - 'type' => collect(QuestionType::toArray())->random()->value, + 'answer' => $this->faker->realText(20), ]; } } diff --git a/database/factories/QuestionFactory.php b/database/factories/QuestionFactory.php index 42843a0..7004794 100644 --- a/database/factories/QuestionFactory.php +++ b/database/factories/QuestionFactory.php @@ -24,7 +24,7 @@ class QuestionFactory extends Factory public function definition() { return [ - 'question' => $this->faker->name, + 'question' => $this->faker->realText(40), 'type' => collect(QuestionType::toArray())->random()->value, ]; } diff --git a/database/migrations/2021_08_10_104859_create_customer_answer_table.php b/database/migrations/2021_08_10_104859_create_customer_answer_table.php index 6124faf..6f092c5 100644 --- a/database/migrations/2021_08_10_104859_create_customer_answer_table.php +++ b/database/migrations/2021_08_10_104859_create_customer_answer_table.php @@ -14,7 +14,7 @@ class CreateCustomerAnswerTable extends Migration public function up() { Schema::create('customer_answer', function (Blueprint $table) { - $table->foreignId('question_id')->constrained('questions'); + $table->foreignId('answer_id')->constrained('answers'); $table->foreignId('customer_id')->constrained('customers'); $table->timestamp('created_at')->useCurrent(); }); diff --git a/database/seeders/BrandSeeder.php b/database/seeders/BrandSeeder.php index f4ed875..f71cc8f 100644 --- a/database/seeders/BrandSeeder.php +++ b/database/seeders/BrandSeeder.php @@ -2,7 +2,9 @@ namespace Database\Seeders; +use App\Models\Answer; use App\Models\Brand; +use App\Models\Question; use App\Models\Service; use Illuminate\Database\Seeder; @@ -20,6 +22,14 @@ class BrandSeeder extends Seeder Service::factory(1)->make()->each( /** @var \App\Models\Service $service*/ function ($service) use ($brand) { + Question::factory(10)->make()->each(function ($question) use ($brand) { + $question->brand()->associate($brand); + $question->save(); + Answer::factory(rand(3, 4))->make()->each(function ($answer) use ($question) { + $answer->question()->associate($question); + $answer->save(); + }); + }); $service->brand()->associate($brand); $service->save(); } diff --git a/routes/api.php b/routes/api.php index 7355450..ed014ca 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,6 +7,8 @@ use App\Http\Controllers\Api\V2\ApiController as V2ApiController; use App\Http\Controllers\Api\V2\ArcadiaController as V2ArcadiaController; use App\Http\Controllers\Api\V2\CustomerController as V2CustomerController; use App\Http\Controllers\Api\V2\OptinController as V2OptinController; +use App\Http\Controllers\Api\V2\AnswerController as V2AnswerController; +use App\Http\Controllers\Api\V2\QuestionController as V2QuestionController; /* |-------------------------------------------------------------------------- @@ -43,11 +45,13 @@ Route::prefix('v2')->name('apiV2.')->group(function () { Route::post('/auth', [V2ApiController::class, 'authenticate'])->name('authenticate'); Route::middleware('auth:api')->group(function () { Route::post('/optin', [V2OptinController::class, 'optin'])->name('optin'); - Route::apiResource('customer', V2CustomerController::class)->except(['index']); - Route::prefix('customer')->name('customer.')->group(function () { + Route::apiResource('customers', V2CustomerController::class)->except(['index']); + Route::prefix('customers')->name('customers.')->group(function () { Route::post('/authenticate', [V2CustomerController::class, 'authenticate'])->name('authenticate'); Route::prefix('{customer}')->group(function () { Route::post('/activate', [V2CustomerController::class, 'activateAccount'])->name('activate'); + Route::post('/answers', [V2AnswerController::class, 'storeCustomerAnswers']) + ->name('answers.store'); }); }); Route::prefix('arcadia')->name('arcadia.')->group(function () { @@ -56,5 +60,12 @@ Route::prefix('v2')->name('apiV2.')->group(function () { Route::post('/outgoing-mail/{customer}/game', [V2ArcadiaController::class, 'outgoingMailGame']) ->name('outgoingMailGame'); }); + Route::apiResource('questions', V2QuestionController::class)->only(['index']); + Route::prefix('questions')->name('questions.')->group(function () { + Route::get('/un-answered-by/{customer}', [V2QuestionController::class, 'unAnsweredBy']) + ->name('unAnsweredBy'); + Route::get('/with-answers/{customer}', [V2QuestionController::class, 'indexWithResponse']) + ->name('indexWithResponse'); + }); }); }); diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index 545d5a6..a01c1cc 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -69,12 +69,16 @@ class ApiBase extends BaseTestCase * @param boolean $status the customer status for a brand * @return array */ - protected function getCustomer(bool $status = true): array + protected function getCustomer(bool $status = true, bool $random = true): array { /** @var \App\Models\Costumer $customer */ $customer = Customer::whereHas('logins', function ($q) use ($status) { $q->where('status', $status); - })->inRandomOrder()->firstOrFail(); + }); + if ($random) { + $customer = $customer->inRandomOrder(); + } + $customer = $customer->firstOrFail(); /** @var \App\Models\Login $login */ $login = $customer->logins()->where('status', $status)->firstOrFail(); /** @var \App\Models\Brand $brand */ diff --git a/tests/Feature/ApiV2/ArcadiaTest.php b/tests/Feature/ApiV2/ArcadiaTest.php index 38cdc72..e3aa138 100644 --- a/tests/Feature/ApiV2/ArcadiaTest.php +++ b/tests/Feature/ApiV2/ArcadiaTest.php @@ -14,7 +14,7 @@ class ArcadiaTest extends ApiBase use DatabaseTransactions; /** - * route: apiV2.customer.arcadia.outgoingMail + * route: apiV2.customers.arcadia.outgoingMail */ public function testArcadiaSendOrderDefault() { diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index 4fe3800..a22204d 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -34,7 +34,7 @@ class CustomerTest extends ApiBase } /** - * route: apiV2.customer.authenticate + * route: apiV2.customers.authenticate */ public function testApiAuthenticateCustomer() { @@ -42,7 +42,7 @@ class CustomerTest extends ApiBase $response = $this->callApiWithToken( $service, 'post', - route('apiV2.customer.authenticate', ['customer' => $info->hashcode]), + route('apiV2.customers.authenticate', ['customer' => $info->hashcode]), [ 'email' => $customer->email, 'password' => 'testPassword' @@ -65,7 +65,7 @@ class CustomerTest extends ApiBase } /** - * route: apiV2.customer.store + * route: apiV2.customers.store */ public function testApiStoreCustomer() { @@ -83,7 +83,7 @@ class CustomerTest extends ApiBase 'postcode' => '72160', 'opt_in' => true ]; - $response = $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $response = $this->callApiWithToken($service, 'post', route('apiV2.customers.store'), $data); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); @@ -106,7 +106,7 @@ class CustomerTest extends ApiBase $this->assertEquals($data['city'], $json->json('data.city')); $this->assertEquals($data['postcode'], $json->json('data.postcode')); - $response = $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $response = $this->callApiWithToken($service, 'post', route('apiV2.customers.store'), $data); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); @@ -115,28 +115,28 @@ class CustomerTest extends ApiBase $data['password'] = 'password'; - $response = $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $response = $this->callApiWithToken($service, 'post', route('apiV2.customers.store'), $data); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO()); $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); } /** - * route: apiV2.customer.activate + * route: apiV2.customers.activate */ public function testActivateCustomer() { extract($this->getCustomer(false)); $response = $this->callApiWithToken($service, 'post', route( - 'apiV2.customer.activate', + 'apiV2.customers.activate', ['customer' => $info->hashcode] ), []); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::OK()); $response = $this->callApiWithToken($service, 'post', route( - 'apiV2.customer.activate', + 'apiV2.customers.activate', ['customer' => $info->hashcode] ), []); $json = $response->decodeResponseJson(); @@ -146,7 +146,7 @@ class CustomerTest extends ApiBase } /** - * route: apiV2.customer.update + * route: apiV2.customers.update */ public function testApiUpdateCustomer() { @@ -167,7 +167,7 @@ class CustomerTest extends ApiBase foreach ($data as $rowName => $row) { $response = $this->callApiWithToken($service, 'patch', route( - 'apiV2.customer.update', + 'apiV2.customers.update', ['customer' => $info->hashcode] ), [$rowName => $row]); $json = $response->decodeResponseJson(); @@ -189,7 +189,7 @@ class CustomerTest extends ApiBase } } $response = $this->callApiWithToken($service, 'patch', route( - 'apiV2.customer.update', + 'apiV2.customers.update', ['customer' => $info->hashcode] ), ['address2' => 'anything']); $json = $response->decodeResponseJson(); @@ -198,14 +198,14 @@ class CustomerTest extends ApiBase } /** - * route: apiV2.customer.show + * route: apiV2.customers.show */ public function testApiGetCustomer() { extract($this->getCustomer()); $response = $this->callApiWithToken($service, 'get', route( - 'apiV2.customer.show', + 'apiV2.customers.show', ['customer' => $info->hashcode] )); $json = $response->decodeResponseJson(); @@ -226,7 +226,7 @@ class CustomerTest extends ApiBase $uuid = Str::uuid(); $response = $this->callApiWithToken($service, 'get', route( - 'apiV2.customer.show', + 'apiV2.customers.show', ['customer' => $uuid] )); $json = $response->decodeResponseJson(); @@ -252,7 +252,7 @@ class CustomerTest extends ApiBase extract($this->getCustomer()); $service = $brand->services()->firstOrFail(); $response = $this->callApiWithToken($service, 'delete', route( - 'apiV2.customer.destroy', + 'apiV2.customers.destroy', ['customer' => $info->hashcode] )); $json = $response->decodeResponseJson(); @@ -260,7 +260,7 @@ class CustomerTest extends ApiBase $this->assertEmpty($json->json('data')); $response = $this->callApiWithToken($service, 'get', route( - 'apiV2.customer.show', + 'apiV2.customers.show', ['customer' => $info->hashcode] )); $json = $response->decodeResponseJson(); diff --git a/tests/Feature/ApiV2/QuestionTest.php b/tests/Feature/ApiV2/QuestionTest.php new file mode 100644 index 0000000..4e072db --- /dev/null +++ b/tests/Feature/ApiV2/QuestionTest.php @@ -0,0 +1,72 @@ +getCustomer()); + + $response = $this->callApiWithToken($service, 'get', route('apiV2.questions.index')); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } + + /** + * route: apiV2.questions.indexWithResponse + */ + public function testQuestionIndexWithAnswers() + { + extract($this->getCustomer(true, false)); + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.questions.indexWithResponse', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } + + /** + * route: apiV2.questions.unAnsweredBy + */ + public function testCustomerUnAnsweredIndex() + { + extract($this->getCustomer()); + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.questions.unAnsweredBy', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } + + /** + * route: apiV2.customers.answers.store + */ + public function testQuestionSetAnswer() + { + extract($this->getCustomer(true, false)); + $question = Question::whereHas('brand', function ($q) use ($brand) { + $q->whereId($brand->id); + })->inRandomOrder()->firstOrFail(); + $answer = $question->answers->first(); + $response = $this->callApiWithToken($service, 'post', route('apiV2.customers.answers.store', [ + 'customer' => $info->hashcode + ]), [ + 'question' => $question->id, + 'answers' => [$answer->id] + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } +} diff --git a/tests/Feature/ApiV2/SingleOptinTest.php b/tests/Feature/ApiV2/SingleOptinTest.php index 6ac1e4c..2b8a035 100644 --- a/tests/Feature/ApiV2/SingleOptinTest.php +++ b/tests/Feature/ApiV2/SingleOptinTest.php @@ -68,6 +68,6 @@ class SingleOptinTest extends ApiBase 'postcode' => '72160', 'opt_in' => true ]; - $this->callApiWithToken($service, 'post', route('apiV2.customer.store'), $data); + $this->callApiWithToken($service, 'post', route('apiV2.customers.store'), $data); } } diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 0bbe892..f2a2c9a 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -251,11 +251,11 @@ class CrmClientV2CustomerTest extends CrmClientTester public function testCustomerDelete() { $api = new CrmApiV2(); - $customer = $api->getCustomerOrNew(static::$info->hashcode); + $customer = $api->getCustomer('toto.delarogerie@gmail.com'); $this->assertTrue($customer->delete()); - $customer = $api->getCustomerOrNew(static::$info->hashcode); + $customer = $api->getCustomerOrNew('toto.delarogerie@gmail.com'); $this->assertEquals(null, $customer->hashcode); } } diff --git a/tests/Unit/CrmClientV2QuestionTest.php b/tests/Unit/CrmClientV2QuestionTest.php new file mode 100644 index 0000000..1b40289 --- /dev/null +++ b/tests/Unit/CrmClientV2QuestionTest.php @@ -0,0 +1,75 @@ +assertNotEmpty($questions, 'Should have returned some questions'); + } + + public function testGetAllQuestionsWithCustomerAnswers() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $questions = Question::allWithCustomerAnswers($customer); + $this->assertNotEmpty($questions, 'Should have returned some questions'); + } + + public function testGetAllCustomerUnAnsweredQuestions() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $questions = Question::unansweredBy($customer); + $this->assertNotEmpty($questions, 'Should have returned some questions'); + } + + public function testSubmitCustomerAnswer() + { + $questions = Question::all(); + + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $result = Answer::setQuestionAnswers( + $customer->hashcode, + $questions[0]->id, + [ + $questions[0]->answers[0]->id, + $questions[0]->answers[1]->id + ] + ); + $this->assertTrue($result); + } + + public function testGetAllCustomerUnansweredQuestionsShouldNotContainFirst() + { + $this->expectNotToPerformAssertions(); + $unWantedQuestion = Question::all()[0]; + + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $questions = Question::unansweredBy($customer); + + foreach ($questions as $question) { + if ($unWantedQuestion->id === $question->id) { + throw new Exception('The question should not appear'); + } + } + } + + public function testGetAllQuestionsWithCustomerAnswersShouldHaveAnswers() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $questions = Question::allWithCustomerAnswers($customer); + $this->assertNotEmpty($questions[0]['customerAnswers'], 'Should have customer answers'); + } +} diff --git a/tests/Unit/CrmClientV2RouterTest.php b/tests/Unit/CrmClientV2RouterTest.php index 7059f01..c4e9321 100644 --- a/tests/Unit/CrmClientV2RouterTest.php +++ b/tests/Unit/CrmClientV2RouterTest.php @@ -36,6 +36,12 @@ class CrmClientV2RouterTest extends CrmClientTester if (\strpos($route['uri'], '{customer}') !== false) { $query['customer'] = 1; } + if (\strpos($route['uri'], '{question}') !== false) { + $query['question'] = 1; + } + if (\strpos($route['uri'], '{answer}') !== false) { + $query['answer'] = 1; + } $request = Utils::getResquest($name, $query, $data); $request->uri; $request->method; From 119aa76e73323b3296485ded0b36a2e618da7e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 11 Aug 2021 17:48:28 +0200 Subject: [PATCH 044/103] WPCRM fix phpcs issues --- clients/WPCrm.php | 83 ++++++++++++++++++++++--------------------- clients/WPCrmUser.php | 11 +++--- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/clients/WPCrm.php b/clients/WPCrm.php index 4c64528..10a16c5 100644 --- a/clients/WPCrm.php +++ b/clients/WPCrm.php @@ -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; } diff --git a/clients/WPCrmUser.php b/clients/WPCrmUser.php index f2e2f73..73c3fa6 100644 --- a/clients/WPCrmUser.php +++ b/clients/WPCrmUser.php @@ -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; } From a472e827683bdb9511c2aaceb00f52fdda96307b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 08:39:51 +0200 Subject: [PATCH 045/103] Customer login test --- tests/Feature/ApiV2/CustomerTest.php | 2 +- tests/Unit/CrmClientV2CustomerTest.php | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index a22204d..2fc930a 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -42,7 +42,7 @@ class CustomerTest extends ApiBase $response = $this->callApiWithToken( $service, 'post', - route('apiV2.customers.authenticate', ['customer' => $info->hashcode]), + route('apiV2.customers.authenticate', []), [ 'email' => $customer->email, 'password' => 'testPassword' diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index f2a2c9a..4ed021a 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -227,6 +227,17 @@ class CrmClientV2CustomerTest extends CrmClientTester $this->assertEquals(null, $customer->password); } + + public function testCustomerLogin() + { + $api = new CrmApiV2(); + $customer = $api->getCustomerOrNew('toto.delarogerie@gmail.com'); + $this->assertTrue($customer->activate()); + $this->assertTrue($customer->status); + + $this->assertTrue(Customer::login($customer->email, 'Complic4tEd!P4assW0rd')); + } + public function testCustomerStorePropsFails() { $this->expectException(ValidationException::class); From 899140445e2a2823bbde5b9cce6c4201164731f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 08:56:35 +0200 Subject: [PATCH 046/103] Add AuthorizationException handling --- app/Enums/ApiError.php | 9 +++++++++ app/Exceptions/Handler.php | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 85e2e91..2b20851 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -51,6 +51,13 @@ use Kwaadpepper\Enum\BaseEnum; * 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 @@ -71,6 +78,7 @@ class ApiError extends BaseEnum 'singleOptinCustomerExists' => 120, 'methodNotAllowed' => 150, 'wrongTimeStamp' => 151, + 'authorizationException' => 403, 'throttleRequestsException' => 429 ]; } @@ -88,6 +96,7 @@ class ApiError extends BaseEnum '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') ]; } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 3b93ee2..203bbed 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -9,6 +9,7 @@ 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; @@ -143,6 +144,14 @@ class Handler extends ExceptionHandler 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') { From 68796d21015f30f9c80a89dcb60b00aba1968bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 14:50:15 +0200 Subject: [PATCH 047/103] Client V2 add WP CRM --- clients/V2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/V2 b/clients/V2 index a336b3b..60af192 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit a336b3bfca10f6d383e585d68e80cc85b1b7295d +Subproject commit 60af19232410dedf07bfe1049599dc4e1b4ad424 From 67a0efd133c8bbabae4b562711d2a5b2b20a352c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 14:53:36 +0200 Subject: [PATCH 048/103] bump version to 2.0.2 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 60af192..73a9d09 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 60af19232410dedf07bfe1049599dc4e1b4ad424 +Subproject commit 73a9d09ecdb17833e41a8868436938ab12629601 diff --git a/composer.json b/composer.json index 399d656..fc0836b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.1", + "version": "2.0.12", "description": "LDC CRM V2", "authors": [ { From aaab3bcf3c51a4d8070dda581f9faa47ff2d1922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 15:25:34 +0200 Subject: [PATCH 049/103] Migrate V1 Api keys by hashing them --- ...152220_hash_api_keys_in_services_table.php | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php diff --git a/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php b/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php new file mode 100644 index 0000000..fc9eb95 --- /dev/null +++ b/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php @@ -0,0 +1,30 @@ +bearer_token = Hash::make($service->bearer_token); + $service->saveOrFail(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + } +} From 16d7bee469bf798cdafd81addabaacfb9fb2e221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 15:56:13 +0200 Subject: [PATCH 050/103] Fix api response middleware for API V1 --- app/Http/Middleware/ApiResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index cc11d52..1567436 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -31,7 +31,7 @@ class ApiResponse try { if ($isApiV1Request) { // Use patched v1 Auth mecanism (without bearer reimplementation) - $bearerToken = $request->bearerToken(); + $bearerToken = $request->bearerToken() ?? $request->api_token; $service = Service::getFromBearerToken($bearerToken); $service->incrementStat(); } From 7313af0104166266d573ff30272d22e60989e937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 12 Aug 2021 15:58:33 +0200 Subject: [PATCH 051/103] fix CRM service hash migration --- .../2021_08_12_152220_hash_api_keys_in_services_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php b/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php index fc9eb95..2e683c4 100644 --- a/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php +++ b/database/migrations/2021_08_12_152220_hash_api_keys_in_services_table.php @@ -15,7 +15,7 @@ class HashApiKeysInServicesTable extends Migration { foreach (Service::all() as $service) { $service->bearer_token = Hash::make($service->bearer_token); - $service->saveOrFail(); + $service->saveQuietly(); } } From dd59621647ec30dd620b2625be8d0e31c8ef4611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 10:27:41 +0200 Subject: [PATCH 052/103] Client set to 2.0.3 add composer respect/validation --- clients/V2 | 2 +- composer.json | 1 + composer.lock | 126 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 73a9d09..73d006c 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 73a9d09ecdb17833e41a8868436938ab12629601 +Subproject commit 73d006cc29d9fd26bbedea8b261b8fd2564d0514 diff --git a/composer.json b/composer.json index fc0836b..5f673d9 100644 --- a/composer.json +++ b/composer.json @@ -41,6 +41,7 @@ "mockery/mockery": "^1.4.2", "nunomaduro/collision": "^5.0", "phpunit/phpunit": "^9.3.3", + "respect/validation": "^2.2", "slevomat/coding-standard": "^7.0", "squizlabs/php_codesniffer": "^3.5", "symfony/process": "^5.3" diff --git a/composer.lock b/composer.lock index 92cf5a6..0cc4db8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "005972f2df23e24166a99b57016676df", + "content-hash": "c9352099241e2b27a8716d0645f001ec", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -7104,6 +7104,130 @@ ], "time": "2021-07-31T15:17:34+00:00" }, + { + "name": "respect/stringifier", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/Respect/Stringifier.git", + "reference": "e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Respect/Stringifier/zipball/e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59", + "reference": "e55af3c8aeaeaa2abb5fa47a58a8e9688cc23b59", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.8", + "malukenho/docheader": "^0.1.7", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Respect\\Stringifier\\": "src/" + }, + "files": [ + "src/stringify.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Respect/Stringifier Contributors", + "homepage": "https://github.com/Respect/Stringifier/graphs/contributors" + } + ], + "description": "Converts any value to a string", + "homepage": "http://respect.github.io/Stringifier/", + "keywords": [ + "respect", + "stringifier", + "stringify" + ], + "support": { + "issues": "https://github.com/Respect/Stringifier/issues", + "source": "https://github.com/Respect/Stringifier/tree/0.2.0" + }, + "time": "2017-12-29T19:39:25+00:00" + }, + { + "name": "respect/validation", + "version": "2.2.3", + "source": { + "type": "git", + "url": "https://github.com/Respect/Validation.git", + "reference": "4c21a7ffc9a4915673cb2c2843963919e664e627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Respect/Validation/zipball/4c21a7ffc9a4915673cb2c2843963919e664e627", + "reference": "4c21a7ffc9a4915673cb2c2843963919e664e627", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8.0", + "respect/stringifier": "^0.2.0", + "symfony/polyfill-mbstring": "^1.2" + }, + "require-dev": { + "egulias/email-validator": "^3.0", + "malukenho/docheader": "^0.1", + "mikey179/vfsstream": "^1.6", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^9.3", + "psr/http-message": "^1.0", + "respect/coding-standard": "^3.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/validator": "^3.0||^4.0", + "zendframework/zend-validator": "^2.1" + }, + "suggest": { + "egulias/email-validator": "Strict (RFC compliant) email validation", + "ext-bcmath": "Arbitrary Precision Mathematics", + "ext-fileinfo": "File Information", + "ext-mbstring": "Multibyte String Functions", + "symfony/validator": "Use Symfony validator through Respect\\Validation", + "zendframework/zend-validator": "Use Zend Framework validator through Respect\\Validation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Respect\\Validation\\": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Respect/Validation Contributors", + "homepage": "https://github.com/Respect/Validation/graphs/contributors" + } + ], + "description": "The most awesome validation engine ever created for PHP", + "homepage": "http://respect.github.io/Validation/", + "keywords": [ + "respect", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/Respect/Validation/issues", + "source": "https://github.com/Respect/Validation/tree/2.2.3" + }, + "time": "2021-03-19T14:12:45+00:00" + }, { "name": "sebastian/cli-parser", "version": "1.0.1", From 320f7bc3b9f42efd50dffb631c28a079ec6277a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 10:58:08 +0200 Subject: [PATCH 053/103] Client set to 2.0.4 bump version to 2.0.4 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 73d006c..aeb8fd1 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 73d006cc29d9fd26bbedea8b261b8fd2564d0514 +Subproject commit aeb8fd1f286c7bf75a6854ce0cb4e0d97d0b715e diff --git a/composer.json b/composer.json index 5f673d9..4a247ab 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.12", + "version": "2.0.4", "description": "LDC CRM V2", "authors": [ { From f170e736606a4af9b3f8f05b81b5bbd60c0b9220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 11:08:25 +0200 Subject: [PATCH 054/103] bump client to 2.0.5 and version to 2.0.5 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index aeb8fd1..2a78f71 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit aeb8fd1f286c7bf75a6854ce0cb4e0d97d0b715e +Subproject commit 2a78f71d5f46851ea1236ec4ecad93e5c1c0a683 diff --git a/composer.json b/composer.json index 4a247ab..d96e3fe 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.4", + "version": "2.0.5", "description": "LDC CRM V2", "authors": [ { From 420e56097c157d5479917f8e051eda7ec6ddced6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 12:20:02 +0200 Subject: [PATCH 055/103] bump to kwaadpepper/enum 2.0.0 --- app/Enums/ApiError.php | 4 ++-- app/Enums/ApiStatus.php | 4 ++-- app/Enums/ArcadiaMailType.php | 4 ++-- app/Enums/ArcadiaOrderType.php | 4 ++-- app/Enums/Country.php | 4 ++-- app/Enums/QuestionType.php | 4 ++-- clients/V2 | 2 +- composer.json | 2 +- composer.lock | 18 +++++++----------- 9 files changed, 21 insertions(+), 25 deletions(-) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 2b20851..38f710b 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -2,7 +2,7 @@ namespace App\Enums; -use Kwaadpepper\Enum\BaseEnum; +use Kwaadpepper\Enum\BaseEnumRoutable; /** * This describes an API error. @@ -62,7 +62,7 @@ use Kwaadpepper\Enum\BaseEnum; * @package LDCCrmClient * @subpackage enums */ -class ApiError extends BaseEnum +class ApiError extends BaseEnumRoutable { public static function values(): array { diff --git a/app/Enums/ApiStatus.php b/app/Enums/ApiStatus.php index 7b36759..c6a87a9 100644 --- a/app/Enums/ApiStatus.php +++ b/app/Enums/ApiStatus.php @@ -2,7 +2,7 @@ namespace App\Enums; -use Kwaadpepper\Enum\BaseEnum; +use Kwaadpepper\Enum\BaseEnumRoutable; /** * @method static self OK() @@ -11,6 +11,6 @@ use Kwaadpepper\Enum\BaseEnum; * @package LDCCrmClient * @subpackage enums */ -class ApiStatus extends BaseEnum +class ApiStatus extends BaseEnumRoutable { } diff --git a/app/Enums/ArcadiaMailType.php b/app/Enums/ArcadiaMailType.php index 81696a4..b56000b 100644 --- a/app/Enums/ArcadiaMailType.php +++ b/app/Enums/ArcadiaMailType.php @@ -2,7 +2,7 @@ namespace App\Enums; -use Kwaadpepper\Enum\BaseEnum; +use Kwaadpepper\Enum\BaseEnumRoutable; /** * @method static self default() @@ -11,7 +11,7 @@ use Kwaadpepper\Enum\BaseEnum; * @package LDCCrmClient * @subpackage enums */ -class ArcadiaMailType extends BaseEnum +class ArcadiaMailType extends BaseEnumRoutable { public static function values(): array { diff --git a/app/Enums/ArcadiaOrderType.php b/app/Enums/ArcadiaOrderType.php index 8228846..5ff85ec 100644 --- a/app/Enums/ArcadiaOrderType.php +++ b/app/Enums/ArcadiaOrderType.php @@ -2,7 +2,7 @@ namespace App\Enums; -use Kwaadpepper\Enum\BaseEnum; +use Kwaadpepper\Enum\BaseEnumRoutable; /** * @method static self cb() @@ -11,7 +11,7 @@ use Kwaadpepper\Enum\BaseEnum; * @package LDCCrmClient * @subpackage enums */ -class ArcadiaOrderType extends BaseEnum +class ArcadiaOrderType extends BaseEnumRoutable { public static function values(): array { diff --git a/app/Enums/Country.php b/app/Enums/Country.php index ee91d04..26d27a2 100644 --- a/app/Enums/Country.php +++ b/app/Enums/Country.php @@ -2,7 +2,7 @@ namespace App\Enums; -use Kwaadpepper\Enum\BaseEnum; +use Kwaadpepper\Enum\BaseEnumRoutable; /** * Liste de Pays @@ -260,7 +260,7 @@ use Kwaadpepper\Enum\BaseEnum; * @package LDCCrmClient * @subpackage enums */ -class Country extends BaseEnum +class Country extends BaseEnumRoutable { public static function values(): array { diff --git a/app/Enums/QuestionType.php b/app/Enums/QuestionType.php index da7ef21..0fb24d4 100644 --- a/app/Enums/QuestionType.php +++ b/app/Enums/QuestionType.php @@ -2,7 +2,7 @@ namespace App\Enums; -use Kwaadpepper\Enum\BaseEnum; +use Kwaadpepper\Enum\BaseEnumRoutable; /** * @method static self select() @@ -12,7 +12,7 @@ use Kwaadpepper\Enum\BaseEnum; * @package LDCCrmClient * @subpackage enums */ -class QuestionType extends BaseEnum +class QuestionType extends BaseEnumRoutable { public static function values(): array { diff --git a/clients/V2 b/clients/V2 index 2a78f71..d24052f 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 2a78f71d5f46851ea1236ec4ecad93e5c1c0a683 +Subproject commit d24052f5436fbcf8049d4136bb5359396ec0ab65 diff --git a/composer.json b/composer.json index d96e3fe..a29cbef 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "fideloper/proxy": "^4.4", "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "^7.0.1", - "kwaadpepper/enum": "^0.1", + "kwaadpepper/enum": "^2.0", "laravel-lang/lang": "~7.0", "laravel/framework": "^8.39", "laravel/tinker": "^2.5" diff --git a/composer.lock b/composer.lock index 0cc4db8..9c61fb4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c9352099241e2b27a8716d0645f001ec", + "content-hash": "aa896812fae801e5d109364dffe99068", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -1345,22 +1345,18 @@ }, { "name": "kwaadpepper/enum", - "version": "0.1.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/Kwaadpepper/enum.git", - "reference": "f9e2b960d86ceea45c905f11c0c5699529e32848" + "reference": "036cc602c05d9b2d141def57620f73d7190a60f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Kwaadpepper/enum/zipball/f9e2b960d86ceea45c905f11c0c5699529e32848", - "reference": "f9e2b960d86ceea45c905f11c0c5699529e32848", + "url": "https://api.github.com/repos/Kwaadpepper/enum/zipball/036cc602c05d9b2d141def57620f73d7190a60f2", + "reference": "036cc602c05d9b2d141def57620f73d7190a60f2", "shasum": "" }, - "require": { - "illuminate/support": "~7|~8", - "laravel/framework": "~7|~8" - }, "require-dev": { "orchestra/testbench": "^6.13", "phpunit/phpunit": "~9.0", @@ -1398,9 +1394,9 @@ ], "support": { "issues": "https://github.com/Kwaadpepper/enum/issues", - "source": "https://github.com/Kwaadpepper/enum/tree/0.1.4" + "source": "https://github.com/Kwaadpepper/enum/tree/2.0.0" }, - "time": "2021-04-06T14:35:59+00:00" + "time": "2021-08-13T10:11:43+00:00" }, { "name": "laravel-lang/lang", From aa0f43c2b3f974e315a79f7845bda1d7450ea8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 12:26:25 +0200 Subject: [PATCH 056/103] bump to 2.0.7 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index d24052f..a80e985 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit d24052f5436fbcf8049d4136bb5359396ec0ab65 +Subproject commit a80e9853829b77955ddc91b250c4d79d2a9444af diff --git a/composer.json b/composer.json index a29cbef..7953511 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.5", + "version": "2.0.7", "description": "LDC CRM V2", "authors": [ { From 33316695418196b75694dd2ba6d0fd9df3ec4ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 12:34:09 +0200 Subject: [PATCH 057/103] fix genreate client --- app/Console/Commands/GenerateCrmClient.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index a19ceed..8e182d8 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -102,14 +102,21 @@ class GenerateCrmClient extends Command private function copyEnums(): void { $this->info('Copy Enums'); - $this->copyToFolder([ + $classes = [ ApiError::class, ApiStatus::class, ArcadiaMailType::class, ArcadiaOrderType::class, Country::class, QuestionType::class - ], 'Enums'); + ]; + $this->copyToFolder($classes, 'Enums'); + $rootTarget = app_path('../' . self::TARGET); + foreach ($classes as $class) { + $classBasename = class_basename($class); + $dest = "$rootTarget/Enums/$classBasename.php"; + $this->replaceInPhpFile($dest, 'BaseEnumRoutable', 'BaseEnum'); + } } private function copyTranslations() From 97e586f21038fd043dc087bdbe932b5ad14935bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 13 Aug 2021 12:34:30 +0200 Subject: [PATCH 058/103] bump version to 2.0.8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7953511..c4a45ec 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.7", + "version": "2.0.8", "description": "LDC CRM V2", "authors": [ { From 03e7f592858d53bd6026cb1b8b34095a3536c7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 08:08:48 +0200 Subject: [PATCH 059/103] Remove translate in enums --- app/Enums/ApiError.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 38f710b..9a386e5 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -86,18 +86,18 @@ class ApiError extends BaseEnumRoutable 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') + 'modelNotFound' => '%s not found.', + 'validationFailed' => 'Could not validate data.', + 'customerExists' => 'User already registered on this site.', + 'customerNotActivated' => 'Account is not activated.', + 'customerAlreadyActive' => 'User already active.', + 'singleOptinCustomerExists' => 'SingleOptin wont be added because customer exists.', + 'authFailed' => 'Authentication failed.', + 'needAuth' => 'You need to be authenticated', + 'methodNotAllowed' => 'This method is not allowed for this request', + 'wrongTimeStamp' => 'Your request is malformed, please check timestamp header and parameter', + 'authorizationException' => 'You are not autorized to access this', + 'throttleRequestsException' => 'Throttle: too many request have been attempted, please retry later' ]; } } From 92e01130692d00b9249dca82b956b890e4609f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 08:09:06 +0200 Subject: [PATCH 060/103] bump and update client to 2.0.9 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index a80e985..951cfc5 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit a80e9853829b77955ddc91b250c4d79d2a9444af +Subproject commit 951cfc59edb58768e47648622548fab2b3932a0a diff --git a/composer.json b/composer.json index c4a45ec..b78c45b 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.8", + "version": "2.0.9", "description": "LDC CRM V2", "authors": [ { From 3383402b4ebf7413a8628098512c224464cca97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 13:18:31 +0200 Subject: [PATCH 061/103] Translate Api Errors labels to FR --- app/Enums/ApiError.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 9a386e5..61b667b 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -86,14 +86,14 @@ class ApiError extends BaseEnumRoutable public static function labels(): array { return [ - 'modelNotFound' => '%s not found.', - 'validationFailed' => 'Could not validate data.', - 'customerExists' => 'User already registered on this site.', - 'customerNotActivated' => 'Account is not activated.', - 'customerAlreadyActive' => 'User already active.', - 'singleOptinCustomerExists' => 'SingleOptin wont be added because customer exists.', - 'authFailed' => 'Authentication failed.', - 'needAuth' => 'You need to be authenticated', + 'modelNotFound' => '%s introuvable.', + 'validationFailed' => 'Impossible de valider les données', + 'customerExists' => 'L\'utilisateur est déjà enregistré sur ce site', + 'customerNotActivated' => 'Le comtpe n\'est pas activé', + 'customerAlreadyActive' => 'Le compte est déjà activé', + 'singleOptinCustomerExists' => 'Le signle optin ne peut être ajouté car un compte existe.', + 'authFailed' => 'Echec de l\'authentification', + 'needAuth' => 'Vous devez être authentifié', 'methodNotAllowed' => 'This method is not allowed for this request', 'wrongTimeStamp' => 'Your request is malformed, please check timestamp header and parameter', 'authorizationException' => 'You are not autorized to access this', From e3f7a7ebea6c8b34379f7333afe3c5f8c9bef425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 13:20:03 +0200 Subject: [PATCH 062/103] handle empty api_token on getFromApiToken, getFromBearerToken and authenticateApi --- app/Models/Service.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Models/Service.php b/app/Models/Service.php index c11afb0..7d25c4a 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -95,7 +95,7 @@ class Service extends Model { $services = static::all(); foreach ($services as $service) { - if (Hash::check($apiToken, $service->api_token)) { + if ($service->api_token and Hash::check($apiToken, $service->api_token)) { return $service; } } @@ -113,7 +113,7 @@ class Service extends Model { $services = static::all(); foreach ($services as $service) { - if (Hash::check($bearerToken, $service->bearer_token)) { + if ($service->bearer_token and Hash::check($bearerToken, $service->bearer_token)) { return $service; } } @@ -129,7 +129,7 @@ class Service extends Model */ public function authenticateApi(string $apiToken): bool { - return Hash::check($apiToken, $this->api_token) and + return $this->api_token and Hash::check($apiToken, $this->api_token) and ((abs(strtotime($this->api_token_date) - time()) * 60) < (config('api.tokenTimeout') * 60)); } From c97abfb253c58d9414b0081869d13426cb43ddb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 13:20:26 +0200 Subject: [PATCH 063/103] Store customer, remove required from address field --- app/Http/Requests/Api/V2/StoreCustomerRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/Api/V2/StoreCustomerRequest.php b/app/Http/Requests/Api/V2/StoreCustomerRequest.php index b609b6f..154cda6 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerRequest.php +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -28,7 +28,7 @@ class StoreCustomerRequest extends BaseApiRequest ->uncompromised() ], 'birth_date' => 'required|date_format:Y-m-d', - 'address' => 'required|string', + 'address' => 'sometimes|string', 'address2' => 'sometimes|string', 'phone' => 'sometimes|string', 'country' => [ From 39b003e02743cde6cd82f5ef524ada174a3ef4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 13:27:42 +0200 Subject: [PATCH 064/103] Update client bump to 2.0.10 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 951cfc5..026f982 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 951cfc59edb58768e47648622548fab2b3932a0a +Subproject commit 026f982dd174c4222df4961be99625ecc240edde diff --git a/composer.json b/composer.json index b78c45b..7c949c4 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.9", + "version": "2.0.10", "description": "LDC CRM V2", "authors": [ { From 6ec8ed1114ece2a01a33f3143b9d50fd6c5b4b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 16:31:15 +0200 Subject: [PATCH 065/103] Fix Api authenticate missing bearer token --- app/Enums/ApiError.php | 5 +++++ app/Http/Controllers/Api/V2/ApiController.php | 3 +++ 2 files changed, 8 insertions(+) diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php index 61b667b..7e1c9a2 100644 --- a/app/Enums/ApiError.php +++ b/app/Enums/ApiError.php @@ -47,6 +47,9 @@ use Kwaadpepper\Enum\BaseEnumRoutable; * a replay attack scenario. Timestamps are provided in headers * and in the http body * + * @method static self missingParameter() + * The request is missing some parameters + * * @method static self throttleRequestsException() * Too many requests have been attempted. The is a throttle security. * Normally you should not trigger this. @@ -78,6 +81,7 @@ class ApiError extends BaseEnumRoutable 'singleOptinCustomerExists' => 120, 'methodNotAllowed' => 150, 'wrongTimeStamp' => 151, + 'missingParameter' => 400, 'authorizationException' => 403, 'throttleRequestsException' => 429 ]; @@ -96,6 +100,7 @@ class ApiError extends BaseEnumRoutable 'needAuth' => 'Vous devez être authentifié', 'methodNotAllowed' => 'This method is not allowed for this request', 'wrongTimeStamp' => 'Your request is malformed, please check timestamp header and parameter', + 'missingParameter' => 'The request is missing some parameters', 'authorizationException' => 'You are not autorized to access this', 'throttleRequestsException' => 'Throttle: too many request have been attempted, please retry later' ]; diff --git a/app/Http/Controllers/Api/V2/ApiController.php b/app/Http/Controllers/Api/V2/ApiController.php index 6be7a29..61455f5 100644 --- a/app/Http/Controllers/Api/V2/ApiController.php +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -20,6 +20,9 @@ class ApiController extends Controller public function authenticate(Request $request) { try { + if (!$request->bearerToken()) { + throw new ApiErrorException(ApiError::missingParameter()); + } /** @var \App\Models\Service */ $service = Service::getFromBearerToken($request->bearerToken()); } catch (ModelNotFoundException $e) { From 443b649604744a7a8c1f8daab45c5279832d0fcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 18:47:31 +0200 Subject: [PATCH 066/103] Handle http auth with bearer token --- app/Http/Controllers/Api/V2/ApiController.php | 6 +++-- app/Http/Middleware/ApiResponse.php | 4 ++-- app/Http/Middleware/Authenticate.php | 2 +- app/Http/Middleware/CheckBrand.php | 3 ++- app/Lib/Helper.php | 22 +++++++++++++++++++ 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/V2/ApiController.php b/app/Http/Controllers/Api/V2/ApiController.php index 61455f5..b2b8502 100644 --- a/app/Http/Controllers/Api/V2/ApiController.php +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\V2; use App\Enums\ApiError; use App\Exceptions\Api\V2\ApiErrorException; use App\Http\Controllers\Controller; +use App\Lib\Helper; use App\Models\Service; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; @@ -20,11 +21,12 @@ class ApiController extends Controller public function authenticate(Request $request) { try { - if (!$request->bearerToken()) { + $bearerToken = Helper::getBearerTokenFromRequest($request); + if (!$bearerToken) { throw new ApiErrorException(ApiError::missingParameter()); } /** @var \App\Models\Service */ - $service = Service::getFromBearerToken($request->bearerToken()); + $service = Service::getFromBearerToken($bearerToken); } catch (ModelNotFoundException $e) { throw new ApiErrorException(ApiError::authFailed()); } diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index 1567436..a8fcea7 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -31,13 +31,13 @@ class ApiResponse try { if ($isApiV1Request) { // Use patched v1 Auth mecanism (without bearer reimplementation) - $bearerToken = $request->bearerToken() ?? $request->api_token; + $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; $service = Service::getFromBearerToken($bearerToken); $service->incrementStat(); } if ($isApiV2Request) { - $bearerToken = $request->bearerToken(); + $bearerToken = Helper::getBearerTokenFromRequest($request); if ($bearerToken) { $service = Service::getFromBearerToken($bearerToken); } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 340aa90..941d1bd 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -27,7 +27,7 @@ class Authenticate extends Middleware // TODO: check this works if (Helper::isRequestForApiV1($request)) { // Use patched v1 Auth mecanism (without bearer reimplementation) - $bearerToken = $request->bearerToken() ?? $request->api_token; + $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; $brand = Service::getFromBearerToken($bearerToken)->brand; $request->request->add(['brand_id' => $brand->id]); $response = $next($request); diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index a0de016..e0276f8 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Lib\Helper; use App\Models\Service; use Closure; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -18,7 +19,7 @@ class CheckBrand */ public function handle(Request $request, Closure $next) { - $apiToken = $request->bearerToken() ?? $request->api_token; + $apiToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; if ($apiToken === null) { abort(401, 'Le token d\'authentification est incorrect dfd' . \json_encode($request->all())); diff --git a/app/Lib/Helper.php b/app/Lib/Helper.php index 889a26e..1c39be1 100644 --- a/app/Lib/Helper.php +++ b/app/Lib/Helper.php @@ -4,6 +4,7 @@ namespace App\Lib; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use Illuminate\Support\Str; /** * Helper Class @@ -92,4 +93,25 @@ class Helper } return static::$packageInfos; } + + /** + * Undocumented function + * + * @param Request $request + * @return string|null + */ + public static function getBearerTokenFromRequest(Request $request): ?string + { + $header = $request->header('Authorization', ''); + + if (Str::contains($header, 'Bearer ')) { + $auths = \explode(',', $header); + foreach ($auths as $auth) { + if (Str::contains($auth, 'Bearer ')) { + return Str::substr($auth, 7); + } + } + } + return null; + } } From e3315c77087b2051c095b5ee56400b2e1f87ea65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 16 Aug 2021 19:23:40 +0200 Subject: [PATCH 067/103] bump to 2.0.11 update client --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 026f982..04e5e6c 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 026f982dd174c4222df4961be99625ecc240edde +Subproject commit 04e5e6cc0eba6dc4b42a501047da119d4c166422 diff --git a/composer.json b/composer.json index 7c949c4..c9d4f03 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.10", + "version": "2.0.11", "description": "LDC CRM V2", "authors": [ { From ad886fe1660665255d5ccf754bad3b7827ce355e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 17 Aug 2021 09:51:13 +0200 Subject: [PATCH 068/103] change fallback locale to en --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index f3a82ab..5bdde92 100644 --- a/config/app.php +++ b/config/app.php @@ -93,7 +93,7 @@ return [ | */ - 'fallback_locale' => 'fr', + 'fallback_locale' => 'en', /* |-------------------------------------------------------------------------- From e6c60ee75c93d69684c4b61f7fc71f2198c6b78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 17 Aug 2021 11:16:00 +0200 Subject: [PATCH 069/103] bump version to 2.0.12 and update client --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 04e5e6c..e302a74 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 04e5e6cc0eba6dc4b42a501047da119d4c166422 +Subproject commit e302a7421520b57485e9fb679fe285c4c4fbaed7 diff --git a/composer.json b/composer.json index c9d4f03..2b071fe 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.11", + "version": "2.0.12", "description": "LDC CRM V2", "authors": [ { From 648f6403273da143ecc7dc0921b630011261ff15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 17 Aug 2021 14:41:00 +0200 Subject: [PATCH 070/103] set migration newcountry default to 76 (france) --- ..._03_163527_add_newcountry_column_to_customer_infos_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php b/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php index 27e496e..d11403a 100644 --- a/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php +++ b/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php @@ -14,7 +14,7 @@ class AddNewcountryColumnToCustomerInfosTable extends Migration public function up() { Schema::table('customer_infos', function (Blueprint $table) { - $table->smallInteger('newcountry')->after('country'); + $table->smallInteger('newcountry')->default(76)->after('country'); }); } From 4527bc01f6ad7c34346a19661061ace6edee5582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:26:40 +0200 Subject: [PATCH 071/103] Refactorisation \App\Models\Customer::getAnswers --- .../Controllers/Api/V2/QuestionController.php | 8 +------- app/Models/Customer.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/V2/QuestionController.php b/app/Http/Controllers/Api/V2/QuestionController.php index 3d53a6e..6c3246a 100644 --- a/app/Http/Controllers/Api/V2/QuestionController.php +++ b/app/Http/Controllers/Api/V2/QuestionController.php @@ -38,13 +38,7 @@ class QuestionController extends Controller $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(); + $customerAnswers = $customer->getAnswers($service); return new QuestionsResource($service->brand->questions, $customerAnswers); } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 935da9c..f490958 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -103,4 +104,22 @@ class Customer extends Model $optInHistory->new_value = $optIn; $optInHistory->saveOrFail(); } + + /** + * Get All answers given by this customer + * for a specific service + * + * @param Service $service + * @return Collection + */ + public function getAnswers(Service $service): Collection + { + return Answer::whereHas('customers', function ($q) { + $q->whereId($this->id); + })->whereHas('question', function ($q) use ($service) { + $q->whereHas('brand', function ($q) use ($service) { + $q->whereId($service->brand->id); + }); + })->get(); + } } From f8a61d0e4c774367198ff25c5b9a44dc2f154711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:27:09 +0200 Subject: [PATCH 072/103] =?UTF-8?q?storeCustomerAnswers=20Mise=20=C3=A0=20?= =?UTF-8?q?jour=20sans=20supprimer=20les=20anciennes=20questions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/V2/AnswerController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php index 86bda2f..d752147 100644 --- a/app/Http/Controllers/Api/V2/AnswerController.php +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -23,8 +23,9 @@ class AnswerController extends Controller 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); + $existingAnswerIds = $customer->getAnswers($service)->pluck('id'); + $answersID = Answer::whereIn('id', $request->answers)->pluck('id'); + $customer->answers()->attach($answersID->diff($existingAnswerIds)); return \response()->json(); } } From 4f281ee77667c58e8ecf9024afa7876b4948eded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:27:40 +0200 Subject: [PATCH 073/103] bump version to 2.0.13 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2b071fe..16e5eab 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.12", + "version": "2.0.13", "description": "LDC CRM V2", "authors": [ { From 055b824ffa311fa371a24bfaf19129ec2f38f218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:43:44 +0200 Subject: [PATCH 074/103] Set address as required --- app/Http/Requests/Api/V2/StoreCustomerRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/Api/V2/StoreCustomerRequest.php b/app/Http/Requests/Api/V2/StoreCustomerRequest.php index 154cda6..b609b6f 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerRequest.php +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -28,7 +28,7 @@ class StoreCustomerRequest extends BaseApiRequest ->uncompromised() ], 'birth_date' => 'required|date_format:Y-m-d', - 'address' => 'sometimes|string', + 'address' => 'required|string', 'address2' => 'sometimes|string', 'phone' => 'sometimes|string', 'country' => [ From fb9de3325e5c344371b711becb27fb3d1b62a0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:44:38 +0200 Subject: [PATCH 075/103] bump version to 2.0.14 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 16e5eab..6399cef 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.13", + "version": "2.0.14", "description": "LDC CRM V2", "authors": [ { From eef979ec8eb12274b7166d6661b615fd8683fb7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:47:52 +0200 Subject: [PATCH 076/103] update generate client to remove copy translations --- app/Console/Commands/GenerateCrmClient.php | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php index 8e182d8..73446fc 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateCrmClient.php @@ -43,7 +43,6 @@ class GenerateCrmClient extends Command { $this->copyEnums(); $this->copyExceptions(); - $this->copyTranslations(); $this->copyRoutesList(); $this->copyApiConfigInfo(); } @@ -119,18 +118,6 @@ class GenerateCrmClient extends Command } } - 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 * From 19324ef64dceec4a16e602b6e285a155af0bf3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 08:48:40 +0200 Subject: [PATCH 077/103] update client --- clients/V2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/V2 b/clients/V2 index e302a74..b1d3c5f 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit e302a7421520b57485e9fb679fe285c4c4fbaed7 +Subproject commit b1d3c5fa5b23a1e1afc5dc426a85da260965cec6 From 77b91a6c09fcf1e4523b43c9a2588fc747664935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 18 Aug 2021 11:56:01 +0200 Subject: [PATCH 078/103] Fix answer api --- app/Http/Controllers/Api/V2/AnswerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php index d752147..2dbf05c 100644 --- a/app/Http/Controllers/Api/V2/AnswerController.php +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -25,7 +25,7 @@ class AnswerController extends Controller $this->authorize('customerBelongsToService', $customer); $existingAnswerIds = $customer->getAnswers($service)->pluck('id'); $answersID = Answer::whereIn('id', $request->answers)->pluck('id'); - $customer->answers()->attach($answersID->diff($existingAnswerIds)); + $customer->answers()->sync($answersID->merge($existingAnswerIds)); return \response()->json(); } } From dd754cf6f9f71efa057d70b1820592ccf47e46ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Thu, 19 Aug 2021 12:25:00 +0200 Subject: [PATCH 079/103] Change http auth code to 401 in Exception handler --- app/Exceptions/Handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 203bbed..9f20eff 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -96,7 +96,7 @@ class Handler extends ExceptionHandler /** @var AuthenticationException $e */ return $this->getReponse( $request, - 407, + 401, ApiError::needAuth()->value, sprintf('%s, %s', ApiError::needAuth()->label, $e->getMessage()) ); From c0578f5d40edb274612255eae9a315a836505fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 20 Aug 2021 16:46:07 +0200 Subject: [PATCH 080/103] Api V2 Fix get questions answers --- app/Http/Controllers/Api/V2/QuestionController.php | 3 --- app/Http/Resources/Api/V2/QuestionsResource.php | 9 +++++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/V2/QuestionController.php b/app/Http/Controllers/Api/V2/QuestionController.php index 6c3246a..d38ced5 100644 --- a/app/Http/Controllers/Api/V2/QuestionController.php +++ b/app/Http/Controllers/Api/V2/QuestionController.php @@ -35,9 +35,6 @@ class QuestionController extends Controller 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 = $customer->getAnswers($service); return new QuestionsResource($service->brand->questions, $customerAnswers); } diff --git a/app/Http/Resources/Api/V2/QuestionsResource.php b/app/Http/Resources/Api/V2/QuestionsResource.php index a3c64e7..336bf63 100644 --- a/app/Http/Resources/Api/V2/QuestionsResource.php +++ b/app/Http/Resources/Api/V2/QuestionsResource.php @@ -2,6 +2,7 @@ namespace App\Http\Resources\Api\V2; +use App\Models\Answer; use App\Models\Question; use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection; use Illuminate\Support\Collection; @@ -34,14 +35,18 @@ class QuestionsResource extends JsonResourceCollection public function toArray($request) { return $this->collection->map(function (Question $question) { + /** @var \Illuminate\Support\Collection */ + $answers = $question->answers; $question = [ 'id' => $question->id, 'type' => $question->type->value, 'question' => $question->question, - 'answers' => new AnswersResource($question->answers) + 'answers' => new AnswersResource($answers) ]; if ($this->userAnwsers) { - $question['customerAnswers'] = new AnswersResource($this->userAnwsers); + $question['customerAnswers'] = new AnswersResource($answers->filter(function (Answer $answer) { + return $this->userAnwsers->keyBy('id')->has($answer->id); + })); } return $question; }); From 8d7fa90cccf9a92de12cd9ccb50a7ddbe8f84f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Fri, 20 Aug 2021 17:53:41 +0200 Subject: [PATCH 081/103] Bump version to 2.1.0 Update client --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index b1d3c5f..201f4a3 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit b1d3c5fa5b23a1e1afc5dc426a85da260965cec6 +Subproject commit 201f4a3ecd749d64888ab7fcb6da5e789ee3bb6c diff --git a/composer.json b/composer.json index 6399cef..c76b851 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.0.14", + "version": "2.1.0", "description": "LDC CRM V2", "authors": [ { From baaf1cc677d0537b9f29833ba03aede237f6c1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 14:24:17 +0200 Subject: [PATCH 082/103] composer require cache/memcached-adapter --- composer.json | 2 + composer.lock | 317 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 317 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c76b851..598eb39 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,9 @@ "ext-curl": "*", "ext-dom": "*", "ext-json": "*", + "ext-memcached": "^3.1", "arondeparon/laravel-request-sanitizer": "^3.1", + "cache/memcached-adapter": "^1.1", "doctrine/dbal": "^3.0", "fideloper/proxy": "^4.4", "fruitcake/laravel-cors": "^2.0", diff --git a/composer.lock b/composer.lock index 9c61fb4..00d42ba 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aa896812fae801e5d109364dffe99068", + "content-hash": "2d86c215e8970135d800e76cda338075", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -182,6 +182,269 @@ ], "time": "2021-01-20T22:51:39+00:00" }, + { + "name": "cache/adapter-common", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/adapter-common.git", + "reference": "6b87c5cbdf03be42437b595dbe5de8e97cd1d497" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/6b87c5cbdf03be42437b595dbe5de8e97cd1d497", + "reference": "6b87c5cbdf03be42437b595dbe5de8e97cd1d497", + "shasum": "" + }, + "require": { + "cache/tag-interop": "^1.0", + "php": "^5.6 || ^7.0 || ^8.0", + "psr/cache": "^1.0", + "psr/log": "^1.0", + "psr/simple-cache": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.16", + "phpunit/phpunit": "^5.7.21" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Common\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "Common classes for PSR-6 adapters", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr-6", + "tag" + ], + "support": { + "source": "https://github.com/php-cache/adapter-common/tree/1.2.0" + }, + "time": "2020-12-14T12:17:39+00:00" + }, + { + "name": "cache/hierarchical-cache", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/hierarchical-cache.git", + "reference": "ba3746c65461b17154fb855068403670fd7fa2d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/hierarchical-cache/zipball/ba3746c65461b17154fb855068403670fd7fa2d3", + "reference": "ba3746c65461b17154fb855068403670fd7fa2d3", + "shasum": "" + }, + "require": { + "cache/adapter-common": "^1.0", + "php": "^5.6 || ^7.0 || ^8.0", + "psr/cache": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\Hierarchy\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "A helper trait and interface to your PSR-6 cache to support hierarchical keys.", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "hierarchical", + "hierarchy", + "psr-6" + ], + "support": { + "source": "https://github.com/php-cache/hierarchical-cache/tree/1.1.0" + }, + "time": "2020-12-14T12:17:39+00:00" + }, + { + "name": "cache/memcached-adapter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-cache/memcached-adapter.git", + "reference": "3e2105684bc2813646c2f44ce9c403020ff2145f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/memcached-adapter/zipball/3e2105684bc2813646c2f44ce9c403020ff2145f", + "reference": "3e2105684bc2813646c2f44ce9c403020ff2145f", + "shasum": "" + }, + "require": { + "cache/adapter-common": "^1.0", + "cache/hierarchical-cache": "^1.0", + "php": "^5.6 || ^7.0 || ^8.0", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0" + }, + "provide": { + "psr/cache-implementation": "^1.0", + "psr/simple-cache-implementation": "^1.0" + }, + "require-dev": { + "cache/integration-tests": "^0.16", + "phpunit/phpunit": "^5.7.21" + }, + "suggest": { + "ext-memcached": "The extension required to use this pool." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\Adapter\\Memcached\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Scherer", + "email": "aequasi@gmail.com", + "homepage": "https://github.com/aequasi" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + } + ], + "description": "A PSR-6 cache implementation using Memcached. This implementation supports tags", + "homepage": "http://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "memcached", + "psr-6", + "tag" + ], + "support": { + "source": "https://github.com/php-cache/memcached-adapter/tree/1.1.0" + }, + "time": "2020-12-14T12:17:39+00:00" + }, + { + "name": "cache/tag-interop", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-cache/tag-interop.git", + "reference": "909a5df87e698f1665724a9e84851c11c45fbfb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/909a5df87e698f1665724a9e84851c11c45fbfb9", + "reference": "909a5df87e698f1665724a9e84851c11c45fbfb9", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0 || ^8.0", + "psr/cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Cache\\TagInterop\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/nyholm" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com", + "homepage": "https://github.com/nicolas-grekas" + } + ], + "description": "Framework interoperable interfaces for tags", + "homepage": "https://www.php-cache.com/en/latest/", + "keywords": [ + "cache", + "psr", + "psr6", + "tag" + ], + "support": { + "issues": "https://github.com/php-cache/tag-interop/issues", + "source": "https://github.com/php-cache/tag-interop/tree/1.0.1" + }, + "time": "2020-12-04T14:11:04+00:00" + }, { "name": "composer/package-versions-deprecated", "version": "1.11.99.2", @@ -2310,6 +2573,55 @@ ], "time": "2020-07-20T17:29:33+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "psr/container", "version": "1.1.1", @@ -8365,7 +8677,8 @@ "php": "^7", "ext-curl": "*", "ext-dom": "*", - "ext-json": "*" + "ext-json": "*", + "ext-memcached": "^3.1" }, "platform-dev": [], "platform-overrides": { From 848fbe4cb1c6c99b0559ac786a66f50caa20fb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 14:29:10 +0200 Subject: [PATCH 083/103] =?UTF-8?q?Rollback=20de=20required=20sur=20Custom?= =?UTF-8?q?er=20address,=20code=20postal=20et=20ville=20(dans=20le=20cas?= =?UTF-8?q?=20ou=20sur=20le=20projet=20on=20utilise=20pas=20forc=C3=A9ment?= =?UTF-8?q?=20MesBR)=20pour=20=C3=AAtre=20conforme=20RGPD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Requests/Api/V2/StoreCustomerRequest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Requests/Api/V2/StoreCustomerRequest.php b/app/Http/Requests/Api/V2/StoreCustomerRequest.php index b609b6f..a3f7f6d 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerRequest.php +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -28,7 +28,7 @@ class StoreCustomerRequest extends BaseApiRequest ->uncompromised() ], 'birth_date' => 'required|date_format:Y-m-d', - 'address' => 'required|string', + 'address' => 'sometimes|string', 'address2' => 'sometimes|string', 'phone' => 'sometimes|string', 'country' => [ @@ -40,8 +40,8 @@ class StoreCustomerRequest extends BaseApiRequest } } ], - 'city' => 'required|string', - 'postcode' => 'required|string', + 'city' => 'sometimes|string', + 'postcode' => 'sometimes|string', 'opt_in' => 'required|boolean', ]; } From b17a86975d494bbc4f542c24a5483be51f3bc68e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 19:34:49 +0200 Subject: [PATCH 084/103] Add cache control on API response --- app/Http/Middleware/ApiResponse.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index a8fcea7..1dee526 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -25,6 +25,9 @@ class ApiResponse /** @var \Illuminate\Http\JsonResponse $response */ $response = $next($request); + // Set no cache header + $response->header('Cache-Control', 'No-Store'); + $isApiV1Request = Helper::isRequestForApiV1($request); $isApiV2Request = Helper::isRequestForApiV2($request); From 2a1aa5f0462c1f6c1d949e98ed761f3ad5fac13d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 19:38:16 +0200 Subject: [PATCH 085/103] Add database job to increment service stats --- .env.testing.example | 2 +- app/Http/Middleware/ApiResponse.php | 36 ++++++++------ app/Jobs/SetServiceStats.php | 48 +++++++++++++++++++ app/Models/Service.php | 10 ++-- .../2021_08_23_180427_create_jobs_table.php | 36 ++++++++++++++ 5 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 app/Jobs/SetServiceStats.php create mode 100644 database/migrations/2021_08_23_180427_create_jobs_table.php diff --git a/.env.testing.example b/.env.testing.example index 4b39a14..658141c 100644 --- a/.env.testing.example +++ b/.env.testing.example @@ -25,7 +25,7 @@ MIGRATIONPAGINATION=20000 BROADCAST_DRIVER=log CACHE_DRIVER=file -QUEUE_CONNECTION=sync +QUEUE_CONNECTION=database SESSION_DRIVER=file SESSION_LIFETIME=120 diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index 1dee526..25ad814 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -31,28 +31,36 @@ class ApiResponse $isApiV1Request = Helper::isRequestForApiV1($request); $isApiV2Request = Helper::isRequestForApiV2($request); - try { - if ($isApiV1Request) { - // Use patched v1 Auth mecanism (without bearer reimplementation) - $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; - $service = Service::getFromBearerToken($bearerToken); + if ($isApiV1Request) { + // Use patched v1 Auth mecanism (without bearer reimplementation) + $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; + try { + $service = Service::getFromBearerToken($bearerToken); $service->incrementStat(); + } catch (ModelNotFoundException $e) { + Log::error(sprintf('Could not increment stats %s', $e->getMessage())); } + } - if ($isApiV2Request) { - $bearerToken = Helper::getBearerTokenFromRequest($request); + if ($isApiV2Request) { + $service = null; + $bearerToken = Helper::getBearerTokenFromRequest($request); + try { if ($bearerToken) { $service = Service::getFromBearerToken($bearerToken); } - if ($request->api_token) { - $service = Service::getFromApiToken($request->api_token); - } - if (isset($service)) { - $service->incrementStat(); + } catch (ModelNotFoundException $e) { + try { + if (!$service and $request->api_token) { + $service = Service::getFromApiToken($request->api_token); + } + } catch (ModelNotFoundException $e) { + Log::error(sprintf('Could not increment stats %s', $e->getMessage())); } } - } catch (ModelNotFoundException $e) { - Log::error(sprintf('Could not increment stats %s', $e->getMessage())); + if ($service) { + $service->incrementStat(); + } } if ($isApiV1Request) { diff --git a/app/Jobs/SetServiceStats.php b/app/Jobs/SetServiceStats.php new file mode 100644 index 0000000..2522c66 --- /dev/null +++ b/app/Jobs/SetServiceStats.php @@ -0,0 +1,48 @@ +service = $service; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); + if (!$serviceStat->exists) { + $serviceStat->service()->associate($this->service); + $serviceStat->brand()->associate($this->service->brand); + } + $serviceStat->nb_calls += 1; + $serviceStat->saveOrFail(); + } +} diff --git a/app/Models/Service.php b/app/Models/Service.php index 7d25c4a..2efe59f 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Jobs\SetServiceStats; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -75,13 +76,8 @@ class Service extends Model */ 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(); + SetServiceStats::dispatch($this) + ->onConnection('database')->delay(\now()->addMinute(1)); } /** diff --git a/database/migrations/2021_08_23_180427_create_jobs_table.php b/database/migrations/2021_08_23_180427_create_jobs_table.php new file mode 100644 index 0000000..1be9e8a --- /dev/null +++ b/database/migrations/2021_08_23_180427_create_jobs_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('jobs'); + } +} From 234bcfe51fe651470ad50da824ccf77b9575c6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 19:59:26 +0200 Subject: [PATCH 086/103] add .env.testing to git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 87b0048..d430557 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ package-lock.json composer.lock .env +.env.testing .env.backup .phpunit.result.cache Homestead.json From 59bf1f231aa419b24302e87b0f42332382a13675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 20:00:07 +0200 Subject: [PATCH 087/103] phpcs fix customer delete --- app/Http/Controllers/CustomerController.php | 3 +- app/Http/Requests/CustomerDeleteRequest.php | 38 --------------------- 2 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 app/Http/Requests/CustomerDeleteRequest.php diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index 0b24f8a..22e5f26 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers; -use App\Http\Requests\CustomerDeleteRequest; use App\Models\Brand; use App\Models\Customer; use App\Models\CustomerInfo; @@ -66,7 +65,7 @@ class CustomerController extends Controller return response()->json($customers); } - public function delete(CustomerDeleteRequest $request, Customer $customer, Brand $brand) + public function delete(Customer $customer, Brand $brand) { $nbBrand = $customer->infos()->count(); diff --git a/app/Http/Requests/CustomerDeleteRequest.php b/app/Http/Requests/CustomerDeleteRequest.php deleted file mode 100644 index 535e54e..0000000 --- a/app/Http/Requests/CustomerDeleteRequest.php +++ /dev/null @@ -1,38 +0,0 @@ - 'exists:customers,id|required', - 'brand' => 'exists:brands,id|required' - ]; - } - - protected function prepareForValidation() - { - $this->merge(['customer' => $this->route('customer')->id, 'brand' => $this->route('brand')->id]); - } -} From cfa87b7924ef715ce7723c6698a70a68fa72b660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 20:00:44 +0200 Subject: [PATCH 088/103] phpcs fix AnswersController --- app/Http/Controllers/Api/V2/AnswerController.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php index 2dbf05c..c5c402b 100644 --- a/app/Http/Controllers/Api/V2/AnswerController.php +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -20,8 +20,11 @@ class AnswerController extends Controller * @return \Illuminate\Http\JsonResponse */ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed - public function storeCustomerAnswers(StoreCustomerAnswers $request, Service $service, Customer $customer): JsonResponse - { + public function storeCustomerAnswers( + StoreCustomerAnswers $request, + Service $service, + Customer $customer + ): JsonResponse { $this->authorize('customerBelongsToService', $customer); $existingAnswerIds = $customer->getAnswers($service)->pluck('id'); $answersID = Answer::whereIn('id', $request->answers)->pluck('id'); From f940d7a71d47fd73345bde798170eed81576c30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 20:03:17 +0200 Subject: [PATCH 089/103] Eloquent model cache for Brand and Service composer require rennokki/laravel-eloquent-query-cache --- app/Models/Brand.php | 41 ++++++++++++++++++++++++++ app/Models/Service.php | 57 +++++++++++++++++++++++++++++++++-- composer.json | 3 +- composer.lock | 67 +++++++++++++++++++++++++++++++++++++++++- config/api.php | 8 ++++- 5 files changed, 170 insertions(+), 6 deletions(-) diff --git a/app/Models/Brand.php b/app/Models/Brand.php index a628480..d80fae1 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -4,15 +4,56 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Rennokki\QueryCache\Traits\QueryCacheable; class Brand extends Model { use HasFactory; + use QueryCacheable; + + protected $cacheFor = 0; protected $fillable = [ 'name' ]; + public function __construct(array $attributes = []) + { + $this->cacheFor = config('api.servicesDBCache') * 60; + parent::__construct($attributes); + } + + /** + * Set the base cache tags that will be present + * on all queries. + * + *! This is used to flush caching + * + * @return array + */ + protected function getCacheBaseTags(): array + { + return [ + 'crm_brands', + ]; + } + + protected static function booted() + { + static::created(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + static::updated(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + static::deleted(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + } + /** * Service hasMany relation * diff --git a/app/Models/Service.php b/app/Models/Service.php index 2efe59f..d970710 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -8,15 +8,40 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; +use Rennokki\QueryCache\Traits\QueryCacheable; class Service extends Model { use HasFactory; + use QueryCacheable; + + protected $cacheFor = 0; protected $fillable = [ 'name' ]; + public function __construct(array $attributes = []) + { + $this->cacheFor = config('api.servicesDBCache') * 60; + parent::__construct($attributes); + } + + /** + * Set the base cache tags that will be present + * on all queries. + * + *! This is used to flush caching + * + * @return array + */ + protected function getCacheBaseTags(): array + { + return [ + 'crm_services', + ]; + } + protected static function booted() { static::creating(function (self $service) { @@ -42,6 +67,18 @@ class Service extends Model $service->api_token = Hash::make($service->api_token); } }); + static::created(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + static::updated(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + static::deleted(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); } /** @@ -89,10 +126,19 @@ class Service extends Model */ public static function getFromApiToken(string $apiToken): self { - $services = static::all(); + $services = static::with([ + 'brand' => function ($query) { + return $query->cacheTags(["service:author"]); + }, + ])->get(); + /** @var \App\Models\Service $service */ foreach ($services as $service) { if ($service->api_token and Hash::check($apiToken, $service->api_token)) { - return $service; + return $service->load([ + 'brand' => function ($query) use ($service) { + return $query->cacheTags(["service:{$service->id}:brand"]); + }, + ]); } } throw new ModelNotFoundException('Could not find service from Api Token'); @@ -108,9 +154,14 @@ class Service extends Model public static function getFromBearerToken(string $bearerToken): self { $services = static::all(); + /** @var \App\Models\Service $service */ foreach ($services as $service) { if ($service->bearer_token and Hash::check($bearerToken, $service->bearer_token)) { - return $service; + return $service->load([ + 'brand' => function ($query) use ($service) { + return $query->cacheTags(["service:{$service->id}:brand"]); + }, + ]); } } throw new ModelNotFoundException('Could not find service from Service Token'); diff --git a/composer.json b/composer.json index 598eb39..b6b8b65 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ "kwaadpepper/enum": "^2.0", "laravel-lang/lang": "~7.0", "laravel/framework": "^8.39", - "laravel/tinker": "^2.5" + "laravel/tinker": "^2.5", + "rennokki/laravel-eloquent-query-cache": "^3.0" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/composer.lock b/composer.lock index 00d42ba..39cc9e3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2d86c215e8970135d800e76cda338075", + "content-hash": "0df11a64ace2bcbbbc3710af5cc6868f", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -3271,6 +3271,71 @@ ], "time": "2020-08-18T17:17:46+00:00" }, + { + "name": "rennokki/laravel-eloquent-query-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/renoki-co/laravel-eloquent-query-cache.git", + "reference": "a253bb0fd23abcff13d580d90906f112716c1da0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/renoki-co/laravel-eloquent-query-cache/zipball/a253bb0fd23abcff13d580d90906f112716c1da0", + "reference": "a253bb0fd23abcff13d580d90906f112716c1da0", + "shasum": "" + }, + "require": { + "illuminate/database": "^7.30|^8.23", + "illuminate/support": "^7.30|^8.23" + }, + "require-dev": { + "chelout/laravel-relationship-events": "^1.4", + "laravel/legacy-factories": "^1.1", + "mockery/mockery": "^1.4", + "orchestra/database": "^5.0|^6.0", + "orchestra/testbench": "^5.0|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Rennokki\\QueryCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Alex Renoki", + "email": "alex@renoki.org", + "homepage": "https://github.com/rennokki", + "role": "Developer" + } + ], + "description": "Adding cache on your Laravel Eloquent queries' results is now a breeze.", + "homepage": "https://github.com/renoki-co/laravel-eloquent-query-cache", + "keywords": [ + "caching", + "eloquent", + "laravel", + "query", + "remember", + "sql" + ], + "support": { + "issues": "https://github.com/renoki-co/laravel-eloquent-query-cache/issues", + "source": "https://github.com/renoki-co/laravel-eloquent-query-cache/tree/3.0.0" + }, + "funding": [ + { + "url": "https://ko-fi.com/rennokki", + "type": "ko_fi" + } + ], + "time": "2021-07-17T17:42:40+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v6.2.7", diff --git a/config/api.php b/config/api.php index 1f6a224..c8d1914 100644 --- a/config/api.php +++ b/config/api.php @@ -18,5 +18,11 @@ return [ * The number of request * per user or ip per minutes */ - 'apiRequestRatePerMinute' => 256 + 'apiRequestRatePerMinute' => 256, + + /** + * Service requests cache + * time in minutes + */ + 'servicesDBCache' => 10 ]; From e618e6fb4683d3375c0e4d86cdc5277fc35b4f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 20:20:51 +0200 Subject: [PATCH 090/103] Customer::info and Customer::login try to get loaded relations first --- .../Controllers/Api/V2/ArcadiaController.php | 2 +- .../Controllers/Api/V2/CustomerController.php | 16 ++--- .../Resources/Api/V2/CustomerResource.php | 4 +- app/Models/Customer.php | 66 +++++++++++++++++-- app/Policies/CustomerPolicy.php | 2 +- tests/Feature/ApiV2/ApiBase.php | 2 +- tests/Feature/ApiV2/CustomerTest.php | 4 +- tests/Unit/CrmClientTester.php | 2 +- tests/Unit/CrmClientV2CustomerTest.php | 2 +- 9 files changed, 79 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/Api/V2/ArcadiaController.php b/app/Http/Controllers/Api/V2/ArcadiaController.php index a30ba1f..ca3c0bd 100644 --- a/app/Http/Controllers/Api/V2/ArcadiaController.php +++ b/app/Http/Controllers/Api/V2/ArcadiaController.php @@ -54,7 +54,7 @@ class ArcadiaController extends Controller $xml = 'none'; try { /** @var \App\Models\CustomerInfo $customerInfo */ - $customerInfo = $customer->info($service->brand)->firstOrFail(); + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL); $orderType = ArcadiaOrderType::make(intval($request->state_order)); $xml = $this->makeXml($request, $type, $orderType, $customer, $customerInfo); diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index f855456..62dbefb 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -45,7 +45,7 @@ class CustomerController extends Controller } /** @var \App\Models\CustomerInfo */ - $customerInfo = $customer->info($service->brand)->firstOrNew(); + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_NEW); if ($customerInfo->id) { throw new ApiErrorException(ApiError::customerExists()); } @@ -55,7 +55,7 @@ class CustomerController extends Controller $customerInfo->saveOrFail(); /** @var \App\Models\CustomerLogin */ - $customerLogin = $customer->login($service->brand)->firstOrNew(); + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_NEW); $customerLogin->fill($request->validated()); $customerLogin->brand()->associate($service->brand); $customerLogin->customer()->associate($customer); @@ -98,12 +98,12 @@ class CustomerController extends Controller } /** @var \App\Models\CustomerInfo */ - $customerInfo = $customer->info($service->brand)->firstOrFail(); + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL); $customerInfo->fill($request->validated()); $customerInfo->saveOrFail(); /** @var \App\Models\CustomerLogin */ - $customerLogin = $customer->login($service->brand)->firstOrFail(); + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); $customerLogin->fill($request->validated()); $customerLogin->saveOrFail(); @@ -123,9 +123,9 @@ class CustomerController extends Controller // 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(); + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL); /** @var \App\Models\CustomerLogin */ - $customerLogin = $customer->login($service->brand)->firstOrFail(); + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); if ( !$customerInfo->delete() || @@ -170,7 +170,7 @@ class CustomerController extends Controller /** @var \App\Models\Customer */ $customer = Customer::where('email', $request->email)->firstOrFail(); /** @var \App\Models\CustomerLogin */ - $customerLogin = $customer->login($service->brand)->firstOrFail(); + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); if (!$customerLogin->passwordMatch($request->password)) { throw new ApiErrorException(ApiError::authFailed()); @@ -198,7 +198,7 @@ class CustomerController extends Controller public function activateAccount(Customer $customer, Service $service) { $this->authorize('customerBelongsToService', $customer); - $login = $customer->login($service->brand)->firstOrFail(); + $login = $customer->login($service->brand, Customer::FIRST_OR_FAIL); if ($login->status === 0) { $login->status = 1; diff --git a/app/Http/Resources/Api/V2/CustomerResource.php b/app/Http/Resources/Api/V2/CustomerResource.php index 243caca..da2a64a 100644 --- a/app/Http/Resources/Api/V2/CustomerResource.php +++ b/app/Http/Resources/Api/V2/CustomerResource.php @@ -38,8 +38,8 @@ class CustomerResource extends JsonResource public function __construct(Brand $brand, Customer $customer) { $this->customer = $customer; - $this->info = $customer->info($brand)->firstOrFail(); - $this->login = $customer->login($brand)->firstOrFail(); + $this->info = $customer->info($brand, Customer::FIRST_OR_FAIL); + $this->login = $customer->login($brand, Customer::FIRST_OR_FAIL); } /** diff --git a/app/Models/Customer.php b/app/Models/Customer.php index f490958..87ad021 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -11,6 +11,10 @@ class Customer extends Model { use HasFactory; + public const FIRST_OR_NULL = 0; + public const FIRST_OR_NEW = 1; + public const FIRST_OR_FAIL = 2; + protected $fillable = [ 'email' ]; @@ -27,17 +31,35 @@ class Customer extends Model /** * Get the CustomerInfo from a specific brand + * or create a New one if $firstOrNewOrFail is 1 + * throw model not found exception if firstOrNewOrFail = 2 * * @param Brand $brand + * @param int $firstOrNewOrFail * @return \Illuminate\Database\Eloquent\Builder */ - public function info(Brand $brand) + public function info(Brand $brand, $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerInfo { - return CustomerInfo::whereHas('brand', function (Builder $query) use ($brand) { + if (isset($this->relations['infos'])) { + /** @var \App\Models\CustomerInfo $info */ + foreach ($this->relations['infos'] as $info) { + if ($info->brand_id === $brand->id) { + return $info; + } + } + } + $query = 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); }); + if ($firstOrNewOrFail === self::FIRST_OR_NEW) { + return $query->firstOrNew(); + } + if ($firstOrNewOrFail === self::FIRST_OR_FAIL) { + return $query->firstOrFail(); + } + return $query->first(); } /** @@ -52,17 +74,53 @@ class Customer extends Model /** * Get the CustomerLogin from a specific brand + * or create a New one if $firstOrNewOrFail is 1 + * throw model not found exception if firstOrNewOrFail = 2 * * @param Brand $brand + * @param int $firstOrNewOrFail * @return \Illuminate\Database\Eloquent\Builder */ - public function login(Brand $brand) + public function login(Brand $brand, $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerLogin { - return CustomerLogin::whereHas('brand', function (Builder $query) use ($brand) { + if (isset($this->relations['logins'])) { + /** @var \App\Models\CustomerLogin $login */ + foreach ($this->relations['logins'] as $login) { + if ($login->brand_id === $brand->id) { + return $login; + } + } + } + $query = 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); }); + if ($firstOrNewOrFail === self::FIRST_OR_NEW) { + return $query->firstOrNew(); + } + if ($firstOrNewOrFail === self::FIRST_OR_FAIL) { + return $query->firstOrFail(); + } + return $query->first(); + } + + /** + * Load login and brand for this customer + * + * @param Brand $brand + * @return self + */ + public function loadBrandData(Brand $brand): self + { + return $this->load([ + 'logins' => function ($q) use ($brand) { + $q->where('brand_id', $brand->id); + }, + 'infos' => function ($q) use ($brand) { + $q->where('brand_id', $brand->id); + } + ]); } /** diff --git a/app/Policies/CustomerPolicy.php b/app/Policies/CustomerPolicy.php index e9253b2..32e33ee 100644 --- a/app/Policies/CustomerPolicy.php +++ b/app/Policies/CustomerPolicy.php @@ -12,6 +12,6 @@ class CustomerPolicy public function customerBelongsToService(Service $service, Customer $customer) { - return $customer->login($service->brand)->exists(); + return !\is_null($customer->login($service->brand)); } } diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php index a01c1cc..0303a08 100644 --- a/tests/Feature/ApiV2/ApiBase.php +++ b/tests/Feature/ApiV2/ApiBase.php @@ -94,7 +94,7 @@ class ApiBase extends BaseTestCase $service->bearer_pwd = $bearer_pwd; // get customer info - $info = $customer->info($brand)->firstOrFail(); + $info = $customer->info($brand, Customer::FIRST_OR_FAIL); return compact('customer', 'service', 'login', 'brand', 'info'); } diff --git a/tests/Feature/ApiV2/CustomerTest.php b/tests/Feature/ApiV2/CustomerTest.php index 2fc930a..bf144b4 100644 --- a/tests/Feature/ApiV2/CustomerTest.php +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -29,8 +29,8 @@ class CustomerTest extends ApiBase $this->assertInstanceOf(Collection::class, $logins); $this->assertInstanceOf(Collection::class, $brands); $this->assertInstanceOf(Collection::class, $optInHistory); - $this->assertInstanceOf(CustomerInfo::class, $customer->info($brands->first())->first()); - $this->assertInstanceOf(CustomerLogin::class, $customer->login($brands->first())->first()); + $this->assertInstanceOf(CustomerInfo::class, $customer->info($brands->first())); + $this->assertInstanceOf(CustomerLogin::class, $customer->login($brands->first())); } /** diff --git a/tests/Unit/CrmClientTester.php b/tests/Unit/CrmClientTester.php index f76d422..6059a01 100644 --- a/tests/Unit/CrmClientTester.php +++ b/tests/Unit/CrmClientTester.php @@ -59,7 +59,7 @@ abstract class CrmClientTester extends TestCase $service = $brand->services()->firstOrFail(); // get customer info - $info = $customer->info($brand)->firstOrFail(); + $info = $customer->info($brand, Customer::FIRST_OR_FAIL); // regen bearer token to get the pwd in clear $bearer_token = $service->generateBearerToken(); diff --git a/tests/Unit/CrmClientV2CustomerTest.php b/tests/Unit/CrmClientV2CustomerTest.php index 4ed021a..9af2328 100644 --- a/tests/Unit/CrmClientV2CustomerTest.php +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -133,7 +133,7 @@ class CrmClientV2CustomerTest extends CrmClientTester /** @var \App\Models\Brand */ $brand = $login->brand; /** @var \App\Models\CustomerInfo */ - $info = $customer->info($brand)->firstOrFail(); + $info = $customer->info($brand, ModelsCustomer::FIRST_OR_FAIL); $api = new CrmApiV2(); $customer = $api->getCustomerOrNew($info->hashcode); From 50a7eb35ad0e254f9d92ef2d21f22628e8107bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 20:21:05 +0200 Subject: [PATCH 091/103] CustomerInfo phpcs fix --- app/Models/CustomerInfo.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Models/CustomerInfo.php b/app/Models/CustomerInfo.php index 9557943..a47f6c0 100644 --- a/app/Models/CustomerInfo.php +++ b/app/Models/CustomerInfo.php @@ -31,6 +31,12 @@ class CustomerInfo extends Model 'opt_in' ]; + /** + *! TODO Try to refactor this but beware this is difficult and breaks unit tests + * + * @return void + */ + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh protected static function booted() { static::creating(function ($customerInfo) { From 13b7228b6d767a7e76a8183f717514a04dfa62a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 21:04:01 +0200 Subject: [PATCH 092/103] Route Service provider better route bind $customer --- app/Providers/RouteServiceProvider.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index b54ffc7..a5ccfc3 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -3,7 +3,9 @@ namespace App\Providers; use App\Models\Customer; +use App\Models\CustomerInfo; use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; @@ -35,11 +37,22 @@ class RouteServiceProvider extends ServiceProvider }); 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(); + if (\ctype_digit($value)) { + $customer = Customer::where('id', '=', $value)->firstOrFail(); + } + if (!isset($customer) and strpos($value, '@') !== false) { + $customer = Customer::where('email', '=', $value)->firstOrFail(); + } + if (!isset($customer)) { + try { + $customer = CustomerInfo::with('customer') + ->where('hashcode', $value)->firstOrFail()->customer; + } catch (ModelNotFoundException $e) { + // Uniform Not found exception using model Customer + throw (new ModelNotFoundException())->setModel(Customer::class); + } + } + return $customer->loadBrandData(app('App\Models\Service')->brand); }); } From aeaa44e8c65dd2f3319d4f15b3743d73cfbefa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 21:41:05 +0200 Subject: [PATCH 093/103] Set correct prop check $customerInfo->exists on api v2 customer store --- app/Http/Controllers/Api/V2/CustomerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index 62dbefb..60dfb26 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -46,7 +46,7 @@ class CustomerController extends Controller /** @var \App\Models\CustomerInfo */ $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_NEW); - if ($customerInfo->id) { + if ($customerInfo->exists) { throw new ApiErrorException(ApiError::customerExists()); } $customerInfo->fill($request->validated()); From 985fd3dba56735d76d82effa42f1c58fbcf195df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 21:46:10 +0200 Subject: [PATCH 094/103] Remove delay on service stat dispatch --- app/Models/Service.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Models/Service.php b/app/Models/Service.php index d970710..fc571de 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -113,8 +113,7 @@ class Service extends Model */ public function incrementStat(): void { - SetServiceStats::dispatch($this) - ->onConnection('database')->delay(\now()->addMinute(1)); + SetServiceStats::dispatch($this)->onConnection('database'); } /** From 2eb24ba4b02b0e3a6e037c6878b2b805d837c6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 21:55:17 +0200 Subject: [PATCH 095/103] set phpunit queudriver database --- phpunit.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 5f66c8e..7b01f29 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -23,7 +23,8 @@ - + + From 6c28c87781425fb131049fe254c49ddce6f08308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 21:56:12 +0200 Subject: [PATCH 096/103] APiv2 customer lastconnection on a job + refactorize login --- .../Controllers/Api/V2/CustomerController.php | 27 ++++----- app/Jobs/UpdateCustomerLastConnection.php | 57 +++++++++++++++++++ 2 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 app/Jobs/UpdateCustomerLastConnection.php diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index 60dfb26..5a954bc 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -10,6 +10,7 @@ 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\Jobs\UpdateCustomerLastConnection; use App\Models\Customer; use App\Models\Service; use App\Models\SingleOptin; @@ -171,22 +172,22 @@ class CustomerController extends Controller $customer = Customer::where('email', $request->email)->firstOrFail(); /** @var \App\Models\CustomerLogin */ $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); - - 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()); } + + if (!$customerLogin->passwordMatch($request->password)) { + throw new ApiErrorException(ApiError::authFailed()); + } + + if ($customerLogin->status === 0) { + throw new ApiErrorException(ApiError::customerNotActivated()); + } + + UpdateCustomerLastConnection::dispatch($customer, $customerLogin, now()) + ->onConnection('database'); + + return new CustomerResource($service->brand, $customer); } /** diff --git a/app/Jobs/UpdateCustomerLastConnection.php b/app/Jobs/UpdateCustomerLastConnection.php new file mode 100644 index 0000000..ad34510 --- /dev/null +++ b/app/Jobs/UpdateCustomerLastConnection.php @@ -0,0 +1,57 @@ +date = $date; + $this->customer = $customer; + $this->customerLogin = $customerLogin; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + if ($this->customerLogin->last_connection < $this->date) { + $this->customerLogin->last_connection = $this->date; + $this->customer->logins()->save($this->customerLogin); + } + } +} From 98b84fbfdf76ba3185ec8579730e2b090f4555b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 22:45:18 +0200 Subject: [PATCH 097/103] Route service provider add comment --- app/Providers/RouteServiceProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index a5ccfc3..ed2bb3c 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -52,6 +52,7 @@ class RouteServiceProvider extends ServiceProvider throw (new ModelNotFoundException())->setModel(Customer::class); } } + /** @var \App\Models\Customer $customer */ return $customer->loadBrandData(app('App\Models\Service')->brand); }); } From 5ae0aa33bdfbe1d9a3f416d689b05f3c87d98a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Mon, 23 Aug 2021 22:47:42 +0200 Subject: [PATCH 098/103] Apiv2 Customer optimize authenticate --- app/Http/Controllers/Api/V2/CustomerController.php | 4 ++-- app/Models/Customer.php | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index 5a954bc..06f48b4 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -170,8 +170,8 @@ class CustomerController extends Controller try { /** @var \App\Models\Customer */ $customer = Customer::where('email', $request->email)->firstOrFail(); - /** @var \App\Models\CustomerLogin */ - $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + $customer->loadBrandData($service->brand, true); + $customerLogin = $customer->logins->first(); } catch (ModelNotFoundException $e) { throw new ApiErrorException(ApiError::authFailed()); } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 87ad021..112f5d6 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\ModelNotFoundException; class Customer extends Model { @@ -50,7 +51,7 @@ class Customer extends Model } $query = CustomerInfo::whereHas('brand', function (Builder $query) use ($brand) { $query->where('id', $brand->id); - })->whereHas('customer', function (Builder $query) use ($brand) { + })->whereHas('customer', function (Builder $query) { $query->where('id', $this->id); }); if ($firstOrNewOrFail === self::FIRST_OR_NEW) { @@ -111,9 +112,9 @@ class Customer extends Model * @param Brand $brand * @return self */ - public function loadBrandData(Brand $brand): self + public function loadBrandData(Brand $brand, bool $orFail = false): self { - return $this->load([ + $this->load([ 'logins' => function ($q) use ($brand) { $q->where('brand_id', $brand->id); }, @@ -121,6 +122,13 @@ class Customer extends Model $q->where('brand_id', $brand->id); } ]); + if ($orFail and !isset($this->relations['logins'][0])) { + throw (new ModelNotFoundException())->setModel(CustomerLogin::class); + } + if ($orFail and !isset($this->relations['infos'][0])) { + throw (new ModelNotFoundException())->setModel(CustomerInfo::class); + } + return $this; } /** From dafa85ff79ad0850fac17c0e5f4ca04136194b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 24 Aug 2021 08:33:19 +0200 Subject: [PATCH 099/103] bump version to 2.1.1 --- clients/V2 | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/V2 b/clients/V2 index 201f4a3..0b2c4b4 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 201f4a3ecd749d64888ab7fcb6da5e789ee3bb6c +Subproject commit 0b2c4b4ec366569e71482cf47db1fe6f64ac1d96 diff --git a/composer.json b/composer.json index b6b8b65..658512c 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "ldc/ldccrmv2client", "type": "project", - "version": "2.1.0", + "version": "2.1.1", "description": "LDC CRM V2", "authors": [ { From 52d8030b7b5bab35bf35b94baa171c622c0bd5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 24 Aug 2021 12:46:01 +0200 Subject: [PATCH 100/103] update client --- clients/V2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/V2 b/clients/V2 index 0b2c4b4..9ad2f56 160000 --- a/clients/V2 +++ b/clients/V2 @@ -1 +1 @@ -Subproject commit 0b2c4b4ec366569e71482cf47db1fe6f64ac1d96 +Subproject commit 9ad2f564d57934a5fe62d928de1fa4568088a7e4 From 0e38fc9ad45c226fa2741b4daac624129e254d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Tue, 24 Aug 2021 16:05:05 +0200 Subject: [PATCH 101/103] Fix Route service provider for $customer --- app/Providers/RouteServiceProvider.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index ed2bb3c..c0b3794 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -52,8 +52,11 @@ class RouteServiceProvider extends ServiceProvider throw (new ModelNotFoundException())->setModel(Customer::class); } } - /** @var \App\Models\Customer $customer */ - return $customer->loadBrandData(app('App\Models\Service')->brand); + if ($brand = app('App\Models\Service')->brand) { + /** @var \App\Models\Customer $customer */ + $customer = $customer->loadBrandData($brand); + } + return $customer; }); } From 202db588771e5e16d1613252f717fb71d2a49003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 25 Aug 2021 09:24:01 +0200 Subject: [PATCH 102/103] Bug fix in authenticate when no token is provided --- app/Http/Middleware/Authenticate.php | 2 +- app/Providers/AppServiceProvider.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 941d1bd..6832d89 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -27,7 +27,7 @@ class Authenticate extends Middleware // TODO: check this works if (Helper::isRequestForApiV1($request)) { // Use patched v1 Auth mecanism (without bearer reimplementation) - $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; + $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token ?? ''; $brand = Service::getFromBearerToken($bearerToken)->brand; $request->request->add(['brand_id' => $brand->id]); $response = $next($request); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 7c7e915..df4b423 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -25,6 +25,8 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { + //! Important pourra etre retiré quand la V1 sera supprimé + // empeche l'encapsulation dans data JsonResource::withoutWrapping(); if (!in_array(config('app.env'), ['local', 'testing'])) { URL::forceScheme('https'); From fef4923f010d07ad575495b89d3fc7f6041b0d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Munsch?= Date: Wed, 25 Aug 2021 11:01:16 +0200 Subject: [PATCH 103/103] update default value mail handler --- config/mail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/mail.php b/config/mail.php index b3d48a7..8481227 100644 --- a/config/mail.php +++ b/config/mail.php @@ -91,7 +91,7 @@ return [ 'MAIL_ADMIN' => env('MAIL_ADMIN'), 'contactsList' => [ - 'serviceweb' => env('MAIL_LIST_SERVICEWEB', 'informatique.internet@ldc.fr') + 'serviceweb' => env('MAIL_LIST_SERVICEWEB', 'equipe.web@ldc.fr') ], /*
NomMarqueDate du tokenRegénérer un tokenDate de création{{ __('Nom') }}{{ __('Marque') }}{{ __('Date du token') }}{{ __('Regénérer un token') }}{{ __('Date de création') }}
{{ $service->name }} {{ $service->brand->name }}{{ \Carbon\Carbon::parse($service->api_token_date)->format('d/m/Y à H:i') }}Générer{{ \Carbon\Carbon::parse($service->created_at)->format('d/m/Y à H:i') }}Modifier{{ Carbon::parse($service->bearer_token_date)->format('d/m/Y à H:i') }}{{ __('Générer') }}{{ Carbon::parse($service->created_at)->format('d/m/Y à H:i') }}{{ __('Modifier') }}