diff --git a/.env.testing.example b/.env.testing.example new file mode 100644 index 0000000..658141c --- /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=database +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/.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 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/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/DeleteUnconfirmedCustomers.php b/app/Console/Commands/DeleteUnconfirmedCustomers.php index 5393156..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,17 +22,8 @@ class DeleteUnconfirmedCustomers extends Command * * @var string */ - protected $description = "Delete the customers that haven't confirmed their acocunt within 24 hours after the registeration."; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } + protected $description = 'Delete the customers that haven\'t + confirmed their acocunt within 24 hours after the registeration.'; /** * Execute the console command. @@ -41,7 +33,7 @@ class DeleteUnconfirmedCustomers extends Command public function handle() { $customersLogins = CustomerLogin::where([['status', 0], ['created_at', '<', Carbon::now()->subDay(1)]])->get(); - foreach($customersLogins as $customerLogin) { + foreach ($customersLogins as $customerLogin) { // Si non activé et créer il y a plus d'un jour : Suppression $customerLogin->customer->infos()->where('brand_id', $customerLogin->brand_id)->first()->delete(); // On récupère son id pour le cas ou on doit le supprimer complètement après diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateCrmClient.php new file mode 100644 index 0000000..73446fc --- /dev/null +++ b/app/Console/Commands/GenerateCrmClient.php @@ -0,0 +1,160 @@ +copyEnums(); + $this->copyExceptions(); + $this->copyRoutesList(); + $this->copyApiConfigInfo(); + } + + private function copyApiConfigInfo() + { + $this->info('Copy Api Config'); + $rootTarget = app_path('../' . self::TARGET); + if (File::exists("$rootTarget/../apiConfig.readonly.json")) { + File::delete("$rootTarget/../apiConfig.readonly.json"); + } + File::put( + "$rootTarget/../apiConfig.readonly.json", + \json_encode(config('api'), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + } + + private function copyRoutesList() + { + $this->info('Copy Routes'); + $routes = []; + /** @var \Illuminate\Routing\Route */ + foreach (Route::getRoutes() as $route) { + if (\strpos($route->getPrefix(), 'v2') !== false) { + $routes[$route->getName()] = [ + 'uri' => $route->uri(), + 'methods' => $route->methods() + ]; + } + } + $rootTarget = app_path('../' . self::TARGET); + if (File::exists("$rootTarget/routes.json")) { + File::delete("$rootTarget/routes.json"); + } + File::put( + "$rootTarget/routes.json", + \json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); + } + + private function copyExceptions(): void + { + $this->info('Copy Exceptions'); + $rootTarget = app_path('../' . self::TARGET); + $this->copyToFolder([ + CrmException::class, + ApiErrorException::class + ], 'Exceptions'); + $this->replaceInPhpFile( + "$rootTarget/Exceptions/ApiErrorException.php", + 'App\Enums\ApiError', + 'LDC\LDCCRM\Enums\ApiError' + ); + } + + private function copyEnums(): void + { + $this->info('Copy Enums'); + $classes = [ + ApiError::class, + ApiStatus::class, + ArcadiaMailType::class, + ArcadiaOrderType::class, + Country::class, + QuestionType::class + ]; + $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'); + } + } + + /** + * Copy Classes to specified folder + * + * @param array $classes + * @param string $folder + * @return void + */ + private function copyToFolder(array $classes, string $folder): void + { + $rootTarget = app_path('../' . self::TARGET); + foreach ($classes as $class) { + $classFileName = \str_replace("App\\$folder\\", '', sprintf('%s.php', $class)); + $classFileName = \str_replace('\\', '/', $classFileName); + $classBasename = class_basename($class); + $source = \app_path("$folder/$classFileName"); + $dest = "$rootTarget/$folder/$classBasename.php"; + if (File::exists($dest)) { + File::delete($dest); + } + File::copy($source, $dest); + $this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE); + } + } + + private function replaceInPhpFile(string $source, string $search, string $replace): void + { + $fContent = File::get($source); + + // remove trailing \Api\V2 + if ($pos = strpos($fContent, '\Api\V2')) { + $fContent = substr_replace($fContent, '', $pos, strlen('\Api\V2')); + } + + // replace namespace top level + $pos = strpos($fContent, $search); + if ($pos !== false) { + File::put($source, \str_replace($search, $replace, $fContent)); + } + } +} 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/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 69914e9..6d23f9c 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -7,14 +7,13 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel { + /** * The Artisan commands provided by your application. * * @var array */ - protected $commands = [ - // - ]; + protected $commands = []; /** * Define the application's command schedule. @@ -24,7 +23,6 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - // $schedule->command('inspire')->hourly(); } /** @@ -34,7 +32,7 @@ class Kernel extends ConsoleKernel */ protected function commands() { - $this->load(__DIR__.'/Commands'); + $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } diff --git a/app/Enums/ApiError.php b/app/Enums/ApiError.php new file mode 100644 index 0000000..7e1c9a2 --- /dev/null +++ b/app/Enums/ApiError.php @@ -0,0 +1,108 @@ + 101, + 'validationFailed' => 102, + 'customerExists' => 103, + 'customerAlreadyActive' => 110, + 'customerNotActivated' => 116, + 'authFailed' => 117, + 'needAuth' => 118, + 'apiAuthFailed' => 119, + 'singleOptinCustomerExists' => 120, + 'methodNotAllowed' => 150, + 'wrongTimeStamp' => 151, + 'missingParameter' => 400, + 'authorizationException' => 403, + 'throttleRequestsException' => 429 + ]; + } + + public static function labels(): array + { + return [ + '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', + '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/Enums/ApiStatus.php b/app/Enums/ApiStatus.php new file mode 100644 index 0000000..c6a87a9 --- /dev/null +++ b/app/Enums/ApiStatus.php @@ -0,0 +1,16 @@ + 1, + 'game' => 2 + ]; + } +} diff --git a/app/Enums/ArcadiaOrderType.php b/app/Enums/ArcadiaOrderType.php new file mode 100644 index 0000000..5ff85ec --- /dev/null +++ b/app/Enums/ArcadiaOrderType.php @@ -0,0 +1,23 @@ + 0, + 'check' => 1 + ]; + } +} diff --git a/app/Enums/Country.php b/app/Enums/Country.php new file mode 100644 index 0000000..26d27a2 --- /dev/null +++ b/app/Enums/Country.php @@ -0,0 +1,774 @@ + 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/Enums/QuestionType.php b/app/Enums/QuestionType.php new file mode 100644 index 0000000..0fb24d4 --- /dev/null +++ b/app/Enums/QuestionType.php @@ -0,0 +1,25 @@ + 1, + 'radio' => 2, + 'checkbox' => 3 + ]; + } +} diff --git a/app/Exceptions/Api/V2/ApiErrorException.php b/app/Exceptions/Api/V2/ApiErrorException.php new file mode 100644 index 0000000..63e839a --- /dev/null +++ b/app/Exceptions/Api/V2/ApiErrorException.php @@ -0,0 +1,89 @@ +error = $error; + $this->message = $message; + $this->httpCode = $httpCode; + parent::__construct( + strlen($this->message) ? $this->message : $error->label, + $error->value, + $previous + ); + } + + /** + * Get the Api Error + * + * @return ApiError + */ + public function getApiError(): ApiError + { + return $this->error; + } + + /** + * Get the http code + * + * @return int + */ + public function getStatusCode(): int + { + return $this->httpCode; + } + + public function report() + { + Log::debug(sprintf('CRM API: %s', $this->getMessage())); + } +} diff --git a/app/Exceptions/Api/V2/CrmException.php b/app/Exceptions/Api/V2/CrmException.php new file mode 100644 index 0000000..f720c9f --- /dev/null +++ b/app/Exceptions/Api/V2/CrmException.php @@ -0,0 +1,22 @@ +getMessage())); + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 7e40d73..9f20eff 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,17 +2,35 @@ namespace App\Exceptions; +use App\Enums\ApiError; +use App\Enums\ApiStatus; +use App\Exceptions\Api\V2\ApiErrorException; +use App\Exceptions\Api\V2\CrmException; +use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse; +use App\Lib\Helper; +use App\Mail\ExceptionAlert; +use Illuminate\Auth\Access\AuthorizationException; +use Illuminate\Auth\AuthenticationException; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Http\Exceptions\ThrottleRequestsException; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Mail; +use Illuminate\Validation\ValidationException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Throwable; class Handler extends ExceptionHandler { + /** * A list of the exception types that are not reported. * * @var array */ protected $dontReport = [ - // + ApiErrorException::class ]; /** @@ -34,4 +52,143 @@ class Handler extends ExceptionHandler { // } + + /** + * Report or log an exception. + * + * @param \Throwable $exception + * @return void + * + * @throws \Exception + */ + public function report(Throwable $e) + { + if (get_class($e) == RedirectTo::class) { + abort(redirect($e->url)); + } + try { + if (!config('app.debug')) { + if ($this->shouldntReport($e)) { + return; + } + Mail::send(new ExceptionAlert($e)); + } + } finally { + parent::report($e); + } + } + + /** + * Render an exception into an HTTP response. + * + * @param \Illuminate\Http\Request $request + * @param \Throwable $e + * @return \Symfony\Component\HttpFoundation\Response + * + * @throws \Throwable + */ + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + public function render($request, Throwable $e) + { + if (Helper::isRequestForApiV2($request) and $request->isJson()) { + switch (get_class($e)) { + case AuthenticationException::class: + /** @var AuthenticationException $e */ + return $this->getReponse( + $request, + 401, + ApiError::needAuth()->value, + sprintf('%s, %s', ApiError::needAuth()->label, $e->getMessage()) + ); + case MethodNotAllowedHttpException::class: + /** @var MethodNotAllowedHttpException $e */ + return $this->getReponse( + $request, + $e->getStatusCode(), + ApiError::methodNotAllowed()->value, + sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage()) + ); + case ValidationException::class: + /** @var ValidationException $e */ + return $this->getReponse( + $request, + $e->status, + ApiError::validationFailed()->value, + sprintf(ApiError::validationFailed()->label, $e->getMessage()), + ['errors' => $e->errors()] + ); + case ModelNotFoundException::class: + /** @var ModelNotFoundException $e */ + return $this->getReponse( + $request, + 404, + ApiError::modelNotFound()->value, + sprintf( + ApiError::modelNotFound()->label, + \array_slice(explode('\\', $e->getModel()), -1)[0] + ) + ); + case ApiErrorException::class: + /** @var ApiErrorException $e */ + return $this->getReponse( + $request, + $e->getStatusCode(), + $e->getCode(), + $e->getMessage() + ); + case ThrottleRequestsException::class: + /** @var ThrottleRequestsException $e */ + return $this->getReponse( + $request, + 429, + ApiError::throttleRequestsException()->value, + ApiError::throttleRequestsException()->label + ); + case AuthorizationException::class: + /** @var AuthorizationException $e */ + return $this->getReponse( + $request, + 403, + ApiError::authorizationException()->value, + ApiError::authorizationException()->label + ); + case CrmException::class: + default: + if (config('app.env') === 'testing') { + // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found + print_r((string)$e); + } + return $this->getReponse($request, 500, $e->getCode(), $e->getMessage()); + } + } + return parent::render($request, $e); + } + + /** + * Generate an ApiV2 json response + * + * @param Request $request + * @param integer $httpCode + * @param integer $apiCode + * @param string $message + * @return JsonResponse + */ + private static function getReponse( + Request $request, + int $httpCode, + int $apiCode, + string $message, + array $data = [] + ): JsonResponse { + return (new V2ApiResponse( + (object)array_merge( + $data, + [ + 'message' => $message, + 'code' => $apiCode + ] + ), + ApiStatus::KO() + ))->toResponse($request)->setStatusCode($httpCode); + } } diff --git a/app/Http/Controllers/Api/v1/ArcadiaController.php b/app/Http/Controllers/Api/V1/ArcadiaController.php similarity index 55% rename from app/Http/Controllers/Api/v1/ArcadiaController.php rename to app/Http/Controllers/Api/V1/ArcadiaController.php index 002331d..3ac0368 100644 --- a/app/Http/Controllers/Api/v1/ArcadiaController.php +++ b/app/Http/Controllers/Api/V1/ArcadiaController.php @@ -1,6 +1,6 @@ 'required_if:type,default|json' ]); - if ($validator->fails()) - return new StatusResource(['status' => 'KO', 'code' => 201, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]); + if ($validator->fails()) { + return new StatusResource([ + 'status' => 'KO', + 'code' => 201, + 'message' => 'Error on required fields.', + 'errors' => $validator->errors() + ]); + } - $brandId = $request->brand_id; - $crmId = $request->crm_id; - $fields = json_decode($request->fields); - $type = $request->type; + $brandId = $request->brand_id; + $crmId = $request->crm_id; + $fields = json_decode($request->fields); + $type = $request->type; $shipping = $type === 'default' ? json_decode($request->shipping) : false; $customer = Customer::where('id', $crmId)->first(); - if (!$customer) + if (!$customer) { return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']); + } - $customerInfos = $customer->infos()->where('brand_id', $brandId)->first(); + $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first(); - if (!$customerInfos) + if (!$CustomerInfo) { return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']); + } $check = $this->checkRequiredFields($fields, $type); - if ($check['status'] !== 'OK') + if ($check['status'] !== 'OK') { return new StatusResource($check); + } if ($type === 'default') { $check = $this->checkShippingRequiredFields($shipping); - if ($check['status'] !== 'OK') + if ($check['status'] !== 'OK') { return new StatusResource($check); + } } - $xml = $this->makeXML($fields, $type, $customer, $customerInfos, $shipping); + $xml = $this->makeXml($fields, $type, $customer, $CustomerInfo, $shipping); try { - Storage::disk('arcadia')->put(date ("Ymd_His") . microtime(true) . ".xml", $xml); + Storage::disk('arcadia')->put(date("Ymd_His") . microtime(true) . ".xml", $xml); return new StatusResource(['status' => 'OK']); - }catch (\Exception $e){ + } catch (\Exception $e) { Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage()); - Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError("ARCADIA XML CREATION FAILED", "Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(), $xml)); + Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError( + "ARCADIA XML CREATION FAILED", + "Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(), + $xml + )); return new StatusResource(['status' => 'KO', 'code' => 204, 'message' => 'Arcadia XML Creation failed.']); } - } private function checkShippingRequiredFields($fields) @@ -76,91 +89,112 @@ class ArcadiaController extends Controller ]; foreach ($test as $t) { - if (!array_key_exists($t, $fields)){ + if (!array_key_exists($t, $fields)) { $errors[] = ['field' => $t, 'error' => 'required']; } } - if (!empty($errors)) + if (!empty($errors)) { return ['status' => 'KO', 'code' => 203, 'message' => $errors]; + } return ['status' => 'OK']; - } + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh private function checkRequiredFields($fields, $type) { $errors = []; $key = array_search('operation', array_column($fields->root, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'operation', 'error' => 'required']; + } $key = array_search('support_code', array_column($fields->orderLine, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'support_code', 'error' => 'required']; + } $key = array_search('label_type_support', array_column($fields->orderLine, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'label_type_support', 'error' => 'required']; + } $key = array_search('order_date', array_column($fields->root, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'order_date', 'error' => 'required']; + } - if (!preg_match("/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/", $fields->root[$key]->fieldValue)) { + $regex = join('', [ + '/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|', + '((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))', + '|((0[1-9]|1\d|2[0-8])\/02\/((19|[2-9]\d)\d{2}))|(29\/02\/((1[6-9]|[2-9]\d)(0[48]', + '|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/' + ]); + if (!preg_match($regex, $fields->root[$key]->fieldValue)) { $errors[] = ['field' => 'order_date', 'error' => 'format must be DD/MM/YYYY']; } - if ($type === 'default'){ - + if ($type === 'default') { $key = array_search('order_num', array_column($fields->root, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'order_num', 'error' => 'required for default type']; + } - if (!is_numeric($fields->root[$key]->fieldValue) || strlen($fields->root[$key]->fieldValue) !== 9) + if (!is_numeric($fields->root[$key]->fieldValue) || strlen($fields->root[$key]->fieldValue) !== 9) { $errors[] = ['field' => 'order_num', 'error' => 'format must be 9 numeric characters']; + } $key = array_search('state_order_cb', array_column($fields->root, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'state_order_cb', 'error' => 'required for default type']; + } - if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') + if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') { $errors[] = ['field' => 'state_order_cb', 'error' => 'value must be 1 or Null (Yes Null not NULL)']; + } $orderCB = $fields->root[$key]->fieldValue; $key = array_search('state_order_check', array_column($fields->root, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'state_order_check', 'error' => 'required for default type']; + } - if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') + if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') { $errors[] = ['field' => 'state_order_check', 'error' => 'value must be 1 or Null (Yes Null not NULL)']; + } $orderCheck = $fields->root[$key]->fieldValue; - if ($orderCB === $orderCheck) - $errors[] = ['field' => 'state_order', 'error' => 'state_order_cb and state_order_check values must be different']; + if ($orderCB === $orderCheck) { + $errors[] = [ + 'field' => 'state_order', + 'error' => 'state_order_cb and state_order_check values must be different' + ]; + } $key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'package_unit_weight', 'error' => 'required for default type']; - - }elseif ($type === 'game'){ - + } + } elseif ($type === 'game') { $key = array_search('code_unique', array_column($fields->orderLine, 'fieldName')); - if ($key === false) + if ($key === false) { $errors[] = ['field' => 'code_unique', 'error' => 'required for game type']; - + } } - if (!empty($errors)) + if (!empty($errors)) { return ['status' => 'KO', 'code' => 203, 'message' => $errors]; + } return ['status' => 'OK']; } - private function makeXML($fields, $type, $customer, $customerInfos, $shipping) + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + private function makeXml($fields, $type, $customer, $customerInfos, $shipping) { $pivot = [ 'order_num' => 'Num_Commande', // required if type = default - format 9 numeric characters @@ -172,12 +206,14 @@ class ArcadiaController extends Controller 'qty' => 'Quantite', 'code_on_pack' => 'Code_on_Pack', 'code_unique' => 'Code_Unique', // required if type = game - 'state_order_cb' => 'Etat_Commande_CB', // required if type = default - format 1 ou Null (oui Null et pas NULL) - 'state_order_check' => 'Etat_Commande_Cheque', // required if type = default - format 1 ou Null (oui Null et pas NULL) + // required if type = default - format 1 ou Null (oui Null et pas NULL) + 'state_order_cb' => 'Etat_Commande_CB', + // required if type = default - format 1 ou Null (oui Null et pas NULL) + 'state_order_check' => 'Etat_Commande_Cheque', ]; - $dom = new DOMDocument('1.0','UTF-8'); + $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElement('export_arcadia'); @@ -185,74 +221,109 @@ class ArcadiaController extends Controller $order = $root->appendChild($dom->createElement('Commande')); $key = array_search('order_num', array_column($fields->root, 'fieldName')); - if ($key === false) + if ($key === false) { $order->appendChild($dom->createElement('Num_Commande')); - else + } else { $order->appendChild($dom->createElement('Num_Commande', $fields->root[$key]->fieldValue)); + } - foreach ($fields->root as $value){ - if (isset($pivot[$value->fieldName]) && $value->fieldName !== 'state_order_cb' && $value->fieldName !== 'state_order_check' && $value->fieldName !== 'order_num') + foreach ($fields->root as $value) { + // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace + if ( + isset($pivot[$value->fieldName]) && $value->fieldName !== 'state_order_cb' && + $value->fieldName !== 'state_order_check' && $value->fieldName !== 'order_num' + ) { $order->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue)); + } } if ($type === 'default') { $key = array_search('state_order_cb', array_column($fields->root, 'fieldName')); - if ($key) + if ($key) { $order->appendChild($dom->createElement('Etat_Commande_CB', $fields->root[$key]->fieldValue)); + } $key = array_search('state_order_check', array_column($fields->root, 'fieldName')); - if ($key) + if ($key) { $order->appendChild($dom->createElement('Etat_Commande_Cheque', $fields->root[$key]->fieldValue)); + } } - $order->appendChild($dom->createElement('Email_conso'))->appendChild($dom->createCDATASection( "$customer->email")); + $order->appendChild($dom->createElement('Email_conso')) + ->appendChild($dom->createCDATASection("$customer->email")); $order->appendChild($dom->createElement('Civilite')); if ($type === 'default') { - $order->appendChild($dom->createElement('Nom'))->appendChild($dom->createCDATASection("$shipping->lastname")); - $order->appendChild($dom->createElement('Prenom'))->appendChild($dom->createCDATASection("$shipping->firstname")); - $order->appendChild($dom->createElement('Adresse'))->appendChild($dom->createCDATASection("$shipping->address")); + $order->appendChild($dom->createElement('Nom')) + ->appendChild($dom->createCDATASection("$shipping->lastname")); + $order->appendChild($dom->createElement('Prenom')) + ->appendChild($dom->createCDATASection("$shipping->firstname")); + $order->appendChild($dom->createElement('Adresse')) + ->appendChild($dom->createCDATASection("$shipping->address")); $order->appendChild($dom->createElement('Adresse_Complementaire')); - $order->appendChild($dom->createElement('CP'))->appendChild($dom->createCDATASection("$shipping->postcode")); + $order->appendChild($dom->createElement('CP')) + ->appendChild($dom->createCDATASection("$shipping->postcode")); $order->appendChild($dom->createElement('Ville'))->appendChild($dom->createCDATASection("$shipping->city")); if (isset($shipping->phone) && !empty($shipping->phone)) { - $order->appendChild($dom->createElement('Telephone'))->appendChild($dom->createCDATASection("$shipping->phone")); - }else{ + $order->appendChild($dom->createElement('Telephone')) + ->appendChild($dom->createCDATASection("$shipping->phone")); + } else { $order->appendChild($dom->createElement('Telephone')); } } else { - $order->appendChild($dom->createElement('Nom'))->appendChild($dom->createCDATASection( "$customerInfos->lastname")); - $order->appendChild($dom->createElement('Prenom'))->appendChild($dom->createCDATASection( "$customerInfos->firstname")); - $order->appendChild($dom->createElement('Adresse'))->appendChild($dom->createCDATASection( "$customerInfos->address")); - $order->appendChild($dom->createElement('Adresse_Complementaire'))->appendChild($dom->createCDATASection( "$customerInfos->address2")); - $order->appendChild($dom->createElement('CP'))->appendChild($dom->createCDATASection( "$customerInfos->postcode")); - $order->appendChild($dom->createElement('Ville'))->appendChild($dom->createCDATASection( "$customerInfos->city")); - $order->appendChild($dom->createElement('Telephone'))->appendChild($dom->createCDATASection( "$customerInfos->phone")); + $order->appendChild($dom->createElement('Nom')) + ->appendChild($dom->createCDATASection("$customerInfos->lastname")); + $order->appendChild($dom->createElement('Prenom')) + ->appendChild($dom->createCDATASection("$customerInfos->firstname")); + $order->appendChild($dom->createElement('Adresse')) + ->appendChild($dom->createCDATASection("$customerInfos->address")); + $order->appendChild($dom->createElement('Adresse_Complementaire')) + ->appendChild($dom->createCDATASection("$customerInfos->address2")); + $order->appendChild($dom->createElement('CP')) + ->appendChild($dom->createCDATASection("$customerInfos->postcode")); + $order->appendChild($dom->createElement('Ville')) + ->appendChild($dom->createCDATASection("$customerInfos->city")); + $order->appendChild($dom->createElement('Telephone')) + ->appendChild($dom->createCDATASection("$customerInfos->phone")); } $orderLine = $order->appendChild($dom->createElement('Ligne_de_Commande')); $key = array_search('qty', array_column($fields->orderLine, 'fieldName')); - if ($key === false) + if ($key === false) { $orderLine->appendChild($dom->createElement('Quantite', '1')); + } $key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName')); - if ($key === false) + if ($key === false) { $orderLine->appendChild($dom->createElement('Poids_Unitaire_Colis')); + } - foreach ($fields->orderLine as $value){ - if (isset($pivot[$value->fieldName]) && $value->fieldName !== 'code_on_pack' && $value->fieldName !== 'code_unique') + foreach ($fields->orderLine as $value) { + // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace + if ( + isset($pivot[$value->fieldName]) && + $value->fieldName !== 'code_on_pack' && + $value->fieldName !== 'code_unique' + ) { $orderLine->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue)); + } } if ($type === 'game') { $key = array_search('code_on_pack', array_column($fields->orderLine, 'fieldName')); - if ($key) - $orderLine->appendChild($dom->createElement('Code_on_Pack'))->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue)); + if ($key) { + $orderLine->appendChild( + $dom->createElement('Code_on_Pack') + )->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue)); + } $key = array_search('code_unique', array_column($fields->orderLine, 'fieldName')); - if ($key) - $orderLine->appendChild($dom->createElement('Code_Unique'))->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue)); + if ($key) { + $orderLine->appendChild( + $dom->createElement('Code_Unique') + )->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue)); + } } return $dom->saveXML(); diff --git a/app/Http/Controllers/Api/v1/CustomerController.php b/app/Http/Controllers/Api/V1/CustomerController.php similarity index 62% rename from app/Http/Controllers/Api/v1/CustomerController.php rename to app/Http/Controllers/Api/V1/CustomerController.php index eaa6827..cee4ff1 100644 --- a/app/Http/Controllers/Api/v1/CustomerController.php +++ b/app/Http/Controllers/Api/V1/CustomerController.php @@ -1,12 +1,12 @@ 'date_format:Y-m-d' ]); - if ($validator->fails()) - return new StatusResource(['status' => 'KO', 'code' => 102, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]); + if ($validator->fails()) { + return new StatusResource([ + 'status' => 'KO', 'code' => 102, + 'message' => 'Error on required fields.', 'errors' => $validator->errors() + ]); + } - $brandId = $request->brand_id; - $email = filter_var($request->email, FILTER_SANITIZE_EMAIL); + $brandId = $request->brand_id; + $email = filter_var($request->email, FILTER_SANITIZE_EMAIL); $firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING); - $lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); - $optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING); - $hashcode = Str::uuid(); + $lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); + $optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING); + $hashcode = Str::uuid(); // Récupère le customer s'il existe déjà $customer = Customer::where('email', $email)->first(); // Si le customer n'existe pas déjà if (!$customer) { - $customer = new Customer(); + $customer = new Customer(); $customer->email = strtolower($email); $customer->save(); $singleOptin = SingleOptin::where([['email', $email], ['brand_id', $brandId]])->first(); - if ($singleOptin) + if ($singleOptin) { $singleOptin->delete(); + } } else { // Récupère les infos s'ils existent déjà - $customerInfos = CustomerInfos::where([['customer_id', $customer->id], ['brand_id', $brandId]])->first(); - if ($customerInfos) - return new StatusResource(['status' => 'KO', 'code' => 103, 'message' => 'User already registered on this site.']); + $CustomerInfo = CustomerInfo::where([['customer_id', $customer->id], ['brand_id', $brandId]])->first(); + if ($CustomerInfo) { + return new StatusResource([ + 'status' => 'KO', 'code' => 103, + 'message' => 'User already registered on this site.' + ]); + } } - $customerInfos = new CustomerInfos(); - $customerInfos->firstname = ucfirst(strtolower($firstname)); - $customerInfos->lastname = ucfirst(strtolower($lastname)); - $customerInfos->birth_date = !empty($request->birth_date) ? filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null; - $customerInfos->address = !empty($request->address) ? filter_var($request->address, FILTER_SANITIZE_STRING) : null; - $customerInfos->address2 = !empty($request->address2) ? filter_var($request->address2, FILTER_SANITIZE_STRING) : null; - $customerInfos->phone = !empty($request->phone) ? filter_var($request->phone, FILTER_SANITIZE_STRING) : null; - $customerInfos->country = !empty($request->country) ? filter_var($request->country, FILTER_SANITIZE_STRING) : null; - $customerInfos->city = !empty($request->city) ? filter_var($request->city, FILTER_SANITIZE_STRING) : null; - $customerInfos->postcode = !empty($request->postcode) ? filter_var($request->postcode, FILTER_SANITIZE_STRING) : null; - $customerInfos->opt_in = $optIn; - $customerInfos->hashcode = $hashcode; - $customerInfos->brand_id = $brandId; + $customerInfos = new CustomerInfo(); + $customerInfos->firstname = ucfirst(strtolower($firstname)); + $customerInfos->lastname = ucfirst(strtolower($lastname)); + $customerInfos->birth_date = !empty($request->birth_date) ? + filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null; + $customerInfos->address = !empty($request->address) ? + filter_var($request->address, FILTER_SANITIZE_STRING) : null; + $customerInfos->address2 = !empty($request->address2) ? + filter_var($request->address2, FILTER_SANITIZE_STRING) : null; + $customerInfos->phone = !empty($request->phone) ? + filter_var($request->phone, FILTER_SANITIZE_STRING) : null; + $customerInfos->country = !empty($request->country) ? + filter_var($request->country, FILTER_SANITIZE_STRING) : null; + $customerInfos->city = !empty($request->city) ? + filter_var($request->city, FILTER_SANITIZE_STRING) : null; + $customerInfos->postcode = !empty($request->postcode) ? + filter_var($request->postcode, FILTER_SANITIZE_STRING) : null; + $customerInfos->opt_in = $optIn; + $customerInfos->hashcode = $hashcode; + $customerInfos->brand_id = $brandId; $customerInfos->customer_id = $customer->id; $customer->infos()->save($customerInfos); // Création du login pour cet utilisateur sur ce service - $customerLogin = new CustomerLogin(); - $customerLogin->customer_id = $customer->id; - $customerLogin->password = Hash::make(trim($request->password)); + $customerLogin = new CustomerLogin(); + $customerLogin->customer_id = $customer->id; + $customerLogin->password = Hash::make(trim($request->password)); $customerLogin->last_connection = now(); - $customerLogin->brand_id = $brandId; - $customerLogin->status = 0; + $customerLogin->brand_id = $brandId; + $customerLogin->status = 0; // Par défaut à 0 pour activation par mail //$customerLogin->status = $request->status !== null ? 1 : 0; $customer->logins()->save($customerLogin); // Ajout dans la table d'historique des opt-in de la nouvelle valeur - $optInHistory = new CustomerOptInHistory(); + $optInHistory = new CustomerOptInHistory(); $optInHistory->customer_id = $customer->id; - $optInHistory->brand_id = $brandId; - $optInHistory->new_value = $optIn; + $optInHistory->brand_id = $brandId; + $optInHistory->new_value = $optIn; $optInHistory->save(); return new CustomerResource($customer, $brandId); @@ -112,20 +128,23 @@ class CustomerController extends Controller { $brandId = $request->brand_id; - if ($type === 'id'){ + if ($type === 'id') { $customer = Customer::find($value); - if (!$customer) + if (!$customer) { return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']); - }else if($type === 'email'){ + } + } elseif ($type === 'email') { $customer = Customer::where('email', $value)->first(); - if (!$customer) + if (!$customer) { return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']); + } - $customerInfos = $customer->infos()->where('brand_id', $brandId)->first(); + $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first(); - if (!$customerInfos) + if (!$CustomerInfo) { return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']); - }else{ + } + } else { return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']); } @@ -139,103 +158,112 @@ class CustomerController extends Controller * @param int $id * @return CustomerResource|StatusResource */ + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded public function update(Request $request, $id) { $validator = Validator::make($request->all(), [ 'birth_date' => 'date_format:Y-m-d' ]); - if ($validator->fails()) - return new StatusResource(['status' => 'KO', 'code' => 104, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]); + if ($validator->fails()) { + return new StatusResource([ + 'status' => 'KO', 'code' => 104, + 'message' => 'Error on required fields.', 'errors' => $validator->errors() + ]); + } - $brandId = $request->brand_id; + $brandId = $request->brand_id; $customer = Customer::find($id); - if (!$customer) + if (!$customer) { return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']); + } - $customerInfos = $customer->infos()->where('brand_id', $brandId)->first(); - if (!$customerInfos) + $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first(); + if (!$CustomerInfo) { return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']); + } - if ($request->has('firstname')) - $customerInfos->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING); + if ($request->has('firstname')) { + $CustomerInfo->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING); + } - if ($request->has('lastname')) - $customerInfos->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); + if ($request->has('lastname')) { + $CustomerInfo->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING); + } if ($request->has('birth_date')) { - if (!empty($request->birth_date)){ - $customerInfos->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->birth_date = null; + if (!empty($request->birth_date)) { + $CustomerInfo->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->birth_date = null; } } if ($request->has('address')) { - if (!empty($request->address)){ - $customerInfos->address = filter_var($request->address, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->address = null; + if (!empty($request->address)) { + $CustomerInfo->address = filter_var($request->address, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->address = null; } } if ($request->has('address2')) { - if (!empty($request->address2)){ - $customerInfos->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->address2 = null; + if (!empty($request->address2)) { + $CustomerInfo->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->address2 = null; } } if ($request->has('phone')) { - if (!empty($request->phone)){ - $customerInfos->phone = filter_var($request->phone, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->phone = null; + if (!empty($request->phone)) { + $CustomerInfo->phone = filter_var($request->phone, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->phone = null; } } if ($request->has('country')) { - if (!empty($request->country)){ - $customerInfos->country = filter_var($request->country, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->country = null; + if (!empty($request->country)) { + $CustomerInfo->country = filter_var($request->country, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->country = null; } } if ($request->has('city')) { - if (!empty($request->city)){ - $customerInfos->city = filter_var($request->city, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->city = null; + if (!empty($request->city)) { + $CustomerInfo->city = filter_var($request->city, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->city = null; } } if ($request->has('postcode')) { - if (!empty($request->postcode)){ - $customerInfos->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING); - }else{ - $customerInfos->postcode = null; + if (!empty($request->postcode)) { + $CustomerInfo->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING); + } else { + $CustomerInfo->postcode = null; } } if ($request->has('opt_in')) { $optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING); - if ($customerInfos->opt_in != $optIn) { + if ($CustomerInfo->opt_in != $optIn) { // Ajout dans la table d'historique des opt-in de la nouvelle valeur - $optInHistory = new CustomerOptInHistory(); + $optInHistory = new CustomerOptInHistory(); $optInHistory->customer_id = $customer->id; - $optInHistory->brand_id = $brandId; - $optInHistory->new_value = $optIn; + $optInHistory->brand_id = $brandId; + $optInHistory->new_value = $optIn; $optInHistory->save(); } - $customerInfos->opt_in = $optIn; + $CustomerInfo->opt_in = $optIn; } - $customerInfos->save(); + $CustomerInfo->save(); return new CustomerResource($customer, $brandId); } @@ -245,17 +273,22 @@ class CustomerController extends Controller * @param Request $request * @return CustomerResource|StatusResource */ - public function authenticate(Request $request) { + public function authenticate(Request $request) + { $validator = Validator::make($request->all(), [ 'email' => 'required|email', 'password' => 'required', ]); - if ($validator->fails()) - return new StatusResource(['status' => 'KO', 'code' => 115, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]); + if ($validator->fails()) { + return new StatusResource([ + 'status' => 'KO', 'code' => 115, + 'message' => 'Error on required fields.', 'errors' => $validator->errors() + ]); + } - $brandId = $request->brand_id; - $email = filter_var($request->email, FILTER_SANITIZE_EMAIL); + $brandId = $request->brand_id; + $email = filter_var($request->email, FILTER_SANITIZE_EMAIL); $password = trim($request->password); $customer = Customer::where('email', $email)->first(); if ($customer) { @@ -286,7 +319,8 @@ class CustomerController extends Controller 'hashcode' => $infos->hashcode, 'status' => $login->status, ] - ]); + ] + ); } $login->last_connection = now(); @@ -310,15 +344,20 @@ class CustomerController extends Controller * @param Request $request * @param $id */ - public function changePassword(Request $request, $id) { + public function changePassword(Request $request, $id) + { $validator = Validator::make($request->all(), [ 'password' => 'required', ]); - if ($validator->fails()) - return new StatusResource(['status' => 'KO', 'code' => 107, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]); + if ($validator->fails()) { + return new StatusResource([ + 'status' => 'KO', 'code' => 107, + 'message' => 'Error on required fields.', 'errors' => $validator->errors() + ]); + } - $brandId = $request->brand_id; + $brandId = $request->brand_id; $customer = Customer::find($id); $password = trim($request->password); @@ -326,7 +365,7 @@ class CustomerController extends Controller $customerLogin = $customer->logins()->where('brand_id', $brandId)->first(); if ($customerLogin) { // Check if same password - if (Hash::check($password, $customerLogin->password)){ + if (Hash::check($password, $customerLogin->password)) { return new StatusResource(['status' => 'KO', 'code' => 123, 'message' => 'Same password.']); } @@ -348,7 +387,10 @@ class CustomerController extends Controller * @param Request $request * @param $id */ - public function activateAccount(Request $request, $id) { + public function activateAccount( + Request $request, + $id + ) { $brandId = $request->brand_id; $customer = Customer::find($id); @@ -379,7 +421,9 @@ class CustomerController extends Controller * @param Request $request * @param $id */ - public function delete(Request $request, $id) { + // phpcs:disable Squiz.PHP.NonExecutableCode.Unreachable + public function delete(Request $request, $id) + { die("Not implemented yet"); // TODO : A voir pour les stats, obuton dans le bo qui permet de faire les stats sur les customer puis supprimer // TODO : Gestion avec mesBR @@ -409,7 +453,8 @@ class CustomerController extends Controller * @param Request $request * @param $id */ - public function toggleOptin(Request $request, $id) { + public function toggleOptin(Request $request, $id) + { // TODO : Liaison des opt-in avec MailJet $brandId = $request->brand_id; @@ -420,10 +465,10 @@ class CustomerController extends Controller $infos->opt_in = $infos->opt_in === 0 ? 1 : 0; // On enregistre l'historique du changement d'optin - $optinHistory = new CustomerOptInHistory(); + $optinHistory = new CustomerOptInHistory(); $optinHistory->customer_id = $customer->id; - $optinHistory->new_value = $infos->opt_in; - $optinHistory->brand_id = $brandId; + $optinHistory->new_value = $infos->opt_in; + $optinHistory->brand_id = $brandId; $optinHistory->save(); $infos->save(); @@ -444,11 +489,12 @@ class CustomerController extends Controller * @param Request $request * @param $hashcode */ - public function removeOptin(Request $request, $hashcode) { + public function removeOptin(Request $request, $hashcode) + { // TODO : Liaison des opt-in avec MailJet $brandId = $request->brand_id; - $infos = CustomerInfos::where([ + $infos = CustomerInfo::where([ ['brand_id', $brandId], ['hashcode', $hashcode] ])->first(); @@ -457,10 +503,10 @@ class CustomerController extends Controller $infos->opt_in = 0; // On enregistre l'historique du changement d'optin - $optinHistory = new CustomerOptInHistory(); + $optinHistory = new CustomerOptInHistory(); $optinHistory->customer_id = $infos->customer_id; - $optinHistory->new_value = 0; - $optinHistory->brand_id = $brandId; + $optinHistory->new_value = 0; + $optinHistory->brand_id = $brandId; $optinHistory->save(); $infos->save(); @@ -472,28 +518,41 @@ class CustomerController extends Controller } } - public function addSingleOptin(Request $request) { + public function addSingleOptin(Request $request) + { $validator = Validator::make($request->all(), [ 'email' => 'required|email', ]); - if ($validator->fails()) - return new StatusResource(['status' => 'KO', 'code' => 120, 'message' => 'Error on required fields.', 'errors' => $validator->errors()]); + if ($validator->fails()) { + return new StatusResource([ + 'status' => 'KO', 'code' => 120, + 'message' => 'Error on required fields.', 'errors' => $validator->errors() + ]); + } $brandId = $request->brand_id; - $email = filter_var($request->email, FILTER_SANITIZE_EMAIL); + $email = filter_var($request->email, FILTER_SANITIZE_EMAIL); // Check si le customer ou l'optin simple existe avant de l'ajouter en optin simple - if (!SingleOptin::where([['email', $email], ['brand_id', $brandId]])->exists() && - !CustomerInfos::join("customers", "customer_infos.customer_id", "=", "customers.id")->where([['brand_id', $brandId], ['email', $email]])->exists()) { - $singleOptin = new SingleOptin(); - $singleOptin->email = $email; + // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace + if ( + !SingleOptin::where([['email', $email], ['brand_id', $brandId]])->exists() && + !CustomerInfo::join("customers", "customer_infos.customer_id", "=", "customers.id")->where([ + ['brand_id', $brandId], ['email', $email] + ])->exists() + ) { + $singleOptin = new SingleOptin(); + $singleOptin->email = $email; $singleOptin->brand_id = $brandId; $singleOptin->save(); } else { // "Cet utilisateur est déjà inscrit à la newsletter pour cette marque" - return new StatusResource(['status' => 'KO', 'code' => 121, 'message' => 'This user is already subscribed to the newsletter for this brand.']); + return new StatusResource([ + 'status' => 'KO', 'code' => 121, + 'message' => 'This user is already subscribed to the newsletter for this brand.' + ]); } - return new StatusResource(['status' => 'OK' , 'message' => 'Optin added.']); + return new StatusResource(['status' => 'OK', 'message' => 'Optin added.']); } } diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php new file mode 100644 index 0000000..c5c402b --- /dev/null +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -0,0 +1,34 @@ +authorize('customerBelongsToService', $customer); + $existingAnswerIds = $customer->getAnswers($service)->pluck('id'); + $answersID = Answer::whereIn('id', $request->answers)->pluck('id'); + $customer->answers()->sync($answersID->merge($existingAnswerIds)); + return \response()->json(); + } +} diff --git a/app/Http/Controllers/Api/V2/ApiController.php b/app/Http/Controllers/Api/V2/ApiController.php new file mode 100644 index 0000000..b2b8502 --- /dev/null +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -0,0 +1,41 @@ +json(); + } + + public function authenticate(Request $request) + { + try { + $bearerToken = Helper::getBearerTokenFromRequest($request); + if (!$bearerToken) { + throw new ApiErrorException(ApiError::missingParameter()); + } + /** @var \App\Models\Service */ + $service = Service::getFromBearerToken($bearerToken); + } catch (ModelNotFoundException $e) { + throw new ApiErrorException(ApiError::authFailed()); + } + if (!Hash::check($request->bearer_pwd, $service->bearer_pwd)) { + throw new ApiErrorException(ApiError::authFailed()); + } + + $apiToken = $service->generateApiToken(); + $service->saveOrFail(); + return response()->json(['api_token' => $apiToken]); + } +} diff --git a/app/Http/Controllers/Api/V2/ArcadiaController.php b/app/Http/Controllers/Api/V2/ArcadiaController.php new file mode 100644 index 0000000..ca3c0bd --- /dev/null +++ b/app/Http/Controllers/Api/V2/ArcadiaController.php @@ -0,0 +1,115 @@ +authorize('customerBelongsToService', $customer); + return $this->handleArcadiaMail($request, ArcadiaMailType::default(), $customer, $service); + } + + public function outgoingMailGame(SendArchadiaGameMailRequest $request, Customer $customer, Service $service) + { + $this->authorize('customerBelongsToService', $customer); + return $this->handleArcadiaMail($request, ArcadiaMailType::game(), $customer, $service); + } + + private function handleArcadiaMail( + BaseApiRequest $request, + ArcadiaMailType $type, + Customer $customer, + Service $service + ) { + $request = $request->merge([ + 'order_number' => str_pad(\intval($request->order_number), 9, '0', STR_PAD_LEFT) + ]); + $xml = 'none'; + try { + /** @var \App\Models\CustomerInfo $customerInfo */ + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL); + $orderType = ArcadiaOrderType::make(intval($request->state_order)); + $xml = $this->makeXml($request, $type, $orderType, $customer, $customerInfo); + + // Format xml + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->preserveWhiteSpace = true; + $doc->formatOutput = true; + $doc->loadXML($xml); + $xml = $doc->saveXML(); + + Storage::disk('arcadia')->put(date("Ymd_His") . ".xml", $xml); + return response()->json(); + } catch (\Exception $e) { + Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage()); + if (!config('app.debug')) { + dispatch(function () use ($xml, $e) { + Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError( + "ARCADIA XML CREATION FAILED", + "Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(), + $xml + )); + })->afterResponse(); + } else { + throw $e; + } + throw new CrmException('Failed to create the arcadia xml file', 0, $e); + } + } + + /** + * render Arcadia xml for order + * + * @param SendArchadiaMailRequest $request + * @param ArcadiaMailType $type + * @param ArcadiaOrderType $orderType + * @param Customer $customer + * @param CustomerInfo $customerInfo + * @return string + */ + private function makeXml( + BaseApiRequest $request, + ArcadiaMailType $type, + ArcadiaOrderType $orderType, + Customer $customer, + CustomerInfo $customerInfo + ): string { + return view( + 'mail.arcadiaOrders.default', + [ + 'request' => $request, + 'type' => $type, + 'orderType' => $orderType, + 'customer' => $customer, + 'customerInfo' => $customerInfo + ] + )->render(); + } +} diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php new file mode 100644 index 0000000..06f48b4 --- /dev/null +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -0,0 +1,212 @@ +validated(); + /** @var \App\Models\Customer */ + $customer = Customer::where('email', $data->email)->firstOrCreate([ + 'email' => $data->email + ]); + $customer->saveOrFail(); + $customer->setOptIn($service->brand, $data->opt_in); + + // Delete Single optin if exists + if ( + $singleOptin = SingleOptin::where('email', $data->email) + ->where('brand_id', $service->brand->id)->first() + ) { + $singleOptin->delete(); + } + + /** @var \App\Models\CustomerInfo */ + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_NEW); + if ($customerInfo->exists) { + 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, Customer::FIRST_OR_NEW); + $customerLogin->fill($request->validated()); + $customerLogin->brand()->associate($service->brand); + $customerLogin->customer()->associate($customer); + $customerLogin->saveOrFail(); + + return new CustomerResource($service->brand, $customer); + } + + /** + * Display the specified resource. + * + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\Resources\Json\JsonResource + */ + public function show(Customer $customer, Service $service): JsonResource + { + $this->authorize('customerBelongsToService', $customer); + return new CustomerResource($service->brand, $customer); + } + + /** + * Update the specified resource in storage. + * + * @param \App\Request\UpdateCustomerRequest $request + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\Resources\Json\JsonResource + */ + public function update(UpdateCustomerRequest $request, Customer $customer, Service $service): JsonResource + { + $this->authorize('customerBelongsToService', $customer); + // To correct with create + $customer->fill($request->all()); + if ($customer->saveOrFail() === false) { + throw new CrmException(sprintf('Could not update customer %d', $customer)); + } + if ($request->has('opt_in')) { + $customer->setOptIn($service->brand, $request->opt_in); + } + + /** @var \App\Models\CustomerInfo */ + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL); + $customerInfo->fill($request->validated()); + $customerInfo->saveOrFail(); + + /** @var \App\Models\CustomerLogin */ + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + $customerLogin->fill($request->validated()); + $customerLogin->saveOrFail(); + + return new CustomerResource($service->brand, $customer); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(Customer $customer, Service $service): JsonResponse + { + $this->authorize('customerBelongsToService', $customer); + // TODO : A voir pour les stats, obuton dans le bo qui permet de faire les stats sur les customer puis supprimer + // TODO : Gestion avec mesBR + /** @var \App\Models\CustomerInfo */ + $customerInfo = $customer->info($service->brand, Customer::FIRST_OR_FAIL); + /** @var \App\Models\CustomerLogin */ + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + + if ( + !$customerInfo->delete() || + !$customerLogin->delete() || + $customer->optInHistory()->whereHas('brand', function ($q) use ($service) { + $q->where('id', $service->brand_id); + })->delete() === false + ) { + throw new CrmException(sprintf( + 'Could not delete customer %d for brand %d', + $customer->id, + $service->brand->id + )); + }; + + $logins = $customer->logins()->count(); + $infos = $customer->infos()->count(); + $history = $customer->optInHistory()->count(); + + // If customer is not associated with any brand then delete + if (!$logins and !$infos and !$history) { + if ($customer->delete() === false) { + throw new CrmException(sprintf( + 'Could not delete customer %d', + $customer->id + )); + } + } + + return response()->json(); + } + + /** + * Authenticate the user + * @param \App\Models\Service $service + * @param \App\Request\AuthenticateRequest $request + * @return \Illuminate\Http\Resources\Json\JsonResource + */ + public function authenticate(AuthenticateRequest $request, Service $service): JsonResource + { + try { + /** @var \App\Models\Customer */ + $customer = Customer::where('email', $request->email)->firstOrFail(); + $customer->loadBrandData($service->brand, true); + $customerLogin = $customer->logins->first(); + } 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); + } + + /** + * Activate the account of the user + * @param Customer $customer + * @param Service $service + * @return \Illuminate\Http\JsonResponse + */ + public function activateAccount(Customer $customer, Service $service) + { + $this->authorize('customerBelongsToService', $customer); + $login = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + + 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..37014ee --- /dev/null +++ b/app/Http/Controllers/Api/V2/OptinController.php @@ -0,0 +1,40 @@ +brand, $request->email)->exists()) { + throw new ApiErrorException(ApiError::singleOptinCustomerExists()); + } + + /** @var \App\Models\SingleOptin */ + $singleOptin = SingleOptin::where('email', $request->email)->firstOrNew(); + + if ($request->opt_in) { + $singleOptin->fill($request->validated()); + $singleOptin->brand()->associate($service->brand); + $singleOptin->saveOrFail(); + } else { + $singleOptin->delete(); + } + return response()->json(); + } +} diff --git a/app/Http/Controllers/Api/V2/QuestionController.php b/app/Http/Controllers/Api/V2/QuestionController.php new file mode 100644 index 0000000..d38ced5 --- /dev/null +++ b/app/Http/Controllers/Api/V2/QuestionController.php @@ -0,0 +1,62 @@ +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 = $customer->getAnswers($service); + 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/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php index 6043d9e..482f5d9 100644 --- a/app/Http/Controllers/BrandController.php +++ b/app/Http/Controllers/BrandController.php @@ -3,35 +3,27 @@ namespace App\Http\Controllers; use App\Http\Controllers\Controller; +use App\Http\Requests\ManageBrandRequest; use App\Models\Brand; -use Illuminate\Http\Request; class BrandController extends Controller { - public function index() { + public function index() + { $brands = Brand::all(); return view('brands.index', compact('brands')); } - public function manage(Request $request, $id = null) { - $brand = null; - - if ($id !== null) - $brand = Brand::find($id); - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $request->validate([ - 'name' => 'required|max:255', - ]); - + public function manage(ManageBrandRequest $request, Brand $brand) + { + if ($request->isMethod('post')) { if (!$brand) { - $brand = new Brand(); + $brand = new Brand($request->validated()); + } else { + $brand->fill($request->validated()); } - - $brand->name = filter_var($request->name, FILTER_SANITIZE_STRING); $brand->save(); - - return redirect()->route('brands')->with('success', 'Modifications enregistrées !'); + return redirect()->route('brands')->with('success', trans('Modifications enregistrées !')); } return view('brands.manage', compact('brand')); 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 8b87ebe..22e5f26 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -2,51 +2,70 @@ namespace App\Http\Controllers; -use App\Http\Requests\CustomerDeleteRequest; use App\Models\Brand; use App\Models\Customer; -use App\Models\CustomerInfos; +use App\Models\CustomerInfo; use App\Models\CustomerLogin; class CustomerController extends Controller { - public function index() { + public function index() + { $brands = Brand::select('id', 'name')->get(); return view('customers.index', compact('brands')); } - public function show($id, $brandId) { - $customerInfos = CustomerInfos::where([['customer_id', $id], ['brand_id', $brandId]])->first(); - $customerLogin = CustomerLogin::where([['customer_id', $id], ['brand_id', $brandId]])->first(); - if (!$customerInfos) - abort(404); + public function show(Customer $customer, Brand $brand) + { + $customerInfos = CustomerInfo::where('customer_id', $customer->id) + ->where('brand_id', $brand->id)->firstOrFail(); + $customerLogin = CustomerLogin::where('customer_id', $customer->id) + ->where('brand_id', $brand->id)->first(); return view('customers.show', compact('customerInfos', 'customerLogin')); } - public function filter($texte, $brandId = null) { - if ($brandId !== null) { + public function filter($texte, Brand $brand = null) + { + if ($brand) { $customers = Customer::join('customer_infos', 'customers.id', '=', 'customer_infos.customer_id') - ->join('customer_login', 'customers.id', '=', 'customer_login.customer_id') - ->select('customer_infos.customer_id', 'customer_infos.firstname', 'customer_infos.lastname', 'customer_infos.opt_in', 'customers.email', 'customer_infos.brand_id', 'customer_login.last_connection') - ->where([['lastname', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brandId], ['customer_login.brand_id', $brandId]]) - ->orWhere([['email', 'like', '%' . $texte . '%'], ['customer_infos.brand_id', $brandId], ['customer_login.brand_id', $brandId]]) - ->get(); + ->join('customer_logins', 'customers.id', '=', 'customer_logins.customer_id')->select( + 'customer_infos.customer_id', + 'customer_infos.firstname', + 'customer_infos.lastname', + 'customer_infos.opt_in', + 'customers.email', + 'customer_infos.brand_id', + 'customer_logins.last_connection' + )->where([ + ['lastname', 'like', '%' . $texte . '%'], + ['customer_infos.brand_id', $brand->id], + ['customer_logins.brand_id', $brand->id] + ])->orWhere([ + ['email', 'like', '%' . $texte . '%'], + ['customer_infos.brand_id', $brand->id], + ['customer_logins.brand_id', $brand->id] + ])->get(); } else { - $customers = CustomerInfos::join('customers', 'customers.id', '=', 'customer_infos.customer_id') - ->join('customer_login', function($join) { - $join->on('customer_login.customer_id', '=', 'customer_infos.customer_id'); - $join->on('customer_login.brand_id', '=', 'customer_infos.brand_id'); - }) - ->select('customer_infos.customer_id', 'customer_infos.firstname', 'customer_infos.lastname', 'customer_infos.opt_in', 'customers.email', 'customer_infos.brand_id', 'customer_login.last_connection') - ->where('lastname', 'like', '%' . $texte . '%') - ->orWhere('email', 'like', '%' . $texte . '%') - ->get(); + $customers = CustomerInfo::join('customers', 'customers.id', '=', 'customer_infos.customer_id') + ->join('customer_logins', function ($join) { + $join->on('customer_logins.customer_id', '=', 'customer_infos.customer_id'); + $join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id'); + })->select( + 'customer_infos.customer_id', + 'customer_infos.firstname', + 'customer_infos.lastname', + 'customer_infos.opt_in', + 'customers.email', + 'customer_infos.brand_id', + 'customer_logins.last_connection' + )->where('lastname', 'like', '%' . $texte . '%') + ->orWhere('email', 'like', '%' . $texte . '%')->get(); } return response()->json($customers); } - 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/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..2d45863 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -2,62 +2,49 @@ namespace App\Http\Controllers; +use App\Http\Requests\ManageServiceRequest; use App\Models\Brand; use App\Models\Service; -use Illuminate\Http\Request; -use Illuminate\Support\Str; class ServiceController extends Controller { - public function index() { + public function index() + { $services = Service::all(); return view('services.index', compact('services')); } - public function manage(Request $request, $id = null) { - $service = null; + public function manage(ManageServiceRequest $request, Service $service = null) + { $brands = Brand::all(); - $edit = false; - - if ($id !== null) { - $service = Service::find($id); - $edit = true; - } - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $request->validate([ - 'name' => 'required|max:255', - ]); + $edit = $service ? true : false; + if ($request->isMethod('post')) { if (!$service) { - $service = new Service(); - $apiToken = Str::random(80); - $service->api_token = hash('sha256', $apiToken); - $service->api_token_date = now(); + $service = new Service($request->validated()); + } else { + $service->fill($request->validated()); } - - $service->name = filter_var($request->name, FILTER_SANITIZE_STRING); - $service->brand_id = filter_var($request->brand_id, FILTER_SANITIZE_NUMBER_INT); + $brand = Brand::findOrFail($request->brand_id); + $service->brand()->associate($brand); $service->save(); - return redirect()->route('services')->with('success', $edit ? 'Modifications enregistrées !' : 'Token du service : ' . $apiToken); + return redirect()->route('services')->with('success', $edit ? 'Modifications enregistrées !' : sprintf( + 'Token du service : \'%s\'', + $service->bearer_token + )); } return view('services.manage', compact('service', 'brands', 'edit')); } - public function generateApiToken($serviceId) { - $service = Service::find($serviceId); - - if (!$service) - return back()->with('error', "Génération impossible : Service introuvable."); - - $apiToken = Str::random(80); - $service->api_token = hash('sha256', $apiToken); - $service->api_token_date = now(); - - $service->save(); - - return back()->with('success', "Nouveau token d'API de " . $service->name . " : " . $apiToken); + public function generateApiToken(Service $service) + { + $bearerToken = $service->generateBearerToken(); + $bearerPwd = $service->bearer_pwd; + $service->saveOrFail(); + return back() + // phpcs:ignore Generic.Files.LineLength.TooLong + ->with('success', "Nouveau token d'API de $service->name : '$bearerToken' et mot de passe '$bearerPwd'"); } } 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/Kernel.php b/app/Http/Kernel.php index f500585..ccf3d9e 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. * @@ -14,13 +15,13 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Fruitcake\Cors\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, - \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + \App\Http\Middleware\XssSanitizer::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class ]; /** @@ -33,7 +34,6 @@ class Kernel extends HttpKernel \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, - // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, @@ -41,7 +41,10 @@ class Kernel extends HttpKernel 'api' => [ 'throttle:api', - \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\JsonOnly::class, + \App\Http\Middleware\ApiResponse::class, + \App\Http\Middleware\ApiCheckTimeStamps::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class ], ]; diff --git a/app/Http/Middleware/ApiCheckTimeStamps.php b/app/Http/Middleware/ApiCheckTimeStamps.php new file mode 100644 index 0000000..09d61fc --- /dev/null +++ b/app/Http/Middleware/ApiCheckTimeStamps.php @@ -0,0 +1,42 @@ +hasHeader('X-API-TIMESTAMP') or !is_numeric($request->header('X-API-TIMESTAMP'))) { + throw new ApiErrorException(ApiError::wrongTimeStamp()); + } + if (!$request->timestamp or !is_numeric($request->timestamp)) { + throw new ApiErrorException(ApiError::wrongTimeStamp()); + } + $diff = intval($request->timestamp) - intval($request->header('X-API-TIMESTAMP')); + $maxDiff = 120; + if (abs($diff) > $maxDiff) { + throw new ApiErrorException(ApiError::wrongTimeStamp()); + } + } + return $next($request); + } +} diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php new file mode 100644 index 0000000..25ad814 --- /dev/null +++ b/app/Http/Middleware/ApiResponse.php @@ -0,0 +1,86 @@ +header('Cache-Control', 'No-Store'); + + $isApiV1Request = Helper::isRequestForApiV1($request); + $isApiV2Request = Helper::isRequestForApiV2($request); + + 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) { + $service = null; + $bearerToken = Helper::getBearerTokenFromRequest($request); + try { + if ($bearerToken) { + $service = Service::getFromBearerToken($bearerToken); + } + } 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())); + } + } + if ($service) { + $service->incrementStat(); + } + } + + if ($isApiV1Request) { + return $response; + } + + $data = $response->getData(); + + // Is reponse is already a V2ApiResponse then just return it + if ( + isset($data->status) and + isset($data->data) and + isset($data->version) and + in_array($data->status, [ApiStatus::OK()->value, ApiStatus::KO()->value]) + ) { + return $response; + } + + /** @ var \App\Enums\ApiStatus */ + $status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO(); + return $response->setData(new V2ApiResponse((object)$response->getData(), $status)); + } +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 704089a..6832d89 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -2,10 +2,45 @@ namespace App\Http\Middleware; +use App\Lib\Helper; +use App\Models\Service; +use Closure; use Illuminate\Auth\Middleware\Authenticate as Middleware; +/** + * Updated for ApiV2 + */ class Authenticate extends Middleware { + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @param string[] ...$guards + * @return mixed + * + * @throws \Illuminate\Auth\AuthenticationException + */ + public function handle($request, Closure $next, ...$guards) + { + // TODO: check this works + if (Helper::isRequestForApiV1($request)) { + // Use patched v1 Auth mecanism (without bearer reimplementation) + $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token ?? ''; + $brand = Service::getFromBearerToken($bearerToken)->brand; + $request->request->add(['brand_id' => $brand->id]); + $response = $next($request); + } else { + // Use Laravel auth check mecanism + $response = \call_user_func_array( + [$this, 'parent::' . __FUNCTION__], + array_merge([$request, $next], $guards) + ); + } + + return $response; + } /** * Get the path the user should be redirected to when they are not authenticated. * diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php index 0fda77b..e0276f8 100644 --- a/app/Http/Middleware/CheckBrand.php +++ b/app/Http/Middleware/CheckBrand.php @@ -2,17 +2,14 @@ namespace App\Http\Middleware; -use App\Models\Brand; +use App\Lib\Helper; use App\Models\Service; -use App\Models\ServiceStat; use Closure; -use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; class CheckBrand { - private $apiToken; - /** * Handle an incoming request. * @@ -22,25 +19,17 @@ class CheckBrand */ public function handle(Request $request, Closure $next) { - if ($request->bearerToken() !== null) - $this->apiToken = $request->bearerToken(); - else - $this->apiToken = ($request->api_token !== null ? $request->api_token : null); + $apiToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token; - if ($this->apiToken === null) - die("Le token d'authentification est incorrect"); - $brand = Brand::whereHas('services', function (Builder $query) { - $query->where("api_token", hash('sha256', $this->apiToken)); - })->first(); + if ($apiToken === null) { + abort(401, 'Le token d\'authentification est incorrect dfd' . \json_encode($request->all())); + } - if (!$brand) - die("Le token d'authentification est incorrect"); - - $serviceStat = ServiceStat::firstOrNew(['date' => date('Y-m-d')]); - $serviceStat->brand_id = $brand->id; - $serviceStat->service_id = Service::where('api_token', hash('sha256', $this->apiToken))->first()->id; - $serviceStat->nb_calls = $serviceStat->nb_calls === 0 ? 1 : $serviceStat->nb_calls + 1; - $serviceStat->save(); + try { + $brand = Service::getFromBearerToken($apiToken)->brand; + } catch (ModelNotFoundException $e) { + abort(401, 'Le token d\'authentification est incorrect ' . $e->getMessage()); + } $request->request->add(['brand_id' => $brand->id]); return $next($request); 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/JsonOnly.php b/app/Http/Middleware/JsonOnly.php new file mode 100644 index 0000000..2930d72 --- /dev/null +++ b/app/Http/Middleware/JsonOnly.php @@ -0,0 +1,27 @@ +isJson()) { + abort(400); + } + + return $next($request); + } +} 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..4f6a710 --- /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..4dec00b --- /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/SendArchadiaDefaultMailRequest.php b/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php new file mode 100644 index 0000000..d554900 --- /dev/null +++ b/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php @@ -0,0 +1,44 @@ + Trim::class, + 'operation' => Trim::class, + 'order_date' => Trim::class, + 'order_num' => RemoveNonNumeric::class, + 'package_unit_weight' => RemoveNonNumeric::class, + 'support_code' => Trim::class, + ]; + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'label_type_support' => 'required|string', + 'operation' => 'required|int', + 'order_date' => 'required|date', + 'order_num' => 'required|numeric', + 'package_unit_weight' => 'required|integer', + 'state_order' => [ + 'required', + new EnumIsValidRule(ArcadiaOrderType::class) + ], + 'support_code' => 'required|int', + ]; + } +} 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/SingleOptinRequest.php b/app/Http/Requests/Api/V2/SingleOptinRequest.php new file mode 100644 index 0000000..9a2fda2 --- /dev/null +++ b/app/Http/Requests/Api/V2/SingleOptinRequest.php @@ -0,0 +1,14 @@ + 'required|email:rfc,dns,spoof,filter', + 'opt_in' => 'required|boolean' + ]; + } +} 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/Requests/Api/V2/StoreCustomerRequest.php b/app/Http/Requests/Api/V2/StoreCustomerRequest.php new file mode 100644 index 0000000..a3f7f6d --- /dev/null +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -0,0 +1,48 @@ + 'required|string|min:2', + 'lastname' => 'required|string|min:2', + 'email' => 'required|email:rfc,dns,spoof,filter', + 'password' => [ + 'required', + Password::min(8) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + ], + 'birth_date' => 'required|date_format:Y-m-d', + 'address' => 'sometimes|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' => 'sometimes|string', + 'postcode' => 'sometimes|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..1da9a71 --- /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..c36a319 --- /dev/null +++ b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php @@ -0,0 +1,62 @@ + rtrim(\implode(',', $fields), ',') + ])); + } + }; + return [ + 'firstname' => [$atLeast, 'string', 'min:2'], + 'lastname' => [$atLeast, 'string', 'min:2'], + 'email' => [$atLeast, 'email:rfc,dns,spoof,filter'], + 'password' => [ + $atLeast, Password::min(8) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + ], + 'birth_date' => [$atLeast, 'date_format:Y-m-d'], + 'address' => [$atLeast, 'required_with:address2', 'string'], + 'address2' => ['sometimes', 'string'], + 'phone' => [$atLeast, 'string'], + 'country' => [ + $atLeast, + 'numeric', + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!\in_array($value, Country::toValues())) { + $fail(trans('not_in')); + } + } + ], + 'city' => [$atLeast, 'string'], + 'postcode' => [$atLeast, 'string'], + 'opt_in' => [$atLeast, 'boolean'], + ]; + } +} diff --git a/app/Http/Requests/Api/V2/UpdateSingleOptinRequest.php b/app/Http/Requests/Api/V2/UpdateSingleOptinRequest.php new file mode 100644 index 0000000..199954e --- /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/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]); - } -} 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/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/ApiResponse.php b/app/Http/Resources/Api/V2/ApiResponse.php new file mode 100644 index 0000000..abc20dc --- /dev/null +++ b/app/Http/Resources/Api/V2/ApiResponse.php @@ -0,0 +1,44 @@ +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..da2a64a --- /dev/null +++ b/app/Http/Resources/Api/V2/CustomerResource.php @@ -0,0 +1,86 @@ + '', + 'firstname' => '', + 'lastname' => '', + 'email' => '', + 'birth_date' => '', + 'address' => '', + 'address2' => '', + 'phone' => '', + 'country' => '', + 'city' => '', + 'postcode' => '', + 'opt_in' => '', + 'status' => '', + 'last_connection' => '' + ]; + + public function __construct(Brand $brand, Customer $customer) + { + $this->customer = $customer; + $this->info = $customer->info($brand, Customer::FIRST_OR_FAIL); + $this->login = $customer->login($brand, Customer::FIRST_OR_FAIL); + } + + /** + * Transform the resource into an array. + * + * @return array + * + firstname string + * + lastname string + * + birth_date datetime + * + address string + * + address2 string + * + phone string + * + country string + * + city string + * + postcode string + * + opt_in boolean + * + hashcode string + * + status boolean + */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + public function toArray($request = null) + { + // patch country TODO remove this when all crm are migrated to v2 + $data = array_intersect_key( + array_merge( + [ + 'email' => $this->customer->email + ], + $this->login->toArray(), + $this->info->toArray(), + ['status' => $this->login->status] + ), + $this->getSkeleton() + ); + + $data['country'] = $this->info->newcountry->value; + return $data; + } + + public static function getSkeleton(): array + { + return self::$skeleton; + } +} 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..336bf63 --- /dev/null +++ b/app/Http/Resources/Api/V2/QuestionsResource.php @@ -0,0 +1,54 @@ +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) { + /** @var \Illuminate\Support\Collection */ + $answers = $question->answers; + $question = [ + 'id' => $question->id, + 'type' => $question->type->value, + 'question' => $question->question, + 'answers' => new AnswersResource($answers) + ]; + if ($this->userAnwsers) { + $question['customerAnswers'] = new AnswersResource($answers->filter(function (Answer $answer) { + return $this->userAnwsers->keyBy('id')->has($answer->id); + })); + } + return $question; + }); + } +} diff --git a/app/Http/Resources/Api/V2/SingleOptinResource.php b/app/Http/Resources/Api/V2/SingleOptinResource.php new file mode 100644 index 0000000..8a05868 --- /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 3d16d2e..66102dd 100644 --- a/app/Http/Resources/CustomerResource.php +++ b/app/Http/Resources/CustomerResource.php @@ -6,9 +6,11 @@ use Illuminate\Http\Resources\Json\JsonResource; class CustomerResource extends JsonResource { + private $brandId; - public function __construct($resource, $brandId) { + public function __construct($resource, $brandId) + { parent::__construct($resource); $this->brandId = $brandId; } @@ -19,6 +21,7 @@ class CustomerResource extends JsonResource * @param \Illuminate\Http\Request $request * @return array */ + //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass public function toArray($request) { $infos = $this->infos()->where('brand_id', $this->brandId)->first(); 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/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/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); + } + } +} diff --git a/app/Lib/Helper.php b/app/Lib/Helper.php new file mode 100644 index 0000000..1c39be1 --- /dev/null +++ b/app/Lib/Helper.php @@ -0,0 +1,117 @@ +path(), 'api/v2') === 0 ? 'apiV2.' : null) ?? 'none'; + return strpos($routeName, 'apiV2.') === 0; + } + + /** + * Tells if the request is within apiV1 routes + * + * @param Request $request + * @return boolean + */ + public static function isRequestForApiV1(Request $request): bool + { + // Small hack to ensure to know if route is From APIv1 + $routePrefix = (strpos(static::getRoutePrefix($request), 'api/v1') ? 'v1' : null) ?? + (strpos($request->path(), 'api/v1') === 0 ? 'v1' : null) ?? 'none'; + return strpos($routePrefix, 'v1') === 0; + } + + /** + * Try to get current route name + * + * @param Request $request + * @return string|null + */ + public static function getRouteName(Request $request) + { + return Route::currentRouteName() ?? ($request->route() ? $request->route()->getName() : null); + } + + /** + * Try to get current route prefix + * + * @param Request $request + * @return string|null + */ + public static function getRoutePrefix(Request $request) + { + return (Route::getCurrentRoute() ? Route::getCurrentRoute()->getPrefix() : null) ?? + ($request->route() ? $request->route()->getPrefix() : null); + } + + /** + * Try to get route from request + * + * @param Request $request + * @return \Illuminate\Routing\Route|null + */ + public static function getRoute(Request $request) + { + return Route::getRoutes()->getByName(static::getRouteName($request)); + } + + /** + * Get composer.json infos + * + * @return array + */ + public static function getPackageInfo(): array + { + if (!static::$packageInfos) { + static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../../composer.json'), true); + } + return static::$packageInfos; + } + + /** + * 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; + } +} 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/Mail/CourrierArcadiaError.php b/app/Mail/CourrierArcadiaError.php index 889a893..52a6255 100644 --- a/app/Mail/CourrierArcadiaError.php +++ b/app/Mail/CourrierArcadiaError.php @@ -9,9 +9,14 @@ use Illuminate\Queue\SerializesModels; class CourrierArcadiaError extends Mailable implements ShouldQueue { - use Queueable, SerializesModels; + use Queueable; + use SerializesModels; - public $subject, $msg, $xml; + public $subject; + + public $msg; + + public $xml; /** * Create a new message instance. @@ -21,8 +26,8 @@ class CourrierArcadiaError extends Mailable implements ShouldQueue public function __construct($subject, $msg, $xml) { $this->subject = $subject; - $this->msg = $msg; - $this->xml = $xml; + $this->msg = $msg; + $this->xml = $xml; } /** @@ -33,6 +38,18 @@ class CourrierArcadiaError extends Mailable implements ShouldQueue public function build() { $msg = $this->msg; - return $this->subject("[LDC CRM] ". $this->subject)->view('mail.arcadia-error', compact('msg'))->attachData($this->xml, date ("Ymd_His") . '.xml', ['mime' => 'application/xml']); + return $this->subject( + sprintf( + "[LDC CRM] %s", + $this->subject + ) + )->view( + 'mail.arcadia-error', + compact('msg') + )->attachData( + $this->xml, + date("Ymd_His") . '.xml', + ['mime' => 'application/xml'] + ); } } diff --git a/app/Mail/ExceptionAlert.php b/app/Mail/ExceptionAlert.php new file mode 100644 index 0000000..48ca654 --- /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', 'informatique.internet@ldc.fr'), + sprintf('%s Exception Handler', config('app.name')) + ); + } +} diff --git a/app/Models/Answer.php b/app/Models/Answer.php new file mode 100644 index 0000000..f85e234 --- /dev/null +++ b/app/Models/Answer.php @@ -0,0 +1,35 @@ +belongsTo(Question::class); + } + + /** + * Answer belongsToMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function customers() + { + return $this->belongsToMany(Customer::class, 'customer_answer'); + } +} diff --git a/app/Models/Brand.php b/app/Models/Brand.php index 5a54e33..d80fae1 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -4,34 +4,113 @@ 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 $table = "brands"; + protected $cacheFor = 0; - public function services() { - return $this->hasMany('App\Models\Service', 'brand_id'); + protected $fillable = [ + 'name' + ]; + + public function __construct(array $attributes = []) + { + $this->cacheFor = config('api.servicesDBCache') * 60; + parent::__construct($attributes); } - public function customer_infos() { - return $this->hasMany('App\Models\CustomerInfos', 'brand_id'); + /** + * 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', + ]; } - public function services_stats() { - return $this->hasMany('App\Models\ServiceStat', 'brand_id'); + 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(); + }); } - public function single_optins() { - return $this->hasMany('App\Models\SingleOptin', 'brand_id'); + /** + * Service hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function services() + { + return $this->hasMany(Service::class); } - public function getAccountsAttribute(){ - return $this->customer_infos()->count(); + /** + * Question hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function questions() + { + return $this->hasMany(Question::class); } - public function getTotalOptinsAttribute(){ - return $this->customer_infos()->where('opt_in', 1)->count() + $this->single_optins()->count(); + /** + * CustomerInfo hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function customerInfos() + { + return $this->hasMany(CustomerInfo::class); + } + + /** + * ServiceStat hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servicesStats() + { + return $this->hasMany(ServiceStat::class); + } + + /** + * SingleOptin hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function singleOptins() + { + return $this->hasMany(SingleOptin::class); + } + + public function getAccountsAttribute() + { + return $this->customerInfos()->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 903a462..112f5d6 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -2,24 +2,190 @@ namespace App\Models; +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 { use HasFactory; - protected $table = "customers"; + public const FIRST_OR_NULL = 0; + public const FIRST_OR_NEW = 1; + public const FIRST_OR_FAIL = 2; - public function infos() { - return $this->hasMany('App\Models\CustomerInfos', 'customer_id'); + protected $fillable = [ + 'email' + ]; + + /** + * CustomerInfo hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function infos() + { + return $this->hasMany(CustomerInfo::class); } - public function logins() { - return $this->hasMany('App\Models\CustomerLogin', 'customer_id'); + /** + * Get the CustomerInfo from a specific brand + * 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, $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerInfo + { + 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) { + $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(); } - public function optInHistory() { - return $this->hasMany('App\Models\CustomerOptInHistory', 'customer_id'); + /** + * CustomerLogin hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function logins() + { + return $this->hasMany(CustomerLogin::class); + } + + /** + * Get the CustomerLogin from a specific brand + * 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, $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerLogin + { + 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, bool $orFail = false): self + { + $this->load([ + 'logins' => function ($q) use ($brand) { + $q->where('brand_id', $brand->id); + }, + 'infos' => function ($q) use ($brand) { + $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; + } + + /** + * CustomerOptInHistory hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function optInHistory() + { + return $this->hasMany(CustomerOptInHistory::class); + } + + /** + * Customer Brand belongsToMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\belongsToMany + */ + public function brands() + { + return $this->belongsToMany(Brand::class, CustomerInfo::class); + } + + /** + * Answers belongsToMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function answers() + { + return $this->belongsToMany(Answer::class, 'customer_answer'); + } + + public function setOptIn(Brand $brand, bool $optIn) + { + // Ajout dans la table d'historique des opt-in de la nouvelle valeur + $optInHistory = new CustomerOptInHistory(); + $optInHistory->customer_id = $this->id; + $optInHistory->brand_id = $brand->id; + $optInHistory->new_value = $optIn; + $optInHistory->saveOrFail(); + } + + /** + * 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(); } } 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 @@ + Country::class + ]; + + protected $fillable = [ + 'firstname', + 'lastname', + 'birth_date', + 'address', + 'address2', + 'phone', + 'newcountry', + 'country', + 'city', + 'postcode', + '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) { + $customerInfo->hashcode = Str::uuid(); + if (ctype_digit($customerInfo->country)) { + try { + $customerInfo->newcountry = Country::make((int)$customerInfo->country); + } catch (\TypeError $e) { + // TODO change this when country column is updated + // Set afghanistan as default country for now + $customerInfo->newcountry = Country::afghanistan(); + } + } + if (\is_object($customerInfo->country) and \get_class($customerInfo->country) === Country::class) { + $customerInfo->newcountry = $customerInfo->country; + $customerInfo->country = $customerInfo->newcountry->label; + } + if (!$customerInfo->newcountry) { + // TODO change this when country column is updated + // Set afghanistan as default country for now + $customerInfo->newcountry = Country::afghanistan(); + } + + if (!$customerInfo->newcountry->equals(Country::afghanistan())) { + $customerInfo->country = $customerInfo->newcountry->label; + } + }); + static::updating(function ($customerInfo) { + if (ctype_digit($customerInfo->country)) { + try { + $customerInfo->newcountry = Country::make((int)$customerInfo->country); + } catch (\TypeError $e) { + // TODO change this when country column is updated + // Set afghanistan as default country for now + $customerInfo->newcountry = Country::afghanistan(); + } + } + if (\is_object($customerInfo->country) and \get_class($customerInfo->country) === Country::class) { + $customerInfo->newcountry = $customerInfo->country; + $customerInfo->country = $customerInfo->newcountry->label; + } + if (!$customerInfo->newcountry) { + // TODO change this when country column is updated + // Set afghanistan as default country for now + $customerInfo->newcountry = Country::afghanistan(); + } + + if (!$customerInfo->newcountry->equals(Country::afghanistan())) { + $customerInfo->country = $customerInfo->newcountry->label; + } + }); + } + + /** + * Customer BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function customer() + { + return $this->belongsTo(Customer::class); + } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); + } + + /** + * Get customer info from email + * + * @param Brand $brand + * @param string $email + * @return \Illuminate\Database\Eloquent\Builder + */ + public static function getFromEmail(Brand $brand, string $email) + { + return CustomerInfo::whereHas('brand', function ($q) use ($brand) { + $q->where('id', $brand->id); + })->whereHas('customer', function ($q) use ($email) { + $q->where('email', $email); + }); + } +} 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..6922714 100644 --- a/app/Models/CustomerLogin.php +++ b/app/Models/CustomerLogin.php @@ -4,14 +4,52 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Hash; class CustomerLogin extends Model { use HasFactory; - protected $table = "customer_login"; + protected $fillable = [ + 'password' + ]; - public function customer() { - return $this->belongsTo('App\Models\Customer', 'customer_id'); + protected static function booted() + { + static::creating(function ($customerLogin) { + if (Hash::needsRehash($customerLogin->password)) { + $customerLogin->password = Hash::make(trim($customerLogin->password)); + } + }); + static::updating(function ($customerLogin) { + if (Hash::needsRehash($customerLogin->password)) { + $customerLogin->password = Hash::make(trim($customerLogin->password)); + } + }); + } + + public function passwordMatch(string $password) + { + return Hash::check($password, $this->password); + } + + /** + * Customer BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function customer() + { + return $this->belongsTo(Customer::class); + } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); } } diff --git a/app/Models/CustomerOptInHistory.php b/app/Models/CustomerOptInHistory.php index 28d069d..6d2ca9a 100644 --- a/app/Models/CustomerOptInHistory.php +++ b/app/Models/CustomerOptInHistory.php @@ -11,7 +11,23 @@ class CustomerOptInHistory extends Model protected $table = "customer_opt_in_history"; - public function customer() { - return $this->belongsTo('App\Models\Customer', 'customer_id'); + /** + * Customer BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function customer() + { + return $this->belongsTo(Customer::class); + } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); } } diff --git a/app/Models/Question.php b/app/Models/Question.php new file mode 100644 index 0000000..e69f3e0 --- /dev/null +++ b/app/Models/Question.php @@ -0,0 +1,43 @@ + 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/Service.php b/app/Models/Service.php index 7a213dd..fc571de 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,20 +2,200 @@ namespace App\Models; +use App\Jobs\SetServiceStats; 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; +use Rennokki\QueryCache\Traits\QueryCacheable; class Service extends Model { use HasFactory; + use QueryCacheable; - protected $table = "services"; + protected $cacheFor = 0; - public function brand() { - return $this->belongsTo('App\Models\Brand', 'brand_id'); + protected $fillable = [ + 'name' + ]; + + public function __construct(array $attributes = []) + { + $this->cacheFor = config('api.servicesDBCache') * 60; + parent::__construct($attributes); } - public function services_stats() { - return $this->hasMany('App\Models\ServiceStat', 'service_id'); + /** + * 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) { + $service->generateBearerToken(); + if (Hash::needsRehash($service->bearer_token)) { + $service->bearer_token = Hash::make($service->bearer_token); + } + if (Hash::needsRehash($service->bearer_pwd)) { + $service->bearer_pwd = Hash::make($service->bearer_pwd); + } + if (Hash::needsRehash($service->api_token)) { + $service->api_token = Hash::make($service->api_token); + } + }); + static::updating(function (self $service) { + if (Hash::needsRehash($service->bearer_token)) { + $service->bearer_token = Hash::make($service->bearer_token); + } + if (Hash::needsRehash($service->bearer_pwd)) { + $service->bearer_pwd = Hash::make($service->bearer_pwd); + } + if (Hash::needsRehash($service->api_token)) { + $service->api_token = Hash::make($service->api_token); + } + }); + static::created(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + static::updated(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + static::deleted(function () { + // flush Eloquent cache + self::flushQueryCache(); + }); + } + + /** + * Generates a new Api Token + * + * @return string + */ + public function generateApiToken(): string + { + $this->api_token = hash('sha256', Str::uuid()); + $this->api_token_date = now(); + return $this->api_token; + } + + /** + * Generates new bearer credentials + * + * @return string + */ + public function generateBearerToken(): string + { + $this->bearer_token = hash('sha256', Str::uuid()); + $this->bearer_token_date = now(); + $this->bearer_pwd = hash('ripemd160', Str::uuid()); + return $this->bearer_token; + } + + /** + * Increment API CRM Stat + * + * @return void + */ + public function incrementStat(): void + { + SetServiceStats::dispatch($this)->onConnection('database'); + } + + /** + * Get Service from apiToken with its brand + * + * @param string $apiToken + * @return \App\Models\Service + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public static function getFromApiToken(string $apiToken): self + { + $services = static::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->load([ + 'brand' => function ($query) use ($service) { + return $query->cacheTags(["service:{$service->id}:brand"]); + }, + ]); + } + } + throw new ModelNotFoundException('Could not find service from Api Token'); + } + + /** + * Get Service from bearerToken with its brand + * + * @param string $bearerToken + * @return \App\Models\Service + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public static function getFromBearerToken(string $bearerToken): self + { + $services = static::all(); + /** @var \App\Models\Service $service */ + foreach ($services as $service) { + if ($service->bearer_token and Hash::check($bearerToken, $service->bearer_token)) { + 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'); + } + + /** + * Authenticate from the api token string + * + * @param array $guards + * @param string $apiToken + * @return bool + */ + public function authenticateApi(string $apiToken): bool + { + return $this->api_token and Hash::check($apiToken, $this->api_token) and + ((abs(strtotime($this->api_token_date) - time()) * 60) < (config('api.tokenTimeout') * 60)); + } + + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); + } + + /** + * ServiceStat hasMany relation + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function servicesStats() + { + return $this->hasMany(ServiceStat::class); } } diff --git a/app/Models/ServiceStat.php b/app/Models/ServiceStat.php index 6495715..9750b84 100644 --- a/app/Models/ServiceStat.php +++ b/app/Models/ServiceStat.php @@ -11,13 +11,27 @@ class ServiceStat extends Model protected $fillable = ['date']; - protected $table = "services_stats"; + protected $attributes = [ + 'nb_calls' => 0 + ]; - public function brand() { - return $this->belongsTo('App\Models\Brand', 'brand_id'); + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); } - public function service() { - return $this->belongsTo('App\Models\Service', 'service_id'); + /** + * Service BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function service() + { + return $this->belongsTo(Service::class); } } diff --git a/app/Models/SingleOptin.php b/app/Models/SingleOptin.php index 29f8465..7776219 100644 --- a/app/Models/SingleOptin.php +++ b/app/Models/SingleOptin.php @@ -9,9 +9,17 @@ class SingleOptin extends Model { use HasFactory; - protected $table = "single_optin"; + protected $fillable = [ + 'email' + ]; - public function brand() { - return $this->belongsTo('App\Models\Brand', 'brand_id'); + /** + * Brand BelongsTo relation + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function brand() + { + return $this->belongsTo(Brand::class); } } diff --git a/app/Models/User.php b/app/Models/User.php index 804799b..6232f42 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,14 +2,14 @@ namespace App\Models; -use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { - use HasFactory, Notifiable; + use HasFactory; + use Notifiable; /** * The attributes that are mass assignable. diff --git a/app/Policies/CustomerPolicy.php b/app/Policies/CustomerPolicy.php new file mode 100644 index 0000000..32e33ee --- /dev/null +++ b/app/Policies/CustomerPolicy.php @@ -0,0 +1,17 @@ +login($service->brand)); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c8242a1..df4b423 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 @@ -24,9 +25,11 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { + //! Important pourra etre retiré quand la V1 sera supprimé + // empeche l'encapsulation dans data JsonResource::withoutWrapping(); - if(config('app.env') !== 'local') { - \URL::forceScheme('https'); + if (!in_array(config('app.env'), ['local', 'testing'])) { + URL::forceScheme('https'); } } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index ce74491..aaddf7d 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,19 +2,22 @@ namespace App\Providers; +use App\Lib\Helper; +use App\Models\Service; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Gate; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; class AuthServiceProvider extends ServiceProvider { + /** * The policy mappings for the application. * * @var array */ - protected $policies = [ - // 'App\Models\Model' => 'App\Policies\ModelPolicy', - ]; + protected $policies = []; /** * Register any authentication / authorization services. @@ -25,6 +28,39 @@ class AuthServiceProvider extends ServiceProvider { $this->registerPolicies(); - // + Auth::viaRequest('api-token', function (Request $request) { + // https://stackoverflow.com/questions/25838183/what-is-the-oauth-2-0-bearer-token-exactly/25843058 + /** + * Un token devrais être fourni pour chaque requete apres authentification + * api_token param est requis par Auth + */ + $apiToken = $request->api_token; + + if (is_null($apiToken)) { + return null; + } + + /** + ** All ApiV2 routes must pass Brand model to controller action since a Brand owns + ** a service token, otherwise the api cannot be called. + ** This hack is a custom binder instead of passing + ** this to route as a parameter + */ + if (!($route = Helper::getRoute($request))) { + return null; + } + + try { + $service = Service::getFromApiToken($apiToken); + $service = $service->load('brand'); + app()->bind(Service::class, function () use ($service) { + return $service; + }); + $test = $service->authenticateApi($apiToken); + return $test ? $service : null; + } catch (ModelNotFoundException $e) { + return null; + } + }); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index a9f10a6..2c03558 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -5,10 +5,10 @@ namespace App\Providers; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { + /** * The event listener mappings for the application. * diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index f3b67e5..c0b3794 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,7 +2,10 @@ 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; @@ -19,15 +22,6 @@ class RouteServiceProvider extends ServiceProvider */ public const HOME = '/customers'; - /** - * The controller namespace for the application. - * - * When present, controller route declarations will automatically be prefixed with this namespace. - * - * @var string|null - */ - // protected $namespace = 'App\\Http\\Controllers'; - /** * Define your route model bindings, pattern filters, etc. * @@ -38,14 +32,31 @@ 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) { + 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); + } + } + if ($brand = app('App\Models\Service')->brand) { + /** @var \App\Models\Customer $customer */ + $customer = $customer->loadBrandData($brand); + } + return $customer; }); } @@ -57,7 +68,8 @@ class RouteServiceProvider extends ServiceProvider protected function configureRateLimiting() { RateLimiter::for('api', function (Request $request) { - return Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()); + return Limit::perMinute(config('api.apiRequestRatePerMinute')) + ->by(optional($request->user())->id ?: $request->ip()); }); } } 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/clients/V2 b/clients/V2 new file mode 160000 index 0000000..9ad2f56 --- /dev/null +++ b/clients/V2 @@ -0,0 +1 @@ +Subproject commit 9ad2f564d57934a5fe62d928de1fa4568088a7e4 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; } diff --git a/composer.json b/composer.json index d20e776..658512c 100644 --- a/composer.json +++ b/composer.json @@ -1,65 +1,103 @@ { - "name": "laravel/laravel", - "type": "project", - "description": "The Laravel Framework.", - "keywords": [ - "framework", - "laravel" - ], - "license": "MIT", - "require": { - "php": "^7.3|^8.0", - "fideloper/proxy": "^4.4", - "fruitcake/laravel-cors": "^2.0", - "guzzlehttp/guzzle": "^7.0.1", - "laravel/framework": "^8.12", - "laravel/tinker": "^2.5", - "ext-curl": "*", - "ext-json": "*", - "ext-dom": "*" - }, - "require-dev": { - "facade/ignition": "^2.5", - "fakerphp/faker": "^1.9.1", - "mockery/mockery": "^1.4.2", - "nunomaduro/collision": "^5.0", - "phpunit/phpunit": "^9.3.3" - }, - "config": { - "optimize-autoloader": true, - "preferred-install": "dist", - "sort-packages": true - }, - "extra": { - "laravel": { - "dont-discover": [] - } - }, - "autoload": { - "psr-4": { - "App\\": "app/", - "Database\\Factories\\": "database/factories/", - "Database\\migrations\\": "database/migrations/", - "Database\\Seeders\\": "database/seeders/" - } - }, - "autoload-dev": { - "psr-4": { - "Tests\\": "tests/" - } - }, - "minimum-stability": "dev", - "prefer-stable": true, - "scripts": { - "post-autoload-dump": [ - "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", - "@php artisan package:discover --ansi" + "name": "ldc/ldccrmv2client", + "type": "project", + "version": "2.1.1", + "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": "*", + "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", + "guzzlehttp/guzzle": "^7.0.1", + "kwaadpepper/enum": "^2.0", + "laravel-lang/lang": "~7.0", + "laravel/framework": "^8.39", + "laravel/tinker": "^2.5", + "rennokki/laravel-eloquent-query-cache": "^3.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", + "respect/validation": "^2.2", + "slevomat/coding-standard": "^7.0", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^5.3" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true, + "platform": { + "php": "7.3.26" + } + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Factories\\": "database/factories/", + "Database\\migrations\\": "database/migrations/", + "Database\\Seeders\\": "database/seeders/", + "LDC\\LDCCRM\\" : "clients/V2/src/", + "LDCCRM\\" : "clients/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "test": [ + "@configclear", + "./test.sh" ], - "post-root-package-install": [ - "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" - ], - "post-create-project-cmd": [ - "@php artisan key:generate --ansi" - ] - } + "configclear": "@php artisan config:clear", + "runtestmigration": "@php artisan migrate:fresh --env=testing --seed", + "runtest": "@php artisan test", + "runserve": "@php artisan serve --host=127.0.0.25 --port=8888 --env=testing&", + "killserve": "kill $(ps -f | grep '[1]27.0.0.25:8888' | awk '{print $2}')", + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover --ansi" + ], + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate --ansi" + ] + } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..39cc9e3 --- /dev/null +++ b/composer.lock @@ -0,0 +1,8753 @@ +{ + "_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": "0df11a64ace2bcbbbc3710af5cc6868f", + "packages": [ + { + "name": "arondeparon/laravel-request-sanitizer", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/ArondeParon/laravel-request-sanitizer.git", + "reference": "025d3e45cddf19d2488e420201e8b34e8007b94d" + }, + "dist": { + "type": "zip", + "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": { + "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.3" + }, + "time": "2021-03-11T06:42:03+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": "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", + "source": { + "type": "git", + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/c6522afe5540d5fc46675043d3ed5a45a740b27c", + "reference": "c6522afe5540d5fc46675043d3ed5a45a740b27c", + "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.2" + }, + "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": "2021-05-24T07:46:03+00:00" + }, + { + "name": "doctrine/cache", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", + "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "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", + "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/2.1.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%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:49:29+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "8e0fde2b90e3f61361013d1e928621beeea07bc0" + }, + "dist": { + "type": "zip", + "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|^2.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "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.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." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "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.1.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%2Fdbal", + "type": "tidelift" + } + ], + "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", + "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.4", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/laravel-cors.git", + "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/a8ccedc7ca95189ead0e407c43b530dc17791d6a", + "reference": "a8ccedc7ca95189ead0e407c43b530dc17791d6a", + "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|^7", + "phpunit/phpunit": "^6|^7|^8|^9", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2021-04-26T11:24:25+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.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7008573787b430c1c1f650e3722d9bba59967628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "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", + "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.3-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.3.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": "2021-03-23T11:33:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "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.1" + }, + "time": "2021-03-07T09:25:29+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", + "reference": "1dc8d9cba3897165e16d12bb13d813afb1eb3fe7", + "shasum": "" + }, + "require": { + "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": { + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "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" + }, + { + "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", + "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/2.0.0" + }, + "time": "2021-06-30T20:03:07+00:00" + }, + { + "name": "kwaadpepper/enum", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/Kwaadpepper/enum.git", + "reference": "036cc602c05d9b2d141def57620f73d7190a60f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Kwaadpepper/enum/zipball/036cc602c05d9b2d141def57620f73d7190a60f2", + "reference": "036cc602c05d9b2d141def57620f73d7190a60f2", + "shasum": "" + }, + "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/2.0.0" + }, + "time": "2021-08-13T10:11:43+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.53.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "5df423c2790f32b26baee90518bb659572aa404f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/5df423c2790f32b26baee90518bb659572aa404f", + "reference": "5df423c2790f32b26baee90518bb659572aa404f", + "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|^2.0", + "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.186.4", + "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.23", + "pda/pheanstalk": "^4.0", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "predis/predis": "^1.1.2", + "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.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.", + "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|^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).", + "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-08-05T14:04:08+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.6.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "04ad32c1a3328081097a181875733fa51f402083" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/04ad32c1a3328081097a181875733fa51f402083", + "reference": "04ad32c1a3328081097a181875733fa51f402083", + "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.1" + }, + "time": "2021-03-02T16:53:12+00:00" + }, + { + "name": "league/commonmark", + "version": "1.6.6", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "c4228d11e30d7493c6836d20872f9582d8ba6dcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4228d11e30d7493c6836d20872f9582d8ba6dcf", + "reference": "c4228d11e30d7493c6836d20872f9582d8ba6dcf", + "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.90", + "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": "2021-07-17T17:13:23+00:00" + }, + { + "name": "league/flysystem", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "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-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.1.4" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-06-23T21:56:05+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.3.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "71312564759a7db5b789296369c1a264efc43aad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/71312564759a7db5b789296369c1a264efc43aad", + "reference": "71312564759a7db5b789296369c1a264efc43aad", + "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.91", + "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.3.2" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2021-07-23T07:42:52+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.51.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "8619c299d1e0d4b344e1f98ca07a1ce2cfbf1922" + }, + "dist": { + "type": "zip", + "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": { + "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-3.x": "3.x-dev", + "dev-master": "2.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": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://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-07-28T13:16:28+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.12.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6608f01670c3cc5079e18c1dab1104e002579143", + "reference": "6608f01670c3cc5079e18c1dab1104e002579143", + "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.12.0" + }, + "time": "2021-07-21T10:44:31+00:00" + }, + { + "name": "opis/closure", + "version": "3.6.2", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/06e2ebd25f2869e54a306dda991f7db58066f7f6", + "reference": "06e2ebd25f2869e54a306dda991f7db58066f7f6", + "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.2" + }, + "time": "2021-04-09T13:42:10+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/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", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://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/1.1.1" + }, + "time": "2021-03-05T17:36:06+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-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", + "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.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "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": "https://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.4" + }, + "time": "2021-05-03T11:20:27+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.8", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "reference": "e4573f47750dd6c92dca5aee543fa77513cbd8d3", + "shasum": "" + }, + "require": { + "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.8" + }, + "time": "2021-04-10T16:23:39+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.2.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/eaca1dc1054ddd10cbd83c1461907bee6fb528fa", + "reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" + }, + "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", + "phpspec/prophecy-phpunit": "^2.0", + "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 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.2.1" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-08-06T03:41:06+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": "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", + "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", + "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.7" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer", + "type": "tidelift" + } + ], + "time": "2021-03-09T12:30:35+00:00" + }, + { + "name": "symfony/console", + "version": "v5.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2", + "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.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", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "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.3.6" + }, + "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-07-27T19:10:22+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7fb120adc7f600a59027775b224c13a33530dd90", + "reference": "7fb120adc7f600a59027775b224c13a33530dd90", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "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.3.4" + }, + "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-07-21T12:38:00+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-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/v2.4.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-03-23T23:28:01+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "281f6c4660bcf5844bb0346fe3a4664722fe4c73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/281f6c4660bcf5844bb0346fe3a4664722fe4c73", + "reference": "281f6c4660bcf5844bb0346fe3a4664722fe4c73", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2|^3", + "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.3.4" + }, + "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-07-23T15:55:36+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "f2fd2208157553874560f3645d4594303058c4bd" + }, + "dist": { + "type": "zip", + "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.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "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", + "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.3.4" + }, + "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-07-23T15:55:36+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "reference": "69fee1ad2332a7cbab3aca13591953da9cdb7a11", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-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.4.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-03-23T23:28:01+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "17f50e06018baec41551a71a15731287dbaab186" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/17f50e06018baec41551a71a15731287dbaab186", + "reference": "17f50e06018baec41551a71a15731287dbaab186", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "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.3.4" + }, + "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-07-23T15:54:19+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "reference": "7e82f6084d7cae521a75ef2cb5c9457bbda785f4", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/http-client-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-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.4.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-04-11T23:07:08+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "a8388f7b7054a7401997008ce9cd8c6b0ab7ac75" + }, + "dist": { + "type": "zip", + "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.16" + }, + "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.3.6" + }, + "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-07-27T17:08:17+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v5.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "60030f209018356b3b553b9dbd84ad2071c1b7e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/60030f209018356b3b553b9dbd84ad2071c1b7e0", + "reference": "60030f209018356b3b553b9dbd84ad2071c1b7e0", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/log": "^1|^2", + "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": "^5.3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/browser-kit": "<4.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<5.3", + "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|2.0" + }, + "require-dev": { + "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.3", + "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.3.6" + }, + "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-07-29T07:06:27+00:00" + }, + { + "name": "symfony/mime", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "633e4e8afe9e529e5599d71238849a4218dd497b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/633e4e8afe9e529e5599d71238849a4218dd497b", + "reference": "633e4e8afe9e529e5599d71238849a4218dd497b", + "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.16" + }, + "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|^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", + "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.3.4" + }, + "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-07-21T12:40:44+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "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\\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.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-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-iconv", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-iconv.git", + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-iconv": "For best performance" + }, + "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\\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.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-27T09:27:20+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535", + "reference": "16880ba9c5ebe3642d1995ab866db29270b36535", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "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\\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.23.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-05-27T12:26:48+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/65bd267525e82759e7d8c4e8ceea44f398838e65", + "reference": "65bd267525e82759e7d8c4e8ceea44f398838e65", + "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.23-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.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-27T09:27:20+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", + "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "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\\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.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-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "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\\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.23.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-05-27T12:26:48+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "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\\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.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-27T09:17:38+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fba8933c384d6476ab14fb7b8526e5287ca7e010", + "reference": "fba8933c384d6476ab14fb7b8526e5287ca7e010", + "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\\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.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-02-19T12:13:01+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "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\\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.23.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-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", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d16634ee55b895bd85ec714dadc58e4428ecf030" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d16634ee55b895bd85ec714dadc58e4428ecf030", + "reference": "d16634ee55b895bd85ec714dadc58e4428ecf030", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "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.3.4" + }, + "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-07-23T15:54:19+00:00" + }, + { + "name": "symfony/routing", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "0a35d2f57d73c46ab6d042ced783b81d09a624c4" + }, + "dist": { + "type": "zip", + "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.16" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<5.3", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12", + "psr/log": "^1|^2|^3", + "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": { + "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.3.4" + }, + "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-07-23T15:55:36+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-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/v2.4.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-04-01T10:43:52+00:00" + }, + { + "name": "symfony/string", + "version": "v5.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1", + "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.3.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-06-27T11:44:38+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d89ad7292932c2699cbe4af98d72c5c6bbc504c1" + }, + "dist": { + "type": "zip", + "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.16", + "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.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "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/polyfill-intl-icu": "^1.21", + "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.3.4" + }, + "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-07-25T09:39:16+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "95c812666f3e91db75385749fe219c5e494c7f95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/95c812666f3e91db75385749fe219c5e494c7f95", + "reference": "95c812666f3e91db75385749fe219c5e494c7f95", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-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.4.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-03-23T23:28:01+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v5.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3dd8ddd1e260e58ecc61bb78da3b6584b3bfcba0" + }, + "dist": { + "type": "zip", + "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.16" + }, + "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.3.6" + }, + "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-07-27T01:56:02+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.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "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.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + } + ], + "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", + "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.8.1", + "source": { + "type": "git", + "url": "https://github.com/facade/flare-client-php.git", + "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/flare-client-php/zipball/47b639dc02bcfdfc4ebb83de703856fa01e35f5f", + "reference": "47b639dc02bcfdfc4ebb83de703856fa01e35f5f", + "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.8.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2021-05-31T19:23:29+00:00" + }, + { + "name": "facade/ignition", + "version": "2.11.2", + "source": { + "type": "git", + "url": "https://github.com/facade/ignition.git", + "reference": "7c4e7a7da184cd00c7ce6eacc590200bb9672de7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facade/ignition/zipball/7c4e7a7da184cd00c7ce6eacc590200bb9672de7", + "reference": "7c4e7a7da184cd00c7ce6eacc590200bb9672de7", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "facade/flare-client-php": "^1.6", + "facade/ignition-contracts": "^1.0.2", + "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-07-20T14:01:22+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.15.0", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "89c6201c74db25fa759ff16e78a4d8f32547770e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/89c6201c74db25fa759ff16e78a4d8f32547770e", + "reference": "89c6201c74db25fa759ff16e78a4d8f32547770e", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/container": "^1.0", + "symfony/deprecation-contracts": "^2.2" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-intl": "*", + "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/" + } + }, + "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.15.0" + }, + "time": "2021-07-06T20:39:40+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.0", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "fdf92f03e150ed84d5967a833ae93abffac0315b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/fdf92f03e150ed84d5967a833ae93abffac0315b", + "reference": "fdf92f03e150ed84d5967a833ae93abffac0315b", + "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.14.0" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2021-07-13T12: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.6.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "0122ac6b03c75279ef78d1c0ad49725dfc52a8d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0122ac6b03c75279ef78d1c0ad49725dfc52a8d2", + "reference": "0122ac6b03c75279ef78d1c0ad49725dfc52a8d2", + "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": "^8.0 || ^9.0", + "nunomaduro/larastan": "^0.6.2", + "nunomaduro/mock-final-classes": "^1.0", + "orchestra/testbench": "^6.0 || ^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-07-26T20:39:06+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "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/2.0.3" + }, + "time": "2021-07-20T11:28:43+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.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "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.13.0" + }, + "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", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "f6293e1b30a2354e8428e004689671b83871edde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde", + "reference": "f6293e1b30a2354e8428e004689671b83871edde", + "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.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-03-28T07:26:59+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.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/191768ccd5c85513b4068bdbe99bb6390c7d54fb", + "reference": "191768ccd5c85513b4068bdbe99bb6390c7d54fb", + "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.3", + "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.4", + "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.8" + }, + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "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", + "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.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "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.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+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.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+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": "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", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625", + "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": "2021-04-09T00:54:41+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "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/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^7", + "ext-curl": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-memcached": "^3.1" + }, + "platform-dev": [], + "platform-overrides": { + "php": "7.3.26" + }, + "plugin-api-version": "2.0.0" +} diff --git a/config/api.php b/config/api.php new file mode 100644 index 0000000..c8d1914 --- /dev/null +++ b/config/api.php @@ -0,0 +1,28 @@ + Helper::getPackageInfo()['version'], + + /** + * Api Token timeout in minutes + */ + 'tokenTimeout' => 60, + + /** + * The number of request + * per user or ip per minutes + */ + 'apiRequestRatePerMinute' => 256, + + /** + * Service requests cache + * time in minutes + */ + 'servicesDBCache' => 10 +]; diff --git a/config/app.php b/config/app.php index bc1a0c6..5bdde92 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), /* |-------------------------------------------------------------------------- @@ -93,7 +93,7 @@ return [ | */ - 'fallback_locale' => 'fr', + 'fallback_locale' => 'en', /* |-------------------------------------------------------------------------- @@ -171,7 +171,6 @@ return [ */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, - // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, @@ -226,7 +225,9 @@ return [ 'URL' => Illuminate\Support\Facades\URL::class, 'Validator' => Illuminate\Support\Facades\Validator::class, 'View' => Illuminate\Support\Facades\View::class, - + 'Carbon' => Illuminate\Support\Carbon::class, + 'ArcadiaMailType' => App\Enums\ArcadiaMailType::class, + 'ArcadiaOrderType' => App\Enums\ArcadiaOrderType::class ], ]; diff --git a/config/auth.php b/config/auth.php index 73f4486..46b56f6 100644 --- a/config/auth.php +++ b/config/auth.php @@ -42,9 +42,7 @@ return [ ], 'api' => [ - 'driver' => 'token', - 'provider' => 'services', - 'hash' => true, + 'driver' => 'api-token' ], ], @@ -69,17 +67,7 @@ return [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, - ], - - 'services' => [ - 'driver' => 'eloquent', - 'model' => App\Models\Service::class, - ], - - // 'users' => [ - // 'driver' => 'database', - // 'table' => 'users', - // ], + ] ], /* diff --git a/config/cache.php b/config/cache.php index 4f41fdf..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'), @@ -99,6 +97,6 @@ return [ | */ - 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'), ]; diff --git a/config/database.php b/config/database.php index eae8298..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), ], @@ -146,7 +146,7 @@ return [ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), ], 'default' => [ 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/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/config/mail.php b/config/mail.php index e58d776..8481227 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', 'equipe.web@ldc.fr') + ], + /* |-------------------------------------------------------------------------- | Markdown Mail Settings 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/factories/AnswerFactory.php b/database/factories/AnswerFactory.php new file mode 100644 index 0000000..b9dcbd0 --- /dev/null +++ b/database/factories/AnswerFactory.php @@ -0,0 +1,29 @@ + $this->faker->realText(20), + ]; + } +} 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..851e862 --- /dev/null +++ b/database/factories/CustomerFactory.php @@ -0,0 +1,33 @@ + preg_replace( + '/@example\..*/', + '@gmail.com', + $this->faker->unique()->safeEmail + ), + ]; + } +} diff --git a/database/factories/CustomerInfoFactory.php b/database/factories/CustomerInfoFactory.php new file mode 100644 index 0000000..e1b7525 --- /dev/null +++ b/database/factories/CustomerInfoFactory.php @@ -0,0 +1,45 @@ +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)]; + 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' => $country, + 'newcountry' => $country, + '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..056ff0e --- /dev/null +++ b/database/factories/CustomerLoginFactory.php @@ -0,0 +1,39 @@ +faker->boolean; + // Force first to be active + if (!self::$first) { + self::$first = true; + $bool = false; + } + return [ + 'password' => 'testPassword', + 'last_connection' => $this->faker->date(), + 'status' => $bool + ]; + } +} diff --git a/database/factories/QuestionFactory.php b/database/factories/QuestionFactory.php new file mode 100644 index 0000000..7004794 --- /dev/null +++ b/database/factories/QuestionFactory.php @@ -0,0 +1,31 @@ + $this->faker->realText(40), + 'type' => collect(QuestionType::toArray())->random()->value, + ]; + } +} 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/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 @@ +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'); + }); + Schema::enableForeignKeyConstraints(); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::disableForeignKeyConstraints(); + 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'); + }); + Schema::enableForeignKeyConstraints(); + } +} 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..e603114 --- /dev/null +++ b/database/migrations/2021_03_15_073250_re_add_api_token_to_services_table.php @@ -0,0 +1,38 @@ +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'); + }); + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('api_token_date'); + }); + } +} 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/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..d11403a --- /dev/null +++ b/database/migrations/2021_08_03_163527_add_newcountry_column_to_customer_infos_table.php @@ -0,0 +1,32 @@ +smallInteger('newcountry')->default(76)->after('country'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('customer_infos', function (Blueprint $table) { + $table->dropColumn('newcountry'); + }); + } +} 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..6f092c5 --- /dev/null +++ b/database/migrations/2021_08_10_104859_create_customer_answer_table.php @@ -0,0 +1,32 @@ +foreignId('answer_id')->constrained('answers'); + $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/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..2e683c4 --- /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->saveQuietly(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + } +} 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'); + } +} diff --git a/database/seeders/BrandSeeder.php b/database/seeders/BrandSeeder.php new file mode 100644 index 0000000..f71cc8f --- /dev/null +++ b/database/seeders/BrandSeeder.php @@ -0,0 +1,39 @@ +create()->each(function ($brand) { + 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/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..f5a1b5c 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -13,6 +13,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 new file mode 100644 index 0000000..f24a49f --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,92 @@ + + + + + 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..7b01f29 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,15 +1,15 @@ ./tests/Unit - ./tests/Feature + ./tests/Feature/ApiV2 @@ -19,12 +19,12 @@ + - - - + + 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.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/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()) + + @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-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 new file mode 100644 index 0000000..18e4298 --- /dev/null +++ b/resources/views/mail/exception.blade.php @@ -0,0 +1,18 @@ + + + + {{ config('app.name') }} 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/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()) + + @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 e3eae2d..ed014ca 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,8 +1,14 @@ 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']); @@ -33,3 +39,33 @@ 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('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 () { + Route::post('/outgoing-mail/{customer}/default', [V2ArcadiaController::class, 'outgoingMailDefault']) + ->name('outgoingMailDefault'); + 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/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 0ddef6b..55b9237 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,27 +17,35 @@ 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/{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'])->name('manage-service'); - Route::get('/services/{id}/token/generate', [ServiceController::class, 'generateApiToken'])->name('generate-token-service'); - // Clients (Customer) + Route::match(['get', 'post'], '/service/manage/{service?}', [ServiceController::class, 'manage']) + ->name('manage-service'); + Route::get('/services/{service}/token/generate', [ServiceController::class, 'generateApiToken']) + ->name('generate-token-service'); + + // 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'); Route::delete('/customer/{customer}/{brand}', [CustomerController::class, 'delete'])->name('customers.delete'); + // 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::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/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/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(); diff --git a/tests/Feature/ApiV2/ApiBase.php b/tests/Feature/ApiV2/ApiBase.php new file mode 100644 index 0000000..0303a08 --- /dev/null +++ b/tests/Feature/ApiV2/ApiBase.php @@ -0,0 +1,101 @@ +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; + $apiToken = $service->generateApiToken(); + $service->saveOrFail(); + $data['api_token'] = $apiToken; + return $this->callApi($method, $uri, $data, $headers); + } + + /** + * Get Customer models with given status (active or not) + * + * @param boolean $status the customer status for a brand + * @return 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); + }); + 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 */ + $brand = $login->brand; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + + // regen bearer token to get the pwd in clear + $bearer_token = $service->generateBearerToken(); + $bearer_pwd = $service->bearer_pwd; + $service->saveOrFail(); + $service->bearer_token = $bearer_token; + $service->bearer_pwd = $bearer_pwd; + + // get customer info + $info = $customer->info($brand, Customer::FIRST_OR_FAIL); + + return compact('customer', 'service', 'login', 'brand', 'info'); + } +} diff --git a/tests/Feature/ApiV2/ApiTest.php b/tests/Feature/ApiV2/ApiTest.php new file mode 100644 index 0000000..940f8b3 --- /dev/null +++ b/tests/Feature/ApiV2/ApiTest.php @@ -0,0 +1,60 @@ +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()); + $json->assertFragment(['data' => [ + 'code' => ApiError::wrongTimeStamp()->value, + 'message' => ApiError::wrongTimeStamp()->label + ]]); + } + + /** + * route: apiV2.authenticate + */ + public function testApiAuthenticate() + { + extract($this->getCustomer()); + $response = $this->callApi( + 'post', + route('apiV2.authenticate'), + ['bearer_pwd' => $service->bearer_pwd], + ['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->assertTrue(Hash::check($json->json('data.api_token'), Service::findOrFail($service->id)->api_token)); + } +} diff --git a/tests/Feature/ApiV2/ArcadiaTest.php b/tests/Feature/ApiV2/ArcadiaTest.php new file mode 100644 index 0000000..e3aa138 --- /dev/null +++ b/tests/Feature/ApiV2/ArcadiaTest.php @@ -0,0 +1,60 @@ +getCustomer()); + $data = [ + '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), + 'package_unit_weight' => 200, + ]; + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.arcadia.outgoingMailDefault', + compact('customer') + ), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + } + + public function testArcadiaSendOrderGame() + { + extract($this->getCustomer()); + $data = [ + '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.arcadia.outgoingMailGame', + 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..bf144b4 --- /dev/null +++ b/tests/Feature/ApiV2/CustomerTest.php @@ -0,0 +1,269 @@ +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())); + $this->assertInstanceOf(CustomerLogin::class, $customer->login($brands->first())); + } + + /** + * route: apiV2.customers.authenticate + */ + public function testApiAuthenticateCustomer() + { + extract($this->getCustomer()); + $response = $this->callApiWithToken( + $service, + 'post', + route('apiV2.customers.authenticate', []), + [ + 'email' => $customer->email, + 'password' => 'testPassword' + ] + ); + + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertNotEmpty($json->json('data')); + $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.customers.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' => Country::france()->value, + 'city' => 'Connerré', + 'postcode' => '72160', + 'opt_in' => true + ]; + $response = $this->callApiWithToken($service, 'post', route('apiV2.customers.store'), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $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.customers.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.customers.store'), $data); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); + } + + /** + * route: apiV2.customers.activate + */ + public function testActivateCustomer() + { + extract($this->getCustomer(false)); + + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.customers.activate', + ['customer' => $info->hashcode] + ), []); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + + $response = $this->callApiWithToken($service, 'post', route( + 'apiV2.customers.activate', + ['customer' => $info->hashcode] + ), []); + $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.customers.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' => Country::bahamas()->value, + 'city' => 'Connerré2', + 'postcode' => '72161', + 'opt_in' => 0 + ]; + + foreach ($data as $rowName => $row) { + $response = $this->callApiWithToken($service, 'patch', route( + 'apiV2.customers.update', + ['customer' => $info->hashcode] + ), [$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.customers.update', + ['customer' => $info->hashcode] + ), ['address2' => 'anything']); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + $this->assertEquals(ApiError::validationFailed()->value, $json->json('data.code')); + } + + /** + * route: apiV2.customers.show + */ + public function testApiGetCustomer() + { + extract($this->getCustomer()); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.customers.show', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $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]; + + $uuid = Str::uuid(); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.customers.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( + sprintf(ApiError::modelNotFound()->label, $csClassName), + $json->json('data.message') + ); + } + + /** + * route: apiV2.customer.destroy + */ + public function testApiDestroyCustomer() + { + extract($this->getCustomer()); + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken($service, 'delete', route( + 'apiV2.customers.destroy', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + $this->assertEmpty($json->json('data')); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV2.customers.show', + ['customer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO()); + } +} 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 new file mode 100644 index 0000000..2b8a035 --- /dev/null +++ b/tests/Feature/ApiV2/SingleOptinTest.php @@ -0,0 +1,73 @@ +callApiWithToken($service, 'post', route('apiV2.optin'), [ + 'email' => 'warthogdj@hotmail.fr', + 'opt_in' => 1 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK()); + + $response = $this->callApiWithToken($service, 'post', route('apiV2.optin'), [ + 'email' => 'warthogdj@hotmail.fr', + 'opt_in' => 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', + 'opt_in' => 1 + ]); + + $json = $response->decodeResponseJson(); + $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.customers.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/CrmClientTester.php b/tests/Unit/CrmClientTester.php new file mode 100644 index 0000000..6059a01 --- /dev/null +++ b/tests/Unit/CrmClientTester.php @@ -0,0 +1,83 @@ + self::$service->bearer_token]); + config(['crm.password' => self::$service->bearer_pwd]); + config(['crm.endpoint' => 'http://127.0.0.25:8888']); + } + + /** + * @return array + */ + protected static function getCustomer(bool $status = true) + { + /** @var \App\Models\Costumer $customer */ + $customer = Customer::whereHas('logins', function ($q) use ($status) { + $q->where('status', $status); + })->inRandomOrder()->firstOrFail(); + /** @var \App\Models\Brand $brand */ + $login = $customer->logins()->where('status', $status)->firstOrFail(); + /** @var \App\Models\CustomerLogin $login */ + $brand = $login->brand; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + + // get customer info + $info = $customer->info($brand, Customer::FIRST_OR_FAIL); + + // regen bearer token to get the pwd in clear + $bearer_token = $service->generateBearerToken(); + $bearer_pwd = $service->bearer_pwd; + $service->saveOrFail(); + $service->bearer_token = $bearer_token; + $service->bearer_pwd = $bearer_pwd; + + return compact('customer', 'service', 'brand', 'login', 'info'); + } + + /** + * 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/CrmClientV1CustomerTest.php b/tests/Unit/CrmClientV1CustomerTest.php new file mode 100644 index 0000000..df98b1a --- /dev/null +++ b/tests/Unit/CrmClientV1CustomerTest.php @@ -0,0 +1,353 @@ +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); + } +} diff --git a/tests/Unit/CrmClientV2ArcadiaTest.php b/tests/Unit/CrmClientV2ArcadiaTest.php new file mode 100644 index 0000000..f87a21c --- /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/CrmClientV2ClientTest.php b/tests/Unit/CrmClientV2ClientTest.php new file mode 100644 index 0000000..9db3b96 --- /dev/null +++ b/tests/Unit/CrmClientV2ClientTest.php @@ -0,0 +1,33 @@ +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()); + } +} 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..9af2328 --- /dev/null +++ b/tests/Unit/CrmClientV2CustomerTest.php @@ -0,0 +1,272 @@ +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->opt_in = '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' => 18, + 'postcode' => 'string', + 'country' => Country::afghanistan(), + 'optin' => true, + 'status' => true, + ]; + + foreach ($propTypes as $prop => $type) { + $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); + } + + 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, ModelsCustomer::FIRST_OR_FAIL); + + $api = new CrmApiV2(); + $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 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->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() + { + $customer = new Customer(); + + $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(); + + $this->assertTrue($customer->save()); + + $this->assertEquals('toto', $customer->firstname); + $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); + $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->getCustomer('toto.delarogerie@gmail.com'); + + $this->assertTrue($customer->delete()); + + $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 new file mode 100644 index 0000000..c4e9321 --- /dev/null +++ b/tests/Unit/CrmClientV2RouterTest.php @@ -0,0 +1,51 @@ +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; + } + 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; + $request->data; + } + } +} 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)); + } +} 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..5c0ebfc --- /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); + }); + } +}
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') }}