From 303834bbca881825a67469eb6a39d9cb3ecfb925 Mon Sep 17 00:00:00 2001 From: Alexis Gousseau Date: Wed, 6 Sep 2023 17:24:53 +0200 Subject: [PATCH] Crm V4 - Make all tests pass Client (V4),CRMV2,CRMV4 - Simplify client submodule path - Move App_url to .env.testing --- .gitignore | 1 + .gitmodules | 4 +- .vscode/settings.json | 2 +- Dockerfile | 2 +- app/Console/Commands/CreateAdmin.php | 162 +- .../Commands/DeleteUnconfirmedCustomers.php | 5 +- ...nerateCrmClient.php => GenerateClient.php} | 77 +- app/Console/Commands/LdcWebStatusChecker.php | 3 +- app/Console/Commands/MigrateCrmUsers.php | 11 +- app/Console/Commands/StartStatJobs.php | 2 +- app/Console/Kernel.php | 9 +- app/Enums/Country.php | 46 +- app/Exceptions/AbortRowImportException.php | 5 +- app/Exceptions/Api/V2/ApiErrorException.php | 24 +- app/Exceptions/Api/V2/CrmException.php | 3 +- app/Exceptions/Api/V4/ApiErrorException.php | 102 + app/Exceptions/Api/V4/CrmException.php | 28 + app/Exceptions/Handler.php | 51 +- app/Http/Controllers/AnswerController.php | 28 +- .../Controllers/Api/V2/AnswerController.php | 4 +- app/Http/Controllers/Api/V2/ApiController.php | 2 +- .../Controllers/Api/V2/ArcadiaController.php | 30 +- .../Controllers/Api/V2/CustomerController.php | 16 +- .../Controllers/Api/V2/OptinController.php | 8 +- .../Controllers/Api/V2/QuestionController.php | 4 +- .../Controllers/Api/V2/StatsController.php | 12 +- .../Controllers/Api/V4/AnswerController.php | 35 + app/Http/Controllers/Api/V4/ApiController.php | 19 + .../Controllers/Api/V4/ArcadiaController.php | 139 ++ .../Controllers/Api/V4/CustomerController.php | 261 +++ .../Controllers/Api/V4/OptinController.php | 93 + .../Controllers/Api/V4/QuestionController.php | 64 + .../Controllers/Api/V4/StatsController.php | 109 + app/Http/Controllers/BrandController.php | 29 +- app/Http/Controllers/Controller.php | 20 +- app/Http/Controllers/CustomerController.php | 73 +- app/Http/Controllers/LoginController.php | 23 +- app/Http/Controllers/QuestionController.php | 44 +- app/Http/Controllers/ServiceController.php | 41 +- .../Controllers/SingleOptinController.php | 11 +- app/Http/Controllers/StatsController.php | 37 +- app/Http/Kernel.php | 17 +- app/Http/Middleware/ApiCheckTimeStamps.php | 8 +- app/Http/Middleware/ApiResponse.php | 18 +- app/Http/Middleware/Authenticate.php | 40 +- app/Http/Middleware/CheckBrand.php | 40 - app/Http/Middleware/JsonOnly.php | 2 +- .../Middleware/RedirectIfAuthenticated.php | 5 +- app/Http/Middleware/SecureHeaders.php | 5 +- app/Http/Middleware/TrustHosts.php | 6 +- app/Http/Middleware/XssSanitizer.php | 2 +- .../Requests/Api/V2/AuthenticateRequest.php | 5 +- app/Http/Requests/Api/V2/BaseApiRequest.php | 8 +- .../Requests/Api/V2/GetBrandStatsRequest.php | 3 + .../Api/V2/GetQuestionStatsRequest.php | 3 + .../Api/V2/SendArchadiaDefaultMailRequest.php | 14 +- .../Api/V2/SendArchadiaGameMailRequest.php | 14 +- .../SingleOptinRemoveWithHashcodeRequest.php | 3 + .../Requests/Api/V2/SingleOptinRequest.php | 3 + .../Requests/Api/V2/StoreCustomerAnswers.php | 10 +- .../Requests/Api/V2/StoreCustomerRequest.php | 47 +- .../Requests/Api/V2/UpdateCustomerRequest.php | 15 +- .../Requests/Api/V4/AuthenticateRequest.php | 29 + app/Http/Requests/Api/V4/BaseApiRequest.php | 51 + .../Requests/Api/V4/GetBrandStatsRequest.php | 40 + .../Api/V4/GetQuestionStatsRequest.php | 40 + .../Api/V4/SendArchadiaDefaultMailRequest.php | 109 + .../Api/V4/SendArchadiaGameMailRequest.php | 102 + .../SingleOptinRemoveWithHashcodeRequest.php | 18 + .../Requests/Api/V4/SingleOptinRequest.php | 19 + .../Requests/Api/V4/StoreCustomerAnswers.php | 48 + .../Requests/Api/V4/StoreCustomerRequest.php | 95 + .../Requests/Api/V4/UpdateCustomerRequest.php | 65 + app/Http/Requests/CustomerExport.php | 7 +- app/Http/Requests/GenerateServiceToken.php | 7 +- app/Http/Requests/ManageBrandRequest.php | 8 +- app/Http/Requests/ManageServiceRequest.php | 8 +- app/Http/Requests/QuestionExport.php | 7 +- app/Http/Requests/StatsByBrandRequest.php | 13 +- app/Http/Requests/StatsByQuestionRequest.php | 12 +- app/Http/Resources/Api/V2/AnswersResource.php | 10 +- app/Http/Resources/Api/V2/ApiResponse.php | 20 +- .../Resources/Api/V2/BrandStatsResource.php | 20 +- .../Resources/Api/V2/CustomerResource.php | 33 +- .../Api/V2/QuestionStatsResource.php | 8 +- .../Resources/Api/V2/QuestionsResource.php | 22 +- app/Http/Resources/Api/V4/AnswersResource.php | 28 + app/Http/Resources/Api/V4/ApiResponse.php | 44 + .../Resources/Api/V4/BrandStatsResource.php | 35 + .../Resources/Api/V4/CustomerResource.php | 101 + .../Api/V4/QuestionStatsResource.php | 43 + .../Resources/Api/V4/QuestionsResource.php | 58 + app/Http/Resources/CustomerResource.php | 45 +- app/Http/Resources/StatusResource.php | 3 - app/Http/Sanitizers/RemoveSpaces.php | 2 + app/Jobs/SetServiceStats.php | 9 +- app/Jobs/StartStatJobs.php | 5 +- app/Jobs/StatJob.php | 7 +- app/Jobs/UpdateCustomerLastConnection.php | 5 +- app/Lib/Helper.php | 62 +- app/Lib/Helpers/ExceptionsHelper.php | 16 +- app/Lib/MigrateOldData.php | 19 +- app/Mail/CourrierArcadiaError.php | 7 +- app/Mail/ServiceCredentialRenewed.php | 12 +- app/Models/Answer.php | 19 +- app/Models/Brand.php | 45 +- app/Models/BrandStat.php | 18 +- app/Models/Customer.php | 56 +- app/Models/CustomerAnswer.php | 18 +- app/Models/CustomerInfo.php | 61 +- app/Models/CustomerLogin.php | 29 +- app/Models/CustomerOptInHistory.php | 28 +- app/Models/Question.php | 19 +- app/Models/Service.php | 84 +- app/Models/ServiceStat.php | 29 +- app/Models/SingleOptin.php | 15 +- app/Models/User.php | 21 +- app/Policies/AnswerPolicy.php | 6 +- app/Policies/BrandPolicy.php | 6 +- app/Policies/CustomerPolicy.php | 5 +- app/Policies/QuestionPolicy.php | 8 +- app/Policies/Rules/UserStaticRules.php | 6 +- app/Policies/ServicePolicy.php | 5 +- app/Policies/SingleOptinPolicy.php | 6 +- app/Providers/AppServiceProvider.php | 7 +- app/Providers/AuthServiceProvider.php | 4 +- app/Providers/RouteServiceProvider.php | 11 +- app/Providers/TranslationServiceProvider.php | 8 +- app/Traits/Enums/BaseEnum.php | 35 +- clients/V2 | 1 - composer.json | 19 +- composer.lock | 412 ++-- package-lock.json | 767 +++---- package.json | 4 +- phpcs.xml | 4 +- phpunit.xml | 40 +- resources/views/answers/index-list.blade.php | 90 +- resources/views/answers/index.blade.php | 5 +- resources/views/customers/export.blade.php | 116 +- resources/views/customers/show.blade.php | 233 +- .../partials/inputs/select-enum.blade.php | 4 +- resources/views/questions/show.blade.php | 4 +- routes/api.php | 62 + routes/console.php | 18 - test.sh | 4 +- tests/CRM/ApiV2/ApiBase.php | 2 +- tests/CRM/ApiV2/ApiTest.php | 14 +- tests/CRM/ApiV2/ArcadiaTest.php | 2 +- tests/CRM/ApiV2/CustomerTest.php | 38 +- tests/CRM/ApiV2/QuestionTest.php | 19 +- tests/CRM/ApiV2/SingleOptinTest.php | 6 +- tests/CRM/ApiV2/StatsTest.php | 19 +- tests/CRM/ApiV4/ApiBase.php | 106 + tests/CRM/ApiV4/ApiTest.php | 186 ++ tests/CRM/ApiV4/ArcadiaTest.php | 87 + tests/CRM/ApiV4/CustomerTest.php | 716 ++++++ tests/CRM/ApiV4/QuestionTest.php | 310 +++ tests/CRM/ApiV4/SingleOptinTest.php | 169 ++ tests/CRM/ApiV4/StatsTest.php | 209 ++ ...cadiaTest.php => CrmClientArcadiaTest.php} | 18 +- ...ClientTest.php => CrmClientClientTest.php} | 12 +- ...ConfigTest.php => CrmClientConfigTest.php} | 2 +- ...omerTest.php => CrmClientCustomerTest.php} | 68 +- ...tionTest.php => CrmClientQuestionTest.php} | 18 +- ...RouterTest.php => CrmClientRouterTest.php} | 4 +- ...nTest.php => CrmClientSingleOptinTest.php} | 8 +- ...V2StatsTest.php => CrmClientStatsTest.php} | 2 +- tests/Client/CrmClientTester.php | 6 +- tests/Client/HelperTest.php | 14 +- .../DeleteUnconfirmedCustomers.php.html | 185 -- .../Commands/LdcWebStatusChecker.php.html | 192 -- .../Console/Commands/MigrateCrmUsers.php.html | 187 -- .../Console/Commands/StartStatJobs.php.html | 151 -- .../Console/Commands/UserCreate.php.html | 164 -- .../coverage/Console/Commands/dashboard.html | 285 --- tests/coverage/Console/Commands/index.html | 155 -- tests/coverage/Console/Kernel.php.html | 170 -- tests/coverage/Console/dashboard.html | 284 --- tests/coverage/Console/index.html | 102 - tests/coverage/Enums/ApiError.php.html | 289 --- tests/coverage/Enums/ArcadiaMailType.php.html | 181 -- .../coverage/Enums/ArcadiaOrderType.php.html | 181 -- tests/coverage/Enums/Country.php.html | 1154 ---------- .../Enums/CustomerExportType.php.html | 168 -- tests/coverage/Enums/QuestionType.php.html | 183 -- tests/coverage/Enums/dashboard.html | 286 --- tests/coverage/Enums/index.html | 257 --- .../Exceptions/Api/V2/CrmException.php.html | 144 -- tests/coverage/Exceptions/Api/dashboard.html | 288 --- tests/coverage/Exceptions/Handler.php.html | 379 ---- tests/coverage/Exceptions/dashboard.html | 289 --- tests/coverage/Exceptions/index.html | 160 -- .../Controllers/AnswerController.php.html | 336 --- .../Api/V1/ArcadiaController.php.html | 515 ----- .../Api/V1/CustomerController.php.html | 798 ------- .../Http/Controllers/Api/V1/index.html | 105 - .../Http/Controllers/Api/dashboard.html | 286 --- .../coverage/Http/Controllers/Api/index.html | 134 -- .../Http/Controllers/BrandController.php.html | 320 --- .../Http/Controllers/Controller.php.html | 266 --- .../Controllers/CustomerController.php.html | 471 ---- .../Http/Controllers/LoginController.php.html | 192 -- .../Controllers/QuestionController.php.html | 480 ---- .../Controllers/ServiceController.php.html | 400 ---- .../SingleOptinController.php.html | 196 -- .../Http/Controllers/StatsController.php.html | 361 --- .../coverage/Http/Controllers/dashboard.html | 285 --- tests/coverage/Http/Controllers/index.html | 237 -- tests/coverage/Http/Kernel.php.html | 171 -- .../Middleware/ApiCheckTimeStamps.php.html | 197 -- .../Http/Middleware/ApiResponse.php.html | 227 -- .../Http/Middleware/Authenticate.php.html | 224 -- .../Http/Middleware/CheckBrand.php.html | 154 -- .../Http/Middleware/EncryptCookies.php.html | 117 - .../Http/Middleware/JsonOnly.php.html | 185 -- .../PreventRequestsDuringMaintenance.php.html | 117 - .../RedirectIfAuthenticated.php.html | 149 -- .../Http/Middleware/TrimStrings.php.html | 120 - .../Http/Middleware/TrustProxies.php.html | 129 -- .../Http/Middleware/VerifyCsrfToken.php.html | 117 - .../Http/Middleware/XssSanitizer.php.html | 179 -- tests/coverage/Http/Middleware/dashboard.html | 291 --- tests/coverage/Http/Middleware/index.html | 364 --- .../Api/V2/GetQuestionStatsRequest.php.html | 197 -- .../Api/V2/StoreCustomerRequest.php.html | 295 --- .../Api/V2/UpdateCustomerRequest.php.html | 217 -- .../Http/Requests/Api/V2/dashboard.html | 297 --- .../coverage/Http/Requests/Api/dashboard.html | 296 --- tests/coverage/Http/Requests/Api/index.html | 121 - .../Http/Requests/CustomerExport.php.html | 170 -- .../Requests/GenerateServiceToken.php.html | 157 -- .../Http/Requests/ManageBrandRequest.php.html | 178 -- .../Requests/ManageServiceRequest.php.html | 179 -- .../Http/Requests/QuestionExport.php.html | 159 -- .../Requests/StatsByBrandRequest.php.html | 183 -- .../Requests/StatsByQuestionRequest.php.html | 184 -- tests/coverage/Http/Requests/dashboard.html | 311 --- tests/coverage/Http/Requests/index.html | 323 --- .../Resources/Api/V2/ApiResponse.php.html | 220 -- .../Http/Resources/Api/dashboard.html | 286 --- tests/coverage/Http/Resources/Api/index.html | 121 - .../Http/Resources/CustomerResource.php.html | 182 -- tests/coverage/Http/Resources/dashboard.html | 285 --- tests/coverage/Http/Resources/index.html | 146 -- tests/coverage/Http/dashboard.html | 316 --- tests/coverage/Http/index.html | 244 -- tests/coverage/Jobs/SetServiceStats.php.html | 231 -- tests/coverage/Jobs/StartStatJobs.php.html | 146 -- tests/coverage/Jobs/StatJob.php.html | 236 -- .../UpdateCustomerLastConnection.php.html | 188 -- tests/coverage/Jobs/dashboard.html | 284 --- tests/coverage/Jobs/index.html | 128 -- tests/coverage/Lib/Helper.php.html | 501 ----- tests/coverage/Lib/MigrateOldData.php.html | 399 ---- tests/coverage/Lib/dashboard.html | 290 --- tests/coverage/Lib/index.html | 132 -- .../Mail/CourrierArcadiaError.php.html | 189 -- .../Mail/ServiceCredentialRenewed.php.html | 184 -- tests/coverage/Mail/index.html | 102 - tests/coverage/Models/Answer.php.html | 217 -- tests/coverage/Models/Brand.php.html | 502 ----- tests/coverage/Models/BrandStat.php.html | 236 -- tests/coverage/Models/Customer.php.html | 563 ----- tests/coverage/Models/CustomerAnswer.php.html | 238 -- tests/coverage/Models/CustomerInfo.php.html | 398 ---- tests/coverage/Models/CustomerLogin.php.html | 311 --- .../Models/CustomerOptInHistory.php.html | 215 -- tests/coverage/Models/Question.php.html | 231 -- tests/coverage/Models/Service.php.html | 585 ----- tests/coverage/Models/ServiceStat.php.html | 224 -- tests/coverage/Models/SingleOptin.php.html | 241 -- tests/coverage/Models/User.php.html | 144 -- tests/coverage/Models/dashboard.html | 307 --- tests/coverage/Models/index.html | 440 ---- tests/coverage/Policies/AnswerPolicy.php.html | 221 -- tests/coverage/Policies/BrandPolicy.php.html | 221 -- .../coverage/Policies/CustomerPolicy.php.html | 251 --- .../coverage/Policies/QuestionPolicy.php.html | 247 -- .../Policies/Rules/UserStaticRules.php.html | 156 -- .../coverage/Policies/ServicePolicy.php.html | 222 -- .../Policies/SingleOptinPolicy.php.html | 221 -- tests/coverage/Policies/dashboard.html | 284 --- .../Providers/AppServiceProvider.php.html | 167 -- .../Providers/AuthServiceProvider.php.html | 230 -- .../Providers/EventServiceProvider.php.html | 125 -- .../Providers/RouteServiceProvider.php.html | 282 --- .../TranslationServiceProvider.php.html | 296 --- .../Providers/ViewServiceProvider.php.html | 185 -- tests/coverage/Providers/dashboard.html | 284 --- tests/coverage/Providers/index.html | 229 -- tests/coverage/_css/bootstrap.min.css | 8 +- tests/coverage/_css/style.css | 44 +- tests/coverage/_js/bootstrap.min.js | 6 +- tests/coverage/_js/jquery.min.js | 4 +- ...rmClient.php.html => CreateAdmin.php.html} | 436 ++-- .../DeleteUnconfirmedCustomers.php.html | 154 +- .../Console/Commands/GenerateClient.php.html} | 456 ++-- .../Commands/LdcWebStatusChecker.php.html | 168 +- .../Console/Commands/MigrateCrmUsers.php.html | 172 +- .../Console/Commands/StartStatJobs.php.html | 45 +- .../app/Console/Commands/UserCreate.php.html | 164 -- .../app/Console/Commands/dashboard.html | 24 +- .../coverage/app/Console/Commands/index.html | 40 +- tests/coverage/app/Console/Kernel.php.html | 217 +- tests/coverage/app/Console/dashboard.html | 24 +- tests/coverage/app/Console/index.html | 82 +- tests/coverage/app/Enums/ApiError.php.html | 284 +-- tests/coverage/app/Enums/ApiStatus.php.html | 61 +- .../app/Enums/ArcadiaMailType.php.html | 146 +- .../app/Enums/ArcadiaOrderType.php.html | 146 +- tests/coverage/app/Enums/Country.php.html | 1575 ++++++------- .../app/Enums/CustomerExportType.php.html | 120 +- .../coverage/app/Enums/QuestionType.php.html | 149 +- tests/coverage/app/Enums/dashboard.html | 28 +- tests/coverage/app/Enums/index.html | 208 +- .../AbortRowImportException.php.html | 47 +- .../Api/V2/ApiErrorException.php.html | 232 +- .../Exceptions/Api/V2/CrmException.php.html | 160 +- .../app/Exceptions/Api/V2/dashboard.html | 29 +- .../coverage/app/Exceptions/Api/V2/index.html | 83 +- .../Api/V4}/ApiErrorException.php.html | 263 ++- .../Exceptions/Api/V4/CrmException.php.html} | 99 +- .../Exceptions/Api/V4}/dashboard.html | 37 +- .../coverage/app/Exceptions/Api/V4/index.html | 150 ++ .../app/Exceptions/Api/dashboard.html | 34 +- tests/coverage/app/Exceptions/Api/index.html | 62 +- .../coverage/app/Exceptions/Handler.php.html | 557 ++--- tests/coverage/app/Exceptions/dashboard.html | 42 +- tests/coverage/app/Exceptions/index.html | 106 +- .../Controllers/AnswerController.php.html | 446 +++- .../Api/V1/ArcadiaController.php.html | 512 ----- .../Api/V1/CustomerController.php.html | 785 ------- .../app/Http/Controllers/Api/V1/index.html | 106 - .../Api/V2/AnswerController.php.html | 79 +- .../Controllers/Api/V2/ApiController.php.html | 55 +- .../Api/V2/ArcadiaController.php.html | 184 +- .../Api/V2/CustomerController.php.html | 609 ++--- .../Api/V2/OptinController.php.html | 243 +- .../Api/V2/QuestionController.php.html | 121 +- .../Api/V2/StatsController.php.html | 161 +- .../Http/Controllers/Api/V2/dashboard.html | 24 +- .../app/Http/Controllers/Api/V2/index.html | 26 +- .../Api/V4}/AnswerController.php.html | 59 +- .../Api/V4}/ApiController.php.html | 39 +- .../Api/V4}/ArcadiaController.php.html | 171 +- .../Api/V4}/CustomerController.php.html | 277 +-- .../Api/V4}/OptinController.php.html | 117 +- .../Api/V4}/QuestionController.php.html | 73 +- .../Api/V4}/StatsController.php.html | 169 +- .../Http/Controllers/Api/V4}/dashboard.html | 27 +- .../Http/Controllers/Api/V4}/index.html | 39 +- .../app/Http/Controllers/Api/dashboard.html | 26 +- .../app/Http/Controllers/Api/index.html | 63 +- .../Http/Controllers/BrandController.php.html | 418 +++- .../app/Http/Controllers/Controller.php.html | 296 ++- .../Controllers/CustomerController.php.html | 846 +++---- .../Http/Controllers/LoginController.php.html | 278 ++- .../Controllers/QuestionController.php.html | 742 ++++-- .../Controllers/ServiceController.php.html | 551 ++++- .../SingleOptinController.php.html | 269 ++- .../Http/Controllers/StatsController.php.html | 617 ++--- .../app/Http/Controllers/dashboard.html | 122 +- .../coverage/app/Http/Controllers/index.html | 365 ++- tests/coverage/app/Http/Kernel.php.html | 105 +- .../Middleware/ApiCheckTimeStamps.php.html | 139 +- .../app/Http/Middleware/ApiResponse.php.html | 183 +- .../app/Http/Middleware/Authenticate.php.html | 214 +- .../app/Http/Middleware/CheckBrand.php.html | 154 -- .../Http/Middleware/EncryptCookies.php.html | 30 +- .../app/Http/Middleware/JsonOnly.php.html | 110 +- .../PreventRequestsDuringMaintenance.php.html | 30 +- .../RedirectIfAuthenticated.php.html | 184 +- .../Http/Middleware/SecureHeaders.php.html | 73 +- .../app/Http/Middleware/TrimStrings.php.html | 30 +- .../app/Http/Middleware/TrustHosts.php.html | 163 +- .../app/Http/Middleware/TrustProxies.php.html | 45 +- .../Http/Middleware/VerifyCsrfToken.php.html | 30 +- .../app/Http/Middleware/XssSanitizer.php.html | 69 +- .../app/Http/Middleware/dashboard.html | 42 +- tests/coverage/app/Http/Middleware/index.html | 191 +- .../Answers/StoreAnswerRequest.php.html | 43 +- .../Answers/UpdateAnswerRequest.php.html | 25 +- .../Http/Requests/Answers/dashboard.html | 19 +- .../Http/Requests/Answers/index.html | 21 +- .../Api/V2/AuthenticateRequest.php.html | 209 +- .../Requests/Api/V2/BaseApiRequest.php.html | 233 +- .../Api/V2/GetBrandStatsRequest.php.html | 201 +- .../Api/V2/GetQuestionStatsRequest.php.html | 201 +- .../SendArchadiaDefaultMailRequest.php.html | 394 ++-- .../V2/SendArchadiaGameMailRequest.php.html | 380 ++-- ...leOptinRemoveWithHashcodeRequest.php.html} | 75 +- .../Api/V2/SingleOptinRequest.php.html | 162 +- .../Api/V2/StoreCustomerAnswers.php.html | 219 +- .../Api/V2/StoreCustomerRequest.php.html | 337 ++- .../Api/V2/UpdateCustomerRequest.php.html | 298 +-- .../app/Http/Requests/Api/V2/dashboard.html | 34 +- .../app/Http/Requests/Api/V2/index.html | 410 ++-- .../Api/V4}/AuthenticateRequest.php.html | 51 +- .../Requests/Api/V4}/BaseApiRequest.php.html | 116 +- .../Api/V4}/GetBrandStatsRequest.php.html | 71 +- .../Api/V4/GetQuestionStatsRequest.php.html | 197 ++ .../SendArchadiaDefaultMailRequest.php.html | 244 +- .../V4}/SendArchadiaGameMailRequest.php.html | 230 +- ...gleOptinRemoveWithHashcodeRequest.php.html | 43 +- .../Api/V4}/SingleOptinRequest.php.html | 45 +- .../Api/V4}/StoreCustomerAnswers.php.html | 156 +- .../Api/V4/StoreCustomerRequest.php.html | 296 +++ .../Api/V4/UpdateCustomerRequest.php.html | 222 ++ .../Http/Requests/Api/V4}/dashboard.html | 35 +- .../Http/Requests/Api/V4}/index.html | 185 +- .../app/Http/Requests/Api/dashboard.html | 36 +- .../coverage/app/Http/Requests/Api/index.html | 75 +- .../Brands/StoreBrandRequest.php.html | 43 +- .../Brands/UpdateBrandRequest.php.html | 25 +- .../Http/Requests/Brands/dashboard.html | 19 +- .../{ => app}/Http/Requests/Brands/index.html | 21 +- .../app/Http/Requests/CustomerExport.php.html | 231 +- .../Requests/GenerateServiceToken.php.html | 205 +- .../Http/Requests/ManageBrandRequest.php.html | 248 +- .../Requests/ManageServiceRequest.php.html | 250 ++- .../app/Http/Requests/QuestionExport.php.html | 209 +- .../Questions/StoreQuestionRequest.php.html | 53 +- .../Questions/UpdateQuestionRequest.php.html | 25 +- .../Http/Requests/Questions/dashboard.html | 19 +- .../Http/Requests/Questions/index.html | 21 +- .../Services/StoreServiceRequest.php.html | 49 +- .../Services/UpdateServiceRequest.php.html | 25 +- .../Http/Requests/Services/dashboard.html | 19 +- .../Http/Requests/Services/index.html | 21 +- .../Requests/StatsByBrandRequest.php.html | 259 ++- .../Requests/StatsByQuestionRequest.php.html | 259 ++- .../coverage/app/Http/Requests/dashboard.html | 83 +- tests/coverage/app/Http/Requests/index.html | 429 +++- .../Resources/Api/V2/AnswersResource.php.html | 101 +- .../Resources/Api/V2/ApiResponse.php.html | 144 +- .../Api/V2/BrandStatsResource.php.html | 115 +- .../Api/V2/CustomerResource.php.html | 176 +- .../Api/V2/QuestionStatsResource.php.html | 176 +- .../Api/V2/QuestionsResource.php.html | 170 +- .../app/Http/Resources/Api/V2/dashboard.html | 22 +- .../app/Http/Resources/Api/V2/index.html | 26 +- .../Api/V4}/AnswersResource.php.html | 85 +- .../Resources/Api/V4/ApiResponse.php.html | 223 ++ .../Api/V4}/BrandStatsResource.php.html | 99 +- .../Api/V4}/CustomerResource.php.html | 140 +- .../Api/V4}/QuestionStatsResource.php.html | 117 +- .../Api/V4}/QuestionsResource.php.html | 145 +- .../Http/Resources/Api/V4}/dashboard.html | 27 +- .../Http/Resources/Api/V4}/index.html | 43 +- .../app/Http/Resources/Api/dashboard.html | 26 +- .../app/Http/Resources/Api/index.html | 46 +- .../Http/Resources/CustomerResource.php.html | 261 ++- .../Http/Resources/StatusResource.php.html | 39 +- .../app/Http/Resources/dashboard.html | 29 +- tests/coverage/app/Http/Resources/index.html | 81 +- .../Http/Sanitizers/RemoveSpaces.php.html | 43 +- .../{ => app}/Http/Sanitizers/dashboard.html | 21 +- .../{ => app}/Http/Sanitizers/index.html | 15 +- tests/coverage/app/Http/dashboard.html | 198 +- tests/coverage/app/Http/index.html | 244 +- .../app/Jobs/SetServiceStats.php.html | 359 +-- .../coverage/app/Jobs/StartStatJobs.php.html | 182 +- tests/coverage/app/Jobs/StatJob.php.html | 357 +-- .../UpdateCustomerLastConnection.php.html | 263 ++- tests/coverage/app/Jobs/dashboard.html | 34 +- tests/coverage/app/Jobs/index.html | 187 +- tests/coverage/app/Lib/Helper.php.html | 596 +++-- .../app/Lib/Helpers/ExceptionsHelper.php.html | 379 ++++ .../{Mail => app/Lib/Helpers}/dashboard.html | 32 +- .../Rules => app/Lib/Helpers}/index.html | 21 +- .../coverage/app/Lib/MigrateOldData.php.html | 549 ++--- tests/coverage/app/Lib/dashboard.html | 34 +- tests/coverage/app/Lib/index.html | 65 +- .../app/Mail/CourrierArcadiaError.php.html | 261 ++- .../Mail/ServiceCredentialRenewed.php.html | 260 ++- tests/coverage/app/Mail/dashboard.html | 32 +- tests/coverage/app/Mail/index.html | 117 +- tests/coverage/app/Models/Answer.php.html | 213 +- tests/coverage/app/Models/Brand.php.html | 538 ++--- tests/coverage/app/Models/BrandStat.php.html | 252 ++- tests/coverage/app/Models/Customer.php.html | 646 +++--- .../app/Models/CustomerAnswer.php.html | 153 +- .../coverage/app/Models/CustomerInfo.php.html | 463 ++-- .../app/Models/CustomerLogin.php.html | 273 +-- .../app/Models/CustomerOptInHistory.php.html | 154 +- tests/coverage/app/Models/Question.php.html | 276 +-- tests/coverage/app/Models/Service.php.html | 855 ++++--- .../coverage/app/Models/ServiceStat.php.html | 175 +- .../coverage/app/Models/SingleOptin.php.html | 303 ++- tests/coverage/app/Models/User.php.html | 228 +- tests/coverage/app/Models/dashboard.html | 78 +- tests/coverage/app/Models/index.html | 159 +- .../app/Policies/AnswerPolicy.php.html | 300 ++- .../app/Policies/BrandPolicy.php.html | 300 ++- .../app/Policies/CustomerPolicy.php.html | 361 ++- .../app/Policies/QuestionPolicy.php.html | 355 ++- .../Policies/Rules/UserStaticRules.php.html | 210 +- .../app/Policies/Rules/dashboard.html | 29 +- tests/coverage/app/Policies/Rules/index.html | 82 +- .../app/Policies/ServicePolicy.php.html | 300 ++- .../app/Policies/SingleOptinPolicy.php.html | 300 ++- tests/coverage/app/Policies/dashboard.html | 65 +- tests/coverage/app/Policies/index.html | 292 ++- .../app/Providers/AppServiceProvider.php.html | 243 +- .../Providers/AuthServiceProvider.php.html | 168 +- .../Providers/EventServiceProvider.php.html | 34 +- .../Providers/RouteServiceProvider.php.html | 273 +-- .../TranslationServiceProvider.php.html | 297 +++ .../Providers/ViewServiceProvider.php.html | 186 ++ tests/coverage/app/Providers/dashboard.html | 30 +- tests/coverage/app/Providers/index.html | 135 +- .../app/Traits/Enums/BaseEnum.php.html | 720 ++++++ .../src => app/Traits}/Enums/dashboard.html | 52 +- tests/coverage/app/Traits/Enums/index.html | 121 + .../Api/V1 => Traits}/dashboard.html | 50 +- .../{Exceptions/Api => app/Traits}/index.html | 54 +- tests/coverage/app/dashboard.html | 348 ++- tests/coverage/app/index.html | 464 ++-- tests/coverage/client/dashboard.html | 438 ++++ tests/coverage/{clients => client}/index.html | 64 +- tests/coverage/client/src/CrmApiV4.php.html | 430 ++++ .../client/src/Enums/ApiError.php.html | 133 ++ .../{ => client/src}/Enums/ApiStatus.php.html | 61 +- .../src/Enums/ArcadiaMailType.php.html} | 62 +- .../src/Enums/ArcadiaOrderType.php.html} | 63 +- .../client/src/Enums/Country.php.html | 792 +++++++ .../src/Enums/HttpMethods.php.html | 68 +- .../client/src/Enums/LogLevel.php.html | 108 + .../src/Enums/QuestionType.php.html} | 69 +- .../Rules => client/src/Enums}/dashboard.html | 31 +- .../{Policies => client/src/Enums}/index.html | 45 +- .../src/Exceptions/AccessError.php.html | 96 +- .../src/Exceptions/ApiErrorException.php.html | 379 ++-- .../src/Exceptions/CrmException.php.html} | 113 +- .../SkipValidationException.php.html | 53 +- .../Exceptions/ValidationException.php.html | 180 +- .../WpCrmInvalidJsonUserInfo.php.html | 105 +- .../src/Exceptions/dashboard.html | 44 +- .../V2 => client}/src/Exceptions/index.html | 175 +- .../V2 => client}/src/Models/Answer.php.html | 315 +-- .../src/Models/ArcadiaMailDefault.php.html | 1046 ++++----- .../src/Models/ArcadiaMailGame.php.html | 908 ++++---- .../client/src/Models/ClientModel.php.html | 747 ++++++ .../client/src/Models/Customer.php.html | 1099 +++++++++ .../client/src/Models/Question.php.html | 641 ++++++ .../client/src/Models/SingleOptin.php.html | 251 +++ .../V2 => client}/src/Models/Stats.php.html | 123 +- .../coverage/client/src/Models/dashboard.html | 323 +++ .../V2 => client}/src/Models/index.html | 111 +- .../client/src/Traits/BaseEnum.php.html | 720 ++++++ .../coverage/client/src/Traits/dashboard.html | 302 +++ .../Api/V2 => client/src/Traits}/index.html | 69 +- .../src/Transport/ApiInterface.php.html | 540 +++++ .../client/src/Transport/Request.php.html | 494 ++++ .../client/src/Transport/Response.php.html | 430 ++++ .../src/Transport/dashboard.html | 67 +- .../V2 => client}/src/Transport/index.html | 55 +- tests/coverage/client/src/Utils.php.html | 1462 ++++++++++++ .../src/WordPress/WPCrm.php.html | 1999 +++++++++-------- .../src/WordPress/WPCrmAnswer.php.html | 56 +- .../src/WordPress/WPCrmQuestion.php.html | 86 +- .../src/WordPress/WPCrmUser.php.html | 637 +++--- .../src/WordPress/dashboard.html | 135 +- .../V2 => client}/src/WordPress/index.html | 35 +- tests/coverage/client/src/dashboard.html | 439 ++++ .../{clients/V2 => client}/src/index.html | 204 +- tests/coverage/clients/V2/dashboard.html | 434 ---- tests/coverage/clients/V2/index.html | 120 - .../coverage/clients/V2/src/CrmApiV2.php.html | 383 ---- .../clients/V2/src/Enums/ApiError.php.html | 290 --- .../V2/src/Enums/ArcadiaOrderType.php.html | 183 -- .../clients/V2/src/Enums/Country.php.html | 960 -------- .../V2/src/Enums/QuestionType.php.html | 185 -- .../coverage/clients/V2/src/Enums/index.html | 288 --- .../V2/src/Exceptions/CrmException.php.html | 144 -- .../V2/src/Models/CrmClientModel.php.html | 736 ------ .../clients/V2/src/Models/Customer.php.html | 1069 --------- .../clients/V2/src/Models/Question.php.html | 631 ------ .../V2/src/Models/SingleOptin.php.html | 207 -- .../clients/V2/src/Models/dashboard.html | 326 --- .../V2/src/Transport/ApiInterface.php.html | 516 ----- .../clients/V2/src/Transport/Request.php.html | 477 ---- .../V2/src/Transport/Response.php.html | 419 ---- tests/coverage/clients/V2/src/Utils.php.html | 1373 ----------- tests/coverage/clients/V2/src/dashboard.html | 435 ---- tests/coverage/clients/dashboard.html | 433 ---- tests/coverage/dashboard.html | 527 ++++- tests/coverage/index.html | 258 +-- vite.config.ts | 4 +- 589 files changed, 43093 insertions(+), 64401 deletions(-) rename app/Console/Commands/{GenerateCrmClient.php => GenerateClient.php} (68%) create mode 100644 app/Exceptions/Api/V4/ApiErrorException.php create mode 100644 app/Exceptions/Api/V4/CrmException.php create mode 100644 app/Http/Controllers/Api/V4/AnswerController.php create mode 100644 app/Http/Controllers/Api/V4/ApiController.php create mode 100644 app/Http/Controllers/Api/V4/ArcadiaController.php create mode 100644 app/Http/Controllers/Api/V4/CustomerController.php create mode 100644 app/Http/Controllers/Api/V4/OptinController.php create mode 100644 app/Http/Controllers/Api/V4/QuestionController.php create mode 100644 app/Http/Controllers/Api/V4/StatsController.php delete mode 100644 app/Http/Middleware/CheckBrand.php create mode 100644 app/Http/Requests/Api/V4/AuthenticateRequest.php create mode 100644 app/Http/Requests/Api/V4/BaseApiRequest.php create mode 100644 app/Http/Requests/Api/V4/GetBrandStatsRequest.php create mode 100644 app/Http/Requests/Api/V4/GetQuestionStatsRequest.php create mode 100644 app/Http/Requests/Api/V4/SendArchadiaDefaultMailRequest.php create mode 100644 app/Http/Requests/Api/V4/SendArchadiaGameMailRequest.php create mode 100644 app/Http/Requests/Api/V4/SingleOptinRemoveWithHashcodeRequest.php create mode 100644 app/Http/Requests/Api/V4/SingleOptinRequest.php create mode 100644 app/Http/Requests/Api/V4/StoreCustomerAnswers.php create mode 100644 app/Http/Requests/Api/V4/StoreCustomerRequest.php create mode 100644 app/Http/Requests/Api/V4/UpdateCustomerRequest.php create mode 100644 app/Http/Resources/Api/V4/AnswersResource.php create mode 100644 app/Http/Resources/Api/V4/ApiResponse.php create mode 100644 app/Http/Resources/Api/V4/BrandStatsResource.php create mode 100644 app/Http/Resources/Api/V4/CustomerResource.php create mode 100644 app/Http/Resources/Api/V4/QuestionStatsResource.php create mode 100644 app/Http/Resources/Api/V4/QuestionsResource.php delete mode 160000 clients/V2 create mode 100644 tests/CRM/ApiV4/ApiBase.php create mode 100644 tests/CRM/ApiV4/ApiTest.php create mode 100644 tests/CRM/ApiV4/ArcadiaTest.php create mode 100644 tests/CRM/ApiV4/CustomerTest.php create mode 100644 tests/CRM/ApiV4/QuestionTest.php create mode 100644 tests/CRM/ApiV4/SingleOptinTest.php create mode 100644 tests/CRM/ApiV4/StatsTest.php rename tests/Client/{CrmClientV2ArcadiaTest.php => CrmClientArcadiaTest.php} (96%) rename tests/Client/{CrmClientV2ClientTest.php => CrmClientClientTest.php} (75%) rename tests/Client/{CrmClientV2ConfigTest.php => CrmClientConfigTest.php} (82%) rename tests/Client/{CrmClientV2CustomerTest.php => CrmClientCustomerTest.php} (89%) rename tests/Client/{CrmClientV2QuestionTest.php => CrmClientQuestionTest.php} (96%) rename tests/Client/{CrmClientV2RouterTest.php => CrmClientRouterTest.php} (93%) rename tests/Client/{CrmClientV2SingleOptinTest.php => CrmClientSingleOptinTest.php} (93%) rename tests/Client/{CrmClientV2StatsTest.php => CrmClientStatsTest.php} (99%) delete mode 100644 tests/coverage/Console/Commands/DeleteUnconfirmedCustomers.php.html delete mode 100644 tests/coverage/Console/Commands/LdcWebStatusChecker.php.html delete mode 100644 tests/coverage/Console/Commands/MigrateCrmUsers.php.html delete mode 100644 tests/coverage/Console/Commands/StartStatJobs.php.html delete mode 100644 tests/coverage/Console/Commands/UserCreate.php.html delete mode 100644 tests/coverage/Console/Commands/dashboard.html delete mode 100644 tests/coverage/Console/Commands/index.html delete mode 100644 tests/coverage/Console/Kernel.php.html delete mode 100644 tests/coverage/Console/dashboard.html delete mode 100644 tests/coverage/Console/index.html delete mode 100644 tests/coverage/Enums/ApiError.php.html delete mode 100644 tests/coverage/Enums/ArcadiaMailType.php.html delete mode 100644 tests/coverage/Enums/ArcadiaOrderType.php.html delete mode 100644 tests/coverage/Enums/Country.php.html delete mode 100644 tests/coverage/Enums/CustomerExportType.php.html delete mode 100644 tests/coverage/Enums/QuestionType.php.html delete mode 100644 tests/coverage/Enums/dashboard.html delete mode 100644 tests/coverage/Enums/index.html delete mode 100644 tests/coverage/Exceptions/Api/V2/CrmException.php.html delete mode 100644 tests/coverage/Exceptions/Api/dashboard.html delete mode 100644 tests/coverage/Exceptions/Handler.php.html delete mode 100644 tests/coverage/Exceptions/dashboard.html delete mode 100644 tests/coverage/Exceptions/index.html delete mode 100644 tests/coverage/Http/Controllers/AnswerController.php.html delete mode 100644 tests/coverage/Http/Controllers/Api/V1/ArcadiaController.php.html delete mode 100644 tests/coverage/Http/Controllers/Api/V1/CustomerController.php.html delete mode 100644 tests/coverage/Http/Controllers/Api/V1/index.html delete mode 100644 tests/coverage/Http/Controllers/Api/dashboard.html delete mode 100644 tests/coverage/Http/Controllers/Api/index.html delete mode 100644 tests/coverage/Http/Controllers/BrandController.php.html delete mode 100644 tests/coverage/Http/Controllers/Controller.php.html delete mode 100644 tests/coverage/Http/Controllers/CustomerController.php.html delete mode 100644 tests/coverage/Http/Controllers/LoginController.php.html delete mode 100644 tests/coverage/Http/Controllers/QuestionController.php.html delete mode 100644 tests/coverage/Http/Controllers/ServiceController.php.html delete mode 100644 tests/coverage/Http/Controllers/SingleOptinController.php.html delete mode 100644 tests/coverage/Http/Controllers/StatsController.php.html delete mode 100644 tests/coverage/Http/Controllers/dashboard.html delete mode 100644 tests/coverage/Http/Controllers/index.html delete mode 100644 tests/coverage/Http/Kernel.php.html delete mode 100644 tests/coverage/Http/Middleware/ApiCheckTimeStamps.php.html delete mode 100644 tests/coverage/Http/Middleware/ApiResponse.php.html delete mode 100644 tests/coverage/Http/Middleware/Authenticate.php.html delete mode 100644 tests/coverage/Http/Middleware/CheckBrand.php.html delete mode 100644 tests/coverage/Http/Middleware/EncryptCookies.php.html delete mode 100644 tests/coverage/Http/Middleware/JsonOnly.php.html delete mode 100644 tests/coverage/Http/Middleware/PreventRequestsDuringMaintenance.php.html delete mode 100644 tests/coverage/Http/Middleware/RedirectIfAuthenticated.php.html delete mode 100644 tests/coverage/Http/Middleware/TrimStrings.php.html delete mode 100644 tests/coverage/Http/Middleware/TrustProxies.php.html delete mode 100644 tests/coverage/Http/Middleware/VerifyCsrfToken.php.html delete mode 100644 tests/coverage/Http/Middleware/XssSanitizer.php.html delete mode 100644 tests/coverage/Http/Middleware/dashboard.html delete mode 100644 tests/coverage/Http/Middleware/index.html delete mode 100644 tests/coverage/Http/Requests/Api/V2/GetQuestionStatsRequest.php.html delete mode 100644 tests/coverage/Http/Requests/Api/V2/StoreCustomerRequest.php.html delete mode 100644 tests/coverage/Http/Requests/Api/V2/UpdateCustomerRequest.php.html delete mode 100644 tests/coverage/Http/Requests/Api/V2/dashboard.html delete mode 100644 tests/coverage/Http/Requests/Api/dashboard.html delete mode 100644 tests/coverage/Http/Requests/Api/index.html delete mode 100644 tests/coverage/Http/Requests/CustomerExport.php.html delete mode 100644 tests/coverage/Http/Requests/GenerateServiceToken.php.html delete mode 100644 tests/coverage/Http/Requests/ManageBrandRequest.php.html delete mode 100644 tests/coverage/Http/Requests/ManageServiceRequest.php.html delete mode 100644 tests/coverage/Http/Requests/QuestionExport.php.html delete mode 100644 tests/coverage/Http/Requests/StatsByBrandRequest.php.html delete mode 100644 tests/coverage/Http/Requests/StatsByQuestionRequest.php.html delete mode 100644 tests/coverage/Http/Requests/dashboard.html delete mode 100644 tests/coverage/Http/Requests/index.html delete mode 100644 tests/coverage/Http/Resources/Api/V2/ApiResponse.php.html delete mode 100644 tests/coverage/Http/Resources/Api/dashboard.html delete mode 100644 tests/coverage/Http/Resources/Api/index.html delete mode 100644 tests/coverage/Http/Resources/CustomerResource.php.html delete mode 100644 tests/coverage/Http/Resources/dashboard.html delete mode 100644 tests/coverage/Http/Resources/index.html delete mode 100644 tests/coverage/Http/dashboard.html delete mode 100644 tests/coverage/Http/index.html delete mode 100644 tests/coverage/Jobs/SetServiceStats.php.html delete mode 100644 tests/coverage/Jobs/StartStatJobs.php.html delete mode 100644 tests/coverage/Jobs/StatJob.php.html delete mode 100644 tests/coverage/Jobs/UpdateCustomerLastConnection.php.html delete mode 100644 tests/coverage/Jobs/dashboard.html delete mode 100644 tests/coverage/Jobs/index.html delete mode 100644 tests/coverage/Lib/Helper.php.html delete mode 100644 tests/coverage/Lib/MigrateOldData.php.html delete mode 100644 tests/coverage/Lib/dashboard.html delete mode 100644 tests/coverage/Lib/index.html delete mode 100644 tests/coverage/Mail/CourrierArcadiaError.php.html delete mode 100644 tests/coverage/Mail/ServiceCredentialRenewed.php.html delete mode 100644 tests/coverage/Mail/index.html delete mode 100644 tests/coverage/Models/Answer.php.html delete mode 100644 tests/coverage/Models/Brand.php.html delete mode 100644 tests/coverage/Models/BrandStat.php.html delete mode 100644 tests/coverage/Models/Customer.php.html delete mode 100644 tests/coverage/Models/CustomerAnswer.php.html delete mode 100644 tests/coverage/Models/CustomerInfo.php.html delete mode 100644 tests/coverage/Models/CustomerLogin.php.html delete mode 100644 tests/coverage/Models/CustomerOptInHistory.php.html delete mode 100644 tests/coverage/Models/Question.php.html delete mode 100644 tests/coverage/Models/Service.php.html delete mode 100644 tests/coverage/Models/ServiceStat.php.html delete mode 100644 tests/coverage/Models/SingleOptin.php.html delete mode 100644 tests/coverage/Models/User.php.html delete mode 100644 tests/coverage/Models/dashboard.html delete mode 100644 tests/coverage/Models/index.html delete mode 100644 tests/coverage/Policies/AnswerPolicy.php.html delete mode 100644 tests/coverage/Policies/BrandPolicy.php.html delete mode 100644 tests/coverage/Policies/CustomerPolicy.php.html delete mode 100644 tests/coverage/Policies/QuestionPolicy.php.html delete mode 100644 tests/coverage/Policies/Rules/UserStaticRules.php.html delete mode 100644 tests/coverage/Policies/ServicePolicy.php.html delete mode 100644 tests/coverage/Policies/SingleOptinPolicy.php.html delete mode 100644 tests/coverage/Policies/dashboard.html delete mode 100644 tests/coverage/Providers/AppServiceProvider.php.html delete mode 100644 tests/coverage/Providers/AuthServiceProvider.php.html delete mode 100644 tests/coverage/Providers/EventServiceProvider.php.html delete mode 100644 tests/coverage/Providers/RouteServiceProvider.php.html delete mode 100644 tests/coverage/Providers/TranslationServiceProvider.php.html delete mode 100644 tests/coverage/Providers/ViewServiceProvider.php.html delete mode 100644 tests/coverage/Providers/dashboard.html delete mode 100644 tests/coverage/Providers/index.html rename tests/coverage/app/Console/Commands/{GenerateCrmClient.php.html => CreateAdmin.php.html} (50%) rename tests/coverage/{Console/Commands/GenerateCrmClient.php.html => app/Console/Commands/GenerateClient.php.html} (73%) delete mode 100644 tests/coverage/app/Console/Commands/UserCreate.php.html rename tests/coverage/{Exceptions/Api/V2 => app/Exceptions/Api/V4}/ApiErrorException.php.html (66%) rename tests/coverage/{clients/V2/src/Enums/ArcadiaMailType.php.html => app/Exceptions/Api/V4/CrmException.php.html} (71%) rename tests/coverage/{Exceptions/Api/V2 => app/Exceptions/Api/V4}/dashboard.html (72%) create mode 100644 tests/coverage/app/Exceptions/Api/V4/index.html delete mode 100644 tests/coverage/app/Http/Controllers/Api/V1/ArcadiaController.php.html delete mode 100644 tests/coverage/app/Http/Controllers/Api/V1/CustomerController.php.html delete mode 100644 tests/coverage/app/Http/Controllers/Api/V1/index.html rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/AnswerController.php.html (83%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/ApiController.php.html (82%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/ArcadiaController.php.html (71%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/CustomerController.php.html (87%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/OptinController.php.html (83%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/QuestionController.php.html (87%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/StatsController.php.html (79%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/dashboard.html (73%) rename tests/coverage/{Http/Controllers/Api/V2 => app/Http/Controllers/Api/V4}/index.html (85%) delete mode 100644 tests/coverage/app/Http/Middleware/CheckBrand.php.html rename tests/coverage/{ => app}/Http/Middleware/SecureHeaders.php.html (80%) rename tests/coverage/{ => app}/Http/Requests/Answers/StoreAnswerRequest.php.html (86%) rename tests/coverage/{ => app}/Http/Requests/Answers/UpdateAnswerRequest.php.html (74%) rename tests/coverage/{ => app}/Http/Requests/Answers/dashboard.html (89%) rename tests/coverage/{ => app}/Http/Requests/Answers/index.html (83%) rename tests/coverage/{Http/Middleware/TrustHosts.php.html => app/Http/Requests/Api/V2/SingleOptinRemoveWithHashcodeRequest.php.html} (66%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/AuthenticateRequest.php.html (69%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/BaseApiRequest.php.html (68%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/GetBrandStatsRequest.php.html (65%) create mode 100644 tests/coverage/app/Http/Requests/Api/V4/GetQuestionStatsRequest.php.html rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/SendArchadiaDefaultMailRequest.php.html (58%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/SendArchadiaGameMailRequest.php.html (52%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/SingleOptinRemoveWithHashcodeRequest.php.html (74%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/SingleOptinRequest.php.html (66%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/StoreCustomerAnswers.php.html (63%) create mode 100644 tests/coverage/app/Http/Requests/Api/V4/StoreCustomerRequest.php.html create mode 100644 tests/coverage/app/Http/Requests/Api/V4/UpdateCustomerRequest.php.html rename tests/coverage/{Http/Controllers/Api/V1 => app/Http/Requests/Api/V4}/dashboard.html (52%) rename tests/coverage/{Http/Requests/Api/V2 => app/Http/Requests/Api/V4}/index.html (73%) rename tests/coverage/{ => app}/Http/Requests/Brands/StoreBrandRequest.php.html (86%) rename tests/coverage/{ => app}/Http/Requests/Brands/UpdateBrandRequest.php.html (74%) rename tests/coverage/{ => app}/Http/Requests/Brands/dashboard.html (89%) rename tests/coverage/{ => app}/Http/Requests/Brands/index.html (83%) rename tests/coverage/{ => app}/Http/Requests/Questions/StoreQuestionRequest.php.html (80%) rename tests/coverage/{ => app}/Http/Requests/Questions/UpdateQuestionRequest.php.html (74%) rename tests/coverage/{ => app}/Http/Requests/Questions/dashboard.html (89%) rename tests/coverage/{ => app}/Http/Requests/Questions/index.html (83%) rename tests/coverage/{ => app}/Http/Requests/Services/StoreServiceRequest.php.html (82%) rename tests/coverage/{ => app}/Http/Requests/Services/UpdateServiceRequest.php.html (74%) rename tests/coverage/{ => app}/Http/Requests/Services/dashboard.html (89%) rename tests/coverage/{ => app}/Http/Requests/Services/index.html (83%) rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/AnswersResource.php.html (62%) create mode 100644 tests/coverage/app/Http/Resources/Api/V4/ApiResponse.php.html rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/BrandStatsResource.php.html (61%) rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/CustomerResource.php.html (72%) rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/QuestionStatsResource.php.html (73%) rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/QuestionsResource.php.html (65%) rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/dashboard.html (71%) rename tests/coverage/{Http/Resources/Api/V2 => app/Http/Resources/Api/V4}/index.html (83%) rename tests/coverage/{ => app}/Http/Sanitizers/RemoveSpaces.php.html (69%) rename tests/coverage/{ => app}/Http/Sanitizers/dashboard.html (86%) rename tests/coverage/{ => app}/Http/Sanitizers/index.html (84%) create mode 100644 tests/coverage/app/Lib/Helpers/ExceptionsHelper.php.html rename tests/coverage/{Mail => app/Lib/Helpers}/dashboard.html (72%) rename tests/coverage/{Policies/Rules => app/Lib/Helpers}/index.html (71%) create mode 100644 tests/coverage/app/Providers/TranslationServiceProvider.php.html create mode 100644 tests/coverage/app/Providers/ViewServiceProvider.php.html create mode 100644 tests/coverage/app/Traits/Enums/BaseEnum.php.html rename tests/coverage/{clients/V2/src => app/Traits}/Enums/dashboard.html (54%) create mode 100644 tests/coverage/app/Traits/Enums/index.html rename tests/coverage/app/{Http/Controllers/Api/V1 => Traits}/dashboard.html (54%) rename tests/coverage/{Exceptions/Api => app/Traits}/index.html (58%) create mode 100644 tests/coverage/client/dashboard.html rename tests/coverage/{clients => client}/index.html (52%) create mode 100644 tests/coverage/client/src/CrmApiV4.php.html create mode 100644 tests/coverage/client/src/Enums/ApiError.php.html rename tests/coverage/{ => client/src}/Enums/ApiStatus.php.html (52%) rename tests/coverage/{Http/Resources/StatusResource.php.html => client/src/Enums/ArcadiaMailType.php.html} (52%) rename tests/coverage/{Exceptions/AbortRowImportException.php.html => client/src/Enums/ArcadiaOrderType.php.html} (53%) create mode 100644 tests/coverage/client/src/Enums/Country.php.html rename tests/coverage/{clients/V2 => client}/src/Enums/HttpMethods.php.html (54%) create mode 100644 tests/coverage/client/src/Enums/LogLevel.php.html rename tests/coverage/{clients/V2/src/Enums/ApiStatus.php.html => client/src/Enums/QuestionType.php.html} (53%) rename tests/coverage/{Policies/Rules => client/src/Enums}/dashboard.html (76%) rename tests/coverage/{Policies => client/src/Enums}/index.html (68%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/AccessError.php.html (70%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/ApiErrorException.php.html (52%) rename tests/coverage/{clients/V2/src/Enums/LogLevel.php.html => client/src/Exceptions/CrmException.php.html} (54%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/SkipValidationException.php.html (70%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/ValidationException.php.html (50%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/WpCrmInvalidJsonUserInfo.php.html (76%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/dashboard.html (67%) rename tests/coverage/{clients/V2 => client}/src/Exceptions/index.html (55%) rename tests/coverage/{clients/V2 => client}/src/Models/Answer.php.html (57%) rename tests/coverage/{clients/V2 => client}/src/Models/ArcadiaMailDefault.php.html (57%) rename tests/coverage/{clients/V2 => client}/src/Models/ArcadiaMailGame.php.html (58%) create mode 100644 tests/coverage/client/src/Models/ClientModel.php.html create mode 100644 tests/coverage/client/src/Models/Customer.php.html create mode 100644 tests/coverage/client/src/Models/Question.php.html create mode 100644 tests/coverage/client/src/Models/SingleOptin.php.html rename tests/coverage/{clients/V2 => client}/src/Models/Stats.php.html (52%) create mode 100644 tests/coverage/client/src/Models/dashboard.html rename tests/coverage/{clients/V2 => client}/src/Models/index.html (70%) create mode 100644 tests/coverage/client/src/Traits/BaseEnum.php.html create mode 100644 tests/coverage/client/src/Traits/dashboard.html rename tests/coverage/{Exceptions/Api/V2 => client/src/Traits}/index.html (52%) create mode 100644 tests/coverage/client/src/Transport/ApiInterface.php.html create mode 100644 tests/coverage/client/src/Transport/Request.php.html create mode 100644 tests/coverage/client/src/Transport/Response.php.html rename tests/coverage/{clients/V2 => client}/src/Transport/dashboard.html (60%) rename tests/coverage/{clients/V2 => client}/src/Transport/index.html (71%) create mode 100644 tests/coverage/client/src/Utils.php.html rename tests/coverage/{clients/V2 => client}/src/WordPress/WPCrm.php.html (79%) rename tests/coverage/{clients/V2 => client}/src/WordPress/WPCrmAnswer.php.html (89%) rename tests/coverage/{clients/V2 => client}/src/WordPress/WPCrmQuestion.php.html (92%) rename tests/coverage/{clients/V2 => client}/src/WordPress/WPCrmUser.php.html (79%) rename tests/coverage/{clients/V2 => client}/src/WordPress/dashboard.html (60%) rename tests/coverage/{clients/V2 => client}/src/WordPress/index.html (85%) create mode 100644 tests/coverage/client/src/dashboard.html rename tests/coverage/{clients/V2 => client}/src/index.html (54%) delete mode 100644 tests/coverage/clients/V2/dashboard.html delete mode 100644 tests/coverage/clients/V2/index.html delete mode 100644 tests/coverage/clients/V2/src/CrmApiV2.php.html delete mode 100644 tests/coverage/clients/V2/src/Enums/ApiError.php.html delete mode 100644 tests/coverage/clients/V2/src/Enums/ArcadiaOrderType.php.html delete mode 100644 tests/coverage/clients/V2/src/Enums/Country.php.html delete mode 100644 tests/coverage/clients/V2/src/Enums/QuestionType.php.html delete mode 100644 tests/coverage/clients/V2/src/Enums/index.html delete mode 100644 tests/coverage/clients/V2/src/Exceptions/CrmException.php.html delete mode 100644 tests/coverage/clients/V2/src/Models/CrmClientModel.php.html delete mode 100644 tests/coverage/clients/V2/src/Models/Customer.php.html delete mode 100644 tests/coverage/clients/V2/src/Models/Question.php.html delete mode 100644 tests/coverage/clients/V2/src/Models/SingleOptin.php.html delete mode 100644 tests/coverage/clients/V2/src/Models/dashboard.html delete mode 100644 tests/coverage/clients/V2/src/Transport/ApiInterface.php.html delete mode 100644 tests/coverage/clients/V2/src/Transport/Request.php.html delete mode 100644 tests/coverage/clients/V2/src/Transport/Response.php.html delete mode 100644 tests/coverage/clients/V2/src/Utils.php.html delete mode 100644 tests/coverage/clients/V2/src/dashboard.html delete mode 100644 tests/coverage/clients/dashboard.html diff --git a/.gitignore b/.gitignore index d393553..771617f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ Homestead.yaml npm-debug.log yarn-error.log .npmrc +.phpdoc diff --git a/.gitmodules b/.gitmodules index 5c9a99e..1833bb0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "clients/V2"] -path = clients/V2 +[submodule "client"] +path = client url = git@lab.s-web.fr:ldc-web/projets/crm-client.git diff --git a/.vscode/settings.json b/.vscode/settings.json index 726063b..c774b95 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,5 +22,5 @@ "commandCenter.border": "#15202b99" }, "peacock.remoteColor": "#73b137", - "intelephense.environment.phpVersion": "7.4" + "intelephense.environment.phpVersion": "8.2" } diff --git a/Dockerfile b/Dockerfile index 27dfa8f..f26f30d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-7.3:latest +FROM registry.s-web.fr/ldc-web/projets/ldc-web-images-docker/ldcweb-production-8.2:latest COPY --chown=www-data:www-data . /app/srcs COPY --chown=www-data:www-data ./public /app/srcs/public COPY --chown=www-data:www-data ./vendor /app/srcs/vendor diff --git a/app/Console/Commands/CreateAdmin.php b/app/Console/Commands/CreateAdmin.php index 5b6d448..99b8026 100644 --- a/app/Console/Commands/CreateAdmin.php +++ b/app/Console/Commands/CreateAdmin.php @@ -9,6 +9,8 @@ use Illuminate\Validation\ValidationException; /** * Creates a new user + * + * @codeCoverageIgnore */ class CreateAdmin extends Command { @@ -26,77 +28,155 @@ class CreateAdmin extends Command */ protected $description = 'Create a backend admin user'; + /** + * The firstname of the new user. + * + * @var string + */ + protected $firstname; + + /** + * The lastname of the new user. + * + * @var string + */ + protected $lastname; + + /** + * The email of the new user. + * + * @var string + */ + protected $email; + + /** + * The password of the new user. + * + * @var string + */ + protected $password; + /** * Execute the console command. * * @return integer */ - public function handle() + public function handle(): int { - $email = null; - while (is_null($email)) { - try { - $tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr'); - $validator = Validator::make(['email' => $tmp], [ - 'email' => 'required|unique:users,email|email:rfc,dns' - ]); - $validator->validate(); - $email = $tmp; - } catch (ValidationException $e) { - $this->error(sprintf( - 'Please provide a valid email %s', - \implode(',', collect($e->errors())->flatten()->all()) - )); - continue; - } - } - $firstname = null; - while (is_null($firstname)) { + $this->firstname = null; + $this->addFirstname(); + + $this->lastname = null; + $this->addLastname(); + + $this->email = null; + $this->addEmail(); + + $this->password = null; + $this->addPassword(); + + $user = new User([ + 'firstname' => $this->firstname, + 'lastname' => $this->lastname, + 'email' => $this->email, + 'password' => $this->password, + 'remember_token' => null, + ]); + $user->saveOrFail(); + + $this->info('User created 👌'); + + return 0; + } + + /** + * Add a firstname to the user. + * + * @return void + */ + private function addFirstname(): void + { + while (is_null($this->firstname)) { try { $tmp = $this->ask('Type the wanted user firstname', 'Admin'); $validator = Validator::make(['firstname' => $tmp], [ - 'firstname' => 'required|min:5,max:255' + 'firstname' => 'required|min:3|max:255' ]); $validator->validate(); - $firstname = $tmp; + $this->firstname = $tmp; } catch (ValidationException $e) { $this->error(sprintf( 'Please provide a valid firstname %s', \implode(',', collect($e->errors())->flatten()->all()) )); continue; - } - } + } //end try + }; //end while + } - $lastname = null; - while (is_null($lastname)) { + /** + * Add a lastname to the user. + * + * @return void + */ + private function addLastname(): void + { + while (is_null($this->lastname)) { try { $tmp = $this->ask('Type the wanted user lastname', 'Saïtama'); $validator = Validator::make(['lastname' => $tmp], [ - 'lastname' => 'required|min:5,max:255' + 'lastname' => 'required|min:3|max:255' ]); $validator->validate(); - $lastname = $tmp; + $this->lastname = $tmp; } catch (ValidationException $e) { $this->error(sprintf( 'Please provide a valid lastname %s', \implode(',', collect($e->errors())->flatten()->all()) )); continue; - } - } + } //end try + }; //end while + } - $password = $this->secret('Type the wanted user password'); + /** + * Add an email to the user. + * + * @return void + */ + private function addEmail(): void + { + while (is_null($this->email)) { + try { + $tmp = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr'); + $validator = Validator::make(['email' => $tmp], [ + 'email' => 'required|unique:users,email|email:rfc,dns' + ]); + $validator->validate(); + $this->email = $tmp; + } catch (ValidationException $e) { + $this->error(sprintf( + 'Please provide a valid email %s', + \implode(',', collect($e->errors())->flatten()->all()) + )); + continue; + } //end try + }; //end while + } - $admin = new User([ - 'lastname' => $lastname, - 'firstname' => $firstname, - 'email' => $email, - 'password' => $password - ]); - $admin->saveOrFail(); - - $this->info('User created.'); - return 0; + /** + * Add a password to the user. + * + * @return void + */ + private function addPassword(): void + { + while ( + ($this->password = $this->secret( + (!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password' + )) !== $this->secret('Confirm the password') + ) { + continue; + }; //end while } } diff --git a/app/Console/Commands/DeleteUnconfirmedCustomers.php b/app/Console/Commands/DeleteUnconfirmedCustomers.php index fa3ab31..ad1a616 100644 --- a/app/Console/Commands/DeleteUnconfirmedCustomers.php +++ b/app/Console/Commands/DeleteUnconfirmedCustomers.php @@ -35,10 +35,11 @@ class DeleteUnconfirmedCustomers extends Command * * @return integer */ - public function handle() + public function handle(): int { /** @var \Illuminate\Database\Eloquent\Collection $customersLogins */ - $customersLogins = CustomerLogin::whereStatus(false) + $customersLogins = CustomerLogin::query() + ->where('status', false) ->where('created_at', '<', Carbon::now()->subDay(1)) ->with(['brand', 'customer']) ->get(); diff --git a/app/Console/Commands/GenerateCrmClient.php b/app/Console/Commands/GenerateClient.php similarity index 68% rename from app/Console/Commands/GenerateCrmClient.php rename to app/Console/Commands/GenerateClient.php index 7ba2838..70e21a1 100644 --- a/app/Console/Commands/GenerateCrmClient.php +++ b/app/Console/Commands/GenerateClient.php @@ -7,18 +7,20 @@ use App\Enums\ApiStatus; use App\Enums\ArcadiaOrderType; use App\Enums\Country; use App\Enums\QuestionType; -use App\Exceptions\Api\V2\ApiErrorException; -use App\Exceptions\Api\V2\CrmException; +use App\Exceptions\Api\V4\ApiErrorException; +use App\Exceptions\Api\V4\CrmException; +use App\Traits\Enums\BaseEnum; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Route; +use Illuminate\Support\Str; /** * @codeCoverageIgnore */ -class GenerateCrmClient extends Command +class GenerateClient extends Command { - private const TARGET = 'clients/V2/src'; + private const TARGET = 'client/src'; private const CLIENTNAMESPACE = 'LDC\\LDCCRM'; /** @@ -33,15 +35,16 @@ class GenerateCrmClient extends Command * * @var string */ - protected $description = 'Generate the crm v2 client in clients/V2 folder'; + protected $description = 'Generate the crm V4 client in client folder'; /** * Execute the console command. * * @return integer */ - public function handle() + public function handle(): int { + $this->copyTraits(); $this->copyEnums(); $this->copyExceptions(); $this->copyRoutesList(); @@ -54,7 +57,7 @@ class GenerateCrmClient extends Command * * @return void */ - private function copyApiConfigInfo() + private function copyApiConfigInfo(): void { $this->info('Copy Api Config'); $rootTarget = app_path('../' . self::TARGET); @@ -72,13 +75,13 @@ class GenerateCrmClient extends Command * * @return void */ - private function copyRoutesList() + private function copyRoutesList(): void { $this->info('Copy Routes'); $routes = []; /** @var \Illuminate\Routing\Route */ foreach (Route::getRoutes() as $route) { - if (\strpos($route->getPrefix(), 'v2') !== false) { + if (\strpos($route->getPrefix(), 'v4') !== false) { $routes[$route->getName()] = [ 'uri' => $route->uri(), 'methods' => $route->methods() @@ -104,15 +107,36 @@ class GenerateCrmClient extends Command { $this->info('Copy Exceptions'); $rootTarget = app_path('../' . self::TARGET); - $this->copyToFolder([ + $classes = [ CrmException::class, ApiErrorException::class - ], 'Exceptions'); - $this->replaceInPhpFile( - "$rootTarget/Exceptions/ApiErrorException.php", - 'App\Enums\ApiError', - 'LDC\LDCCRM\Enums\ApiError' - ); + ]; + $this->copyToFolder($classes, 'Exceptions'); + foreach ($classes as $class) { + $classBasename = class_basename($class); + $dest = "$rootTarget/Exceptions/$classBasename.php"; + $this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\'); + } + } + + /** + * Copy base enums classes + * + * @return void + */ + private function copyTraits(): void + { + $this->info('Copy Traits'); + $rootTarget = app_path('../' . self::TARGET); + $classes = [ + BaseEnum::class + ]; + $this->copyToFolder($classes, 'Traits'); + foreach ($classes as $class) { + $classBasename = class_basename($class); + $dest = "$rootTarget/Traits/$classBasename.php"; + $this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\'); + } } /** @@ -123,7 +147,8 @@ class GenerateCrmClient extends Command private function copyEnums(): void { $this->info('Copy Enums'); - $classes = [ + $rootTarget = app_path('../' . self::TARGET); + $classes = [ ApiError::class, ApiStatus::class, ArcadiaOrderType::class, @@ -131,11 +156,10 @@ class GenerateCrmClient extends Command 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'); + $this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\'); } } @@ -148,9 +172,10 @@ class GenerateCrmClient extends Command */ private function copyToFolder(array $classes, string $folder): void { - $rootTarget = app_path('../' . self::TARGET); + $rootTarget = app_path('../' . self::TARGET); + $folderNamespace = \str_replace('/', '\\', $folder); foreach ($classes as $class) { - $classFileName = \str_replace("App\\$folder\\", '', sprintf('%s.php', $class)); + $classFileName = \str_replace("App\\$folderNamespace\\", '', sprintf('%s.php', $class)); $classFileName = \str_replace('\\', '/', $classFileName); $classBasename = class_basename($class); $source = \app_path("$folder/$classFileName"); @@ -175,11 +200,15 @@ class GenerateCrmClient extends Command { $fContent = File::get($source); - // Remove trailing \Api\V2 . - if ($pos = strpos($fContent, '\Api\V2')) { - $fContent = substr_replace($fContent, '', $pos, strlen('\Api\V2')); + // Remove trailing \Api\V4 . + if ($pos = strpos($fContent, '\Api\V4')) { + $fContent = substr_replace($fContent, '', $pos, strlen('\Api\V4')); } + // Remove trailing \Api . + $fContent = Str::of($fContent)->replaceMatches("/\\\(Api)([\s;\\\])/", '$2'); + $fContent = Str::of($fContent)->replaceMatches("/\\\Traits\\\Enums/", '\Traits'); + // Replace namespace top level. $pos = strpos($fContent, $search); if ($pos !== false) { diff --git a/app/Console/Commands/LdcWebStatusChecker.php b/app/Console/Commands/LdcWebStatusChecker.php index 1ffec37..c68f2d4 100644 --- a/app/Console/Commands/LdcWebStatusChecker.php +++ b/app/Console/Commands/LdcWebStatusChecker.php @@ -12,6 +12,7 @@ use Illuminate\Console\Command; * Author URI: vincent.bridier@ldc.fr * * @codeCoverageIgnore + * */ class LdcWebStatusChecker extends Command { @@ -34,7 +35,7 @@ class LdcWebStatusChecker extends Command * * @return mixed */ - public function handle() + public function handle(): mixed { $data = []; $data['uuid'] = config('ldcweb.project_uuid'); diff --git a/app/Console/Commands/MigrateCrmUsers.php b/app/Console/Commands/MigrateCrmUsers.php index eb7f5f5..246e5d3 100644 --- a/app/Console/Commands/MigrateCrmUsers.php +++ b/app/Console/Commands/MigrateCrmUsers.php @@ -4,7 +4,6 @@ namespace App\Console\Commands; use App\Lib\MigrateOldData; use App\Models\Brand; -use Exception; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -33,20 +32,20 @@ class MigrateCrmUsers extends Command * * @return integer */ - public function handle() + public function handle(): int { try { $table = $this->ask('What is the tmp table name with users data ?'); try { DB::connection(config('database.oldDB'))->table($table)->exists(); - } catch (Exception $e) { + } catch (\Exception $e) { $this->error(sprintf('%s table not found on old crm database', $table)); return -1; } $brandname = $this->ask('What is the brand name ?'); - $brand = Brand::where('name', 'LIKE', "%$brandname%")->first(); + $brand = Brand::query()->where('name', 'LIKE', "%$brandname%")->first(); if (!$brand) { if (!$this->confirm(sprintf('Would you like to use %s ? ', $brandname))) { $this->info('Command cancelled'); @@ -64,10 +63,10 @@ class MigrateCrmUsers extends Command $mgOldData = new MigrateOldData(); $mgOldData->runMigration($brand, $table); - } catch (Exception $e) { + } catch (\Exception $e) { $this->error(sprintf('Failed %s', $e)); return -1; - }//end try + } //end try return 0; } } diff --git a/app/Console/Commands/StartStatJobs.php b/app/Console/Commands/StartStatJobs.php index 08efb4c..d565b3c 100644 --- a/app/Console/Commands/StartStatJobs.php +++ b/app/Console/Commands/StartStatJobs.php @@ -29,7 +29,7 @@ class StartStatJobs extends Command * * @return integer */ - public function handle() + public function handle(): int { \dispatch(new JobsStartStatJobs())->afterResponse(); return 0; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a297e81..9f6cd18 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -7,9 +7,6 @@ use App\Jobs\StartStatJobs; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; -/** - * @codeCoverageIgnore - */ class Kernel extends ConsoleKernel { /** @@ -25,10 +22,12 @@ class Kernel extends ConsoleKernel * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ - protected function schedule(Schedule $schedule) + protected function schedule(Schedule $schedule): void { + // @codeCoverageIgnoreStart $schedule->job(StartStatJobs::class)->dailyAt('03:30'); $schedule->command(DeleteUnconfirmedCustomers::class)->dailyAt('05:30'); + // @codeCoverageIgnoreEnd } /** @@ -36,7 +35,7 @@ class Kernel extends ConsoleKernel * * @return void */ - protected function commands() + protected function commands(): void { $this->load(__DIR__ . '/Commands'); diff --git a/app/Enums/Country.php b/app/Enums/Country.php index 88186d2..5cad35d 100644 --- a/app/Enums/Country.php +++ b/app/Enums/Country.php @@ -516,7 +516,7 @@ enum Country: int /** * Get valid countries label. * - * @return array + * @return array */ public static function getValidCountriesLabel(): array { @@ -528,12 +528,12 @@ enum Country: int /** * Get valid countries value. * - * @return array + * @return array */ public static function getValidCountriesValues(): array { return collect(self::getValidCountries())->map(function (self $country) { - return $country->value; + return $country->value(); })->all(); } @@ -580,20 +580,32 @@ enum Country: int protected static function isoCodes(): array { return [ - self::france->name => 'FR', - self::belgium->name => 'BE', - self::luxembourg->name => 'LU', - self::guadeloupe->name => 'GP', - self::martinique->name => 'MQ', - self::frenchGuiana->name => 'GF', - self::reunion->name => 'RE', - self::saintPierreAndMiquelon->name => 'PM', - self::mayotte->name => 'YT', - self::saintBarthelemy->name => 'BL', - self::saintMartinFrenchPart->name => 'MF', - self::wallisAndFutuna->name => 'WF', - self::frenchPolynesia->name => 'PF', - self::newCaledonia->name => 'NC', + 'france' => 'FR', + 'belgium' => 'BE', + 'luxembourg' => 'LU', + 'guadeloupe' => 'GP', + 'martinique' => 'MQ', + 'frenchGuiana' => 'GF', + 'reunion' => 'RE', + 'saintPierreAndMiquelon' => 'PM', + 'mayotte' => 'YT', + 'saintBarthelemy' => 'BL', + 'saintMartinFrenchPart' => 'MF', + 'wallisAndFutuna' => 'WF', + 'frenchPolynesia' => 'PF', + 'newCaledonia' => 'NC', ]; } + + /** + * @param string $attribute + * @return integer|string|null + */ + public function get(string $attribute): int|string|null + { + if ($attribute === 'iso') { + return $this->isoCodes()[$this->name] ?? null; + } + return $this->{$attribute}; + } } diff --git a/app/Exceptions/AbortRowImportException.php b/app/Exceptions/AbortRowImportException.php index bda67ee..fde07b6 100644 --- a/app/Exceptions/AbortRowImportException.php +++ b/app/Exceptions/AbortRowImportException.php @@ -2,13 +2,12 @@ namespace App\Exceptions; -use Exception; - /** * This class Exception is used to skip a row import from * database migration (2016 -> 2020) + * * @author Jérémy Munsch */ -class AbortRowImportException extends Exception +class AbortRowImportException extends \RuntimeException { } diff --git a/app/Exceptions/Api/V2/ApiErrorException.php b/app/Exceptions/Api/V2/ApiErrorException.php index b09f5a0..c502248 100644 --- a/app/Exceptions/Api/V2/ApiErrorException.php +++ b/app/Exceptions/Api/V2/ApiErrorException.php @@ -10,11 +10,11 @@ use Throwable; /** * This would be thrown if any of the case * referenced in \App\Enums\ApiError would - * occur within the crm + * occur within the crm. * * On the client an ApiErrorException would * also be throw with the ApiError corresponding to - * the situation + * the situation. * * @package LDCCrmClient * @subpackage exceptions @@ -34,7 +34,7 @@ class ApiErrorException extends Exception /** * This is an explicit error message, * ou may print this to the front or not depending - * on $error value + * on $error value. * * @var string */ @@ -52,10 +52,10 @@ class ApiErrorException extends Exception /** * Create a new exception base on ApiError enum. * - * @param ApiError $error - * @param integer $httpCode - * @param string $message - * @param Throwable|null $previous + * @param \App\Enums\ApiError $error + * @param integer $httpCode + * @param string $message + * @param Throwable|null $previous */ public function __construct(ApiError $error, int $httpCode = 400, string $message = '', Throwable $previous = null) { @@ -64,15 +64,15 @@ class ApiErrorException extends Exception $this->httpCode = $httpCode; parent::__construct( strlen($this->message) ? $this->message : $error->label(), - $error->value, + $error->value(), $previous ); } /** - * Get the Api Error + * Get the Api Error. * - * @return ApiError + * @return \App\Enums\ApiError */ public function getApiError(): ApiError { @@ -80,7 +80,7 @@ class ApiErrorException extends Exception } /** - * Get the http code + * Get the http code. * * @return integer */ @@ -95,7 +95,7 @@ class ApiErrorException extends Exception * * @return void */ - public function report() + public function report(): void { // @codeCoverageIgnoreStart Log::debug(sprintf('CRM API: %s', $this->getMessage())); diff --git a/app/Exceptions/Api/V2/CrmException.php b/app/Exceptions/Api/V2/CrmException.php index 3a386b0..dce05a7 100644 --- a/app/Exceptions/Api/V2/CrmException.php +++ b/app/Exceptions/Api/V2/CrmException.php @@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Log; * * @package LDCCrmClient * @subpackage exceptions - * @codeCoverageIgnore */ class CrmException extends Exception { @@ -22,7 +21,7 @@ class CrmException extends Exception * * @return void */ - public function report() + public function report(): void { Log::critical(sprintf('CRM API: %s', $this->getMessage())); } diff --git a/app/Exceptions/Api/V4/ApiErrorException.php b/app/Exceptions/Api/V4/ApiErrorException.php new file mode 100644 index 0000000..d72c4af --- /dev/null +++ b/app/Exceptions/Api/V4/ApiErrorException.php @@ -0,0 +1,102 @@ +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 \App\Enums\ApiError + */ + public function getApiError(): ApiError + { + return $this->error; + } + + /** + * Get the http code. + * + * @return integer + */ + public function getStatusCode(): int + { + return $this->httpCode; + } + + /** + * This is used by laravel only. + * It uses Log class to print the error. + * + * @return void + */ + public function report(): void + { + // @codeCoverageIgnoreStart + Log::debug(sprintf('CRM API: %s', $this->getMessage())); + // @codeCoverageIgnoreEnd + } +} diff --git a/app/Exceptions/Api/V4/CrmException.php b/app/Exceptions/Api/V4/CrmException.php new file mode 100644 index 0000000..3fc732c --- /dev/null +++ b/app/Exceptions/Api/V4/CrmException.php @@ -0,0 +1,28 @@ +getMessage())); + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a079a11..1d74c47 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,8 +4,10 @@ namespace App\Exceptions; use App\Enums\ApiError; use App\Enums\ApiStatus; -use App\Exceptions\Api\V2\ApiErrorException; +use App\Exceptions\Api\V2\ApiErrorException as V2ApiErrorException; +use App\Exceptions\Api\V4\ApiErrorException as V4ApiErrorException; use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse; +use App\Http\Resources\Api\V4\ApiResponse as V4ApiResponse; use App\Lib\Helper; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Auth\AuthenticationException; @@ -15,6 +17,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; class Handler extends ExceptionHandler @@ -44,7 +47,7 @@ class Handler extends ExceptionHandler * * @return void */ - public function register() + public function register(): void { $this->reportable(function (\Throwable $e) { if (app()->bound('sentry')) { @@ -60,10 +63,10 @@ class Handler extends ExceptionHandler * @param \Throwable $e * @return \Symfony\Component\HttpFoundation\Response */ - public function render(mixed $request, \Throwable $e) + public function render(mixed $request, \Throwable $e): Response { - if (Helper::isRequestForApiV2($request) and $request->isJson()) { - return $this->handleApiV2Exception($request, $e); + if ((Helper::isRequestForApiV2($request) || Helper::isRequestForApiV4($request)) and $request->isJson()) { + return $this->handleApiException($request, $e); } //end if // @codeCoverageIgnoreStart return parent::render($request, $e); @@ -71,13 +74,13 @@ class Handler extends ExceptionHandler } /** - * Generate a json response for any apiV2 error case + * Generate a json response for any apiV4 error case. * * @param \Illuminate\Http\Request $request * @param \Throwable $e * @return \Illuminate\Http\JsonResponse */ - private function handleApiV2Exception(Request $request, \Throwable $e): JsonResponse + private function handleApiException(Request $request, \Throwable $e): JsonResponse { switch (get_class($e)) { case AuthenticationException::class: @@ -85,7 +88,7 @@ class Handler extends ExceptionHandler return $this->getReponse( $request, 401, - ApiError::apiAuthFailed->value, + ApiError::apiAuthFailed->value(), sprintf('%s, %s', ApiError::apiAuthFailed->label(), $e->getMessage()) ); case MethodNotAllowedHttpException::class: @@ -93,7 +96,7 @@ class Handler extends ExceptionHandler return $this->getReponse( $request, $e->getStatusCode(), - ApiError::methodNotAllowed->value, + ApiError::methodNotAllowed->value(), sprintf('%s, %s', ApiError::methodNotAllowed->label(), $e->getMessage()) ); case ValidationException::class: @@ -101,7 +104,7 @@ class Handler extends ExceptionHandler return $this->getReponse( $request, $e->status, - ApiError::validationFailed->value, + ApiError::validationFailed->value(), sprintf(ApiError::validationFailed->label(), $e->getMessage()), ['errors' => $e->errors()] ); @@ -110,14 +113,22 @@ class Handler extends ExceptionHandler return $this->getReponse( $request, 404, - ApiError::modelNotFound->value, + ApiError::modelNotFound->value(), sprintf( ApiError::modelNotFound->label(), \array_slice(explode('\\', $e->getModel()), -1)[0] ) ); - case ApiErrorException::class: - /** @var ApiErrorException $e */ + case V4ApiErrorException::class: + /** @var \App\Exceptions\Api\V4\ApiErrorException $e */ + return $this->getReponse( + $request, + $e->getStatusCode(), + $e->getCode(), + $e->getMessage() + ); + case V2ApiErrorException::class: + /** @var \App\Exceptions\Api\V2\ApiErrorException $e */ return $this->getReponse( $request, $e->getStatusCode(), @@ -129,7 +140,7 @@ class Handler extends ExceptionHandler return $this->getReponse( $request, 429, - ApiError::throttleRequestsException->value, + ApiError::throttleRequestsException->value(), ApiError::throttleRequestsException->label() ); case AuthorizationException::class: @@ -137,7 +148,7 @@ class Handler extends ExceptionHandler return $this->getReponse( $request, 403, - ApiError::authorizationException->value, + ApiError::authorizationException->value(), ApiError::authorizationException->label() ); } //end switch @@ -151,7 +162,7 @@ class Handler extends ExceptionHandler } /** - * Generate an ApiV2 json response + * Generate an ApiV4 json response. * * @param \Illuminate\Http\Request $request * @param integer $httpCode @@ -167,7 +178,13 @@ class Handler extends ExceptionHandler string $message, array $data = [] ): JsonResponse { - return (new V2ApiResponse( + if (Helper::isRequestForApiV2($request)) { + return (new V2ApiResponse( + (object)array_merge($data, ['message' => $message, 'code' => $apiCode]), + ApiStatus::KO + ))->toResponse($request)->setStatusCode($httpCode); + } + return (new V4ApiResponse( (object)array_merge($data, ['message' => $message, 'code' => $apiCode]), ApiStatus::KO ))->toResponse($request)->setStatusCode($httpCode); diff --git a/app/Http/Controllers/AnswerController.php b/app/Http/Controllers/AnswerController.php index eafa1c4..ccc52bb 100644 --- a/app/Http/Controllers/AnswerController.php +++ b/app/Http/Controllers/AnswerController.php @@ -11,18 +11,15 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; -/** - * @codeCoverageIgnore - */ class AnswerController extends Controller { /** * Display a listing of the resource. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): \Illuminate\Contracts\View\View { /** @var \App\Models\Question $questionModel */ $questionModel = $request->question; @@ -52,9 +49,9 @@ class AnswerController extends Controller * * @param \Illuminate\Http\Request $request * @param \App\Models\Answer $answer - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function create(Request $request, Answer $answer) + public function create(Request $request, Answer $answer): \Illuminate\Contracts\View\View { /** @var \App\Models\Question $questionModel */ $questionModel = $request->question; @@ -69,9 +66,9 @@ class AnswerController extends Controller * Store a newly created resource in storage. * * @param \App\Http\Requests\Answers\StoreAnswerRequest $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function store(StoreAnswerRequest $request) + public function store(StoreAnswerRequest $request): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request) { $answer = new Answer(); @@ -98,9 +95,9 @@ class AnswerController extends Controller * * @param \App\Models\Question $question * @param \App\Models\Answer $answers - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function edit(Question $question, Answer $answers) + public function edit(Question $question, Answer $answers): \Illuminate\Contracts\View\View { return view('answers.edit', [ 'answerModel' => $answers, @@ -114,10 +111,13 @@ class AnswerController extends Controller * @param \App\Http\Requests\Answers\UpdateAnswerRequest $request * @param \App\Models\Question $question * @param \App\Models\Answer $answer - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function update(UpdateAnswerRequest $request, Question $question, Answer $answer) - { + public function update( + UpdateAnswerRequest $request, + Question $question, + Answer $answer + ): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request, $question, $answer) { $answer->fill($request->validated()); if ($answer->saveOrFail()) { diff --git a/app/Http/Controllers/Api/V2/AnswerController.php b/app/Http/Controllers/Api/V2/AnswerController.php index c14e23c..76da369 100644 --- a/app/Http/Controllers/Api/V2/AnswerController.php +++ b/app/Http/Controllers/Api/V2/AnswerController.php @@ -12,7 +12,7 @@ use Illuminate\Http\JsonResponse; class AnswerController extends Controller { /** - * Store the customer answers + * Store the customer answers. * * @param \App\Http\Requests\Api\V2\StoreCustomerAnswers $request * @param \App\Models\Service $service @@ -28,7 +28,7 @@ class AnswerController extends Controller $this->authorize('customerBelongsToService', $customer); $existingAnswerIds = $customer->getAnswers($service)->pluck('id'); - $answersID = Answer::whereIn('id', $request->answers)->pluck('id'); + $answersID = Answer::query()->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 index be92525..9c92630 100644 --- a/app/Http/Controllers/Api/V2/ApiController.php +++ b/app/Http/Controllers/Api/V2/ApiController.php @@ -8,7 +8,7 @@ use Illuminate\Http\JsonResponse; class ApiController extends Controller { /** - * Just returns HTTP 200 response + * Just returns HTTP 200 response. * * @return \Illuminate\Http\JsonResponse */ diff --git a/app/Http/Controllers/Api/V2/ArcadiaController.php b/app/Http/Controllers/Api/V2/ArcadiaController.php index 1bdf52c..8db7ecd 100644 --- a/app/Http/Controllers/Api/V2/ArcadiaController.php +++ b/app/Http/Controllers/Api/V2/ArcadiaController.php @@ -29,7 +29,7 @@ use Illuminate\Support\Str; class ArcadiaController extends Controller { /** - * Send arcadia default mail (reclamations) + * Send arcadia default mail (reclamations). * * @param \App\Http\Requests\Api\V2\SendArchadiaDefaultMailRequest $request * @return \Illuminate\Http\JsonResponse @@ -40,7 +40,7 @@ class ArcadiaController extends Controller } /** - * Send arcadia game mail (games) + * Send arcadia game mail (games). * * @param \App\Http\Requests\Api\V2\SendArchadiaGameMailRequest $request * @return \Illuminate\Http\JsonResponse @@ -52,7 +52,7 @@ class ArcadiaController extends Controller /** * Form xml message and save it in the apropriate storage location - * to be picked up by the arcadia job + * to be picked up by the arcadia job. * * @param \App\Http\Requests\Api\V2\BaseApiRequest $request * @param \App\Enums\ArcadiaMailType $type @@ -67,14 +67,14 @@ class ArcadiaController extends Controller $xml = 'none'; try { $customerInfo = \array_intersect_key($request->all()['deliver'], [ - 'email' => '', + 'email' => '', 'firstname' => '', - 'lastname' => '', - 'address' => '', - 'address2' => '', - 'city' => '', - 'postcode' => '', - 'phone' => '' + 'lastname' => '', + 'address' => '', + 'address2' => '', + 'city' => '', + 'postcode' => '', + 'phone' => '' ]); $orderType = ArcadiaOrderType::make(intval($request->state_order)); $xml = $this->makeXml($request, $type, $orderType, $customerInfo); @@ -109,11 +109,11 @@ class ArcadiaController extends Controller } throw $e; // @codeCoverageIgnoreEnd - }//end try + } //end try } /** - * render Arcadia xml for order + * Render Arcadia xml for order. * * @param \App\Http\Requests\Api\V2\BaseApiRequest $request * @param \App\Enums\ArcadiaMailType $type @@ -130,9 +130,9 @@ class ArcadiaController extends Controller $arcadiaMailType = $type->equals(ArcadiaMailType::default) ? 'mail.arcadiaOrders.default' : 'mail.arcadiaOrders.game'; return view($arcadiaMailType, [ - 'request' => $request, - 'type' => $type, - 'orderType' => $orderType, + 'request' => $request, + 'type' => $type, + 'orderType' => $orderType, 'customerInfo' => $customerInfo ])->render(); } diff --git a/app/Http/Controllers/Api/V2/CustomerController.php b/app/Http/Controllers/Api/V2/CustomerController.php index f9cf035..34975e3 100644 --- a/app/Http/Controllers/Api/V2/CustomerController.php +++ b/app/Http/Controllers/Api/V2/CustomerController.php @@ -37,7 +37,7 @@ class CustomerController extends Controller $data = (object)$request->validated(); /** @var \App\Models\Customer */ - $customer = Customer::where('email', $data->email)->firstOrCreate([ + $customer = Customer::query()->where('email', $data->email)->firstOrCreate([ 'email' => $data->email ]); @@ -84,7 +84,7 @@ class CustomerController extends Controller } /** - * Renders the specific customer infos + * Renders the specific customer infos. * * @param \App\Models\Service $service * @param \App\Models\Customer $customer @@ -99,7 +99,7 @@ class CustomerController extends Controller } /** - * Updates information for a specific customer + * Updates information for a specific customer. * * @param \App\Http\Requests\Api\V2\UpdateCustomerRequest $request * @param \App\Models\Service $service @@ -141,7 +141,7 @@ class CustomerController extends Controller } /** - * Deletes customer information for a specific service + * Deletes customer information for a specific service. * * @param \App\Models\Service $service * @param \App\Models\Customer $customer @@ -193,7 +193,7 @@ class CustomerController extends Controller } /** - * Authenticate the user + * Authenticate the user. * * @param \App\Http\Requests\Api\V2\AuthenticateRequest $request * @param \App\Models\Service $service @@ -207,7 +207,7 @@ class CustomerController extends Controller { try { /** @var \App\Models\Customer */ - $customer = Customer::where('email', $request->email)->firstOrFail(); + $customer = Customer::query()->where('email', $request->email)->firstOrFail(); /** @var \App\Models\CustomerLogin $customerLogin */ $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); @@ -229,7 +229,7 @@ class CustomerController extends Controller } /** - * Activate the account of the user + * Activate the account of the user. * * @param \App\Models\Customer $customer * @param \App\Models\Service $service @@ -238,7 +238,7 @@ class CustomerController extends Controller * if customer account is already active. * @throws \App\Exceptions\Api\V2\CrmException If saving the customer to DB failed. */ - public function activateAccount(Customer $customer, Service $service) + public function activateAccount(Customer $customer, Service $service): JsonResponse { // * Make sure we are accessing a customer that belongs to our service. $this->authorize('customerBelongsToService', $customer); diff --git a/app/Http/Controllers/Api/V2/OptinController.php b/app/Http/Controllers/Api/V2/OptinController.php index cd20711..304ed7e 100644 --- a/app/Http/Controllers/Api/V2/OptinController.php +++ b/app/Http/Controllers/Api/V2/OptinController.php @@ -17,7 +17,7 @@ use App\Models\SingleOptin; class OptinController extends Controller { /** - * Creates a new Single optin or destroys it if exists + * Creates a new Single optin or destroys it if exists. * * @param \App\Http\Requests\Api\V2\SingleOptinRequest $request * @param \App\Models\Service $service @@ -48,14 +48,14 @@ class OptinController extends Controller } /** - * Creates a new Single optin or destroys it if exists using hashcode + * Creates a new Single optin or destroys it if exists using hashcode. * * @param \App\Http\Requests\Api\V2\SingleOptinRemoveWithHashcodeRequest $request * @param \App\Models\Service $service * @return \Illuminate\Http\JsonResponse * @throws \App\Exceptions\Api\V2\CrmException If the single optin could not be removed. */ - public function optoutWithHashcode(SingleOptinRemoveWithHashcodeRequest $request, Service $service) + public function optoutWithHashcode(SingleOptinRemoveWithHashcodeRequest $request, Service $service): JsonResponse { /** @var \App\Models\SingleOptin */ $singleOptin = SingleOptin::query()->where('hashcode', $request->hashcode) @@ -73,7 +73,7 @@ class OptinController extends Controller } /** - * Changes the optin state + * Changes the optin state. * * @param \App\Models\Service $service * @param \App\Http\Requests\Api\V2\BaseApiRequest $request diff --git a/app/Http/Controllers/Api/V2/QuestionController.php b/app/Http/Controllers/Api/V2/QuestionController.php index 5608207..101b546 100644 --- a/app/Http/Controllers/Api/V2/QuestionController.php +++ b/app/Http/Controllers/Api/V2/QuestionController.php @@ -52,10 +52,10 @@ class QuestionController extends Controller // * Make sure we are accessing a customer that belongs to our service. $this->authorize('customerBelongsToService', $customer); - $questions = Question::whereHas('brand', function ($q) use ($service) { + $questions = Question::query()->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->whereIn('id', Answer::query()->whereHas('customers', function ($q) use ($customer) { $q->where('id', $customer->id); })->pluck('id')); })->with('answers')->get(); diff --git a/app/Http/Controllers/Api/V2/StatsController.php b/app/Http/Controllers/Api/V2/StatsController.php index e1fcc5f..eb400a6 100644 --- a/app/Http/Controllers/Api/V2/StatsController.php +++ b/app/Http/Controllers/Api/V2/StatsController.php @@ -18,7 +18,7 @@ use Illuminate\Support\Collection; class StatsController extends Controller { /** - * Get specific brand statistics + * Get specific brand statistics. * * @param \App\Http\Requests\Api\V2\GetBrandStatsRequest $request * @param \App\Models\Service $service @@ -26,14 +26,14 @@ class StatsController extends Controller */ public function brandStats(GetBrandStatsRequest $request, Service $service): ResourceCollection { - $stats = BrandStat::whereDate('created_at', '>=', $request->start) + $stats = BrandStat::query()->whereDate('created_at', '>=', $request->start) ->whereDate('created_at', '<=', $request->end) ->whereBrandId($service->brand->id)->get(); return new BrandStatsResource($stats); } /** - * Get statistics for a specific question + * Get statistics for a specific question. * * @param \App\Http\Requests\Api\V2\GetQuestionStatsRequest $request * @param \App\Models\Question $question @@ -55,7 +55,7 @@ class StatsController extends Controller } /** - * Total réponses par dates + * Total réponses par dates. * * @param \Illuminate\Support\Collection $answers * @param \Illuminate\Support\Carbon $start @@ -65,13 +65,13 @@ class StatsController extends Controller private function answersByDays(Collection $answers, Carbon $start, Carbon $end): Collection { // * Totalize by date all answers. - $answersByDays = CustomerAnswer::whereDate('created_at', '>=', $start) + $answersByDays = CustomerAnswer::query()->whereDate('created_at', '>=', $start) ->whereDate('created_at', '<=', $end) ->whereIn('answer_id', $answers->pluck('id')) ->get()->map(function ($custAnswer) { return [ 'answer_id' => $custAnswer->answer_id, - 'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'), + 'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'), ]; })->groupBy(['created_at', 'answer_id'])->map(function ($day) { return $day->map(function ($custAnswers) { diff --git a/app/Http/Controllers/Api/V4/AnswerController.php b/app/Http/Controllers/Api/V4/AnswerController.php new file mode 100644 index 0000000..e36fe5d --- /dev/null +++ b/app/Http/Controllers/Api/V4/AnswerController.php @@ -0,0 +1,35 @@ +authorize('customerBelongsToService', $customer); + + $existingAnswerIds = $customer->getAnswers($service)->pluck('id'); + $answersID = Answer::query()->whereIn('id', $request->answers)->pluck('id'); + $customer->answers()->sync($answersID->merge($existingAnswerIds)); + return response()->json(); + } +} diff --git a/app/Http/Controllers/Api/V4/ApiController.php b/app/Http/Controllers/Api/V4/ApiController.php new file mode 100644 index 0000000..c81992d --- /dev/null +++ b/app/Http/Controllers/Api/V4/ApiController.php @@ -0,0 +1,19 @@ +json(); + } +} diff --git a/app/Http/Controllers/Api/V4/ArcadiaController.php b/app/Http/Controllers/Api/V4/ArcadiaController.php new file mode 100644 index 0000000..01dafbf --- /dev/null +++ b/app/Http/Controllers/Api/V4/ArcadiaController.php @@ -0,0 +1,139 @@ +handleArcadiaMail($request, ArcadiaMailType::default); + } + + /** + * Send arcadia game mail (games). + * + * @param \App\Http\Requests\Api\V4\SendArchadiaGameMailRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function outgoingMailGame(SendArchadiaGameMailRequest $request): JsonResponse + { + return $this->handleArcadiaMail($request, ArcadiaMailType::game); + } + + /** + * Form xml message and save it in the apropriate storage location + * to be picked up by the arcadia job. + * + * @param \App\Http\Requests\Api\V4\BaseApiRequest $request + * @param \App\Enums\ArcadiaMailType $type + * @return \Illuminate\Http\JsonResponse + * @throws \Exception If an error occurs while generating and saving the xml file. + */ + private function handleArcadiaMail(BaseApiRequest $request, ArcadiaMailType $type): JsonResponse + { + $request = $request->merge([ + 'order_num' => str_pad(\intval($request->order_num), 9, '0', STR_PAD_LEFT) + ]); + $xml = 'none'; + try { + $customerInfo = \array_intersect_key($request->all()['deliver'], [ + 'email' => '', + 'firstname' => '', + 'lastname' => '', + 'address' => '', + 'address2' => '', + 'city' => '', + 'postcode' => '', + 'phone' => '' + ]); + $orderType = ArcadiaOrderType::make(intval($request->state_order)); + $xml = $this->makeXml($request, $type, $orderType, $customerInfo); + + // * Format xml. + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->xmlStandalone = true; + $doc->preserveWhiteSpace = false; + $doc->formatOutput = true; + $doc->loadXML($xml); + $xml = $doc->saveXML(); + + Storage::disk('arcadia')->put(Str::slug(sprintf( + 'v4_order_%s_%s_%s%s', + $request->order_num, + $type->label(), + date('Ymd_His'), + \microtime(true) + )) . '.xml', $xml); + return response()->json(); + // @codeCoverageIgnoreStart + } catch (\Exception $e) { + Log::critical("ARCADIA XML CREATION FAILED FOR $request->order_num : " . $e->getMessage()); + if (!config('app.debug')) { + dispatch(function () use ($xml, $e, $request) { + Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError( + "ARCADIA XML CREATION FAILED FOR $request->order_num", + "Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(), + $xml ?: '' + )); + })->afterResponse(); + } + throw $e; + // @codeCoverageIgnoreEnd + } //end try + } + + /** + * Render Arcadia xml for order. + * + * @param \App\Http\Requests\Api\V4\BaseApiRequest $request + * @param \App\Enums\ArcadiaMailType $type + * @param \App\Enums\ArcadiaOrderType $orderType + * @param array $customerInfo + * @return string + */ + private function makeXml( + BaseApiRequest $request, + ArcadiaMailType $type, + ArcadiaOrderType $orderType, + array $customerInfo + ): string { + $arcadiaMailType = $type->equals(ArcadiaMailType::default) ? + 'mail.arcadiaOrders.default' : 'mail.arcadiaOrders.game'; + return view($arcadiaMailType, [ + 'request' => $request, + 'type' => $type, + 'orderType' => $orderType, + 'customerInfo' => $customerInfo + ])->render(); + } +} diff --git a/app/Http/Controllers/Api/V4/CustomerController.php b/app/Http/Controllers/Api/V4/CustomerController.php new file mode 100644 index 0000000..ba039c5 --- /dev/null +++ b/app/Http/Controllers/Api/V4/CustomerController.php @@ -0,0 +1,261 @@ +validated(); + + /** @var \App\Models\Customer */ + $customer = Customer::query()->where('email', $data->email)->firstOrCreate([ + 'email' => $data->email + ]); + + DB::transaction(function () use (&$customer, $data, $request, $service) { + + $saved = $customer->save(); + $customer->setOptIn($service->brand, $data->opt_in); + + // * Delete Single optin if exists. + /** @var \App\Models\SingleOptin|null $singleOptin */ + $singleOptin = SingleOptin::query()->where('email', $data->email) + ->whereHas('brand', function (Builder $q) use ($service) { + $q->where('id', $service->brand->id); + })->first(); + if ($singleOptin) { + $saved = $singleOptin->delete() && $saved; + } + + /** @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); + $saved = $customerInfo->save() && $saved; + + /** @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); + $saved = $customerLogin->save() && $saved; + + if (!$saved) { + // @codeCoverageIgnoreStart + throw new CrmException('Api failed to store customer.'); + // @codeCoverageIgnoreEnd + } + }); + + return new CustomerResource($service->brand, $customer); + } + + /** + * Renders the specific customer infos. + * + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\Resources\Json\JsonResource + */ + public function show(Service $service, Customer $customer): JsonResource + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('customerBelongsToService', $customer); + + return new CustomerResource($service->brand, $customer); + } + + /** + * Updates information for a specific customer. + * + * @param \App\Http\Requests\Api\V4\UpdateCustomerRequest $request + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\Resources\Json\JsonResource + * @throws \App\Exceptions\Api\V4\CrmException If saving the customer to DB failed. + */ + public function update(UpdateCustomerRequest $request, Service $service, Customer $customer): JsonResource + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('customerBelongsToService', $customer); + + DB::transaction(function () use ($request, $customer, $service) { + // To correct with create. + $customer->fill($request->validated()); + $saved = $customer->save(); + 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()); + $saved = $customerInfo->save() && $saved; + + /** @var \App\Models\CustomerLogin */ + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + $customerLogin->fill($request->validated()); + $saved = $customerLogin->save() && $saved; + + if (!$saved) { + // @codeCoverageIgnoreStart + throw new CrmException('Api failed to update customer.'); + // @codeCoverageIgnoreEnd + } + }); + + return new CustomerResource($service->brand, $customer); + } + + /** + * Deletes customer information for a specific service. + * + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\JsonResponse + * @throws \App\Exceptions\Api\V4\CrmException If removing the customer off the DB failed. + */ + public function destroy(Service $service, Customer $customer): JsonResponse + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('customerBelongsToService', $customer); + + // 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); + + DB::transaction(function () use ($service, $customer, $customerLogin, $customerInfo) { + if ( + $customerInfo->delete() === false || + $customerLogin->delete() === false + ) { + // @codeCoverageIgnoreStart + throw new CrmException('Api failed to delete customer.'); + // @codeCoverageIgnoreEnd + }; + + $customer->optInHistory()->whereHas('brand', function ($q) use ($service) { + $q->where('id', $service->brand_id); + })->delete(); + + // * Force loading real relation counts. + $customer->unsetRelation('logins'); + $customer->unsetRelation('infos'); + $logins = $customer->logins()->count(); + $infos = $customer->infos()->count(); + + // If customer is not associated with any brand then delete. + if (!$logins and !$infos) { + if ($customer->delete() === false) { + // @codeCoverageIgnoreStart + throw new CrmException('Api failed to update customer.'); + // @codeCoverageIgnoreEnd + } + } + }); + + return response()->json(); + } + + /** + * Authenticate the user. + * + * @param \App\Http\Requests\Api\V4\AuthenticateRequest $request + * @param \App\Models\Service $service + * @return \Illuminate\Http\Resources\Json\JsonResource + * @throws \App\Exceptions\Api\V4\ApiErrorException With ApiError::authFailed in case + * of wrong credentials. + * @throws \App\Exceptions\Api\V4\ApiErrorException With ApiError::customerNotActivated if + * customer account is not activated. + */ + public function authenticate(AuthenticateRequest $request, Service $service): JsonResource + { + try { + /** @var \App\Models\Customer */ + $customer = Customer::query()->where('email', $request->email)->firstOrFail(); + + /** @var \App\Models\CustomerLogin $customerLogin */ + $customerLogin = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + if (!$customerLogin->passwordMatch($request->password)) { + throw new ApiErrorException(ApiError::authFailed); + } + + if (!$customerLogin->status) { + throw new ApiErrorException(ApiError::customerNotActivated); + } + + UpdateCustomerLastConnection::dispatch($customer, $customerLogin, now()) + ->onConnection('database')->afterResponse(); + + return new CustomerResource($service->brand, $customer); + } catch (ModelNotFoundException $e) { + throw new ApiErrorException(ApiError::authFailed); + } //end try + } + + /** + * Activate the account of the user. + * + * @param \App\Models\Customer $customer + * @param \App\Models\Service $service + * @return \Illuminate\Http\JsonResponse + * @throws \App\Exceptions\Api\V4\ApiErrorException With ApiError::customerAlreadyActive + * if customer account is already active. + * @throws \App\Exceptions\Api\V4\CrmException If saving the customer to DB failed. + */ + public function activateAccount(Customer $customer, Service $service): JsonResponse + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('customerBelongsToService', $customer); + + /** @var \App\Models\CustomerLogin $login */ + $login = $customer->login($service->brand, Customer::FIRST_OR_FAIL); + + if ($login->status) { + throw new ApiErrorException(ApiError::customerAlreadyActive); + } + + $login->status = true; + if (!$login->save()) { + // @codeCoverageIgnoreStart + throw new CrmException('Failed to save login'); + // @codeCoverageIgnoreEnd + } + return response()->json(); + } +} diff --git a/app/Http/Controllers/Api/V4/OptinController.php b/app/Http/Controllers/Api/V4/OptinController.php new file mode 100644 index 0000000..ff8a76a --- /dev/null +++ b/app/Http/Controllers/Api/V4/OptinController.php @@ -0,0 +1,93 @@ +brand, $request->email)->exists()) { + throw new ApiErrorException(ApiError::singleOptinCustomerExists); + } + + /** @var \App\Models\SingleOptin */ + $singleOptin = SingleOptin::query()->where('email', $request->email) + ->whereHas('brand', function ($q) use ($service) { + $q->where('id', $service->brand->id); + })->firstOrNew(); + + if (!$this->changeOptin($service, $request, \boolval($request->opt_in), $singleOptin)) { + // @codeCoverageIgnoreStart + throw new CrmException('Api failed to update single optin.'); + // @codeCoverageIgnoreEnd + } + + return response()->json(); + } + + /** + * Creates a new Single optin or destroys it if exists using hashcode. + * + * @param \App\Http\Requests\Api\V4\SingleOptinRemoveWithHashcodeRequest $request + * @param \App\Models\Service $service + * @return \Illuminate\Http\JsonResponse + * @throws \App\Exceptions\Api\V4\CrmException If the single optin could not be removed. + */ + public function optoutWithHashcode(SingleOptinRemoveWithHashcodeRequest $request, Service $service): JsonResponse + { + /** @var \App\Models\SingleOptin */ + $singleOptin = SingleOptin::query()->where('hashcode', $request->hashcode) + ->whereHas('brand', function ($q) use ($service) { + $q->whereId($service->brand->id); + })->firstOrFail(); + + if ($singleOptin and !$this->changeOptin($service, $request, \boolval($request->opt_in), $singleOptin)) { + // @codeCoverageIgnoreStart + throw new CrmException('Api failed to update single optin.'); + // @codeCoverageIgnoreEnd + } + + return response()->json(); + } + + /** + * Changes the optin state. + * + * @param \App\Models\Service $service + * @param \App\Http\Requests\Api\V4\BaseApiRequest $request + * @param boolean $optin + * @param \App\Models\SingleOptin $singleOptin + * @return boolean Returns false in case of failure. + */ + private function changeOptin(Service $service, BaseApiRequest $request, bool $optin, SingleOptin $singleOptin): bool + { + if ($optin) { + $singleOptin->fill($request->validated()); + $singleOptin->brand()->associate($service->brand); + return $singleOptin->save(); + } + return $singleOptin->delete() !== false; + } +} diff --git a/app/Http/Controllers/Api/V4/QuestionController.php b/app/Http/Controllers/Api/V4/QuestionController.php new file mode 100644 index 0000000..f9345d0 --- /dev/null +++ b/app/Http/Controllers/Api/V4/QuestionController.php @@ -0,0 +1,64 @@ +brand->questions); + } + + /** + * Returns all service questions with user answers for all of them. + * + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\Resources\Json\ResourceCollection + */ + public function indexWithResponse(Service $service, Customer $customer): ResourceCollection + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('customerBelongsToService', $customer); + + $customerAnswers = $customer->getAnswers($service); + return new QuestionsResource($service->brand->questions, $customerAnswers); + } + + /** + * Returns all service questions that was not answered by a user. + * + * @param \App\Models\Service $service + * @param \App\Models\Customer $customer + * @return \Illuminate\Http\Resources\Json\JsonResource + */ + public function unAnsweredBy(Service $service, Customer $customer): JsonResource + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('customerBelongsToService', $customer); + + $questions = Question::query()->whereHas('brand', function ($q) use ($service) { + $q->where('id', $service->brand->id); + })->whereDoesntHave('answers', function ($q) use ($customer) { + $q->whereIn('id', Answer::query()->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/Api/V4/StatsController.php b/app/Http/Controllers/Api/V4/StatsController.php new file mode 100644 index 0000000..ae36f79 --- /dev/null +++ b/app/Http/Controllers/Api/V4/StatsController.php @@ -0,0 +1,109 @@ +whereDate('created_at', '>=', $request->start) + ->whereDate('created_at', '<=', $request->end) + ->whereBrandId($service->brand->id)->get(); + return new BrandStatsResource($stats); + } + + /** + * Get statistics for a specific question. + * + * @param \App\Http\Requests\Api\V4\GetQuestionStatsRequest $request + * @param \App\Models\Question $question + * @return \App\Http\Resources\Api\V4\QuestionStatsResource + */ + public function questionStats(GetQuestionStatsRequest $request, Question $question): QuestionStatsResource + { + // * Make sure we are accessing a customer that belongs to our service. + $this->authorize('questionBelongsToService', $question); + + $answers = $question->answers; + $start = Carbon::parse($request->start); + $end = Carbon::parse($request->end); + + return new QuestionStatsResource( + $this->answersByDays($answers, $start, $end), + $question + ); + } + + /** + * Total réponses par dates. + * + * @param \Illuminate\Support\Collection $answers + * @param \Illuminate\Support\Carbon $start + * @param \Illuminate\Support\Carbon $end + * @return \Illuminate\Support\Collection + */ + private function answersByDays(Collection $answers, Carbon $start, Carbon $end): Collection + { + // * Totalize by date all answers. + $answersByDays = CustomerAnswer::query()->whereDate('created_at', '>=', $start) + ->whereDate('created_at', '<=', $end) + ->whereIn('answer_id', $answers->pluck('id')) + ->get()->map(function ($custAnswer) { + return [ + 'answer_id' => $custAnswer->answer_id, + 'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'), + ]; + })->groupBy(['created_at', 'answer_id'])->map(function ($day) { + return $day->map(function ($custAnswers) { + return $custAnswers->count(); + }); + }); + + do { + $currentDate = $start->isoFormat('YYYY-MM-DD'); + /** @var \Illuminate\Support\Collection $answersByDays */ + if (!$answersByDays->has($currentDate)) { + $answersByDays->put( + $currentDate, + $answers->mapWithKeys(function ($answer) { + return [$answer->id => 0]; + }) + ); + } else { + $curentAnswers = $answersByDays->pull($currentDate); + $answersByDays->put( + $currentDate, + $answers->mapWithKeys(function ($answer) use ($curentAnswers) { + return [$answer->id => + $curentAnswers->has($answer->id) ? + $curentAnswers->get($answer->id) : 0]; + }) + ); + } + + $start = $start->addDay(); + } while ($start->lessThanOrEqualTo($end)); + + return $answersByDays; + } +} diff --git a/app/Http/Controllers/BrandController.php b/app/Http/Controllers/BrandController.php index 51cb6ad..a93a707 100644 --- a/app/Http/Controllers/BrandController.php +++ b/app/Http/Controllers/BrandController.php @@ -11,18 +11,15 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; -/** - * @codeCoverageIgnore - */ class BrandController extends Controller { /** * Display a listing of the resource. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): \Illuminate\Contracts\View\View { /** @var \Illuminate\Database\Eloquent\Builder $query */ $query = Brand::query(); @@ -52,9 +49,9 @@ class BrandController extends Controller * Show the form for creating a new resource. * * @param \App\Models\Brand $brands - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function create(Brand $brands) + public function create(Brand $brands): \Illuminate\Contracts\View\View { return view('brands.create', [ 'brandModel' => $brands @@ -65,9 +62,9 @@ class BrandController extends Controller * Store a newly created resource in storage. * * @param \App\Http\Requests\Brands\StoreBrandRequest $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function store(StoreBrandRequest $request) + public function store(StoreBrandRequest $request): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request) { $brand = new Brand(); @@ -93,9 +90,9 @@ class BrandController extends Controller * Show the form for editing the specified resource. * * @param \App\Models\Brand $brands - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function edit(Brand $brands) + public function edit(Brand $brands): \Illuminate\Contracts\View\View { return view('brands.edit', [ 'brandModel' => $brands @@ -107,9 +104,9 @@ class BrandController extends Controller * * @param \App\Http\Requests\Brands\UpdateBrandRequest $request * @param \App\Models\Brand $brand - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function update(UpdateBrandRequest $request, Brand $brand) + public function update(UpdateBrandRequest $request, Brand $brand): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request, $brand) { $brand->fill($request->validated()); @@ -133,12 +130,12 @@ class BrandController extends Controller /** * Store a newly created resource in storage. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ - public function jsonPaginate() + public function jsonPaginate(): \Illuminate\Http\JsonResponse { return DB::transaction(function () { - /** @var \App\Models\Tag */ + /** @var \App\Models\Brand */ $brandModels = Brand::paginate(12); return \response()->json($brandModels ?? 500); }); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index ec840e7..6d16798 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -10,9 +10,6 @@ use Illuminate\Routing\Controller as BaseController; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Session; -/** - * @codeCoverageIgnore - */ class Controller extends BaseController { use AuthorizesRequests; @@ -21,7 +18,7 @@ class Controller extends BaseController /** * Build Search query on specified fields - * splitting search into words or using whole sentence + * splitting search into words or using whole sentence. * * @param \Illuminate\Database\Eloquent\Builder $query The eloquent query builder. * @param string $search The query string. @@ -36,7 +33,7 @@ class Controller extends BaseController string $search, callable $searchQuery = null, string ...$fields - ): Builder { + ): \Illuminate\Database\Eloquent\Builder { if (count($fields)) { // Search using words. $words = explode(' ', $search); @@ -58,15 +55,18 @@ class Controller extends BaseController } /** - * Build query search on all fields + * Build query search on all fields. * * @param \Illuminate\Database\Eloquent\Builder $query * @param string $search * @param array $fields * @return \Illuminate\Database\Eloquent\Builder */ - private function searchOnFields(Builder $query, string $search, array $fields): Builder - { + private function searchOnFields( + Builder $query, + string $search, + array $fields + ): \Illuminate\Database\Eloquent\Builder { $table = $query->getModel()->getTable(); foreach ($fields as $f) { $f = str_replace('?', '\?', $f); @@ -88,13 +88,13 @@ class Controller extends BaseController } /** - * Sort columns with a query + * Sort columns with a query. * * @param \Illuminate\Database\Eloquent\Builder $query * @param array $ignore * @return \Illuminate\Database\Eloquent\Builder */ - protected function sortQuery(Builder $query, array $ignore = []): Builder + protected function sortQuery(Builder $query, array $ignore = []): \Illuminate\Database\Eloquent\Builder { $rName = request()->route()->getName(); $table = $query->getModel()->getTable(); diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php index e155119..f913445 100644 --- a/app/Http/Controllers/CustomerController.php +++ b/app/Http/Controllers/CustomerController.php @@ -20,34 +20,29 @@ use Illuminate\Support\Str; use League\Csv\Writer; use Symfony\Component\HttpFoundation\StreamedResponse; -/** - * @codeCoverageIgnore - */ class CustomerController extends Controller { /** - * View all customers + * View all customers. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): \Illuminate\Contracts\View\View { $modelTable = (new Customer())->getTable(); $modelClass = Customer::class; - $brands = Brand::select('id', 'name')->get(); + $brands = Brand::query()->select('id', 'name')->get(); $columns = ['customer_infos.lastname', 'customer_infos.firstname']; // * Jointure pour pouvoir trier les colonnes. /** @var \Illuminate\Database\Eloquent\Builder */ - $query = CustomerInfo::join('customer_logins', function (JoinClause $join) { + $query = CustomerInfo::query()->join('customer_logins', function (JoinClause $join) { $join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id') - ->on('customer_logins.customer_id', '=', 'customer_infos.customer_id'); - }) - ->join('customers', function (JoinClause $join) { + ->on('customer_logins.customer_id', '=', 'customer_infos.customer_id'); + })->join('customers', function (JoinClause $join) { $join->on('customers.id', '=', 'customer_infos.customer_id'); - }) - ->join('brands', function (JoinClause $join) { + })->join('brands', function (JoinClause $join) { $join->on('brands.id', '=', 'customer_infos.brand_id'); }); @@ -89,15 +84,15 @@ class CustomerController extends Controller Session::has("crud.$modelTable.sort_col") and Session::has("crud.$modelTable.sort_way") and (Schema::hasColumn($modelTable, Session::get("crud.$modelTable.sort_col")) or - in_array(Session::get("crud.$modelTable.sort_col"), [ - 'brands.name', - 'customers.email', - 'customer_infos.firstname', - 'customer_infos.lastname', - 'customer_infos.opt_in', - 'customer_logins.last_connection', - 'customer_logins.status' - ])) + in_array(Session::get("crud.$modelTable.sort_col"), [ + 'brands.name', + 'customers.email', + 'customer_infos.firstname', + 'customer_infos.lastname', + 'customer_infos.opt_in', + 'customer_logins.last_connection', + 'customer_logins.status' + ])) ) { $query = $query->orderBy( Session::get("crud.$modelTable.sort_col"), @@ -120,17 +115,17 @@ class CustomerController extends Controller } /** - * Show a specific custmer + * Show a specific custmer. * * @param \App\Models\Customer $customer * @param \App\Models\Brand $brand - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function show(Customer $customer, Brand $brand) + public function show(Customer $customer, Brand $brand): \Illuminate\Contracts\View\View { - $customerInfos = CustomerInfo::where('customer_id', $customer->id) + $customerInfos = CustomerInfo::query()->where('customer_id', $customer->id) ->where('brand_id', $brand->id)->firstOrFail(); - $customerLogin = CustomerLogin::where('customer_id', $customer->id) + $customerLogin = CustomerLogin::query()->where('customer_id', $customer->id) ->where('brand_id', $brand->id)->first(); return view('customers.show', compact( @@ -140,13 +135,13 @@ class CustomerController extends Controller } /** - * Delete a specific customer info + * Delete a specific customer info. * * @param \App\Models\Customer $customer * @param \App\Models\Brand $brand * @return \Illuminate\Http\RedirectResponse */ - public function delete(Customer $customer, Brand $brand) + public function delete(Customer $customer, Brand $brand): \Illuminate\Http\RedirectResponse { try { // TODO: Gestion avec mesBR. @@ -185,18 +180,18 @@ class CustomerController extends Controller }); } catch (\ErrorException $e) { return redirect()->route('customers.index') - ->with('error', trans('Le compte n\'a pas pu être supprimé.')); + ->with('error', trans('Le compte n\'a pas pu être supprimé.')); } //end try return redirect()->route('customers.index')->with('warning', trans('Compte supprimé.')); } /** - * Show export the customers form + * Show export the customers form. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function export() + public function export(): \Illuminate\Contracts\View\View { $brands = Brand::all(); return view('customers.export', compact( @@ -205,13 +200,13 @@ class CustomerController extends Controller } /** - * Exports customers following the requested export type + * Exports customers following the requested export type. * * @param \App\Http\Requests\CustomerExport $request * @return \Symfony\Component\HttpFoundation\StreamedResponse * @throws \Exception If the request export type in not handled. */ - public function exportSubmit(CustomerExport $request) + public function exportSubmit(CustomerExport $request): \Symfony\Component\HttpFoundation\StreamedResponse { $pagination = 2000; $header = '"lastname";"firstname";"email";"hashcode"'; @@ -233,7 +228,7 @@ class CustomerController extends Controller sprintf( 'attachment; filename="Export pour newsletter %s %s.csv"', Carbon::now()->isoFormat('DD-MM-YYYY_HH-mm'), - Str::slug($exportType) + Str::slug($exportType->label()) ) ); @@ -244,8 +239,8 @@ class CustomerController extends Controller case CustomerExportType::customer: $page = 1; /** @var \Illuminate\Database\Eloquent\Builder $req */ - $req = CustomerInfo::where('brand_id', $request->brand) - ->where('opt_in', true); + $req = CustomerInfo::query()->where('brand_id', $request->brand) + ->where('opt_in', true); $total = $req->count(); $pages = \ceil($total / $pagination); do { @@ -284,7 +279,7 @@ class CustomerController extends Controller }); break; default: - throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label)); + throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label())); } //end switch /* diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index 6409a2a..486e610 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -5,28 +5,25 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -/** - * @codeCoverageIgnore - */ class LoginController extends Controller { /** - * Shows the login view + * Shows the login view. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index() + public function index(): \Illuminate\Contracts\View\View { return view('login'); } /** - * Undocumented function + * Authenticate specific user. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function authenticate(Request $request) + public function authenticate(Request $request): \Illuminate\Http\RedirectResponse { if ( Auth::attempt( @@ -36,17 +33,17 @@ class LoginController extends Controller ) { return redirect()->route('customers.index'); } else { - return back()->withInput($request->except("password")) + return redirect()->back()->withInput($request->except("password")) ->withErrors("Les identifiants entrés sont incorrects, veuillez réessayer."); } } /** - * Undocumented function + * Logout user connected. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function logout() + public function logout(): \Illuminate\Http\RedirectResponse { Auth::logout(); return redirect()->route('login'); diff --git a/app/Http/Controllers/QuestionController.php b/app/Http/Controllers/QuestionController.php index abbcfe5..2670f6a 100644 --- a/app/Http/Controllers/QuestionController.php +++ b/app/Http/Controllers/QuestionController.php @@ -15,24 +15,21 @@ use Illuminate\Support\Str; use League\Csv\Writer; use Symfony\Component\HttpFoundation\StreamedResponse; -/** - * @codeCoverageIgnore - */ class QuestionController extends Controller { /** * Display a listing of the resource. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): \Illuminate\Contracts\View\View { /** @var \Illuminate\Database\Eloquent\Builder $query */ $query = Question::query(); if ($request->brand) { - $query = $query->where('brand_id', '=', request()->brand); + $query = $query->where('brand_id', '=', $request->brand); } if ($request->search) { $this->searchQuery( @@ -56,11 +53,11 @@ class QuestionController extends Controller * Show the form for creating a new resource. * * @param \App\Models\Question $question - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function create(Question $question) + public function create(Question $question): \Illuminate\Contracts\View\View { - $brandModels = Brand::get(); + $brandModels = Brand::all(); return view('questions.create', [ 'questionModel' => $question, @@ -72,9 +69,9 @@ class QuestionController extends Controller * Store a newly created resource in storage. * * @param \App\Http\Requests\Questions\StoreQuestionRequest $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function store(StoreQuestionRequest $request) + public function store(StoreQuestionRequest $request): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request) { $question = new Question(); @@ -100,11 +97,11 @@ class QuestionController extends Controller * Show the form for editing the specified resource. * * @param \App\Models\Question $question - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function edit(Question $question) + public function edit(Question $question): \Illuminate\Contracts\View\View { - $brandModels = Brand::get(); + $brandModels = Brand::all(); return view('questions.edit', [ 'questionModel' => $question, @@ -117,9 +114,9 @@ class QuestionController extends Controller * * @param \App\Http\Requests\Questions\UpdateQuestionRequest $request * @param \App\Models\Question $question - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function update(UpdateQuestionRequest $request, Question $question) + public function update(UpdateQuestionRequest $request, Question $question): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request, $question) { $question->fill($request->validated()); @@ -144,9 +141,9 @@ class QuestionController extends Controller * Show the specified resource. * * @param \App\Models\Question $question - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function show(Question $question) + public function show(Question $question): \Illuminate\Contracts\View\View { return view('questions.show', [ 'questionModel' => $question @@ -178,9 +175,9 @@ class QuestionController extends Controller /** * Shows the question export form * - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function export() + public function export(): \Illuminate\Contracts\View\View { $brands = Brand::all(); return view('questions.export', compact('brands')); @@ -192,10 +189,11 @@ class QuestionController extends Controller * @param \App\Http\Requests\QuestionExport $request * @return \Symfony\Component\HttpFoundation\StreamedResponse */ - public function exportSubmit(QuestionExport $request) + public function exportSubmit(QuestionExport $request): \Symfony\Component\HttpFoundation\StreamedResponse { - $results = []; - $brand = Brand::findOrFail($request->brand); + $results = []; + /** @var \App\Models\Brand $brand */ + $brand = Brand::query()->where('id', $request->brand)->firstOrFail(); $questions = $brand->questions->load('answers'); foreach ($questions as $question) { diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index c615a2f..370e437 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -14,24 +14,21 @@ use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; -/** - * @codeCoverageIgnore - */ class ServiceController extends Controller { /** * Display a listing of the resource. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): \Illuminate\Contracts\View\View { /** @var \Illuminate\Database\Eloquent\Builder $query */ $query = Service::query(); if ($request->brand) { - $query = $query->where('brand_id', '=', request()->brand); + $query = $query->where('brand_id', '=', $request->brand); } if ($request->search) { $this->searchQuery( @@ -54,11 +51,11 @@ class ServiceController extends Controller * Show the form for creating a new resource. * * @param \App\Models\Service $service - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function create(Service $service) + public function create(Service $service): \Illuminate\Contracts\View\View { - $brandModels = Brand::get(); + $brandModels = Brand::all(); return view('services.create', [ 'serviceModel' => $service, @@ -70,9 +67,9 @@ class ServiceController extends Controller * Store a newly created resource in storage. * * @param \App\Http\Requests\Services\StoreServiceRequest $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\RedirectResponse */ - public function store(StoreServiceRequest $request) + public function store(StoreServiceRequest $request): \Illuminate\Http\RedirectResponse { return DB::transaction(function () use ($request) { $service = new Service(); @@ -100,11 +97,11 @@ class ServiceController extends Controller * Show the form for editing the specified resource. * * @param \App\Models\Service $service - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function edit(Service $service) + public function edit(Service $service): \Illuminate\Contracts\View\View { - $brandModels = Brand::get(); + $brandModels = Brand::all(); return view('services.edit', [ 'serviceModel' => $service, @@ -117,9 +114,9 @@ class ServiceController extends Controller * * @param \App\Http\Requests\Services\UpdateServiceRequest $request * @param \App\Models\Service $service - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function update(UpdateServiceRequest $request, Service $service) + public function update(UpdateServiceRequest $request, Service $service): \Illuminate\Contracts\View\View { return DB::transaction(function () use ($request, $service) { $service->fill($request->validated()); @@ -144,9 +141,9 @@ class ServiceController extends Controller * Show the specified resource. * * @param \App\Models\Service $service - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function show(Service $service) + public function show(Service $service): \Illuminate\Contracts\View\View { return view('services.show', [ 'serviceModel' => $service @@ -176,15 +173,17 @@ class ServiceController extends Controller } /** - * Regen service credentials + * Regen service credentials. * * @param \App\Http\Requests\GenerateServiceToken $request * @param \App\Models\Service $service * @return \Illuminate\Http\RedirectResponse */ // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed, Squiz.Commenting.FunctionComment.Missing - public function generateServicePassword(Service $service, GenerateServiceToken $request = null) - { + public function generateServicePassword( + Service $service, + GenerateServiceToken $request = null + ): \Illuminate\Http\RedirectResponse { /** @var \App\Models\User $user */ $user = auth()->user(); diff --git a/app/Http/Controllers/SingleOptinController.php b/app/Http/Controllers/SingleOptinController.php index 4bbca5a..053c018 100644 --- a/app/Http/Controllers/SingleOptinController.php +++ b/app/Http/Controllers/SingleOptinController.php @@ -7,25 +7,22 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; use Illuminate\Support\Str; -/** - * @codeCoverageIgnore - */ class SingleOptinController extends Controller { /** * Display a listing of the resource. * * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function index(Request $request) + public function index(Request $request): \Illuminate\Contracts\View\View { /** @var \Illuminate\Database\Eloquent\Builder $query */ $query = SingleOptin::query(); if ($request->brand) { - $query = $query->whereHas('brand', function (Builder $q) { - $q->where('id', request()->brand); + $query = $query->whereHas('brand', function (Builder $q) use ($request) { + $q->where('id', $request->brand); }); } if ($request->search) { diff --git a/app/Http/Controllers/StatsController.php b/app/Http/Controllers/StatsController.php index 1b449b4..be7b162 100644 --- a/app/Http/Controllers/StatsController.php +++ b/app/Http/Controllers/StatsController.php @@ -13,13 +13,10 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Schema; use League\Csv\Writer; -/** - * @codeCoverageIgnore - */ class StatsController extends Controller { /** - * Shows stats by brand + * Shows stats by brand. * * @param \App\Http\Requests\StatsByBrandRequest $request * @return \Illuminate\Http\Response @@ -28,7 +25,7 @@ class StatsController extends Controller { $stats = null; if ($request->post()) { - $stats = BrandStat::whereDate('created_at', '>=', $request->start) + $stats = BrandStat::query()->whereDate('created_at', '>=', $request->start) ->whereDate('created_at', '<=', $request->end) ->whereBrandId($request->brand)->get(); } @@ -36,15 +33,15 @@ class StatsController extends Controller } /** - * Exports stats by brand as a csv file + * Exports stats by brand as a csv file. * * @param \App\Http\Requests\StatsByBrandRequest $request * @return \Illuminate\Http\Response */ - public function exportByBrandCsv(StatsByBrandRequest $request) + public function exportByBrandCsv(StatsByBrandRequest $request): \Illuminate\Http\Response { - $brand = Brand::findOrFail($request->brand); - $stats = BrandStat::with('brand')->whereDate('created_at', '>=', $request->start) + $brand = Brand::query()->findOrFail($request->brand); + $stats = BrandStat::query()->with('brand')->whereDate('created_at', '>=', $request->start) ->whereDate('created_at', '<=', $request->end) ->whereBrandId($request->brand)->get(); @@ -80,18 +77,18 @@ class StatsController extends Controller } /** - * Views stats for a specific question + * Views stats for a specific question. * * @param \App\Http\Requests\StatsByQuestionRequest $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Contracts\View\View */ - public function viewByQuestion(StatsByQuestionRequest $request) + public function viewByQuestion(StatsByQuestionRequest $request): \Illuminate\Contracts\View\View { $question = null; $answers = null; $answersByDays = null; if ($request->post()) { - $question = Question::findOrFail($request->question); + $question = Question::query()->findOrFail($request->question); $answers = $question->answers; $start = Carbon::parse($request->start); $end = Carbon::parse($request->end); @@ -103,14 +100,14 @@ class StatsController extends Controller } /** - * Exports stats for a specific question as csv + * Exports stats for a specific question as csv. * * @param \App\Http\Requests\StatsByQuestionRequest $request * @return \Illuminate\Http\Response */ - public function exportByQuestionCsv(StatsByQuestionRequest $request) + public function exportByQuestionCsv(StatsByQuestionRequest $request): \Illuminate\Http\Response { - $question = Question::findOrFail($request->question); + $question = Question::query()->findOrFail($request->question); $answers = $question->answers; $start = Carbon::parse($request->start); $end = Carbon::parse($request->end); @@ -144,7 +141,7 @@ class StatsController extends Controller } /** - * Makes a collection of total users answers by day + * Makes a collection of total users answers by day. * * @param \Illuminate\Support\Collection $answers A collection of Answer. * @param \Illuminate\Support\Carbon $start The wanted start period. @@ -152,16 +149,16 @@ class StatsController extends Controller * @return \Illuminate\Support\Collection A collection with date as Y_m_d index * with array of answers and their total. */ - private function answersByDays(Collection $answers, Carbon $start, Carbon $end): Collection + private function answersByDays(Collection $answers, Carbon $start, Carbon $end): \Illuminate\Support\Collection { // * Totalize by date all answers. - $answersByDays = CustomerAnswer::whereDate('created_at', '>=', $start) + $answersByDays = CustomerAnswer::query()->whereDate('created_at', '>=', $start) ->whereDate('created_at', '<=', $end) ->whereIn('answer_id', $answers->pluck('id')) ->get()->map(function ($custAnswer) { return [ 'answer_id' => $custAnswer->answer_id, - 'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'), + 'created_at' => $custAnswer->created_at->isoFormat('YYYY-MM-DD'), ]; })->groupBy(['created_at', 'answer_id'])->map(function ($day) { return $day->map(function ($custAnswers) { diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 8ab25b3..86384bb 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -55,15 +55,14 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ - 'auth' => \App\Http\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'check-brand' => \App\Http\Middleware\CheckBrand::class, - 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, - 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, ]; } diff --git a/app/Http/Middleware/ApiCheckTimeStamps.php b/app/Http/Middleware/ApiCheckTimeStamps.php index d0bdaba..21925db 100644 --- a/app/Http/Middleware/ApiCheckTimeStamps.php +++ b/app/Http/Middleware/ApiCheckTimeStamps.php @@ -3,7 +3,7 @@ namespace App\Http\Middleware; use App\Enums\ApiError; -use App\Exceptions\Api\V2\ApiErrorException; +use App\Exceptions\Api\V4\ApiErrorException; use App\Lib\Helper; use Illuminate\Http\Request; use Illuminate\Support\Carbon; @@ -21,13 +21,13 @@ class ApiCheckTimeStamps * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed - * @throws \App\Exceptions\Api\V2\ApiErrorException With ApiError::wrongTimeStamp + * @throws \App\Exceptions\Api\V4\ApiErrorException With ApiError::wrongTimeStamp * if timestamp has an incorrect * format or is timed out. */ - public function handle(Request $request, \Closure $next) + public function handle(Request $request, \Closure $next): mixed { - if (Helper::isRequestForApiV2($request)) { + if (Helper::isRequestForApiV2($request) || Helper::isRequestForApiV4($request)) { if (!\is_string($request->timestamp) or !Carbon::hasFormat($request->timestamp, 'Y-m-d H:i:s')) { throw new ApiErrorException(ApiError::wrongTimeStamp); } diff --git a/app/Http/Middleware/ApiResponse.php b/app/Http/Middleware/ApiResponse.php index 7d520fb..fbbbd91 100644 --- a/app/Http/Middleware/ApiResponse.php +++ b/app/Http/Middleware/ApiResponse.php @@ -4,6 +4,8 @@ namespace App\Http\Middleware; use App\Enums\ApiStatus; use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse; +use App\Http\Resources\Api\V4\ApiResponse as V4ApiResponse; +use App\Lib\Helper; use App\Models\Service; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -18,7 +20,7 @@ class ApiResponse * @return mixed * @throws \ErrorException If the received response is not a JsonResponse. */ - public function handle(Request $request, \Closure $next) + public function handle(Request $request, \Closure $next): mixed { /** @var \Illuminate\Http\JsonResponse $response */ $response = $next($request); @@ -40,18 +42,24 @@ class ApiResponse /** @var object $data */ $data = $response->getData(); - // If reponse is already a V2ApiResponse then just return it. This happens if error handler comes to action. + /** Check if it's V2 or V4 response. */ if ( + gettype($data) === "object" and isset($data->status) and isset($data->data) and isset($data->version) and - in_array($data->status, [ApiStatus::OK->value, ApiStatus::KO->value]) + in_array($data->status, [ApiStatus::OK->value(), ApiStatus::KO->value()]) ) { + /** Return direct ApiResponse */ return $response; } - /** @var \App\Enums\ApiStatus $status */ $status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK : ApiStatus::KO; - return $response->setData(new V2ApiResponse((object)$response->getData(), $status)); + + if (Helper::isRequestForApiV2($request)) { + return $response->setData(new V2ApiResponse((object)$data, $status)); + } + + return $response->setData(new V4ApiResponse((object)$data, $status)); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index d8d27eb..251a319 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -2,60 +2,30 @@ namespace App\Http\Middleware; -use Closure; use Illuminate\Auth\Middleware\Authenticate as Middleware; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; /** - * Updated for ApiV2 + * Updated for ApiV4. */ class Authenticate extends Middleware { - /** - * Handle an incoming request. - * - * @param mixed $request - * @param \Closure $next - * @param mixed ...$guards - * @return mixed - * - * @throws \Illuminate\Auth\AuthenticationException If authentification fails. - */ - public function handle(mixed $request, Closure $next, mixed ...$guards) - { - // Use Laravel auth check mecanism. - return \call_user_func_array( - [$this, 'parent::' . __FUNCTION__], - array_merge([$request, $next], $guards) - ); - } /** * Get the path the user should be redirected to when they are not authenticated. * - * @param mixed $request + * @param \Illuminate\Http\Request $request * @return string|null - * @codeCoverageIgnore + * */ - protected function redirectTo(mixed $request) + protected function redirectTo(Request $request): ?string { - $logins = [ - 'backend' => route('login') - ]; if (!$request->expectsJson()) { // * Notify must login Session::flash('warning', trans('Veuillez vous connecter pour continuer')); - foreach ($request->route()->gatherMiddleware() as $middleware) { - if (\is_string($middleware) and strpos($middleware, 'auth:') !== false) { - $route = $logins[str_replace('auth:', '', $middleware)] ?? null; - if ($route) { - return $route; - } - } - } return route('login'); } - abort(401); return null; } diff --git a/app/Http/Middleware/CheckBrand.php b/app/Http/Middleware/CheckBrand.php deleted file mode 100644 index a371d68..0000000 --- a/app/Http/Middleware/CheckBrand.php +++ /dev/null @@ -1,40 +0,0 @@ -api_token ?? ''; - - if ($apiToken === null) { - abort(401, 'Le token d\'authentification est incorrect ' . \json_encode($request->all())); - } - - try { - $brand = Service::getFromBearerToken($apiToken)->brand; - $request->request->add(['brand_id' => $brand->id]); - } catch (ModelNotFoundException $e) { - abort(401, 'Le token d\'authentification est incorrect ' . $e->getMessage()); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/JsonOnly.php b/app/Http/Middleware/JsonOnly.php index 41b829e..f74233c 100644 --- a/app/Http/Middleware/JsonOnly.php +++ b/app/Http/Middleware/JsonOnly.php @@ -13,7 +13,7 @@ class JsonOnly * @param \Closure $next * @return mixed */ - public function handle(Request $request, \Closure $next) + public function handle(Request $request, \Closure $next): mixed { if ( (!$request->isJson() or diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index 1d80bf3..48b1b3b 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -7,9 +7,6 @@ use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -/** - * @codeCoverageIgnore - */ class RedirectIfAuthenticated { /** @@ -20,7 +17,7 @@ class RedirectIfAuthenticated * @param string|null ...$guards * @return mixed */ - public function handle(Request $request, Closure $next, ...$guards) + public function handle(Request $request, Closure $next, ...$guards): mixed { $guards = empty($guards) ? [null] : $guards; diff --git a/app/Http/Middleware/SecureHeaders.php b/app/Http/Middleware/SecureHeaders.php index 05f324f..cd87640 100644 --- a/app/Http/Middleware/SecureHeaders.php +++ b/app/Http/Middleware/SecureHeaders.php @@ -7,7 +7,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\Vite; -use Symfony\Component\HttpFoundation\Response; class SecureHeaders { @@ -31,7 +30,7 @@ class SecureHeaders * @return \Symfony\Component\HttpFoundation\Response * @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint */ - public function handle(Request $request, Closure $next): Response + public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response { // phpcs:enable View::share('nonce', $nonce = Vite::useCspNonce()); @@ -71,7 +70,7 @@ class SecureHeaders * @param array $headerList * @return void */ - private function removeUnwantedHeaders(array $headerList) + private function removeUnwantedHeaders(array $headerList): void { foreach ($headerList as $header) { header_remove($header); diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php index 7942640..0fbf358 100644 --- a/app/Http/Middleware/TrustHosts.php +++ b/app/Http/Middleware/TrustHosts.php @@ -4,9 +4,6 @@ namespace App\Http\Middleware; use Illuminate\Http\Middleware\TrustHosts as Middleware; -/** - * @codeCoverageIgnore - */ class TrustHosts extends Middleware { /** @@ -14,10 +11,11 @@ class TrustHosts extends Middleware * * @return array */ - public function hosts() + public function hosts(): array { return [ $this->allSubdomainsOfApplicationUrl(), + '172.18.0.6' ]; } } diff --git a/app/Http/Middleware/XssSanitizer.php b/app/Http/Middleware/XssSanitizer.php index 75001b1..ccee246 100644 --- a/app/Http/Middleware/XssSanitizer.php +++ b/app/Http/Middleware/XssSanitizer.php @@ -13,7 +13,7 @@ class XssSanitizer * @param \Closure $next * @return mixed */ - public function handle(mixed $request, Closure $next) + public function handle(mixed $request, Closure $next): mixed { $input = $request->all(); array_walk_recursive($input, function (&$input) { diff --git a/app/Http/Requests/Api/V2/AuthenticateRequest.php b/app/Http/Requests/Api/V2/AuthenticateRequest.php index 9409aac..45c9489 100644 --- a/app/Http/Requests/Api/V2/AuthenticateRequest.php +++ b/app/Http/Requests/Api/V2/AuthenticateRequest.php @@ -2,6 +2,9 @@ namespace App\Http\Requests\Api\V2; +/** + * @codeCoverageIgnore + */ class AuthenticateRequest extends BaseApiRequest { /** @@ -9,7 +12,7 @@ class AuthenticateRequest extends BaseApiRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return true; } diff --git a/app/Http/Requests/Api/V2/BaseApiRequest.php b/app/Http/Requests/Api/V2/BaseApiRequest.php index f270966..da22834 100644 --- a/app/Http/Requests/Api/V2/BaseApiRequest.php +++ b/app/Http/Requests/Api/V2/BaseApiRequest.php @@ -11,12 +11,16 @@ use ArondeParon\RequestSanitizer\Sanitizers\TrimDuplicateSpaces; use ArondeParon\RequestSanitizer\Traits\SanitizesInputs; use Illuminate\Foundation\Http\FormRequest; +/** + * @codeCoverageIgnore + */ abstract class BaseApiRequest extends FormRequest { use SanitizesInputs; /** - * Using these sanitizers before proceeding the request + * Using these sanitizers before proceeding the request. + * * @var array */ protected $sanitizers = [ @@ -36,7 +40,7 @@ abstract class BaseApiRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } diff --git a/app/Http/Requests/Api/V2/GetBrandStatsRequest.php b/app/Http/Requests/Api/V2/GetBrandStatsRequest.php index 1b92e1b..5fd1a0f 100644 --- a/app/Http/Requests/Api/V2/GetBrandStatsRequest.php +++ b/app/Http/Requests/Api/V2/GetBrandStatsRequest.php @@ -6,6 +6,9 @@ use App\Http\Requests\Api\V2\BaseApiRequest; use Illuminate\Support\Carbon; use Illuminate\Validation\ValidationException; +/** + * @codeCoverageIgnore + */ class GetBrandStatsRequest extends BaseApiRequest { /** diff --git a/app/Http/Requests/Api/V2/GetQuestionStatsRequest.php b/app/Http/Requests/Api/V2/GetQuestionStatsRequest.php index 8636453..b84b760 100644 --- a/app/Http/Requests/Api/V2/GetQuestionStatsRequest.php +++ b/app/Http/Requests/Api/V2/GetQuestionStatsRequest.php @@ -7,6 +7,9 @@ use Exception; use Illuminate\Support\Carbon; use Illuminate\Validation\ValidationException; +/** + * @codeCoverageIgnore + */ class GetQuestionStatsRequest extends BaseApiRequest { /** diff --git a/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php b/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php index 2ad29b6..f207d99 100644 --- a/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php +++ b/app/Http/Requests/Api/V2/SendArchadiaDefaultMailRequest.php @@ -9,12 +9,16 @@ use ArondeParon\RequestSanitizer\Sanitizers\Trim; use ArondeParon\RequestSanitizer\Traits\SanitizesInputs; use Illuminate\Validation\Rules\Enum; +/** + * @codeCoverageIgnore + */ class SendArchadiaDefaultMailRequest extends BaseApiRequest { use SanitizesInputs; /** - * Using these sanitizers before proceeding the request + * Using these sanitizers before proceeding the request. + * * @var array */ protected $sanitizers = [ @@ -65,11 +69,11 @@ class SendArchadiaDefaultMailRequest extends BaseApiRequest } /** - * Attributes + * Attributes. * * @return array */ - public function attributes() + public function attributes(): array { return [ 'label_type_support' => trans('label type support'), @@ -92,11 +96,11 @@ class SendArchadiaDefaultMailRequest extends BaseApiRequest } /** - * Messages + * Messages. * * @return array */ - public function messages() + public function messages(): array { return [ 'deliver.phone.phone' => trans('Le champ :attribute est invalide, il accepte les numéros diff --git a/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php b/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php index c7f4f94..da5a4e9 100644 --- a/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php +++ b/app/Http/Requests/Api/V2/SendArchadiaGameMailRequest.php @@ -6,12 +6,16 @@ use App\Http\Sanitizers\RemoveSpaces; use ArondeParon\RequestSanitizer\Sanitizers\Trim; use ArondeParon\RequestSanitizer\Traits\SanitizesInputs; +/** + * @codeCoverageIgnore + */ class SendArchadiaGameMailRequest extends BaseApiRequest { use SanitizesInputs; /** - * Using these sanitizers before proceeding the request + * Using these sanitizers before proceeding the request. + * * @var array */ protected $sanitizers = [ @@ -59,11 +63,11 @@ class SendArchadiaGameMailRequest extends BaseApiRequest } /** - * Attributes + * Attributes. * * @return array */ - public function attributes() + public function attributes(): array { return [ 'code_on_pack' => trans('code ean emballage'), @@ -85,11 +89,11 @@ class SendArchadiaGameMailRequest extends BaseApiRequest } /** - * Messages + * Messages. * * @return array */ - public function messages() + public function messages(): array { return [ 'deliver.phone.phone' => trans('Le champ :attribute est invalide, il accepte diff --git a/app/Http/Requests/Api/V2/SingleOptinRemoveWithHashcodeRequest.php b/app/Http/Requests/Api/V2/SingleOptinRemoveWithHashcodeRequest.php index 009bd46..ce06a71 100644 --- a/app/Http/Requests/Api/V2/SingleOptinRemoveWithHashcodeRequest.php +++ b/app/Http/Requests/Api/V2/SingleOptinRemoveWithHashcodeRequest.php @@ -2,6 +2,9 @@ namespace App\Http\Requests\Api\V2; +/** + * @codeCoverageIgnore + */ class SingleOptinRemoveWithHashcodeRequest extends BaseApiRequest { /** diff --git a/app/Http/Requests/Api/V2/SingleOptinRequest.php b/app/Http/Requests/Api/V2/SingleOptinRequest.php index c27ca30..bc8b87f 100644 --- a/app/Http/Requests/Api/V2/SingleOptinRequest.php +++ b/app/Http/Requests/Api/V2/SingleOptinRequest.php @@ -2,6 +2,9 @@ namespace App\Http\Requests\Api\V2; +/** + * @codeCoverageIgnore + */ class SingleOptinRequest extends BaseApiRequest { /** diff --git a/app/Http/Requests/Api/V2/StoreCustomerAnswers.php b/app/Http/Requests/Api/V2/StoreCustomerAnswers.php index 58d7550..88e8801 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerAnswers.php +++ b/app/Http/Requests/Api/V2/StoreCustomerAnswers.php @@ -4,7 +4,11 @@ namespace App\Http\Requests\Api\V2; use App\Models\Answer; use App\Models\Question; +use App\Models\Service; +/** + * @codeCoverageIgnore + */ class StoreCustomerAnswers extends BaseApiRequest { /** @@ -15,9 +19,9 @@ class StoreCustomerAnswers extends BaseApiRequest public function rules(): array { // * Resolve Service. - $service = app()->make(\App\Models\Service::class); + $service = app()->make(Service::class); $brandId = $service->brand->id; - $question = Question::findOrFail($this->question); + $question = Question::query()->findOrFail($this->question); $questionTableName = (new Question())->getTable(); $answerTableName = (new Answer())->getTable(); return [ @@ -37,7 +41,7 @@ class StoreCustomerAnswers extends BaseApiRequest 'distinct', "exists:$answerTableName,id", function ($attribute, $value, $fail) use ($question) { - if (Answer::findOrFail($value)->question->id !== $question->id) { + if (Answer::query()->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 index 0a5d80d..0b7db61 100644 --- a/app/Http/Requests/Api/V2/StoreCustomerRequest.php +++ b/app/Http/Requests/Api/V2/StoreCustomerRequest.php @@ -6,6 +6,9 @@ use App\Enums\Country; use Illuminate\Validation\Rules\Enum; use Illuminate\Validation\Rules\Password; +/** + * @codeCoverageIgnore + */ class StoreCustomerRequest extends BaseApiRequest { /** @@ -15,6 +18,21 @@ class StoreCustomerRequest extends BaseApiRequest */ protected $currentCountry = null; + /** + * Prepare the data for validation. + * + * @return void + */ + protected function prepareForValidation(): void + { + try { + $this->currentCountry = Country::make(\intval($this->country)); + } catch (\TypeError | \BadMethodCallException $e) { + $this->currentCountry = null; + } + parent::prepareForValidation(); + } + /** * Get the validation rules that apply to the request. * @@ -22,6 +40,8 @@ class StoreCustomerRequest extends BaseApiRequest */ public function rules(): array { + $countryList = ($this->currentCountry && $this->currentCountry->get('iso')) ? + $this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes()); return [ 'firstname' => 'required|string|min:1|max:255', 'lastname' => 'required|string|min:1|max:255', @@ -39,8 +59,7 @@ class StoreCustomerRequest extends BaseApiRequest 'birth_date' => 'required|date_format:Y-m-d|max:255', 'address' => 'sometimes|required_with:address2|string|max:255', 'address2' => 'sometimes|string|max:255', - 'phone' => ['sometimes', 'string', 'phone:' . ($this->currentCountry ? - $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes()))], + 'phone' => ['sometimes', 'string', "phone:{$countryList}"], 'country' => [ 'sometimes', 'numeric', @@ -51,35 +70,17 @@ class StoreCustomerRequest extends BaseApiRequest ) ], 'city' => 'sometimes|string|min:2|max:255', - 'postcode' => ['sometimes', 'string', 'postal_code:' . ($this->currentCountry ? - $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes()))], + 'postcode' => ['sometimes', 'string', "postal_code:{$countryList}"], 'opt_in' => 'required|boolean', ]; } /** - * Prepare the data for validation. - * - * @return void - */ - protected function prepareForValidation(): void - { - try { - if (is_array($this->country)) { - $this->currentCountry = Country::make(\intval($this->country)); - } - } catch (\TypeError | \BadMethodCallException $e) { - $this->currentCountry = null; - } - parent::prepareForValidation(); - } - - /** - * Messages + * Messages. * * @return array */ - public function messages() + public function messages(): array { return [ 'phone.phone' => trans( diff --git a/app/Http/Requests/Api/V2/UpdateCustomerRequest.php b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php index cc1a098..befcb70 100644 --- a/app/Http/Requests/Api/V2/UpdateCustomerRequest.php +++ b/app/Http/Requests/Api/V2/UpdateCustomerRequest.php @@ -6,6 +6,9 @@ use App\Enums\Country; use Illuminate\Validation\Rules\Enum; use Illuminate\Validation\Rules\Password; +/** + * @codeCoverageIgnore + */ class UpdateCustomerRequest extends StoreCustomerRequest { /** @@ -41,8 +44,10 @@ class UpdateCustomerRequest extends StoreCustomerRequest 'birth_date' => [$atLeast('birth_date'), 'date_format:Y-m-d', 'max:255'], 'address' => [$atLeast('address'), 'required_with:address2', 'string', 'max:255'], 'address2' => ['sometimes', 'string', 'max:255'], - 'phone' => [$atLeast('phone'), 'string', 'phone:' . ($this->currentCountry ? - $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes())), 'max:255'], + 'phone' => [$atLeast('phone'), 'string', 'phone:' . ( + ($this->currentCountry && $this->currentCountry->get('iso')) ? + $this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes()) + ), 'max:255'], 'country' => [ $atLeast('country'), 'numeric', @@ -53,8 +58,10 @@ class UpdateCustomerRequest extends StoreCustomerRequest ) ], 'city' => [$atLeast('city'), 'string', 'min:2', 'max:255'], - 'postcode' => [$atLeast('postcode'), 'string', 'postal_code:' . ($this->currentCountry ? - $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes())), 'max:255'], + 'postcode' => [$atLeast('postcode'), 'string', 'postal_code:' . ( + ($this->currentCountry && $this->currentCountry->get('iso')) ? + $this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes()) + ), 'max:255'], 'opt_in' => [$atLeast('opt_in'), 'boolean'], ]; } diff --git a/app/Http/Requests/Api/V4/AuthenticateRequest.php b/app/Http/Requests/Api/V4/AuthenticateRequest.php new file mode 100644 index 0000000..cc2378d --- /dev/null +++ b/app/Http/Requests/Api/V4/AuthenticateRequest.php @@ -0,0 +1,29 @@ + 'required|email:rfc', + 'password' => 'required' + ]; + } +} diff --git a/app/Http/Requests/Api/V4/BaseApiRequest.php b/app/Http/Requests/Api/V4/BaseApiRequest.php new file mode 100644 index 0000000..db2bd5c --- /dev/null +++ b/app/Http/Requests/Api/V4/BaseApiRequest.php @@ -0,0 +1,51 @@ + [Trim::class, TrimDuplicateSpaces::class, Capitalize::class], + 'lastname' => [Trim::class, TrimDuplicateSpaces::class, Capitalize::class], + 'address' => [Trim::class, TrimDuplicateSpaces::class], + 'address2' => [Trim::class, TrimDuplicateSpaces::class], + 'city' => [Trim::class, TrimDuplicateSpaces::class], + 'password' => Trim::class, + 'phone' => [Trim::class, RemoveSpaces::class], + 'email' => [Lowercase::class, Trim::class, RemoveSpaces::class], + 'postcode' => [RemoveNonNumeric::class, Trim::class] + ]; + + /** + * Determine if the user is authorized to make this request. + * + * @return boolean + */ + public function authorize(): bool + { + 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/V4/GetBrandStatsRequest.php b/app/Http/Requests/Api/V4/GetBrandStatsRequest.php new file mode 100644 index 0000000..7ef8d79 --- /dev/null +++ b/app/Http/Requests/Api/V4/GetBrandStatsRequest.php @@ -0,0 +1,40 @@ +end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) { + throw ValidationException::withMessages([ + 'Vous devez sélectionner une période de maximum 3 mois' + ]); + } + } catch (\Exception $e) { + if (\get_class($e) !== ValidationException::class) { + throw ValidationException::withMessages([ + 'Les dates séléectionnées ne sembles pas être valides' + ]); + } + throw $e; + } + return [ + 'start' => 'required|date|date_format:Y-m-d', + 'end' => 'required|date|date_format:Y-m-d|after_or_equal:start', + ]; + } +} diff --git a/app/Http/Requests/Api/V4/GetQuestionStatsRequest.php b/app/Http/Requests/Api/V4/GetQuestionStatsRequest.php new file mode 100644 index 0000000..374ff19 --- /dev/null +++ b/app/Http/Requests/Api/V4/GetQuestionStatsRequest.php @@ -0,0 +1,40 @@ +end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) { + throw ValidationException::withMessages([ + 'Vous devez sélectionner une période de maximum 3 mois' + ]); + } + } catch (\Exception $e) { + if (\get_class($e) !== ValidationException::class) { + throw ValidationException::withMessages([ + 'Les dates sélectionnées ne sembles pas être valides' + ]); + } + throw $e; + } + return [ + 'start' => 'required|date|date_format:Y-m-d', + 'end' => 'required|date|date_format:Y-m-d|after_or_equal:start', + ]; + } +} diff --git a/app/Http/Requests/Api/V4/SendArchadiaDefaultMailRequest.php b/app/Http/Requests/Api/V4/SendArchadiaDefaultMailRequest.php new file mode 100644 index 0000000..bf4994a --- /dev/null +++ b/app/Http/Requests/Api/V4/SendArchadiaDefaultMailRequest.php @@ -0,0 +1,109 @@ + Trim::class, + 'operation' => Trim::class, + 'order_date' => Trim::class, + 'order_num' => RemoveNonNumeric::class, + 'package_unit_weight' => RemoveNonNumeric::class, + 'support_code' => Trim::class, + 'deliver.firstname' => Trim::class, + 'deliver.lastname' => Trim::class, + 'deliver.email' => Trim::class, + 'deliver.address' => Trim::class, + 'deliver.address2' => Trim::class, + 'deliver.city' => Trim::class, + 'deliver.postcode' => Trim::class, + 'deliver.phone' => [Trim::class, RemoveSpaces::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 Enum(ArcadiaOrderType::class) + ], + 'support_code' => 'required|int', + 'deliver' => 'required|array', + 'deliver.firstname' => 'required|string|min:1|max:255', + 'deliver.lastname' => 'required|string|min:1|max:255', + 'deliver.email' => 'required|email:rfc,dns,spoof,filter|max:255', + 'deliver.address' => 'required|string|min:2|max:255', + 'deliver.address2' => 'sometimes|string|min:2|max:255', + 'deliver.city' => 'required|string|min:2|max:255', + 'deliver.postcode' => 'required|string|postal_code:FR|max:255', + 'deliver.phone' => 'present|string|nullable|phone:FR|max:255', + ]; + } + + /** + * Attributes. + * + * @return array + */ + public function attributes(): array + { + return [ + 'label_type_support' => trans('label type support'), + 'operation' => trans('code opération'), + 'order_date' => trans('date de la commande'), + 'order_num' => trans('numero de commande'), + 'package_unit_weight' => trans('poids unitaire du paquet'), + 'state_order' => trans('type de commande arcadia'), + 'support_code' => trans('code de support'), + 'deliver' => trans('coordonées de livraison'), + 'deliver.civility' => trans('civilitée dans coordonées de livraison'), + 'deliver.firstname' => trans('prénom dans coordonées de livraison'), + 'deliver.lastname' => trans('nom dans coordonées de livraison'), + 'deliver.address' => trans('adresse dans coordonées de livraison'), + 'deliver.postcode' => trans('code postal dans coordonées de livraison'), + 'deliver.phone' => trans('téléphone dans coordonées de livraison'), + 'deliver.city' => trans('ville dans coordonées de livraison'), + 'deliver.email' => trans('e-mail dans coordonées de livraison'), + ]; + } + + /** + * Messages. + * + * @return array + */ + public function messages(): array + { + return [ + 'deliver.phone.phone' => trans('Le champ :attribute est invalide, il accepte les numéros + des pays suivants: France.'), + 'deliver.postcode.postal_code' => trans('Le champ :attribute accepte les codes postaux des pays + suivants :countries comme par exemple :examples.'), + ]; + } +} diff --git a/app/Http/Requests/Api/V4/SendArchadiaGameMailRequest.php b/app/Http/Requests/Api/V4/SendArchadiaGameMailRequest.php new file mode 100644 index 0000000..8e072ae --- /dev/null +++ b/app/Http/Requests/Api/V4/SendArchadiaGameMailRequest.php @@ -0,0 +1,102 @@ + 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, + 'deliver.firstname' => Trim::class, + 'deliver.lastname' => Trim::class, + 'deliver.email' => Trim::class, + 'deliver.address' => Trim::class, + 'deliver.address2' => Trim::class, + 'deliver.city' => Trim::class, + 'deliver.postcode' => Trim::class, + 'deliver.phone' => [Trim::class, RemoveSpaces::class] + ]; + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + return [ + 'code_on_pack' => 'sometimes|string|max:50', + 'code_unique' => 'required|string', + 'label_type_support' => 'required|string', + 'operation' => 'required|int', + 'order_date' => 'required|date', + 'support_code' => 'required|int', + 'deliver' => 'required|array', + 'deliver.firstname' => 'required|string|min:1|max:255', + 'deliver.lastname' => 'required|string|min:1|max:255', + 'deliver.email' => 'required|email:rfc,dns,spoof,filter|max:255', + 'deliver.address' => 'required|string|min:2|max:255', + 'deliver.address2' => 'sometimes|string|min:2|max:255', + 'deliver.city' => 'required|string|min:2|max:255', + 'deliver.postcode' => 'required|string|postal_code:FR|max:255', + 'deliver.phone' => 'present|string|nullable|phone:FR|max:255', + ]; + } + + /** + * Attributes. + * + * @return array + */ + public function attributes(): array + { + return [ + 'code_on_pack' => trans('code ean emballage'), + 'code_unique' => trans('code unique'), + 'label_type_support' => trans('label type support'), + 'operation' => trans('code opération'), + 'order_date' => trans('date de la commande'), + 'support_code' => trans('code de support'), + 'deliver' => trans('coordonées de livraison'), + 'deliver.civility' => trans('civilitée dans coordonées de livraison'), + 'deliver.firstname' => trans('prénom dans coordonées de livraison'), + 'deliver.lastname' => trans('nom dans coordonées de livraison'), + 'deliver.address' => trans('adresse dans coordonées de livraison'), + 'deliver.postcode' => trans('code postal dans coordonées de livraison'), + 'deliver.phone' => trans('téléphone dans coordonées de livraison'), + 'deliver.city' => trans('ville dans coordonées de livraison'), + 'deliver.email' => trans('e-mail dans coordonées de livraison'), + ]; + } + + /** + * Messages. + * + * @return array + */ + public function messages(): array + { + return [ + 'deliver.phone.phone' => trans('Le champ :attribute est invalide, il accepte + les numéros des pays suivants: France.'), + 'deliver.postcode.postal_code' => trans('Le champ :attribute accepte les codes postaux + des pays suivants :countries comme par exemple :examples.'), + ]; + } +} diff --git a/app/Http/Requests/Api/V4/SingleOptinRemoveWithHashcodeRequest.php b/app/Http/Requests/Api/V4/SingleOptinRemoveWithHashcodeRequest.php new file mode 100644 index 0000000..eb588cc --- /dev/null +++ b/app/Http/Requests/Api/V4/SingleOptinRemoveWithHashcodeRequest.php @@ -0,0 +1,18 @@ + 'required|string|size:36' + ]; + } +} diff --git a/app/Http/Requests/Api/V4/SingleOptinRequest.php b/app/Http/Requests/Api/V4/SingleOptinRequest.php new file mode 100644 index 0000000..d5aa778 --- /dev/null +++ b/app/Http/Requests/Api/V4/SingleOptinRequest.php @@ -0,0 +1,19 @@ + 'required|email:rfc,dns,spoof,filter', + 'opt_in' => 'required|boolean' + ]; + } +} diff --git a/app/Http/Requests/Api/V4/StoreCustomerAnswers.php b/app/Http/Requests/Api/V4/StoreCustomerAnswers.php new file mode 100644 index 0000000..caa6a65 --- /dev/null +++ b/app/Http/Requests/Api/V4/StoreCustomerAnswers.php @@ -0,0 +1,48 @@ +make(Service::class); + $brandId = $service->brand->id; + $question = Question::query()->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::query()->findOrFail($value)->question->id !== $question->id) { + $fail("$attribute does not belong to question $question->id"); + } + } + ] + ]; + } +} diff --git a/app/Http/Requests/Api/V4/StoreCustomerRequest.php b/app/Http/Requests/Api/V4/StoreCustomerRequest.php new file mode 100644 index 0000000..4a0d435 --- /dev/null +++ b/app/Http/Requests/Api/V4/StoreCustomerRequest.php @@ -0,0 +1,95 @@ +currentCountry = Country::make(\intval($this->country)); + } catch (\TypeError | \BadMethodCallException $e) { + $this->currentCountry = null; + } + parent::prepareForValidation(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules(): array + { + $countryList = ($this->currentCountry && $this->currentCountry->get('iso')) ? + $this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes()); + return [ + 'firstname' => 'required|string|min:1|max:255', + 'lastname' => 'required|string|min:1|max:255', + 'email' => 'required|email:rfc,dns,spoof,filter|max:255', + 'password' => [ + 'required', + 'max:255', + Password::min(8) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + ], + 'birth_date' => 'required|date_format:Y-m-d|max:255', + 'address' => 'sometimes|required_with:address2|string|max:255', + 'address2' => 'sometimes|string|max:255', + 'phone' => ['sometimes', 'string', "phone:{$countryList}"], + 'country' => [ + 'sometimes', + 'numeric', + new Enum(Country::class), + sprintf( + 'in:%s', + implode(',', Country::getValidCountriesValues()) + ) + ], + 'city' => 'sometimes|string|min:2|max:255', + 'postcode' => ['sometimes', 'string', "postal_code:{$countryList}"], + 'opt_in' => 'required|boolean', + ]; + } + + /** + * Messages. + * + * @return array + */ + public function messages(): array + { + return [ + 'phone.phone' => trans( + 'Le champ :attribute est invalide, il accepte les numéros des pays + suivants :countries.', + ['countries' => implode(',', Country::getValidCountriesLabel())] + ), + 'country.in' => trans( + 'Le champ :attribute est invalide, il accepte les pays suivants + :countries.', + ['countries' => implode(',', Country::getValidCountriesLabel())] + ) + ]; + } +} diff --git a/app/Http/Requests/Api/V4/UpdateCustomerRequest.php b/app/Http/Requests/Api/V4/UpdateCustomerRequest.php new file mode 100644 index 0000000..ba9fdf9 --- /dev/null +++ b/app/Http/Requests/Api/V4/UpdateCustomerRequest.php @@ -0,0 +1,65 @@ +filter(function ($fieldName) use ($attribute) { + return $fieldName !== $attribute; + })->implode(','); + }; + return [ + 'firstname' => [$atLeast('firstname'), 'string', 'min:1', 'max:255'], + 'lastname' => [$atLeast('lastname'), 'string', 'min:1', 'max:255'], + 'email' => [$atLeast('email'), 'email:rfc,dns,spoof,filter', 'max:255'], + 'password' => [ + $atLeast('password'), 'max:255', Password::min(8) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + ], + 'birth_date' => [$atLeast('birth_date'), 'date_format:Y-m-d', 'max:255'], + 'address' => [$atLeast('address'), 'required_with:address2', 'string', 'max:255'], + 'address2' => ['sometimes', 'string', 'max:255'], + 'phone' => [$atLeast('phone'), 'string', 'phone:' . ( + ($this->currentCountry && $this->currentCountry->get('iso')) ? + $this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes()) + ), 'max:255'], + 'country' => [ + $atLeast('country'), + 'numeric', + new Enum(Country::class), + sprintf( + 'in:%s', + implode(',', Country::getValidCountriesValues()) + ) + ], + 'city' => [$atLeast('city'), 'string', 'min:2', 'max:255'], + 'postcode' => [$atLeast('postcode'), 'string', 'postal_code:' . ( + ($this->currentCountry && $this->currentCountry->get('iso')) ? + $this->currentCountry->get('iso') : implode(',', Country::getValidIsoAplha2Codes()) + ), 'max:255'], + 'opt_in' => [$atLeast('opt_in'), 'boolean'], + ]; + } +} diff --git a/app/Http/Requests/CustomerExport.php b/app/Http/Requests/CustomerExport.php index d9e8c75..4d15474 100644 --- a/app/Http/Requests/CustomerExport.php +++ b/app/Http/Requests/CustomerExport.php @@ -5,9 +5,6 @@ namespace App\Http\Requests; use App\Enums\CustomerExportType; use Illuminate\Foundation\Http\FormRequest; -/** - * @codeCoverageIgnore - */ class CustomerExport extends FormRequest { /** @@ -15,7 +12,7 @@ class CustomerExport extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -25,7 +22,7 @@ class CustomerExport extends FormRequest * * @return array */ - public function rules() + public function rules(): array { return [ 'brand' => 'required|numeric|exists:brands,id', diff --git a/app/Http/Requests/GenerateServiceToken.php b/app/Http/Requests/GenerateServiceToken.php index a143342..38750f5 100644 --- a/app/Http/Requests/GenerateServiceToken.php +++ b/app/Http/Requests/GenerateServiceToken.php @@ -4,9 +4,6 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; -/** - * @codeCoverageIgnore - */ class GenerateServiceToken extends FormRequest { /** @@ -14,7 +11,7 @@ class GenerateServiceToken extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -24,7 +21,7 @@ class GenerateServiceToken extends FormRequest * * @return array */ - public function rules() + public function rules(): array { return []; } diff --git a/app/Http/Requests/ManageBrandRequest.php b/app/Http/Requests/ManageBrandRequest.php index f38264b..d5c264f 100644 --- a/app/Http/Requests/ManageBrandRequest.php +++ b/app/Http/Requests/ManageBrandRequest.php @@ -6,15 +6,13 @@ use ArondeParon\RequestSanitizer\Sanitizers\FilterVars; use ArondeParon\RequestSanitizer\Traits\SanitizesInputs; use Illuminate\Foundation\Http\FormRequest; -/** - * @codeCoverageIgnore - */ class ManageBrandRequest extends FormRequest { use SanitizesInputs; /** * Using these sanitizers before proceeding the request + * * @var array */ protected $sanitizers = [ @@ -30,7 +28,7 @@ class ManageBrandRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -40,7 +38,7 @@ class ManageBrandRequest extends FormRequest * * @return array */ - public function rules() + public function rules(): array { if ($this->isMethod('post')) { return [ diff --git a/app/Http/Requests/ManageServiceRequest.php b/app/Http/Requests/ManageServiceRequest.php index 7eda01c..9a74d5b 100644 --- a/app/Http/Requests/ManageServiceRequest.php +++ b/app/Http/Requests/ManageServiceRequest.php @@ -6,15 +6,13 @@ use ArondeParon\RequestSanitizer\Sanitizers\FilterVars; use ArondeParon\RequestSanitizer\Traits\SanitizesInputs; use Illuminate\Foundation\Http\FormRequest; -/** - * @codeCoverageIgnore - */ class ManageServiceRequest extends FormRequest { use SanitizesInputs; /** * Using these sanitizers before proceeding the request + * * @var array */ protected $sanitizers = [ @@ -30,7 +28,7 @@ class ManageServiceRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -40,7 +38,7 @@ class ManageServiceRequest extends FormRequest * * @return array */ - public function rules() + public function rules(): array { if ($this->isMethod('post')) { return [ diff --git a/app/Http/Requests/QuestionExport.php b/app/Http/Requests/QuestionExport.php index 4c1bb97..a532558 100644 --- a/app/Http/Requests/QuestionExport.php +++ b/app/Http/Requests/QuestionExport.php @@ -4,9 +4,6 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; -/** - * @codeCoverageIgnore - */ class QuestionExport extends FormRequest { /** @@ -14,7 +11,7 @@ class QuestionExport extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -24,7 +21,7 @@ class QuestionExport extends FormRequest * * @return array */ - public function rules() + public function rules(): array { return [ 'brand' => 'required|numeric|exists:brands,id' diff --git a/app/Http/Requests/StatsByBrandRequest.php b/app/Http/Requests/StatsByBrandRequest.php index 0f1a759..97d568a 100644 --- a/app/Http/Requests/StatsByBrandRequest.php +++ b/app/Http/Requests/StatsByBrandRequest.php @@ -2,14 +2,10 @@ namespace App\Http\Requests; -use Exception; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Carbon; use Illuminate\Validation\ValidationException; -/** - * @codeCoverageIgnore - */ class StatsByBrandRequest extends FormRequest { /** @@ -17,7 +13,7 @@ class StatsByBrandRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -26,10 +22,11 @@ class StatsByBrandRequest extends FormRequest * Get the validation rules that apply to the request. * * @return array - * @throws \Illuminate\Validation\ValidationException If given dates are unparsable of period is bigger than 1 year. + * @throws \Illuminate\Validation\ValidationException If given dates are unparsable of period + * is bigger than 1 year. * @throws \Exception If carbon date processing failed. */ - public function rules() + public function rules(): array { if ($this->post()) { try { @@ -38,7 +35,7 @@ class StatsByBrandRequest extends FormRequest 'Vous devez sélectionner une période de maximum 1 ans' ]); } - } catch (Exception $e) { + } catch (\Exception $e) { if (\get_class($e) !== ValidationException::class) { throw ValidationException::withMessages([ 'Les dates séléectionnées ne sembles pas être valides' diff --git a/app/Http/Requests/StatsByQuestionRequest.php b/app/Http/Requests/StatsByQuestionRequest.php index 14373f4..12f0800 100644 --- a/app/Http/Requests/StatsByQuestionRequest.php +++ b/app/Http/Requests/StatsByQuestionRequest.php @@ -2,14 +2,10 @@ namespace App\Http\Requests; -use Exception; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Carbon; use Illuminate\Validation\ValidationException; -/** - * @codeCoverageIgnore - */ class StatsByQuestionRequest extends FormRequest { /** @@ -17,7 +13,7 @@ class StatsByQuestionRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return auth()->check(); } @@ -30,7 +26,7 @@ class StatsByQuestionRequest extends FormRequest * is bigger than 1 year. * @throws \Exception If carbon date processing failed. */ - public function rules() + public function rules(): array { if ($this->post()) { try { @@ -39,10 +35,10 @@ class StatsByQuestionRequest extends FormRequest 'Vous devez sélectionner une période de maximum 1 ans' ]); } - } catch (Exception $e) { + } catch (\Exception $e) { if (\get_class($e) !== ValidationException::class) { throw ValidationException::withMessages([ - 'Les dates séléectionnées ne sembles pas être valides' + 'Les dates sélectionnées ne sembles pas être valides' ]); } throw $e; diff --git a/app/Http/Resources/Api/V2/AnswersResource.php b/app/Http/Resources/Api/V2/AnswersResource.php index c423b87..8c310e2 100644 --- a/app/Http/Resources/Api/V2/AnswersResource.php +++ b/app/Http/Resources/Api/V2/AnswersResource.php @@ -3,6 +3,7 @@ namespace App\Http\Resources\Api\V2; use App\Models\Answer; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection; class AnswersResource extends JsonResourceCollection @@ -10,12 +11,13 @@ class AnswersResource extends JsonResourceCollection /** * Transform the resource into an array. * - * @param mixed $request - * @return array + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Support\Collection + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request) + public function toArray(Request $request): \Illuminate\Support\Collection { + //phpcs:enable return $this->collection->map(function (Answer $answer) { return [ 'id' => $answer->id, diff --git a/app/Http/Resources/Api/V2/ApiResponse.php b/app/Http/Resources/Api/V2/ApiResponse.php index b62c07a..df4778b 100644 --- a/app/Http/Resources/Api/V2/ApiResponse.php +++ b/app/Http/Resources/Api/V2/ApiResponse.php @@ -3,22 +3,23 @@ namespace App\Http\Resources\Api\V2; use App\Enums\ApiStatus; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use stdClass; class ApiResponse extends JsonResource { - /** @var \stdClass */ + /** @var \stdClass|null */ protected $data; /** @var \App\Enums\ApiStatus */ protected $status; /** - * @param \stdClass $data + * @param \stdClass|null $data * @param \App\Enums\ApiStatus $status */ - public function __construct(stdClass $data = null, ApiStatus $status) + public function __construct(stdClass|null $data = null, ApiStatus $status) { $this->status = $status; $this->data = $data; @@ -27,16 +28,17 @@ class ApiResponse extends JsonResource /** * Transform the resource into an array. * - * @param mixed $request + * @param \Illuminate\Http\Request $request * @return array + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request): array + public function toArray(Request $request): array { + //phpcs:enable return [ - 'status' => $this->status->value, - 'data' => (array)$this->data, - 'version' => config('api.version') + 'status' => $this->status->value(), + 'data' => (array)($this->data ?? []), + 'version' => '3.0.6' ]; } } diff --git a/app/Http/Resources/Api/V2/BrandStatsResource.php b/app/Http/Resources/Api/V2/BrandStatsResource.php index 582ff2e..ff0d415 100644 --- a/app/Http/Resources/Api/V2/BrandStatsResource.php +++ b/app/Http/Resources/Api/V2/BrandStatsResource.php @@ -3,6 +3,7 @@ namespace App\Http\Resources\Api\V2; use App\Models\BrandStat; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection; class BrandStatsResource extends JsonResourceCollection @@ -10,22 +11,23 @@ class BrandStatsResource extends JsonResourceCollection /** * Transform the resource into an array. * - * @param mixed $request + * @param \Illuminate\Http\Request $request * @return array + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request) + public function toArray(Request $request): array { + //phpcs:enable return [ 'stats' => $this->collection->map(function (BrandStat $brandStat) { return [ - 'date' => $brandStat->created_at->isoFormat('YYYY-MM-DD'), - 'active' => $brandStat->active, - 'inactive' => $brandStat->inactive, - 'old_accounts' => $brandStat->old_accounts, - 'new_accounts' => $brandStat->new_accounts, + 'date' => $brandStat->created_at->isoFormat('YYYY-MM-DD'), + 'active' => $brandStat->active, + 'inactive' => $brandStat->inactive, + 'old_accounts' => $brandStat->old_accounts, + 'new_accounts' => $brandStat->new_accounts, 'total_accounts' => $brandStat->total_accounts, - 'total_opt_in' => $brandStat->total_opt_in + 'total_opt_in' => $brandStat->total_opt_in ]; }) ]; diff --git a/app/Http/Resources/Api/V2/CustomerResource.php b/app/Http/Resources/Api/V2/CustomerResource.php index 8874c61..9d9b69f 100644 --- a/app/Http/Resources/Api/V2/CustomerResource.php +++ b/app/Http/Resources/Api/V2/CustomerResource.php @@ -19,24 +19,24 @@ class CustomerResource extends JsonResource /** @var array */ private static $skeleton = [ - 'hashcode' => '', - 'firstname' => '', - 'lastname' => '', - 'email' => '', + 'hashcode' => '', + 'firstname' => '', + 'lastname' => '', + 'email' => '', 'birth_date' => '', - 'address' => '', - 'address2' => '', - 'phone' => '', - 'country' => '', - 'city' => '', - 'postcode' => '', - 'opt_in' => '', - 'status' => '', + 'address' => '', + 'address2' => '', + 'phone' => '', + 'country' => '', + 'city' => '', + 'postcode' => '', + 'opt_in' => '', + 'status' => '', 'last_connection' => '' ]; /** - * CustomerResource + * CustomerResource. * * @param \App\Models\Brand $brand * @param \App\Models\Customer $customer @@ -67,10 +67,11 @@ class CustomerResource extends JsonResource * + hashcode string * + status boolean * + last_connection date + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request = null) + public function toArray($request = null): array { + //phpcs:enable $data = array_intersect_key( array_merge( [ @@ -83,7 +84,7 @@ class CustomerResource extends JsonResource $this->getSkeleton() ); - $data['country'] = $this->info->country->value; + $data['country'] = $this->info->country->value(); $data['address2'] = $this->info->address2 ?? ''; return $data; } diff --git a/app/Http/Resources/Api/V2/QuestionStatsResource.php b/app/Http/Resources/Api/V2/QuestionStatsResource.php index 3e6dd8b..aef95ad 100644 --- a/app/Http/Resources/Api/V2/QuestionStatsResource.php +++ b/app/Http/Resources/Api/V2/QuestionStatsResource.php @@ -3,6 +3,7 @@ namespace App\Http\Resources\Api\V2; use App\Models\Question; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection; use Illuminate\Support\Collection; @@ -27,12 +28,13 @@ class QuestionStatsResource extends JsonResourceCollection /** * Transform the resource into an array. * - * @param mixed $request + * @param \Illuminate\Http\Request $request * @return array + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request) + public function toArray(Request $request): array { + //phpcs:enable return [ 'answers' => (new AnswersResource($this->question->answers))->toArray($request), 'stats' => $this->collection->toArray() diff --git a/app/Http/Resources/Api/V2/QuestionsResource.php b/app/Http/Resources/Api/V2/QuestionsResource.php index 3f3939a..e3a774f 100644 --- a/app/Http/Resources/Api/V2/QuestionsResource.php +++ b/app/Http/Resources/Api/V2/QuestionsResource.php @@ -4,23 +4,26 @@ namespace App\Http\Resources\Api\V2; use App\Models\Answer; use App\Models\Question; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection as JsonResourceCollection; use Illuminate\Support\Collection; class QuestionsResource extends JsonResourceCollection { - /** @var \Illuminate\Support\Collection */ + /** @var \Illuminate\Support\Collection<\App\Models\CustomerAnswer> */ protected $userAnwsers = null; /** * Create a new resource instance. * - * @param mixed $resource - * @param \Illuminate\Support\Collection $userAnwsers + * @param mixed $resource + * @param \Illuminate\Support\Collection<\App\Models\CustomerAnswer> $userAnwsers * @return void + * @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint */ - public function __construct($resource, Collection $userAnwsers = null) + public function __construct(mixed $resource, Collection $userAnwsers = null) { + //phpcs:enable $this->userAnwsers = $userAnwsers; parent::__construct($resource); } @@ -28,18 +31,19 @@ class QuestionsResource extends JsonResourceCollection /** * Transform the resource into an array. * - * @param mixed $request - * @return array + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Support\Collection + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ - // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request) + public function toArray(Request $request): \Illuminate\Support\Collection { + //phpcs:enable return $this->collection->map(function (Question $question) { /** @var \Illuminate\Support\Collection */ $answers = $question->answers; $question = [ 'id' => $question->id, - 'type' => $question->type->value, + 'type' => $question->type->value(), 'question' => $question->question, 'answers' => new AnswersResource($answers) ]; diff --git a/app/Http/Resources/Api/V4/AnswersResource.php b/app/Http/Resources/Api/V4/AnswersResource.php new file mode 100644 index 0000000..f070968 --- /dev/null +++ b/app/Http/Resources/Api/V4/AnswersResource.php @@ -0,0 +1,28 @@ +collection->map(function (Answer $answer) { + return [ + 'id' => $answer->id, + 'answer' => $answer->answer + ]; + }); + } +} diff --git a/app/Http/Resources/Api/V4/ApiResponse.php b/app/Http/Resources/Api/V4/ApiResponse.php new file mode 100644 index 0000000..66d0bb7 --- /dev/null +++ b/app/Http/Resources/Api/V4/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:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + */ + public function toArray(Request $request): array + { + //phpcs:enable + return [ + 'status' => $this->status->value(), + 'data' => (array)($this->data ?? []), + 'version' => config('api.version') + ]; + } +} diff --git a/app/Http/Resources/Api/V4/BrandStatsResource.php b/app/Http/Resources/Api/V4/BrandStatsResource.php new file mode 100644 index 0000000..fad4b95 --- /dev/null +++ b/app/Http/Resources/Api/V4/BrandStatsResource.php @@ -0,0 +1,35 @@ + $this->collection->map(function (BrandStat $brandStat) { + return [ + 'date' => $brandStat->created_at->isoFormat('YYYY-MM-DD'), + 'active' => $brandStat->active, + 'inactive' => $brandStat->inactive, + 'old_accounts' => $brandStat->old_accounts, + 'new_accounts' => $brandStat->new_accounts, + 'total_accounts' => $brandStat->total_accounts, + 'total_opt_in' => $brandStat->total_opt_in + ]; + }) + ]; + } +} diff --git a/app/Http/Resources/Api/V4/CustomerResource.php b/app/Http/Resources/Api/V4/CustomerResource.php new file mode 100644 index 0000000..be291c6 --- /dev/null +++ b/app/Http/Resources/Api/V4/CustomerResource.php @@ -0,0 +1,101 @@ + '', + 'firstname' => '', + 'lastname' => '', + 'email' => '', + 'birth_date' => '', + 'address' => '', + 'address2' => '', + 'phone' => '', + 'country' => '', + 'city' => '', + 'postcode' => '', + 'opt_in' => '', + 'status' => '', + 'last_connection' => '' + ]; + + /** + * CustomerResource. + * + * @param \App\Models\Brand $brand + * @param \App\Models\Customer $customer + */ + 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. + * + * @param \Illuminate\Http\Request|null $request + * @return array + * + email string + * + 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 + * + last_connection date + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing + */ + public function toArray($request = null): array + { + //phpcs:enable + $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->country->value(); + $data['address2'] = $this->info->address2 ?? ''; + return $data; + } + + /** + * Returns the resource skeletton + * + * @return array + */ + public static function getSkeleton(): array + { + return self::$skeleton; + } +} diff --git a/app/Http/Resources/Api/V4/QuestionStatsResource.php b/app/Http/Resources/Api/V4/QuestionStatsResource.php new file mode 100644 index 0000000..f210551 --- /dev/null +++ b/app/Http/Resources/Api/V4/QuestionStatsResource.php @@ -0,0 +1,43 @@ +question = $question; + parent::__construct($questionStats); + } + + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + */ + public function toArray(Request $request): array + { + //phpcs:enable + return [ + 'answers' => (new AnswersResource($this->question->answers))->toArray($request), + 'stats' => $this->collection->toArray() + ]; + } +} diff --git a/app/Http/Resources/Api/V4/QuestionsResource.php b/app/Http/Resources/Api/V4/QuestionsResource.php new file mode 100644 index 0000000..b56625f --- /dev/null +++ b/app/Http/Resources/Api/V4/QuestionsResource.php @@ -0,0 +1,58 @@ + */ + protected $userAnwsers = null; + + /** + * Create a new resource instance. + * + * @param mixed $resource + * @param \Illuminate\Support\Collection<\App\Models\CustomerAnswer> $userAnwsers + * @return void + * @phpcs:disable Squiz.Commenting.FunctionComment.IncorrectTypeHint + */ + public function __construct(mixed $resource, Collection $userAnwsers = null) + { + //phpcs:enable + $this->userAnwsers = $userAnwsers; + parent::__construct($resource); + } + + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Support\Collection + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass + */ + public function toArray(Request $request): \Illuminate\Support\Collection + { + //phpcs:enable + 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/CustomerResource.php b/app/Http/Resources/CustomerResource.php index d7d03fa..38ac941 100644 --- a/app/Http/Resources/CustomerResource.php +++ b/app/Http/Resources/CustomerResource.php @@ -2,21 +2,19 @@ namespace App\Http\Resources; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; -/** - * @codeCoverageIgnore - */ class CustomerResource extends JsonResource { /** @var integer */ private $brandId; /** - * @param mixed $resource - * @param mixed $brandId + * @param mixed $resource + * @param integer $brandId */ - public function __construct($resource, $brandId) + public function __construct(mixed $resource, int $brandId) { parent::__construct($resource); $this->brandId = $brandId; @@ -25,31 +23,34 @@ class CustomerResource extends JsonResource /** * Transform the resource into an array. * - * @param mixed $request + * @param \Illuminate\Http\Request $request * @return array + * @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass */ - //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing - public function toArray($request) + public function toArray(Request $request): array { + //phpcs:disable + /** @var \App\Models\CustomerInfo */ $infos = $this->infos()->where('brand_id', $this->brandId)->first(); + /** @var \App\Models\CustomerLogin */ $login = $this->logins()->where('brand_id', $this->brandId)->first(); return [ 'status' => 'OK', 'user' => [ - 'crm_id' => $this->id, - 'firstname' => $infos->firstname, - 'lastname' => $infos->lastname, - 'email' => $this->email, + 'crm_id' => $this->id, + 'firstname' => $infos->firstname, + 'lastname' => $infos->lastname, + 'email' => $this->email, 'birth_date' => $infos->birth_date, - 'address' => $infos->address, - 'address2' => $infos->address2, - 'phone' => $infos->phone, - 'country' => $infos->country->label, - 'city' => $infos->city, - 'postcode' => $infos->postcode, - 'opt_in' => $infos->opt_in, - 'hashcode' => $infos->hashcode, - 'status' => $login->status, + 'address' => $infos->address, + 'address2' => $infos->address2, + 'phone' => $infos->phone, + 'country' => $infos->country->label(), + 'city' => $infos->city, + 'postcode' => $infos->postcode, + 'opt_in' => $infos->opt_in, + 'hashcode' => $infos->hashcode, + 'status' => $login->status, ] ]; } diff --git a/app/Http/Resources/StatusResource.php b/app/Http/Resources/StatusResource.php index 0768686..effa264 100644 --- a/app/Http/Resources/StatusResource.php +++ b/app/Http/Resources/StatusResource.php @@ -4,9 +4,6 @@ namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; -/** - * @codeCoverageIgnore - */ class StatusResource extends JsonResource { } diff --git a/app/Http/Sanitizers/RemoveSpaces.php b/app/Http/Sanitizers/RemoveSpaces.php index 946495c..2a1b122 100644 --- a/app/Http/Sanitizers/RemoveSpaces.php +++ b/app/Http/Sanitizers/RemoveSpaces.php @@ -9,9 +9,11 @@ class RemoveSpaces implements Sanitizer /** * @param mixed $input * @return string + * @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing */ public function sanitize($input): string { + //phpcs:enable return preg_replace('~\s{1,}~', '', $input); } } diff --git a/app/Jobs/SetServiceStats.php b/app/Jobs/SetServiceStats.php index fc41664..ab73887 100644 --- a/app/Jobs/SetServiceStats.php +++ b/app/Jobs/SetServiceStats.php @@ -11,9 +11,6 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; -/** - * @codeCoverageIgnore - */ class SetServiceStats implements ShouldQueue { use Dispatchable; @@ -42,7 +39,7 @@ class SetServiceStats implements ShouldQueue * * @return void */ - public function handle() + public function handle(): void { $serviceStat = ServiceStat::firstOrNew([ 'date' => date('Y-m-d'), @@ -62,7 +59,7 @@ class SetServiceStats implements ShouldQueue * * @return \DateTime */ - public function retryUntil() + public function retryUntil(): \DateTime { return now()->addMinutes(rand(0, 360)); } @@ -72,7 +69,7 @@ class SetServiceStats implements ShouldQueue * * @return array */ - public function middleware() + public function middleware(): array { return [ (new WithoutOverlapping(self::KEY)) diff --git a/app/Jobs/StartStatJobs.php b/app/Jobs/StartStatJobs.php index 3f4c39c..393b5b8 100644 --- a/app/Jobs/StartStatJobs.php +++ b/app/Jobs/StartStatJobs.php @@ -8,9 +8,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; -/** - * @codeCoverageIgnore - */ class StartStatJobs implements ShouldQueue { use Dispatchable; @@ -22,7 +19,7 @@ class StartStatJobs implements ShouldQueue * * @return void */ - public function handle() + public function handle(): void { $brands = Brand::all(); foreach ($brands as $brand) { diff --git a/app/Jobs/StatJob.php b/app/Jobs/StatJob.php index 8ea412a..2f1d419 100644 --- a/app/Jobs/StatJob.php +++ b/app/Jobs/StatJob.php @@ -16,9 +16,6 @@ use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Carbon; -/** - * @codeCoverageIgnore - */ class StatJob implements ShouldQueue { use Dispatchable; @@ -55,7 +52,7 @@ class StatJob implements ShouldQueue * * @return void */ - public function handle() + public function handle(): void { $stats = new BrandStat([ 'active' => CustomerLogin::query()->whereHas('brand', function (Builder $q) { @@ -91,7 +88,7 @@ class StatJob implements ShouldQueue * * @return array */ - public function middleware() + public function middleware(): array { // * Dispatch randomly overlaping jobs if overlapping. return [(new WithoutOverlapping(self::JOBKEY))->releaseAfter(rand(20, 90))]; diff --git a/app/Jobs/UpdateCustomerLastConnection.php b/app/Jobs/UpdateCustomerLastConnection.php index 927ca52..0570054 100644 --- a/app/Jobs/UpdateCustomerLastConnection.php +++ b/app/Jobs/UpdateCustomerLastConnection.php @@ -11,9 +11,6 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Carbon; -/** - * @codeCoverageIgnore - */ class UpdateCustomerLastConnection implements ShouldQueue { use Dispatchable; @@ -53,7 +50,7 @@ class UpdateCustomerLastConnection implements ShouldQueue * * @return void */ - public function handle() + public function handle(): void { if ($this->customerLogin->last_connection < $this->date) { $this->customerLogin->last_connection = $this->date; diff --git a/app/Lib/Helper.php b/app/Lib/Helper.php index c074260..7d259e1 100644 --- a/app/Lib/Helper.php +++ b/app/Lib/Helper.php @@ -2,12 +2,11 @@ namespace App\Lib; -use App\Exceptions\Api\V2\CrmException; +use App\Exceptions\Api\V4\CrmException; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Route; -use Illuminate\Support\Str; /** * Helper Class @@ -15,19 +14,19 @@ use Illuminate\Support\Str; class Helper { /** - * Package infos + * Package infos. * * @var array */ private static $packageInfos; /** - * Tells if the request is within apiV2 routes + * Tells if the request is within apiV2 routes. * * @param \Illuminate\Http\Request $request * @return boolean */ - public static function isRequestForApiV2(Request $request) + public static function isRequestForApiV2(Request $request): bool { // * Small hack to ensure to know if route is From APIv2. $routeName = static::getRouteName($request) ?? @@ -35,63 +34,56 @@ class Helper return strpos($routeName, 'apiV2.') === 0; } + /** + * Tells if the request is within apiV4 routes. + * + * @param \Illuminate\Http\Request $request + * @return boolean + */ + public static function isRequestForApiV4(Request $request): bool + { + // * Small hack to ensure to know if route is From APIv4. + $routeName = static::getRouteName($request) ?? + (strpos($request->path(), 'api/v4') === 0 ? 'apiV4.' : null) ?? 'none'; + return strpos($routeName, 'apiV4.') === 0; + } + /** * Try to get current route name * * @param \Illuminate\Http\Request $request * @return string|null */ - public static function getRouteName(Request $request) + public static function getRouteName(Request $request): ?string { return Route::currentRouteName() ?? ($request->route() ? $request->route()->getName() : null); } /** - * Get the bearer token from request + * Try to get current route prefix. * * @param \Illuminate\Http\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; - } - - /** - * Try to get current route prefix - * - * @param \Illuminate\Http\Request $request - * @return string|null - */ - public static function getRoutePrefix(Request $request) + public static function getRoutePrefix(Request $request): ?string { return (Route::getCurrentRoute() ? Route::getCurrentRoute()->getPrefix() : null) ?? ($request->route() ? $request->route()->getPrefix() : null); } /** - * Try to get route from request + * Try to get route from request. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Routing\Route|null */ - public static function getRoute(Request $request) + public static function getRoute(Request $request): \Illuminate\Routing\Route|null { return Route::getRoutes()->getByName(static::getRouteName($request)); } /** - * Get composer.json infos + * Get composer.json infos. * * @return array */ @@ -104,14 +96,14 @@ class Helper } /** - * Calculate the request signature + * Calculate the request signature. * * @param string $secret * @param string $uri * @param string $method * @param array $parameters * @return string request signature - * @throws \App\Exceptions\Api\V2\CrmException If an error occurs. + * @throws \App\Exceptions\Api\V4\CrmException If an error occurs. */ public static function signRequest(string $secret, string $uri, string $method, array $parameters): string { @@ -175,6 +167,6 @@ class Helper throw new CrmException('Failed to decrypt service credentials', 0, $e); } catch (\Exception $e) { throw new CrmException('An error occured when authenticating service', 0, $e); - }//end try + } //end try } } diff --git a/app/Lib/Helpers/ExceptionsHelper.php b/app/Lib/Helpers/ExceptionsHelper.php index 4813275..d7ba16b 100644 --- a/app/Lib/Helpers/ExceptionsHelper.php +++ b/app/Lib/Helpers/ExceptionsHelper.php @@ -2,10 +2,13 @@ namespace App\Lib\Helpers; +/** + * @codeCoverageIgnore + */ class ExceptionsHelper { /** - * Handle sentry event + * Handle sentry event. * * @param \Sentry\Event $event * @return null|\Sentry\Event @@ -26,7 +29,7 @@ class ExceptionsHelper } /** - * Anonimize Sentry Exception bag + * Anonimize Sentry Exception bag. * * @param \Sentry\ExceptionDataBag $exceptionDataBag * @return \Sentry\ExceptionDataBag @@ -42,7 +45,7 @@ class ExceptionsHelper } /** - * Anonymise sentry stacktrace + * Anonymise sentry stacktrace. * * @param \Sentry\Stacktrace $stackTrace * @return \Sentry\Stacktrace @@ -70,7 +73,7 @@ class ExceptionsHelper } /** - * Get anonymized sentry params + * Get anonymized sentry params. * * @param mixed[] $params * @return string[] @@ -103,9 +106,8 @@ class ExceptionsHelper return $params; } - /** - * Anonymize at least Sql queries + * Anonymize at least Sql queries. * * @param string $message * @return string @@ -159,7 +161,7 @@ class ExceptionsHelper } /** - * Handle Args + * Handle Args. * * @param array $stack * @return string diff --git a/app/Lib/MigrateOldData.php b/app/Lib/MigrateOldData.php index 5ce9ffe..f33ed2f 100644 --- a/app/Lib/MigrateOldData.php +++ b/app/Lib/MigrateOldData.php @@ -29,13 +29,13 @@ class MigrateOldData extends LeGaulois2016Migration private $table; /** - * runMigration + * RunMigration. * * @param \App\Models\Brand $brand * @param string $table * @return void */ - public function runMigration(Brand $brand, string $table) + public function runMigration(Brand $brand, string $table): void { $this->table = $table; $this->brand = $brand; @@ -44,13 +44,14 @@ class MigrateOldData extends LeGaulois2016Migration } /** - * instruct + * Instruct. * * @return array + * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh */ - // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing public function instruct(): array { + //phpcs:enable $this->initFile(); $cleanDegeu = function (string $str) { return html_entity_decode( @@ -199,7 +200,7 @@ class MigrateOldData extends LeGaulois2016Migration 'updated_at' => $updated_at ] ); - }//end if + } //end if throw new AbortRowImportException(); }, 'name' => [ @@ -225,22 +226,22 @@ class MigrateOldData extends LeGaulois2016Migration } /** - * Create the migration file + * Create the migration file. * * @return void */ - public function initFile() + public function initFile(): void { File::put(storage_path('crm_migration.csv'), sprintf('new;old;hashcode;created_at;updated_at') . PHP_EOL); } /** - * Write date in the migration file + * Write date in the migration file. * * @param array $data * @return void */ - public function write(array $data) + public function write(array $data): void { foreach ($data as &$d) { $d = sprintf('"%s"', $d); diff --git a/app/Mail/CourrierArcadiaError.php b/app/Mail/CourrierArcadiaError.php index b3890fc..e939f08 100644 --- a/app/Mail/CourrierArcadiaError.php +++ b/app/Mail/CourrierArcadiaError.php @@ -7,9 +7,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; -/** - * @codeCoverageIgnore - */ class CourrierArcadiaError extends Mailable implements ShouldQueue { use Queueable; @@ -42,9 +39,9 @@ class CourrierArcadiaError extends Mailable implements ShouldQueue /** * Build the message. * - * @return $this + * @return \Illuminate\Mail\Mailable */ - public function build() + public function build(): \Illuminate\Mail\Mailable { $msg = $this->msg; return $this->subject( diff --git a/app/Mail/ServiceCredentialRenewed.php b/app/Mail/ServiceCredentialRenewed.php index 8952048..bb634c8 100644 --- a/app/Mail/ServiceCredentialRenewed.php +++ b/app/Mail/ServiceCredentialRenewed.php @@ -6,20 +6,17 @@ use App\Models\Service; use App\Models\User; use Illuminate\Mail\Mailable; -/** - * @codeCoverageIgnore - */ class ServiceCredentialRenewed extends Mailable { /** - * The concerned service + * The concerned service. * * @var \App\Models\Service */ public $service; /** - * The user who did the action + * The user who did the action. * * @var \App\Models\User */ @@ -30,7 +27,6 @@ class ServiceCredentialRenewed extends Mailable * * @param \App\Models\Service $service * @param \App\Models\User $user - * @return void */ public function __construct(Service $service, User $user) { @@ -41,9 +37,9 @@ class ServiceCredentialRenewed extends Mailable /** * Build the message. * - * @return $this + * @return \Illuminate\Mail\Mailable */ - public function build() + public function build(): \Illuminate\Mail\Mailable { return $this->view('mail.service-renewed') ->subject('!!! Warning !!!, New service credential have been generated !') diff --git a/app/Models/Answer.php b/app/Models/Answer.php index d31a21a..29601ee 100644 --- a/app/Models/Answer.php +++ b/app/Models/Answer.php @@ -5,6 +5,17 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @property integer $id Id. + * @property string $answer Answer. + * @property integer $question_id Foreign key Question. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Question $question Question BelongsTo relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $customers Customer BelongsToMany relation. + */ class Answer extends Model { use HasFactory; @@ -21,21 +32,21 @@ class Answer extends Model // * RELATIONS /** - * Question BelongsTo relation + * Question BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function question() + public function question(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Question::class); } /** - * Answer belongsToMany relation + * Customer belongsToMany relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function customers() + public function customers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Customer::class, 'customer_answer'); } diff --git a/app/Models/Brand.php b/app/Models/Brand.php index de81ae3..9e8e028 100644 --- a/app/Models/Brand.php +++ b/app/Models/Brand.php @@ -6,13 +6,30 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Rennokki\QueryCache\Traits\QueryCacheable; +/** + * @property integer $id Id. + * @property string $name Name. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \Illuminate\Database\Eloquent\Collection + * $services Service HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $questions Question HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $customerInfos CustomerInfo HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $servicesStats ServicesStat HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $singleOptins SingleOptin HasMany relation. + */ class Brand extends Model { use HasFactory; use QueryCacheable; /** - * The timeout cache for this model + * The timeout cache for this model. * * @var integer */ @@ -59,7 +76,7 @@ class Brand extends Model * * @return void */ - protected static function booted() + protected static function booted(): void { static::created(function () { // * Flush Eloquent cache. @@ -78,51 +95,51 @@ class Brand extends Model // * RELATIONS /** - * Service hasMany relation + * Service hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function services() + public function services(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Service::class); } /** - * Question hasMany relation + * Question hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function questions() + public function questions(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Question::class); } /** - * CustomerInfo hasMany relation + * CustomerInfo hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function customerInfos() + public function customerInfos(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(CustomerInfo::class); } /** - * ServiceStat hasMany relation + * ServiceStat hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function servicesStats() + public function servicesStats(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ServiceStat::class); } /** - * SingleOptin hasMany relation + * SingleOptin hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function singleOptins() + public function singleOptins(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(SingleOptin::class); } @@ -130,7 +147,7 @@ class Brand extends Model // * METHODS /** - * Account attribute accessor + * Account attribute accessor. * * @return integer */ @@ -140,7 +157,7 @@ class Brand extends Model } /** - * Total optins attribute accessor + * Total optins attribute accessor. * * @return integer */ diff --git a/app/Models/BrandStat.php b/app/Models/BrandStat.php index a01f8e0..db44d55 100644 --- a/app/Models/BrandStat.php +++ b/app/Models/BrandStat.php @@ -4,6 +4,18 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +/** + * @property integer $active + * @property integer $inactive + * @property integer $old_accounts + * @property integer $new_accounts + * @property integer $total_accounts + * @property integer $total_opt_in + * @property integer $brand_id Foreign key Brand. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + */ class BrandStat extends Model { /** @@ -41,7 +53,7 @@ class BrandStat extends Model * * @return void */ - protected static function booted() + protected static function booted(): void { static::creating(function (self $model) { if (!$model->created_at) { @@ -53,11 +65,11 @@ class BrandStat extends Model // * RELATIONS /** - * Brand hasMany relation + * Brand hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index 06ea04e..5ffb33e 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -7,7 +7,25 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Database\Eloquent\Relations\HasMany; +/** + * @property integer $id Id. + * @property string $email Email. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \Illuminate\Database\Eloquent\Collection + * $infos CustomerInfo HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $logins CustomerLogin HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $optInHistory CustomerOptInHistory HasMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $brands Brand BelongsToMany relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $answers Answer BelongsToMany relation. + */ class Customer extends Model { use HasFactory; @@ -28,51 +46,51 @@ class Customer extends Model // * RELATIONS /** - * CustomerInfo hasMany relation + * CustomerInfo hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function infos() + public function infos(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(CustomerInfo::class); } /** - * CustomerLogin hasMany relation + * CustomerLogin hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function logins() + public function logins(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(CustomerLogin::class); } /** - * CustomerOptInHistory hasMany relation + * CustomerOptInHistory hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function optInHistory() + public function optInHistory(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(CustomerOptInHistory::class); } /** - * Customer Brand belongsToMany relation + * Customer Brand belongsToMany relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function brands() + public function brands(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Brand::class, CustomerInfo::class); } /** - * Answers belongsToMany relation + * Answers belongsToMany relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ - public function answers() + public function answers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Answer::class, 'customer_answer'); } @@ -82,11 +100,11 @@ class Customer extends Model /** * Get the CustomerInfo from a specific brand * or create a New one if $firstOrNewOrFail is 1 - * throw model not found exception if firstOrNewOrFail = 2 + * throw model not found exception if firstOrNewOrFail = 2. * * @param \App\Models\Brand $brand * @param integer $firstOrNewOrFail - * @return \Illuminate\Database\Eloquent\Builder + * @return \App\Models\CustomerLogin|null */ public function info(Brand $brand, int $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerInfo { @@ -115,11 +133,11 @@ class Customer extends Model /** * Get the CustomerLogin from a specific brand * or create a New one if $firstOrNewOrFail is 1 - * throw model not found exception if firstOrNewOrFail = 2 + * throw model not found exception if firstOrNewOrFail = 2. * * @param \App\Models\Brand $brand * @param integer $firstOrNewOrFail - * @return \Illuminate\Database\Eloquent\Builder + * @return \App\Models\CustomerLogin|null */ public function login(Brand $brand, int $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerLogin { @@ -151,15 +169,15 @@ class Customer extends Model * @param \App\Models\Brand $brand * @param boolean $orFail * @return self - * @throws ModelNotFoundException If no login or info was found for the brand. + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException If no login or info was found for the brand. */ public function loadBrandData(Brand $brand, bool $orFail = false): self { $this->load([ - 'logins' => function ($q) use ($brand) { + 'logins' => function (HasMany $q) use ($brand) { $q->where('brand_id', $brand->id); }, - 'infos' => function ($q) use ($brand) { + 'infos' => function (HasMany $q) use ($brand) { $q->where('brand_id', $brand->id); } ]); @@ -173,7 +191,7 @@ class Customer extends Model } /** - * Set this customer as optin + * Set this customer as optin. * * @param \App\Models\Brand $brand * @param boolean $optIn @@ -194,7 +212,7 @@ class Customer extends Model /** * Get All answers given by this customer - * for a specific service + * for a specific service. * * @param \App\Models\Service $service * @return \Illuminate\Database\Eloquent\Collection diff --git a/app/Models/CustomerAnswer.php b/app/Models/CustomerAnswer.php index fc3be58..7bcc05f 100644 --- a/app/Models/CustomerAnswer.php +++ b/app/Models/CustomerAnswer.php @@ -4,11 +4,19 @@ namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; +/** + * @property integer $answer_id Foreign key Answer. + * @property integer $customer_id Foreign key Customer. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * + * @property-read \App\Models\Customer $customer Customer BelongsTo relation. + * @property-read \App\Models\Answer $answer Answer BelongsTo relation. + */ class CustomerAnswer extends Pivot { /** * Inhibit update_at column as we only have - * created_at + * created_at. * * @return null */ @@ -20,21 +28,21 @@ class CustomerAnswer extends Pivot // * RELATIONS /** - * Customer belongsTo relation + * Customer belongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function customer() + public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Customer::class); } /** - * Customer belongsTo relation + * Customer belongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function answer() + public function answer(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Answer::class); } diff --git a/app/Models/CustomerInfo.php b/app/Models/CustomerInfo.php index aba6e2e..f45eba8 100644 --- a/app/Models/CustomerInfo.php +++ b/app/Models/CustomerInfo.php @@ -8,6 +8,27 @@ use Illuminate\Support\Str; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @property integer $id Id. + * @property integer $customer_id Foreign key Customer. + * @property integer $brand_id Foreign key Brand. + * @property string $firstname Firstname. + * @property string $lastname Lastname. + * @property \Carbon\Carbon|null $birth_date Birth_date. + * @property string|null $address Address. + * @property string|null $address2 Address2. + * @property string $phone Phone. + * @property \App\Enums\Country $country Country. + * @property string|null $city City. + * @property string|null $postcode Postcode. + * @property boolean $opt_in Opt_in. + * @property string $hashcode Hashcode. + * @property \Illuminate\Support\Carbon $created_at Created date. + * @property \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Customer $customer Customer BelongsTo relation. + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + */ class CustomerInfo extends Model { use HasFactory; @@ -37,7 +58,7 @@ class CustomerInfo extends Model */ protected $casts = [ 'country' => Country::class, - 'opt_in' => 'bool', + 'opt_in' => 'bool', ]; /** @@ -45,7 +66,7 @@ class CustomerInfo extends Model * * @return void */ - protected static function booted() + protected static function booted(): void { static::creating(function (self $model) { $model->hashcode = Str::uuid(); @@ -62,31 +83,21 @@ class CustomerInfo extends Model // * RELATIONS /** - * Customer BelongsTo relation + * Customer BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function customer() + public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Customer::class); } /** - * get Login - * - * @return \App\Models\CustomerLogin - */ - public function login() - { - return $this->customer->login($this->brand); - } - - /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } @@ -94,13 +105,23 @@ class CustomerInfo extends Model // * METHODS /** - * Get customer info from email + * Get Login. + * + * @return \App\Models\CustomerLogin + */ + public function login(): \App\Models\CustomerLogin + { + return $this->customer->login($this->brand); + } + + /** + * Get customer info from email. * * @param \App\Models\Brand $brand * @param string $email * @return \Illuminate\Database\Eloquent\Builder */ - public static function getFromEmail(Brand $brand, string $email) + public static function getFromEmail(Brand $brand, string $email): \Illuminate\Database\Eloquent\Builder { return CustomerInfo::query()->whereHas('brand', function (Builder $q) use ($brand) { $q->where('id', $brand->id); @@ -110,13 +131,13 @@ class CustomerInfo extends Model } /** - * Get customer info from hashcode + * Get customer info from hashcode. * * @param \App\Models\Brand $brand * @param string $hashcode * @return \Illuminate\Database\Eloquent\Builder */ - public static function getFromHashcode(Brand $brand, string $hashcode) + public static function getFromHashcode(Brand $brand, string $hashcode): \Illuminate\Database\Eloquent\Builder { return CustomerInfo::query()->whereHas('brand', function (Builder $q) use ($brand) { $q->where('id', $brand->id); diff --git a/app/Models/CustomerLogin.php b/app/Models/CustomerLogin.php index 6b36cae..185b44f 100644 --- a/app/Models/CustomerLogin.php +++ b/app/Models/CustomerLogin.php @@ -6,6 +6,19 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Hash; +/** + * @property integer $id Id. + * @property integer $customer_id Foreign key Customer. + * @property integer $brand_id Foreign key Brand. + * @property string $password Answer. + * @property \Carbon\Carbon $last_connection Answer. + * @property boolean $status Answer. + * @property \Illuminate\Support\Carbon $created_at Created date. + * @property \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Customer $customer Customer BelongsTo relation. + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + */ class CustomerLogin extends Model { use HasFactory; @@ -34,7 +47,8 @@ class CustomerLogin extends Model * @var array */ protected $casts = [ - 'status' => 'bool', + 'status' => 'bool', + 'last_connection' => 'datetime', ]; /** @@ -42,7 +56,7 @@ class CustomerLogin extends Model * * @return void */ - protected static function booted() + protected static function booted(): void { static::creating(function (self $model) { if (Hash::needsRehash($model->password)) { @@ -59,28 +73,29 @@ class CustomerLogin extends Model // * RELATIONS /** - * Customer BelongsTo relation + * Customer BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function customer() + public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Customer::class); } /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } // * METHODS + /** - * Check if the user password is correct + * Check if the user password is correct. * * @param string $password * @return boolean diff --git a/app/Models/CustomerOptInHistory.php b/app/Models/CustomerOptInHistory.php index 1032f18..e01798a 100644 --- a/app/Models/CustomerOptInHistory.php +++ b/app/Models/CustomerOptInHistory.php @@ -5,6 +5,17 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @property integer $id Id. + * @property integer $customer_id Foreign key Customer. + * @property integer $brand_id Foreign key Brand. + * @property boolean $new_value + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Customer $customer Customer BelongsTo relation. + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + */ class CustomerOptInHistory extends Model { use HasFactory; @@ -16,24 +27,33 @@ class CustomerOptInHistory extends Model */ protected $table = "customer_opt_in_history"; + /** + * The attributes that should be cast to native types. + * + * @var array + */ + protected $casts = [ + 'new_value' => 'bool', + ]; + // * RELATIONS /** - * Customer BelongsTo relation + * Customer BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function customer() + public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Customer::class); } /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } diff --git a/app/Models/Question.php b/app/Models/Question.php index 35d155a..c409ff8 100644 --- a/app/Models/Question.php +++ b/app/Models/Question.php @@ -6,6 +6,17 @@ use App\Enums\QuestionType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @property integer $id Id. + * @property integer $brand_id Foreign key Brand. + * @property string $question Question. + * @property \App\Enums\QuestionType $type Type de question. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Answer $answer Answer BelongsTo relation. + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + */ class Question extends Model { use HasFactory; @@ -33,21 +44,21 @@ class Question extends Model // * RELATIONS /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } /** - * Answer Has many relation + * Answer Has many relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function answers() + public function answers(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Answer::class); } diff --git a/app/Models/Service.php b/app/Models/Service.php index 3c4a8e1..9dec3bd 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -12,8 +12,21 @@ use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; use Rennokki\QueryCache\Traits\QueryCacheable; -use RuntimeException; +/** + * @property integer $id Id. + * @property integer $brand_id Foreign key Brand. + * @property string $name Nom. + * @property string $bearer_token + * @property string $bearer_pwd + * @property \Carbon\Carbon $bearer_token_date + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + * @property-read \Illuminate\Database\Eloquent\Collection + * $servicesStats ServiceStat HasMany relation. + */ class Service extends Model { use HasFactory; @@ -37,7 +50,16 @@ class Service extends Model ]; /** - * Service + * The attributes that should be cast to native types. + * + * @var array + */ + protected $casts = [ + 'bearer_token_date' => 'datetime', + ]; + + /** + * Service. * * @param array $attributes */ @@ -47,27 +69,13 @@ class Service extends Model $this->cacheFor = config('api.servicesDBCache') * 60; } - /** - * 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', - ]; - } /** * Perform any actions required after the model boots. * * @return void */ - protected static function booted() + protected static function booted(): void { static::creating(function (self $model) { $model->generateBearerToken(); @@ -90,21 +98,21 @@ class Service extends Model // * RELATIONS /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } /** - * ServiceStat hasMany relation + * ServiceStat hasMany relation. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function servicesStats() + public function servicesStats(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(ServiceStat::class); } @@ -112,7 +120,22 @@ class Service extends Model // * METHODS /** - * Generates new bearer token + * 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', + ]; + } + + /** + * Generates new bearer token. * * @return string */ @@ -126,7 +149,7 @@ class Service extends Model /** * Generates new bearer password * It store the crypted password in model - * and returns the plain password + * and returns the plain password. * * @return string */ @@ -138,7 +161,7 @@ class Service extends Model } /** - * Increment API CRM Stat + * Increment API CRM Stat. * * @return void */ @@ -149,9 +172,7 @@ class Service extends Model } /** - * Get Service from bearerToken with its brand - * - * TODO remove this method when dropping V1 + * Get Service from bearerToken with its brand. * * @param string $bearerToken * @return \App\Models\Service @@ -162,11 +183,12 @@ class Service extends Model $services = static::all(); /** @var \App\Models\Service $service */ foreach ($services as $service) { - // * Keep compatibility with V3. $tokenEquals = false; try { $tokenEquals = Hash::check($bearerToken, $service->bearer_token); - } catch (RuntimeException $e) { + } catch (\RuntimeException) { + // * Keep compatibility with V1. + // TODO: Remove this method when no more service will be in V1 (hashed instead of encrypted). $tokenEquals = $bearerToken === $service->bearer_token; } if ($service->bearer_token and $tokenEquals) { @@ -208,11 +230,9 @@ class Service extends Model * * * ! They should then be concat in a string separated by \n * - * - * * @param \Illuminate\Http\Request $request * @return boolean - * @throws \App\Exceptions\Api\V2\CrmException If an error occurs. + * @throws \App\Exceptions\Api\V4\CrmException If an error occurs. */ public function authenticateApi(Request $request): bool { diff --git a/app/Models/ServiceStat.php b/app/Models/ServiceStat.php index fd05dca..e3941bd 100644 --- a/app/Models/ServiceStat.php +++ b/app/Models/ServiceStat.php @@ -5,6 +5,18 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @property integer $id Id. + * @property integer $brand_id Foreign key Brand. + * @property integer $service_id Foreign key Service. + * @property \Illuminate\Support\Carbon $date Date. + * @property integer $nb_calls Nombre d'appels. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + * + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + * @property-read \App\Models\Service $service Service BelongsTo relation. + */ class ServiceStat extends Model { use HasFactory; @@ -27,24 +39,33 @@ class ServiceStat extends Model 'nb_calls' => 0, ]; + /** + * Map attribute names to enum classes. + * + * @var array + */ + protected $casts = [ + 'date' => 'date', + ]; + // * RELATIONS /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } /** - * Service BelongsTo relation + * Service BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function service() + public function service(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Service::class); } diff --git a/app/Models/SingleOptin.php b/app/Models/SingleOptin.php index 91676be..813c0f8 100644 --- a/app/Models/SingleOptin.php +++ b/app/Models/SingleOptin.php @@ -6,6 +6,15 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; +/** + * @property integer $hashcode Hashcode. + * @property integer $brand_id Foreign key Brand. + * @property string $email Email. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + + * @property-read \App\Models\Brand $brand Brand BelongsTo relation. + */ class SingleOptin extends Model { use HasFactory; @@ -45,7 +54,7 @@ class SingleOptin extends Model * * @return void */ - protected static function booted() + protected static function booted(): void { static::creating(function (self $model) { $model->hashcode = Str::uuid(); @@ -55,11 +64,11 @@ class SingleOptin extends Model // * RELATIONS /** - * Brand BelongsTo relation + * Brand BelongsTo relation. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function brand() + public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Brand::class); } diff --git a/app/Models/User.php b/app/Models/User.php index 244c32e..6c63c29 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,6 +8,16 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; +/** + * @property integer $id Id. + * @property string $lastname Nom. + * @property string $firstname Prénom. + * @property string $email Email. + * @property string $password Mot de passe. + * @property string|null $remember_token Token mot de passe. + * @property-read \Illuminate\Support\Carbon $created_at Created date. + * @property-read \Illuminate\Support\Carbon $updated_at Updated date. + */ class User extends Authenticatable { use HasFactory; @@ -35,21 +45,12 @@ class User extends Authenticatable 'remember_token', ]; - /** - * The attributes that should be cast to native types. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; - /** * Perform any actions required after the model boots. * * @return void */ - protected static function booted() + protected static function booted(): void { // * EVENT CLOSURES. static::creating(function (self $model) { diff --git a/app/Policies/AnswerPolicy.php b/app/Policies/AnswerPolicy.php index 7776113..446e1cd 100644 --- a/app/Policies/AnswerPolicy.php +++ b/app/Policies/AnswerPolicy.php @@ -4,9 +4,6 @@ namespace App\Policies; use App\Policies\Rules\UserStaticRules as USR; -/** - * @codeCoverageIgnore - */ class AnswerPolicy { /** @@ -20,7 +17,8 @@ class AnswerPolicy } /** - * viewAny an Answer. + * ViewAny an Answer. + * * @return boolean */ public function viewAny(): bool diff --git a/app/Policies/BrandPolicy.php b/app/Policies/BrandPolicy.php index 5135c4f..1dd93fc 100644 --- a/app/Policies/BrandPolicy.php +++ b/app/Policies/BrandPolicy.php @@ -4,9 +4,6 @@ namespace App\Policies; use App\Policies\Rules\UserStaticRules as USR; -/** - * @codeCoverageIgnore - */ class BrandPolicy { /** @@ -20,7 +17,8 @@ class BrandPolicy } /** - * viewAny a Brand. + * ViewAny a Brand. + * * @return boolean */ public function viewAny(): bool diff --git a/app/Policies/CustomerPolicy.php b/app/Policies/CustomerPolicy.php index a52d997..a15a1cd 100644 --- a/app/Policies/CustomerPolicy.php +++ b/app/Policies/CustomerPolicy.php @@ -7,9 +7,6 @@ use App\Models\Service; use Illuminate\Auth\Access\HandlesAuthorization; use App\Policies\Rules\UserStaticRules as USR; -/** - * @codeCoverageIgnore - */ class CustomerPolicy { use HandlesAuthorization; @@ -65,7 +62,7 @@ class CustomerPolicy } /** - * Check if the customer belongs to the service + * Check if the customer belongs to the service. * * @param \App\Models\Service $service * @param \App\Models\Customer $customer diff --git a/app/Policies/QuestionPolicy.php b/app/Policies/QuestionPolicy.php index 80ff224..9a55808 100644 --- a/app/Policies/QuestionPolicy.php +++ b/app/Policies/QuestionPolicy.php @@ -6,9 +6,6 @@ use App\Models\Question; use App\Models\Service; use App\Policies\Rules\UserStaticRules as USR; -/** - * @codeCoverageIgnore - */ class QuestionPolicy { /** @@ -22,7 +19,8 @@ class QuestionPolicy } /** - * viewAny a Question. + * ViewAny a Question. + * * @return boolean */ public function viewAny(): bool @@ -61,7 +59,7 @@ class QuestionPolicy } /** - * Check if the question belongs to the service + * Check if the question belongs to the service. * * @param \App\Models\Service $service * @param \App\Models\Question $question diff --git a/app/Policies/Rules/UserStaticRules.php b/app/Policies/Rules/UserStaticRules.php index 1e00ba6..cafb34e 100644 --- a/app/Policies/Rules/UserStaticRules.php +++ b/app/Policies/Rules/UserStaticRules.php @@ -5,12 +5,12 @@ namespace App\Policies\Rules; /** * Userlevel Policiy Static Rules * - * @codeCoverageIgnore + * */ abstract class UserStaticRules { /** - * Allow everyone + * Allow everyone. * * @return boolean */ @@ -19,7 +19,7 @@ abstract class UserStaticRules return true; } /** - * Allow no one + * Allow no one. * * @return boolean */ diff --git a/app/Policies/ServicePolicy.php b/app/Policies/ServicePolicy.php index bbde246..77d3c36 100644 --- a/app/Policies/ServicePolicy.php +++ b/app/Policies/ServicePolicy.php @@ -4,9 +4,6 @@ namespace App\Policies; use App\Policies\Rules\UserStaticRules as USR; -/** - * @codeCoverageIgnore - */ class ServicePolicy { /** @@ -20,7 +17,7 @@ class ServicePolicy } /** - * viewAny an Service. + * ViewAny an Service. * * @return boolean */ diff --git a/app/Policies/SingleOptinPolicy.php b/app/Policies/SingleOptinPolicy.php index 00e379a..a9cd08d 100644 --- a/app/Policies/SingleOptinPolicy.php +++ b/app/Policies/SingleOptinPolicy.php @@ -4,9 +4,6 @@ namespace App\Policies; use App\Policies\Rules\UserStaticRules as USR; -/** - * @codeCoverageIgnore - */ class SingleOptinPolicy { /** @@ -20,7 +17,8 @@ class SingleOptinPolicy } /** - * viewAny an Answer. + * ViewAny an Answer. + * * @return boolean */ public function viewAny(): bool diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index eff81f4..ea8d022 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -10,9 +10,6 @@ use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; -/** - * @codeCoverageIgnore - */ class AppServiceProvider extends ServiceProvider { /** @@ -20,7 +17,7 @@ class AppServiceProvider extends ServiceProvider * * @return void */ - public function boot() + public function boot(): void { // * STATS jobs Rate Limiting. RateLimiter::for('stats', function () { @@ -39,7 +36,7 @@ class AppServiceProvider extends ServiceProvider return request()->getClientIp(); } else { return request()->server('HTTP_X_FORWARDED_FOR') ?? - request()->getClientIp(); + request()->getClientIp(); } }); // * LOCAL FORCE eg: for Carbon. diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index f2942eb..c6de1d8 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -23,7 +23,7 @@ class AuthServiceProvider extends ServiceProvider * * @return void */ - public function boot() + public function boot(): void { // Register policies. $this->registerPolicies(); @@ -57,7 +57,7 @@ class AuthServiceProvider extends ServiceProvider $service = $service->load('brand'); /** - ** All ApiV2 routes must pass Brand model to controller action since a Brand owns + ** All ApiV4 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 diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 5d3e6d6..b287163 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -10,6 +10,7 @@ use App\Models\Question; use App\Models\Service; use App\Models\SingleOptin; use Illuminate\Cache\RateLimiting\Limit; +use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Http\Request; @@ -32,7 +33,7 @@ class RouteServiceProvider extends ServiceProvider * * @return void */ - public function boot() + public function boot(): void { $this->configureRateLimiting(); @@ -59,7 +60,7 @@ class RouteServiceProvider extends ServiceProvider // ? Parameter is email if (strpos($value, '@') !== false) { - $customer = Customer::whereHas('brands', function ($q) use ($brand) { + $customer = Customer::query()->whereHas('brands', function (Builder $q) use ($brand) { $q->where('brands.id', '=', $brand->id); })->where('email', '=', $value)->firstOrFail(); } @@ -68,7 +69,7 @@ class RouteServiceProvider extends ServiceProvider // ? Have we found a customer yet if (!isset($customer) or \get_class($customer) !== Customer::class) { // ? Parameter is hashcode - $customer = CustomerInfo::whereHas('brand', function ($q) use ($brand) { + $customer = CustomerInfo::query()->whereHas('brand', function (Builder $q) use ($brand) { $q->where('brands.id', '=', $brand->id); })->where('hashcode', $value) ->with('customer')->firstOrFail()->customer; @@ -90,7 +91,7 @@ class RouteServiceProvider extends ServiceProvider * * @return void */ - protected function configureRateLimiting() + protected function configureRateLimiting(): void { RateLimiter::for('web', function (Request $request) { // @codeCoverageIgnoreStart @@ -101,7 +102,7 @@ class RouteServiceProvider extends ServiceProvider // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass RateLimiter::for('api', function (Request $request) { return Limit::perMinute(config('api.apiRequestRatePerMinute', 100)) - ->by(app('clientIp')); + ->by(app('clientIp')); }); } } diff --git a/app/Providers/TranslationServiceProvider.php b/app/Providers/TranslationServiceProvider.php index 757546c..5401859 100644 --- a/app/Providers/TranslationServiceProvider.php +++ b/app/Providers/TranslationServiceProvider.php @@ -14,7 +14,7 @@ class TranslationServiceProvider extends ServiceProvider * * @return void */ - public function boot() + public function boot(): void { foreach ($this->collectLocalesStrings() as $locale) { // * Suported locales @@ -29,7 +29,7 @@ class TranslationServiceProvider extends ServiceProvider } /** - * Gather provided langs checking from files + * Gather provided langs checking from files. * * @return \Illuminate\Support\Collection */ @@ -43,7 +43,7 @@ class TranslationServiceProvider extends ServiceProvider } /** - * Gather all php translations + * Gather all php translations. * * @param string $locale * @return array @@ -59,7 +59,7 @@ class TranslationServiceProvider extends ServiceProvider } /** - * Gather json translations + * Gather json translations. * * @param string $locale * @return array diff --git a/app/Traits/Enums/BaseEnum.php b/app/Traits/Enums/BaseEnum.php index 36c7b35..c44dbee 100644 --- a/app/Traits/Enums/BaseEnum.php +++ b/app/Traits/Enums/BaseEnum.php @@ -8,7 +8,7 @@ trait BaseEnum { // * GENERIC METHODS /** - * Get enum value + * Get enum value. * * @return string|integer */ @@ -18,7 +18,7 @@ trait BaseEnum } /** - * Get enum label + * Get enum label. * * @return string */ @@ -28,7 +28,7 @@ trait BaseEnum } /** - * Get enum name + * Get enum name. * * @return string */ @@ -40,7 +40,7 @@ trait BaseEnum // * STATIC FETCH METHODS /** - * Get enum labels + * Get enum labels. * * @return array */ @@ -62,7 +62,7 @@ trait BaseEnum } /** - * Get enum values + * Get enum values. * * @return array */ @@ -76,7 +76,7 @@ trait BaseEnum } /** - * Get enums + * Get enums. * * @return array */ @@ -86,7 +86,7 @@ trait BaseEnum } /** - * Get enums + * Get enums. * * @return array */ @@ -115,19 +115,20 @@ trait BaseEnum } /** - * Fetch enum using value + * Fetch enum using value. * * @param \App\Traits\Enums\BaseEnum|integer|string $value + * @param string $field * @return \App\Traits\Enums\BaseEnum|null */ - public static function make(self|int|string $value): self|null + public static function make(self|int|string $value, string $field = 'value'): self|null { return !\is_object($value) ? - \collect(self::cases())->keyBy('value')->get($value) : $value; + \collect(self::cases())->keyBy($field)->get($value) : $value; } /** - * Test if 2 enum are equals + * Test if 2 enum are equals. * * @param object ...$others * @return boolean @@ -151,7 +152,7 @@ trait BaseEnum } /** - * Serialize to json + * Serialize to json. * * @return mixed */ @@ -164,7 +165,7 @@ trait BaseEnum // * CACHE /** - * Get cache array + * Get cache array. * * @param string $index * @return array @@ -179,7 +180,7 @@ trait BaseEnum } /** - * Set cache array + * Set cache array. * * @param string $index * @param array $items @@ -195,7 +196,7 @@ trait BaseEnum } /** - * Init cache if needed + * Init cache if needed. * * @return void */ @@ -216,7 +217,7 @@ trait BaseEnum // * PRIVATE /** - * Fetch the LABELS constant if exists + * Fetch the LABELS constant if exists. * * @return array|null */ @@ -232,7 +233,7 @@ trait BaseEnum } /** - * Get custom constants + * Get custom constants. * * @return array */ diff --git a/clients/V2 b/clients/V2 deleted file mode 160000 index b15e9bc..0000000 --- a/clients/V2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b15e9bcb59f4680a736e34dabdb059682fc7b8bc diff --git a/composer.json b/composer.json index e7088e6..d264615 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "ldc/ldccrm", - "version": "3.0.6", + "version": "4.0.0", "type": "project", "description": "LDC CRM", "license": "MIT", @@ -11,8 +11,11 @@ "framework", "laravel" ], + "platform": { + "php": "^8.2" + }, "require": { - "php": "^8.1", + "php": "^8.2", "ext-curl": "*", "ext-dom": "*", "ext-json": "*", @@ -20,7 +23,7 @@ "axlon/laravel-postal-code-validation": "^3.2", "diglactic/laravel-breadcrumbs": "^8.1", "doctrine/dbal": "^3.0", - "guzzlehttp/guzzle": "^7.2", + "guzzlehttp/guzzle": "^7.4", "laravel-lang/lang": "~7.0", "laravel/framework": "^10.0", "laravel/tinker": "^2.8", @@ -62,8 +65,7 @@ "Database\\Factories\\": "database/factories/", "Database\\migrations\\": "database/migrations/", "Database\\Seeders\\": "database/seeders/", - "LDC\\LDCCRM\\": "clients/V2/src/", - "LDCCRM\\": "clients/" + "LDC\\LDCCRM\\": "client/src/" } }, "autoload-dev": { @@ -88,8 +90,11 @@ "exportclear": "rm -f storage/export/arcadia/*.xml 2> /dev/null", "configclear": "@php artisan config:clear", "runtestmigration": "@php artisan migrate:fresh --env=testing --seed", - "runtest": "@php artisan test --no-coverage --testsuite CRM,Client", - "runtestWithCoverage": "@php artisan test --testsuite CRM,Client", + "runtestCRMV2": "@php artisan test --env=testing --no-coverage --testsuite CRMV2", + "runtestCRMV4": "@php artisan test --env=testing --no-coverage --testsuite CRMV4", + "runtestClient": "@php artisan test --no-coverage --env=testing --testsuite Client", + "runtestV4": "@php artisan test --env=testing --no-coverage --testsuite CRMV2,CRMV4,Client", + "runtestWithCoverageV4": "@php -d pcov.enabled=1 artisan test --env=testing --coverage --testsuite CRMV2,CRMV4,Client", "runserve": "@php artisan serve -q --host=127.0.0.1 --port=8888 --env=testing&", "killserve": "kill $(ps -f | grep '[1]27.0.0.1:8888' | awk '{print $2}')", "post-autoload-dump": [ diff --git a/composer.lock b/composer.lock index 06d4b58..43d44d7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51de564899fd64dcbe6c0e1ec5bc0b55", + "content-hash": "9b2d8175bcd0a0bc97775df317340b49", "packages": [ { "name": "arondeparon/laravel-request-sanitizer", @@ -498,16 +498,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.6", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "63646ffd71d1676d2f747f871be31b7e921c7864" + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/63646ffd71d1676d2f747f871be31b7e921c7864", - "reference": "63646ffd71d1676d2f747f871be31b7e921c7864", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", "shasum": "" }, "require": { @@ -523,9 +523,9 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.29", + "phpstan/phpstan": "1.10.35", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.9", + "phpunit/phpunit": "9.6.13", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.7.2", @@ -591,7 +591,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.6" + "source": "https://github.com/doctrine/dbal/tree/3.7.0" }, "funding": [ { @@ -607,20 +607,20 @@ "type": "tidelift" } ], - "time": "2023-08-17T05:38:17+00:00" + "time": "2023-09-26T20:56:55+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", "shasum": "" }, "require": { @@ -652,9 +652,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.2" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2023-09-27T20:04:15+00:00" }, { "name": "doctrine/event-manager", @@ -1169,16 +1169,16 @@ }, { "name": "giggsey/libphonenumber-for-php", - "version": "8.13.19", + "version": "8.13.22", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php.git", - "reference": "7b60d1264ba806e68fb99b06e73e2ed07815689e" + "reference": "fc69cbcaff14c1af1f935b79033bdc5c0fa4a846" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/7b60d1264ba806e68fb99b06e73e2ed07815689e", - "reference": "7b60d1264ba806e68fb99b06e73e2ed07815689e", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/fc69cbcaff14c1af1f935b79033bdc5c0fa4a846", + "reference": "fc69cbcaff14c1af1f935b79033bdc5c0fa4a846", "shasum": "" }, "require": { @@ -1237,7 +1237,7 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php/issues", "source": "https://github.com/giggsey/libphonenumber-for-php" }, - "time": "2023-08-22T13:59:44+00:00" + "time": "2023-09-29T14:53:55+00:00" }, { "name": "giggsey/locale", @@ -1924,16 +1924,16 @@ }, { "name": "laravel/framework", - "version": "v10.21.0", + "version": "v10.26.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "96b15c7ac382a9adb4a56d40c640e782d669a112" + "reference": "6e5440f7c518f26b4495e5d7e4796ec239e26df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/96b15c7ac382a9adb4a56d40c640e782d669a112", - "reference": "96b15c7ac382a9adb4a56d40c640e782d669a112", + "url": "https://api.github.com/repos/laravel/framework/zipball/6e5440f7c518f26b4495e5d7e4796ec239e26df9", + "reference": "6e5440f7c518f26b4495e5d7e4796ec239e26df9", "shasum": "" }, "require": { @@ -1951,7 +1951,7 @@ "ext-tokenizer": "*", "fruitcake/php-cors": "^1.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1", + "laravel/prompts": "^0.1.9", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", @@ -2033,7 +2033,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^8.4", + "orchestra/testbench-core": "^8.12", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", @@ -2120,20 +2120,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-08-29T13:55:56+00:00" + "time": "2023-10-03T14:24:20+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.6", + "version": "v0.1.11", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "b514c5620e1b3b61221b0024dc88def26d9654f4" + "reference": "cce65a90e64712909ea1adc033e1d88de8455ffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/b514c5620e1b3b61221b0024dc88def26d9654f4", - "reference": "b514c5620e1b3b61221b0024dc88def26d9654f4", + "url": "https://api.github.com/repos/laravel/prompts/zipball/cce65a90e64712909ea1adc033e1d88de8455ffd", + "reference": "cce65a90e64712909ea1adc033e1d88de8455ffd", "shasum": "" }, "require": { @@ -2142,6 +2142,10 @@ "php": "^8.1", "symfony/console": "^6.2" }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, "require-dev": { "mockery/mockery": "^1.5", "pestphp/pest": "^2.3", @@ -2152,6 +2156,11 @@ "ext-pcntl": "Required for the spinner to be animated." }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, "autoload": { "files": [ "src/helpers.php" @@ -2166,9 +2175,9 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.6" + "source": "https://github.com/laravel/prompts/tree/v0.1.11" }, - "time": "2023-08-18T13:32:23+00:00" + "time": "2023-10-03T01:07:35+00:00" }, { "name": "laravel/serializable-closure", @@ -2489,16 +2498,16 @@ }, { "name": "league/csv", - "version": "9.10.0", + "version": "9.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "d24b0d484812313b07ab74b0fe4db9661606df6c" + "reference": "33149c4bea4949aa4fa3d03fb11ed28682168b39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/d24b0d484812313b07ab74b0fe4db9661606df6c", - "reference": "d24b0d484812313b07ab74b0fe4db9661606df6c", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/33149c4bea4949aa4fa3d03fb11ed28682168b39", + "reference": "33149c4bea4949aa4fa3d03fb11ed28682168b39", "shasum": "" }, "require": { @@ -2573,20 +2582,20 @@ "type": "github" } ], - "time": "2023-08-04T15:12:48+00:00" + "time": "2023-09-23T10:09:54+00:00" }, { "name": "league/flysystem", - "version": "3.15.1", + "version": "3.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "a141d430414fcb8bf797a18716b09f759a385bed" + "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a141d430414fcb8bf797a18716b09f759a385bed", - "reference": "a141d430414fcb8bf797a18716b09f759a385bed", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729", + "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729", "shasum": "" }, "require": { @@ -2595,6 +2604,8 @@ "php": "^8.0.2" }, "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "guzzlehttp/guzzle": "<7.0", "guzzlehttp/ringphp": "<1.1.1", @@ -2614,7 +2625,7 @@ "microsoft/azure-storage-blob": "^1.1", "phpseclib/phpseclib": "^3.0.14", "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^9.5.11", + "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, "type": "library", @@ -2649,7 +2660,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.15.1" + "source": "https://github.com/thephpleague/flysystem/tree/3.16.0" }, "funding": [ { @@ -2661,20 +2672,20 @@ "type": "github" } ], - "time": "2023-05-04T09:04:26+00:00" + "time": "2023-09-07T19:22:17+00:00" }, { "name": "league/flysystem-local", - "version": "3.15.0", + "version": "3.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3" + "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/543f64c397fefdf9cfeac443ffb6beff602796b3", - "reference": "543f64c397fefdf9cfeac443ffb6beff602796b3", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ec7383f25642e6fd4bb0c9554fc2311245391781", + "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781", "shasum": "" }, "require": { @@ -2709,7 +2720,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.15.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.16.0" }, "funding": [ { @@ -2721,7 +2732,7 @@ "type": "github" } ], - "time": "2023-05-02T20:02:14+00:00" + "time": "2023-08-30T10:23:59+00:00" }, { "name": "league/mime-type-detection", @@ -2882,16 +2893,16 @@ }, { "name": "nesbot/carbon", - "version": "2.69.0", + "version": "2.71.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "4308217830e4ca445583a37d1bf4aff4153fa81c" + "reference": "98276233188583f2ff845a0f992a235472d9466a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4308217830e4ca445583a37d1bf4aff4153fa81c", - "reference": "4308217830e4ca445583a37d1bf4aff4153fa81c", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/98276233188583f2ff845a0f992a235472d9466a", + "reference": "98276233188583f2ff845a0f992a235472d9466a", "shasum": "" }, "require": { @@ -2984,7 +2995,7 @@ "type": "tidelift" } ], - "time": "2023-08-03T09:00:52+00:00" + "time": "2023-09-25T11:31:05+00:00" }, { "name": "nette/schema", @@ -3050,16 +3061,16 @@ }, { "name": "nette/utils", - "version": "v4.0.1", + "version": "v4.0.2", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "9124157137da01b1f5a5a22d6486cb975f26db7e" + "reference": "cead6637226456b35e1175cc53797dd585d85545" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/9124157137da01b1f5a5a22d6486cb975f26db7e", - "reference": "9124157137da01b1f5a5a22d6486cb975f26db7e", + "url": "https://api.github.com/repos/nette/utils/zipball/cead6637226456b35e1175cc53797dd585d85545", + "reference": "cead6637226456b35e1175cc53797dd585d85545", "shasum": "" }, "require": { @@ -3081,8 +3092,7 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { @@ -3131,9 +3141,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.1" + "source": "https://github.com/nette/utils/tree/v4.0.2" }, - "time": "2023-07-30T15:42:21+00:00" + "time": "2023-09-19T11:58:07+00:00" }, { "name": "nikic/php-parser", @@ -4089,16 +4099,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -4135,9 +4145,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", @@ -4350,16 +4360,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.20", + "version": "v0.11.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "0fa27040553d1d280a67a4393194df5228afea5b" + "reference": "bcb22101107f3bf770523b65630c9d547f60c540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0fa27040553d1d280a67a4393194df5228afea5b", - "reference": "0fa27040553d1d280a67a4393194df5228afea5b", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/bcb22101107f3bf770523b65630c9d547f60c540", + "reference": "bcb22101107f3bf770523b65630c9d547f60c540", "shasum": "" }, "require": { @@ -4389,6 +4399,10 @@ "extra": { "branch-alias": { "dev-main": "0.11.x-dev" + }, + "bamarni-bin": { + "bin-links": false, + "forward-command": false } }, "autoload": { @@ -4420,9 +4434,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.20" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.21" }, - "time": "2023-07-31T14:32:22+00:00" + "time": "2023-09-17T21:15:54+00:00" }, { "name": "ralouphie/getallheaders", @@ -4874,16 +4888,16 @@ }, { "name": "sentry/sentry-laravel", - "version": "3.7.3", + "version": "3.8.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "2aee4ad217be8ef04ffcde6e9f7dd17af5a3b0bf" + "reference": "b6142a80fa9360a10b786d2da032339602d0e362" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2aee4ad217be8ef04ffcde6e9f7dd17af5a3b0bf", - "reference": "2aee4ad217be8ef04ffcde6e9f7dd17af5a3b0bf", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/b6142a80fa9360a10b786d2da032339602d0e362", + "reference": "b6142a80fa9360a10b786d2da032339602d0e362", "shasum": "" }, "require": { @@ -4896,6 +4910,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.11", + "laravel/folio": "^1.0", "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", "mockery/mockery": "^1.3", "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0", @@ -4949,7 +4964,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/3.7.3" + "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.1" }, "funding": [ { @@ -4961,7 +4976,7 @@ "type": "custom" } ], - "time": "2023-08-03T10:10:23+00:00" + "time": "2023-10-04T10:21:16+00:00" }, { "name": "symfony/console", @@ -5187,16 +5202,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a" + "reference": "1f69476b64fb47105c06beef757766c376b548c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/85fd65ed295c4078367c784e8a5a6cee30348b7a", - "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/1f69476b64fb47105c06beef757766c376b548c4", + "reference": "1f69476b64fb47105c06beef757766c376b548c4", "shasum": "" }, "require": { @@ -5241,7 +5256,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.3.2" + "source": "https://github.com/symfony/error-handler/tree/v6.3.5" }, "funding": [ { @@ -5257,7 +5272,7 @@ "type": "tidelift" } ], - "time": "2023-07-16T17:05:46+00:00" + "time": "2023-09-12T06:57:20+00:00" }, { "name": "symfony/event-dispatcher", @@ -5417,16 +5432,16 @@ }, { "name": "symfony/finder", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e" + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e", - "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e", + "url": "https://api.github.com/repos/symfony/finder/zipball/a1b31d88c0e998168ca7792f222cbecee47428c4", + "reference": "a1b31d88c0e998168ca7792f222cbecee47428c4", "shasum": "" }, "require": { @@ -5461,7 +5476,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.3.3" + "source": "https://github.com/symfony/finder/tree/v6.3.5" }, "funding": [ { @@ -5477,20 +5492,20 @@ "type": "tidelift" } ], - "time": "2023-07-31T08:31:44+00:00" + "time": "2023-09-26T12:56:25+00:00" }, { "name": "symfony/http-client", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00" + "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", - "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", + "url": "https://api.github.com/repos/symfony/http-client/zipball/213e564da4cbf61acc9728d97e666bcdb868c10d", + "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d", "shasum": "" }, "require": { @@ -5553,7 +5568,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.2" + "source": "https://github.com/symfony/http-client/tree/v6.3.5" }, "funding": [ { @@ -5569,7 +5584,7 @@ "type": "tidelift" } ], - "time": "2023-07-05T08:41:27+00:00" + "time": "2023-09-29T15:57:12+00:00" }, { "name": "symfony/http-client-contracts", @@ -5651,16 +5666,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "cac1556fdfdf6719668181974104e6fcfa60e844" + "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/cac1556fdfdf6719668181974104e6fcfa60e844", - "reference": "cac1556fdfdf6719668181974104e6fcfa60e844", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b50f5e281d722cb0f4c296f908bacc3e2b721957", + "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957", "shasum": "" }, "require": { @@ -5708,7 +5723,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.4" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.5" }, "funding": [ { @@ -5724,20 +5739,20 @@ "type": "tidelift" } ], - "time": "2023-08-22T08:20:46+00:00" + "time": "2023-09-04T21:33:54+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb" + "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", - "reference": "36abb425b4af863ae1fe54d8a8b8b4c76a2bccdb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f991a964368bee8d883e8d57ced4fe9fff04dfc", + "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc", "shasum": "" }, "require": { @@ -5821,7 +5836,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.3.4" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.5" }, "funding": [ { @@ -5837,20 +5852,20 @@ "type": "tidelift" } ], - "time": "2023-08-26T13:54:49+00:00" + "time": "2023-09-30T06:37:04+00:00" }, { "name": "symfony/mailer", - "version": "v6.3.0", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", - "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "url": "https://api.github.com/repos/symfony/mailer/zipball/d89611a7830d51b5e118bca38e390dea92f9ea06", + "reference": "d89611a7830d51b5e118bca38e390dea92f9ea06", "shasum": "" }, "require": { @@ -5901,7 +5916,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.3.0" + "source": "https://github.com/symfony/mailer/tree/v6.3.5" }, "funding": [ { @@ -5917,20 +5932,20 @@ "type": "tidelift" } ], - "time": "2023-05-29T12:49:39+00:00" + "time": "2023-09-06T09:47:15+00:00" }, { "name": "symfony/mime", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98" + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", - "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", + "url": "https://api.github.com/repos/symfony/mime/zipball/d5179eedf1cb2946dbd760475ebf05c251ef6a6e", + "reference": "d5179eedf1cb2946dbd760475ebf05c251ef6a6e", "shasum": "" }, "require": { @@ -5985,7 +6000,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.3.3" + "source": "https://github.com/symfony/mime/tree/v6.3.5" }, "funding": [ { @@ -6001,7 +6016,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-09-29T06:59:36+00:00" }, { "name": "symfony/options-resolver", @@ -6960,16 +6975,16 @@ }, { "name": "symfony/routing", - "version": "v6.3.3", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a" + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/e7243039ab663822ff134fbc46099b5fdfa16f6a", - "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a", + "url": "https://api.github.com/repos/symfony/routing/zipball/82616e59acd3e3d9c916bba798326cb7796d7d31", + "reference": "82616e59acd3e3d9c916bba798326cb7796d7d31", "shasum": "" }, "require": { @@ -7023,7 +7038,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.3.3" + "source": "https://github.com/symfony/routing/tree/v6.3.5" }, "funding": [ { @@ -7039,7 +7054,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-09-20T16:05:51+00:00" }, { "name": "symfony/service-contracts", @@ -7125,16 +7140,16 @@ }, { "name": "symfony/string", - "version": "v6.3.2", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "53d1a83225002635bca3482fcbf963001313fb68" + "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68", - "reference": "53d1a83225002635bca3482fcbf963001313fb68", + "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339", + "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339", "shasum": "" }, "require": { @@ -7191,7 +7206,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.2" + "source": "https://github.com/symfony/string/tree/v6.3.5" }, "funding": [ { @@ -7207,7 +7222,7 @@ "type": "tidelift" } ], - "time": "2023-07-05T08:41:27+00:00" + "time": "2023-09-18T10:38:32+00:00" }, { "name": "symfony/translation", @@ -7458,16 +7473,16 @@ }, { "name": "symfony/var-dumper", - "version": "v6.3.4", + "version": "v6.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45" + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2027be14f8ae8eae999ceadebcda5b4909b81d45", - "reference": "2027be14f8ae8eae999ceadebcda5b4909b81d45", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5", "shasum": "" }, "require": { @@ -7522,7 +7537,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.5" }, "funding": [ { @@ -7538,7 +7553,7 @@ "type": "tidelift" } ], - "time": "2023-08-24T14:51:05+00:00" + "time": "2023-09-12T10:11:35+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7813,16 +7828,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.2.6", + "version": "v7.2.8", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "7f372b5bb59b4271adedc67d3129df29b84c4173" + "reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/7f372b5bb59b4271adedc67d3129df29b84c4173", - "reference": "7f372b5bb59b4271adedc67d3129df29b84c4173", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/882b02d197328138686bb06ce7d8cbb98fc0a16c", + "reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c", "shasum": "" }, "require": { @@ -7892,7 +7907,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.2.6" + "source": "https://github.com/paratestphp/paratest/tree/v7.2.8" }, "funding": [ { @@ -7904,7 +7919,7 @@ "type": "paypal" } ], - "time": "2023-08-29T07:47:39+00:00" + "time": "2023-10-04T13:38:04+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -8381,37 +8396,37 @@ }, { "name": "nunomaduro/collision", - "version": "v7.8.1", + "version": "v7.9.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "61553ad3260845d7e3e49121b7074619233d361b" + "reference": "296d0cf9fe462837ac0da8a568b56fc026b132da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/61553ad3260845d7e3e49121b7074619233d361b", - "reference": "61553ad3260845d7e3e49121b7074619233d361b", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/296d0cf9fe462837ac0da8a568b56fc026b132da", + "reference": "296d0cf9fe462837ac0da8a568b56fc026b132da", "shasum": "" }, "require": { "filp/whoops": "^2.15.3", "nunomaduro/termwind": "^1.15.1", "php": "^8.1.0", - "symfony/console": "^6.3.2" + "symfony/console": "^6.3.4" }, "require-dev": { - "brianium/paratest": "^7.2.4", - "laravel/framework": "^10.17.1", - "laravel/pint": "^1.10.5", - "laravel/sail": "^1.23.1", - "laravel/sanctum": "^3.2.5", - "laravel/tinker": "^2.8.1", + "brianium/paratest": "^7.2.7", + "laravel/framework": "^10.23.1", + "laravel/pint": "^1.13.1", + "laravel/sail": "^1.25.0", + "laravel/sanctum": "^3.3.1", + "laravel/tinker": "^2.8.2", "nunomaduro/larastan": "^2.6.4", - "orchestra/testbench-core": "^8.5.9", - "pestphp/pest": "^2.12.1", - "phpunit/phpunit": "^10.3.1", + "orchestra/testbench-core": "^8.11.0", + "pestphp/pest": "^2.19.1", + "phpunit/phpunit": "^10.3.5", "sebastian/environment": "^6.0.1", - "spatie/laravel-ignition": "^2.2.0" + "spatie/laravel-ignition": "^2.3.0" }, "type": "library", "extra": { @@ -8470,7 +8485,7 @@ "type": "patreon" } ], - "time": "2023-08-07T08:03:21+00:00" + "time": "2023-09-19T10:45:09+00:00" }, { "name": "phar-io/manifest", @@ -8585,16 +8600,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.23.1", + "version": "1.24.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" + "reference": "bcad8d995980440892759db0c32acae7c8e79442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", - "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", + "reference": "bcad8d995980440892759db0c32acae7c8e79442", "shasum": "" }, "require": { @@ -8626,22 +8641,22 @@ "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/1.23.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" }, - "time": "2023-08-03T16:32:59+00:00" + "time": "2023-09-26T12:28:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.4", + "version": "10.1.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "cd59bb34756a16ca8253ce9b2909039c227fff71" + "reference": "355324ca4980b8916c18b9db29f3ef484078f26e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cd59bb34756a16ca8253ce9b2909039c227fff71", - "reference": "cd59bb34756a16ca8253ce9b2909039c227fff71", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e", + "reference": "355324ca4980b8916c18b9db29f3ef484078f26e", "shasum": "" }, "require": { @@ -8698,7 +8713,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.4" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7" }, "funding": [ { @@ -8706,7 +8721,7 @@ "type": "github" } ], - "time": "2023-08-31T14:04:38+00:00" + "time": "2023-10-04T15:34:17+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8953,16 +8968,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.3.2", + "version": "10.3.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0dafb1175c366dd274eaa9a625e914451506bcd1" + "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0dafb1175c366dd274eaa9a625e914451506bcd1", - "reference": "0dafb1175c366dd274eaa9a625e914451506bcd1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/747c3b2038f1139e3dcd9886a3f5a948648b7503", + "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503", "shasum": "" }, "require": { @@ -8976,7 +8991,7 @@ "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.1", + "phpunit/php-code-coverage": "^10.1.5", "phpunit/php-file-iterator": "^4.0", "phpunit/php-invoker": "^4.0", "phpunit/php-text-template": "^3.0", @@ -8986,7 +9001,7 @@ "sebastian/comparator": "^5.0", "sebastian/diff": "^5.0", "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.0", + "sebastian/exporter": "^5.1", "sebastian/global-state": "^6.0.1", "sebastian/object-enumerator": "^5.0", "sebastian/recursion-context": "^5.0", @@ -9034,7 +9049,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.2" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.5" }, "funding": [ { @@ -9050,7 +9065,7 @@ "type": "tidelift" } ], - "time": "2023-08-15T05:34:23+00:00" + "time": "2023-09-19T05:42:37+00:00" }, { "name": "respect/stringifier", @@ -9419,16 +9434,16 @@ }, { "name": "sebastian/complexity", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "c70b73893e10757af9c6a48929fa6a333b56a97a" + "reference": "68cfb347a44871f01e33ab0ef8215966432f6957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c70b73893e10757af9c6a48929fa6a333b56a97a", - "reference": "c70b73893e10757af9c6a48929fa6a333b56a97a", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68cfb347a44871f01e33ab0ef8215966432f6957", + "reference": "68cfb347a44871f01e33ab0ef8215966432f6957", "shasum": "" }, "require": { @@ -9441,7 +9456,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.1-dev" } }, "autoload": { @@ -9465,7 +9480,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.1.0" }, "funding": [ { @@ -9473,7 +9488,7 @@ "type": "github" } ], - "time": "2023-08-31T09:55:53+00:00" + "time": "2023-09-28T11:50:59+00:00" }, { "name": "sebastian/diff", @@ -9608,16 +9623,16 @@ }, { "name": "sebastian/exporter", - "version": "5.0.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0" + "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", - "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc", + "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc", "shasum": "" }, "require": { @@ -9631,7 +9646,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -9673,7 +9688,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1" }, "funding": [ { @@ -9681,7 +9697,7 @@ "type": "github" } ], - "time": "2023-02-03T07:06:49+00:00" + "time": "2023-09-24T13:22:09+00:00" }, { "name": "sebastian/global-state", diff --git a/package-lock.json b/package-lock.json index dab6a48..32d369b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "srcs", + "name": "crm", "lockfileVersion": 3, "requires": true, "packages": { @@ -37,7 +37,7 @@ "sass-loader": "^11.0.1", "slugify": "^1.6.6", "stylelint": "^14.16.1", - "stylelint-config-idiomatic-order": "^8.1.0", + "stylelint-config-idiomatic-order": "^9.0.0", "stylelint-config-sass-guidelines": "^9.0.1", "stylelint-config-standard-scss": "^6.1.0", "stylelint-scss": "^4.4.0", @@ -94,31 +94,31 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.20.tgz", + "integrity": "sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.15.tgz", - "integrity": "sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.15", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15", - "convert-source-map": "^1.7.0", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -142,12 +142,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -280,22 +280,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -314,12 +314,12 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", - "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -338,16 +338,16 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.15.tgz", - "integrity": "sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", "@babel/helper-simple-access": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.15" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -378,14 +378,14 @@ } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", - "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.9" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -395,13 +395,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { @@ -457,9 +457,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz", - "integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -475,40 +475,40 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz", - "integrity": "sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", "dev": true, "dependencies": { "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.10" + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -517,9 +517,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.15.tgz", - "integrity": "sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -873,9 +873,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz", - "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -960,9 +960,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz", - "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1148,12 +1148,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1164,12 +1164,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz", - "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1181,15 +1181,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", - "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.9", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1329,9 +1329,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz", - "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1580,12 +1580,12 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.15.tgz", - "integrity": "sha512-tZFHr54GBkHk6hQuVA8w4Fmq+MSPsfvMG0vPnOYyTnJpyfMqybL8/MbNCPRT9zc2KBO2pe4tq15g6Uno4Jpoag==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.20.tgz", + "integrity": "sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", + "@babel/compat-data": "^7.22.20", "@babel/helper-compilation-targets": "^7.22.15", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", @@ -1659,7 +1659,7 @@ "@babel/plugin-transform-unicode-regex": "^7.22.5", "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.22.15", + "@babel/types": "^7.22.19", "babel-plugin-polyfill-corejs2": "^0.4.5", "babel-plugin-polyfill-corejs3": "^0.8.3", "babel-plugin-polyfill-regenerator": "^0.5.2", @@ -1703,9 +1703,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1735,19 +1735,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.15.tgz", - "integrity": "sha512-DdHPwvJY0sEeN4xJU5uRLmZjgMMDIvMPniLuYzUVXj/GGzysPl0/fwt44JBkyUIzGJPV8QgHMcQdQ34XFuKTYQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1756,13 +1756,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.15.tgz", - "integrity": "sha512-X+NLXr0N8XXmN5ZsaQdm9U2SSC3UbIYq/doL++sueHOTisgZHoKaQtZxGuV2cUPQHMfjKEfg/g6oy7Hm6SKFtA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.15", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2153,9 +2153,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", - "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", + "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2185,9 +2185,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2212,9 +2212,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", - "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2433,24 +2433,24 @@ } }, "node_modules/@rushstack/eslint-patch": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz", - "integrity": "sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", + "integrity": "sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==", "dev": true }, "node_modules/@types/bootstrap": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.6.tgz", - "integrity": "sha512-BlAc3YATdasbHoxMoBWODrSF6qwQO/E9X8wVxCCSa6rWjnaZfpkr2N6pUMCY6jj2+wf0muUtLySbvU9etX6YqA==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.7.tgz", + "integrity": "sha512-vWs0HzobIB8Af2F0B1GXpaVLSVn1NWULDYgTIWp08Et/r2B3aAwwhFBeOs/rRFWJA38EZTXkWP3tepIjpQkpLg==", "dev": true, "dependencies": { "@popperjs/core": "^2.9.2" } }, "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "version": "8.44.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", + "integrity": "sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2458,9 +2458,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz", + "integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==", "dev": true, "peer": true, "dependencies": { @@ -2469,15 +2469,15 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", + "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, "node_modules/@types/json5": { @@ -2488,22 +2488,22 @@ "peer": true }, "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", + "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", "dev": true }, "node_modules/@types/node": { - "version": "20.5.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", - "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==", + "version": "20.8.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", + "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", "dev": true, "peer": true }, "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", + "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", "dev": true }, "node_modules/@types/parse-json": { @@ -2513,15 +2513,15 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, "node_modules/@types/vue-select": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/@types/vue-select/-/vue-select-3.16.2.tgz", - "integrity": "sha512-KkmQiNI7Cei2YV3wn7O2fmM6PpfuM9ybYFMDqq2AvIZNZ2BRHMMV70yaa1EKBRvqu5qY5ekel5WzcRRfNu56PQ==", + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/vue-select/-/vue-select-3.16.3.tgz", + "integrity": "sha512-rXJP96FOz0U8cnLBRdASnUth3dCY5Z3vbvSoH8TYszIJ/26RJPu/evvCg7FmTivBeNv8hcVNgP6euOy/XzW9yQ==", "dev": true, "dependencies": { "vue": "^2.0.0" @@ -2762,9 +2762,9 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz", - "integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.4.0.tgz", + "integrity": "sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==", "dev": true, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -2876,9 +2876,9 @@ } }, "node_modules/@vue/language-core": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.8.tgz", - "integrity": "sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.15.tgz", + "integrity": "sha512-zche5Aw8kkvp3YaghuLiOZyVIpoWHjSQ0EfjxGSsqHOPMamdCoa9x3HtbenpR38UMUoKJ88wiWuiOrV3B/Yq+A==", "dev": true, "dependencies": { "@volar/language-core": "~1.10.0", @@ -2986,13 +2986,13 @@ "dev": true }, "node_modules/@vue/typescript": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.8.tgz", - "integrity": "sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.15.tgz", + "integrity": "sha512-qWyanQKXOsK84S8rP7QBrqsvUdQ0nZABZmTjXMpb3ox4Bp5IbkscREA3OPUrkgl64mAxwwCzIWcOc3BPTCPjQw==", "dev": true, "dependencies": { "@volar/typescript": "~1.10.0", - "@vue/language-core": "1.8.8" + "@vue/language-core": "1.8.15" } }, "node_modules/@webassemblyjs/ast": { @@ -3371,15 +3371,15 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -3390,15 +3390,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -3409,15 +3409,16 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", - "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", "dev": true, "peer": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.2", "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "is-array-buffer": "^3.0.2", "is-shared-array-buffer": "^1.0.2" @@ -3467,9 +3468,9 @@ } }, "node_modules/axios": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", - "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "dev": true, "dependencies": { "follow-redirects": "^1.15.0", @@ -3501,13 +3502,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", - "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz", + "integrity": "sha512-9l//BZZsPR+5XjyJMPtZSK4jv0BsTO1zDac2GC6ygx9WLGlcsnRd1Co0B2zT5fF5Ic6BZy+9m3HNZ3QcOeDKfg==", "dev": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.31.0" + "core-js-compat": "^3.32.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3556,9 +3557,9 @@ "dev": true }, "node_modules/bootstrap": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz", - "integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", "dev": true, "funding": [ { @@ -3597,9 +3598,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -3616,10 +3617,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -3685,9 +3686,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001527", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001527.tgz", - "integrity": "sha512-YkJi7RwPgWtXVSgK4lG9AHH57nSzvvOp9MesgXmw4Q7n0C3H04L0foHqfxcmSAm5AcWb8dW9AYj2tR7/5GnddQ==", + "version": "1.0.30001546", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz", + "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==", "dev": true, "funding": [ { @@ -3820,15 +3821,15 @@ "dev": true }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, "node_modules/core-js": { - "version": "3.32.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.1.tgz", - "integrity": "sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz", + "integrity": "sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==", "dev": true, "hasInstallScript": true, "funding": { @@ -3837,12 +3838,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.32.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.1.tgz", - "integrity": "sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", + "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", "dev": true, "dependencies": { - "browserslist": "^4.21.10" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", @@ -3969,13 +3970,29 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "node_modules/define-data-property": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", + "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", "dev": true, "peer": true, "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -4084,9 +4101,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.508", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz", - "integrity": "sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg==", + "version": "1.4.542", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.542.tgz", + "integrity": "sha512-6+cpa00G09N3sfh2joln4VUXHquWrOFx3FLZqiVQvl45+zS9DskDBTPvob+BhvFRmTBkyDSk0vvLMMRo/qc6mQ==", "dev": true }, "node_modules/emoji-regex": { @@ -4139,19 +4156,19 @@ } }, "node_modules/es-abstract": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", - "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", + "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", "dev": true, "peer": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.2", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", + "function.prototype.name": "^1.1.6", "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", @@ -4167,23 +4184,23 @@ "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", + "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "safe-array-concat": "^1.0.0", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", "typed-array-buffer": "^1.0.0", "typed-array-byte-length": "^1.0.0", "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.10" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -4193,9 +4210,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", "dev": true, "peer": true }, @@ -4298,16 +4315,16 @@ } }, "node_modules/eslint": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", - "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.48.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.12.4", @@ -4611,9 +4628,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.21.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", - "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -4870,15 +4887,15 @@ } }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "dev": true, "funding": [ { @@ -4957,7 +4974,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "dev": true, + "peer": true }, "node_modules/function.prototype.name": { "version": "1.1.6", @@ -5193,13 +5211,10 @@ } }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -6024,9 +6039,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", - "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz", + "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -6543,9 +6558,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -6598,9 +6613,9 @@ } }, "node_modules/postcss-html/node_modules/js-tokens": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.1.tgz", - "integrity": "sha512-3AGrZT6tuMm1ZWWn9mLXh7XMfi2YtiLNPALCVxBCiUVq0LD1OQMxV/AdS/s7rLJU5o9i/jBZw/N4vXXL5dm29A==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-8.0.2.tgz", + "integrity": "sha512-Olnt+V7xYdvGze9YTbGFZIfQXuGV4R3nQwwl8BrtgaPE/wq8UFpUHWuTNc05saowhSr1ZO6tx+V6RjE9D5YQog==", "dev": true }, "node_modules/postcss-media-query-parser": { @@ -6632,9 +6647,9 @@ } }, "node_modules/postcss-scss": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.7.tgz", - "integrity": "sha512-xPv2GseoyXPa58Nro7M73ZntttusuCmZdeOojUFR5PZDz2BR62vfYx1w9TyOnp1+nYFowgOMipsCBhxzVkAEPw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", "dev": true, "funding": [ { @@ -6654,7 +6669,7 @@ "node": ">=12.0" }, "peerDependencies": { - "postcss": "^8.4.19" + "postcss": "^8.4.29" } }, "node_modules/postcss-selector-parser": { @@ -6671,39 +6686,12 @@ } }, "node_modules/postcss-sorting": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-5.0.1.tgz", - "integrity": "sha512-Y9fUFkIhfrm6i0Ta3n+89j56EFqaNRdUKqXyRp6kvTcSXnmgEjaVowCXH+JBe9+YKWqd4nc28r2sgwnzJalccA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz", + "integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==", "dev": true, - "dependencies": { - "lodash": "^4.17.14", - "postcss": "^7.0.17" - }, - "engines": { - "node": ">=8.7.0" - } - }, - "node_modules/postcss-sorting/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/postcss-sorting/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "peerDependencies": { + "postcss": "^8.3.9" } }, "node_modules/postcss-value-parser": { @@ -6966,9 +6954,9 @@ "dev": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", "dev": true, "dependencies": { "regenerate": "^1.4.2" @@ -6999,15 +6987,15 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "set-function-name": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -7064,9 +7052,9 @@ } }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -7105,6 +7093,12 @@ "node": ">=12" } }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -7131,9 +7125,9 @@ } }, "node_modules/rollup": { - "version": "3.28.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz", - "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==", + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -7170,14 +7164,14 @@ } }, "node_modules/safe-array-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", - "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", + "get-intrinsic": "^1.2.1", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -7225,9 +7219,9 @@ } }, "node_modules/sass": { - "version": "1.66.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", - "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", + "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -7337,6 +7331,21 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7503,9 +7512,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz", + "integrity": "sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==", "dev": true }, "node_modules/ssr-window": { @@ -7529,15 +7538,15 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -7547,15 +7556,15 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dev": true, "peer": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7685,12 +7694,15 @@ } }, "node_modules/stylelint-config-idiomatic-order": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/stylelint-config-idiomatic-order/-/stylelint-config-idiomatic-order-8.1.0.tgz", - "integrity": "sha512-iTPY6JjbkIdzy+21x3a1xi/tG33zKhLJb6lZl1xg6jZrXjgIYelnRZ5xVtbcEP9rElxZq/Zu1eGthfvI+ri+YQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-idiomatic-order/-/stylelint-config-idiomatic-order-9.0.0.tgz", + "integrity": "sha512-+LtfPycY1Paayf1MaERyh6BzVPnZxemX5NtzdUPqi4u8hyAR7859f/4EL02+Kr9va76iX7mbYC4HendocXKJZQ==", "dev": true, "dependencies": { - "stylelint-order": "^3.1.1" + "stylelint-order": "^5.0.0" + }, + "engines": { + "node": ">=12" }, "peerDependencies": { "stylelint": ">=11" @@ -7743,28 +7755,6 @@ "stylelint": "^14.0.1" } }, - "node_modules/stylelint-config-sass-guidelines/node_modules/postcss-sorting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-7.0.1.tgz", - "integrity": "sha512-iLBFYz6VRYyLJEJsBJ8M3TCqNcckVzz4wFounSc5Oez35ogE/X+aoC5fFu103Ot7NyvjU3/xqIXn93Gp3kJk4g==", - "dev": true, - "peerDependencies": { - "postcss": "^8.3.9" - } - }, - "node_modules/stylelint-config-sass-guidelines/node_modules/stylelint-order": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-5.0.0.tgz", - "integrity": "sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==", - "dev": true, - "dependencies": { - "postcss": "^8.3.11", - "postcss-sorting": "^7.0.1" - }, - "peerDependencies": { - "stylelint": "^14.0.0" - } - }, "node_modules/stylelint-config-standard": { "version": "29.0.0", "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz", @@ -7797,43 +7787,16 @@ } }, "node_modules/stylelint-order": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-3.1.1.tgz", - "integrity": "sha512-4gP/r8j/6JGZ/LL41b2sYtQqfwZl4VSqTp7WeIwI67v/OXNQ08dnn64BGXNwAUSgb2+YIvIOxQaMzqMyQMzoyQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-5.0.0.tgz", + "integrity": "sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==", "dev": true, "dependencies": { - "lodash": "^4.17.15", - "postcss": "^7.0.17", - "postcss-sorting": "^5.0.1" - }, - "engines": { - "node": ">=8.7.0" + "postcss": "^8.3.11", + "postcss-sorting": "^7.0.1" }, "peerDependencies": { - "stylelint": ">=10.0.1" - } - }, - "node_modules/stylelint-order/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true - }, - "node_modules/stylelint-order/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "stylelint": "^14.0.0" } }, "node_modules/stylelint-scss": { @@ -7931,9 +7894,9 @@ "dev": true }, "node_modules/sweetalert2": { - "version": "11.7.27", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.27.tgz", - "integrity": "sha512-QbRXGQn1sb7HEhzA/K2xtWIwQHh/qkSbb1w6jYcTql2xy17876lTREEt1D4X6Q0x2wHtfUjKJ+Cb8IVkRoq7DQ==", + "version": "11.7.31", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.7.31.tgz", + "integrity": "sha512-AVNODg41LqhC241UHTanW2ZQdh0ziiw+Gc3Dmy1EuCFP+g09P3mBEhvauU88J3JLEX6UpTfhp7iok9t8uhEdQQ==", "dev": true, "funding": { "type": "individual", @@ -8018,9 +7981,9 @@ } }, "node_modules/terser": { - "version": "5.19.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", - "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz", + "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==", "dev": true, "peer": true, "dependencies": { @@ -8427,9 +8390,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -8503,9 +8466,9 @@ } }, "node_modules/vite": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", - "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", + "integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==", "dev": true, "dependencies": { "esbuild": "^0.18.10", @@ -8842,9 +8805,9 @@ } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", - "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==", "dev": true }, "node_modules/vscode-languageserver-types": { @@ -9025,13 +8988,13 @@ } }, "node_modules/vue-tsc": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.8.tgz", - "integrity": "sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ==", + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.15.tgz", + "integrity": "sha512-4DoB3LUj7IToLmggoCxRiFG+QU5lem0nv03m1ocqugXA9rSVoTOEoYYaP8vu8b99Eh+/cCVdYOeIAQ+RsgUYUw==", "dev": true, "dependencies": { - "@vue/language-core": "1.8.8", - "@vue/typescript": "1.8.8", + "@vue/language-core": "1.8.15", + "@vue/typescript": "1.8.15", "semver": "^7.3.8" }, "bin": { diff --git a/package.json b/package.json index ed06e78..71ef4b1 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "sass-loader": "^11.0.1", "slugify": "^1.6.6", "stylelint": "^14.16.1", - "stylelint-config-idiomatic-order": "^8.1.0", + "stylelint-config-idiomatic-order": "^9.0.0", "stylelint-config-sass-guidelines": "^9.0.1", "stylelint-config-standard-scss": "^6.1.0", "stylelint-scss": "^4.4.0", @@ -60,8 +60,8 @@ "vue": "^3.2.38", "vue-eslint-parser": "^9.1.0", "vue-loader": "^16.8.3", - "vue-tsc": "^1.2.0", "vue-select": "^4.0.0-beta.3", + "vue-tsc": "^1.2.0", "vue3-nestable": "^3.0.3" } } diff --git a/phpcs.xml b/phpcs.xml index 9af88c2..80914bd 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,5 +1,6 @@ - + Created with the PHP Coding Standard Generator. http://edorian.github.com/php-coding-standard-generator/ @@ -21,6 +22,7 @@ + diff --git a/phpunit.xml b/phpunit.xml index 4bbafda..d2ae68a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,32 +1,26 @@ - + + + + + + - - ./tests/Client + + tests/CRM/ApiV2 - - ./tests/CRM + + tests/CRM/ApiV4 + + + tests/Client - ./tests/StressTest + tests/StressTest - - - ./app - ./clients/V2/src - - - - - - @@ -35,4 +29,10 @@ + + + app + client/src + + diff --git a/resources/views/answers/index-list.blade.php b/resources/views/answers/index-list.blade.php index 5afbe96..d631f43 100644 --- a/resources/views/answers/index-list.blade.php +++ b/resources/views/answers/index-list.blade.php @@ -2,52 +2,58 @@ @php - $cols = [ - 'answer' => __('Réponse'), - 'updated_at' => __('Date d\'ajout en base') - ]; + $cols = [ + 'answer' => __('Réponse'), + 'updated_at' => __('Date d\'ajout en base'), + ]; @endphp - @include('modules.table-col-sorter', ['cols' => $cols, 'mobileHide' => ['status'], 'ignore' => ['updated_at']]) + @include('modules.table-col-sorter', [ + 'cols' => $cols, + 'mobileHide' => ['status'], + 'ignore' => ['updated_at'], + ]) - @if($answerModels->count()) - @foreach ($answerModels as $answerModel) - - - - - - @endforeach + @if ($answerModels->count()) + @foreach ($answerModels as $answerModel) + + + + + + @endforeach @else - - - + + + @endif
{{ $answerModel->answer }} - {{ $answerModel->updated_at->formatLocalized('%d/%m/%Y à %H:%M') }} - -
$questionModel, 'answer' => $answerModel]) }}" - method="POST" - class="btn-group"> - @can('update', \App\Models\Answer::class) - - - - @endcan - @csrf - @method('DELETE') - @can('delete', \App\Models\Answer::class) - - @endcan -
-
{{ $answerModel->answer }} + {{ $answerModel->updated_at->formatLocalized('%d/%m/%Y à %H:%M') }} + +
+ @can('update', \App\Models\Answer::class) + + + + @endcan + @csrf + @method('DELETE') + @can('delete', \App\Models\Answer::class) + + @endcan +
+
- {{ __('Aucun résultat pour les :models', ['models' => __('models.classes.answers')]) }} -
+ {{ __('Aucun résultat pour les :models', ['models' => __('models.classes.answers')]) }} +
diff --git a/resources/views/answers/index.blade.php b/resources/views/answers/index.blade.php index c785616..b68b163 100644 --- a/resources/views/answers/index.blade.php +++ b/resources/views/answers/index.blade.php @@ -25,7 +25,10 @@ @can('create', \App\Models\Answer::class)
diff --git a/resources/views/customers/export.blade.php b/resources/views/customers/export.blade.php index de0d24e..493da06 100644 --- a/resources/views/customers/export.blade.php +++ b/resources/views/customers/export.blade.php @@ -5,72 +5,74 @@ @section('metaIndex', 'noindex,nofollow') @section('content') -
-
-
-
-
-
- @include('breadcrumbs.breadcrumb-body') +
+
+
+
+
+
+ @include('breadcrumbs.breadcrumb-body') +
-
-
-
-
-
- @csrf -
-
-

{{ __('Exporter la liste de client pour une newsletter') }}

+
+
+
+ + @csrf +
+
+

{{ __('Exporter la liste de client pour une newsletter') }}

+
-
-
-
-
- {{ __('La marque concernée') }} -
- - + + @foreach ($brands as $i => $brand) + + @endforeach + +
+
+
+
+
+
+
+ {{ __('Type d\'export') }} + @foreach (\App\Enums\CustomerExportType::toArray() as $i => $exportType) +
+ + +
@endforeach - -
- + +
-
-
-
-
- {{ __('Type d\'export') }} - @foreach(\App\Enums\CustomerExportType::toArray() as $i => $exportType) -
- - -
- @endforeach -
+
+
+ +
-
-
-
- -
-
- + +
-
@endsection diff --git a/resources/views/customers/show.blade.php b/resources/views/customers/show.blade.php index 47a8467..f37884d 100644 --- a/resources/views/customers/show.blade.php +++ b/resources/views/customers/show.blade.php @@ -3,126 +3,129 @@ @section('title', sprintf('%s - %s', __('models.classes.customers'), Str::ucfirst(__('aperçu')))) @section('description', __('Aperçu d\'un :model', ['model' => __('models.classes.customers')])) @section('metaIndex', 'noindex,nofollow') -@section('breadcrumb', request()->route()->getName()) +@section('breadcrumb', + request()->route()->getName()) @section('content') -
-
-
-
- @can('viewAny', \App\Models\Customer::class) - - - - @endcan - @include('breadcrumbs.breadcrumb-body') -
-
-
- @csrf - @method('DELETE') - @can('delete', \App\Models\Customer::class) - -
-
-
-
-
-
-
-
-

{{ __('Informations du client') }} {{ $customerInfos->firstname }} {{ $customerInfos->lastname }} - {{ Str::singular(__('models.classes.brands')) }} {{ $customerInfos->brand->name }}

+ @include('breadcrumbs.breadcrumb-body') +
+
+
+ @csrf + @method('DELETE') + @can('delete', \App\Models\Customer::class) + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @if($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->exists()) - - @else - - @endif - - - - - - - - - - - - - - - - - - - - - - -
{{ __('Email') }}{{ $customerInfos->customer->email }}
{{ __('Prénom') }}{{ $customerInfos->firstname }}
{{ __('Nom') }}{{ $customerInfos->lastname }}
{{ __('Date de naissance') }}{{ \Carbon\Carbon::parse($customerInfos->birth_date)->isoFormat('DD\/MM\/YYYY') }}
{{ __('Téléphone') }}{{ $customerInfos->phone }}
{{ __('Adresse') }}{{ $customerInfos->address }}
{{ __('Adresse complémentaire') }}{{ $customerInfos->address2 }}
{{ __('Pays') }}{{ $customerInfos->country }}
{{ __('Ville') }}{{ $customerInfos->city }}
{{ __('Code postal') }}{{ $customerInfos->postcode }}
{{ __('Abonné à la newsletter') }}{{ $customerInfos->opt_in ? "Oui" : "Non" }}
{{ __('Date de la dernière modification de l\'abonnement') }}{{ \Carbon\Carbon::parse($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->first()->created_at)->isoFormat('DD\/MM\/YYYY à HH:mm') }}
{{ __('Dernière connexion') }}{{ \Carbon\Carbon::parse($customerLogin->last_connection)->isoFormat('DD\/MM\/YYYY à HH:mm') }}
{{ __('Status du compte') }}{{ $customerLogin->status ? "Activé" : "Non activé" }}
{{ __('Hashcode') }}{{ $customerInfos->hashcode }}
{{ __('Date de création') }}{{ \Carbon\Carbon::parse($customerInfos->created_at)->isoFormat('DD\/MM\/YYYY à HH:mm') }}
{{ __('Date de modification') }}{{ \Carbon\Carbon::parse($customerInfos->updated_at)->isoFormat('DD\/MM\/YYYY à HH:mm') }}
+
+
+
+
+

{{ __('Informations du client') }} {{ $customerInfos->firstname }} + {{ $customerInfos->lastname }} - {{ Str::singular(__('models.classes.brands')) }} + {{ $customerInfos->brand->name }}

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if ($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->exists()) + + @else + + @endif + + + + + + + + + + + + + + + + + + + + + + +
{{ __('Email') }}{{ $customerInfos->customer->email }}
{{ __('Prénom') }}{{ $customerInfos->firstname }}
{{ __('Nom') }}{{ $customerInfos->lastname }}
{{ __('Date de naissance') }}{{ \Carbon\Carbon::parse($customerInfos->birth_date)->isoFormat('DD\/MM\/YYYY') }}
{{ __('Téléphone') }}{{ $customerInfos->phone }}
{{ __('Adresse') }}{{ $customerInfos->address }}
{{ __('Adresse complémentaire') }}{{ $customerInfos->address2 }}
{{ __('Pays') }}{{ $customerInfos->country->label() }}
{{ __('Ville') }}{{ $customerInfos->city }}
{{ __('Code postal') }}{{ $customerInfos->postcode }}
{{ __('Abonné à la newsletter') }}{{ $customerInfos->opt_in ? 'Oui' : 'Non' }}
{{ __('Date de la dernière modification de l\'abonnement') }}{{ \Carbon\Carbon::parse($customerInfos->customer->optInHistory()->orderBy('created_at', 'DESC')->first()->created_at)->isoFormat('DD\/MM\/YYYY à HH:mm') }} +
{{ __('Dernière connexion') }}{{ \Carbon\Carbon::parse($customerLogin->last_connection)->isoFormat('DD\/MM\/YYYY à HH:mm') }} +
{{ __('Status du compte') }}{{ $customerLogin->status ? 'Activé' : 'Non activé' }}
{{ __('Hashcode') }}{{ $customerInfos->hashcode }}
{{ __('Date de création') }}{{ \Carbon\Carbon::parse($customerInfos->created_at)->isoFormat('DD\/MM\/YYYY à HH:mm') }}
{{ __('Date de modification') }}{{ \Carbon\Carbon::parse($customerInfos->updated_at)->isoFormat('DD\/MM\/YYYY à HH:mm') }}
+
-
@endsection diff --git a/resources/views/partials/inputs/select-enum.blade.php b/resources/views/partials/inputs/select-enum.blade.php index 70ff84b..2bc67fd 100644 --- a/resources/views/partials/inputs/select-enum.blade.php +++ b/resources/views/partials/inputs/select-enum.blade.php @@ -3,8 +3,8 @@ @endif diff --git a/resources/views/questions/show.blade.php b/resources/views/questions/show.blade.php index 4400ada..64c5f44 100644 --- a/resources/views/questions/show.blade.php +++ b/resources/views/questions/show.blade.php @@ -66,8 +66,8 @@ {{ __('Type') }} @foreach (\App\Enums\QuestionType::toArray() as $questionType) - @if($questionType->value === $questionModel->type->value) - {{ $questionType->label }} + @if($questionType->value() === $questionModel->type->value()) + {{ $questionType->label() }} @endif @endforeach diff --git a/routes/api.php b/routes/api.php index af85eb2..f258b9d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -8,6 +8,13 @@ use App\Http\Controllers\Api\V2\OptinController as V2OptinController; use App\Http\Controllers\Api\V2\AnswerController as V2AnswerController; use App\Http\Controllers\Api\V2\QuestionController as V2QuestionController; use App\Http\Controllers\Api\V2\StatsController as V2StatsController; +use App\Http\Controllers\Api\V4\ApiController as V4ApiController; +use App\Http\Controllers\Api\V4\ArcadiaController as V4ArcadiaController; +use App\Http\Controllers\Api\V4\CustomerController as V4CustomerController; +use App\Http\Controllers\Api\V4\OptinController as V4OptinController; +use App\Http\Controllers\Api\V4\AnswerController as V4AnswerController; +use App\Http\Controllers\Api\V4\QuestionController as V4QuestionController; +use App\Http\Controllers\Api\V4\StatsController as V4StatsController; /* |-------------------------------------------------------------------------- @@ -74,3 +81,58 @@ Route::prefix('v2')->name('apiV2.')->group(function () { }); }); }); + +Route::prefix('v4')->name('apiV4.')->group(function () { + + // * PUBLIC + Route::get('/ping', [V4ApiController::class, 'ping'])->name('ping'); + + // * PRIVATE + Route::middleware('auth:api')->group(function () { + + // * OPTINS + Route::post('/optin', [V4OptinController::class, 'optin'])->name('optin'); + Route::post('/optout', [V4OptinController::class, 'optoutWithHashcode'])->name('optout-hashcode'); + + // * CUSTOMERS + Route::apiResource('customers', V4CustomerController::class, [ + 'except' => 'index', + 'parameters' => ['customers' => 'serviceCustomer'] + ]); + Route::prefix('customers')->name('customers.')->group(function () { + Route::post('/authenticate', [V4CustomerController::class, 'authenticate'])->name('authenticate'); + Route::prefix('{serviceCustomer}')->group(function () { + Route::post('/activate', [V4CustomerController::class, 'activateAccount'])->name('activate'); + Route::post('/answers', [V4AnswerController::class, 'storeCustomerAnswers']) + ->name('answers.store'); + }); + }); + + // * ARCADIA + Route::prefix('arcadia')->name('arcadia.')->group(function () { + Route::post('/outgoing-mail/default', [V4ArcadiaController::class, 'outgoingMailDefault']) + ->name('outgoingMailDefault'); + Route::post('/outgoing-mail/game', [V4ArcadiaController::class, 'outgoingMailGame']) + ->name('outgoingMailGame'); + }); + + // * QUESTIONS + Route::apiResource('questions', V4QuestionController::class, [ + 'parameters' => ['questions' => 'serviceQuestion'] + ])->only('index'); + Route::prefix('questions')->name('questions.')->group(function () { + Route::get('/un-answered-by/{serviceCustomer}', [V4QuestionController::class, 'unAnsweredBy']) + ->name('unAnsweredBy'); + Route::get('/with-answers/{serviceCustomer}', [V4QuestionController::class, 'indexWithResponse']) + ->name('indexWithResponse'); + }); + + // * STATS + Route::prefix('stats')->name('stats.')->group(function () { + Route::get('/brand', [V4StatsController::class, 'brandStats']) + ->name('brandStats'); + Route::get('/question/{serviceQuestion}', [V4StatsController::class, 'questionStats']) + ->name('questionStats'); + }); + }); +}); diff --git a/routes/console.php b/routes/console.php index e216465..b3d9bbc 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,19 +1 @@ comment(Inspiring::quote()); -})->purpose('Display an inspiring quote'); diff --git a/test.sh b/test.sh index 4024d17..b50228c 100755 --- a/test.sh +++ b/test.sh @@ -5,9 +5,9 @@ composer runserve # If argument is passed run with coverage if [ -z "$1" ]; then echo 'no coverage' - composer runtest + composer runtestV4 else echo 'with coverage' - composer runtestWithCoverage + composer runtestWithCoverageV4 fi composer killserve diff --git a/tests/CRM/ApiV2/ApiBase.php b/tests/CRM/ApiV2/ApiBase.php index 6f1329e..2ce58cd 100644 --- a/tests/CRM/ApiV2/ApiBase.php +++ b/tests/CRM/ApiV2/ApiBase.php @@ -27,7 +27,7 @@ class ApiBase extends BaseTestCase */ protected function assertApiResponseStatusIs(AssertableJsonString $json, ApiStatus $status) { - $json->assertFragment(['status' => $status->value]); + $json->assertFragment(['status' => $status->value()]); } /** diff --git a/tests/CRM/ApiV2/ApiTest.php b/tests/CRM/ApiV2/ApiTest.php index 98e0d4e..751008b 100644 --- a/tests/CRM/ApiV2/ApiTest.php +++ b/tests/CRM/ApiV2/ApiTest.php @@ -52,7 +52,7 @@ class ApiTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $json->assertFragment(['data' => [ - 'code' => ApiError::wrongTimeStamp->value, + 'code' => ApiError::wrongTimeStamp->value(), 'message' => ApiError::wrongTimeStamp->label() ]]); } @@ -68,7 +68,7 @@ class ApiTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $json->assertFragment(['data' => [ - 'code' => ApiError::wrongTimeStamp->value, + 'code' => ApiError::wrongTimeStamp->value(), 'message' => ApiError::wrongTimeStamp->label() ]]); } @@ -86,7 +86,7 @@ class ApiTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $json->assertFragment(['data' => [ - 'code' => ApiError::wrongTimeStamp->value, + 'code' => ApiError::wrongTimeStamp->value(), 'message' => ApiError::wrongTimeStamp->label() ]]); } @@ -126,7 +126,7 @@ class ApiTest extends ApiBase ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::methodNotAllowed, $json->json('data.code')); + $this->assertEquals(ApiError::methodNotAllowed->value(), $json->json('data.code')); } /** @@ -143,7 +143,7 @@ class ApiTest extends ApiBase ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::apiAuthFailed, $json->json('data.code')); + $this->assertEquals(ApiError::apiAuthFailed->value(), $json->json('data.code')); } /** @@ -161,7 +161,7 @@ class ApiTest extends ApiBase ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::apiAuthFailed, $json->json('data.code')); + $this->assertEquals(ApiError::apiAuthFailed->value(), $json->json('data.code')); } /** @@ -181,6 +181,6 @@ class ApiTest extends ApiBase ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::apiAuthFailed, $json->json('data.code')); + $this->assertEquals(ApiError::apiAuthFailed->value(), $json->json('data.code')); } } diff --git a/tests/CRM/ApiV2/ArcadiaTest.php b/tests/CRM/ApiV2/ArcadiaTest.php index 837714d..28fbf12 100644 --- a/tests/CRM/ApiV2/ArcadiaTest.php +++ b/tests/CRM/ApiV2/ArcadiaTest.php @@ -36,7 +36,7 @@ class ArcadiaTest extends ApiBase 'operation' => 16, 'label_type_support' => 'Send Departaimants', 'order_date' => \date('Y/m/d H:i:s'), - 'state_order' => ArcadiaOrderType::cb->value, + 'state_order' => ArcadiaOrderType::cb->value(), 'support_code' => 1, 'label_type_support' => 'Fond carte Aimanté', 'order_num' => 23, diff --git a/tests/CRM/ApiV2/CustomerTest.php b/tests/CRM/ApiV2/CustomerTest.php index 646c443..8d5825c 100644 --- a/tests/CRM/ApiV2/CustomerTest.php +++ b/tests/CRM/ApiV2/CustomerTest.php @@ -103,7 +103,7 @@ class CustomerTest extends ApiBase $this->assertNotEmpty($json->json('data')); $this->assertNotEmpty($json->json('data.message')); $this->assertNotEmpty($json->json('data.code')); - $this->assertEquals(ApiError::authFailed->value, $json->json('data.code')); + $this->assertEquals(ApiError::authFailed->value(), $json->json('data.code')); $this->assertEquals(ApiError::authFailed->label(), $json->json('data.message')); } @@ -132,7 +132,7 @@ class CustomerTest extends ApiBase $this->assertNotEmpty($json->json('data')); $this->assertNotEmpty($json->json('data.message')); $this->assertNotEmpty($json->json('data.code')); - $this->assertEquals(ApiError::authFailed->value, $json->json('data.code')); + $this->assertEquals(ApiError::authFailed->value(), $json->json('data.code')); $this->assertEquals(ApiError::authFailed->label(), $json->json('data.message')); } @@ -167,7 +167,7 @@ class CustomerTest extends ApiBase $this->assertNotEmpty($json->json('data')); $this->assertNotEmpty($json->json('data.message')); $this->assertNotEmpty($json->json('data.code')); - $this->assertEquals(ApiError::customerNotActivated->value, $json->json('data.code')); + $this->assertEquals(ApiError::customerNotActivated->value(), $json->json('data.code')); $this->assertEquals(ApiError::customerNotActivated->label(), $json->json('data.message')); } @@ -195,7 +195,7 @@ class CustomerTest extends ApiBase 'email' => 'newcustomer@hotmail.fr', 'password' => '123.Mot.De.P4sse', 'phone' => '+33654821562', - 'country' => Country::france->value, + 'country' => Country::france->value(), 'city' => 'Connerré', 'postcode' => '72160', 'opt_in' => true @@ -242,7 +242,7 @@ class CustomerTest extends ApiBase 'email' => 'newcustomer@hotmail.fr', 'password' => '123.Mot.De.P4sse', 'phone' => '+33654821562', - 'country' => Country::france->value, + 'country' => Country::france->value(), 'city' => 'Connerré', 'postcode' => '72160', 'opt_in' => true @@ -254,7 +254,7 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::customerExists->value, $json->json('data.code')); + $this->assertEquals(ApiError::customerExists->value(), $json->json('data.code')); $this->assertEquals(ApiError::customerExists->label(), $json->json('data.message')); } @@ -275,7 +275,7 @@ class CustomerTest extends ApiBase 'email' => 'newcustomer.failures@hotmail.fr', 'password' => '123.Mot.De.P4sse', 'phone' => '+33654821562', - 'country' => Country::france->value, + 'country' => Country::france->value(), 'city' => 'Connerré', 'postcode' => '72160', 'opt_in' => true @@ -288,7 +288,7 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $this->assertEquals( - ApiError::validationFailed->value, + ApiError::validationFailed->value(), $json->json('data.code'), 'Password strengh should be checked' ); @@ -300,7 +300,7 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $this->assertEquals( - ApiError::validationFailed->value, + ApiError::validationFailed->value(), $json->json('data.code'), 'Post code should be checked' ); @@ -312,7 +312,7 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $this->assertEquals( - ApiError::validationFailed->value, + ApiError::validationFailed->value(), $json->json('data.code'), 'Country code should be checked' ); @@ -324,7 +324,7 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $this->assertEquals( - ApiError::validationFailed->value, + ApiError::validationFailed->value(), $json->json('data.code'), 'Country code should be checked' ); @@ -336,7 +336,7 @@ class CustomerTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); $this->assertEquals( - ApiError::validationFailed->value, + ApiError::validationFailed->value(), $json->json('data.code'), 'Email code should be checked' ); @@ -382,7 +382,7 @@ class CustomerTest extends ApiBase ), []); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::customerAlreadyActive->value, $json->json('data.code')); + $this->assertEquals(ApiError::customerAlreadyActive->value(), $json->json('data.code')); $this->assertEquals(ApiError::customerAlreadyActive->label(), $json->json('data.message')); } @@ -403,7 +403,7 @@ class CustomerTest extends ApiBase 'email' => 'newcustomertoupdate@hotmail.fr', 'password' => '123.Mot.De.P4sseNew', 'phone' => '+33654821563', - 'country' => Country::luxembourg->value, + 'country' => Country::luxembourg->value(), 'city' => 'Connerré2', 'postcode' => '72161', 'opt_in' => 0 @@ -417,7 +417,7 @@ class CustomerTest extends ApiBase 'email' => 'newcustomertoupdate3@hotmail.fr', 'password' => '123.Mot.De.P4sseNew3', 'phone' => '+33654821564', - 'country' => Country::france->value, + 'country' => Country::france->value(), 'city' => 'Connerré3', 'postcode' => '72163', 'opt_in' => 1 @@ -456,7 +456,7 @@ class CustomerTest extends ApiBase ), ['address2' => 'anything']); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::validationFailed->value, $json->json('data.code')); + $this->assertEquals(ApiError::validationFailed->value(), $json->json('data.code')); } /** @@ -476,7 +476,7 @@ class CustomerTest extends ApiBase 'email' => 'anewcustomertoupdate@hotmail.fr', 'password' => '123.Mot.De.P4sseNew', 'phone' => '+33654821563', - 'country' => Country::luxembourg->value, + 'country' => Country::luxembourg->value(), 'city' => 'Connerré2', 'postcode' => '72161', 'opt_in' => 0 @@ -490,7 +490,7 @@ class CustomerTest extends ApiBase )); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); } /** @@ -574,7 +574,7 @@ class CustomerTest extends ApiBase })->first(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + $this->assertEquals(ApiError::modelNotFound->value(), $json->json('data.code')); $this->assertEquals( sprintf(ApiError::modelNotFound->label(), $csClassName), $json->json('data.message') diff --git a/tests/CRM/ApiV2/QuestionTest.php b/tests/CRM/ApiV2/QuestionTest.php index 3f299d9..85185b7 100644 --- a/tests/CRM/ApiV2/QuestionTest.php +++ b/tests/CRM/ApiV2/QuestionTest.php @@ -13,6 +13,7 @@ class QuestionTest extends ApiBase /** * Test get all service questions. * route: apiV2.questions.index + * * @return void */ public function testQuestionIndex() @@ -48,7 +49,7 @@ class QuestionTest extends ApiBase foreach ($json->json('data') as $question) { $this->assertTrue($questions->contains($question['id'])); $DBquestion = $questions->get($question['id']); - $this->assertEquals($DBquestion->type->value, $question['type']); + $this->assertEquals($DBquestion->type->value(), $question['type']); $this->assertEquals($DBquestion->question, $question['question']); foreach ($question['answers'] as $answer) { $answers = $DBquestion->answers->keyBy('id'); @@ -62,6 +63,7 @@ class QuestionTest extends ApiBase /** * Test get all service questions with customer answers. * route: apiV2.questions.indexWithResponse + * * @return void */ public function testQuestionIndexWithAnswers() @@ -118,7 +120,7 @@ class QuestionTest extends ApiBase foreach ($json->json('data') as $question) { $this->assertTrue($questions->contains($question['id'])); $DBquestion = $questions->get($question['id']); - $this->assertEquals($DBquestion->type->value, $question['type']); + $this->assertEquals($DBquestion->type->value(), $question['type']); $this->assertEquals($DBquestion->question, $question['question']); foreach ($question['answers'] as $answer) { $answers = $DBquestion->answers->keyBy('id'); @@ -136,6 +138,7 @@ class QuestionTest extends ApiBase /** * Test get all service questions with customer answers when customer is unknown. * route: apiV2.questions.indexWithResponse + * * @return void */ public function testQuestionIndexWithAnswersWhenCustomerNotFound() @@ -150,12 +153,13 @@ class QuestionTest extends ApiBase )); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + $this->assertEquals(ApiError::modelNotFound->value(), $json->json('data.code')); } /** * Test get all service questions with customer answers when customer is unknown. * route: apiV2.questions.unAnsweredBy + * * @return void */ public function testCustomerUnAnsweredIndex() @@ -211,7 +215,7 @@ class QuestionTest extends ApiBase foreach ($json->json('data') as $question) { $this->assertTrue($questions->contains($question['id'])); $DBquestion = $questions->get($question['id']); - $this->assertEquals($DBquestion->type->value, $question['type']); + $this->assertEquals($DBquestion->type->value(), $question['type']); $this->assertEquals($DBquestion->question, $question['question']); foreach ($question['answers'] as $answer) { $answers = $DBquestion->answers->keyBy('id'); @@ -230,6 +234,7 @@ class QuestionTest extends ApiBase /** * Test get all service questions unanswered by customer when customer is unknown. * route: apiV2.questions.indexWithResponse + * * @return void */ public function testUnAnsweredIndexWhenCustomerNotFound() @@ -244,12 +249,13 @@ class QuestionTest extends ApiBase )); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + $this->assertEquals(ApiError::modelNotFound->value(), $json->json('data.code')); } /** * Test save customer answer. * route: apiV2.customers.answers.store + * * @return void */ public function testQuestionSetAnswer() @@ -284,6 +290,7 @@ class QuestionTest extends ApiBase /** * Test get all save customer answers when customer is unknown. * route: apiV2.questions.indexWithResponse + * * @return void */ public function testQuestionSetAnswerWhenCustomerNotFound() @@ -298,6 +305,6 @@ class QuestionTest extends ApiBase )); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + $this->assertEquals(ApiError::modelNotFound->value(), $json->json('data.code')); } } diff --git a/tests/CRM/ApiV2/SingleOptinTest.php b/tests/CRM/ApiV2/SingleOptinTest.php index 20ef6cb..73528a2 100644 --- a/tests/CRM/ApiV2/SingleOptinTest.php +++ b/tests/CRM/ApiV2/SingleOptinTest.php @@ -83,7 +83,7 @@ class SingleOptinTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::singleOptinCustomerExists->value, $json->json('data.code')); + $this->assertEquals(ApiError::singleOptinCustomerExists->value(), $json->json('data.code')); $this->assertEquals(ApiError::singleOptinCustomerExists->label(), $json->json('data.message')); } @@ -140,7 +140,7 @@ class SingleOptinTest extends ApiBase ]); $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + $this->assertEquals(ApiError::modelNotFound->value(), $json->json('data.code')); } /** @@ -159,7 +159,7 @@ class SingleOptinTest extends ApiBase 'email' => 'singleoptin@hotmail.fr', 'password' => '123.Mot.De.P4sse', 'phone' => '+33654821562', - 'country' => Country::france->value, + 'country' => Country::france->value(), 'city' => 'Connerré', 'postcode' => '72160', 'opt_in' => true diff --git a/tests/CRM/ApiV2/StatsTest.php b/tests/CRM/ApiV2/StatsTest.php index 8cf3c7f..b40273f 100644 --- a/tests/CRM/ApiV2/StatsTest.php +++ b/tests/CRM/ApiV2/StatsTest.php @@ -20,6 +20,7 @@ class StatsTest extends ApiBase /** * Test get stats by brand. * route: apiV2.stats.brandStats + * * @return void */ public function testStatsByBrand() @@ -53,6 +54,7 @@ class StatsTest extends ApiBase /** * Test get stats by brand should fail with date bigger than 3 month. * route: apiV2.stats.brandStats + * * @return void */ public function testStatsByBrandShouldFailWithInvalidDate() @@ -66,12 +68,13 @@ class StatsTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); } /** * Test get stats by brand should fail with invalid date format. * route: apiV2.stats.brandStats + * * @return void */ public function testStatsByBrandShouldFailWithInvalidDateFormat() @@ -85,12 +88,13 @@ class StatsTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); } /** * Test stats by question. * route: apiV2.stats.questionStats + * * @return void */ public function testStatsByQuestion() @@ -142,7 +146,8 @@ class StatsTest extends ApiBase /** * Test stats by question when it does not belong to brand. - * route: apiV2.stats.questionStats + * route: apiv.stats.questionStats + * * @return void */ public function testStatsByQuestionShouldFailWithInvalidQuestion() @@ -157,12 +162,13 @@ class StatsTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); } /** * Test stats by question should fail with date bigger than 3 month. * route: apiV2.stats.questionStats + * * @return void */ public function testStatsByQuestionShouldFailWithInvalidDate() @@ -177,12 +183,13 @@ class StatsTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); } /** * Test stats by question should fail with invalid date format. * route: apiV2.stats.questionStats + * * @return void */ public function testStatsByQuestionShouldFailWithInvalidDateFormat() @@ -197,6 +204,6 @@ class StatsTest extends ApiBase $json = $response->decodeResponseJson(); $this->assertApiResponseStatusIs($json, ApiStatus::KO); - $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); } } diff --git a/tests/CRM/ApiV4/ApiBase.php b/tests/CRM/ApiV4/ApiBase.php new file mode 100644 index 0000000..33fc6eb --- /dev/null +++ b/tests/CRM/ApiV4/ApiBase.php @@ -0,0 +1,106 @@ +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 + { + if (!isset($data['timestamp'])) { + $data['timestamp'] = \time(); + } + return $this->json($method, $uri, $data, $headers); + } + + /** + * Raw Function to call api with Token + * + * @param \App\Models\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 { + $data['bearer_token'] = $service->bearer_token; + $data['timestamp'] = \gmdate('Y-m-d H:i:s'); + + $data['signature'] = Helper::signRequest( + $service->bearer_pwd, + $uri, + $method, + $data + ); + + 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. + * @param boolean $random Randomize the customer to get. + * @return array + */ + protected function getCustomer(bool $status = true, bool $random = true): array + { + $login = CustomerLogin::query()->where('status', $status); + if ($random) { + $login = $login->inRandomOrder(); + } + /** @var \App\Models\CustomerLogin $login */ + $login = $login->firstOrFail(); + /** @var \App\Models\Brand $brand */ + $brand = $login->brand; + /** @var \App\Models\Customer $customer */ + $customer = $login->customer; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + /** @var \App\Models\CustomerInfo $info */ + $info = $customer->info($brand, Customer::FIRST_OR_FAIL); + + return compact('customer', 'service', 'login', 'brand', 'info'); + } +} diff --git a/tests/CRM/ApiV4/ApiTest.php b/tests/CRM/ApiV4/ApiTest.php new file mode 100644 index 0000000..82a5c4c --- /dev/null +++ b/tests/CRM/ApiV4/ApiTest.php @@ -0,0 +1,186 @@ +call('get', route('apiV4.ping'), [ + 'timestamp' => \gmdate('Y-m-d H:i:s') + ]); + $response->assertStatus(400); + } + + /** + * Test timestamp middleware works as expected. + * + * @return void + */ + public function testTimestampMiddleware() + { + $response = $this->json('get', route('apiV4.ping'), [ + 'timestamp' => \gmdate('Y-m-d H:i:s') + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + } + + /** + * Timestamp middleware should fail if timestamp parameter is missing. + * + * @return void + */ + public function testTimestampMiddlewareCheckFailMissing() + { + $response = $this->json('get', route('apiV4.ping'), []); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $json->assertFragment(['data' => [ + 'code' => ApiError::wrongTimeStamp->value(), + 'message' => ApiError::wrongTimeStamp->label() + ]]); + } + + /** + * Timestamp middleware should fail if timestamp parameter has wrong format. + * + * @return void + */ + public function testTimestampMiddlewareCheckFailWrongFormat() + { + $response = $this->json('get', route('apiV4.ping'), ['timestamp' => \gmdate('Y/m/d')]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $json->assertFragment(['data' => [ + 'code' => ApiError::wrongTimeStamp->value(), + 'message' => ApiError::wrongTimeStamp->label() + ]]); + } + + /** + * Timestamp middleware should fail if timestamp is older than "config('api.timestampTimeout', 10)". + * + * @return void + */ + public function testTimestampMiddlewareCheckFailIfTooOld() + { + $response = $this->json('get', route('apiV4.ping'), [ + 'timestamp' => \gmdate('Y-m-d H:i:s', \time() - config('api.timestampTimeout', 10)) + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $json->assertFragment(['data' => [ + 'code' => ApiError::wrongTimeStamp->value(), + 'message' => ApiError::wrongTimeStamp->label() + ]]); + } + + /** + * Test api route throttle. + * + * ! This has to be the last test of this class. + * + * @return void + */ + public function testApiThrottle() + { + for ($i = config('api.apiRequestRatePerMinute', 100); $i >= 0; $i--) { + $this->json('get', route('apiV4.ping'), [ + 'timestamp' => \gmdate('Y-m-d H:i:s') + ]); + } + + $response = $this->json('get', route('apiV4.ping'), [ + 'timestamp' => \gmdate('Y-m-d H:i:s') + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $response->assertStatus(429); + } + + /** + * Test wrong http method call + * + * @return void + */ + public function testWrongMethodCall() + { + $response = $this->json('post', route('apiV4.ping'), [ + 'timestamp' => \gmdate('Y-m-d H:i:s', \time()) + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::methodNotAllowed->value(), $json->json('data.code')); + } + + /** + * Test Auth fail when no token + * + * @return void + */ + public function testAuthFailNoToken() + { + $response = $this->json('get', route('apiV4.customers.show', [ + 'serviceCustomer' => Str::uuid() + ]), [ + 'timestamp' => \gmdate('Y-m-d H:i:s', \time()) + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::apiAuthFailed->value(), $json->json('data.code')); + } + + /** + * Test Auth fail when unknown token + * + * @return void + */ + public function testAuthFailUnknownToken() + { + $response = $this->json('get', route('apiV4.customers.show', [ + 'serviceCustomer' => Str::uuid() + ]), [ + 'bearer_token' => 'qsdqsdqsd', + 'timestamp' => \gmdate('Y-m-d H:i:s', \time()) + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::apiAuthFailed->value(), $json->json('data.code')); + } + + /** + * Test Auth fail when wrong signature + * + * @return void + */ + public function testAuthFailWrongSignature() + { + $service = Service::query()->firstOrFail(); + $response = $this->json('get', route('apiV4.customers.show', [ + 'serviceCustomer' => Str::uuid() + ]), [ + 'bearer_token' => $service->bearer_token, + 'timestamp' => \gmdate('Y-m-d H:i:s', \time()), + 'signature' => 'sdqdqsdqd' + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::apiAuthFailed->value(), $json->json('data.code')); + } +} diff --git a/tests/CRM/ApiV4/ArcadiaTest.php b/tests/CRM/ApiV4/ArcadiaTest.php new file mode 100644 index 0000000..0b9f9c1 --- /dev/null +++ b/tests/CRM/ApiV4/ArcadiaTest.php @@ -0,0 +1,87 @@ +getCustomer()); + $data = [ + 'deliver' => [ + 'email' => $customer->email, + 'firstname' => $info->firstname, + 'lastname' => $info->lastname, + 'phone' => $info->phone, + 'address' => $info->address, + 'address2' => $info->address2, + 'city' => $info->city, + 'postcode' => $info->postcode, + ], + 'operation' => 16, + 'label_type_support' => 'Send Departaimants', + 'order_date' => \date('Y/m/d H:i:s'), + 'state_order' => ArcadiaOrderType::cb->value, + 'support_code' => 1, + 'label_type_support' => 'Fond carte Aimanté', + 'order_num' => 23, + 'package_unit_weight' => 0, + ]; + $response = $this->callApiWithToken($service, 'post', route( + 'apiV4.arcadia.outgoingMailDefault' + ), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + } + + /** + * Test arcadia send game reclamation. + * apiV4.arcadia.outgoingMailGame + * + * @return void + */ + public function testArcadiaSendOrderGame() + { + extract($this->getCustomer()); + $data = [ + 'deliver' => [ + 'email' => $customer->email, + 'firstname' => $info->firstname, + 'lastname' => $info->lastname, + 'phone' => $info->phone, + 'address' => $info->address, + 'address2' => $info->address2, + 'city' => $info->city, + 'postcode' => $info->postcode, + ], + 'operation' => 16, + 'order_date' => date('Y/m/d H:i:s'), + 'support_code' => 32, + 'label_type_support' => 'Jeux Reghalal', + 'code_unique' => \strtoupper(\str_replace('-', '', (string)Str::uuid())), + 'code_on_pack' => '2563547' + ]; + $response = $this->callApiWithToken($service, 'post', route( + 'apiV4.arcadia.outgoingMailGame', + ), $data); + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + } +} diff --git a/tests/CRM/ApiV4/CustomerTest.php b/tests/CRM/ApiV4/CustomerTest.php new file mode 100644 index 0000000..0c74a45 --- /dev/null +++ b/tests/CRM/ApiV4/CustomerTest.php @@ -0,0 +1,716 @@ +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())); + } + + /** + * Test customer Authentification ok. + * route: apiV4.customers.authenticate + * + * @return void + */ + public function testApiAuthenticateCustomerShouldWork() + { + extract($this->getCustomer(true)); + + $login->password = 'testPassword'; + $login->saveOrFail(); + + $response = $this->callApiWithToken( + $service, + 'post', + route('apiV4.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')); + } + + /** + * Customer authentification should fail if the password is wrong. + * route: apiV4.customers.authenticate + * + * @return void + */ + public function testApiAuthenticateCustomerShouldFailIfWrongPassword() + { + extract($this->getCustomer()); + $response = $this->callApiWithToken( + $service, + 'post', + route('apiV4.customers.authenticate', []), + [ + 'email' => $customer->email, + 'password' => 'azdqdqdazd' + ] + ); + + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertNotEmpty($json->json('data')); + $this->assertNotEmpty($json->json('data.message')); + $this->assertNotEmpty($json->json('data.code')); + $this->assertEquals(ApiError::authFailed->value(), $json->json('data.code')); + $this->assertEquals(ApiError::authFailed->label(), $json->json('data.message')); + } + + /** + * Customer authentification should fail if the email is wrong. + * route: apiV4.customers.authenticate + * + * @return void + */ + public function testApiAuthenticateCustomerShouldFailIfWrongEmail() + { + extract($this->getCustomer()); + $response = $this->callApiWithToken( + $service, + 'post', + route('apiV4.customers.authenticate', []), + [ + 'email' => 'wrongemail@hotmail.fr', + 'password' => 'azdqdqdazd' + ] + ); + + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertNotEmpty($json->json('data')); + $this->assertNotEmpty($json->json('data.message')); + $this->assertNotEmpty($json->json('data.code')); + $this->assertEquals(ApiError::authFailed->value(), $json->json('data.code')); + $this->assertEquals(ApiError::authFailed->label(), $json->json('data.message')); + } + + /** + * Customer authentification should fail if the customer is not activated. + * route: apiV4.customers.authenticate + * + * @return void + */ + public function testApiAuthenticateCustomerShouldFailIfCustomerNotActivated() + { + $login = CustomerLogin::query()->where('status', false)->firstOrFail(); + /** @var \App\Models\Brand $brand */ + $brand = $login->brand; + /** @var \App\Models\Customer $customer */ + $customer = $login->customer; + /** @var \App\Models\Service $service */ + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken( + $service, + 'post', + route('apiV4.customers.authenticate', []), + [ + 'email' => $customer->email, + 'password' => 'testPassword' + ] + ); + + $json = $response->decodeResponseJson(); + + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertNotEmpty($json->json('data')); + $this->assertNotEmpty($json->json('data.message')); + $this->assertNotEmpty($json->json('data.code')); + $this->assertEquals(ApiError::customerNotActivated->value(), $json->json('data.code')); + $this->assertEquals(ApiError::customerNotActivated->label(), $json->json('data.message')); + } + + /** + * Customer store ok + * route: apiV4.customers.store + * + * @return void + */ + public function testApiStoreCustomerOk() + { + extract($this->getCustomer()); + + // * Single optin should be deleted when creating new customer. + $singleOptin = new SingleOptin(); + $singleOptin->email = 'newcustomer@hotmail.fr'; + $singleOptin->brand()->associate($brand); + $singleOptin->saveOrFail(); + + $data = [ + 'firstname' => 'Jean', + 'lastname' => 'Bond', + 'birth_date' => \date('1992-03-08'), + 'address' => '10 rue des toto', + 'email' => 'newcustomer@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('apiV4.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($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')); + } + + /** + * Customer store should fail if customer already exists. + * route: apiV4.customers.store + * + * @return void + */ + public function testApiStoreCustomerShouldFailIfCustomerAlreadyExists() + { + extract($this->getCustomer()); + + $data = [ + 'firstname' => 'Jean', + 'lastname' => 'Bond', + 'birth_date' => \date('1992-03-08'), + 'address' => '10 rue des toto', + 'email' => 'newcustomer@hotmail.fr', + 'password' => '123.Mot.De.P4sse', + 'phone' => '+33654821562', + 'country' => Country::france->value(), + 'city' => 'Connerré', + 'postcode' => '72160', + 'opt_in' => true + ]; + + $this->createNewCustomer($data, $brand); + + $response = $this->callApiWithToken($service, 'post', route('apiV4.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')); + } + + /** + * Customer store validationFailures + * route: apiV4.customers.store + * + * @return void + */ + public function testApiStoreCustomerValidationFailures() + { + extract($this->getCustomer()); + $data = [ + 'firstname' => 'Jean', + 'lastname' => 'Bond', + 'birth_date' => \date('1992-03-08'), + 'address' => '10 rue des toto', + 'email' => 'newcustomer.failures@hotmail.fr', + 'password' => '123.Mot.De.P4sse', + 'phone' => '+33654821562', + 'country' => Country::france->value(), + 'city' => 'Connerré', + 'postcode' => '72160', + 'opt_in' => true + ]; + + $sendData = $data; + $sendData['password'] = 'password'; + + $response = $this->callApiWithToken($service, 'post', route('apiV4.customers.store'), $sendData); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + ApiError::validationFailed->value(), + $json->json('data.code'), + 'Password strengh should be checked' + ); + + $sendData = $data; + $sendData['postcode'] = '12'; + + $response = $this->callApiWithToken($service, 'post', route('apiV4.customers.store'), $sendData); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + ApiError::validationFailed->value(), + $json->json('data.code'), + 'Post code should be checked' + ); + + $sendData = $data; + $sendData['country'] = '12'; + + $response = $this->callApiWithToken($service, 'post', route('apiV4.customers.store'), $sendData); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + ApiError::validationFailed->value(), + $json->json('data.code'), + 'Country code should be checked' + ); + + $sendData = $data; + $sendData['phone'] = '+49-89-636-48018'; + + $response = $this->callApiWithToken($service, 'post', route('apiV4.customers.store'), $sendData); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + ApiError::validationFailed->value(), + $json->json('data.code'), + 'Country code should be checked' + ); + + $sendData = $data; + $sendData['email'] = 'toto@nonexistingfqdndomain.lol'; + + $response = $this->callApiWithToken($service, 'post', route('apiV4.customers.store'), $sendData); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + ApiError::validationFailed->value(), + $json->json('data.code'), + 'Email code should be checked' + ); + } + + /** + * Test activate customer ok + * route: apiV4.customers.activate + * + * @return void + */ + public function testActivateCustomerOk() + { + extract($this->getCustomer(false)); + + $login->status = false; + $login->saveOrFail(); + + $response = $this->callApiWithToken($service, 'post', route( + 'apiV4.customers.activate', + ['serviceCustomer' => $info->hashcode] + ), []); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + } + + /** + * Test activate customer already activated + * route: apiV4.customers.activate + * + * @return void + */ + public function testActivateCustomerAlreadyActivated() + { + extract($this->getCustomer(true)); + + $login->status = true; + $login->saveOrFail(); + + $response = $this->callApiWithToken($service, 'post', route( + 'apiV4.customers.activate', + ['serviceCustomer' => $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')); + } + + /** + * Test customer update ok + * route: apiV4.customers.update + * + * @return void + */ + public function testApiUpdateCustomerOk() + { + extract($this->getCustomer()); + $data = [ + 'firstname' => 'Jean2', + 'lastname' => 'Bond2', + 'birth_date' => \date('1992-03-09'), + 'address' => '10 rue des toto', + 'email' => 'newcustomertoupdate@hotmail.fr', + 'password' => '123.Mot.De.P4sseNew', + 'phone' => '+33654821563', + 'country' => Country::luxembourg->value(), + 'city' => 'Connerré2', + 'postcode' => '72161', + 'opt_in' => 0 + ]; + + $newdata = [ + 'firstname' => 'Jean3', + 'lastname' => 'Bond3', + 'birth_date' => \date('1992-03-07'), + 'address' => '10 rue des toto3', + 'email' => 'newcustomertoupdate3@hotmail.fr', + 'password' => '123.Mot.De.P4sseNew3', + 'phone' => '+33654821564', + 'country' => Country::france->value(), + 'city' => 'Connerré3', + 'postcode' => '72163', + 'opt_in' => 1 + ]; + + $this->createNewCustomer($data, $brand); + + foreach ($data as $rowName => $row) { + $response = $this->callApiWithToken($service, 'patch', route( + 'apiV4.customers.update', + ['serviceCustomer' => $info->hashcode] + ), [$rowName => $newdata[$rowName]]); + $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( + $newdata[$rowName], + $json->json('data.' . $rowName), + "Failed asserting $rowName is equal to $newdata[$rowName]" + ); + } 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 null" + ); + } + } //end foreach + + // Address2 does not exists in crm. + $response = $this->callApiWithToken($service, 'patch', route( + 'apiV4.customers.update', + ['serviceCustomer' => $info->hashcode] + ), ['address2' => 'anything']); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::validationFailed->value(), $json->json('data.code')); + } + + /** + * Test customer update should fail if no changes are provided + * route: apiV4.customers.update + * + * @return void + */ + public function testApiUpdateCustomerShouldFailWhenNothingChanges() + { + extract($this->getCustomer()); + $data = [ + 'firstname' => 'Jean2', + 'lastname' => 'Bond2', + 'birth_date' => \date('1992-03-09'), + 'address' => '10 rue des toto', + 'email' => 'anewcustomertoupdate@hotmail.fr', + 'password' => '123.Mot.De.P4sseNew', + 'phone' => '+33654821563', + 'country' => Country::luxembourg->value(), + 'city' => 'Connerré2', + 'postcode' => '72161', + 'opt_in' => 0 + ]; + + $this->createNewCustomer($data, $brand); + + $response = $this->callApiWithToken($service, 'patch', route( + 'apiV4.customers.update', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value()); + } + + /** + * Test customer show ok + * route: apiV4.customers.show + * + * @return void + */ + public function testApiGetCustomerOk() + { + extract($this->getCustomer()); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.customers.show', + ['serviceCustomer' => $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')); + } + + /** + * Test customer show using email ok + * route: apiV4.customers.show + * + * @return void + */ + public function testApiGetCustomerByEmailOk() + { + extract($this->getCustomer()); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.customers.show', + ['serviceCustomer' => $customer->email] + )); + $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')); + } + + /** + * Test customer show not found + * route: apiV4.customers.show + * + * @return void + */ + public function testApiGetCustomerNotFound() + { + extract($this->getCustomer()); + $csClassName = \array_slice(explode('\\', get_class($customer)), -1)[0]; + + $uuid = Str::uuid(); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.customers.show', + ['serviceCustomer' => $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') + ); + } + + /** + * Test delete customer. + * route: apiV4.customer.destroy + * + * @return void + */ + public function testApiDestroyCustomer() + { + extract($this->getCustomer()); + $csClassName = \array_slice(explode('\\', get_class($customer)), -1)[0]; + + $service = $brand->services()->firstOrFail(); + $response = $this->callApiWithToken($service, 'delete', route( + 'apiV4.customers.destroy', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $this->assertEmpty($json->json('data')); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.customers.show', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + sprintf(ApiError::modelNotFound->label(), $csClassName), + $json->json('data.message'), + 'Customer should have been deleted.' + ); + } + + /** + * Test delete customer all of data. + * route: apiV4.customer.destroy + * + * @return void + */ + public function testApiCompletelyDestroyCustomer() + { + $customer = Customer::whereHas('logins', null, '>', 1)->firstOrFail(); + + /** @var \Illuminate\Database\Eloquent\Collection $logins */ + $logins = $customer->logins; + /** @var \App\Models\CustomerLogin $keepLogin */ + $keepLogin = $logins->pull(0); + + $logins->map(function (CustomerLogin $login) use ($customer) { + CustomerInfo::whereHas('customer', function ($q) use ($customer) { + $q->whereId($customer->id); + })->whereHas('brand', function ($q) use ($login) { + $q->whereId($login->brand->id); + })->delete(); + $login->delete(); + }); + + $this->assertTrue( + (bool)$customer->logins()->count(), + 'This test assumes customer belongs to multiple brand.' + ); + + $csClassName = \array_slice(explode('\\', get_class($customer)), -1)[0]; + + /** @var \App\Models\CustomerInfo $info */ + $info = $customer->info($keepLogin->brand, Customer::FIRST_OR_FAIL); + /** @var \App\Models\Service $service */ + $service = $keepLogin->brand->services()->firstOrFail(); + + $response = $this->callApiWithToken($service, 'delete', route( + 'apiV4.customers.destroy', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $this->assertEmpty($json->json('data')); + + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.customers.show', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals( + sprintf(ApiError::modelNotFound->label(), $csClassName), + $json->json('data.message'), + 'Customer should have been deleted.' + ); + } + + /** + * Make sute a customer exists and contains data + * + * @param array $data + * @param \App\Models\Brand $brand + * @return void + */ + private function createNewCustomer(array $data, Brand $brand) + { + $customer = Customer::whereEmail($data['email']) + ->whereHas('brands', function ($q) use ($brand) { + $q->where('brands.id', '=', $brand->id); + })->firstOrNew(); + $customer->fill($data); + $saved = $customer->save(); + + // * Delete Single optin if exists. + /** @var \App\Models\SingleOptin|null $singleOptin */ + $singleOptin = SingleOptin::query()->where('email', $data['email']) + ->whereHas('brand', function ($q) use ($brand) { + $q->where('brands.id', '=', $brand->id); + })->first(); + if ($singleOptin) { + $saved = $singleOptin->delete() && $saved; + } + + /** @var \App\Models\CustomerInfo */ + $customerInfo = $customer->info($brand, Customer::FIRST_OR_NEW); + $customerInfo->fill($data); + $customerInfo->brand()->associate($brand); + $customerInfo->customer()->associate($customer); + $saved = $customerInfo->save() && $saved; + + /** @var \App\Models\CustomerLogin */ + $customerLogin = $customer->login($brand, Customer::FIRST_OR_NEW); + $customerLogin->fill($data); + $customerLogin->brand()->associate($brand); + $customerLogin->customer()->associate($customer); + $saved = $customerLogin->save() && $saved; + + $this->assertTrue($saved, 'A new customer should have been created.'); + } +} diff --git a/tests/CRM/ApiV4/QuestionTest.php b/tests/CRM/ApiV4/QuestionTest.php new file mode 100644 index 0000000..85c54e7 --- /dev/null +++ b/tests/CRM/ApiV4/QuestionTest.php @@ -0,0 +1,310 @@ +getCustomer()); + + /** @var \Illuminate\Database\Eloquent\Collection $questions */ + $questions = Question::whereHas('brand', function ($q) use ($service) { + $q->whereId($service->brand->id); + })->with('answers')->get()->keyBy('id'); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.questions.index')); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + + $json->assertStructure([ + 'data' => [ + '*' => [ + 'id', + 'type', + 'question', + 'answers' => [ + '*' => [ + 'id', + 'answer' + ] + ], + ] + ] + ]); + + // Should contain questions with answers. + foreach ($json->json('data') as $question) { + $this->assertTrue($questions->contains($question['id'])); + $DBquestion = $questions->get($question['id']); + $this->assertEquals($DBquestion->type->value, $question['type']); + $this->assertEquals($DBquestion->question, $question['question']); + foreach ($question['answers'] as $answer) { + $answers = $DBquestion->answers->keyBy('id'); + $this->assertTrue($answers->contains($answer['id'])); + $DBanswer = $answers->get($answer['id']); + $this->assertEquals($DBanswer->answer, $answer['answer']); + } + } + } + + /** + * Test get all service questions with customer answers. + * route: apiV4.questions.indexWithResponse + * + * @return void + */ + public function testQuestionIndexWithAnswers() + { + extract($this->getCustomer(true, false)); + + /** @var \Illuminate\Database\Eloquent\Collection $questions */ + $questions = Question::whereHas('brand', function ($q) use ($service) { + $q->whereId($service->brand->id); + })->with('answers')->get()->keyBy('id'); + + + // * Make sure a customer answer is set. + $firstAnswer = $questions->first()->answers->first(); + $firstAnswer->load('question'); + + /** @var \App\Models\CustomerAnswer $custAnswer */ + $custAnswer = CustomerAnswer::whereHas('customer', function ($q) use ($customer) { + $q->whereId($customer->id); + })->whereHas('answer', function ($q) use ($firstAnswer) { + $q->whereId($firstAnswer->id); + })->firstOrNew(); + + $custAnswer->customer()->associate($customer); + $custAnswer->answer()->associate($firstAnswer); + $custAnswer->saveOrFail(); + + // * Call + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.questions.indexWithResponse', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + + $json->assertStructure([ + 'data' => [ + '*' => [ + 'id', + 'type', + 'question', + 'answers' => [ + '*' => [ + 'id', + 'answer' + ] + ], + 'customerAnswers' + ] + ] + ]); + + // Should contain questions with answers. + foreach ($json->json('data') as $question) { + $this->assertTrue($questions->contains($question['id'])); + $DBquestion = $questions->get($question['id']); + $this->assertEquals($DBquestion->type->value, $question['type']); + $this->assertEquals($DBquestion->question, $question['question']); + foreach ($question['answers'] as $answer) { + $answers = $DBquestion->answers->keyBy('id'); + $this->assertTrue($answers->contains($answer['id'])); + $DBanswer = $answers->get($answer['id']); + $this->assertEquals($DBanswer->answer, $answer['answer']); + } + if ($firstAnswer->question->id === $DBquestion->id) { + $this->assertEquals($question['customerAnswers'][0]['id'], $firstAnswer->id); + $this->assertEquals($question['customerAnswers'][0]['answer'], $firstAnswer->answer); + } + } + } + + /** + * Test get all service questions with customer answers when customer is unknown. + * route: apiV4.questions.indexWithResponse + * + * @return void + */ + public function testQuestionIndexWithAnswersWhenCustomerNotFound() + { + extract($this->getCustomer(true, false)); + $service = Service::whereHas('brand', function ($q) use ($brand) { + $q->where('id', '!=', $brand->id); + })->firstOrFail(); + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.questions.indexWithResponse', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + } + + /** + * Test get all service questions with customer answers when customer is unknown. + * route: apiV4.questions.unAnsweredBy + * + * @return void + */ + public function testCustomerUnAnsweredIndex() + { + extract($this->getCustomer(true, false)); + + /** @var \Illuminate\Database\Eloquent\Collection $questions */ + $questions = Question::whereHas('brand', function ($q) use ($service) { + $q->whereId($service->brand->id); + })->with('answers')->get()->keyBy('id'); + + + // * Make sure a customer answer is set. + $firstAnswer = $questions->first()->answers->first(); + $firstAnswer->load('question'); + + /** @var \App\Models\CustomerAnswer $custAnswer */ + $custAnswer = CustomerAnswer::whereHas('customer', function ($q) use ($customer) { + $q->whereId($customer->id); + })->whereHas('answer', function ($q) use ($firstAnswer) { + $q->whereId($firstAnswer->id); + })->firstOrNew(); + + $custAnswer->customer()->associate($customer); + $custAnswer->answer()->associate($firstAnswer); + $custAnswer->saveOrFail(); + + // * Call + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.questions.unAnsweredBy', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + + $json->assertStructure([ + 'data' => [ + '*' => [ + 'id', + 'type', + 'question', + 'answers' => [ + '*' => [ + 'id', + 'answer' + ] + ] + ] + ] + ]); + + // Should contain questions with answers. + foreach ($json->json('data') as $question) { + $this->assertTrue($questions->contains($question['id'])); + $DBquestion = $questions->get($question['id']); + $this->assertEquals($DBquestion->type->value, $question['type']); + $this->assertEquals($DBquestion->question, $question['question']); + foreach ($question['answers'] as $answer) { + $answers = $DBquestion->answers->keyBy('id'); + $this->assertTrue($answers->contains($answer['id'])); + $DBanswer = $answers->get($answer['id']); + $this->assertEquals($DBanswer->answer, $answer['answer']); + } + $this->assertNotEquals( + $firstAnswer->question->id, + $DBquestion->id, + 'This question has been answered by our user' + ); + } + } + + /** + * Test get all service questions unanswered by customer when customer is unknown. + * route: apiV4.questions.indexWithResponse + * + * @return void + */ + public function testUnAnsweredIndexWhenCustomerNotFound() + { + extract($this->getCustomer(true, false)); + $service = Service::whereHas('brand', function ($q) use ($brand) { + $q->where('id', '!=', $brand->id); + })->firstOrFail(); + $response = $this->callApiWithToken($service, 'get', route( + 'apiV4.questions.unAnsweredBy', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + } + + /** + * Test save customer answer. + * route: apiV4.customers.answers.store + * + * @return void + */ + public function testQuestionSetAnswer() + { + extract($this->getCustomer(true, false)); + $question = Question::whereHas('brand', function ($q) use ($brand) { + $q->whereId($brand->id); + })->whereHas('answers', function ($q) use ($customer) { + $q->whereDoesntHave('customers', function ($q) use ($customer) { + $q->whereId($customer->id); + }); + })->inRandomOrder()->firstOrFail(); + + $answer = $question->answers->first(); + $response = $this->callApiWithToken($service, 'post', route( + 'apiV4.customers.answers.store', + ['serviceCustomer' => $info->hashcode] + ), [ + 'question' => $question->id, + 'answers' => [$answer->id] + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + + $this->assertTrue(CustomerAnswer::whereHas('customer', function ($q) use ($customer) { + $q->whereId($customer->id); + })->whereHas('answer', function ($q) use ($answer) { + $q->whereId($answer->id); + })->exists(), 'Question should have been answered.'); + } + + /** + * Test get all save customer answers when customer is unknown. + * route: apiV4.questions.indexWithResponse + * + * @return void + */ + public function testQuestionSetAnswerWhenCustomerNotFound() + { + extract($this->getCustomer(true, false)); + $service = Service::whereHas('brand', function ($q) use ($brand) { + $q->where('id', '!=', $brand->id); + })->firstOrFail(); + $response = $this->callApiWithToken($service, 'post', route( + 'apiV4.customers.answers.store', + ['serviceCustomer' => $info->hashcode] + )); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + } +} diff --git a/tests/CRM/ApiV4/SingleOptinTest.php b/tests/CRM/ApiV4/SingleOptinTest.php new file mode 100644 index 0000000..e1ef010 --- /dev/null +++ b/tests/CRM/ApiV4/SingleOptinTest.php @@ -0,0 +1,169 @@ +callApiWithToken($service, 'post', route('apiV4.optin'), [ + 'email' => 'singleoptin@hotmail.fr', + 'opt_in' => 1 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $this->assertTrue( + SingleOptin::query()->where('email', 'singleoptin@hotmail.fr')->exists(), + 'Single optin should have been set for email "singleoptin@hotmail.fr"' + ); + } + + /** + * Test set single optin off + * route: apiV4.optin + * + * @return void + */ + public function testSetSingleOptinOff() + { + $service = Service::firstOrFail(); + + $response = $this->callApiWithToken($service, 'post', route('apiV4.optin'), [ + 'email' => 'singleoptin@hotmail.fr', + 'opt_in' => 0 + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $this->assertFalse( + SingleOptin::query()->where('email', 'singleoptin@hotmail.fr')->exists(), + 'Single optin should have been removed for email "singleoptin@hotmail.fr"' + ); + } + + /** + * Should throw an ApiError exception if trying to set + * optin to an email that already have an account. + * + * @return void + */ + public function testSingleOptinShouldFailIfCustomerExists() + { + $service = Service::firstOrFail(); + $this->storeCustomer($service); + + // * Make sure a single optin exists. + $singleOptin = SingleOptin::query()->where('email', 'singleoptin@hotmail.fr')->firstOrNew(); + $singleOptin->brand()->associate($service->brand); + $singleOptin->email = 'singleoptin@hotmail.fr'; + $singleOptin->saveOrFail(); + + $response = $this->callApiWithToken($service, 'post', route('apiV4.optin'), [ + 'email' => 'singleoptin@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')); + } + + /** + * Test set single optin off using hashcode + * route: apiV4.optout-hashcode + * + * @return void + */ + public function testSetSingleOptoutWithHashcode() + { + $service = Service::firstOrFail(); + + // * Make sure a single optin exists. + $singleOptin = SingleOptin::query()->where('email', 'singleoptout@hotmail.fr')->firstOrNew(); + $singleOptin->brand()->associate($service->brand); + $singleOptin->email = 'singleoptout@hotmail.fr'; + $singleOptin->saveOrFail(); + $singleOptin->refresh(); + + $response = $this->callApiWithToken($service, 'post', route('apiV4.optout-hashcode'), [ + 'hashcode' => $singleOptin->hashcode + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $this->assertFalse( + SingleOptin::query()->where('email', 'singleoptout@hotmail.fr')->exists(), + 'Single optin should have been removed for email "singleoptout@hotmail.fr"' + ); + } + + /** + * Test set single optin off using hashcode fails if does not exists + * route: apiV4.optout-hashcode + * + * @return void + */ + public function testSetSingleOptoutWithHashcodeFailsIfDoesNotExists() + { + $service = Service::firstOrFail(); + + // * Make sure a single optin exists. + $singleOptin = SingleOptin::query()->where('email', 'singleoptout@hotmail.fr')->firstOrNew(); + $singleOptin->brand()->associate($service->brand); + $singleOptin->email = 'singleoptout@hotmail.fr'; + $singleOptin->saveOrFail(); + $singleOptin->refresh(); + + // * Make sure a single optin does exists. + $singleOptin->delete(); + + $response = $this->callApiWithToken($service, 'post', route('apiV4.optout-hashcode'), [ + 'hashcode' => $singleOptin->hashcode + ]); + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals(ApiError::modelNotFound->value, $json->json('data.code')); + } + + /** + * Store a customer in DB + * + * @param \App\Models\Service $service + * @return void + */ + private function storeCustomer(Service $service) + { + $data = [ + 'firstname' => 'Jean', + 'lastname' => 'Bond', + 'birth_date' => \date('1992-03-08'), + 'address' => '10 rue des totos', + 'email' => 'singleoptin@hotmail.fr', + 'password' => '123.Mot.De.P4sse', + 'phone' => '+33654821562', + 'country' => Country::france->value, + 'city' => 'Connerré', + 'postcode' => '72160', + 'opt_in' => true + ]; + $this->callApiWithToken($service, 'post', route('apiV4.customers.store'), $data); + } +} diff --git a/tests/CRM/ApiV4/StatsTest.php b/tests/CRM/ApiV4/StatsTest.php new file mode 100644 index 0000000..bc2ef0b --- /dev/null +++ b/tests/CRM/ApiV4/StatsTest.php @@ -0,0 +1,209 @@ +callApiWithToken($service, 'get', route('apiV4.stats.brandStats'), [ + 'start' => Carbon::now()->subMonths(3)->isoFormat('YYYY-MM-DD'), + 'end' => Carbon::now()->isoFormat('YYYY-MM-DD') + ]); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $json->assertStructure([ + 'data' => [ + 'stats' => [ + '*' => [ + 'date', + 'active', + 'inactive', + 'old_accounts', + 'new_accounts', + 'total_accounts', + 'total_opt_in' + ] + ] + ] + ]); + } + + /** + * Test get stats by brand should fail with date bigger than 3 month. + * route: apiV4.stats.brandStats + * + * @return void + */ + public function testStatsByBrandShouldFailWithInvalidDate() + { + $service = Service::firstOrFail(); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.stats.brandStats'), [ + 'start' => Carbon::now()->subMonths(3)->subDay()->isoFormat('YYYY-MM-DD'), + 'end' => Carbon::now()->isoFormat('YYYY-MM-DD') + ]); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + } + + /** + * Test get stats by brand should fail with invalid date format. + * route: apiV4.stats.brandStats + * + * @return void + */ + public function testStatsByBrandShouldFailWithInvalidDateFormat() + { + $service = Service::firstOrFail(); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.stats.brandStats'), [ + 'start' => Carbon::now()->subMonths(3)->subDay()->isoFormat('YYYY-MM-DD HH:mmDD'), + 'end' => Carbon::now()->isoFormat('YYYY-MM-DD') + ]); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + } + + /** + * Test stats by question. + * route: apiV4.stats.questionStats + * + * @return void + */ + public function testStatsByQuestion() + { + $service = Service::firstOrFail(); + $question = Question::whereBrandId($service->brand->id)->firstOrFail(); + $answersIds = $question->answers()->pluck('id'); + $start = Carbon::now()->subDays(3); + $end = Carbon::now(); + + // * Make sure a customer answer is set. + /** @var \App\Models\CustomerAnswer $custAnswer */ + $custAnswer = CustomerAnswer::whereHas('answer', function ($q) use ($answersIds) { + $q->whereId($answersIds->first()); + })->firstOrNew(); + $custAnswer->customer()->associate($custAnswer->customer ?? Customer::query()->firstOrFail()); + $custAnswer->answer()->associate($answersIds->first()); + $custAnswer->saveOrFail(); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.stats.questionStats', $question), [ + 'start' => $start->isoFormat('YYYY-MM-DD'), + 'end' => $end->isoFormat('YYYY-MM-DD') + ]); + + $datesIndexes = []; + + do { + $datesIndexes[$start->isoFormat('YYYY-MM-DD')] = $answersIds + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + ->keys()->all(); + + $start = $start->addDay(); + } while ($start->lessThanOrEqualTo($end)); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::OK); + $json->assertStructure([ + 'data' => [ + 'answers' => [ + '*' => [ + 'id', + 'answer' + ] + ], + 'stats' => $datesIndexes + ] + ]); + } + + /** + * Test stats by question when it does not belong to brand. + * route: apiv.stats.questionStats + * + * @return void + */ + public function testStatsByQuestionShouldFailWithInvalidQuestion() + { + $service = Service::firstOrFail(); + $question = Question::where('brand_id', '!=', $service->brand->id)->firstOrFail(); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.stats.questionStats', $question), [ + 'start' => Carbon::now()->subMonths(3)->subDay()->isoFormat('YYYY-MM-DD'), + 'end' => Carbon::now()->isoFormat('YYYY-MM-DD') + ]); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + } + + /** + * Test stats by question should fail with date bigger than 3 month. + * route: apiV4.stats.questionStats + * + * @return void + */ + public function testStatsByQuestionShouldFailWithInvalidDate() + { + $service = Service::firstOrFail(); + $question = Question::whereBrandId($service->brand->id)->firstOrFail(); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.stats.questionStats', $question), [ + 'start' => Carbon::now()->subMonths(3)->subDay()->isoFormat('YYYY-MM-DD'), + 'end' => Carbon::now()->isoFormat('YYYY-MM-DD') + ]); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + } + + /** + * Test stats by question should fail with invalid date format. + * route: apiV4.stats.questionStats + * + * @return void + */ + public function testStatsByQuestionShouldFailWithInvalidDateFormat() + { + $service = Service::firstOrFail(); + $question = Question::whereBrandId($service->brand->id)->firstOrFail(); + + $response = $this->callApiWithToken($service, 'get', route('apiV4.stats.questionStats', $question), [ + 'start' => Carbon::now()->subMonths(3)->isoFormat('YYYY-MM Ddh'), + 'end' => Carbon::now()->isoFormat('YYYY-MM-DD') + ]); + + $json = $response->decodeResponseJson(); + $this->assertApiResponseStatusIs($json, ApiStatus::KO); + $this->assertEquals($json->json('data.code'), ApiError::validationFailed->value); + } +} diff --git a/tests/Client/CrmClientV2ArcadiaTest.php b/tests/Client/CrmClientArcadiaTest.php similarity index 96% rename from tests/Client/CrmClientV2ArcadiaTest.php rename to tests/Client/CrmClientArcadiaTest.php index 20bd166..532aedb 100644 --- a/tests/Client/CrmClientV2ArcadiaTest.php +++ b/tests/Client/CrmClientArcadiaTest.php @@ -5,12 +5,12 @@ namespace Tests\Client; use DOMDocument; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; -use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\CrmApiV4; use LDC\LDCCRM\Enums\ArcadiaOrderType; use LDC\LDCCRM\Models\ArcadiaMailDefault; use LDC\LDCCRM\Models\ArcadiaMailGame; -class CrmClientV2ArcadiaTest extends CrmClientTester +class CrmClientArcadiaTest extends CrmClientTester { /** * Send arcadia order default test. @@ -19,7 +19,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester */ public function testArcadiaSendOrderDefault() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $arcadiaOrderMail = new ArcadiaMailDefault([ @@ -34,7 +34,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester 'operation' => 16, 'label_type_support' => 'Send Departaimants', 'order_date' => \date('Y/m/d H:i:s'), - 'state_order' => ArcadiaOrderType::cb->value, + 'state_order' => ArcadiaOrderType::cb->value(), 'support_code' => 1, 'label_type_support' => 'Fond carte Aimanté', 'order_num' => 23, @@ -62,7 +62,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester 'operation' => 16, 'label_type_support' => 'Send Departaimants', 'order_date' => \date('Y/m/d H:i:s'), - 'state_order' => ArcadiaOrderType::cb->value, + 'state_order' => ArcadiaOrderType::cb->value(), 'support_code' => 1, 'label_type_support' => 'Fond carte Aimanté', 'order_num' => 23, @@ -97,7 +97,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester */ public function testArcadiaSendOrderGame() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $arcadiaOrderMail = new ArcadiaMailGame([ 'email' => static::$customer->email, @@ -125,7 +125,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester */ public function testArcadiaSendOrderGameNullString() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $arcadiaOrderMail = new ArcadiaMailGame([ 'email' => 'toto@gmail.com', @@ -204,7 +204,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester /** @var \Symfony\Component\Finder\SplFileInfo $file */ foreach ($files as $file) { - if (\strpos($file->getFilename(), 'v2-order-000000023-default-') === 0) { + if (\strpos($file->getFilename(), 'v4-order-000000023-default-') === 0) { $fileFound = true; // Enable user error handling. @@ -233,7 +233,7 @@ class CrmClientV2ArcadiaTest extends CrmClientTester /** @var \Symfony\Component\Finder\SplFileInfo $file */ foreach ($files as $file) { - if (\strpos($file->getFilename(), 'v2-order-000000000-game') === 0) { + if (\strpos($file->getFilename(), 'v4-order-000000000-game') === 0) { $fileFound = true; // Enable user error handling. diff --git a/tests/Client/CrmClientV2ClientTest.php b/tests/Client/CrmClientClientTest.php similarity index 75% rename from tests/Client/CrmClientV2ClientTest.php rename to tests/Client/CrmClientClientTest.php index a9bd9be..e12ad76 100644 --- a/tests/Client/CrmClientV2ClientTest.php +++ b/tests/Client/CrmClientClientTest.php @@ -2,7 +2,7 @@ namespace Tests\Client; -use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\CrmApiV4; use LDC\LDCCRM\Enums\ApiStatus; use LDC\LDCCRM\Enums\HttpMethods; use LDC\LDCCRM\Exceptions\AccessError; @@ -10,7 +10,7 @@ use LDC\LDCCRM\Models\Customer; use LDC\LDCCRM\Transport\ApiInterface; use LDC\LDCCRM\Transport\Request; -class CrmClientV2ClientTest extends CrmClientTester +class CrmClientClientTest extends CrmClientTester { /** * Test api interface. @@ -23,13 +23,13 @@ class CrmClientV2ClientTest extends CrmClientTester $interface = new ApiInterface(); $request = new Request( - HttpMethods::make(\strtolower($routes['apiV2.ping']['methods'][0])), - $routes['apiV2.ping']['uri'] + HttpMethods::make(\strtolower($routes['apiV4.ping']['methods'][0]), 'name'), + $routes['apiV4.ping']['uri'] ); $response = $interface->call($request); - $this->assertEquals(ApiStatus::OK->value, $response->status->value); + $this->assertEquals(ApiStatus::OK->value(), $response->status->value()); } /** @@ -39,7 +39,7 @@ class CrmClientV2ClientTest extends CrmClientTester */ public function testClientPing() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $this->assertTrue($api->ping()); } diff --git a/tests/Client/CrmClientV2ConfigTest.php b/tests/Client/CrmClientConfigTest.php similarity index 82% rename from tests/Client/CrmClientV2ConfigTest.php rename to tests/Client/CrmClientConfigTest.php index 4fb02c6..011d566 100644 --- a/tests/Client/CrmClientV2ConfigTest.php +++ b/tests/Client/CrmClientConfigTest.php @@ -4,7 +4,7 @@ namespace Tests\Client; use LDC\LDCCRM\Utils; -class CrmClientV2ConfigTest extends CrmClientTester +class CrmClientConfigTest extends CrmClientTester { /** * Test Utils::getConfig diff --git a/tests/Client/CrmClientV2CustomerTest.php b/tests/Client/CrmClientCustomerTest.php similarity index 89% rename from tests/Client/CrmClientV2CustomerTest.php rename to tests/Client/CrmClientCustomerTest.php index 4100c66..3c653fa 100644 --- a/tests/Client/CrmClientV2CustomerTest.php +++ b/tests/Client/CrmClientCustomerTest.php @@ -6,7 +6,7 @@ use App\Models\Brand; use App\Models\Customer as ModelsCustomer; use App\Models\SingleOptin; use Illuminate\Support\Str; -use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\CrmApiV4; use LDC\LDCCRM\Enums\ApiError; use LDC\LDCCRM\Enums\Country; use LDC\LDCCRM\Exceptions\AccessError; @@ -14,7 +14,7 @@ use LDC\LDCCRM\Exceptions\ApiErrorException; use LDC\LDCCRM\Exceptions\ValidationException; use LDC\LDCCRM\Models\Customer; -class CrmClientV2CustomerTest extends CrmClientTester +class CrmClientCustomerTest extends CrmClientTester { /** * Test get customer prop. @@ -90,7 +90,7 @@ class CrmClientV2CustomerTest extends CrmClientTester 'address2' => 'string', 'city' => 'string', 'postcode' => 'string', - 'country' => Country::afghanistan->value, + 'country' => Country::afghanistan->value(), 'opt_in' => 1, 'status' => 1, ]; @@ -121,7 +121,7 @@ class CrmClientV2CustomerTest extends CrmClientTester 'address2' => 'string', 'city' => 'string', 'postcode' => 'string', - 'country' => Country::afghanistan->value, + 'country' => Country::afghanistan->value(), 'opt_in' => true, 'status' => true, ]; @@ -132,7 +132,7 @@ class CrmClientV2CustomerTest extends CrmClientTester $jsonDecoded = \json_decode(\json_encode($client), true); $this->assertIsArray($jsonDecoded); unset($propTypes['password']); - $propTypes['country'] = Country::afghanistan->value; + $propTypes['country'] = Country::afghanistan->value(); foreach ($propTypes as $key => $value) { $this->assertArrayHasKey($key, $jsonDecoded); $this->assertEquals($value, $jsonDecoded[$key]); @@ -161,7 +161,7 @@ class CrmClientV2CustomerTest extends CrmClientTester 'address2' => 'string', 'city' => 18, 'postcode' => 'string', - 'country' => Country::afghanistan->value, + 'country' => Country::afghanistan->value(), 'opt_in' => true, 'status' => true, ]; @@ -192,7 +192,7 @@ class CrmClientV2CustomerTest extends CrmClientTester 'address2' => 'string', 'city' => 'string', 'postcode' => 'string', - 'country' => Country::afghanistan->value, + 'country' => Country::afghanistan->value(), 'opt_in' => true, 'status' => true, ]); @@ -221,7 +221,7 @@ class CrmClientV2CustomerTest extends CrmClientTester 'address2' => 'string', 'city' => 'string', 'postcode' => 'string', - 'country' => Country::afghanistan->value, + 'country' => Country::afghanistan->value(), 'opt_in' => true, 'status' => true, ]); @@ -236,7 +236,7 @@ class CrmClientV2CustomerTest extends CrmClientTester */ public function testCustomerGet() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $this->assertEquals(Customer::class, get_class($customer)); @@ -250,8 +250,8 @@ class CrmClientV2CustomerTest extends CrmClientTester $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->value, static::$info->country->value); - $this->assertEquals($customer->country->label, static::$info->country->label); + $this->assertEquals($customer->country->value(), static::$info->country->value()); + $this->assertEquals($customer->country->label(), static::$info->country->label()); } /** @@ -271,7 +271,7 @@ class CrmClientV2CustomerTest extends CrmClientTester $info = $customer->info(static::$brand, ModelsCustomer::FIRST_OR_FAIL); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew($info->hashcode); $this->assertTrue($customer->activate()); @@ -289,7 +289,7 @@ class CrmClientV2CustomerTest extends CrmClientTester */ public function testCustomerUpdateProps() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $customer->firstname = 'toto'; @@ -304,11 +304,17 @@ class CrmClientV2CustomerTest extends CrmClientTester $customer = $api->getCustomerOrNew(static::$info->hashcode); $this->assertEquals('Pouet', $customer->lastname); - $customer->country = Country::belgium->value; + $customer->country = Country::belgium->value(); $customer->save(); $customer = $api->getCustomerOrNew(static::$info->hashcode); - $this->assertEquals(Country::belgium->value, $customer->country); + $this->assertEquals(Country::belgium, $customer->country); + + $customer->country = Country::france->value(); + $customer->save(); + + $customer = $api->getCustomerOrNew(static::$info->hashcode); + $this->assertEquals(Country::france, $customer->country); $customer->email = 'pouet@gmail.com'; $customer->birth_date = '1992-03-08'; @@ -336,7 +342,7 @@ class CrmClientV2CustomerTest extends CrmClientTester */ public function testCustomerToggleOptin() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $customer->opt_in = false; @@ -370,7 +376,7 @@ class CrmClientV2CustomerTest extends CrmClientTester $customer->address = 'rue des totos'; $customer->city = 'Toto ville'; $customer->postcode = '72160'; - $customer->country = Country::france->value; + $customer->country = Country::france->value(); $this->assertTrue($customer->save()); @@ -385,7 +391,7 @@ class CrmClientV2CustomerTest extends CrmClientTester */ public function testCustomerLogin() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew('toto.delarogerie@gmail.com'); $this->assertTrue($customer->activate()); $this->assertTrue($customer->status); @@ -401,7 +407,7 @@ class CrmClientV2CustomerTest extends CrmClientTester public function testCustomerLoginUsingCustomerfromAnotherBrand() { $this->expectException(ApiErrorException::class); - $this->expectExceptionCode(ApiError::authFailed); + $this->expectExceptionCode(ApiError::authFailed->value()); $email = Str::uuid() . 'loginfromantoherbrand@hotmail.fr'; $data = [ 'firstname' => 'Jean2', @@ -411,7 +417,7 @@ class CrmClientV2CustomerTest extends CrmClientTester 'email' => $email, 'password' => '123.Mot.De.P4sseNew', 'phone' => '+33654821563', - 'country' => Country::luxembourg->value, + 'country' => Country::luxembourg->value(), 'city' => 'Connerré2', 'postcode' => '72161', 'opt_in' => 0 @@ -421,11 +427,11 @@ class CrmClientV2CustomerTest extends CrmClientTester $this->createNewCustomer($data, $brand); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $crmCustomer = $api->getCustomerOrNew($email); $this->assertNull($crmCustomer->hashcode); - CrmApiV2::login($email, '123.Mot.De.P4sseNew'); + CrmApiV4::login($email, '123.Mot.De.P4sseNew'); } /** @@ -447,7 +453,7 @@ class CrmClientV2CustomerTest extends CrmClientTester $customer->address = 'rue des totos'; $customer->city = 'Toto ville'; $customer->postcode = '72160'; - $customer->country = Country::france->value; + $customer->country = Country::france->value(); $this->assertTrue($customer->save()); @@ -476,13 +482,13 @@ class CrmClientV2CustomerTest extends CrmClientTester $customer->address = 'rue des totos'; $customer->city = 'Toto ville'; $customer->postcode = '72160'; - $customer->country = Country::france->value; + $customer->country = Country::france->value(); $this->assertTrue($customer->save()); $this->assertTrue($customer->delete()); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew('delete.delarogerie@gmail.com'); // * If hash code is null then it is a new customer @@ -499,18 +505,18 @@ class CrmClientV2CustomerTest extends CrmClientTester private function createNewCustomer(array $data, Brand $brand) { $customer = \App\Models\Customer::whereEmail($data['email']) - ->whereHas('brands', function ($q) use ($brand) { - $q->where('brands.id', '=', $brand->id); - })->firstOrNew(); + ->whereHas('brands', function ($q) use ($brand) { + $q->where('brands.id', '=', $brand->id); + })->firstOrNew(); $customer->fill($data); $saved = $customer->save(); // * Delete Single optin if exists. /** @var \App\Models\SingleOptin|null $singleOptin */ $singleOptin = SingleOptin::query()->where('email', $data['email']) - ->whereHas('brand', function ($q) use ($brand) { - $q->where('brands.id', '=', $brand->id); - })->first(); + ->whereHas('brand', function ($q) use ($brand) { + $q->where('brands.id', '=', $brand->id); + })->first(); if ($singleOptin) { $saved = $singleOptin->delete() && $saved; } diff --git a/tests/Client/CrmClientV2QuestionTest.php b/tests/Client/CrmClientQuestionTest.php similarity index 96% rename from tests/Client/CrmClientV2QuestionTest.php rename to tests/Client/CrmClientQuestionTest.php index bab560d..32bca48 100644 --- a/tests/Client/CrmClientV2QuestionTest.php +++ b/tests/Client/CrmClientQuestionTest.php @@ -4,11 +4,11 @@ namespace Tests\Client; use App\Models\CustomerAnswer; use App\Models\Question as ModelsQuestion; -use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\CrmApiV4; use LDC\LDCCRM\Models\Answer; use LDC\LDCCRM\Models\Question; -class CrmClientV2QuestionTest extends CrmClientTester +class CrmClientQuestionTest extends CrmClientTester { /** * Get All Questions @@ -49,7 +49,7 @@ class CrmClientV2QuestionTest extends CrmClientTester $this->assertArrayHasKey('question', $jsonDecoded); $this->assertEquals($question->question, $jsonDecoded['question']); $this->assertArrayHasKey('type', $jsonDecoded); - $this->assertEquals($question->type->value, $jsonDecoded['type']); + $this->assertEquals($question->type->value(), $jsonDecoded['type']); $this->assertArrayHasKey('answers', $jsonDecoded); $this->assertIsArray($jsonDecoded['answers']); foreach ($jsonDecoded['answers'] as $key => $answer) { @@ -89,7 +89,7 @@ class CrmClientV2QuestionTest extends CrmClientTester $custAnswer->answer()->associate($firstAnswer); $custAnswer->saveOrFail(); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $questions = Question::allWithCustomerAnswers($customer); @@ -156,7 +156,7 @@ class CrmClientV2QuestionTest extends CrmClientTester $custAnswer->answer()->associate($firstAnswer); $custAnswer->saveOrFail(); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $questions = Question::unansweredBy($customer); @@ -193,7 +193,7 @@ class CrmClientV2QuestionTest extends CrmClientTester $q->whereId(static::$brand->id); })->inRandomOrder()->firstOrFail(); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $result = Answer::setQuestionAnswers( $customer->hashcode, @@ -205,7 +205,7 @@ class CrmClientV2QuestionTest extends CrmClientTester ); $this->assertTrue($result); - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $questions = Question::unansweredBy($customer); @@ -239,7 +239,7 @@ class CrmClientV2QuestionTest extends CrmClientTester { $unWantedQuestion = Question::all()[0]; - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $questions = Question::unansweredBy($customer); @@ -255,7 +255,7 @@ class CrmClientV2QuestionTest extends CrmClientTester */ public function testGetAllQuestionsWithCustomerAnswersShouldHaveAnswers() { - $api = new CrmApiV2(); + $api = new CrmApiV4(); $customer = $api->getCustomerOrNew(static::$info->hashcode); $questions = Question::allWithCustomerAnswers($customer); $this->assertNotEmpty($questions[0]['customerAnswers'], 'Should have customer answers'); diff --git a/tests/Client/CrmClientV2RouterTest.php b/tests/Client/CrmClientRouterTest.php similarity index 93% rename from tests/Client/CrmClientV2RouterTest.php rename to tests/Client/CrmClientRouterTest.php index 08bf8e5..a2970e7 100644 --- a/tests/Client/CrmClientV2RouterTest.php +++ b/tests/Client/CrmClientRouterTest.php @@ -5,7 +5,7 @@ namespace Tests\Client; use Illuminate\Support\Facades\Route; use LDC\LDCCRM\Utils; -class CrmClientV2RouterTest extends CrmClientTester +class CrmClientRouterTest extends CrmClientTester { /** * Test all Routes @@ -18,7 +18,7 @@ class CrmClientV2RouterTest extends CrmClientTester /** @var \Illuminate\Routing\Route */ foreach (Route::getRoutes() as $route) { - if (\strpos($route->getPrefix(), 'v2') !== false) { + if (\strpos($route->getPrefix(), 'v4') !== false) { $this->assertArrayHasKey($route->getName(), $routes); $this->assertArrayHasKey('uri', $routes[$route->getName()]); $this->assertArrayHasKey('methods', $routes[$route->getName()]); diff --git a/tests/Client/CrmClientV2SingleOptinTest.php b/tests/Client/CrmClientSingleOptinTest.php similarity index 93% rename from tests/Client/CrmClientV2SingleOptinTest.php rename to tests/Client/CrmClientSingleOptinTest.php index 1f32da5..900f63f 100644 --- a/tests/Client/CrmClientV2SingleOptinTest.php +++ b/tests/Client/CrmClientSingleOptinTest.php @@ -4,14 +4,14 @@ namespace Tests\Client; use App\Models\SingleOptin as ModelsSingleOptin; use Illuminate\Support\Str; -use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\CrmApiV4; use LDC\LDCCRM\Enums\ApiError; use LDC\LDCCRM\Enums\Country; use LDC\LDCCRM\Exceptions\ApiErrorException; use LDC\LDCCRM\Models\Customer; use LDC\LDCCRM\Models\SingleOptin; -class CrmClientV2SingleOptinTest extends CrmClientTester +class CrmClientSingleOptinTest extends CrmClientTester { /** * Create and remove single optin test @@ -27,7 +27,7 @@ class CrmClientV2SingleOptinTest extends CrmClientTester $this->assertTrue(ModelsSingleOptin::query()->where('email', 'singleoptinrandom@gmail.com')->exists()); // Set single optin again should not fail. - $this->assertTrue(CrmApiV2::setStatus('singleoptinrandom@gmail.com', true)); + $this->assertTrue(CrmApiV4::setStatus('singleoptinrandom@gmail.com', true)); // Remove single optin test and check. $this->assertTrue(SingleOptin::setStatus('singleoptinrandom@gmail.com', false)); @@ -56,7 +56,7 @@ class CrmClientV2SingleOptinTest extends CrmClientTester $customer->address = 'rue des totos'; $customer->city = 'Toto ville'; $customer->postcode = '72160'; - $customer->country = Country::france->value; + $customer->country = Country::france->value(); $this->assertTrue($customer->save()); $this->assertFalse(SingleOptin::setStatus($randomMail, true)); diff --git a/tests/Client/CrmClientV2StatsTest.php b/tests/Client/CrmClientStatsTest.php similarity index 99% rename from tests/Client/CrmClientV2StatsTest.php rename to tests/Client/CrmClientStatsTest.php index e79a8dc..39e16e6 100644 --- a/tests/Client/CrmClientV2StatsTest.php +++ b/tests/Client/CrmClientStatsTest.php @@ -9,7 +9,7 @@ use LDC\LDCCRM\Exceptions\ApiErrorException; use LDC\LDCCRM\Models\Question as ModelsQuestion; use LDC\LDCCRM\Models\Stats; -class CrmClientV2StatsTest extends CrmClientTester +class CrmClientStatsTest extends CrmClientTester { /** * Get stats by brand diff --git a/tests/Client/CrmClientTester.php b/tests/Client/CrmClientTester.php index d6b25fc..ec329a7 100644 --- a/tests/Client/CrmClientTester.php +++ b/tests/Client/CrmClientTester.php @@ -5,7 +5,7 @@ namespace Tests\Client; use App\Models\Customer; use Illuminate\Support\Facades\Crypt; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; -use LDC\LDCCRM\CrmApiV2; +use LDC\LDCCRM\CrmApiV4; use Tests\CreatesApplication; abstract class CrmClientTester extends BaseTestCase @@ -45,7 +45,7 @@ abstract class CrmClientTester extends BaseTestCase self::$login = $login; self::$info = $info; } - new CrmApiV2( + new CrmApiV4( config('app.url'), self::$service->bearer_token, Crypt::decryptString(self::$service->bearer_pwd) @@ -84,6 +84,6 @@ abstract class CrmClientTester extends BaseTestCase */ protected function getClientRoutes(): array { - return \json_decode(\file_get_contents(__DIR__ . '/../../clients/V2/src/routes.json'), true); + return \json_decode(\file_get_contents(__DIR__ . '/../../client/src/routes.json'), true); } } diff --git a/tests/Client/HelperTest.php b/tests/Client/HelperTest.php index 1cf670a..c07bc07 100644 --- a/tests/Client/HelperTest.php +++ b/tests/Client/HelperTest.php @@ -19,7 +19,7 @@ class HelperTest extends TestCase */ public function testGetRoute() { - $request = $this->makeRequest('apiV2.ping'); + $request = $this->makeRequest('apiV4.ping'); $this->assertInstanceOf(\Illuminate\Routing\Route::class, Helper::getRoute($request)); } @@ -30,7 +30,7 @@ class HelperTest extends TestCase */ public function testGetRoutePrefix() { - $request = $this->makeRequest('apiV2.ping'); + $request = $this->makeRequest('apiV4.ping'); $this->assertEquals($request->route()->getPrefix(), Helper::getRoutePrefix($request)); } @@ -41,19 +41,19 @@ class HelperTest extends TestCase */ public function testGetRouteName() { - $request = $this->makeRequest('apiV2.ping'); + $request = $this->makeRequest('apiV4.ping'); $this->assertEquals($request->route()->getName(), Helper::getRouteName($request)); } /** - * Helper::isRequestForApiV2 test + * Helper::isRequestForApiV4 test * * @return void */ - public function testIsRequestForApiV2() + public function testIsRequestForApiV4() { - $request = $this->makeRequest('apiV2.ping'); - $this->assertTrue(Helper::isRequestForApiV2($request)); + $request = $this->makeRequest('apiV4.ping'); + $this->assertTrue(Helper::isRequestForApiV4($request)); } /** diff --git a/tests/coverage/Console/Commands/DeleteUnconfirmedCustomers.php.html b/tests/coverage/Console/Commands/DeleteUnconfirmedCustomers.php.html deleted file mode 100644 index 09fb9fd..0000000 --- a/tests/coverage/Console/Commands/DeleteUnconfirmedCustomers.php.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/DeleteUnconfirmedCustomers.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
DeleteUnconfirmedCustomers
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console\Commands;
4
5use App\Models\Customer;
6use App\Models\CustomerLogin;
7use App\Models\CustomerOptInHistory;
8use Carbon\Carbon;
9use Illuminate\Console\Command;
10use Illuminate\Support\Facades\DB;
11use Illuminate\Support\Facades\Log;
12
13/**
14 * @codeCoverageIgnore
15 */
16class DeleteUnconfirmedCustomers extends Command
17{
18    /**
19     * The name and signature of the console command.
20     *
21     * @var string
22     */
23    protected $signature = 'customer:deleteuc';
24
25    /**
26     * The console command description.
27     *
28     * @var string
29     */
30    protected $description = 'Delete the customers that haven\'t
31    confirmed their acocunt within 24 hours after the registeration.';
32
33    /**
34     * Execute the console command.
35     *
36     * @return integer
37     */
38    public function handle()
39    {
40        /** @var \Illuminate\Database\Eloquent\Collection $customersLogins */
41        $customersLogins = CustomerLogin::whereStatus(false)
42            ->where('created_at', '<', Carbon::now()->subDay(1))
43            ->with(['brand', 'customer'])
44            ->get();
45        $customersLogins->map(function (CustomerLogin $customerLogin) {
46            Log::info($customerLogin->customer_id);
47            DB::transaction(function () use ($customerLogin) {
48                /** @var \App\Models\Customer $customer */
49                $customer = $customerLogin->customer;
50
51                /** @var \App\Models\Brand */
52                $brand = $customerLogin->brand;
53
54                // * Suppression de ses infos de login pour la marque.
55                $customer->info($brand, Customer::FIRST_OR_FAIL)->deleteOrFail();
56                $customer->login($brand, Customer::FIRST_OR_FAIL)->deleteOrFail();
57
58                // * Si le customer n'a plus d'info il n'a donc plus besoin de compte en crm.
59                if (!$customer->infos()->count()) {
60                    // * Suppression du customer une fois tout le reste supprimé.
61                    $customer->optInHistory()->get()
62                        ->map(function (CustomerOptInHistory $customerOptInHistory) {
63                            $customerOptInHistory->deleteOrFail();
64                        });
65                    $customer->deleteOrFail();
66                }
67            });
68        });
69        return 0;
70    }
71}
- - - -
- - - - - - diff --git a/tests/coverage/Console/Commands/LdcWebStatusChecker.php.html b/tests/coverage/Console/Commands/LdcWebStatusChecker.php.html deleted file mode 100644 index 8dee930..0000000 --- a/tests/coverage/Console/Commands/LdcWebStatusChecker.php.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/LdcWebStatusChecker.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
LdcWebStatusChecker
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
5
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console\Commands;
4
5use Illuminate\Console\Command;
6
7/**
8 * Command Name: LDC Web Status Checker
9 * Description: Envoi des infos Serveur/Laravel vers LDC Web
10 * Version: 1.1
11 * Author:  Vincent BRIDIER
12 * Author URI: vincent.bridier@ldc.fr
13 *
14 * @codeCoverageIgnore
15 */
16class LdcWebStatusChecker extends Command
17{
18    /**
19     * The name and signature of the console command.
20     *
21     * @var string
22     */
23    protected $signature = 'ldcweb:statuschecker';
24
25    /**
26     * The console command description.
27     *
28     * @var string
29     */
30    protected $description = 'Envoi des infos Serveur/Laravel vers LDC Web';
31
32    /**
33     * Execute the console command.
34     *
35     * @return mixed
36     */
37    public function handle()
38    {
39        $data         = [];
40        $data['uuid'] = config('ldcweb.project_uuid');
41
42        // Sitemap.xml ?
43        if (file_exists(public_path() . '/sitemap.xml')) {
44            $data['sitemap'] = 'true';
45        } else {
46            $data['sitemap'] = 'false';
47        }
48
49        // Robots.txt ?
50        if (file_exists(public_path() . '/robots.txt')) {
51            $data['robots'] = 'true';
52        } else {
53            $data['robots'] = 'false';
54        }
55
56        // Check logs.
57        $data['logs'] = 0;
58        $patherror    = storage_path('/logs');
59        $files        = glob($patherror . '/*.log');
60        if (count($files) > 0) {
61            foreach ($files as $file) {
62                $data['logs'] = $data['logs'] + filesize($file);
63            }
64        }
65
66        $data['code']['version'] = app()->version();
67
68        // Send by cURL.
69        $url     = 'https://ldc-web.ldc.fr/api/status-checker';
70        $ch      = curl_init($url);
71        $payload = ['token' => 'CE8S52Qq7stf5k8frF4T', 'datas' => json_encode($data)];
72        curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
73        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
74        curl_exec($ch);
75        curl_close($ch);
76        return 0;
77    }
78}
- - - -
- - - - - - diff --git a/tests/coverage/Console/Commands/MigrateCrmUsers.php.html b/tests/coverage/Console/Commands/MigrateCrmUsers.php.html deleted file mode 100644 index 02ccd51..0000000 --- a/tests/coverage/Console/Commands/MigrateCrmUsers.php.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/MigrateCrmUsers.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
MigrateCrmUsers
n/a
0 / 0
n/a
0 / 0
6
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
6
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console\Commands;
4
5use App\Lib\MigrateOldData;
6use App\Models\Brand;
7use Exception;
8use Illuminate\Console\Command;
9use Illuminate\Support\Facades\DB;
10
11/**
12 * @codeCoverageIgnore
13 */
14class MigrateCrmUsers extends Command
15{
16    /**
17     * The name and signature of the console command.
18     *
19     * @var string
20     */
21    protected $signature = 'migrate:crm';
22
23    /**
24     * The console command description.
25     *
26     * @var string
27     */
28    protected $description = 'Migrate crm users';
29
30
31    /**
32     * Execute the console command.
33     *
34     * @return integer
35     */
36    public function handle()
37    {
38        try {
39            $table = $this->ask('What is the tmp table name with users data ?');
40
41            try {
42                DB::connection(config('database.oldDB'))->table($table)->exists();
43            } catch (Exception $e) {
44                $this->error(sprintf('%s table not found on old crm database', $table));
45                return -1;
46            }
47
48            $brandname = $this->ask('What is the brand name ?');
49            $brand     = Brand::where('name', 'LIKE', "%$brandname%")->first();
50            if (!$brand) {
51                if (!$this->confirm(sprintf('Would you like to use %s ? ', $brandname))) {
52                    $this->info('Command cancelled');
53                    return 0;
54                }
55                if ($this->confirm('Would you like to create a new one ?')) {
56                    $brand       = new Brand();
57                    $brand->name = $brandname;
58                    $brand->save();
59                } else {
60                    $this->info('Command cancelled');
61                    return 0;
62                }
63            }
64
65            $mgOldData = new MigrateOldData();
66            $mgOldData->runMigration($brand, $table);
67        } catch (Exception $e) {
68            $this->error(sprintf('Failed %s', $e));
69            return -1;
70        }//end try
71        return 0;
72    }
73}
- - - -
- - - - - - diff --git a/tests/coverage/Console/Commands/StartStatJobs.php.html b/tests/coverage/Console/Commands/StartStatJobs.php.html deleted file mode 100644 index a882370..0000000 --- a/tests/coverage/Console/Commands/StartStatJobs.php.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/StartStatJobs.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
StartStatJobs
n/a
0 / 0
n/a
0 / 0
1
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console\Commands;
4
5use App\Jobs\StartStatJobs as JobsStartStatJobs;
6use Illuminate\Console\Command;
7
8/**
9 * @codeCoverageIgnore
10 */
11class StartStatJobs extends Command
12{
13    /**
14     * The name and signature of the console command.
15     *
16     * @var string
17     */
18    protected $signature = 'stats:collect';
19
20    /**
21     * The console command description.
22     *
23     * @var string
24     */
25    protected $description = 'Force push the collect stats jobs into the queue (stats)';
26
27    /**
28     * Execute the console command.
29     *
30     * @return integer
31     */
32    public function handle()
33    {
34        \dispatch(new JobsStartStatJobs())->afterResponse();
35        return 0;
36    }
37}
- - - -
- - - - - - diff --git a/tests/coverage/Console/Commands/UserCreate.php.html b/tests/coverage/Console/Commands/UserCreate.php.html deleted file mode 100644 index 3c1a263..0000000 --- a/tests/coverage/Console/Commands/UserCreate.php.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/UserCreate.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
UserCreate
n/a
0 / 0
n/a
0 / 0
1
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console\Commands;
4
5use App\Models\User;
6use Illuminate\Console\Command;
7use Illuminate\Support\Facades\Hash;
8
9/**
10 * @codeCoverageIgnore
11 */
12class UserCreate extends Command
13{
14    /**
15     * The name and signature of the console command.
16     *
17     * @var string
18     */
19    protected $signature = 'user:create';
20
21    /**
22     * The console command description.
23     *
24     * @var string
25     */
26    protected $description = 'Command description';
27
28    /**
29     * Execute the console command.
30     *
31     * @return integer
32     */
33    public function handle()
34    {
35        $firstname = $this->ask('What is the firstname?');
36        $lastname  = $this->ask('What is the lastname?');
37        $email     = $this->ask('What is the email?');
38        $pwd       = $this->secret('What is the password?');
39
40        $user            = new User();
41        $user->firstname = $firstname;
42        $user->lastname  = $lastname;
43        $user->email     = $email;
44        $user->password  = Hash::make($pwd);
45        $user->save();
46
47        $this->info('User created.');
48        return 0;
49    }
50}
- - - -
- - - - - - diff --git a/tests/coverage/Console/Commands/dashboard.html b/tests/coverage/Console/Commands/dashboard.html deleted file mode 100644 index 23c6501..0000000 --- a/tests/coverage/Console/Commands/dashboard.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Console/Commands - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Console/Commands/index.html b/tests/coverage/Console/Commands/index.html deleted file mode 100644 index 9ba6c8d..0000000 --- a/tests/coverage/Console/Commands/index.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
DeleteUnconfirmedCustomers.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
GenerateCrmClient.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
LdcWebStatusChecker.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
MigrateCrmUsers.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StartStatJobs.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
UserCreate.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Console/Kernel.php.html b/tests/coverage/Console/Kernel.php.html deleted file mode 100644 index 6e8b288..0000000 --- a/tests/coverage/Console/Kernel.php.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console/Kernel.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
Kernel
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 schedule
n/a
0 / 0
n/a
0 / 0
1
 commands
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console;
4
5use App\Console\Commands\DeleteUnconfirmedCustomers;
6use App\Jobs\StartStatJobs;
7use Illuminate\Console\Scheduling\Schedule;
8use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
9
10/**
11 * @codeCoverageIgnore
12 */
13class Kernel extends ConsoleKernel
14{
15    /**
16     * The Artisan commands provided by your application.
17     *
18     * @var array
19     */
20    protected $commands = [];
21
22    /**
23     * Define the application's command schedule.
24     *
25     * @param  \Illuminate\Console\Scheduling\Schedule $schedule
26     * @return void
27     */
28    protected function schedule(Schedule $schedule)
29    {
30        $schedule->job(StartStatJobs::class)->dailyAt('03:30');
31        $schedule->command(DeleteUnconfirmedCustomers::class)->dailyAt('05:30');
32    }
33
34    /**
35     * Register the commands for the application.
36     *
37     * @return void
38     */
39    protected function commands()
40    {
41        $this->load(__DIR__ . '/Commands');
42
43        require base_path('routes/console.php');
44    }
45}
- - - -
- - - - - - diff --git a/tests/coverage/Console/dashboard.html b/tests/coverage/Console/dashboard.html deleted file mode 100644 index b38b1cf..0000000 --- a/tests/coverage/Console/dashboard.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Console - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Console/index.html b/tests/coverage/Console/index.html deleted file mode 100644 index c37dbfa..0000000 --- a/tests/coverage/Console/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Console - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
Commands
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
Kernel.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Enums/ApiError.php.html b/tests/coverage/Enums/ApiError.php.html deleted file mode 100644 index cfd4f5e..0000000 --- a/tests/coverage/Enums/ApiError.php.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums/ApiError.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ApiError
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 values
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 labels
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Enums;
4
5use Kwaadpepper\Enum\BaseEnumRoutable;
6
7/**
8 * This describes an API error.
9 *
10 * @method static self modelNotFound()
11 * The request model was not found (typically the customer)
12 *
13 * @method static self validationFailed()
14 * This means some data could not be validated,
15 * you may have to look into message to get the details
16 *
17 * @method static self customerExists()
18 * The customer already exists
19 *
20 * @method static self customerNotActivated()
21 * The customer account is not activated and cannot perform
22 * this action
23 *
24 * @method static self customerAlreadyActive()
25 * You have requested to activate a customer account,
26 * but it is already active
27 *
28 * @method static self singleOptinCustomerExists()
29 * You have requested to create a single optin for an email,
30 * but it already exists
31 *
32 * @method static self apiAuthFailed()
33 * The authentification to the api failed. You may have to
34 * check into config or .env file for your service credentials
35 *
36 * @method static self authFailed()
37 * The authentification for this customer failed because of wrong credentials
38 *
39 * @method static self methodNotAllowed()
40 * This http method is not allowed
41 *
42 * @method static self wrongTimeStamp()
43 * The timestamp given is wrong. This would happen in case if
44 * a replay attack scenario. Timestamps are provided in headers
45 * and in the http body
46 *
47 * @method static self missingParameter()
48 * The request is missing some parameters
49 *
50 * @method static self throttleRequestsException()
51 * Too many requests have been attempted. The is a throttle security.
52 * Normally you should not trigger this.
53 * If this happens fill a ticket on https://supportweb.ldc.fr
54 * with some technical details
55 *
56 * @method static self authorizationException()
57 * You are maybe trying to access data that is out of your range.
58 * This should not happen normally, it is here to allow you to handle this case.
59 * If this happens fill a ticket on https://supportweb.ldc.fr
60 * with some technical details
61 *
62 * @package LDCCrmClient
63 * @subpackage enums
64 */
65class ApiError extends BaseEnumRoutable
66{
67    /**
68     * Valeurs des possibilités de cet Ã©numération (value)
69     *
70     * @return array
71     */
72    public static function values(): array
73    {
74        return [
75            'modelNotFound' => 101,
76            'validationFailed' => 102,
77            'customerExists' => 103,
78            'customerAlreadyActive' => 110,
79            'customerNotActivated' => 116,
80            'authFailed' => 117,
81            'apiAuthFailed' => 119,
82            'singleOptinCustomerExists' => 120,
83            'methodNotAllowed' => 150,
84            'wrongTimeStamp' => 151,
85            'missingParameter' => 400,
86            'authorizationException' => 403,
87            'throttleRequestsException' => 429
88        ];
89    }
90
91    /**
92     * Libellés des possibilités de cet Ã©numération (label)
93     *
94     * @return array
95     */
96    public static function labels(): array
97    {
98        return [
99            'modelNotFound' => '%s introuvable.',
100            'validationFailed' => 'Impossible de valider les données',
101            'customerExists' => 'L\'utilisateur est déjà enregistré sur ce site',
102            'customerNotActivated' => 'Le compte n\'est pas activé',
103            'customerAlreadyActive' => 'Le compte est déjà activé',
104            'singleOptinCustomerExists' => 'Le single optin ne peut Ãªtre ajouté car un compte existe.',
105            'authFailed' => 'Echec de l\'authentification du compte',
106            'apiAuthFailed' => 'Echec de l\'authentification client Ã  l\'Api LDC-CRM pour ce service',
107            'methodNotAllowed' => 'This method is not allowed for this request',
108            'wrongTimeStamp' => 'Your request is malformed, please check timestamp header and parameter',
109            'missingParameter' => 'The request is missing some parameters',
110            'authorizationException' => 'You are not autorized to access this',
111            'throttleRequestsException' => 'Throttle: too many request have been attempted, please retry later'
112        ];
113    }
114}
- - - -
- - - - - - diff --git a/tests/coverage/Enums/ArcadiaMailType.php.html b/tests/coverage/Enums/ArcadiaMailType.php.html deleted file mode 100644 index 713de9d..0000000 --- a/tests/coverage/Enums/ArcadiaMailType.php.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums/ArcadiaMailType.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ArcadiaMailType
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 values
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Enums;
4
5use Kwaadpepper\Enum\BaseEnumRoutable;
6
7/**
8 * @method static self default()
9 * @method static self game()
10 *
11 * @package LDCCrmClient
12 * @subpackage enums
13 */
14class ArcadiaMailType extends BaseEnumRoutable
15{
16    /**
17     * Valeurs des possibilités de cet Ã©numération (value)
18     *
19     * @return array
20     */
21    public static function values(): array
22    {
23        return [
24            'default' => 1,
25            'game' => 2
26        ];
27    }
28}
- - - -
- - - - - - diff --git a/tests/coverage/Enums/ArcadiaOrderType.php.html b/tests/coverage/Enums/ArcadiaOrderType.php.html deleted file mode 100644 index 3244a06..0000000 --- a/tests/coverage/Enums/ArcadiaOrderType.php.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums/ArcadiaOrderType.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ArcadiaOrderType
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 values
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Enums;
4
5use Kwaadpepper\Enum\BaseEnumRoutable;
6
7/**
8 * @method static self cb()
9 * @method static self check()
10 *
11 * @package LDCCrmClient
12 * @subpackage enums
13 */
14class ArcadiaOrderType extends BaseEnumRoutable
15{
16    /**
17     * Valeurs des possibilités de cet Ã©numération (value)
18     *
19     * @return array
20     */
21    public static function values(): array
22    {
23        return [
24            'cb' => 0,
25            'check' => 1
26        ];
27    }
28}
- - - -
- - - - - - diff --git a/tests/coverage/Enums/Country.php.html b/tests/coverage/Enums/Country.php.html deleted file mode 100644 index 7ac5930..0000000 --- a/tests/coverage/Enums/Country.php.html +++ /dev/null @@ -1,1154 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums/Country.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 95.83% covered (success) -
-
-
95.83%
23 / 24
-
- 85.71% covered (warning) -
-
-
85.71%
6 / 7
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Country
-
- 95.83% covered (success) -
-
-
95.83%
23 / 24
-
- 85.71% covered (warning) -
-
-
85.71%
6 / 7
8
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 values
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 labels
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getValidCountriesLabel
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getValidIsoAplha2Codes
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getValidCountries
-
- 100.00% covered (success) -
-
-
100.00%
14 / 14
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 isoCodes
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 __get
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2.15
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Enums;
4
5use Kwaadpepper\Enum\BaseEnumRoutable;
6
7/**
8 * Liste de Pays
9 * @source https://datahub.io/core/country-list Data Hub country list
10 * @method static self afghanistan()
11 * @method static self alandIslands()
12 * @method static self albania()
13 * @method static self algeria()
14 * @method static self americanSamoa()
15 * @method static self andorra()
16 * @method static self angola()
17 * @method static self anguilla()
18 * @method static self antarctica()
19 * @method static self antiguaAndBarbuda()
20 * @method static self argentina()
21 * @method static self armenia()
22 * @method static self aruba()
23 * @method static self australia()
24 * @method static self austria()
25 * @method static self azerbaijan()
26 * @method static self bahrain()
27 * @method static self bahamas()
28 * @method static self bangladesh()
29 * @method static self barbados()
30 * @method static self belarus()
31 * @method static self belgium()
32 * @method static self belize()
33 * @method static self benin()
34 * @method static self bermuda()
35 * @method static self bhutan()
36 * @method static self boliviaPlurinationalStateOf()
37 * @method static self bonaireSintEustatiusAndSaba()
38 * @method static self bosniaAndHerzegovina()
39 * @method static self botswana()
40 * @method static self bouvetIsland()
41 * @method static self brazil()
42 * @method static self britishIndianOceanTerritory()
43 * @method static self bruneiDarussalam()
44 * @method static self bulgaria()
45 * @method static self burkinaFaso()
46 * @method static self burundi()
47 * @method static self cambodia()
48 * @method static self cameroon()
49 * @method static self canada()
50 * @method static self capeVerde()
51 * @method static self caymanIslands()
52 * @method static self centralAfricanRepublic()
53 * @method static self chad()
54 * @method static self chile()
55 * @method static self china()
56 * @method static self christmasIsland()
57 * @method static self cocosKeelingIslands()
58 * @method static self colombia()
59 * @method static self comoros()
60 * @method static self congo()
61 * @method static self congoTheDemocraticRepublicOfThe()
62 * @method static self cookIslands()
63 * @method static self costaRica()
64 * @method static self coteDivoire()
65 * @method static self croatia()
66 * @method static self cuba()
67 * @method static self curacao()
68 * @method static self cyprus()
69 * @method static self czechRepublic()
70 * @method static self denmark()
71 * @method static self djibouti()
72 * @method static self dominica()
73 * @method static self dominicanRepublic()
74 * @method static self ecuador()
75 * @method static self egypt()
76 * @method static self elSalvador()
77 * @method static self equatorialGuinea()
78 * @method static self eritrea()
79 * @method static self estonia()
80 * @method static self ethiopia()
81 * @method static self falklandIslandsMalvinas()
82 * @method static self faroeIslands()
83 * @method static self fiji()
84 * @method static self finland()
85 * @method static self france()
86 * @method static self frenchGuiana()
87 * @method static self frenchPolynesia()
88 * @method static self frenchSouthernTerritories()
89 * @method static self gabon()
90 * @method static self gambia()
91 * @method static self georgia()
92 * @method static self germany()
93 * @method static self ghana()
94 * @method static self gibraltar()
95 * @method static self greece()
96 * @method static self greenland()
97 * @method static self grenada()
98 * @method static self guadeloupe()
99 * @method static self guam()
100 * @method static self guatemala()
101 * @method static self guernsey()
102 * @method static self guinea()
103 * @method static self guineaBissau()
104 * @method static self guyana()
105 * @method static self haiti()
106 * @method static self heardIslandAndMcdonaldIslands()
107 * @method static self holySeeVaticanCityState()
108 * @method static self honduras()
109 * @method static self hongKong()
110 * @method static self hungary()
111 * @method static self iceland()
112 * @method static self india()
113 * @method static self indonesia()
114 * @method static self iranIslamicRepublicOf()
115 * @method static self iraq()
116 * @method static self ireland()
117 * @method static self isleOfMan()
118 * @method static self israel()
119 * @method static self italy()
120 * @method static self jamaica()
121 * @method static self japan()
122 * @method static self jersey()
123 * @method static self jordan()
124 * @method static self kazakhstan()
125 * @method static self kenya()
126 * @method static self kiribati()
127 * @method static self koreaDemocraticPeoplesRepublicOf()
128 * @method static self koreaRepublicOf()
129 * @method static self kuwait()
130 * @method static self kyrgyzstan()
131 * @method static self laoPeoplesDemocraticRepublic()
132 * @method static self latvia()
133 * @method static self lebanon()
134 * @method static self lesotho()
135 * @method static self liberia()
136 * @method static self libya()
137 * @method static self liechtenstein()
138 * @method static self lithuania()
139 * @method static self luxembourg()
140 * @method static self macao()
141 * @method static self macedoniaTheFormerYugoslavRepublicOf()
142 * @method static self madagascar()
143 * @method static self malawi()
144 * @method static self malaysia()
145 * @method static self maldives()
146 * @method static self mali()
147 * @method static self malta()
148 * @method static self marshallIslands()
149 * @method static self martinique()
150 * @method static self mauritania()
151 * @method static self mauritius()
152 * @method static self mayotte()
153 * @method static self mexico()
154 * @method static self micronesiaFederatedStatesOf()
155 * @method static self moldovaRepublicOf()
156 * @method static self monaco()
157 * @method static self mongolia()
158 * @method static self montenegro()
159 * @method static self montserrat()
160 * @method static self morocco()
161 * @method static self mozambique()
162 * @method static self myanmar()
163 * @method static self namibia()
164 * @method static self nauru()
165 * @method static self nepal()
166 * @method static self netherlands()
167 * @method static self newCaledonia()
168 * @method static self newZealand()
169 * @method static self nicaragua()
170 * @method static self niger()
171 * @method static self nigeria()
172 * @method static self niue()
173 * @method static self norfolkIsland()
174 * @method static self northernMarianaIslands()
175 * @method static self norway()
176 * @method static self oman()
177 * @method static self pakistan()
178 * @method static self palau()
179 * @method static self palestineStateOf()
180 * @method static self panama()
181 * @method static self papuaNewGuinea()
182 * @method static self paraguay()
183 * @method static self peru()
184 * @method static self philippines()
185 * @method static self pitcairn()
186 * @method static self poland()
187 * @method static self portugal()
188 * @method static self puertoRico()
189 * @method static self qatar()
190 * @method static self reunion()
191 * @method static self romania()
192 * @method static self russianFederation()
193 * @method static self rwanda()
194 * @method static self saintBarthelemy()
195 * @method static self saintHelenaAscensionAndTristanDaCunha()
196 * @method static self saintKittsAndNevis()
197 * @method static self saintLucia()
198 * @method static self saintMartinFrenchPart()
199 * @method static self saintPierreAndMiquelon()
200 * @method static self saintVincentAndTheGrenadines()
201 * @method static self samoa()
202 * @method static self sanMarino()
203 * @method static self saoTomeAndPrincipe()
204 * @method static self saudiArabia()
205 * @method static self senegal()
206 * @method static self serbia()
207 * @method static self seychelles()
208 * @method static self sierraLeone()
209 * @method static self singapore()
210 * @method static self sintMaartenDutchPart()
211 * @method static self slovakia()
212 * @method static self slovenia()
213 * @method static self solomonIslands()
214 * @method static self somalia()
215 * @method static self southAfrica()
216 * @method static self southGeorgiaAndTheSouthSandwichIslands()
217 * @method static self southSudan()
218 * @method static self spain()
219 * @method static self sriLanka()
220 * @method static self sudan()
221 * @method static self suriname()
222 * @method static self svalbardAndJanMayen()
223 * @method static self swaziland()
224 * @method static self sweden()
225 * @method static self switzerland()
226 * @method static self syrianArabRepublic()
227 * @method static self taiwanProvinceOfChina()
228 * @method static self tajikistan()
229 * @method static self tanzaniaUnitedRepublicOf()
230 * @method static self thailand()
231 * @method static self timorLeste()
232 * @method static self togo()
233 * @method static self tokelau()
234 * @method static self tonga()
235 * @method static self trinidadAndTobago()
236 * @method static self tunisia()
237 * @method static self turkey()
238 * @method static self turkmenistan()
239 * @method static self turksAndCaicosIslands()
240 * @method static self tuvalu()
241 * @method static self uganda()
242 * @method static self ukraine()
243 * @method static self unitedArabEmirates()
244 * @method static self unitedKingdom()
245 * @method static self unitedStates()
246 * @method static self unitedStatesMinorOutlyingIslands()
247 * @method static self uruguay()
248 * @method static self uzbekistan()
249 * @method static self vanuatu()
250 * @method static self venezuelaBolivarianRepublicOf()
251 * @method static self vietNam()
252 * @method static self virginIslandsBritish()
253 * @method static self virginIslandsUs()
254 * @method static self wallisAndFutuna()
255 * @method static self westernSahara()
256 * @method static self yemen()
257 * @method static self zambia()
258 * @method static self zimbabwe()
259 *
260 * @package LDCCrmClient
261 * @subpackage enums
262 */
263class Country extends BaseEnumRoutable
264{
265    /**
266     * Valeurs des possibilités de cet Ã©numération (value)
267     *
268     * @return array
269     */
270    public static function values(): array
271    {
272        return [
273            'afghanistan' => 1,
274            'alandIslands' => 2,
275            'albania' => 3,
276            'algeria' => 4,
277            'americanSamoa' => 5,
278            'andorra' => 6,
279            'angola' => 7,
280            'anguilla' => 8,
281            'antarctica' => 9,
282            'antiguaAndBarbuda' => 10,
283            'argentina' => 11,
284            'armenia' => 12,
285            'aruba' => 13,
286            'australia' => 14,
287            'austria' => 15,
288            'azerbaijan' => 16,
289            'bahrain' => 17,
290            'bahamas' => 18,
291            'bangladesh' => 19,
292            'barbados' => 20,
293            'belarus' => 21,
294            'belgium' => 22,
295            'belize' => 23,
296            'benin' => 24,
297            'bermuda' => 25,
298            'bhutan' => 26,
299            'boliviaPlurinationalStateOf' => 27,
300            'bonaireSintEustatiusAndSaba' => 28,
301            'bosniaAndHerzegovina' => 29,
302            'botswana' => 30,
303            'bouvetIsland' => 31,
304            'brazil' => 32,
305            'britishIndianOceanTerritory' => 33,
306            'bruneiDarussalam' => 34,
307            'bulgaria' => 35,
308            'burkinaFaso' => 36,
309            'burundi' => 37,
310            'cambodia' => 38,
311            'cameroon' => 39,
312            'canada' => 40,
313            'capeVerde' => 41,
314            'caymanIslands' => 42,
315            'centralAfricanRepublic' => 43,
316            'chad' => 44,
317            'chile' => 45,
318            'china' => 46,
319            'christmasIsland' => 47,
320            'cocosKeelingIslands' => 48,
321            'colombia' => 49,
322            'comoros' => 50,
323            'congo' => 51,
324            'congoTheDemocraticRepublicOfThe' => 52,
325            'cookIslands' => 53,
326            'costaRica' => 54,
327            'coteDivoire' => 55,
328            'croatia' => 56,
329            'cuba' => 57,
330            'curacao' => 58,
331            'cyprus' => 59,
332            'czechRepublic' => 60,
333            'denmark' => 61,
334            'djibouti' => 62,
335            'dominica' => 63,
336            'dominicanRepublic' => 64,
337            'ecuador' => 65,
338            'egypt' => 66,
339            'elSalvador' => 67,
340            'equatorialGuinea' => 68,
341            'eritrea' => 69,
342            'estonia' => 70,
343            'ethiopia' => 71,
344            'falklandIslandsMalvinas' => 72,
345            'faroeIslands' => 73,
346            'fiji' => 74,
347            'finland' => 75,
348            'france' => 76,
349            'frenchGuiana' => 77,
350            'frenchPolynesia' => 78,
351            'frenchSouthernTerritories' => 79,
352            'gabon' => 80,
353            'gambia' => 81,
354            'georgia' => 82,
355            'germany' => 83,
356            'ghana' => 84,
357            'gibraltar' => 85,
358            'greece' => 86,
359            'greenland' => 87,
360            'grenada' => 88,
361            'guadeloupe' => 89,
362            'guam' => 90,
363            'guatemala' => 91,
364            'guernsey' => 92,
365            'guinea' => 93,
366            'guineaBissau' => 94,
367            'guyana' => 95,
368            'haiti' => 96,
369            'heardIslandAndMcdonaldIslands' => 97,
370            'holySeeVaticanCityState' => 98,
371            'honduras' => 99,
372            'hongKong' => 100,
373            'hungary' => 101,
374            'iceland' => 102,
375            'india' => 103,
376            'indonesia' => 104,
377            'iranIslamicRepublicOf' => 105,
378            'iraq' => 106,
379            'ireland' => 107,
380            'isleOfMan' => 108,
381            'israel' => 109,
382            'italy' => 110,
383            'jamaica' => 111,
384            'japan' => 112,
385            'jersey' => 113,
386            'jordan' => 114,
387            'kazakhstan' => 115,
388            'kenya' => 116,
389            'kiribati' => 117,
390            'koreaDemocraticPeoplesRepublicOf' => 118,
391            'koreaRepublicOf' => 119,
392            'kuwait' => 120,
393            'kyrgyzstan' => 121,
394            'laoPeoplesDemocraticRepublic' => 122,
395            'latvia' => 123,
396            'lebanon' => 124,
397            'lesotho' => 125,
398            'liberia' => 126,
399            'libya' => 127,
400            'liechtenstein' => 128,
401            'lithuania' => 129,
402            'luxembourg' => 130,
403            'macao' => 131,
404            'macedoniaTheFormerYugoslavRepublicOf' => 132,
405            'madagascar' => 133,
406            'malawi' => 134,
407            'malaysia' => 135,
408            'maldives' => 136,
409            'mali' => 137,
410            'malta' => 138,
411            'marshallIslands' => 139,
412            'martinique' => 140,
413            'mauritania' => 141,
414            'mauritius' => 142,
415            'mayotte' => 143,
416            'mexico' => 144,
417            'micronesiaFederatedStatesOf' => 145,
418            'moldovaRepublicOf' => 146,
419            'monaco' => 147,
420            'mongolia' => 148,
421            'montenegro' => 149,
422            'montserrat' => 150,
423            'morocco' => 151,
424            'mozambique' => 152,
425            'myanmar' => 153,
426            'namibia' => 154,
427            'nauru' => 155,
428            'nepal' => 156,
429            'netherlands' => 157,
430            'newCaledonia' => 158,
431            'newZealand' => 159,
432            'nicaragua' => 160,
433            'niger' => 161,
434            'nigeria' => 162,
435            'niue' => 163,
436            'norfolkIsland' => 164,
437            'northernMarianaIslands' => 165,
438            'norway' => 166,
439            'oman' => 167,
440            'pakistan' => 168,
441            'palau' => 169,
442            'palestineStateOf' => 170,
443            'panama' => 171,
444            'papuaNewGuinea' => 172,
445            'paraguay' => 173,
446            'peru' => 174,
447            'philippines' => 175,
448            'pitcairn' => 176,
449            'poland' => 177,
450            'portugal' => 178,
451            'puertoRico' => 179,
452            'qatar' => 180,
453            'reunion' => 181,
454            'romania' => 182,
455            'russianFederation' => 183,
456            'rwanda' => 184,
457            'saintBarthelemy' => 185,
458            'saintHelenaAscensionAndTristanDaCunha' => 186,
459            'saintKittsAndNevis' => 187,
460            'saintLucia' => 188,
461            'saintMartinFrenchPart' => 189,
462            'saintPierreAndMiquelon' => 190,
463            'saintVincentAndTheGrenadines' => 191,
464            'samoa' => 192,
465            'sanMarino' => 193,
466            'saoTomeAndPrincipe' => 194,
467            'saudiArabia' => 195,
468            'senegal' => 196,
469            'serbia' => 197,
470            'seychelles' => 198,
471            'sierraLeone' => 199,
472            'singapore' => 200,
473            'sintMaartenDutchPart' => 201,
474            'slovakia' => 202,
475            'slovenia' => 203,
476            'solomonIslands' => 204,
477            'somalia' => 205,
478            'southAfrica' => 206,
479            'southGeorgiaAndTheSouthSandwichIslands' => 207,
480            'southSudan' => 208,
481            'spain' => 209,
482            'sriLanka' => 210,
483            'sudan' => 211,
484            'suriname' => 212,
485            'svalbardAndJanMayen' => 213,
486            'swaziland' => 214,
487            'sweden' => 215,
488            'switzerland' => 216,
489            'syrianArabRepublic' => 217,
490            'taiwanProvinceOfChina' => 218,
491            'tajikistan' => 219,
492            'tanzaniaUnitedRepublicOf' => 220,
493            'thailand' => 221,
494            'timorLeste' => 222,
495            'togo' => 223,
496            'tokelau' => 224,
497            'tonga' => 225,
498            'trinidadAndTobago' => 226,
499            'tunisia' => 227,
500            'turkey' => 228,
501            'turkmenistan' => 229,
502            'turksAndCaicosIslands' => 230,
503            'tuvalu' => 231,
504            'uganda' => 232,
505            'ukraine' => 233,
506            'unitedArabEmirates' => 234,
507            'unitedKingdom' => 235,
508            'unitedStates' => 236,
509            'unitedStatesMinorOutlyingIslands' => 237,
510            'uruguay' => 238,
511            'uzbekistan' => 239,
512            'vanuatu' => 240,
513            'venezuelaBolivarianRepublicOf' => 241,
514            'vietNam' => 242,
515            'virginIslandsBritish' => 243,
516            'virginIslandsUs' => 244,
517            'wallisAndFutuna' => 245,
518            'westernSahara' => 246,
519            'yemen' => 247,
520            'zambia' => 248,
521            'zimbabwe' => 249
522        ];
523    }
524
525    /**
526     * Libellés des possibilités de cet Ã©numération (label)
527     *
528     * @return array
529     */
530    public static function labels(): array
531    {
532        return [
533            'afghanistan' => 'Afghanistan',
534            'alandIslands' => 'Ã…land Islands',
535            'albania' => 'Albania',
536            'algeria' => 'Algeria',
537            'americanSamoa' => 'American Samoa',
538            'andorra' => 'Andorra',
539            'angola' => 'Angola',
540            'anguilla' => 'Anguilla',
541            'antarctica' => 'Antarctica',
542            'antiguaAndBarbuda' => 'Antigua and Barbuda',
543            'argentina' => 'Argentina',
544            'armenia' => 'Armenia',
545            'aruba' => 'Aruba',
546            'australia' => 'Australia',
547            'austria' => 'Austria',
548            'azerbaijan' => 'Azerbaijan',
549            'bahrain' => 'Bahrain',
550            'bahamas' => 'Bahamas',
551            'bangladesh' => 'Bangladesh',
552            'barbados' => 'Barbados',
553            'belarus' => 'Belarus',
554            'belgium' => 'Belgium',
555            'belize' => 'Belize',
556            'benin' => 'Benin',
557            'bermuda' => 'Bermuda',
558            'bhutan' => 'Bhutan',
559            'boliviaPlurinationalStateOf' => 'Bolivia, Plurinational State of',
560            'bonaireSintEustatiusAndSaba' => 'Bonaire, Sint Eustatius and Saba',
561            'bosniaAndHerzegovina' => 'Bosnia and Herzegovina',
562            'botswana' => 'Botswana',
563            'bouvetIsland' => 'Bouvet Island',
564            'brazil' => 'Brazil',
565            'britishIndianOceanTerritory' => 'British Indian Ocean Territory',
566            'bruneiDarussalam' => 'Brunei Darussalam',
567            'bulgaria' => 'Bulgaria',
568            'burkinaFaso' => 'Burkina Faso',
569            'burundi' => 'Burundi',
570            'cambodia' => 'Cambodia',
571            'cameroon' => 'Cameroon',
572            'canada' => 'Canada',
573            'capeVerde' => 'Cape Verde',
574            'caymanIslands' => 'Cayman Islands',
575            'centralAfricanRepublic' => 'Central African Republic',
576            'chad' => 'Chad',
577            'chile' => 'Chile',
578            'china' => 'China',
579            'christmasIsland' => 'Christmas Island',
580            'cocosKeelingIslands' => 'Cocos (Keeling) Islands',
581            'colombia' => 'Colombia',
582            'comoros' => 'Comoros',
583            'congo' => 'Congo',
584            'congoTheDemocraticRepublicOfThe' => 'Congo, the Democratic Republic of the',
585            'cookIslands' => 'Cook Islands',
586            'costaRica' => 'Costa Rica',
587            'coteDivoire' => 'Côte d\'Ivoire',
588            'croatia' => 'Croatia',
589            'cuba' => 'Cuba',
590            'curacao' => 'Curaçao',
591            'cyprus' => 'Cyprus',
592            'czechRepublic' => 'Czech Republic',
593            'denmark' => 'Denmark',
594            'djibouti' => 'Djibouti',
595            'dominica' => 'Dominica',
596            'dominicanRepublic' => 'Dominican Republic',
597            'ecuador' => 'Ecuador',
598            'egypt' => 'Egypt',
599            'elSalvador' => 'El Salvador',
600            'equatorialGuinea' => 'Equatorial Guinea',
601            'eritrea' => 'Eritrea',
602            'estonia' => 'Estonia',
603            'ethiopia' => 'Ethiopia',
604            'falklandIslandsMalvinas' => 'Falkland Islands (Malvinas)',
605            'faroeIslands' => 'Faroe Islands',
606            'fiji' => 'Fiji',
607            'finland' => 'Finland',
608            'france' => 'France',
609            'frenchGuiana' => 'French Guiana',
610            'frenchPolynesia' => 'French Polynesia',
611            'frenchSouthernTerritories' => 'French Southern Territories',
612            'gabon' => 'Gabon',
613            'gambia' => 'Gambia',
614            'georgia' => 'Georgia',
615            'germany' => 'Germany',
616            'ghana' => 'Ghana',
617            'gibraltar' => 'Gibraltar',
618            'greece' => 'Greece',
619            'greenland' => 'Greenland',
620            'grenada' => 'Grenada',
621            'guadeloupe' => 'Guadeloupe',
622            'guam' => 'Guam',
623            'guatemala' => 'Guatemala',
624            'guernsey' => 'Guernsey',
625            'guinea' => 'Guinea',
626            'guineaBissau' => 'GuineaBissau',
627            'guyana' => 'Guyana',
628            'haiti' => 'Haiti',
629            'heardIslandAndMcdonaldIslands' => 'Heard Island and McDonald Islands',
630            'holySeeVaticanCityState' => 'Holy See (Vatican City State)',
631            'honduras' => 'Honduras',
632            'hongKong' => 'Hong Kong',
633            'hungary' => 'Hungary',
634            'iceland' => 'Iceland',
635            'india' => 'India',
636            'indonesia' => 'Indonesia',
637            'iranIslamicRepublicOf' => 'Iran, Islamic Republic of',
638            'iraq' => 'Iraq',
639            'ireland' => 'Ireland',
640            'isleOfMan' => 'Isle of Man',
641            'israel' => 'Israel',
642            'italy' => 'Italy',
643            'jamaica' => 'Jamaica',
644            'japan' => 'Japan',
645            'jersey' => 'Jersey',
646            'jordan' => 'Jordan',
647            'kazakhstan' => 'Kazakhstan',
648            'kenya' => 'Kenya',
649            'kiribati' => 'Kiribati',
650            'koreaDemocraticPeoplesRepublicOf' => 'Korea, Democratic People\'s Republic of',
651            'koreaRepublicOf' => 'Korea, Republic of',
652            'kuwait' => 'Kuwait',
653            'kyrgyzstan' => 'Kyrgyzstan',
654            'laoPeoplesDemocraticRepublic' => 'Lao People\'s Democratic Republic',
655            'latvia' => 'Latvia',
656            'lebanon' => 'Lebanon',
657            'lesotho' => 'Lesotho',
658            'liberia' => 'Liberia',
659            'libya' => 'Libya',
660            'liechtenstein' => 'Liechtenstein',
661            'lithuania' => 'Lithuania',
662            'luxembourg' => 'Luxembourg',
663            'macao' => 'Macao',
664            'macedoniaTheFormerYugoslavRepublicOf' => 'Macedonia, the Former Yugoslav Republic of',
665            'madagascar' => 'Madagascar',
666            'malawi' => 'Malawi',
667            'malaysia' => 'Malaysia',
668            'maldives' => 'Maldives',
669            'mali' => 'Mali',
670            'malta' => 'Malta',
671            'marshallIslands' => 'Marshall Islands',
672            'martinique' => 'Martinique',
673            'mauritania' => 'Mauritania',
674            'mauritius' => 'Mauritius',
675            'mayotte' => 'Mayotte',
676            'mexico' => 'Mexico',
677            'micronesiaFederatedStatesOf' => 'Micronesia, Federated States of',
678            'moldovaRepublicOf' => 'Moldova, Republic of',
679            'monaco' => 'Monaco',
680            'mongolia' => 'Mongolia',
681            'montenegro' => 'Montenegro',
682            'montserrat' => 'Montserrat',
683            'morocco' => 'Morocco',
684            'mozambique' => 'Mozambique',
685            'myanmar' => 'Myanmar',
686            'namibia' => 'Namibia',
687            'nauru' => 'Nauru',
688            'nepal' => 'Nepal',
689            'netherlands' => 'Netherlands',
690            'newCaledonia' => 'New Caledonia',
691            'newZealand' => 'New Zealand',
692            'nicaragua' => 'Nicaragua',
693            'niger' => 'Niger',
694            'nigeria' => 'Nigeria',
695            'niue' => 'Niue',
696            'norfolkIsland' => 'Norfolk Island',
697            'northernMarianaIslands' => 'Northern Mariana Islands',
698            'norway' => 'Norway',
699            'oman' => 'Oman',
700            'pakistan' => 'Pakistan',
701            'palau' => 'Palau',
702            'palestineStateOf' => 'Palestine, State of',
703            'panama' => 'Panama',
704            'papuaNewGuinea' => 'Papua New Guinea',
705            'paraguay' => 'Paraguay',
706            'peru' => 'Peru',
707            'philippines' => 'Philippines',
708            'pitcairn' => 'Pitcairn',
709            'poland' => 'Poland',
710            'portugal' => 'Portugal',
711            'puertoRico' => 'Puerto Rico',
712            'qatar' => 'Qatar',
713            'reunion' => 'Réunion',
714            'romania' => 'Romania',
715            'russianFederation' => 'Russian Federation',
716            'rwanda' => 'Rwanda',
717            'saintBarthelemy' => 'Saint Barthélemy',
718            'saintHelenaAscensionAndTristanDaCunha' => 'Saint Helena, Ascension and Tristan da Cunha',
719            'saintKittsAndNevis' => 'Saint Kitts and Nevis',
720            'saintLucia' => 'Saint Lucia',
721            'saintMartinFrenchPart' => 'Saint Martin (French part)',
722            'saintPierreAndMiquelon' => 'Saint Pierre and Miquelon',
723            'saintVincentAndTheGrenadines' => 'Saint Vincent and the Grenadines',
724            'samoa' => 'Samoa',
725            'sanMarino' => 'San Marino',
726            'saoTomeAndPrincipe' => 'Sao Tome and Principe',
727            'saudiArabia' => 'Saudi Arabia',
728            'senegal' => 'Senegal',
729            'serbia' => 'Serbia',
730            'seychelles' => 'Seychelles',
731            'sierraLeone' => 'Sierra Leone',
732            'singapore' => 'Singapore',
733            'sintMaartenDutchPart' => 'Sint Maarten (Dutch part)',
734            'slovakia' => 'Slovakia',
735            'slovenia' => 'Slovenia',
736            'solomonIslands' => 'Solomon Islands',
737            'somalia' => 'Somalia',
738            'southAfrica' => 'South Africa',
739            'southGeorgiaAndTheSouthSandwichIslands' => 'South Georgia and the South Sandwich Islands',
740            'southSudan' => 'South Sudan',
741            'spain' => 'Spain',
742            'sriLanka' => 'Sri Lanka',
743            'sudan' => 'Sudan',
744            'suriname' => 'Suriname',
745            'svalbardAndJanMayen' => 'Svalbard and Jan Mayen',
746            'swaziland' => 'Swaziland',
747            'sweden' => 'Sweden',
748            'switzerland' => 'Switzerland',
749            'syrianArabRepublic' => 'Syrian Arab Republic',
750            'taiwanProvinceOfChina' => 'Taiwan, Province of China',
751            'tajikistan' => 'Tajikistan',
752            'tanzaniaUnitedRepublicOf' => 'Tanzania, United Republic of',
753            'thailand' => 'Thailand',
754            'timorLeste' => 'TimorLeste',
755            'togo' => 'Togo',
756            'tokelau' => 'Tokelau',
757            'tonga' => 'Tonga',
758            'trinidadAndTobago' => 'Trinidad and Tobago',
759            'tunisia' => 'Tunisia',
760            'turkey' => 'Turkey',
761            'turkmenistan' => 'Turkmenistan',
762            'turksAndCaicosIslands' => 'Turks and Caicos Islands',
763            'tuvalu' => 'Tuvalu',
764            'uganda' => 'Uganda',
765            'ukraine' => 'Ukraine',
766            'unitedArabEmirates' => 'United Arab Emirates',
767            'unitedKingdom' => 'United Kingdom',
768            'unitedStates' => 'United States',
769            'unitedStatesMinorOutlyingIslands' => 'United States Minor Outlying Islands',
770            'uruguay' => 'Uruguay',
771            'uzbekistan' => 'Uzbekistan',
772            'vanuatu' => 'Vanuatu',
773            'venezuelaBolivarianRepublicOf' => 'Venezuela, Bolivarian Republic of',
774            'vietNam' => 'Viet Nam',
775            'virginIslandsBritish' => 'Virgin Islands, British',
776            'virginIslandsUs' => 'Virgin Islands, U.S.',
777            'wallisAndFutuna' => 'Wallis and Futuna',
778            'westernSahara' => 'Western Sahara',
779            'yemen' => 'Yemen',
780            'zambia' => 'Zambia',
781            'zimbabwe' => 'Zimbabwe'
782        ];
783    }
784
785    /**
786     * Get valid countries label
787     *
788     * @return array
789     */
790    public static function getValidCountriesLabel(): array
791    {
792        return collect(self::getValidCountries())->map(function (self $country) {
793            return $country->label;
794        })->all();
795    }
796
797    /**
798     * Get Alpha 2 ISO code of valid countries
799     *
800     * @return array
801     */
802    public static function getValidIsoAplha2Codes(): array
803    {
804        return \array_values(self::isoCodes());
805    }
806
807    /**
808     * Get valid countries
809     *
810     * @return array
811     */
812    public static function getValidCountries(): array
813    {
814        return [
815            Country::belgium(),
816            Country::luxembourg(),
817            Country::france(),
818            // * DROM COM.
819            Country::guadeloupe(),
820            Country::martinique(),
821            Country::frenchGuiana(),
822            Country::reunion(),
823            Country::saintPierreAndMiquelon(),
824            Country::mayotte(),
825            Country::saintBarthelemy(),
826            Country::saintMartinFrenchPart(),
827            Country::wallisAndFutuna(),
828            Country::frenchPolynesia(),
829            Country::newCaledonia()
830        ];
831    }
832
833    /**
834     * Enum classes
835     *
836     * @return array
837     */
838    protected static function isoCodes(): array
839    {
840        return [
841            'france' => 'FR',
842            'belgium' => 'BE',
843            'luxembourg' => 'LU',
844            'guadeloupe' => 'GP',
845            'martinique' => 'MQ',
846            'frenchGuiana' => 'GF',
847            'reunion' => 'RE',
848            'saintPierreAndMiquelon' => 'PM',
849            'mayotte' => 'YT',
850            'saintBarthelemy' => 'BL',
851            'saintMartinFrenchPart' => 'MF',
852            'wallisAndFutuna' => 'WF',
853            'frenchPolynesia' => 'PF',
854            'newCaledonia' => 'NC',
855        ];
856    }
857
858    /**
859     * @param string $attribute
860     * @return integer|string
861     */
862    public function __get(string $attribute)
863    {
864        if ($attribute === 'iso') {
865            return $this->isoCodes()[$this->getDefinition()] ?? null;
866        }
867        return parent::__get($attribute);
868    }
869}
- - - -
- - - - - - diff --git a/tests/coverage/Enums/CustomerExportType.php.html b/tests/coverage/Enums/CustomerExportType.php.html deleted file mode 100644 index 1878f41..0000000 --- a/tests/coverage/Enums/CustomerExportType.php.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums/CustomerExportType.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CustomerExportType
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 values
n/a
0 / 0
n/a
0 / 0
1
 labels
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Enums;
4
5use Kwaadpepper\Enum\BaseEnumRoutable;
6
7/**
8 * @method static self singleOptin()
9 * @method static self customer()
10 * @method static self both()
11 *
12 * @codeCoverageIgnore
13 */
14class CustomerExportType extends BaseEnumRoutable
15{
16    /**
17     * Valeurs des possibilités de cet Ã©numération (value)
18     *
19     * @return array
20     */
21    public static function values(): array
22    {
23        return [
24            'singleOptin' => 1,
25            'customer' => 2,
26            'both' => 3
27        ];
28    }
29
30    /**
31     * Libellés des possibilités de cet Ã©numération (label)
32     *
33     * @return array
34     */
35    public static function labels(): array
36    {
37        return [
38            'singleOptin' => trans('Les single optins uniquement'),
39            'customer' => trans('Les clients uniquement acceptant de recevoir les newsletters'),
40            'both' => trans('La totalité des client acceptant de recevoir les newsletters')
41        ];
42    }
43}
- - - -
- - - - - - diff --git a/tests/coverage/Enums/QuestionType.php.html b/tests/coverage/Enums/QuestionType.php.html deleted file mode 100644 index 6ad949a..0000000 --- a/tests/coverage/Enums/QuestionType.php.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums/QuestionType.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
QuestionType
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 values
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Enums;
4
5use Kwaadpepper\Enum\BaseEnumRoutable;
6
7/**
8 * @method static self select()
9 * @method static self radio()
10 * @method static self checkbox()
11 *
12 * @package LDCCrmClient
13 * @subpackage enums
14 */
15class QuestionType extends BaseEnumRoutable
16{
17    /**
18     * Valeurs des possibilités de cet Ã©numération (value)
19     *
20     * @return array
21     */
22    public static function values(): array
23    {
24        return [
25            'select' => 1,
26            'radio' => 2,
27            'checkbox' => 3
28        ];
29    }
30}
- - - -
- - - - - - diff --git a/tests/coverage/Enums/dashboard.html b/tests/coverage/Enums/dashboard.html deleted file mode 100644 index 8eabfa0..0000000 --- a/tests/coverage/Enums/dashboard.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Enums - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - -
MethodCoverage
__get66%
-
-
-
-

Project Risks

-
- - - - - - - - - - - -
MethodCRAP
__get2
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Enums/index.html b/tests/coverage/Enums/index.html deleted file mode 100644 index c70bd4c..0000000 --- a/tests/coverage/Enums/index.html +++ /dev/null @@ -1,257 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Enums - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 96.55% covered (success) -
-
-
96.55%
28 / 29
-
- 91.67% covered (success) -
-
-
91.67%
11 / 12
-
- 80.00% covered (warning) -
-
-
80.00%
4 / 5
ApiError.php
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ApiStatus.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
ArcadiaMailType.php
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ArcadiaOrderType.php
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Country.php
-
- 95.83% covered (success) -
-
-
95.83%
23 / 24
-
- 85.71% covered (warning) -
-
-
85.71%
6 / 7
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CustomerExportType.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
QuestionType.php
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- -
- - diff --git a/tests/coverage/Exceptions/Api/V2/CrmException.php.html b/tests/coverage/Exceptions/Api/V2/CrmException.php.html deleted file mode 100644 index e620bd9..0000000 --- a/tests/coverage/Exceptions/Api/V2/CrmException.php.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Exceptions/Api/V2/CrmException.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CrmException
n/a
0 / 0
n/a
0 / 0
1
n/a
0 / 0
 report
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Exceptions\Api\V2;
4
5use Exception;
6use Illuminate\Support\Facades\Log;
7
8/**
9 * This is a critical error.
10 * The request could not be handled. You may dump any value you can
11 * get before submitting a ticker to https://supportweb.ldc.fr
12 *
13 * @package LDCCrmClient
14 * @subpackage exceptions
15 * @codeCoverageIgnore
16 */
17class CrmException extends Exception
18{
19    /**
20     * This is used by laravel only.
21     * It uses Log class to print the error.
22     *
23     * @return void
24     */
25    public function report()
26    {
27        Log::critical(sprintf('CRM API: %s', $this->getMessage()));
28    }
29}
- - - -
- - - - - - diff --git a/tests/coverage/Exceptions/Api/dashboard.html b/tests/coverage/Exceptions/Api/dashboard.html deleted file mode 100644 index d175b48..0000000 --- a/tests/coverage/Exceptions/Api/dashboard.html +++ /dev/null @@ -1,288 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Exceptions/Api - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - -
ClassCoverage
App\Exceptions\Api\V2\ApiErrorException87%
-
-
-
-

Project Risks

-
- - - - - - - - - - - -
ClassCRAP
App\Exceptions\Api\V2\ApiErrorException5
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - -
MethodCoverage
getApiError0%
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Exceptions/Handler.php.html b/tests/coverage/Exceptions/Handler.php.html deleted file mode 100644 index 167147c..0000000 --- a/tests/coverage/Exceptions/Handler.php.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Exceptions/Handler.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 91.18% covered (success) -
-
-
91.18%
31 / 34
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Handler
-
- 91.18% covered (success) -
-
-
91.18%
31 / 34
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
14.13
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 register
n/a
0 / 0
n/a
0 / 0
1
 render
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
 handleApiV2Exception
-
- 89.29% covered (warning) -
-
-
89.29%
25 / 28
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
9.10
 getReponse
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Exceptions;
4
5use App\Enums\ApiError;
6use App\Enums\ApiStatus;
7use App\Exceptions\Api\V2\ApiErrorException;
8use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse;
9use App\Lib\Helper;
10use Illuminate\Auth\Access\AuthorizationException;
11use Illuminate\Auth\AuthenticationException;
12use Illuminate\Database\Eloquent\ModelNotFoundException;
13use Illuminate\Http\Exceptions\ThrottleRequestsException;
14use Illuminate\Http\JsonResponse;
15use Illuminate\Http\Request;
16use Illuminate\Validation\ValidationException;
17use Kwaadpepper\ExceptionHandler\Exceptions\ExceptionHandler;
18use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
19
20class Handler extends ExceptionHandler
21{
22    /**
23     * A list of the exception types that are not reported.
24     *
25     * @var array
26     */
27    protected $dontReport = [
28        ApiErrorException::class,
29        RedirectTo::class
30    ];
31
32    /**
33     * A list of the inputs that are never flashed for validation exceptions.
34     *
35     * @var array
36     */
37    protected $dontFlash = [
38        'password',
39        'password_confirmation',
40    ];
41
42    /**
43     * Register the exception handling callbacks for the application.
44     *
45     * @return void
46     */
47    public function register()
48    {
49    }
50
51    /**
52     * Render an exception into an HTTP response.
53     *
54     * @param  mixed      $request
55     * @param  \Throwable $e
56     * @return \Symfony\Component\HttpFoundation\Response
57     */
58    public function render($request, \Throwable $e)
59    {
60        if (Helper::isRequestForApiV2($request) and $request->isJson()) {
61            return $this->handleApiV2Exception($request, $e);
62        } //end if
63        // @codeCoverageIgnoreStart
64        return parent::render($request, $e);
65        // @codeCoverageIgnoreEnd
66    }
67
68    /**
69     * Generate a json response for any apiV2 error case
70     *
71     * @param Request    $request
72     * @param \Throwable $e
73     * @return JsonResponse
74     */
75    private function handleApiV2Exception(Request $request, \Throwable $e): JsonResponse
76    {
77        switch (get_class($e)) {
78            case AuthenticationException::class:
79                /** @var AuthenticationException $e */
80                return $this->getReponse(
81                    $request,
82                    401,
83                    ApiError::apiAuthFailed()->value,
84                    sprintf('%s, %s', ApiError::apiAuthFailed()->label, $e->getMessage())
85                );
86            case MethodNotAllowedHttpException::class:
87                /** @var MethodNotAllowedHttpException $e */
88                return $this->getReponse(
89                    $request,
90                    $e->getStatusCode(),
91                    ApiError::methodNotAllowed()->value,
92                    sprintf('%s, %s', ApiError::methodNotAllowed()->label, $e->getMessage())
93                );
94            case ValidationException::class:
95                /** @var ValidationException $e */
96                return $this->getReponse(
97                    $request,
98                    $e->status,
99                    ApiError::validationFailed()->value,
100                    sprintf(ApiError::validationFailed()->label, $e->getMessage()),
101                    ['errors' => $e->errors()]
102                );
103            case ModelNotFoundException::class:
104                /** @var ModelNotFoundException $e */
105                return $this->getReponse(
106                    $request,
107                    404,
108                    ApiError::modelNotFound()->value,
109                    sprintf(
110                        ApiError::modelNotFound()->label,
111                        \array_slice(explode('\\', $e->getModel()), -1)[0]
112                    )
113                );
114            case ApiErrorException::class:
115                /** @var ApiErrorException $e */
116                return $this->getReponse(
117                    $request,
118                    $e->getStatusCode(),
119                    $e->getCode(),
120                    $e->getMessage()
121                );
122            case ThrottleRequestsException::class:
123                /** @var ThrottleRequestsException $e */
124                return $this->getReponse(
125                    $request,
126                    429,
127                    ApiError::throttleRequestsException()->value,
128                    ApiError::throttleRequestsException()->label
129                );
130            case AuthorizationException::class:
131                /** @var AuthorizationException $e */
132                return $this->getReponse(
133                    $request,
134                    403,
135                    ApiError::authorizationException()->value,
136                    ApiError::authorizationException()->label
137                );
138        } //end switch
139        // @codeCoverageIgnoreStart
140        if (config('app.env') === 'testing') {
141            // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Found
142            print_r((string)$e);
143        }
144        return $this->getReponse($request, 500, \intval($e->getCode()), $e->getMessage());
145        // @codeCoverageIgnoreEnd
146    }
147
148    /**
149     * Generate an ApiV2 json response
150     *
151     * @param Request $request
152     * @param integer $httpCode
153     * @param integer $apiCode
154     * @param string  $message
155     * @param array   $data
156     * @return JsonResponse
157     */
158    private static function getReponse(
159        Request $request,
160        int $httpCode,
161        int $apiCode,
162        string $message,
163        array $data = []
164    ): JsonResponse {
165        return (new V2ApiResponse(
166            (object)array_merge($data, ['message' => $message, 'code' => $apiCode]),
167            ApiStatus::KO()
168        ))->toResponse($request)->setStatusCode($httpCode);
169    }
170}
- - - -
- - - - - - diff --git a/tests/coverage/Exceptions/dashboard.html b/tests/coverage/Exceptions/dashboard.html deleted file mode 100644 index d292c37..0000000 --- a/tests/coverage/Exceptions/dashboard.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Exceptions - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - -
ClassCoverage
App\Exceptions\Api\V2\ApiErrorException87%
-
-
-
-

Project Risks

-
- - - - - - - - - - - -
ClassCRAP
App\Exceptions\Api\V2\ApiErrorException5
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - -
MethodCoverage
getApiError0%
handleApiV2Exception89%
-
-
-
-

Project Risks

-
- - - - - - - - - - - -
MethodCRAP
handleApiV2Exception9
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Exceptions/index.html b/tests/coverage/Exceptions/index.html deleted file mode 100644 index 142ac5f..0000000 --- a/tests/coverage/Exceptions/index.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Exceptions - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 90.48% covered (success) -
-
-
90.48%
38 / 42
-
- 66.67% covered (warning) -
-
-
66.67%
4 / 6
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
Api
-
- 87.50% covered (warning) -
-
-
87.50%
7 / 8
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
AbortRowImportException.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
Handler.php
-
- 91.18% covered (success) -
-
-
91.18%
31 / 34
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- -
- - diff --git a/tests/coverage/Http/Controllers/AnswerController.php.html b/tests/coverage/Http/Controllers/AnswerController.php.html deleted file mode 100644 index 4252b29..0000000 --- a/tests/coverage/Http/Controllers/AnswerController.php.html +++ /dev/null @@ -1,336 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/AnswerController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
AnswerController
n/a
0 / 0
n/a
0 / 0
10
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
2
 create
n/a
0 / 0
n/a
0 / 0
1
 store
n/a
0 / 0
n/a
0 / 0
2
 edit
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
2
 destroy
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Http\Controllers\Controller;
6use App\Http\Requests\Answers\StoreAnswerRequest;
7use App\Http\Requests\Answers\UpdateAnswerRequest;
8use App\Models\Answer;
9use App\Models\Question;
10use Illuminate\Http\Request;
11use Illuminate\Support\Facades\DB;
12use Illuminate\Support\Str;
13
14/**
15 * @codeCoverageIgnore
16 */
17class AnswerController extends Controller
18{
19    /**
20     * Display a listing of the resource.
21     *
22     * @param \Illuminate\Http\Request $request
23     * @return \Illuminate\Http\Response
24     */
25    public function index(Request $request)
26    {
27        /** @var \App\Models\Question $questionModel */
28        $questionModel = $request->question;
29
30        /** @var \Illuminate\Database\Eloquent\Builder $answers */
31        $answers = Answer::query()->where('question_id', $questionModel->id);
32
33        if ($request->search) {
34            $this->searchQuery(
35                $answers,
36                $request->search,
37                null,
38                'answer',
39            );
40        }
41
42        $this->sortQuery($answers);
43        $searchFields = trans('Réponse');
44
45        $answerModels = $answers->paginate(12);
46
47        return view('answers.index', compact('answerModels', 'questionModel', 'searchFields'));
48    }
49
50    /**
51     * Show the form for creating a new resource.
52     *
53     * @param \Illuminate\Http\Request $request
54     * @param \App\Models\Answer       $answer
55     * @return \Illuminate\Http\Response
56     */
57    public function create(Request $request, Answer $answer)
58    {
59        /** @var \App\Models\Question $questionModel */
60        $questionModel = $request->question;
61
62        return view('answers.create', [
63            'answerModel' => $answer,
64            'questionModel' => $questionModel
65        ]);
66    }
67
68    /**
69     * Store a newly created resource in storage.
70     *
71     * @param \App\Http\Requests\Answers\StoreAnswerRequest $request
72     * @return \Illuminate\Http\Response
73     */
74    public function store(StoreAnswerRequest $request)
75    {
76        return DB::transaction(function () use ($request) {
77            $answer = new Answer();
78            $answer->fill($request->validated());
79            if ($answer->saveOrFail()) {
80                return redirect()
81                    ->route('answers.edit', $answer)
82                    ->with('success', trans(
83                        ':model a Ã©té créé.',
84                        ['model' => Str::singular(trans('models.classes.answers'))]
85                    ));
86            }
87            return redirect()
88                ->back()
89                ->with('error', trans(
90                    ':model n\'a pas Ã©té créé.',
91                    ['model' => Str::singular(trans('models.classes.answers'))]
92                ));
93        });
94    }
95
96    /**
97     * Show the form for editing the specified resource.
98     *
99     * @param \App\Models\Question $question
100     * @param \App\Models\Answer   $answers
101     * @return \Illuminate\Http\Response
102     */
103    public function edit(Question $question, Answer $answers)
104    {
105        return view('answers.edit', [
106            'answerModel' => $answers,
107            'questionModel' => $question
108        ]);
109    }
110
111    /**
112     * Update a spacific resource in storage.
113     *
114     * @param \App\Http\Requests\Answers\UpdateAnswerRequest $request
115     * @param \App\Models\Question                           $question
116     * @param \App\Models\Answer                             $answer
117     * @return \Illuminate\Http\Response
118     */
119    public function update(UpdateAnswerRequest $request, Question $question, Answer $answer)
120    {
121        return DB::transaction(function () use ($request, $question, $answer) {
122            $answer->fill($request->validated());
123            if ($answer->saveOrFail()) {
124                return redirect()
125                    ->route('questions.answers.edit', ['question' => $question, 'answer' => $answer])
126                    ->with('success', trans(
127                        ':model a Ã©té mis Ã  jour.',
128                        ['model' => Str::singular(trans('models.classes.answers'))]
129                    ));
130            }
131            return redirect()
132                ->back()
133                ->with('error', trans(
134                    ':model n\'a pas Ã©té mis Ã  jour.',
135                    ['model' => Str::singular(trans('models.classes.answers'))]
136                ));
137        });
138    }
139
140    /**
141     * Remove the specified resource from storage.
142     *
143     * @param \App\Models\Question $question
144     * @param \App\Models\Answer   $answer
145     * @return \Illuminate\Http\RedirectResponse
146     */
147    public function destroy(Question $question, Answer $answer): \Illuminate\Http\RedirectResponse
148    {
149        if ($answer->deleteOrFail()) {
150            return redirect()->route('questions.answers.index', $question)
151                ->with('success', trans(
152                    ':model a Ã©té supprimé.',
153                    ['model' => Str::singular(trans('models.classes.answers'))]
154                ));
155        }
156        return redirect()->back()
157            ->with('error', trans(
158                ':model n\'a pas pu Ãªtre supprimé.',
159                ['model' => Str::singular(trans('models.classes.answers'))]
160            ));
161    }
162}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/Api/V1/ArcadiaController.php.html b/tests/coverage/Http/Controllers/Api/V1/ArcadiaController.php.html deleted file mode 100644 index b9379b6..0000000 --- a/tests/coverage/Http/Controllers/Api/V1/ArcadiaController.php.html +++ /dev/null @@ -1,515 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/Api/V1/ArcadiaController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ArcadiaController
n/a
0 / 0
n/a
0 / 0
57
n/a
0 / 0
 outgoingMail
n/a
0 / 0
n/a
0 / 0
10
 checkShippingRequiredFields
n/a
0 / 0
n/a
0 / 0
4
 checkRequiredFields
n/a
0 / 0
n/a
0 / 0
21
 makeXml
n/a
0 / 0
n/a
0 / 0
22
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers\Api\V1;
4
5use App\Http\Controllers\Controller;
6use App\Http\Resources\StatusResource;
7use App\Mail\CourrierArcadiaError;
8use App\Models\Customer;
9use DOMDocument;
10use Illuminate\Http\Request;
11use Illuminate\Support\Facades\Log;
12use Illuminate\Support\Facades\Mail;
13use Illuminate\Support\Facades\Storage;
14use Illuminate\Support\Facades\Validator;
15
16/**
17 * @codeCoverageIgnore
18 */
19class ArcadiaController extends Controller
20{
21    /**
22     * @param \Illuminate\Http\Request $request
23     * @return \App\Http\Resources\StatusResource
24     */
25    public function outgoingMail(Request $request)
26    {
27        $validator = Validator::make($request->all(), [
28            'crm_id' => 'required',
29            'fields' => 'required|json',
30            'type' => 'required',
31            'shipping' => 'required_if:type,default|json'
32        ]);
33
34        if ($validator->fails()) {
35            return new StatusResource([
36                'status' => 'KO',
37                'code' => 201,
38                'message' => 'Error on required fields.',
39                'errors' => $validator->errors()
40            ]);
41        }
42
43        $brandId  = $request->brand_id;
44        $crmId    = $request->crm_id;
45        $fields   = json_decode($request->fields);
46        $type     = $request->type;
47        $shipping = $type === 'default' ? json_decode($request->shipping) : false;
48
49        $customer = Customer::where('id', $crmId)->first();
50        if (!$customer) {
51            return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']);
52        }
53
54        $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
55
56        if (!$CustomerInfo) {
57            return new StatusResource(['status' => 'KO', 'code' => 202, 'message' => 'User not found.']);
58        }
59
60        $check = $this->checkRequiredFields($fields, $type);
61        if ($check['status'] !== 'OK') {
62            return new StatusResource($check);
63        }
64
65        if ($type === 'default') {
66            $check = $this->checkShippingRequiredFields($shipping);
67            if ($check['status'] !== 'OK') {
68                return new StatusResource($check);
69            }
70        }
71
72        $xml = $this->makeXml($fields, $type, $customer, $CustomerInfo, $shipping);
73
74        try {
75            Storage::disk('arcadia')->put(date("Ymd_His") . microtime(true) . ".xml", $xml);
76            return new StatusResource(['status' => 'OK']);
77        } catch (\Exception $e) {
78            Log::critical("ARCADIA XML CREATION FAILED : " . $e->getMessage());
79            Mail::to(config('mail.MAIL_ADMIN'))->send(new CourrierArcadiaError(
80                "ARCADIA XML CREATION FAILED",
81                "Une erreur est survenue lors de la création du fichier XML : " . $e->getMessage(),
82                $xml ?: ''
83            ));
84            return new StatusResource(['status' => 'KO', 'code' => 204, 'message' => 'Arcadia XML Creation failed.']);
85        }
86    }
87
88    /**
89     * @param mixed $fields
90     * @return array
91     */
92    private function checkShippingRequiredFields($fields)
93    {
94        $test = [
95            'firstname',
96            'lastname',
97            'address',
98            'city',
99            'postcode'
100        ];
101
102        foreach ($test as $t) {
103            if (!array_key_exists($t, $fields)) {
104                $errors[] = ['field' => $t, 'error' => 'required'];
105            }
106        }
107
108        if (!empty($errors)) {
109            return ['status' => 'KO', 'code' => 203, 'message' => $errors];
110        }
111
112        return ['status' => 'OK'];
113    }
114
115    /**
116     *
117     * @param mixed $fields
118     * @param mixed $type
119     * @return array
120     */
121    // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
122    private function checkRequiredFields($fields, $type): array
123    {
124        $errors = [];
125
126        $key = array_search('operation', array_column($fields->root, 'fieldName'));
127        if ($key === false) {
128            $errors[] = ['field' => 'operation', 'error' => 'required'];
129        }
130
131        $key = array_search('support_code', array_column($fields->orderLine, 'fieldName'));
132        if ($key === false) {
133            $errors[] = ['field' => 'support_code', 'error' => 'required'];
134        }
135
136        $key = array_search('label_type_support', array_column($fields->orderLine, 'fieldName'));
137        if ($key === false) {
138            $errors[] = ['field' => 'label_type_support', 'error' => 'required'];
139        }
140
141        $key = array_search('order_date', array_column($fields->root, 'fieldName'));
142        if ($key === false) {
143            $errors[] = ['field' => 'order_date', 'error' => 'required'];
144        }
145
146        $regex = join('', [
147            '/^(((0[1-9]|[12]\d|3[01])\/(0[13578]|1[02])\/((19|[2-9]\d)\d{2}))|',
148            '((0[1-9]|[12]\d|30)\/(0[13456789]|1[012])\/((19|[2-9]\d)\d{2}))',
149            '|((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]',
150            '|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))$/'
151        ]);
152        if (!preg_match($regex, $fields->root[$key]->fieldValue)) {
153            $errors[] = ['field' => 'order_date', 'error' => 'format must be DD/MM/YYYY'];
154        }
155
156        if ($type === 'default') {
157            $key = array_search('order_num', array_column($fields->root, 'fieldName'));
158            if ($key === false) {
159                $errors[] = ['field' => 'order_num', 'error' => 'required for default type'];
160            }
161
162            if (!is_numeric($fields->root[$key]->fieldValue) || strlen($fields->root[$key]->fieldValue) !== 9) {
163                $errors[] = ['field' => 'order_num', 'error' => 'format must be 9 numeric characters'];
164            }
165
166            $key = array_search('state_order_cb', array_column($fields->root, 'fieldName'));
167            if ($key === false) {
168                $errors[] = ['field' => 'state_order_cb', 'error' => 'required for default type'];
169            }
170
171            if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') {
172                $errors[] = ['field' => 'state_order_cb', 'error' => 'value must be 1 or Null (Yes Null not NULL)'];
173            }
174
175            $orderCB = $fields->root[$key]->fieldValue;
176
177            $key = array_search('state_order_check', array_column($fields->root, 'fieldName'));
178            if ($key === false) {
179                $errors[] = ['field' => 'state_order_check', 'error' => 'required for default type'];
180            }
181
182            if ($fields->root[$key]->fieldValue !== 1 && $fields->root[$key]->fieldValue !== 'Null') {
183                $errors[] = ['field' => 'state_order_check', 'error' => 'value must be 1 or Null (Yes Null not NULL)'];
184            }
185
186            $orderCheck = $fields->root[$key]->fieldValue;
187
188            if ($orderCB === $orderCheck) {
189                $errors[] = [
190                    'field' => 'state_order',
191                    'error' => 'state_order_cb and state_order_check values must be different'
192                ];
193            }
194
195            $key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName'));
196            if ($key === false) {
197                $errors[] = ['field' => 'package_unit_weight', 'error' => 'required for default type'];
198            }
199        } elseif ($type === 'game') {
200            $key = array_search('code_unique', array_column($fields->orderLine, 'fieldName'));
201            if ($key === false) {
202                $errors[] = ['field' => 'code_unique', 'error' => 'required for game type'];
203            }
204        }//end if
205
206        if (!empty($errors)) {
207            return ['status' => 'KO', 'code' => 203, 'message' => $errors];
208        }
209
210        return ['status' => 'OK'];
211    }
212
213    /**
214     * @param mixed $fields
215     * @param mixed $type
216     * @param mixed $customer
217     * @param mixed $customerInfos
218     * @param mixed $shipping
219     * @return string|false
220     */
221    // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
222    private function makeXml($fields, $type, $customer, $customerInfos, $shipping)
223    {
224        $pivot = [
225            'order_num' => 'Num_Commande',
226            // Required if type = default - format 9 numeric characters.
227            'order_date' => 'Date_Commande',
228            // Required - format : DD/MM/YYYY .
229            'operation' => 'Operation',
230            // Required.
231            'support_code' => 'Code_Support',
232            // Required.
233            'label_type_support' => 'Libelle_Type_Support',
234            // Required.
235            'package_unit_weight' => 'Poids_Unitaire_Colis',
236            // Required if type = default.
237            'qty' => 'Quantite',
238            'code_on_pack' => 'Code_on_Pack',
239            'code_unique' => 'Code_Unique',
240            // Required if type = game.
241            // Required if type = default - format 1 ou Null (oui Null et pas NULL).
242            'state_order_cb' => 'Etat_Commande_CB',
243            // Required if type = default - format 1 ou Null (oui Null et pas NULL).
244            'state_order_check' => 'Etat_Commande_Cheque',
245
246        ];
247
248        $dom               = new DOMDocument('1.0', 'UTF-8');
249        $dom->formatOutput = true;
250
251        $root = $dom->createElement('export_arcadia');
252        $dom->appendChild($root);
253        $order = $root->appendChild($dom->createElement('Commande'));
254
255        $key = array_search('order_num', array_column($fields->root, 'fieldName'));
256        if ($key === false) {
257            $order->appendChild($dom->createElement('Num_Commande'));
258        } else {
259            $order->appendChild($dom->createElement('Num_Commande', $fields->root[$key]->fieldValue));
260        }
261
262        foreach ($fields->root as $value) {
263            // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
264            if (
265                isset($pivot[$value->fieldName]) && $value->fieldName !== 'state_order_cb' &&
266                $value->fieldName !== 'state_order_check' && $value->fieldName !== 'order_num'
267            ) {
268                $order->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue));
269            }
270        }
271
272        if ($type === 'default') {
273            $key = array_search('state_order_cb', array_column($fields->root, 'fieldName'));
274            if ($key) {
275                $order->appendChild($dom->createElement('Etat_Commande_CB', $fields->root[$key]->fieldValue));
276            }
277
278            $key = array_search('state_order_check', array_column($fields->root, 'fieldName'));
279            if ($key) {
280                $order->appendChild($dom->createElement('Etat_Commande_Cheque', $fields->root[$key]->fieldValue));
281            }
282        }
283
284        $order->appendChild($dom->createElement('Email_conso'))
285        ->appendChild($dom->createCDATASection("$customer->email"));
286        $order->appendChild($dom->createElement('Civilite'));
287
288        if ($type === 'default') {
289            $order->appendChild($dom->createElement('Nom'))
290                ->appendChild($dom->createCDATASection("$shipping->lastname"));
291            $order->appendChild($dom->createElement('Prenom'))
292                ->appendChild($dom->createCDATASection("$shipping->firstname"));
293            $order->appendChild($dom->createElement('Adresse'))
294                ->appendChild($dom->createCDATASection("$shipping->address"));
295            $order->appendChild($dom->createElement('Adresse_Complementaire'));
296            $order->appendChild($dom->createElement('CP'))
297            ->appendChild($dom->createCDATASection("$shipping->postcode"));
298            $order->appendChild($dom->createElement('Ville'))->appendChild($dom->createCDATASection("$shipping->city"));
299            if (isset($shipping->phone) && !empty($shipping->phone)) {
300                $order->appendChild($dom->createElement('Telephone'))
301                    ->appendChild($dom->createCDATASection("$shipping->phone"));
302            } else {
303                $order->appendChild($dom->createElement('Telephone'));
304            }
305        } else {
306            $order->appendChild($dom->createElement('Nom'))
307                ->appendChild($dom->createCDATASection("$customerInfos->lastname"));
308            $order->appendChild($dom->createElement('Prenom'))
309                ->appendChild($dom->createCDATASection("$customerInfos->firstname"));
310            $order->appendChild($dom->createElement('Adresse'))
311                ->appendChild($dom->createCDATASection("$customerInfos->address"));
312            $order->appendChild($dom->createElement('Adresse_Complementaire'))
313                ->appendChild($dom->createCDATASection("$customerInfos->address2"));
314            $order->appendChild($dom->createElement('CP'))
315                ->appendChild($dom->createCDATASection("$customerInfos->postcode"));
316            $order->appendChild($dom->createElement('Ville'))
317                ->appendChild($dom->createCDATASection("$customerInfos->city"));
318            $order->appendChild($dom->createElement('Telephone'))
319                ->appendChild($dom->createCDATASection("$customerInfos->phone"));
320        }//end if
321
322        $orderLine = $order->appendChild($dom->createElement('Ligne_de_Commande'));
323
324        $key = array_search('qty', array_column($fields->orderLine, 'fieldName'));
325        if ($key === false) {
326            $orderLine->appendChild($dom->createElement('Quantite', '1'));
327        }
328
329        $key = array_search('package_unit_weight', array_column($fields->orderLine, 'fieldName'));
330        if ($key === false) {
331            $orderLine->appendChild($dom->createElement('Poids_Unitaire_Colis'));
332        }
333
334        foreach ($fields->orderLine as $value) {
335            // phpcs:ignore PSR2.ControlStructures.ControlStructureSpacing.SpacingAfterOpenBrace
336            if (
337                isset($pivot[$value->fieldName]) &&
338                $value->fieldName !== 'code_on_pack' &&
339                $value->fieldName !== 'code_unique'
340            ) {
341                $orderLine->appendChild($dom->createElement($pivot[$value->fieldName], $value->fieldValue));
342            }
343        }
344
345        if ($type === 'game') {
346            $key = array_search('code_on_pack', array_column($fields->orderLine, 'fieldName'));
347            if ($key !== false) {
348                $orderLine->appendChild(
349                    $dom->createElement('Code_on_Pack')
350                )->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
351            }
352
353            $key = array_search('code_unique', array_column($fields->orderLine, 'fieldName'));
354            if ($key !== false) {
355                $orderLine->appendChild(
356                    $dom->createElement('Code_Unique')
357                )->appendChild($dom->createCDATASection($fields->orderLine[$key]->fieldValue));
358            }
359        }
360
361        return $dom->saveXML();
362    }
363}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/Api/V1/CustomerController.php.html b/tests/coverage/Http/Controllers/Api/V1/CustomerController.php.html deleted file mode 100644 index 8504960..0000000 --- a/tests/coverage/Http/Controllers/Api/V1/CustomerController.php.html +++ /dev/null @@ -1,798 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/Api/V1/CustomerController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CustomerController
n/a
0 / 0
n/a
0 / 0
69
n/a
0 / 0
 store
n/a
0 / 0
n/a
0 / 0
12
 show
n/a
0 / 0
n/a
0 / 0
6
 update
n/a
0 / 0
n/a
0 / 0
22
 authenticate
n/a
0 / 0
n/a
0 / 0
6
 changePassword
n/a
0 / 0
n/a
0 / 0
5
 activateAccount
n/a
0 / 0
n/a
0 / 0
4
 delete
n/a
0 / 0
n/a
0 / 0
4
 toggleOptin
n/a
0 / 0
n/a
0 / 0
4
 removeOptin
n/a
0 / 0
n/a
0 / 0
2
 addSingleOptin
n/a
0 / 0
n/a
0 / 0
4
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers\Api\V1;
4
5use App\Enums\Country;
6use App\Http\Controllers\Controller;
7use App\Http\Resources\CustomerResource;
8use App\Http\Resources\StatusResource;
9use App\Models\Customer;
10use App\Models\CustomerInfo;
11use App\Models\CustomerLogin;
12use App\Models\CustomerOptInHistory;
13use App\Models\SingleOptin;
14use Illuminate\Http\Request;
15use Illuminate\Support\Facades\Hash;
16use Illuminate\Support\Facades\Validator;
17use Illuminate\Support\Str;
18
19/**
20 * @codeCoverageIgnore
21 */
22class CustomerController extends Controller
23{
24    /**
25     * Store a newly created resource in storage
26     * @param \Illuminate\Http\Request $request
27     * @return \App\Http\Resources\CustomerResource|\App\Http\Resources\StatusResource
28     */
29    // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
30    public function store(Request $request)
31    {
32        $validator = Validator::make($request->all(), [
33            'email' => 'required|email',
34            'password' => 'required',
35            'firstname' => 'required',
36            'lastname' => 'required',
37            'opt_in' => 'required',
38            'birth_date' => 'date_format:Y-m-d'
39        ]);
40
41        if ($validator->fails()) {
42            return new StatusResource([
43                'status' => 'KO', 'code' => 102,
44                'message' => 'Error on required fields.', 'errors' => $validator->errors()
45            ]);
46        }
47
48        $brandId   = $request->brand_id;
49        $email     = strtolower(filter_var($request->email, FILTER_SANITIZE_EMAIL));
50        $firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING);
51        $lastname  = filter_var($request->lastname, FILTER_SANITIZE_STRING);
52        $optIn     = filter_var($request->opt_in, FILTER_SANITIZE_STRING);
53        $hashcode  = Str::uuid();
54
55        // Récupère le customer s'il existe déjà.
56        $customer = Customer::where('email', $email)->first();
57
58        // Si le customer n'existe pas déjà.
59        if (!$customer) {
60            $customer        = new Customer();
61            $customer->email = $email;
62            $customer->save();
63            $singleOptin = SingleOptin::query()->where([
64                ['email', $email], ['brand_id', $brandId]
65            ])->first();
66            if ($singleOptin) {
67                $singleOptin->delete();
68            }
69        } else {
70            // Récupère les infos s'ils existent déjà.
71            $CustomerInfo = CustomerInfo::query()->where([
72                ['customer_id', $customer->id], ['brand_id', $brandId]
73            ])->first();
74            if ($CustomerInfo) {
75                return new StatusResource([
76                    'status' => 'KO', 'code' => 103,
77                    'message' => 'User already registered on this site.'
78                ]);
79            }
80        }//end if
81
82        $customerInfos             = new CustomerInfo();
83        $customerInfos->firstname  = ucfirst(strtolower($firstname));
84        $customerInfos->lastname   = ucfirst(strtolower($lastname));
85        $customerInfos->birth_date = !empty($request->birth_date) ?
86            filter_var($request->birth_date, FILTER_SANITIZE_STRING) : null;
87        $customerInfos->address    = !empty($request->address) ?
88            filter_var($request->address, FILTER_SANITIZE_STRING) : null;
89        $customerInfos->address2   = !empty($request->address2) ?
90            filter_var($request->address2, FILTER_SANITIZE_STRING) : null;
91        $customerInfos->phone      = !empty($request->phone) ?
92            filter_var($request->phone, FILTER_SANITIZE_STRING) : null;
93        try {
94            $customerInfos->country = Country::make(filter_var($request->country, FILTER_SANITIZE_STRING));
95        } catch (\Exception $e) {
96            $customerInfos->country = Country::france();
97        }
98        $customerInfos->city        = !empty($request->city) ?
99            filter_var($request->city, FILTER_SANITIZE_STRING) : null;
100        $customerInfos->postcode    = !empty($request->postcode) ?
101            filter_var($request->postcode, FILTER_SANITIZE_STRING) : null;
102        $customerInfos->opt_in      = $optIn;
103        $customerInfos->hashcode    = $hashcode;
104        $customerInfos->brand_id    = $brandId;
105        $customerInfos->customer_id = $customer->id;
106
107        $customer->infos()->save($customerInfos);
108
109        // Création du login pour cet utilisateur sur ce service.
110        $customerLogin                  = new CustomerLogin();
111        $customerLogin->customer_id     = $customer->id;
112        $customerLogin->password        = Hash::make(trim($request->password));
113        $customerLogin->last_connection = now();
114        $customerLogin->brand_id        = $brandId;
115        $customerLogin->status          = 0;
116
117        $customer->logins()->save($customerLogin);
118
119        // Ajout dans la table d'historique des opt-in de la nouvelle valeur.
120        $optInHistory              = new CustomerOptInHistory();
121        $optInHistory->customer_id = $customer->id;
122        $optInHistory->brand_id    = $brandId;
123        $optInHistory->new_value   = $optIn;
124        $optInHistory->save();
125
126        return new CustomerResource($customer, $brandId);
127    }
128
129    /**
130     * Display the specified resource.
131     *
132     * @param  \Illuminate\Http\Request $request
133     * @param  mixed                    $type
134     * @param  mixed                    $value
135     * @return \App\Http\Resources\CustomerResource|\App\Http\Resources\StatusResource
136     */
137    public function show(Request $request, $type, $value)
138    {
139        $brandId = $request->brand_id;
140
141        if ($type === 'id') {
142            $customer = Customer::find($value);
143            if (!$customer) {
144                return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
145            }
146        } elseif ($type === 'email') {
147            $customer = Customer::where('email', $value)->first();
148            if (!$customer) {
149                return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
150            }
151
152            $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
153
154            if (!$CustomerInfo) {
155                return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
156            }
157        } else {
158            return new StatusResource(['status' => 'KO', 'code' => 101, 'message' => 'User not found.']);
159        }
160
161        return new CustomerResource($customer, $brandId);
162    }
163
164    /**
165     * Update the specified resource in storage.
166     *
167     * @param  \Illuminate\Http\Request  $request
168     * @param  integer                   $id
169     * @return \App\Http\Resources\CustomerResource|\App\Http\Resources\StatusResource
170     */
171    // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Squiz.Commenting.FunctionComment.Missing
172    public function update(Request $request, $id)
173    {
174        $validator = Validator::make($request->all(), [
175            'birth_date' => 'date_format:Y-m-d'
176        ]);
177
178        if ($validator->fails()) {
179            return new StatusResource([
180                'status' => 'KO', 'code' => 104,
181                'message' => 'Error on required fields.', 'errors' => $validator->errors()
182            ]);
183        }
184
185        $brandId  = $request->brand_id;
186        $customer = Customer::find($id);
187
188        if (!$customer) {
189            return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']);
190        }
191
192        $CustomerInfo = $customer->infos()->where('brand_id', $brandId)->first();
193        if (!$CustomerInfo) {
194            return new StatusResource(['status' => 'KO', 'code' => 105, 'message' => 'User not found.']);
195        }
196
197        if ($request->has('firstname')) {
198            $CustomerInfo->firstname = filter_var($request->firstname, FILTER_SANITIZE_STRING);
199        }
200
201        if ($request->has('lastname')) {
202            $CustomerInfo->lastname = filter_var($request->lastname, FILTER_SANITIZE_STRING);
203        }
204
205        if ($request->has('birth_date')) {
206            if (!empty($request->birth_date)) {
207                $CustomerInfo->birth_date = filter_var($request->birth_date, FILTER_SANITIZE_STRING);
208            } else {
209                $CustomerInfo->birth_date = null;
210            }
211        }
212
213        if ($request->has('address')) {
214            if (!empty($request->address)) {
215                $CustomerInfo->address = filter_var($request->address, FILTER_SANITIZE_STRING);
216            } else {
217                $CustomerInfo->address = null;
218            }
219        }
220
221        if ($request->has('address2')) {
222            if (!empty($request->address2)) {
223                $CustomerInfo->address2 = filter_var($request->address2, FILTER_SANITIZE_STRING);
224            } else {
225                $CustomerInfo->address2 = null;
226            }
227        }
228
229        if ($request->has('phone')) {
230            if (!empty($request->phone)) {
231                $CustomerInfo->phone = filter_var($request->phone, FILTER_SANITIZE_STRING);
232            } else {
233                $CustomerInfo->phone = null;
234            }
235        }
236
237        if ($request->has('country')) {
238            try {
239                $CustomerInfo->country = Country::make(filter_var($request->country, FILTER_SANITIZE_STRING));
240            } catch (\Exception $e) {
241                $CustomerInfo->country = Country::france();
242            }
243        }
244
245        if ($request->has('city')) {
246            if (!empty($request->city)) {
247                $CustomerInfo->city = filter_var($request->city, FILTER_SANITIZE_STRING);
248            } else {
249                $CustomerInfo->city = null;
250            }
251        }
252
253        if ($request->has('postcode')) {
254            if (!empty($request->postcode)) {
255                $CustomerInfo->postcode = filter_var($request->postcode, FILTER_SANITIZE_STRING);
256            } else {
257                $CustomerInfo->postcode = null;
258            }
259        }
260
261        if ($request->has('opt_in')) {
262            $optIn = filter_var($request->opt_in, FILTER_SANITIZE_STRING);
263
264            if ($CustomerInfo->opt_in != $optIn) {
265                // Ajout dans la table d'historique des opt-in de la nouvelle valeur.
266                $optInHistory              = new CustomerOptInHistory();
267                $optInHistory->customer_id = $customer->id;
268                $optInHistory->brand_id    = $brandId;
269                $optInHistory->new_value   = $optIn;
270                $optInHistory->save();
271            }
272
273            $CustomerInfo->opt_in = $optIn;
274        }
275
276        $CustomerInfo->save();
277
278        return new CustomerResource($customer, $brandId);
279    }
280
281    /**
282     * Authenticate the user
283     * @param \Illuminate\Http\Request $request
284     * @return \App\Http\Resources\CustomerResource|\App\Http\Resources\StatusResource
285     */
286    public function authenticate(Request $request)
287    {
288        $validator = Validator::make($request->all(), [
289            'email' => 'required|email',
290            'password' => 'required',
291        ]);
292
293        if ($validator->fails()) {
294            return new StatusResource([
295                'status' => 'KO', 'code' => 115,
296                'message' => 'Error on required fields.', 'errors' => $validator->errors()
297            ]);
298        }
299
300        $brandId  = $request->brand_id;
301        $email    = filter_var($request->email, FILTER_SANITIZE_EMAIL);
302        $password = trim($request->password);
303        $customer = Customer::where('email', $email)->first();
304        if ($customer) {
305            $login = $customer->logins()->where('brand_id', $brandId)->first();
306            if ($login) {
307                if (Hash::check($password, $login->password)) {
308                    if ($login->status === false) {
309                        $infos = $customer->infos()->where('brand_id', $brandId)->first();
310                        // "Le compte de l'utilisateur n'est pas activé"
311                        return new StatusResource(
312                            [
313                                'status' => 'KO',
314                                'code' => 116,
315                                'message' => 'Account not activated.',
316                                'user' => [
317                                    'crm_id' => $customer->id,
318                                    'firstname' => $infos->firstname,
319                                    'lastname' => $infos->lastname,
320                                    'email' => $customer->email,
321                                    'birth_date' => $infos->birth_date,
322                                    'address' => $infos->address,
323                                    'address2' => $infos->address2,
324                                    'phone' => $infos->phone,
325                                    'country' => $infos->country->label,
326                                    'city' => $infos->city,
327                                    'postcode' => $infos->postcode,
328                                    'opt_in' => $infos->opt_in,
329                                    'hashcode' => $infos->hashcode,
330                                    'status' => $login->status,
331                                ]
332                            ]
333                        );
334                    } //end if
335
336                    $login->last_connection = now();
337                    $customer->logins()->save($login);
338                    return new CustomerResource($customer, $brandId);
339                } //end if
340                // "Mot de passe incorrect"
341                return new StatusResource(['status' => 'KO', 'code' => 117, 'message' => 'Authentication failed.']);
342            } else {
343                // "Ce compte n'est pas enregistré pour cette marque"
344                return new StatusResource(['status' => 'KO', 'code' => 118, 'message' => 'User not found.']);
345            } //end if
346        } else {
347            // "Ce compte n'existe pas dans la CRM"
348            return new StatusResource(['status' => 'KO', 'code' => 119, 'message' => 'User not found.']);
349        } //end if
350    }
351
352    /**
353     * Change the password of the user for the brand
354     * @param \Illuminate\Http\Request $request
355     * @param mixed                    $id
356     * @return \App\Http\Resources\StatusResource
357     */
358    public function changePassword(Request $request, $id)
359    {
360        $validator = Validator::make($request->all(), [
361            'password' => 'required',
362        ]);
363
364        if ($validator->fails()) {
365            return new StatusResource([
366                'status' => 'KO', 'code' => 107,
367                'message' => 'Error on required fields.', 'errors' => $validator->errors()
368            ]);
369        }
370
371        $brandId  = $request->brand_id;
372        $customer = Customer::find($id);
373        $password = trim($request->password);
374
375        if ($customer) {
376            $customerLogin = $customer->logins()->where('brand_id', $brandId)->first();
377            if ($customerLogin) {
378                // Check if same password.
379                if (Hash::check($password, $customerLogin->password)) {
380                    return new StatusResource(['status' => 'KO', 'code' => 123, 'message' => 'Same password.']);
381                }
382
383                $customerLogin->password = Hash::make($password);
384                $customerLogin->save();
385            } else {
386                // "Ce compte n'est pas enregistré pour cette marque"
387                return new StatusResource(['status' => 'KO', 'code' => 108, 'message' => 'User not found.']);
388            }
389        } else {
390            // "Ce compte n'existe pas"
391            return new StatusResource(['status' => 'KO', 'code' => 109, 'message' => 'User not found.']);
392        }
393        return new StatusResource(['status' => 'OK', 'message' => 'Update complete.']);
394    }
395
396    /**
397     * Activate the account of the user
398     * @param \Illuminate\Http\Request $request
399     * @param mixed                    $id
400     * @return \App\Http\Resources\StatusResource
401     */
402    public function activateAccount(Request $request, $id)
403    {
404        $brandId = $request->brand_id;
405
406        $customer = Customer::find($id);
407        if ($customer) {
408            $login = $customer->logins()->where('brand_id', $brandId)->first();
409            if ($login) {
410                if ($login->status === false) {
411                    $login->status = true;
412                    $login->save();
413                    // "Le compte a bien Ã©té activé"
414                    return new StatusResource(['status' => 'OK', 'message' => 'Update complete.']);
415                } else {
416                    // "Ce compte est déjà activé"
417                    return new StatusResource(['status' => 'KO', 'code' => 110, 'message' => 'User already active.']);
418                }
419            } else {
420                // "Ce compte n'est pas enregistré pour cette marque"
421                return new StatusResource(['status' => 'KO', 'code' => 111, 'message' => 'User not found.']);
422            }
423        } else {
424            // "Cet utilisateur n'existe pas dans la CRM"
425            return new StatusResource(['status' => 'KO', 'code' => 112, 'message' => 'User not found.']);
426        }
427    }
428
429    /**
430     * Delete the user account for the brand
431     * @param \Illuminate\Http\Request $request
432     * @param mixed                    $id
433     */
434    // phpcs:disable Squiz.PHP.NonExecutableCode.Unreachable, Squiz.Commenting.FunctionComment.Missing
435    public function delete(Request $request, $id)
436    {
437        die("Not implemented yet");
438        // TODO : Gestion avec mesBR.
439        $brandId = $request->brand_id;
440
441        $customer = Customer::find($id);
442        if ($customer) {
443            $customer->infos()->where('brand_id', $brandId)->first()->delete();
444            $customer->logins()->where('brand_id', $brandId)->first()->delete();
445
446            if (count($customer->infos) === 0 && count($customer->logins) === 0) {
447                $customer->optInHistory()->delete();
448                $customer->delete();
449                // Suppression du compte de la CRM terminée.
450                return new StatusResource(['status' => 'OK', 'message' => 'Deletion complete.']);
451            } else {
452                // Suppression des informations du compte pour la marque terminée.
453                return new StatusResource(['status' => 'OK', 'message' => 'Deletion complete.']);
454            }
455        } else {
456            return new StatusResource(['status' => 'KO', 'code' => 106, 'message' => 'User not found.']);
457        }
458        // phpcs:enable Squiz.PHP.NonExecutableCode.Unreachable, Squiz.Commenting.FunctionComment.Missing
459    }
460
461    /**
462     * Change the status of the opt-in for the customer on this brand
463     * @param \Illuminate\Http\Request $request
464     * @param mixed                    $id
465     * @return \App\Http\Resources\StatusResource
466     */
467    public function toggleOptin(Request $request, $id)
468    {
469        // TODO : Liaison des opt-in avec MailJet.
470        $brandId = $request->brand_id;
471
472        $customer = Customer::find($id);
473        if ($customer) {
474            $infos = $customer->infos()->where('brand_id', $brandId)->first();
475            if ($infos) {
476                $infos->opt_in = $infos->opt_in === 0 ? 1 : 0;
477
478                // On enregistre l'historique du changement d'optin.
479                $optinHistory              = new CustomerOptInHistory();
480                $optinHistory->customer_id = $customer->id;
481                $optinHistory->new_value   = $infos->opt_in;
482                $optinHistory->brand_id    = $brandId;
483                $optinHistory->save();
484
485                $infos->save();
486                // "Changement effectué"
487                return new StatusResource(['status' => 'OK', 'message' => 'Update complete.']);
488            } else {
489                // "Ce compte n'a pas d'information liés Ã  cette marque"
490                return new StatusResource(['status' => 'KO', 'code' => 113, 'message' => 'User not found.']);
491            }
492        } else {
493            // "Cet utilisateur n'existe pas dans la CRM"
494            return new StatusResource(['status' => 'KO', 'code' => 114, 'message' => 'User not found.']);
495        } //end if
496    }
497
498    /**
499     * Remove opt-in for the customer on this brand
500     * @param \Illuminate\Http\Request $request
501     * @param mixed                    $hashcode
502     * @return \App\Http\Resources\StatusResource
503     */
504    public function removeOptin(Request $request, $hashcode)
505    {
506        // TODO : Liaison des opt-in avec MailJet.
507        $brandId = $request->brand_id;
508
509        $infos = CustomerInfo::where([
510            ['brand_id', $brandId],
511            ['hashcode', $hashcode]
512        ])->first();
513
514        if ($infos) {
515            $infos->opt_in = 0;
516
517            // On enregistre l'historique du changement d'optin.
518            $optinHistory              = new CustomerOptInHistory();
519            $optinHistory->customer_id = $infos->customer_id;
520            $optinHistory->new_value   = 0;
521            $optinHistory->brand_id    = $brandId;
522            $optinHistory->save();
523
524            $infos->save();
525            // "Changement effectué"
526            return new StatusResource(['status' => 'OK', 'message' => 'Update complete.']);
527        } else {
528            // "Ce compte n'a pas d'information liés Ã  cette marque"
529            return new StatusResource(['status' => 'KO', 'code' => 122, 'message' => 'User not found.']);
530        }
531    }
532
533    /**
534     * @param \Illuminate\Http\Request $request
535     * @return void
536     */
537    // phpcs:ignore Squiz.PHP.NonExecutableCode.Unreachable, Squiz.Commenting.FunctionComment.Missing
538    public function addSingleOptin(Request $request)
539    {
540        $validator = Validator::make($request->all(), [
541            'email' => 'required|email',
542        ]);
543
544        if ($validator->fails()) {
545            return new StatusResource([
546                'status' => 'KO', 'code' => 120,
547                'message' => 'Error on required fields.', 'errors' => $validator->errors()
548            ]);
549        }
550
551        $brandId = $request->brand_id;
552        $email   = filter_var($request->email, FILTER_SANITIZE_EMAIL);
553
554        // Check si le customer ou l'optin simple existe avant de l'ajouter en optin simple.
555        if (
556            !SingleOptin::query()->where([['email', $email], ['brand_id', $brandId]])->exists() &&
557            !CustomerInfo::join("customers", "customer_infos.customer_id", "=", "customers.id")->where([
558                ['brand_id', $brandId], ['email', $email]
559            ])->exists()
560        ) {
561            $singleOptin           = new SingleOptin();
562            $singleOptin->email    = $email;
563            $singleOptin->brand_id = $brandId;
564            $singleOptin->save();
565        } else {
566            // "Cet utilisateur est déjà inscrit Ã  la newsletter pour cette marque"
567            return new StatusResource([
568                'status' => 'KO', 'code' => 121,
569                'message' => 'This user is already subscribed to the newsletter for this brand.'
570            ]);
571        }
572        return new StatusResource(['status' => 'OK', 'message' => 'Optin added.']);
573    }
574}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/Api/V1/index.html b/tests/coverage/Http/Controllers/Api/V1/index.html deleted file mode 100644 index e100563..0000000 --- a/tests/coverage/Http/Controllers/Api/V1/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/Api/V1 - - - - - - - -
-
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
ArcadiaController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
CustomerController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Http/Controllers/Api/dashboard.html b/tests/coverage/Http/Controllers/Api/dashboard.html deleted file mode 100644 index 74f2645..0000000 --- a/tests/coverage/Http/Controllers/Api/dashboard.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Controllers/Api - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Controllers/Api/index.html b/tests/coverage/Http/Controllers/Api/index.html deleted file mode 100644 index e145e37..0000000 --- a/tests/coverage/Http/Controllers/Api/index.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/Api - - - - - - - -
-
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
171 / 171
-
- 100.00% covered (success) -
-
-
100.00%
21 / 21
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
V1
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
V2
-
- 100.00% covered (success) -
-
-
100.00%
171 / 171
-
- 100.00% covered (success) -
-
-
100.00%
21 / 21
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
-
- -
- - diff --git a/tests/coverage/Http/Controllers/BrandController.php.html b/tests/coverage/Http/Controllers/BrandController.php.html deleted file mode 100644 index a8499e3..0000000 --- a/tests/coverage/Http/Controllers/BrandController.php.html +++ /dev/null @@ -1,320 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/BrandController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
BrandController
n/a
0 / 0
n/a
0 / 0
9
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
2
 create
n/a
0 / 0
n/a
0 / 0
1
 store
n/a
0 / 0
n/a
0 / 0
2
 edit
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
2
 jsonPaginate
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Http\Controllers\Controller;
6use App\Http\Requests\Brands\StoreBrandRequest;
7use App\Http\Requests\Brands\UpdateBrandRequest;
8use App\Models\Brand;
9use Illuminate\Database\Eloquent\Builder;
10use Illuminate\Http\Request;
11use Illuminate\Support\Facades\DB;
12use Illuminate\Support\Str;
13
14/**
15 * @codeCoverageIgnore
16 */
17class BrandController extends Controller
18{
19    /**
20     * Display a listing of the resource.
21     *
22     * @param \Illuminate\Http\Request $request
23     * @return \Illuminate\Http\Response
24     */
25    public function index(Request $request)
26    {
27        /** @var \Illuminate\Database\Eloquent\Builder $query */
28        $query = Brand::query();
29
30        if ($request->search) {
31            $this->searchQuery(
32                $query,
33                $request->search,
34                null,
35                'name',
36            );
37        }
38
39        $this->sortQuery($query);
40        $searchFields = trans('Nom');
41
42        $query->withCount(['customerInfos', 'customerInfos as customer_infos_optin_count' => function (Builder $q) {
43            $q->where('opt_in', true);
44        }]);
45
46        $brandModels = $query->paginate(12);
47
48        return view('brands.index', compact('brandModels', 'searchFields'));
49    }
50
51    /**
52     * Show the form for creating a new resource.
53     *
54     * @param \App\Models\Brand $brands
55     * @return \Illuminate\Http\Response
56     */
57    public function create(Brand $brands)
58    {
59        return view('brands.create', [
60            'brandModel' => $brands
61        ]);
62    }
63
64    /**
65     * Store a newly created resource in storage.
66     *
67     * @param \App\Http\Requests\Brands\StoreBrandRequest $request
68     * @return \Illuminate\Http\Response
69     */
70    public function store(StoreBrandRequest $request)
71    {
72        return DB::transaction(function () use ($request) {
73            $brand = new Brand();
74            $brand->fill($request->validated());
75            if ($brand->saveOrFail()) {
76                return redirect()
77                    ->route('brands.edit', $brand)
78                    ->with('success', trans(
79                        ':model a Ã©té créé.',
80                        ['model' => Str::singular(trans('models.classes.brands'))]
81                    ));
82            }
83            return redirect()
84                ->back()
85                ->with('error', trans(
86                    ':model n\'a pas Ã©té créé.',
87                    ['model' => Str::singular(trans('models.classes.brands'))]
88                ));
89        });
90    }
91
92    /**
93     * Show the form for editing the specified resource.
94     *
95     * @param \App\Models\Brand $brands
96     * @return \Illuminate\Http\Response
97     */
98    public function edit(Brand $brands)
99    {
100        return view('brands.edit', [
101            'brandModel' => $brands
102        ]);
103    }
104
105    /**
106     * Update a spacific resource in storage.
107     *
108     * @param \App\Http\Requests\Brands\UpdateBrandRequest $request
109     * @param \App\Models\Brand                            $brand
110     * @return \Illuminate\Http\Response
111     */
112    public function update(UpdateBrandRequest $request, Brand $brand)
113    {
114        return DB::transaction(function () use ($request, $brand) {
115            $brand->fill($request->validated());
116            if ($brand->saveOrFail()) {
117                return redirect()
118                    ->route('brands.edit', $brand)
119                    ->with('success', trans(
120                        ':model a Ã©té mis Ã  jour.',
121                        ['model' => Str::singular(trans('models.classes.brands'))]
122                    ));
123            }
124            return redirect()
125                ->back()
126                ->with('error', trans(
127                    ':model n\'a pas Ã©té mis Ã  jour.',
128                    ['model' => Str::singular(trans('models.classes.brands'))]
129                ));
130        });
131    }
132
133    /**
134     * Store a newly created resource in storage.
135     *
136     * @return \Illuminate\Http\Response
137     */
138    public function jsonPaginate()
139    {
140        return DB::transaction(function () {
141            /** @var \App\Models\Tag */
142            $brandModels = Brand::paginate(12);
143            return \response()->json($brandModels ?? 500);
144        });
145    }
146}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/Controller.php.html b/tests/coverage/Http/Controllers/Controller.php.html deleted file mode 100644 index 05d7ffc..0000000 --- a/tests/coverage/Http/Controllers/Controller.php.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/Controller.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
Controller
n/a
0 / 0
n/a
0 / 0
19
n/a
0 / 0
 searchQuery
n/a
0 / 0
n/a
0 / 0
5
 searchOnFields
n/a
0 / 0
n/a
0 / 0
3
 sortQuery
n/a
0 / 0
n/a
0 / 0
11
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
7use Illuminate\Foundation\Bus\DispatchesJobs;
8use Illuminate\Foundation\Validation\ValidatesRequests;
9use Illuminate\Routing\Controller as BaseController;
10use Illuminate\Support\Facades\Schema;
11use Illuminate\Support\Facades\Session;
12
13/**
14 * @codeCoverageIgnore
15 */
16class Controller extends BaseController
17{
18    use AuthorizesRequests;
19    use DispatchesJobs;
20    use ValidatesRequests;
21
22    /**
23     * Build Search query on specified fields
24     * splitting search into words or using whole sentence
25     *
26     * @param \Illuminate\Database\Eloquent\Builder $query       The eloquent query builder.
27     * @param string                                $search      The query string.
28     * @param callable                              $searchQuery The query string.
29     * @param string                                ...$fields   Either a simple array or a string
30     *                                                           in case of a string the two fields
31     *                                                           would be concatenate to make the search.
32     * @return \Illuminate\Database\Eloquent\Builder
33     */
34    protected function searchQuery(
35        Builder $query,
36        string $search,
37        callable $searchQuery = null,
38        string ...$fields
39    ): Builder {
40        if (count($fields)) {
41            // Search using words.
42            $words = explode(' ', $search);
43            $query->where(function ($query) use ($searchQuery, $words, $search, $fields) {
44                foreach ($words as $word) {
45                    if (!strlen($word)) {
46                        continue;
47                    }
48                    $this->searchOnFields($query, $word, $fields);
49                } //end foreach
50                // Search whole using whole sentence.
51                $this->searchOnFields($query, $search, $fields);
52                if ($searchQuery) {
53                    $searchQuery($query);
54                }
55            });
56        } //end if
57        return $query;
58    }
59
60    /**
61     * Build query search on all fields
62     *
63     * @param \Illuminate\Database\Eloquent\Builder $query
64     * @param string                                $search
65     * @param array                                 $fields
66     * @return \Illuminate\Database\Eloquent\Builder
67     */
68    private function searchOnFields(Builder $query, string $search, array $fields): Builder
69    {
70        $table = $query->getModel()->getTable();
71        foreach ($fields as $f) {
72            $f = str_replace('?', '\?', $f);
73            if (is_array($f)) {
74                $query->orWhereRaw(
75                    sprintf("UPPER(CONCAT(%s)) LIKE UPPER(?)", collect($f)->map(function ($q) use ($table) {
76                        return "`$table`.`$q`";
77                    })->implode(', \' \', ')),
78                    ['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
79                );
80            } else {
81                $query->orWhereRaw(
82                    "UPPER(`$table`.`$f`) LIKE UPPER(?)",
83                    ['%' . filter_var($search, FILTER_SANITIZE_STRING) . '%']
84                );
85            }
86        }
87        return $query;
88    }
89
90    /**
91     * Sort columns with a query
92     *
93     * @param \Illuminate\Database\Eloquent\Builder $query
94     * @param array                                 $ignore
95     * @return \Illuminate\Database\Eloquent\Builder
96     */
97    protected function sortQuery(Builder $query, array $ignore = []): Builder
98    {
99        $rName = request()->route()->getName();
100        $table = $query->getModel()->getTable();
101        if (request()->sort_col) {
102            Session::put("$rName.sort_col", request()->sort_col);
103            Session::put("$rName.sorted", true);
104        }
105        if (request()->sort_way) {
106            Session::put("$rName.sort_way", request()->sort_way);
107            Session::put("$rName.sorted", true);
108        }
109        if (request()->rst) {
110            Session::remove("$rName.sorted");
111            Session::remove("$rName.sort_col");
112            Session::remove("$rName.sort_way");
113        }
114        if (request()->sort_col and request()->sort_way and Schema::hasColumn($table, request()->sort_col)) {
115            if (
116                !isset($ignore) or
117                (isset($ignore) and !in_array(request()->sort_col, $ignore))
118            ) {
119                $query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
120            }
121        } elseif (Schema::hasColumn($table, 'order')) {
122            $query = $query->orderBy('order', 'ASC');
123            Session::put("$rName.sort_col", 'order');
124            Session::put("$rName.sort_way", 'asc');
125        }
126        return $query;
127    }
128}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/CustomerController.php.html b/tests/coverage/Http/Controllers/CustomerController.php.html deleted file mode 100644 index e990c8e..0000000 --- a/tests/coverage/Http/Controllers/CustomerController.php.html +++ /dev/null @@ -1,471 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/CustomerController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CustomerController
n/a
0 / 0
n/a
0 / 0
31
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
13
 show
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
8
 export
n/a
0 / 0
n/a
0 / 0
1
 exportSubmit
n/a
0 / 0
n/a
0 / 0
8
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Enums\CustomerExportType;
6use App\Http\Requests\CustomerExport;
7use App\Models\Brand;
8use App\Models\Customer;
9use App\Models\CustomerInfo;
10use App\Models\CustomerLogin;
11use App\Models\SingleOptin;
12use Illuminate\Database\Eloquent\Builder;
13use Illuminate\Database\Query\JoinClause;
14use Illuminate\Http\Request;
15use Illuminate\Support\Carbon;
16use Illuminate\Support\Facades\DB;
17use Illuminate\Support\Facades\Schema;
18use Illuminate\Support\Facades\Session;
19use Illuminate\Support\Str;
20use League\Csv\Writer;
21use Symfony\Component\HttpFoundation\StreamedResponse;
22
23/**
24 * @codeCoverageIgnore
25 */
26class CustomerController extends Controller
27{
28    /**
29     * View all customers
30     *
31     * @param \Illuminate\Http\Request $request
32     * @return \Illuminate\Http\Response
33     */
34    public function index(Request $request)
35    {
36        $modelTable = (new Customer())->getTable();
37        $modelClass = Customer::class;
38        $brands     = Brand::select('id', 'name')->get();
39        $columns    = ['customer_infos.lastname', 'customer_infos.firstname'];
40
41        // * Jointure pour pouvoir trier les colonnes.
42        /** @var \Illuminate\Database\Eloquent\Builder */
43        $query = CustomerInfo::join('customer_logins', function (JoinClause $join) {
44            $join->on('customer_logins.brand_id', '=', 'customer_infos.brand_id')
45            ->on('customer_logins.customer_id', '=', 'customer_infos.customer_id');
46        })
47        ->join('customers', function (JoinClause $join) {
48            $join->on('customers.id', '=', 'customer_infos.customer_id');
49        })
50        ->join('brands', function (JoinClause $join) {
51            $join->on('brands.id', '=', 'customer_infos.brand_id');
52        });
53
54        if ($request->rst) {
55            Session::remove("crud.$modelTable.brand");
56            Session::remove("crud.$modelTable.search");
57            Session::remove("crud.$modelTable.sort_way");
58            Session::remove("crud.$modelTable.sort_col");
59        }
60
61        if (request()->brand or Session::has("crud.$modelTable.brand")) {
62            $brand = $request->brand ?? Session::get("crud.$modelTable.brand");
63            Session::put("crud.$modelTable.brand", $brand);
64            $query = $query->where('customer_infos.brand_id', $brand);
65        }
66
67        // * Search on crud Label column.
68        if ($request->search or Session::has("crud.$modelTable.search")) {
69            $search = $request->search ?? Session::get("crud.$modelTable.search");
70            Session::put("crud.$modelTable.search", $search);
71            $query->where(function ($q) use ($columns, $search) {
72                foreach ($columns as $column) {
73                    $q = $q->orWhere($column, 'LIKE', "%$search%");
74                }
75                $q = $q->orWhere('customers.email', 'LIKE', "%$search%");
76                $q = $q->orWhere('customer_logins.last_connection', 'LIKE', "%$search%");
77            });
78        }
79
80        // * Sort columns.
81        if ($request->sort_col) {
82            Session::put("crud.$modelTable.sort_col", $request->sort_col);
83        }
84        if ($request->sort_way) {
85            Session::put("crud.$modelTable.sort_way", $request->sort_way);
86        }
87
88        if (
89            Session::has("crud.$modelTable.sort_col") and
90            Session::has("crud.$modelTable.sort_way") and
91            (Schema::hasColumn($modelTable, Session::get("crud.$modelTable.sort_col")) or
92            in_array(Session::get("crud.$modelTable.sort_col"), [
93                'brands.name',
94                'customers.email',
95                'customer_infos.firstname',
96                'customer_infos.lastname',
97                'customer_infos.opt_in',
98                'customer_logins.last_connection',
99                'customer_logins.status'
100            ]))
101        ) {
102            $query = $query->orderBy(
103                Session::get("crud.$modelTable.sort_col"),
104                Session::get("crud.$modelTable.sort_way")
105            );
106        }
107
108        $searchFields = trans('Email, Prénom, Nom');
109
110        /** @var \Illuminate\Pagination\AbstractPaginator */
111        $customerModels = $query->paginate(50);
112
113        return view('customers.index', compact(
114            'modelClass',
115            'modelTable',
116            'customerModels',
117            'brands',
118            'searchFields'
119        ));
120    }
121
122    /**
123     * Show a specific custmer
124     *
125     * @param \App\Models\Customer $customer
126     * @param \App\Models\Brand    $brand
127     * @return \Illuminate\Http\Response
128     */
129    public function show(Customer $customer, Brand $brand)
130    {
131        $customerInfos = CustomerInfo::where('customer_id', $customer->id)
132            ->where('brand_id', $brand->id)->firstOrFail();
133        $customerLogin = CustomerLogin::where('customer_id', $customer->id)
134            ->where('brand_id', $brand->id)->first();
135
136        return view('customers.show', compact(
137            'customerInfos',
138            'customerLogin'
139        ));
140    }
141
142    /**
143     * Delete a specific customer info
144     *
145     * @param \App\Models\Customer $customer
146     * @param \App\Models\Brand    $brand
147     * @return \Illuminate\Http\RedirectResponse
148     */
149    public function delete(Customer $customer, Brand $brand)
150    {
151        try {
152            // TODO: Gestion avec mesBR.
153            /** @var \App\Models\CustomerInfo */
154            $customerInfo = $customer->info($brand, Customer::FIRST_OR_FAIL);
155            /** @var \App\Models\CustomerLogin */
156            $customerLogin = $customer->login($brand, Customer::FIRST_OR_FAIL);
157
158            DB::transaction(function () use (
159                $brand,
160                $customer,
161                $customerLogin,
162                $customerInfo
163            ) {
164                if (
165                    $customerInfo->delete() === false ||
166                    $customerLogin->delete() === false
167                ) {
168                    throw new \ErrorException('Api failed to delete customer.');
169                };
170
171                $customer->optInHistory()->whereHas('brand', function ($q) use ($brand) {
172                    $q->where('id', $brand->id);
173                })->delete();
174
175                $logins  = $customer->logins()->count();
176                $infos   = $customer->infos()->count();
177                $history = $customer->optInHistory()->count();
178
179                // If customer is not associated with any brand then delete.
180                if (!$logins and !$infos and !$history) {
181                    if ($customer->delete() === false) {
182                        throw new \ErrorException('Api failed to update customer.');
183                    }
184                }
185            });
186        } catch (\ErrorException $e) {
187            return redirect()->route('customers.index')
188            ->with('error', trans('Le compte n\'a pas pu Ãªtre supprimé.'));
189        } //end try
190
191        return redirect()->route('customers.index')->with('warning', trans('Compte supprimé.'));
192    }
193
194    /**
195     * Show export the customers form
196     *
197     * @return \Illuminate\Http\Response
198     */
199    public function export()
200    {
201        $brands = Brand::all();
202        return view('customers.export', compact(
203            'brands'
204        ));
205    }
206
207    /**
208     * Exports customers following the requested export type
209     *
210     * @param \App\Http\Requests\CustomerExport $request
211     * @return \Symfony\Component\HttpFoundation\StreamedResponse
212     * @throws \Exception If the request export type in not handled.
213     */
214    public function exportSubmit(CustomerExport $request)
215    {
216        $pagination = 2000;
217        $header     = '"lastname";"firstname";"email";"hashcode"';
218
219        $w = Writer::createFromString($header . PHP_EOL);
220        $w->setDelimiter(';');
221        $w->setEnclosure('"');
222        $w->setOutputBOM(Writer::BOM_UTF8);
223
224        $exportType = CustomerExportType::make(\intval($request->export_type));
225
226        // * We send the CSV using Symfony StreamedResponse.
227        $response = new StreamedResponse();
228        $response->headers->set('Content-Encoding', 'none');
229        $response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
230        $response->headers->set('Description', 'File Transfer');
231        $response->headers->set(
232            'Content-Disposition',
233            sprintf(
234                'attachment; filename="Export pour newsletter %s %s.csv"',
235                Carbon::now()->format('d-m-Y_H-i'),
236                Str::slug($exportType)
237            )
238        );
239
240        switch ($exportType) {
241            case CustomerExportType::both():
242                $continue = true;
243                // ! Continue
244            case CustomerExportType::customer():
245                $page = 1;
246                /** @var \Illuminate\Database\Eloquent\Builder $req */
247                $req   = CustomerInfo::where('brand_id', $request->brand)
248                ->where('opt_in', true);
249                $total = $req->count();
250                $pages = \ceil($total / $pagination);
251                do {
252                    /** @var \Illuminate\Pagination\LengthAwarePaginator $items */
253                    $items = $req->with('customer', function ($q) {
254                        $q->select(['id', 'email']);
255                    })->paginate(
256                        $pagination,
257                        ['lastname', 'firstname', 'hashcode', 'customer_id'],
258                        'page',
259                        $page
260                    );
261                    $items->map(function (CustomerInfo $customerInfo) use ($w) {
262                        $w->insertOne([
263                            'lastname' => $customerInfo->lastname,
264                            'firstname' => $customerInfo->firstname,
265                            'email' => $customerInfo->customer->email,
266                            'hashcode' => $customerInfo->hashcode
267                        ]);
268                    });
269                } while ($page++ <= $pages);
270                if (!isset($continue)) {
271                    break;
272                }
273                // ! Continue
274            case CustomerExportType::singleOptin():
275                SingleOptin::query()->whereHas('brand', function (Builder $q) use ($request) {
276                    $q->where('id', $request->brand);
277                })->get()->map(function (SingleOptin $singleOptin) use ($w) {
278                    $w->insertOne([
279                        'lastname' => '',
280                        'firstname' => '',
281                        'email' => $singleOptin->email,
282                        'hashcode' => $singleOptin->hashcode
283                    ]);
284                });
285                break;
286            default:
287                throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label));
288        } //end switch
289
290        /*
291            We create a callable to output the CSV in chunk
292            with Symfony StreamResponse you can flush the body content if necessary
293            see Symfony documentation for more information
294        */
295
296        // * The flush value should depend on your CSV size.
297        $flush_threshold = 1000;
298
299        $response->setCallback(function () use ($w, $flush_threshold) {
300            foreach ($w->chunk(1024) as $offset => $chunk) {
301                echo $chunk;
302                if ($offset % $flush_threshold === 0) {
303                    flush();
304                }
305            }
306        });
307        return $response;
308    }
309}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/LoginController.php.html b/tests/coverage/Http/Controllers/LoginController.php.html deleted file mode 100644 index 815f4cc..0000000 --- a/tests/coverage/Http/Controllers/LoginController.php.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/LoginController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
LoginController
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
1
 authenticate
n/a
0 / 0
n/a
0 / 0
3
 logout
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use Illuminate\Http\Request;
6use Illuminate\Support\Facades\Auth;
7
8/**
9 * @codeCoverageIgnore
10 */
11class LoginController extends Controller
12{
13    /**
14     * Shows the login view
15     *
16     * @return \Illuminate\Http\Response
17     */
18    public function index()
19    {
20        return view('login');
21    }
22
23    /**
24     * Undocumented function
25     *
26     * @param \Illuminate\Http\Request $request
27     * @return \Illuminate\Http\Response
28     */
29    public function authenticate(Request $request)
30    {
31        if (
32            Auth::attempt(
33                ["email" => $request->email, "password" => $request->password],
34                $request->remember !== null ? true : false
35            )
36        ) {
37            return redirect()->route('customers.index');
38        } else {
39            return back()->withInput($request->except("password"))
40                ->withErrors("Les identifiants entrés sont incorrects, veuillez réessayer.");
41        }
42    }
43
44    /**
45     * Undocumented function
46     *
47     * @return \Illuminate\Http\Response
48     */
49    public function logout()
50    {
51        Auth::logout();
52        return redirect()->route('login');
53    }
54}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/QuestionController.php.html b/tests/coverage/Http/Controllers/QuestionController.php.html deleted file mode 100644 index f7c3dca..0000000 --- a/tests/coverage/Http/Controllers/QuestionController.php.html +++ /dev/null @@ -1,480 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/QuestionController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
QuestionController
n/a
0 / 0
n/a
0 / 0
20
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
3
 create
n/a
0 / 0
n/a
0 / 0
1
 store
n/a
0 / 0
n/a
0 / 0
2
 edit
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
2
 show
n/a
0 / 0
n/a
0 / 0
1
 destroy
n/a
0 / 0
n/a
0 / 0
2
 export
n/a
0 / 0
n/a
0 / 0
1
 exportSubmit
n/a
0 / 0
n/a
0 / 0
7
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Http\Controllers\Controller;
6use App\Http\Requests\QuestionExport;
7use App\Http\Requests\Questions\StoreQuestionRequest;
8use App\Http\Requests\Questions\UpdateQuestionRequest;
9use App\Models\Brand;
10use App\Models\Question;
11use Carbon\Carbon;
12use Illuminate\Http\Request;
13use Illuminate\Support\Facades\DB;
14use Illuminate\Support\Str;
15use League\Csv\Writer;
16use Symfony\Component\HttpFoundation\StreamedResponse;
17
18/**
19 * @codeCoverageIgnore
20 */
21class QuestionController extends Controller
22{
23    /**
24     * Display a listing of the resource.
25     *
26     * @param \Illuminate\Http\Request $request
27     * @return \Illuminate\Http\Response
28     */
29    public function index(Request $request)
30    {
31        /** @var \Illuminate\Database\Eloquent\Builder $query */
32        $query = Question::query();
33
34        if ($request->brand) {
35            $query = $query->where('brand_id', '=', request()->brand);
36        }
37        if ($request->search) {
38            $this->searchQuery(
39                $query,
40                $request->search,
41                null,
42                'question',
43            );
44        }
45
46        $this->sortQuery($query);
47        $searchFields = trans('Question');
48
49        /** @var \Illuminate\Pagination\AbstractPaginator */
50        $questionModels = $query->paginate(12);
51
52        return view('questions.index', compact('questionModels', 'searchFields'));
53    }
54
55    /**
56     * Show the form for creating a new resource.
57     *
58     * @param \App\Models\Question $question
59     * @return \Illuminate\Http\Response
60     */
61    public function create(Question $question)
62    {
63        $brandModels = Brand::get();
64
65        return view('questions.create', [
66            'questionModel' => $question,
67            'brandModels' => $brandModels
68        ]);
69    }
70
71    /**
72     * Store a newly created resource in storage.
73     *
74     * @param \App\Http\Requests\Questions\StoreQuestionRequest $request
75     * @return \Illuminate\Http\Response
76     */
77    public function store(StoreQuestionRequest $request)
78    {
79        return DB::transaction(function () use ($request) {
80            $question = new Question();
81            $question->fill($request->validated());
82            if ($question->saveOrFail()) {
83                return redirect()
84                    ->route('questions.edit', $question)
85                    ->with('success', trans(
86                        ':model a Ã©té créé.',
87                        ['model' => Str::singular(trans('models.classes.questions'))]
88                    ));
89            }
90            return redirect()
91                ->back()
92                ->with('error', trans(
93                    ':model n\'a pas Ã©té créé.',
94                    ['model' => Str::singular(trans('models.classes.questions'))]
95                ));
96        });
97    }
98
99    /**
100     * Show the form for editing the specified resource.
101     *
102     * @param \App\Models\Question $question
103     * @return \Illuminate\Http\Response
104     */
105    public function edit(Question $question)
106    {
107        $brandModels = Brand::get();
108
109        return view('questions.edit', [
110            'questionModel' => $question,
111            'brandModels' => $brandModels
112        ]);
113    }
114
115    /**
116     * Update a spacific resource in storage.
117     *
118     * @param \App\Http\Requests\Questions\UpdateQuestionRequest $request
119     * @param \App\Models\Question                               $question
120     * @return \Illuminate\Http\Response
121     */
122    public function update(UpdateQuestionRequest $request, Question $question)
123    {
124        return DB::transaction(function () use ($request, $question) {
125            $question->fill($request->validated());
126            if ($question->saveOrFail()) {
127                return redirect()
128                    ->route('questions.edit', $question)
129                    ->with('success', trans(
130                        ':model a Ã©té mis Ã  jour.',
131                        ['model' => Str::singular(trans('models.classes.questions'))]
132                    ));
133            }
134            return redirect()
135                ->back()
136                ->with('error', trans(
137                    ':model n\'a pas Ã©té mis Ã  jour.',
138                    ['model' => Str::singular(trans('models.classes.questions'))]
139                ));
140        });
141    }
142
143    /**
144     * Show the specified resource.
145     *
146     * @param \App\Models\Question $question
147     * @return \Illuminate\Http\Response
148     */
149    public function show(Question $question)
150    {
151        return view('questions.show', [
152            'questionModel' => $question
153        ]);
154    }
155
156    /**
157     * Remove the specified resource from storage.
158     *
159     * @param \App\Models\Question $question
160     * @return \Illuminate\Http\RedirectResponse
161     */
162    public function destroy(Question $question): \Illuminate\Http\RedirectResponse
163    {
164        if ($question->deleteOrFail()) {
165            return redirect()->route('questions.index')
166                ->with('success', trans(
167                    ':model a Ã©té supprimé.',
168                    ['model' => Str::singular(trans('models.classes.questions'))]
169                ));
170        }
171        return redirect()->back()
172            ->with('error', trans(
173                ':model n\'a pas pu Ãªtre supprimé.',
174                ['model' => Str::singular(trans('models.classes.questions'))]
175            ));
176    }
177
178    /**
179     * Shows the question export form
180     *
181     * @return \Illuminate\Http\Response
182     */
183    public function export()
184    {
185        $brands = Brand::all();
186        return view('questions.export', compact('brands'));
187    }
188
189    /**
190     * Exports questions as csv the requested export type
191     *
192     * @param \App\Http\Requests\QuestionExport $request
193     * @return \Symfony\Component\HttpFoundation\StreamedResponse
194     */
195    public function exportSubmit(QuestionExport $request)
196    {
197        $results   = [];
198        $brand     = Brand::findOrFail($request->brand);
199        $questions = $brand->questions->load('answers');
200
201        foreach ($questions as $question) {
202            $answersCounts = DB::query()
203                ->selectRaw('count(*) as total, answer_id')
204                ->from('customer_answer')
205                ->whereIn('answer_id', $question->answers->pluck('id'))
206                ->groupBy('answer_id')
207                ->get()->keyBy('answer_id');
208
209            $results[$question->id] = [
210                'question' => $question->question,
211                'answers' => []
212            ];
213            foreach ($question->answers as $answer) {
214                $results[$question->id]['answers'][] =
215                    [
216                        "$answer->answer ",
217                        $answersCounts[$answer->id]->total ?? 0
218                    ];
219            }
220        }
221
222        $w = Writer::createFromString('');
223        $w->setDelimiter(';');
224        $w->setEnclosure('"');
225        $w->setOutputBOM(Writer::BOM_UTF8);
226
227        // * We send the CSV using Symfony StreamedResponse.
228        $response = new StreamedResponse();
229        $response->headers->set('Content-Encoding', 'none');
230        $response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
231        $response->headers->set('Description', 'File Transfer');
232        $response->headers->set(
233            'Content-Disposition',
234            sprintf(
235                'attachment; filename="Export pour newsletter %s %s.csv"',
236                Carbon::now()->format('d-m-Y_H-i'),
237                Str::slug($brand->name)
238            )
239        );
240
241        foreach ($results as $result) {
242            $w->insertOne([
243                'question' => $result['question'],
244            ]);
245            foreach ($result['answers'] as $answer) {
246                $w->insertOne($answer);
247            }
248            $w->insertOne([]);
249        }
250
251        /*
252            We create a callable to output the CSV in chunk
253            with Symfony StreamResponse you can flush the body content if necessary
254            see Symfony documentation for more information
255        */
256
257        // * The flush value should depend on your CSV size.
258        $flush_threshold = 1000;
259
260        $response->setCallback(function () use ($w, $flush_threshold) {
261            foreach ($w->chunk(1024) as $offset => $chunk) {
262                echo $chunk;
263                if ($offset % $flush_threshold === 0) {
264                    flush();
265                }
266            }
267        });
268        return $response;
269    }
270}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/ServiceController.php.html b/tests/coverage/Http/Controllers/ServiceController.php.html deleted file mode 100644 index 13dbed6..0000000 --- a/tests/coverage/Http/Controllers/ServiceController.php.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/ServiceController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ServiceController
n/a
0 / 0
n/a
0 / 0
14
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
3
 create
n/a
0 / 0
n/a
0 / 0
1
 store
n/a
0 / 0
n/a
0 / 0
2
 edit
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
2
 show
n/a
0 / 0
n/a
0 / 0
1
 destroy
n/a
0 / 0
n/a
0 / 0
2
 generateServicePassword
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Http\Requests\GenerateServiceToken;
6use App\Http\Requests\Services\StoreServiceRequest;
7use App\Http\Requests\Services\UpdateServiceRequest;
8use App\Mail\ServiceCredentialRenewed;
9use App\Models\Brand;
10use App\Models\Service;
11use Illuminate\Http\Request;
12use Illuminate\Support\Facades\DB;
13use Illuminate\Support\Facades\Hash;
14use Illuminate\Support\Facades\Mail;
15use Illuminate\Support\Str;
16
17/**
18 * @codeCoverageIgnore
19 */
20class ServiceController extends Controller
21{
22    /**
23     * Display a listing of the resource.
24     *
25     * @param \Illuminate\Http\Request $request
26     * @return \Illuminate\Http\Response
27     */
28    public function index(Request $request)
29    {
30        /** @var \Illuminate\Database\Eloquent\Builder $query */
31        $query = Service::query();
32
33        if ($request->brand) {
34            $query = $query->where('brand_id', '=', request()->brand);
35        }
36        if ($request->search) {
37            $this->searchQuery(
38                $query,
39                $request->search,
40                null,
41                'name',
42            );
43        }
44
45        $this->sortQuery($query);
46        $searchFields = trans('Nom');
47
48        $serviceModels = $query->paginate(12);
49
50        return view('services.index', compact('serviceModels', 'searchFields'));
51    }
52
53    /**
54     * Show the form for creating a new resource.
55     *
56     * @param \App\Models\Service $service
57     * @return \Illuminate\Http\Response
58     */
59    public function create(Service $service)
60    {
61        $brandModels = Brand::get();
62
63        return view('services.create', [
64            'serviceModel' => $service,
65            'brandModels' => $brandModels
66        ]);
67    }
68
69    /**
70     * Store a newly created resource in storage.
71     *
72     * @param \App\Http\Requests\Services\StoreServiceRequest $request
73     * @return \Illuminate\Http\Response
74     */
75    public function store(StoreServiceRequest $request)
76    {
77        return DB::transaction(function () use ($request) {
78            $service = new Service();
79            $service->fill($request->validated());
80            $service->generateBearerToken();
81            $this->generateServicePassword($service);
82            if ($service->saveOrFail()) {
83                return redirect()
84                    ->route('services.edit', $service)
85                    ->with('success', trans(
86                        ':model a Ã©té créé.',
87                        ['model' => Str::singular(trans('models.classes.services'))]
88                    ));
89            }
90            return redirect()
91                ->back()
92                ->with('error', trans(
93                    ':model n\'a pas Ã©té créé.',
94                    ['model' => Str::singular(trans('models.classes.services'))]
95                ));
96        });
97    }
98
99    /**
100     * Show the form for editing the specified resource.
101     *
102     * @param \App\Models\Service $service
103     * @return \Illuminate\Http\Response
104     */
105    public function edit(Service $service)
106    {
107        $brandModels = Brand::get();
108
109        return view('services.edit', [
110            'serviceModel' => $service,
111            'brandModels' => $brandModels
112        ]);
113    }
114
115    /**
116     * Update a spacific resource in storage.
117     *
118     * @param \App\Http\Requests\Services\UpdateServiceRequest $request
119     * @param \App\Models\Service                              $service
120     * @return \Illuminate\Http\Response
121     */
122    public function update(UpdateServiceRequest $request, Service $service)
123    {
124        return DB::transaction(function () use ($request, $service) {
125            $service->fill($request->validated());
126            if ($service->saveOrFail()) {
127                return redirect()
128                    ->route('services.edit', $service)
129                    ->with('success', trans(
130                        ':model a Ã©té mis Ã  jour.',
131                        ['model' => Str::singular(trans('models.classes.services'))]
132                    ));
133            }
134            return redirect()
135                ->back()
136                ->with('error', trans(
137                    ':model n\'a pas Ã©té mis Ã  jour.',
138                    ['model' => Str::singular(trans('models.classes.services'))]
139                ));
140        });
141    }
142
143    /**
144     * Show the specified resource.
145     *
146     * @param \App\Models\Service $service
147     * @return \Illuminate\Http\Response
148     */
149    public function show(Service $service)
150    {
151        return view('services.show', [
152            'serviceModel' => $service
153        ]);
154    }
155
156    /**
157     * Remove the specified resource from storage.
158     *
159     * @param \App\Models\Service $service
160     * @return \Illuminate\Http\RedirectResponse
161     */
162    public function destroy(Service $service): \Illuminate\Http\RedirectResponse
163    {
164        if ($service->deleteOrFail()) {
165            return redirect()->route('services.index')
166                ->with('success', trans(
167                    ':model a Ã©té supprimé.',
168                    ['model' => Str::singular(trans('models.classes.services'))]
169                ));
170        }
171        return redirect()->back()
172            ->with('error', trans(
173                ':model n\'a pas pu Ãªtre supprimé.',
174                ['model' => Str::singular(trans('models.classes.services'))]
175            ));
176    }
177
178    /**
179     * Regen service credentials
180     *
181     * @param \App\Http\Requests\GenerateServiceToken $request
182     * @param \App\Models\Service                     $service
183     * @return \Illuminate\Http\RedirectResponse
184     */
185    // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed, Squiz.Commenting.FunctionComment.Missing
186    public function generateServicePassword(Service $service, GenerateServiceToken $request = null)
187    {
188        /** @var \App\Models\User $user */
189        $user = auth()->user();
190
191        // * Special Condition for migration from v2 to v3.
192        $service->bearer_token = !Hash::needsRehash($service->bearer_token) ?
193            $service->generateBearerToken() : $service->bearer_token;
194
195        $bearerPwd = $service->generateBearerPassword();
196        $service->saveOrFail();
197        Mail::send(new ServiceCredentialRenewed($service, $user));
198        return back()
199            // phpcs:ignore Generic.Files.LineLength.TooLong
200            ->with('error', "ATTENTION : METTEZ Ã€ JOUR LE SITE ! \nNouveau token d'API de $service->name : '$service->bearer_token' et mot de passe '$bearerPwd'");
201    }
202}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/SingleOptinController.php.html b/tests/coverage/Http/Controllers/SingleOptinController.php.html deleted file mode 100644 index cd4ba43..0000000 --- a/tests/coverage/Http/Controllers/SingleOptinController.php.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/SingleOptinController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
SingleOptinController
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 index
n/a
0 / 0
n/a
0 / 0
3
 destroy
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Models\SingleOptin;
6use Illuminate\Database\Eloquent\Builder;
7use Illuminate\Http\Request;
8use Illuminate\Support\Str;
9
10/**
11 * @codeCoverageIgnore
12 */
13class SingleOptinController extends Controller
14{
15    /**
16     * Display a listing of the resource.
17     *
18     * @param \Illuminate\Http\Request $request
19     * @return \Illuminate\Http\Response
20     */
21    public function index(Request $request)
22    {
23        /** @var \Illuminate\Database\Eloquent\Builder $query */
24        $query = SingleOptin::query();
25
26        if ($request->brand) {
27            $query = $query->whereHas('brand', function (Builder $q) {
28                $q->where('id', request()->brand);
29            });
30        }
31        if ($request->search) {
32            $this->searchQuery(
33                $query,
34                $request->search,
35                null,
36                'email',
37            );
38        }
39
40        $this->sortQuery($query);
41        $searchFields = trans('E-mail');
42
43        /** @var \Illuminate\Pagination\AbstractPaginator */
44        $singleoptinModels = $query->paginate(12);
45
46        return view('singleoptins.index', compact('singleoptinModels', 'searchFields'));
47    }
48
49    /**
50     * Remove the specified resource from storage.
51     *
52     * @param \App\Models\SingleOptin $singleoptin
53     * @return \Illuminate\Http\RedirectResponse
54     */
55    public function destroy(SingleOptin $singleoptin): \Illuminate\Http\RedirectResponse
56    {
57        if ($singleoptin->deleteOrFail()) {
58            return redirect()->route('single_optins.index')
59                ->with('success', trans(
60                    ':model a Ã©té supprimé.',
61                    ['model' => Str::singular(trans('models.classes.singleoptins'))]
62                ));
63        }
64        return redirect()->back()
65            ->with('error', trans(
66                ':model n\'a pas pu Ãªtre supprimé.',
67                ['model' => Str::singular(trans('models.classes.singleoptins'))]
68            ));
69    }
70}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/StatsController.php.html b/tests/coverage/Http/Controllers/StatsController.php.html deleted file mode 100644 index f7b6753..0000000 --- a/tests/coverage/Http/Controllers/StatsController.php.html +++ /dev/null @@ -1,361 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers/StatsController.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
StatsController
n/a
0 / 0
n/a
0 / 0
12
n/a
0 / 0
 viewStatsByBrand
n/a
0 / 0
n/a
0 / 0
2
 exportByBrandCsv
n/a
0 / 0
n/a
0 / 0
3
 viewByQuestion
n/a
0 / 0
n/a
0 / 0
2
 exportByQuestionCsv
n/a
0 / 0
n/a
0 / 0
2
 answersByDays
n/a
0 / 0
n/a
0 / 0
3
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Controllers;
4
5use App\Http\Requests\StatsByBrandRequest;
6use App\Http\Requests\StatsByQuestionRequest;
7use App\Models\Brand;
8use App\Models\BrandStat;
9use App\Models\CustomerAnswer;
10use App\Models\Question;
11use Illuminate\Support\Carbon;
12use Illuminate\Support\Collection;
13use Illuminate\Support\Facades\Schema;
14use League\Csv\Writer;
15
16/**
17 * @codeCoverageIgnore
18 */
19class StatsController extends Controller
20{
21    /**
22     * Shows stats by brand
23     *
24     * @param \App\Http\Requests\StatsByBrandRequest $request
25     * @return \Illuminate\Http\Response
26     */
27    public function viewStatsByBrand(StatsByBrandRequest $request)
28    {
29        $stats = null;
30        if ($request->post()) {
31            $stats = BrandStat::whereDate('created_at', '>=', $request->start)
32                ->whereDate('created_at', '<=', $request->end)
33                ->whereBrandId($request->brand)->get();
34        }
35        return view('stats.view-stats-by-brands', \compact('stats'));
36    }
37
38    /**
39     * Exports stats by brand as a csv file
40     *
41     * @param \App\Http\Requests\StatsByBrandRequest $request
42     * @return \Illuminate\Http\Response
43     */
44    public function exportByBrandCsv(StatsByBrandRequest $request)
45    {
46        $brand = Brand::findOrFail($request->brand);
47        $stats = BrandStat::with('brand')->whereDate('created_at', '>=', $request->start)
48            ->whereDate('created_at', '<=', $request->end)
49            ->whereBrandId($request->brand)->get();
50
51        $columns = collect(Schema::getColumnListing((new BrandStat())->getTable()))
52            ->filter(function ($value) {
53                return !in_array($value, ['created_at', 'brand_id']);
54            })->all();
55
56        $header = '"date";"marque";' . implode(';', $columns);
57
58        $w = Writer::createFromString($header . PHP_EOL);
59        $w->setDelimiter(';');
60        $w->setEnclosure('"');
61        $w->setOutputBOM(Writer::BOM_UTF8);
62
63        foreach ($stats as $stat) {
64            $cols = [
65                $stat->created_at->format('Y-m-d'),
66                $stat->brand->name
67            ];
68            foreach ($columns as $colname) {
69                $cols[] = $stat->{$colname};
70            }
71            $w->insertOne($cols);
72        }
73        return response((string)$w, 200, [
74            'Content-Encoding' => 'none',
75            'Content-Type' => 'text/csv; charset=UTF-8',
76            // phpcs:ignore Generic.Files.LineLength.TooLong
77            'Content-Disposition' => "attachment; filename=\"Statistiques du $request->start au $request->end pour $brand->name.csv\"",
78            'Content-Description' => 'File Transfer',
79        ]);
80    }
81
82    /**
83     * Views stats for a specific question
84     *
85     * @param \App\Http\Requests\StatsByQuestionRequest $request
86     * @return \Illuminate\Http\Response
87     */
88    public function viewByQuestion(StatsByQuestionRequest $request)
89    {
90        $question      = null;
91        $answers       = null;
92        $answersByDays = null;
93        if ($request->post()) {
94            $question = Question::findOrFail($request->question);
95            $answers  = $question->answers;
96            $start    = Carbon::parse($request->start);
97            $end      = Carbon::parse($request->end);
98
99            $answersByDays = $this->answersByDays($answers, $start, $end);
100        }
101
102        return view('stats.view-stats-by-questions', \compact('question', 'answers', 'answersByDays'));
103    }
104
105    /**
106     * Exports stats for a specific question as csv
107     *
108     * @param \App\Http\Requests\StatsByQuestionRequest $request
109     * @return \Illuminate\Http\Response
110     */
111    public function exportByQuestionCsv(StatsByQuestionRequest $request)
112    {
113        $question = Question::findOrFail($request->question);
114        $answers  = $question->answers;
115        $start    = Carbon::parse($request->start);
116        $end      = Carbon::parse($request->end);
117
118        $answersByDays = $this->answersByDays($answers, $start, $end);
119
120        $header = '"date";' . implode(';', $answers->map(function ($answer) {
121            return "\"$answer->answer\"";
122        })->all());
123
124        $w = Writer::createFromString($header . PHP_EOL);
125        $w->setDelimiter(';');
126        $w->setEnclosure('"');
127        $w->setOutputBOM(Writer::BOM_UTF8);
128
129        foreach ($answersByDays as $day => $dayAnswers) {
130            $w->insertOne(array_merge(
131                [
132                    $day
133                ],
134                $dayAnswers->values()->all()
135            ));
136        }
137        return response((string)$w, 200, [
138            'Content-Encoding' => 'none',
139            'Content-Type' => 'text/csv; charset=UTF-8',
140            // phpcs:ignore Generic.Files.LineLength.TooLong
141            'Content-Disposition' => "attachment; filename=\"Statistiques du $request->start au $request->end pour $question->question.csv\"",
142            'Content-Description' => 'File Transfer',
143        ]);
144    }
145
146    /**
147     * Makes a collection of total users answers by day
148     *
149     * @param \Illuminate\Support\Collection $answers A collection of Answer.
150     * @param \Illuminate\Support\Carbon     $start   The wanted start period.
151     * @param \Illuminate\Support\Carbon     $end     The wanted end period.
152     * @return \Illuminate\Support\Collection A collection with date as Y_m_d index
153     *                                        with array of answers and their total.
154     */
155    private function answersByDays(Collection $answers, Carbon $start, Carbon $end): Collection
156    {
157        // * Totalize by date all answers.
158        $answersByDays = CustomerAnswer::whereDate('created_at', '>=', $start)
159            ->whereDate('created_at', '<=', $end)
160            ->whereIn('answer_id', $answers->pluck('id'))
161            ->get()->map(function ($custAnswer) {
162                return [
163                    'answer_id' => $custAnswer->answer_id,
164                    'created_at' => $custAnswer->created_at->format('Y-m-d'),
165                ];
166            })->groupBy(['created_at', 'answer_id'])->map(function ($day) {
167                return $day->map(function ($custAnswers) {
168                    return $custAnswers->count();
169                });
170            });
171
172        do {
173            $currentDate = $start->format('Y-m-d');
174            /** @var \Illuminate\Support\Collection $answersByDays */
175            if (!$answersByDays->has($currentDate)) {
176                $answersByDays->put(
177                    $currentDate,
178                    $answers->mapWithKeys(function ($answer) {
179                        return [$answer->answer => 0];
180                    })
181                );
182            } else {
183                $curentAnswers = $answersByDays->pull($currentDate);
184                $answersByDays->put(
185                    $currentDate,
186                    $answers->mapWithKeys(function ($answer) use ($curentAnswers) {
187                        return [$answer->answer =>
188                        $curentAnswers->has($answer->id) ?
189                            $curentAnswers->get($answer->id) : 0];
190                    })
191                );
192            }
193
194            $start = $start->addDay();
195        } while ($start->lessThanOrEqualTo($end));
196
197        return $answersByDays;
198    }
199}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Controllers/dashboard.html b/tests/coverage/Http/Controllers/dashboard.html deleted file mode 100644 index b2c8178..0000000 --- a/tests/coverage/Http/Controllers/dashboard.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Controllers - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Controllers/index.html b/tests/coverage/Http/Controllers/index.html deleted file mode 100644 index 8aa1571..0000000 --- a/tests/coverage/Http/Controllers/index.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Controllers - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
171 / 171
-
- 100.00% covered (success) -
-
-
100.00%
21 / 21
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
Api
-
- 100.00% covered (success) -
-
-
100.00%
171 / 171
-
- 100.00% covered (success) -
-
-
100.00%
21 / 21
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
AnswerController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
BrandController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
Controller.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
CustomerController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
LoginController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
QuestionController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
ServiceController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
SingleOptinController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StatsController.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Http/Kernel.php.html b/tests/coverage/Http/Kernel.php.html deleted file mode 100644 index 4b3ab0c..0000000 --- a/tests/coverage/Http/Kernel.php.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Kernel.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
Kernel
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http;
4
5use Illuminate\Foundation\Http\Kernel as HttpKernel;
6
7class Kernel extends HttpKernel
8{
9    /**
10     * The application's global HTTP middleware stack.
11     *
12     * These middleware are run during every request to your application.
13     *
14     * @var array
15     */
16    protected $middleware = [
17        \App\Http\Middleware\TrustProxies::class,
18        \Fruitcake\Cors\HandleCors::class,
19        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
20        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
21        \App\Http\Middleware\TrimStrings::class,
22        \App\Http\Middleware\XssSanitizer::class
23    ];
24
25    /**
26     * The application's route middleware groups.
27     *
28     * @var array
29     */
30    protected $middlewareGroups = [
31        'web' => [
32            \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
33            \App\Http\Middleware\EncryptCookies::class,
34            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
35            \Illuminate\Session\Middleware\StartSession::class,
36            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
37            \App\Http\Middleware\VerifyCsrfToken::class,
38            \Illuminate\Routing\Middleware\SubstituteBindings::class,
39            \App\Http\Middleware\SecureHeaders::class
40        ],
41
42        'api' => [
43            'throttle:api',
44            \App\Http\Middleware\JsonOnly::class,
45            \App\Http\Middleware\ApiResponse::class,
46            \App\Http\Middleware\ApiCheckTimeStamps::class,
47            \Illuminate\Routing\Middleware\SubstituteBindings::class
48        ],
49    ];
50
51    /**
52     * The application's route middleware.
53     *
54     * These middleware may be assigned to groups or used individually.
55     *
56     * @var array
57     */
58    protected $routeMiddleware = [
59        'auth' => \App\Http\Middleware\Authenticate::class,
60        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
61        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
62        'can' => \Illuminate\Auth\Middleware\Authorize::class,
63        'check-brand' => \App\Http\Middleware\CheckBrand::class,
64        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
65        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
66        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
67        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
68        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
69    ];
70}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/ApiCheckTimeStamps.php.html b/tests/coverage/Http/Middleware/ApiCheckTimeStamps.php.html deleted file mode 100644 index 874270d..0000000 --- a/tests/coverage/Http/Middleware/ApiCheckTimeStamps.php.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/ApiCheckTimeStamps.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ApiCheckTimeStamps
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 handle
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use App\Enums\ApiError;
6use App\Exceptions\Api\V2\ApiErrorException;
7use App\Lib\Helper;
8use Illuminate\Http\Request;
9use Illuminate\Support\Carbon;
10
11/**
12 * Check Http Header Timestamp with request timestamp
13 * to prevent replay attacks
14 * https://restfulapi.net/security-essentials/
15 */
16class ApiCheckTimeStamps
17{
18    /**
19     * Handle an incoming request.
20     *
21     * @param \Illuminate\Http\Request $request
22     * @param \Closure                 $next
23     * @return mixed
24     * @throws \App\Exceptions\Api\V2\ApiErrorException With ApiError::wrongTimeStamp
25     *                                                  if timestamp has an incorrect
26     *                                                  format or is timed out.
27     */
28    public function handle(Request $request, \Closure $next)
29    {
30        if (Helper::isRequestForApiV2($request)) {
31            if (!\is_string($request->timestamp) or !Carbon::hasFormat($request->timestamp, 'Y-m-d H:i:s')) {
32                throw new ApiErrorException(ApiError::wrongTimeStamp());
33            }
34            if (
35                Carbon::parse($request->timestamp, 'utc')
36                ->isBefore(Carbon::now('UTC')->subSeconds(config('api.timestampTimeout', 10)))
37            ) {
38                throw new ApiErrorException(ApiError::wrongTimeStamp());
39            }
40        }
41        return $next($request);
42    }
43}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/ApiResponse.php.html b/tests/coverage/Http/Middleware/ApiResponse.php.html deleted file mode 100644 index 410e816..0000000 --- a/tests/coverage/Http/Middleware/ApiResponse.php.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/ApiResponse.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
14 / 14
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ApiResponse
-
- 100.00% covered (success) -
-
-
100.00%
14 / 14
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
10
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 handle
-
- 100.00% covered (success) -
-
-
100.00%
14 / 14
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
10
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use App\Enums\ApiStatus;
6use App\Http\Resources\Api\V2\ApiResponse as V2ApiResponse;
7use App\Lib\Helper;
8use App\Models\Service;
9use Illuminate\Database\Eloquent\ModelNotFoundException;
10use Illuminate\Http\JsonResponse;
11use Illuminate\Http\Request;
12use Illuminate\Support\Facades\Log;
13
14class ApiResponse
15{
16    /**
17     * Handle an incoming request.
18     *
19     * @param  \Illuminate\Http\Request $request
20     * @param  \Closure                 $next
21     * @return mixed
22     * @throws \ErrorException If the received response is not a JsonResponse.
23     */
24    public function handle(Request $request, \Closure $next)
25    {
26        /** @var \Illuminate\Http\JsonResponse $response */
27        $response = $next($request);
28
29        if (\get_class($response) !== JsonResponse::class) {
30            // @codeCoverageIgnoreStart
31            throw new \ErrorException('All api responses should be json.');
32            // @codeCoverageIgnoreEnd
33        }
34
35        // Set no cache header.
36        $response->header('Cache-Control', 'No-Store');
37
38        if (Helper::isRequestForApiV1($request)) {
39            // @codeCoverageIgnoreStart
40            try {
41                $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token ?? '';
42                $service     = Service::getFromBearerToken($bearerToken);
43                $service->incrementStat();
44            } catch (ModelNotFoundException $e) {
45                Log::error(sprintf('Could not increment stats %s', $e->getMessage()));
46            }
47            return $response;
48            // @codeCoverageIgnoreEnd
49        }
50
51        /** @var \App\Models\Service|null */
52        if (\optional($service = app(Service::class))->id) {
53            $service->incrementStat();
54        }
55
56        /** @var object $data */
57        $data = $response->getData();
58
59        // If reponse is already a V2ApiResponse then just return it. This happens if error handler comes to action.
60        if (
61            isset($data->status) and
62            isset($data->data) and
63            isset($data->version) and
64            in_array($data->status, [ApiStatus::OK()->value, ApiStatus::KO()->value])
65        ) {
66            return $response;
67        }
68
69        /** @var \App\Enums\ApiStatus $status */
70        $status = intval($response->getStatusCode() / 100) == 2 ? ApiStatus::OK() : ApiStatus::KO();
71        return $response->setData(new V2ApiResponse((object)$response->getData(), $status));
72    }
73}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/Authenticate.php.html b/tests/coverage/Http/Middleware/Authenticate.php.html deleted file mode 100644 index 72e261a..0000000 --- a/tests/coverage/Http/Middleware/Authenticate.php.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/Authenticate.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Authenticate
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 handle
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
 redirectTo
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use App\Lib\Helper;
6use App\Models\Service;
7use Closure;
8use Illuminate\Auth\Middleware\Authenticate as Middleware;
9
10/**
11 * Updated for ApiV2
12 */
13class Authenticate extends Middleware
14{
15    /**
16     * Handle an incoming request.
17     *
18     * @param mixed    $request
19     * @param \Closure $next
20     * @param mixed    ...$guards
21     * @return mixed
22     *
23     * @throws \Illuminate\Auth\AuthenticationException If authentification fails.
24     */
25    public function handle($request, Closure $next, ...$guards)
26    {
27        if (Helper::isRequestForApiV1($request)) {
28            // @codeCoverageIgnoreStart
29            // Use patched v1 Auth mecanism (without bearer reimplementation).
30            $bearerToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token ?? '';
31            $service     = Service::getFromBearerToken($bearerToken);
32            $request->request->add(['brand_id' => $service->brand->id]);
33            $response = $next($request);
34            // @codeCoverageIgnoreEnd
35        } else {
36            // Use Laravel auth check mecanism.
37            $response = \call_user_func_array(
38                [$this, 'parent::' . __FUNCTION__],
39                array_merge([$request, $next], $guards)
40            );
41        }
42
43        return $response;
44    }
45    /**
46     * Get the path the user should be redirected to when they are not authenticated.
47     *
48     * @param  mixed $request
49     * @return string|null
50     * @codeCoverageIgnore
51     */
52    protected function redirectTo($request)
53    {
54        if (! $request->expectsJson()) {
55            return route('login');
56        }
57    }
58}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/CheckBrand.php.html b/tests/coverage/Http/Middleware/CheckBrand.php.html deleted file mode 100644 index d50d7b2..0000000 --- a/tests/coverage/Http/Middleware/CheckBrand.php.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/CheckBrand.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CheckBrand
n/a
0 / 0
n/a
0 / 0
3
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
3
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use App\Lib\Helper;
6use App\Models\Service;
7use Closure;
8use Illuminate\Database\Eloquent\ModelNotFoundException;
9use Illuminate\Http\Request;
10
11/**
12 * @codeCoverageIgnore
13 */
14class CheckBrand
15{
16    /**
17     * Handle an incoming request.
18     *
19     * @param \Illuminate\Http\Request $request
20     * @param \Closure                 $next
21     * @return mixed
22     */
23    public function handle(Request $request, Closure $next)
24    {
25        $apiToken = Helper::getBearerTokenFromRequest($request) ?? $request->api_token ?? '';
26
27        if ($apiToken === null) {
28            abort(401, 'Le token d\'authentification est incorrect ' . \json_encode($request->all()));
29        }
30
31        try {
32            $brand = Service::getFromBearerToken($apiToken)->brand;
33            $request->request->add(['brand_id' => $brand->id]);
34        } catch (ModelNotFoundException $e) {
35            abort(401, 'Le token d\'authentification est incorrect ' . $e->getMessage());
36        }
37
38        return $next($request);
39    }
40}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/EncryptCookies.php.html b/tests/coverage/Http/Middleware/EncryptCookies.php.html deleted file mode 100644 index a586486..0000000 --- a/tests/coverage/Http/Middleware/EncryptCookies.php.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/EncryptCookies.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
EncryptCookies
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
6
7class EncryptCookies extends Middleware
8{
9    /**
10     * The names of the cookies that should not be encrypted.
11     *
12     * @var array
13     */
14    protected $except = [];
15}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/JsonOnly.php.html b/tests/coverage/Http/Middleware/JsonOnly.php.html deleted file mode 100644 index 9e5bcc4..0000000 --- a/tests/coverage/Http/Middleware/JsonOnly.php.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/JsonOnly.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
JsonOnly
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 handle
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use App\Lib\Helper;
6use Illuminate\Http\Request;
7
8class JsonOnly
9{
10    /**
11     * Handle an incoming request.
12     *
13     * @param  \Illuminate\Http\Request $request
14     * @param  \Closure                 $next
15     * @return mixed
16     */
17    public function handle(Request $request, \Closure $next)
18    {
19        // TODO remove condition v1 check when dropped.
20        if (
21            !Helper::isRequestForApiV1($request) and
22            (!$request->isJson() or
23                !$request->expectsJson() or
24                is_null(json_decode($request->getContent())))
25        ) {
26            abort(400, 'Invalid JSON');
27        }
28
29        return $next($request);
30    }
31}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/PreventRequestsDuringMaintenance.php.html b/tests/coverage/Http/Middleware/PreventRequestsDuringMaintenance.php.html deleted file mode 100644 index b4aa0ef..0000000 --- a/tests/coverage/Http/Middleware/PreventRequestsDuringMaintenance.php.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/PreventRequestsDuringMaintenance.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
PreventRequestsDuringMaintenance
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
6
7class PreventRequestsDuringMaintenance extends Middleware
8{
9    /**
10     * The URIs that should be reachable while maintenance mode is enabled.
11     *
12     * @var array
13     */
14    protected $except = [];
15}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/RedirectIfAuthenticated.php.html b/tests/coverage/Http/Middleware/RedirectIfAuthenticated.php.html deleted file mode 100644 index 129fd4d..0000000 --- a/tests/coverage/Http/Middleware/RedirectIfAuthenticated.php.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/RedirectIfAuthenticated.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
RedirectIfAuthenticated
n/a
0 / 0
n/a
0 / 0
4
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
4
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use App\Providers\RouteServiceProvider;
6use Closure;
7use Illuminate\Http\Request;
8use Illuminate\Support\Facades\Auth;
9
10/**
11 * @codeCoverageIgnore
12 */
13class RedirectIfAuthenticated
14{
15    /**
16     * Handle an incoming request.
17     *
18     * @param  \Illuminate\Http\Request $request
19     * @param  \Closure                 $next
20     * @param  string|null              ...$guards
21     * @return mixed
22     */
23    public function handle(Request $request, Closure $next, ...$guards)
24    {
25        $guards = empty($guards) ? [null] : $guards;
26
27        foreach ($guards as $guard) {
28            if (Auth::guard($guard)->check()) {
29                return redirect(RouteServiceProvider::HOME);
30            }
31        }
32
33        return $next($request);
34    }
35}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/TrimStrings.php.html b/tests/coverage/Http/Middleware/TrimStrings.php.html deleted file mode 100644 index 9e9173b..0000000 --- a/tests/coverage/Http/Middleware/TrimStrings.php.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/TrimStrings.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
TrimStrings
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
6
7class TrimStrings extends Middleware
8{
9    /**
10     * The names of the attributes that should not be trimmed.
11     *
12     * @var array
13     */
14    protected $except = [
15        'password',
16        'password_confirmation',
17    ];
18}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/TrustProxies.php.html b/tests/coverage/Http/Middleware/TrustProxies.php.html deleted file mode 100644 index f7a9e17..0000000 --- a/tests/coverage/Http/Middleware/TrustProxies.php.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/TrustProxies.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
TrustProxies
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use Fideloper\Proxy\TrustProxies as Middleware;
6use Illuminate\Http\Request;
7
8class TrustProxies extends Middleware
9{
10    /**
11     * The trusted proxies for this application.
12     *
13     * @var array|string|null
14     */
15    protected $proxies;
16
17    /**
18     * The headers that should be used to detect proxies.
19     *
20     * @var integer
21     */
22    protected $headers = Request::HEADER_X_FORWARDED_FOR |
23    Request::HEADER_X_FORWARDED_HOST |
24    Request::HEADER_X_FORWARDED_PORT |
25    Request::HEADER_X_FORWARDED_PROTO |
26    Request::HEADER_X_FORWARDED_AWS_ELB;
27}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/VerifyCsrfToken.php.html b/tests/coverage/Http/Middleware/VerifyCsrfToken.php.html deleted file mode 100644 index cbff728..0000000 --- a/tests/coverage/Http/Middleware/VerifyCsrfToken.php.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/VerifyCsrfToken.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
VerifyCsrfToken
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
6
7class VerifyCsrfToken extends Middleware
8{
9    /**
10     * The URIs that should be excluded from CSRF verification.
11     *
12     * @var array
13     */
14    protected $except = [];
15}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/XssSanitizer.php.html b/tests/coverage/Http/Middleware/XssSanitizer.php.html deleted file mode 100644 index f381af2..0000000 --- a/tests/coverage/Http/Middleware/XssSanitizer.php.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware/XssSanitizer.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
XssSanitizer
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 handle
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Middleware;
4
5use Closure;
6
7class XssSanitizer
8{
9    /**
10     * Handle an incoming request.
11     *
12     * @param  mixed    $request
13     * @param  \Closure $next
14     * @return mixed
15     */
16    public function handle($request, Closure $next)
17    {
18        $input = $request->all();
19        array_walk_recursive($input, function (&$input) {
20            $input = strip_tags($input);
21        });
22        $request->merge($input);
23        return $next($request);
24    }
25}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Middleware/dashboard.html b/tests/coverage/Http/Middleware/dashboard.html deleted file mode 100644 index da92594..0000000 --- a/tests/coverage/Http/Middleware/dashboard.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Middleware - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - -
ClassCoverage
App\Http\Middleware\SecureHeaders0%
-
-
-
-

Project Risks

-
- - - - - - - - - - - -
ClassCRAP
App\Http\Middleware\SecureHeaders30
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - -
MethodCoverage
handle0%
removeUnwantedHeaders0%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - -
MethodCRAP
handle12
removeUnwantedHeaders6
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Middleware/index.html b/tests/coverage/Http/Middleware/index.html deleted file mode 100644 index 6f34374..0000000 --- a/tests/coverage/Http/Middleware/index.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Middleware - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 66.67% covered (warning) -
-
-
66.67%
36 / 54
-
- 71.43% covered (warning) -
-
-
71.43%
5 / 7
-
- 83.33% covered (warning) -
-
-
83.33%
5 / 6
ApiCheckTimeStamps.php
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ApiResponse.php
-
- 100.00% covered (success) -
-
-
100.00%
14 / 14
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Authenticate.php
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CheckBrand.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
EncryptCookies.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
JsonOnly.php
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
PreventRequestsDuringMaintenance.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
RedirectIfAuthenticated.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
SecureHeaders.php
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 18
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
TrimStrings.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
TrustHosts.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
TrustProxies.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
VerifyCsrfToken.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
XssSanitizer.php
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- -
- - diff --git a/tests/coverage/Http/Requests/Api/V2/GetQuestionStatsRequest.php.html b/tests/coverage/Http/Requests/Api/V2/GetQuestionStatsRequest.php.html deleted file mode 100644 index 5a5d186..0000000 --- a/tests/coverage/Http/Requests/Api/V2/GetQuestionStatsRequest.php.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/Api/V2/GetQuestionStatsRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 85.71% covered (warning) -
-
-
85.71%
6 / 7
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
GetQuestionStatsRequest
-
- 85.71% covered (warning) -
-
-
85.71%
6 / 7
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
4.05
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 rules
-
- 85.71% covered (warning) -
-
-
85.71%
6 / 7
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
4.05
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests\Api\V2;
4
5use App\Http\Requests\Api\V2\BaseApiRequest;
6use Exception;
7use Illuminate\Support\Carbon;
8use Illuminate\Validation\ValidationException;
9
10class GetQuestionStatsRequest extends BaseApiRequest
11{
12    /**
13     * Get the validation rules that apply to the request.
14     *
15     * @return array
16     * @throws \Illuminate\Validation\ValidationException If given dates are unparsable of
17     *                                                    period is bigger than 3 months.
18     * @throws \Exception If carbon date processing failed.
19     */
20    public function rules(): array
21    {
22        try {
23            if (Carbon::parse($this->end)->floatDiffInMonths(Carbon::parse($this->start)) > 3) {
24                throw ValidationException::withMessages([
25                    'Vous devez sélectionner une période de maximum 3 mois'
26                ]);
27            }
28        } catch (Exception $e) {
29            if (\get_class($e) !== ValidationException::class) {
30                throw ValidationException::withMessages([
31                    'Les dates sélectionnées ne sembles pas Ãªtre valides'
32                ]);
33            }
34            throw $e;
35        }
36        return [
37            'start' => 'required|date|date_format:Y-m-d',
38            'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
39        ];
40    }
41}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/Api/V2/StoreCustomerRequest.php.html b/tests/coverage/Http/Requests/Api/V2/StoreCustomerRequest.php.html deleted file mode 100644 index 04bb1b0..0000000 --- a/tests/coverage/Http/Requests/Api/V2/StoreCustomerRequest.php.html +++ /dev/null @@ -1,295 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/Api/V2/StoreCustomerRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 92.00% covered (success) -
-
-
92.00%
23 / 25
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
StoreCustomerRequest
-
- 92.00% covered (success) -
-
-
92.00%
23 / 25
-
- 66.67% covered (warning) -
-
-
66.67%
2 / 3
7.03
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 rules
-
- 100.00% covered (success) -
-
-
100.00%
16 / 16
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
 prepareForValidation
-
- 60.00% covered (warning) -
-
-
60.00%
3 / 5
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
3.58
 messages
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests\Api\V2;
4
5use App\Enums\Country;
6use Illuminate\Validation\Rules\Password;
7use Kwaadpepper\Enum\Rules\EnumIsValidRule;
8
9class StoreCustomerRequest extends BaseApiRequest
10{
11    /**
12     * Current country
13     *
14     * @var \App\Enums\Country|null
15     */
16    protected $currentCountry = null;
17
18    /**
19     * Get the validation rules that apply to the request.
20     *
21     * @return array
22     */
23    public function rules(): array
24    {
25        return [
26            'firstname' => 'required|string|min:1|max:255',
27            'lastname' => 'required|string|min:1|max:255',
28            'email' => 'required|email:rfc,dns,spoof,filter|max:255',
29            'password' => [
30                'required',
31                'max:255',
32                Password::min(8)
33                    ->mixedCase()
34                    ->letters()
35                    ->numbers()
36                    ->symbols()
37                    ->uncompromised()
38            ],
39            'birth_date' => 'required|date_format:Y-m-d|max:255',
40            'address' => 'sometimes|required_with:address2|string|max:255',
41            'address2' => 'sometimes|string|max:255',
42            'phone' => ['sometimes', 'string',  'phone:' . ($this->currentCountry ?
43                $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes()))],
44            'country' => [
45                'sometimes',
46                'numeric',
47                new EnumIsValidRule(Country::class),
48                sprintf(
49                    'in:%s',
50                    implode(',', Country::getValidCountries())
51                )
52            ],
53            'city' => 'sometimes|string|min:2|max:255',
54            'postcode' => ['sometimes', 'string',  'postal_code:' . ($this->currentCountry ?
55                $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes()))],
56            'opt_in' => 'required|boolean',
57        ];
58    }
59
60    /**
61     * Prepare the data for validation.
62     *
63     * @return void
64     */
65    protected function prepareForValidation(): void
66    {
67        try {
68            if (is_array($this->country)) {
69                $this->currentCountry = Country::make(\intval($this->country));
70            }
71        } catch (\TypeError | \BadMethodCallException $e) {
72            $this->currentCountry = null;
73        }
74        parent::prepareForValidation();
75    }
76
77    /**
78     * Messages
79     *
80     * @return array
81     */
82    public function messages()
83    {
84        return [
85            // phpcs:ignore Generic.Files.LineLength.TooLong
86            'phone.phone' => trans('Le champ :attribute est invalide, il accepte les numéros des pays suivants :countries.', [
87                'countries' => implode(',', Country::getValidCountriesLabel())
88            ]),
89            // phpcs:ignore Generic.Files.LineLength.TooLong
90            'country.in' => trans('Le champ :attribute est invalide, il accepte les pays suivants :countries.', [
91                'countries' => implode(',', Country::getValidCountriesLabel())
92            ])
93        ];
94    }
95}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/Api/V2/UpdateCustomerRequest.php.html b/tests/coverage/Http/Requests/Api/V2/UpdateCustomerRequest.php.html deleted file mode 100644 index 0990732..0000000 --- a/tests/coverage/Http/Requests/Api/V2/UpdateCustomerRequest.php.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/Api/V2/UpdateCustomerRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
26 / 26
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
UpdateCustomerRequest
-
- 100.00% covered (success) -
-
-
100.00%
26 / 26
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 rules
-
- 100.00% covered (success) -
-
-
100.00%
26 / 26
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests\Api\V2;
4
5use App\Enums\Country;
6use Illuminate\Validation\Rules\Password;
7use Kwaadpepper\Enum\Rules\EnumIsValidRule;
8
9class UpdateCustomerRequest extends StoreCustomerRequest
10{
11    /**
12     * Get the validation rules that apply to the request.
13     *
14     * @return array
15     */
16    public function rules(): array
17    {
18        $fields  = [
19            'firstname', 'lastname', 'email', 'password',
20            'birth_date', 'address', 'phone', 'country',
21            'city', 'postcode', 'opt_in'
22        ];
23        $atLeast = function ($attribute) use ($fields) {
24            return 'required_without_all:' . collect($fields)
25                ->filter(function ($fieldName) use ($attribute) {
26                    return $fieldName !== $attribute;
27                })->implode(',');
28        };
29        return [
30            'firstname' => [$atLeast('firstname'), 'string', 'min:1', 'max:255'],
31            'lastname' => [$atLeast('lastname'), 'string', 'min:1', 'max:255'],
32            'email' => [$atLeast('email'), 'email:rfc,dns,spoof,filter', 'max:255'],
33            'password' => [
34                $atLeast('password'), 'max:255', Password::min(8)
35                    ->mixedCase()
36                    ->letters()
37                    ->numbers()
38                    ->symbols()
39                    ->uncompromised()
40            ],
41            'birth_date' => [$atLeast('birth_date'), 'date_format:Y-m-d', 'max:255'],
42            'address' => [$atLeast('address'), 'required_with:address2', 'string', 'max:255'],
43            'address2' => ['sometimes', 'string', 'max:255'],
44            'phone' => [$atLeast('phone'), 'string', 'phone:' . ($this->currentCountry ?
45            $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes())), 'max:255'],
46            'country' => [
47                $atLeast('country'),
48                'numeric',
49                new EnumIsValidRule(Country::class),
50                sprintf(
51                    'in:%s',
52                    implode(',', Country::getValidCountries())
53                )
54            ],
55            'city' => [$atLeast('city'), 'string', 'min:2', 'max:255'],
56            'postcode' => [$atLeast('postcode'), 'string', 'postal_code:' . ($this->currentCountry ?
57            $this->currentCountry->iso : implode(',', Country::getValidIsoAplha2Codes())), 'max:255'],
58            'opt_in' => [$atLeast('opt_in'), 'boolean'],
59        ];
60    }
61}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/Api/V2/dashboard.html b/tests/coverage/Http/Requests/Api/V2/dashboard.html deleted file mode 100644 index 8ddb6a2..0000000 --- a/tests/coverage/Http/Requests/Api/V2/dashboard.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Requests/Api/V2 - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

- -
- -
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - - -
MethodCoverage
prepareForValidation60%
rules85%
rules85%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - - -
MethodCRAP
rules4
prepareForValidation3
rules3
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Requests/Api/dashboard.html b/tests/coverage/Http/Requests/Api/dashboard.html deleted file mode 100644 index 6dc2e12..0000000 --- a/tests/coverage/Http/Requests/Api/dashboard.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Requests/Api - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

- -
- -
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - - -
MethodCoverage
prepareForValidation60%
rules85%
rules85%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - - -
MethodCRAP
rules4
prepareForValidation3
rules3
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Requests/Api/index.html b/tests/coverage/Http/Requests/Api/index.html deleted file mode 100644 index b9b4e8c..0000000 --- a/tests/coverage/Http/Requests/Api/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/Api - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 95.93% covered (success) -
-
-
95.93%
118 / 123
-
- 83.33% covered (warning) -
-
-
83.33%
15 / 18
-
- 72.73% covered (warning) -
-
-
72.73%
8 / 11
V2
-
- 95.93% covered (success) -
-
-
95.93%
118 / 123
-
- 83.33% covered (warning) -
-
-
83.33%
15 / 18
-
- 72.73% covered (warning) -
-
-
72.73%
8 / 11
-
- -
- - diff --git a/tests/coverage/Http/Requests/CustomerExport.php.html b/tests/coverage/Http/Requests/CustomerExport.php.html deleted file mode 100644 index 92e5169..0000000 --- a/tests/coverage/Http/Requests/CustomerExport.php.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/CustomerExport.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CustomerExport
n/a
0 / 0
n/a
0 / 0
3
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use App\Enums\CustomerExportType;
6use Illuminate\Foundation\Http\FormRequest;
7
8/**
9 * @codeCoverageIgnore
10 */
11class CustomerExport extends FormRequest
12{
13    /**
14     * Determine if the user is authorized to make this request.
15     *
16     * @return boolean
17     */
18    public function authorize()
19    {
20        return auth()->check();
21    }
22
23    /**
24     * Get the validation rules that apply to the request.
25     *
26     * @return array
27     */
28    public function rules()
29    {
30        return [
31            'brand' => 'required|numeric|exists:brands,id',
32            'export_type' => [
33                'required',
34                'numeric',
35                // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
36                function ($attribute, $value, $fail) {
37                    if (!\in_array($value, CustomerExportType::values())) {
38                        $fail(trans('Le type d\'export invalide'));
39                    }
40                }
41            ]
42        ];
43    }
44}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/GenerateServiceToken.php.html b/tests/coverage/Http/Requests/GenerateServiceToken.php.html deleted file mode 100644 index b39eb40..0000000 --- a/tests/coverage/Http/Requests/GenerateServiceToken.php.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/GenerateServiceToken.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
GenerateServiceToken
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use Illuminate\Foundation\Http\FormRequest;
6
7/**
8 * @codeCoverageIgnore
9 */
10class GenerateServiceToken extends FormRequest
11{
12    /**
13     * Determine if the user is authorized to make this request.
14     *
15     * @return boolean
16     */
17    public function authorize()
18    {
19        return auth()->check();
20    }
21
22    /**
23     * Get the validation rules that apply to the request.
24     *
25     * @return array
26     */
27    public function rules()
28    {
29        return [];
30    }
31}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/ManageBrandRequest.php.html b/tests/coverage/Http/Requests/ManageBrandRequest.php.html deleted file mode 100644 index a7720b1..0000000 --- a/tests/coverage/Http/Requests/ManageBrandRequest.php.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/ManageBrandRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ManageBrandRequest
n/a
0 / 0
n/a
0 / 0
3
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use ArondeParon\RequestSanitizer\Sanitizers\FilterVars;
6use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
7use Illuminate\Foundation\Http\FormRequest;
8
9/**
10 * @codeCoverageIgnore
11 */
12class ManageBrandRequest extends FormRequest
13{
14    use SanitizesInputs;
15
16    /**
17     * Using these sanitizers before proceeding the request
18     * @var array
19     */
20    protected $sanitizers = [
21        'name' => [
22            FilterVars::class => [
23                'filter' => FILTER_SANITIZE_STRING
24            ]
25        ],
26    ];
27
28    /**
29     * Determine if the user is authorized to make this request.
30     *
31     * @return boolean
32     */
33    public function authorize()
34    {
35        return auth()->check();
36    }
37
38    /**
39     * Get the validation rules that apply to the request.
40     *
41     * @return array
42     */
43    public function rules()
44    {
45        if ($this->isMethod('post')) {
46            return [
47                'name' => 'required|string|min:5|max:255'
48            ];
49        }
50        return [];
51    }
52}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/ManageServiceRequest.php.html b/tests/coverage/Http/Requests/ManageServiceRequest.php.html deleted file mode 100644 index 5ccdd7d..0000000 --- a/tests/coverage/Http/Requests/ManageServiceRequest.php.html +++ /dev/null @@ -1,179 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/ManageServiceRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ManageServiceRequest
n/a
0 / 0
n/a
0 / 0
3
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use ArondeParon\RequestSanitizer\Sanitizers\FilterVars;
6use ArondeParon\RequestSanitizer\Traits\SanitizesInputs;
7use Illuminate\Foundation\Http\FormRequest;
8
9/**
10 * @codeCoverageIgnore
11 */
12class ManageServiceRequest extends FormRequest
13{
14    use SanitizesInputs;
15
16    /**
17     * Using these sanitizers before proceeding the request
18     * @var array
19     */
20    protected $sanitizers = [
21        'name' => [
22            FilterVars::class => [
23                'filter' => FILTER_SANITIZE_STRING
24            ]
25        ],
26    ];
27
28    /**
29     * Determine if the user is authorized to make this request.
30     *
31     * @return boolean
32     */
33    public function authorize()
34    {
35        return auth()->check();
36    }
37
38    /**
39     * Get the validation rules that apply to the request.
40     *
41     * @return array
42     */
43    public function rules()
44    {
45        if ($this->isMethod('post')) {
46            return [
47                'name' => 'required|string|min:5|max:255',
48                'brand_id' => 'required|exists:brands,id'
49            ];
50        }
51        return [];
52    }
53}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/QuestionExport.php.html b/tests/coverage/Http/Requests/QuestionExport.php.html deleted file mode 100644 index a27f111..0000000 --- a/tests/coverage/Http/Requests/QuestionExport.php.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/QuestionExport.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
QuestionExport
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use Illuminate\Foundation\Http\FormRequest;
6
7/**
8 * @codeCoverageIgnore
9 */
10class QuestionExport extends FormRequest
11{
12    /**
13     * Determine if the user is authorized to make this request.
14     *
15     * @return boolean
16     */
17    public function authorize()
18    {
19        return auth()->check();
20    }
21
22    /**
23     * Get the validation rules that apply to the request.
24     *
25     * @return array
26     */
27    public function rules()
28    {
29        return [
30            'brand' => 'required|numeric|exists:brands,id'
31        ];
32    }
33}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/StatsByBrandRequest.php.html b/tests/coverage/Http/Requests/StatsByBrandRequest.php.html deleted file mode 100644 index 86a69c7..0000000 --- a/tests/coverage/Http/Requests/StatsByBrandRequest.php.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/StatsByBrandRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
StatsByBrandRequest
n/a
0 / 0
n/a
0 / 0
6
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
5
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use Exception;
6use Illuminate\Foundation\Http\FormRequest;
7use Illuminate\Support\Carbon;
8use Illuminate\Validation\ValidationException;
9
10/**
11 * @codeCoverageIgnore
12 */
13class StatsByBrandRequest extends FormRequest
14{
15    /**
16     * Determine if the user is authorized to make this request.
17     *
18     * @return boolean
19     */
20    public function authorize()
21    {
22        return auth()->check();
23    }
24
25    /**
26     * Get the validation rules that apply to the request.
27     *
28     * @return array
29     * @throws \Illuminate\Validation\ValidationException If given dates are unparsable of period is bigger than 1 year.
30     * @throws \Exception If carbon date processing failed.
31     */
32    public function rules()
33    {
34        if ($this->post()) {
35            try {
36                if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)) + 1) > 365) {
37                    throw ValidationException::withMessages([
38                        'Vous devez sélectionner une période de maximum 1 ans'
39                    ]);
40                }
41            } catch (Exception $e) {
42                if (\get_class($e) !== ValidationException::class) {
43                    throw ValidationException::withMessages([
44                        'Les dates séléectionnées ne sembles pas Ãªtre valides'
45                    ]);
46                }
47                throw $e;
48            }
49            return [
50                'brand' => 'required|exists:brands,id',
51                'start' => 'required|date|date_format:Y-m-d',
52                'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
53            ];
54        } //end if
55        return [];
56    }
57}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/StatsByQuestionRequest.php.html b/tests/coverage/Http/Requests/StatsByQuestionRequest.php.html deleted file mode 100644 index 3d59362..0000000 --- a/tests/coverage/Http/Requests/StatsByQuestionRequest.php.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests/StatsByQuestionRequest.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
StatsByQuestionRequest
n/a
0 / 0
n/a
0 / 0
6
n/a
0 / 0
 authorize
n/a
0 / 0
n/a
0 / 0
1
 rules
n/a
0 / 0
n/a
0 / 0
5
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Requests;
4
5use Exception;
6use Illuminate\Foundation\Http\FormRequest;
7use Illuminate\Support\Carbon;
8use Illuminate\Validation\ValidationException;
9
10/**
11 * @codeCoverageIgnore
12 */
13class StatsByQuestionRequest extends FormRequest
14{
15    /**
16     * Determine if the user is authorized to make this request.
17     *
18     * @return boolean
19     */
20    public function authorize()
21    {
22        return auth()->check();
23    }
24
25    /**
26     * Get the validation rules that apply to the request.
27     *
28     * @return array
29     * @throws \Illuminate\Validation\ValidationException If given dates are unparsable of period
30     *                                                    is bigger than 1 year.
31     * @throws \Exception If carbon date processing failed.
32     */
33    public function rules()
34    {
35        if ($this->post()) {
36            try {
37                if ((Carbon::parse($this->end)->diffInDays(Carbon::parse($this->start)) + 1) > 365) {
38                    throw ValidationException::withMessages([
39                        'Vous devez sélectionner une période de maximum 1 ans'
40                    ]);
41                }
42            } catch (Exception $e) {
43                if (\get_class($e) !== ValidationException::class) {
44                    throw ValidationException::withMessages([
45                        'Les dates séléectionnées ne sembles pas Ãªtre valides'
46                    ]);
47                }
48                throw $e;
49            }
50            return [
51                'question' => 'required|exists:questions,id',
52                'start' => 'required|date|date_format:Y-m-d',
53                'end' => 'required|date|date_format:Y-m-d|after_or_equal:start',
54            ];
55        } //end if
56        return [];
57    }
58}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Requests/dashboard.html b/tests/coverage/Http/Requests/dashboard.html deleted file mode 100644 index 0ed46e5..0000000 --- a/tests/coverage/Http/Requests/dashboard.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Requests - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
- -
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - - - - - - - - - - - - - - -
MethodCoverage
authorize0%
rules0%
attributes0%
authorize0%
rules0%
attributes0%
authorize0%
rules0%
attributes0%
authorize0%
rules0%
attributes0%
prepareForValidation60%
rules85%
rules85%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - - -
MethodCRAP
rules4
prepareForValidation3
rules3
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Requests/index.html b/tests/coverage/Http/Requests/index.html deleted file mode 100644 index a540f42..0000000 --- a/tests/coverage/Http/Requests/index.html +++ /dev/null @@ -1,323 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Requests - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 84.89% covered (warning) -
-
-
84.89%
118 / 139
-
- 50.00% covered (danger) -
-
-
50.00%
15 / 30
-
- 53.33% covered (warning) -
-
-
53.33%
8 / 15
Answers
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Api
-
- 95.93% covered (success) -
-
-
95.93%
118 / 123
-
- 83.33% covered (warning) -
-
-
83.33%
15 / 18
-
- 72.73% covered (warning) -
-
-
72.73%
8 / 11
Brands
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Questions
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 6
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Services
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CustomerExport.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
GenerateServiceToken.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
ManageBrandRequest.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
ManageServiceRequest.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
QuestionExport.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StatsByBrandRequest.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StatsByQuestionRequest.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Http/Resources/Api/V2/ApiResponse.php.html b/tests/coverage/Http/Resources/Api/V2/ApiResponse.php.html deleted file mode 100644 index 70e8eb0..0000000 --- a/tests/coverage/Http/Resources/Api/V2/ApiResponse.php.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Resources/Api/V2/ApiResponse.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ApiResponse
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 __construct
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 toArray
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Resources\Api\V2;
4
5use App\Enums\ApiStatus;
6use Illuminate\Http\Resources\Json\JsonResource;
7use stdClass;
8
9class ApiResponse extends JsonResource
10{
11    /** @var \stdClass */
12    protected $data;
13
14    /** @var \App\Enums\ApiStatus */
15    protected $status;
16
17    /**
18     * @param \stdClass            $data
19     * @param \App\Enums\ApiStatus $status
20     */
21    public function __construct(stdClass $data = null, ApiStatus $status)
22    {
23        $this->status = $status;
24        $this->data   = $data;
25    }
26
27    /**
28     * Transform the resource into an array.
29     *
30     * @param  mixed  $request
31     * @return array
32     */
33    // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing
34    public function toArray($request): array
35    {
36        return [
37            'status' => $this->status->value,
38            'data' => (array)$this->data,
39            'version' => config('api.version')
40        ];
41    }
42}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Resources/Api/dashboard.html b/tests/coverage/Http/Resources/Api/dashboard.html deleted file mode 100644 index 7d441f2..0000000 --- a/tests/coverage/Http/Resources/Api/dashboard.html +++ /dev/null @@ -1,286 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Resources/Api - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Resources/Api/index.html b/tests/coverage/Http/Resources/Api/index.html deleted file mode 100644 index afc6c6f..0000000 --- a/tests/coverage/Http/Resources/Api/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Resources/Api - - - - - - - -
-
-
- -
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
46 / 46
-
- 100.00% covered (success) -
-
-
100.00%
11 / 11
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
V2
-
- 100.00% covered (success) -
-
-
100.00%
46 / 46
-
- 100.00% covered (success) -
-
-
100.00%
11 / 11
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- -
- - diff --git a/tests/coverage/Http/Resources/CustomerResource.php.html b/tests/coverage/Http/Resources/CustomerResource.php.html deleted file mode 100644 index 1091f74..0000000 --- a/tests/coverage/Http/Resources/CustomerResource.php.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Resources/CustomerResource.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CustomerResource
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 toArray
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Http\Resources;
4
5use Illuminate\Http\Resources\Json\JsonResource;
6
7/**
8 * @codeCoverageIgnore
9 */
10class CustomerResource extends JsonResource
11{
12    /** @var integer */
13    private $brandId;
14
15    /**
16     * @param mixed $resource
17     * @param mixed $brandId
18     */
19    public function __construct($resource, $brandId)
20    {
21        parent::__construct($resource);
22        $this->brandId = $brandId;
23    }
24
25    /**
26     * Transform the resource into an array.
27     *
28     * @param  mixed  $request
29     * @return array
30     */
31    //phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass, Squiz.Commenting.FunctionComment.Missing
32    public function toArray($request)
33    {
34        $infos = $this->infos()->where('brand_id', $this->brandId)->first();
35        $login = $this->logins()->where('brand_id', $this->brandId)->first();
36        return [
37            'status' => 'OK',
38            'user' => [
39                'crm_id' => $this->id,
40                'firstname' => $infos->firstname,
41                'lastname' => $infos->lastname,
42                'email' => $this->email,
43                'birth_date' => $infos->birth_date,
44                'address' => $infos->address,
45                'address2' => $infos->address2,
46                'phone' => $infos->phone,
47                'country' => $infos->country->label,
48                'city' => $infos->city,
49                'postcode' => $infos->postcode,
50                'opt_in' => $infos->opt_in,
51                'hashcode' => $infos->hashcode,
52                'status' => $login->status,
53            ]
54        ];
55    }
56}
- - - -
- - - - - - diff --git a/tests/coverage/Http/Resources/dashboard.html b/tests/coverage/Http/Resources/dashboard.html deleted file mode 100644 index 6a28be8..0000000 --- a/tests/coverage/Http/Resources/dashboard.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http/Resources - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/Resources/index.html b/tests/coverage/Http/Resources/index.html deleted file mode 100644 index 7654368..0000000 --- a/tests/coverage/Http/Resources/index.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http/Resources - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
46 / 46
-
- 100.00% covered (success) -
-
-
100.00%
11 / 11
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
Api
-
- 100.00% covered (success) -
-
-
100.00%
46 / 46
-
- 100.00% covered (success) -
-
-
100.00%
11 / 11
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
CustomerResource.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StatusResource.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Http/dashboard.html b/tests/coverage/Http/dashboard.html deleted file mode 100644 index 2316bbd..0000000 --- a/tests/coverage/Http/dashboard.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Http - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
- -
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
MethodCoverage
handle0%
rules0%
rules0%
authorize0%
attributes0%
rules0%
authorize0%
attributes0%
authorize0%
removeUnwantedHeaders0%
attributes0%
rules0%
authorize0%
attributes0%
prepareForValidation60%
rules85%
rules85%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - - - - -
MethodCRAP
handle12
removeUnwantedHeaders6
rules4
prepareForValidation3
rules3
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Http/index.html b/tests/coverage/Http/index.html deleted file mode 100644 index f6935ce..0000000 --- a/tests/coverage/Http/index.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Http - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 90.51% covered (success) -
-
-
90.51%
372 / 411
-
- 75.71% covered (warning) -
-
-
75.71%
53 / 70
-
- 77.14% covered (warning) -
-
-
77.14%
27 / 35
Controllers
-
- 100.00% covered (success) -
-
-
100.00%
171 / 171
-
- 100.00% covered (success) -
-
-
100.00%
21 / 21
-
- 100.00% covered (success) -
-
-
100.00%
7 / 7
Middleware
-
- 66.67% covered (warning) -
-
-
66.67%
36 / 54
-
- 71.43% covered (warning) -
-
-
71.43%
5 / 7
-
- 83.33% covered (warning) -
-
-
83.33%
5 / 6
Requests
-
- 84.89% covered (warning) -
-
-
84.89%
118 / 139
-
- 50.00% covered (danger) -
-
-
50.00%
15 / 30
-
- 53.33% covered (warning) -
-
-
53.33%
8 / 15
Resources
-
- 100.00% covered (success) -
-
-
100.00%
46 / 46
-
- 100.00% covered (success) -
-
-
100.00%
11 / 11
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
Sanitizers
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Kernel.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Jobs/SetServiceStats.php.html b/tests/coverage/Jobs/SetServiceStats.php.html deleted file mode 100644 index 19b751a..0000000 --- a/tests/coverage/Jobs/SetServiceStats.php.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Jobs/SetServiceStats.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
SetServiceStats
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 handle
n/a
0 / 0
n/a
0 / 0
2
 retryUntil
n/a
0 / 0
n/a
0 / 0
1
 middleware
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Jobs;
4
5use App\Models\Service;
6use App\Models\ServiceStat;
7use Illuminate\Bus\Queueable;
8use Illuminate\Contracts\Queue\ShouldQueue;
9use Illuminate\Foundation\Bus\Dispatchable;
10use Illuminate\Queue\InteractsWithQueue;
11use Illuminate\Queue\Middleware\WithoutOverlapping;
12use Illuminate\Queue\SerializesModels;
13
14/**
15 * @codeCoverageIgnore
16 */
17class SetServiceStats implements ShouldQueue
18{
19    use Dispatchable;
20    use InteractsWithQueue;
21    use Queueable;
22    use SerializesModels;
23
24    private const KEY = '2202020';
25
26    /** @var \App\Models\Service */
27    protected $service;
28
29    /**
30     * Create a new job instance.
31     *
32     * @param \App\Models\Service $service
33     * @return void
34     */
35    public function __construct(Service $service)
36    {
37        $this->service = $service;
38    }
39
40    /**
41     * Execute the job.
42     *
43     * @return void
44     */
45    public function handle()
46    {
47        $serviceStat = ServiceStat::firstOrNew([
48            'date' => date('Y-m-d'),
49            'service_id' => $this->service->id,
50            'brand_id' => $this->service->brand->id,
51        ]);
52        if (!$serviceStat->exists) {
53            $serviceStat->service()->associate($this->service);
54            $serviceStat->brand()->associate($this->service->brand);
55        }
56        $serviceStat->nb_calls += 1;
57        $serviceStat->saveOrFail();
58    }
59
60    /**
61     * Determine the time at which the job should timeout.
62     *
63     * @return \DateTime
64     */
65    public function retryUntil()
66    {
67        return now()->addMinutes(rand(0, 360));
68    }
69
70    /**
71     * Get the middleware the job should pass through.
72     *
73     * @return array
74     */
75    public function middleware()
76    {
77        return [
78            (new WithoutOverlapping(self::KEY))
79                ->releaseAfter(rand(90, 180))
80        ];
81    }
82}
- - - -
- - - - - - diff --git a/tests/coverage/Jobs/StartStatJobs.php.html b/tests/coverage/Jobs/StartStatJobs.php.html deleted file mode 100644 index 1e57a60..0000000 --- a/tests/coverage/Jobs/StartStatJobs.php.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Jobs/StartStatJobs.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
StartStatJobs
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 handle
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Jobs;
4
5use App\Models\Brand;
6use Illuminate\Bus\Queueable;
7use Illuminate\Contracts\Queue\ShouldQueue;
8use Illuminate\Foundation\Bus\Dispatchable;
9use Illuminate\Queue\InteractsWithQueue;
10
11/**
12 * @codeCoverageIgnore
13 */
14class StartStatJobs implements ShouldQueue
15{
16    use Dispatchable;
17    use InteractsWithQueue;
18    use Queueable;
19
20    /**
21     * Execute the job.
22     *
23     * @return void
24     */
25    public function handle()
26    {
27        $brands = Brand::all();
28        foreach ($brands as $brand) {
29            \dispatch(new StatJob($brand))
30                ->delay(rand(0, 90));
31        }
32    }
33}
- - - -
- - - - - - diff --git a/tests/coverage/Jobs/StatJob.php.html b/tests/coverage/Jobs/StatJob.php.html deleted file mode 100644 index 233e3c9..0000000 --- a/tests/coverage/Jobs/StatJob.php.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Jobs/StatJob.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
StatJob
n/a
0 / 0
n/a
0 / 0
3
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 handle
n/a
0 / 0
n/a
0 / 0
1
 middleware
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Jobs;
4
5use App\Models\Brand;
6use App\Models\BrandStat;
7use App\Models\CustomerInfo;
8use App\Models\CustomerLogin;
9use App\Models\SingleOptin;
10use Illuminate\Bus\Queueable;
11use Illuminate\Contracts\Queue\ShouldQueue;
12use Illuminate\Database\Eloquent\Builder;
13use Illuminate\Foundation\Bus\Dispatchable;
14use Illuminate\Queue\InteractsWithQueue;
15use Illuminate\Queue\Middleware\WithoutOverlapping;
16use Illuminate\Queue\SerializesModels;
17use Illuminate\Support\Carbon;
18
19/**
20 * @codeCoverageIgnore
21 */
22class StatJob implements ShouldQueue
23{
24    use Dispatchable;
25    use InteractsWithQueue;
26    use Queueable;
27    use SerializesModels;
28
29    /**
30     * Set high max tries as using prevent overlap
31     *
32     * @var integer
33     */
34    public $tries = 10;
35
36    /** Used to prevent overlaping */
37    private const JOBKEY = 32644296;
38
39    /** @var \App\Models\Brand */
40    private $brand;
41
42    /**
43     * Create a new job instance.
44     *
45     * @param \App\Models\Brand $brand
46     * @return void
47     */
48    public function __construct(Brand $brand)
49    {
50        $this->brand = $brand;
51    }
52
53    /**
54     * Execute the job.
55     *
56     * @return void
57     */
58    public function handle()
59    {
60        $stats = new BrandStat([
61            'active' => CustomerLogin::query()->whereHas('brand', function (Builder $q) {
62                $q->where('id', $this->brand->id);
63            })->whereDate('last_connection', '<', Carbon::now()->subMonths(6))->count(),
64            'inactive' => CustomerLogin::query()->whereHas('brand', function (Builder $q) {
65                $q->where('id', $this->brand->id);
66            })
67                ->whereDate('last_connection', '>', Carbon::now()->subMonths(6))->count(),
68            'new_accounts' => CustomerLogin::query()->whereHas('brand', function (Builder $q) {
69                $q->where('id', $this->brand->id);
70            })
71                ->whereDate('created_at', '<', Carbon::now()->subMonths(6))->count(),
72            'old_accounts' => CustomerLogin::query()->whereHas('brand', function (Builder $q) {
73                $q->where('id', $this->brand->id);
74            })
75                ->whereDate('created_at', '>', Carbon::now()->subMonths(6))->count(),
76            'total_accounts' => CustomerLogin::query()->whereHas('brand', function (Builder $q) {
77                $q->where('id', $this->brand->id);
78            })->count(),
79            'total_opt_in' => CustomerInfo::query()->whereHas('brand', function (Builder $q) {
80                $q->where('id', $this->brand->id);
81            })->whereOptIn(true)->count() + SingleOptin::query()->whereHas('brand', function (Builder $q) {
82                $q->where('id', $this->brand->id);
83            })->count()
84        ]);
85        $stats->brand()->associate($this->brand);
86        $stats->saveOrFail();
87    }
88
89    /**
90     * Get the middleware the job should pass through.
91     *
92     * @return array
93     */
94    public function middleware()
95    {
96        // * Dispatch randomly overlaping jobs if overlapping.
97        return [(new WithoutOverlapping(self::JOBKEY))->releaseAfter(rand(20, 90))];
98    }
99}
- - - -
- - - - - - diff --git a/tests/coverage/Jobs/UpdateCustomerLastConnection.php.html b/tests/coverage/Jobs/UpdateCustomerLastConnection.php.html deleted file mode 100644 index 8ef3df0..0000000 --- a/tests/coverage/Jobs/UpdateCustomerLastConnection.php.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Jobs/UpdateCustomerLastConnection.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
UpdateCustomerLastConnection
n/a
0 / 0
n/a
0 / 0
3
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 handle
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Jobs;
4
5use App\Models\Customer;
6use App\Models\CustomerLogin;
7use Illuminate\Bus\Queueable;
8use Illuminate\Contracts\Queue\ShouldQueue;
9use Illuminate\Foundation\Bus\Dispatchable;
10use Illuminate\Queue\InteractsWithQueue;
11use Illuminate\Queue\SerializesModels;
12use Illuminate\Support\Carbon;
13
14/**
15 * @codeCoverageIgnore
16 */
17class UpdateCustomerLastConnection implements ShouldQueue
18{
19    use Dispatchable;
20    use InteractsWithQueue;
21    use Queueable;
22    use SerializesModels;
23
24    /** @var \Illuminate\Support\Carbon */
25    protected $date = null;
26
27    /** @var \App\Models\Customer */
28    protected $customer = null;
29
30    /** @var \App\Models\CustomerLogin */
31    protected $customerLogin = null;
32
33    /**
34     * Create a new job instance.
35     *
36     * @param \App\Models\Customer       $customer
37     * @param \App\Models\CustomerLogin  $customerLogin
38     * @param \Illuminate\Support\Carbon $date
39     * @return void
40     */
41    public function __construct(
42        Customer $customer,
43        CustomerLogin $customerLogin,
44        Carbon $date
45    ) {
46        $this->date          = $date;
47        $this->customer      = $customer;
48        $this->customerLogin = $customerLogin;
49    }
50
51    /**
52     * Execute the job.
53     *
54     * @return void
55     */
56    public function handle()
57    {
58        if ($this->customerLogin->last_connection < $this->date) {
59            $this->customerLogin->last_connection = $this->date;
60            $this->customer->logins()->save($this->customerLogin);
61        }
62    }
63}
- - - -
- - - - - - diff --git a/tests/coverage/Jobs/dashboard.html b/tests/coverage/Jobs/dashboard.html deleted file mode 100644 index 354b968..0000000 --- a/tests/coverage/Jobs/dashboard.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Jobs - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Jobs/index.html b/tests/coverage/Jobs/index.html deleted file mode 100644 index b5c579c..0000000 --- a/tests/coverage/Jobs/index.html +++ /dev/null @@ -1,128 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Jobs - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
SetServiceStats.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StartStatJobs.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
StatJob.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
UpdateCustomerLastConnection.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Lib/Helper.php.html b/tests/coverage/Lib/Helper.php.html deleted file mode 100644 index 695b15d..0000000 --- a/tests/coverage/Lib/Helper.php.html +++ /dev/null @@ -1,501 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Lib/Helper.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 77.78% covered (warning) -
-
-
77.78%
42 / 54
-
- 75.00% covered (warning) -
-
-
75.00%
6 / 8
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Helper
-
- 77.78% covered (warning) -
-
-
77.78%
42 / 54
-
- 75.00% covered (warning) -
-
-
75.00%
6 / 8
30.32
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 isRequestForApiV2
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
 isRequestForApiV1
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
 getRouteName
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
 getBearerTokenFromRequest
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 7
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
20
 getRoutePrefix
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
 getRoute
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getPackageInfo
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
 signRequest
-
- 85.29% covered (warning) -
-
-
85.29%
29 / 34
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
7.16
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Lib;
4
5use App\Exceptions\Api\V2\CrmException;
6use Illuminate\Contracts\Encryption\DecryptException;
7use Illuminate\Http\Request;
8use Illuminate\Support\Facades\Crypt;
9use Illuminate\Support\Facades\Route;
10use Illuminate\Support\Str;
11
12/**
13 * Helper Class
14 */
15class Helper
16{
17    /**
18     * Package infos
19     *
20     * @var array
21     */
22    private static $packageInfos;
23
24    /**
25     * Tells if the request is within apiV2 routes
26     *
27     * @param \Illuminate\Http\Request $request
28     * @return boolean
29     */
30    public static function isRequestForApiV2(Request $request)
31    {
32        // * Small hack to ensure to know if route is From APIv2.
33        $routeName = static::getRouteName($request) ??
34            (strpos($request->path(), 'api/v2') === 0 ? 'apiV2.' : null) ?? 'none';
35        return strpos($routeName, 'apiV2.') === 0;
36    }
37
38    /**
39     * Tells if the request is within apiV1 routes
40     *
41     * @param \Illuminate\Http\Request $request
42     * @return boolean
43     */
44    public static function isRequestForApiV1(Request $request): bool
45    {
46        // * Small hack to ensure to know if route is From APIv1.
47        $routePrefix = (strpos(static::getRoutePrefix($request), 'api/v1') ? 'v1' : null) ??
48            (strpos($request->path(), 'api/v1') === 0 ? 'v1' : null) ?? 'none';
49        return strpos($routePrefix, 'v1') === 0;
50    }
51
52    /**
53     * Try to get current route name
54     *
55     * @param \Illuminate\Http\Request $request
56     * @return string|null
57     */
58    public static function getRouteName(Request $request)
59    {
60        return Route::currentRouteName() ?? ($request->route() ? $request->route()->getName() : null);
61    }
62
63    /**
64     * Get the bearer token from request
65     *
66     * @param \Illuminate\Http\Request $request
67     * @return string|null
68     */
69    public static function getBearerTokenFromRequest(Request $request): ?string
70    {
71        $header = $request->header('Authorization', '');
72
73        if (Str::contains($header, 'Bearer ')) {
74            $auths = \explode(',', $header);
75            foreach ($auths as $auth) {
76                if (Str::contains($auth, 'Bearer ')) {
77                    return Str::substr($auth, 7);
78                }
79            }
80        }
81        return null;
82    }
83
84    /**
85     * Try to get current route prefix
86     *
87     * @param \Illuminate\Http\Request $request
88     * @return string|null
89     */
90    public static function getRoutePrefix(Request $request)
91    {
92        return (Route::getCurrentRoute() ? Route::getCurrentRoute()->getPrefix() : null) ??
93            ($request->route() ? $request->route()->getPrefix() : null);
94    }
95
96    /**
97     * Try to get route from request
98     *
99     * @param \Illuminate\Http\Request $request
100     * @return \Illuminate\Routing\Route|null
101     */
102    public static function getRoute(Request $request)
103    {
104        return Route::getRoutes()->getByName(static::getRouteName($request));
105    }
106
107    /**
108     * Get composer.json infos
109     *
110     * @return array
111     */
112    public static function getPackageInfo(): array
113    {
114        if (!static::$packageInfos) {
115            static::$packageInfos = \json_decode(\file_get_contents(__DIR__ . '/../../composer.json'), true);
116        }
117        return static::$packageInfos;
118    }
119
120    /**
121     * Calculate the request signature
122     *
123     * @param string $secret
124     * @param string $uri
125     * @param string $method
126     * @param array  $parameters
127     * @return string request signature
128     * @throws \App\Exceptions\Api\V2\CrmException If an error occurs.
129     */
130    public static function signRequest(string $secret, string $uri, string $method, array $parameters): string
131    {
132        try {
133            $uri    = collect(\parse_url($uri));
134            $method = \strtolower($method);
135
136            // * Remove signature from parameters to calculate the hashmac.
137            $signature = collect([
138                'uri' => sprintf(
139                    '%s://%s%s',
140                    $uri->get('scheme'),
141                    $uri->get('host'),
142                    $uri->get('path')
143                ),
144                'method' => $method
145            ]);
146
147            $flattenRequestArray = function (array $array) use (&$flattenRequestArray) {
148                $out = [];
149                foreach ($array as $key => $value) {
150                    if (is_array($value)) {
151                        $subArray = $flattenRequestArray($value);
152                        foreach ($subArray as $subKey => $subValue) {
153                            $out["$key.$subKey"] = $subValue;
154                        }
155                        continue;
156                    }
157                    $out[$key] = $value;
158                }
159                return $out;
160            };
161
162            $parameters = $flattenRequestArray($parameters);
163            $parameters = collect($parameters);
164
165            // * Remove signature from parameters.
166            $parameters->pull('signature');
167
168            // * Natural sort all parameters.
169            $parameters = $parameters->sortKeys(\SORT_NATURAL)
170                ->map(function ($value, $key) {
171                    return sprintf('%s=%s', \rawurlencode($key), \rawurlencode($value));
172                });
173
174            $signature->push($parameters->implode('&'));
175
176            $calculatedHash = base64_encode(hash_hmac(
177                'sha256',
178                $signature->implode('\n'),
179                Crypt::decryptString($secret),
180                true
181            ));
182
183            if (!$calculatedHash) {
184                throw new CrmException('Failed to calculate requests hash');
185            }
186
187            return $calculatedHash;
188        } catch (DecryptException $e) {
189            throw new CrmException('Failed to decrypt service credentials', 0, $e);
190        } catch (\Exception $e) {
191            throw new CrmException('An error occured when authenticating service', 0, $e);
192        }//end try
193    }
194}
- - - -
- - - - - - diff --git a/tests/coverage/Lib/MigrateOldData.php.html b/tests/coverage/Lib/MigrateOldData.php.html deleted file mode 100644 index 9c748c2..0000000 --- a/tests/coverage/Lib/MigrateOldData.php.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Lib/MigrateOldData.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
MigrateOldData
n/a
0 / 0
n/a
0 / 0
19
n/a
0 / 0
 runMigration
n/a
0 / 0
n/a
0 / 0
1
 instruct
n/a
0 / 0
n/a
0 / 0
15
 initFile
n/a
0 / 0
n/a
0 / 0
1
 write
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Lib;
4
5use App\Exceptions\AbortRowImportException;
6use App\Models\Brand;
7use App\Models\Customer;
8use App\Models\CustomerInfo;
9use App\Models\CustomerLogin;
10use App\Models\SingleOptin;
11use Carbon\Exceptions\InvalidFormatException;
12use Database\migrations\LeGaulois2016Migration;
13use Illuminate\Database\Eloquent\Builder;
14use Illuminate\Support\Carbon;
15use Illuminate\Support\Facades\File;
16use Illuminate\Support\Facades\Hash;
17use Illuminate\Support\Facades\Validator;
18use Illuminate\Support\Str;
19
20/**
21 * @codeCoverageIgnore
22 */
23class MigrateOldData extends LeGaulois2016Migration
24{
25    /** @var \App\Models\Brand */
26    private $brand;
27
28    /** @var string */
29    private $table;
30
31    /**
32     * runMigration
33     *
34     * @param \App\Models\Brand $brand
35     * @param string            $table
36     * @return void
37     */
38    public function runMigration(Brand $brand, string $table)
39    {
40        $this->table = $table;
41        $this->brand = $brand;
42        $this->instruct();
43        $this->up();
44    }
45
46    /**
47     * instruct
48     *
49     * @return array
50     */
51    // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Squiz.Commenting.FunctionComment.Missing
52    public function instruct(): array
53    {
54        $this->initFile();
55        $cleanDegeu = function (string $str) {
56            return html_entity_decode(
57                trim(str_replace('\\', '', ucwords(stripcslashes(strtolower($str))))),
58                ENT_QUOTES | ENT_HTML5,
59                'UTF-8'
60            );
61        };
62        return [
63            'Users' => [
64                'table' => $this->table,
65                'model' => Customer::class,
66                'comment' => sprintf('Migrating %s customers', $this->brand->name),
67                'bind' => [
68                    'email' => [
69                        'apply' => function ($v, $model) use ($cleanDegeu) {
70                            $v                      = \mb_convert_encoding($v, "UTF-8");
71                            $v                      = explode('#|#', $v);
72                            $oldId                  = $v[0];
73                            $first_name             = $cleanDegeu($v[1]);
74                            $last_name              = $cleanDegeu($v[2]);
75                            $birth_date             = $v[3];
76                            $address                = $cleanDegeu($v[4]);
77                            $address2               = $cleanDegeu($v[5]);
78                            $city                   = $cleanDegeu($v[6]);
79                            $postcode               = $v[7];
80                            $user_inscritNewsletter = $v[8];
81                            $email                  = strtolower($v[9]);
82                            $pwd                    = $v[10];
83                            $created_at             = $v[11];
84                            $updated_at             = $v[12];
85                            $last_cnx               = $v[13];
86
87                            // * Just save Single optin.
88                            if (empty($first_name) && empty($last_name)) {
89                                $sglOptin           = SingleOptin::query()->where('email', strtolower($email))
90                                    ->whereHas('brand', function (Builder $q) {
91                                        $q->where('id', $this->brand->id);
92                                    })->firstOrNew();
93                                $sglOptin->email    = strtolower($email);
94                                $sglOptin->brand_id = $this->brand->id;
95                                $sglOptin->save();
96                            } else {
97                                // * Validate Data.
98                                $d = [
99                                    'first_name' => $first_name,
100                                    'last_name' => $last_name,
101                                    'address' => $address,
102                                    'address2' => $address2,
103                                    'city' => $city,
104                                    'postcode' => $postcode,
105                                ];
106
107                                $validator = Validator::make($d, [
108                                    'first_name' => 'min:2',
109                                    'last_name' => 'min:2'
110                                ]);
111
112                                if ($validator->fails()) {
113                                    throw new AbortRowImportException();
114                                }
115
116                                $rules = [
117                                    'address' => 'string|min:2',
118                                    'address2' => 'string|min:2',
119                                    'city' => 'string|min:2',
120                                    'postcode' => 'digits_between:4,5'
121                                ];
122
123                                $validate = function (array $data, array $rule) {
124                                    return Validator::make($data, $rule)->fails();
125                                };
126
127                                foreach ($rules as $key => $rule) {
128                                    if ($validate([$key => $d[$key]], [$key => $rule])) {
129                                        ${$key} = null;
130                                    }
131                                }
132
133                                $model             = Customer::where('email', $email)->firstOrNew();
134                                $model->email      = $email;
135                                $model->created_at = $created_at;
136                                $model->updated_at = $updated_at;
137                                $model->save();
138
139                                // * Filtre birthdate puisque c'est du sale dans la vielle crm.
140                                try {
141                                    $expl = explode('-', $birth_date);
142                                    $d    = Carbon::parse($birth_date);
143                                    if (
144                                        $d->toDateString() === sprintf(
145                                            '%s-%s-%s',
146                                            $expl[2] ?? '',
147                                            $expl[1] ?? '',
148                                            $expl[0] ?? ''
149                                        )
150                                    ) {
151                                        $birth_date = $d->toDateString();
152                                    }
153                                } catch (InvalidFormatException $e) {
154                                    $birth_date = '';
155                                }
156
157
158                                if ($birth_date === '0000-00-00') {
159                                    $birth_date = '';
160                                }
161
162                                $city     = !empty($city) ? $city : null;
163                                $postcode = !empty($postcode) ? $postcode : null;
164                                $address  = !empty($address) ? $address : null;
165                                $address2 = !empty($address2) ? $address2 : null;
166
167                                $cInfo              = new CustomerInfo();
168                                $cInfo->hashcode    = Str::uuid();
169                                $cInfo->firstname   = ucfirst(strtolower($first_name));
170                                $cInfo->lastname    = ucfirst(strtolower($last_name));
171                                $cInfo->birth_date  = !strlen($birth_date) ? null : $birth_date;
172                                $cInfo->address     = $address;
173                                $cInfo->address2    = $address2;
174                                $cInfo->city        = $city;
175                                $cInfo->postcode    = $postcode;
176                                $cInfo->opt_in      = $user_inscritNewsletter ? true : false;
177                                $cInfo->brand_id    = $this->brand->id;
178                                $cInfo->customer_id = $model->id;
179                                $cInfo->created_at  = $created_at;
180                                $cInfo->updated_at  = $updated_at;
181                                $cInfo->save();
182
183                                $cLogin                  = new CustomerLogin();
184                                $cLogin->password        = Hash::make(trim($pwd));
185                                $cLogin->brand_id        = $this->brand->id;
186                                $cLogin->customer_id     = $model->id;
187                                $cLogin->status          = true;
188                                $cLogin->created_at      = $created_at;
189                                $cLogin->updated_at      = $updated_at;
190                                $cLogin->last_connection = $last_cnx;
191                                $cLogin->save();
192
193                                $this->write(
194                                    [
195                                        'new' => $model->id,
196                                        'old' => $oldId,
197                                        'hashcode' => $cInfo->hashcode,
198                                        'created_at' => $created_at,
199                                        'updated_at' => $updated_at
200                                    ]
201                                );
202                            }//end if
203                            throw new AbortRowImportException();
204                        },
205                        'name' => [
206                            'user_id',
207                            'firstname',
208                            'lastname',
209                            'birthdate',
210                            'address',
211                            'address2',
212                            'city',
213                            'postcode',
214                            'optin',
215                            'email',
216                            'pwd',
217                            'created_at',
218                            'updated_at',
219                            'last_cnx'
220                        ]
221                    ],
222                ]
223            ],
224        ];
225    }
226
227    /**
228     * Create the migration file
229     *
230     * @return void
231     */
232    public function initFile()
233    {
234        File::put(storage_path('crm_migration.csv'), sprintf('new;old;hashcode;created_at;updated_at') . PHP_EOL);
235    }
236
237    /**
238     * Write date in the migration file
239     *
240     * @param array $data
241     * @return void
242     */
243    public function write(array $data)
244    {
245        foreach ($data as &$d) {
246            $d = sprintf('"%s"', $d);
247        }
248        File::append(storage_path('crm_migration.csv'), implode(';', $data) . PHP_EOL);
249    }
250}
- - - -
- - - - - - diff --git a/tests/coverage/Lib/dashboard.html b/tests/coverage/Lib/dashboard.html deleted file mode 100644 index f375f9c..0000000 --- a/tests/coverage/Lib/dashboard.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Lib - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - -
ClassCoverage
App\Lib\Helper77%
-
-
-
-

Project Risks

-
- - - - - - - - - - - -
ClassCRAP
App\Lib\Helper30
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - -
MethodCoverage
getBearerTokenFromRequest0%
signRequest85%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - -
MethodCRAP
getBearerTokenFromRequest20
signRequest7
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Lib/index.html b/tests/coverage/Lib/index.html deleted file mode 100644 index eab9fbb..0000000 --- a/tests/coverage/Lib/index.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Lib - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 77.78% covered (warning) -
-
-
77.78%
42 / 54
-
- 75.00% covered (warning) -
-
-
75.00%
6 / 8
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Helper.php
-
- 77.78% covered (warning) -
-
-
77.78%
42 / 54
-
- 75.00% covered (warning) -
-
-
75.00%
6 / 8
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
MigrateOldData.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Mail/CourrierArcadiaError.php.html b/tests/coverage/Mail/CourrierArcadiaError.php.html deleted file mode 100644 index 79e39b9..0000000 --- a/tests/coverage/Mail/CourrierArcadiaError.php.html +++ /dev/null @@ -1,189 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Mail/CourrierArcadiaError.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CourrierArcadiaError
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 build
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Mail;
4
5use Illuminate\Bus\Queueable;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Mail\Mailable;
8use Illuminate\Queue\SerializesModels;
9
10/**
11 * @codeCoverageIgnore
12 */
13class CourrierArcadiaError extends Mailable implements ShouldQueue
14{
15    use Queueable;
16    use SerializesModels;
17
18    /** @var string */
19    public $subject;
20
21    /** @var string */
22    public $msg;
23
24    /** @var string */
25    public $xml;
26
27    /**
28     * Create a new message instance.
29     *
30     * @param string $subject
31     * @param string $msg
32     * @param string $xml
33     * @return void
34     */
35    public function __construct(string $subject, string $msg, string $xml)
36    {
37        $this->subject = $subject;
38        $this->msg     = $msg;
39        $this->xml     = $xml;
40    }
41
42    /**
43     * Build the message.
44     *
45     * @return $this
46     */
47    public function build()
48    {
49        $msg = $this->msg;
50        return $this->subject(
51            sprintf(
52                "[LDC CRM] %s",
53                $this->subject
54            )
55        )->view(
56            'mail.arcadia-error',
57            compact('msg')
58        )->attachData(
59            $this->xml,
60            date("Ymd_His") . '.xml',
61            ['mime' => 'application/xml']
62        );
63    }
64}
- - - -
- - - - - - diff --git a/tests/coverage/Mail/ServiceCredentialRenewed.php.html b/tests/coverage/Mail/ServiceCredentialRenewed.php.html deleted file mode 100644 index 45df271..0000000 --- a/tests/coverage/Mail/ServiceCredentialRenewed.php.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Mail/ServiceCredentialRenewed.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ServiceCredentialRenewed
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 __construct
n/a
0 / 0
n/a
0 / 0
1
 build
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Mail;
4
5use App\Models\Service;
6use App\Models\User;
7use Illuminate\Mail\Mailable;
8
9/**
10 * @codeCoverageIgnore
11 */
12class ServiceCredentialRenewed extends Mailable
13{
14    /**
15     * The concerned service
16     *
17     * @var \App\Models\Service
18     */
19    public $service;
20
21    /**
22     * The user who did the action
23     *
24     * @var \App\Models\User
25     */
26    public $user;
27
28    /**
29     * Create a new message instance.
30     *
31     * @param \App\Models\Service $service
32     * @param \App\Models\User    $user
33     * @return void
34     */
35    public function __construct(Service $service, User $user)
36    {
37        $this->service = $service;
38        $this->user    = $user;
39    }
40
41    /**
42     * Build the message.
43     *
44     * @return $this
45     */
46    public function build()
47    {
48        return $this->view('mail.service-renewed')
49            ->subject('!!! Warning !!!, New service credential have been generated !')
50            ->text('mail.service-renewed-plain')
51            ->with('appName', config('app.name'))
52            ->with('service', $this->service)
53            ->with('by', sprintf('%s %s (%s)', $this->user->firstname, $this->user->lastname, $this->user->email))
54            ->to(
55                config('mail.contactsList.serviceweb', 'equipe.web@ldc.fr'),
56                sprintf('%s service warning', config('app.name'))
57            );
58    }
59}
- - - -
- - - - - - diff --git a/tests/coverage/Mail/index.html b/tests/coverage/Mail/index.html deleted file mode 100644 index 7564d01..0000000 --- a/tests/coverage/Mail/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Mail - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
CourrierArcadiaError.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
ServiceCredentialRenewed.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Models/Answer.php.html b/tests/coverage/Models/Answer.php.html deleted file mode 100644 index f8e55a3..0000000 --- a/tests/coverage/Models/Answer.php.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/Answer.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Answer
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 question
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 customers
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7
8class Answer extends Model
9{
10    use HasFactory;
11
12    /**
13     * The attributes that are mass assignable.
14     *
15     * @var string[]
16     */
17    protected $fillable = [
18        'answer'
19    ];
20
21    // * RELATIONS
22
23    /**
24     * Question BelongsTo relation
25     *
26     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
27     */
28    public function question()
29    {
30        return $this->belongsTo(Question::class);
31    }
32
33    /**
34     * Answer belongsToMany relation
35     *
36     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
37     */
38    public function customers()
39    {
40        return $this->belongsToMany(Customer::class, 'customer_answer');
41    }
42}
- - - -
- - - - - - diff --git a/tests/coverage/Models/Brand.php.html b/tests/coverage/Models/Brand.php.html deleted file mode 100644 index aa46e55..0000000 --- a/tests/coverage/Models/Brand.php.html +++ /dev/null @@ -1,502 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/Brand.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 50.00% covered (danger) -
-
-
50.00%
8 / 16
-
- 40.00% covered (danger) -
-
-
40.00%
4 / 10
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Brand
-
- 50.00% covered (danger) -
-
-
50.00%
8 / 16
-
- 40.00% covered (danger) -
-
-
40.00%
4 / 10
22.50
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 __construct
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getCacheBaseTags
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 booted
-
- 50.00% covered (danger) -
-
-
50.00%
3 / 6
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
1.12
 services
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 questions
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 customerInfos
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 servicesStats
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 singleOptins
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 getAccountsAttribute
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 getTotalOptinsAttribute
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7use Rennokki\QueryCache\Traits\QueryCacheable;
8
9class Brand extends Model
10{
11    use HasFactory;
12    use QueryCacheable;
13
14    /**
15     * The timeout cache for this model
16     *
17     * @var integer
18     */
19    protected $cacheFor = 0;
20
21    /**
22     * The attributes that are mass assignable.
23     *
24     * @var string[]
25     */
26    protected $fillable = [
27        'name'
28    ];
29
30    /**
31     * Brand
32     *
33     * @param array $attributes
34     */
35    public function __construct(array $attributes = [])
36    {
37        $this->cacheFor = config('api.servicesDBCache') * 60;
38
39        parent::__construct($attributes);
40    }
41
42    /**
43     * Set the base cache tags that will be present
44     * on all queries.
45     *
46     * ! This is used to flush caching
47     *
48     * @return array
49     */
50    protected function getCacheBaseTags(): array
51    {
52        return [
53            'crm_brands',
54        ];
55    }
56
57    /**
58     * Perform any actions required after the model boots.
59     *
60     * @return void
61     */
62    protected static function booted()
63    {
64        static::created(function () {
65            // * Flush Eloquent cache.
66            self::flushQueryCache();
67        });
68        static::updated(function () {
69            // * Flush Eloquent cache.
70            self::flushQueryCache();
71        });
72        static::deleted(function () {
73            // * Flush Eloquent cache.
74            self::flushQueryCache();
75        });
76    }
77
78    // * RELATIONS
79
80    /**
81     * Service hasMany relation
82     *
83     * @return \Illuminate\Database\Eloquent\Relations\HasMany
84     */
85    public function services()
86    {
87        return $this->hasMany(Service::class);
88    }
89
90    /**
91     * Question hasMany relation
92     *
93     * @return \Illuminate\Database\Eloquent\Relations\HasMany
94     */
95    public function questions()
96    {
97        return $this->hasMany(Question::class);
98    }
99
100    /**
101     * CustomerInfo hasMany relation
102     *
103     * @return \Illuminate\Database\Eloquent\Relations\HasMany
104     */
105    public function customerInfos()
106    {
107        return $this->hasMany(CustomerInfo::class);
108    }
109
110    /**
111     * ServiceStat hasMany relation
112     *
113     * @return \Illuminate\Database\Eloquent\Relations\HasMany
114     */
115    public function servicesStats()
116    {
117        return $this->hasMany(ServiceStat::class);
118    }
119
120    /**
121     * SingleOptin hasMany relation
122     *
123     * @return \Illuminate\Database\Eloquent\Relations\HasMany
124     */
125    public function singleOptins()
126    {
127        return $this->hasMany(SingleOptin::class);
128    }
129
130    // * METHODS
131
132    /**
133     * Account attribute accessor
134     *
135     * @return integer
136     */
137    public function getAccountsAttribute(): int
138    {
139        return $this->customerInfos()->count();
140    }
141
142    /**
143     * Total optins attribute accessor
144     *
145     * @return integer
146     */
147    public function getTotalOptinsAttribute(): int
148    {
149        return $this->customerInfos()->where('opt_in', 1)->count() + $this->singleOptins()->count();
150    }
151}
- - - -
- - - - - - diff --git a/tests/coverage/Models/BrandStat.php.html b/tests/coverage/Models/BrandStat.php.html deleted file mode 100644 index c9d5a6d..0000000 --- a/tests/coverage/Models/BrandStat.php.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/BrandStat.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 25.00% covered (danger) -
-
-
25.00%
1 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
BrandStat
-
- 25.00% covered (danger) -
-
-
25.00%
1 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
6.80
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 booted
-
- 33.33% covered (danger) -
-
-
33.33%
1 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
3.19
 brand
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Model;
6
7class BrandStat extends Model
8{
9    /**
10     * Indicates if the model should be timestamped.
11     *
12     * @var boolean
13     */
14    public $timestamps = false;
15
16    /**
17     * The attributes that should be cast to native types.
18     *
19     * @var array
20     */
21    public $casts = [
22        'created_at' => 'date'
23    ];
24
25    /**
26     * The attributes that are mass assignable.
27     *
28     * @var string[]
29     */
30    protected $fillable = [
31        'active', 'inactive',
32        'old_accounts', 'new_accounts',
33        'total_accounts', 'total_opt_in'
34    ];
35
36    /**
37     * Perform any actions required after the model boots.
38     *
39     * @return void
40     */
41    protected static function booted()
42    {
43        static::creating(function (self $model) {
44            if (!$model->created_at) {
45                $model->created_at = now();
46            }
47        });
48    }
49
50    // * RELATIONS
51
52    /**
53     * Brand hasMany relation
54     *
55     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
56     */
57    public function brand()
58    {
59        return $this->belongsTo(Brand::class);
60    }
61}
- - - -
- - - - - - diff --git a/tests/coverage/Models/Customer.php.html b/tests/coverage/Models/Customer.php.html deleted file mode 100644 index 14c90f4..0000000 --- a/tests/coverage/Models/Customer.php.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/Customer.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 92.45% covered (success) -
-
-
92.45%
49 / 53
-
- 70.00% covered (warning) -
-
-
70.00%
7 / 10
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Customer
-
- 92.45% covered (success) -
-
-
92.45%
49 / 53
-
- 70.00% covered (warning) -
-
-
70.00%
7 / 10
24.25
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 infos
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 logins
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 optInHistory
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 brands
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 answers
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 info
-
- 92.31% covered (success) -
-
-
92.31%
12 / 13
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
6.02
 login
-
- 92.31% covered (success) -
-
-
92.31%
12 / 13
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
6.02
 loadBrandData
-
- 80.00% covered (warning) -
-
-
80.00%
8 / 10
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
5.20
 setOptIn
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getAnswers
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Collection;
7use Illuminate\Database\Eloquent\Factories\HasFactory;
8use Illuminate\Database\Eloquent\Model;
9use Illuminate\Database\Eloquent\ModelNotFoundException;
10
11class Customer extends Model
12{
13    use HasFactory;
14
15    public const FIRST_OR_NULL = 0;
16    public const FIRST_OR_NEW  = 1;
17    public const FIRST_OR_FAIL = 2;
18
19    /**
20     * The attributes that are mass assignable.
21     *
22     * @var string[]
23     */
24    protected $fillable = [
25        'email'
26    ];
27
28    // * RELATIONS
29
30    /**
31     * CustomerInfo hasMany relation
32     *
33     * @return \Illuminate\Database\Eloquent\Relations\HasMany
34     */
35    public function infos()
36    {
37        return $this->hasMany(CustomerInfo::class);
38    }
39
40    /**
41     * CustomerLogin hasMany relation
42     *
43     * @return \Illuminate\Database\Eloquent\Relations\HasMany
44     */
45    public function logins()
46    {
47        return $this->hasMany(CustomerLogin::class);
48    }
49
50    /**
51     * CustomerOptInHistory hasMany relation
52     *
53     * @return \Illuminate\Database\Eloquent\Relations\HasMany
54     */
55    public function optInHistory()
56    {
57        return $this->hasMany(CustomerOptInHistory::class);
58    }
59
60    /**
61     * Customer Brand belongsToMany relation
62     *
63     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
64     */
65    public function brands()
66    {
67        return $this->belongsToMany(Brand::class, CustomerInfo::class);
68    }
69
70    /**
71     * Answers belongsToMany relation
72     *
73     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
74     */
75    public function answers()
76    {
77        return $this->belongsToMany(Answer::class, 'customer_answer');
78    }
79
80    // * METHODS
81
82    /**
83     * Get the CustomerInfo from a specific brand
84     * or create a New one if $firstOrNewOrFail is 1
85     * throw model not found exception if firstOrNewOrFail = 2
86     *
87     * @param \App\Models\Brand $brand
88     * @param integer           $firstOrNewOrFail
89     * @return \Illuminate\Database\Eloquent\Builder
90     */
91    public function info(Brand $brand, int $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerInfo
92    {
93        if (isset($this->relations['infos'])) {
94            /** @var \App\Models\CustomerInfo $info */
95            foreach ($this->relations['infos'] as $info) {
96                if ($info->brand_id === $brand->id) {
97                    return $info;
98                }
99            }
100        }
101        $query = CustomerInfo::query()->whereHas('brand', function (Builder $q) use ($brand) {
102            $q->where('id', $brand->id);
103        })->whereHas('customer', function (Builder $q) {
104            $q->where('id', $this->id);
105        });
106        if ($firstOrNewOrFail === self::FIRST_OR_NEW) {
107            return $query->firstOrNew();
108        }
109        if ($firstOrNewOrFail === self::FIRST_OR_FAIL) {
110            return $query->firstOrFail();
111        }
112        return $query->first();
113    }
114
115    /**
116     * Get the CustomerLogin from a specific brand
117     * or create a New one if $firstOrNewOrFail is 1
118     * throw model not found exception if firstOrNewOrFail = 2
119     *
120     * @param \App\Models\Brand $brand
121     * @param integer           $firstOrNewOrFail
122     * @return \Illuminate\Database\Eloquent\Builder
123     */
124    public function login(Brand $brand, int $firstOrNewOrFail = self::FIRST_OR_NULL): ?CustomerLogin
125    {
126        if (isset($this->relations['logins'])) {
127            /** @var \App\Models\CustomerLogin $login */
128            foreach ($this->relations['logins'] as $login) {
129                if ($login->brand_id === $brand->id) {
130                    return $login;
131                }
132            }
133        }
134        $query = CustomerLogin::query()->whereHas('brand', function (Builder $q) use ($brand) {
135            $q->where('id', $brand->id);
136        })->whereHas('customer', function (Builder $q) {
137            $q->where('id', $this->id);
138        });
139        if ($firstOrNewOrFail === self::FIRST_OR_NEW) {
140            return $query->firstOrNew();
141        }
142        if ($firstOrNewOrFail === self::FIRST_OR_FAIL) {
143            return $query->firstOrFail();
144        }
145        return $query->first();
146    }
147
148    /**
149     * Load login and info for this customer and this specific brand.
150     *
151     * @param \App\Models\Brand $brand
152     * @param boolean           $orFail
153     * @return self
154     * @throws ModelNotFoundException If no login or info was found for the brand.
155     */
156    public function loadBrandData(Brand $brand, bool $orFail = false): self
157    {
158        $this->load([
159            'logins' => function ($q) use ($brand) {
160                $q->where('brand_id', $brand->id);
161            },
162            'infos' => function ($q) use ($brand) {
163                $q->where('brand_id', $brand->id);
164            }
165        ]);
166        if ($orFail and !isset($this->relations['logins'][0])) {
167            throw (new ModelNotFoundException())->setModel(CustomerLogin::class);
168        }
169        if ($orFail and !isset($this->relations['infos'][0])) {
170            throw (new ModelNotFoundException())->setModel(CustomerInfo::class);
171        }
172        return $this;
173    }
174
175    /**
176     * Set this customer as optin
177     *
178     * @param \App\Models\Brand $brand
179     * @param boolean           $optIn
180     * @return void
181     * @throws \Throwable If customer history update failed.
182     */
183    public function setOptIn(Brand $brand, bool $optIn): void
184    {
185        $this->opt_in = $optIn;
186
187        // * Ajout dans la table d'historique des opt-in de la nouvelle valeur.
188        $optInHistory            = new CustomerOptInHistory();
189        $optInHistory->new_value = $optIn;
190        $optInHistory->customer()->associate($this);
191        $optInHistory->brand()->associate($brand);
192        $optInHistory->saveOrFail();
193    }
194
195    /**
196     * Get All answers given by this customer
197     * for a specific service
198     *
199     * @param \App\Models\Service $service
200     * @return \Illuminate\Database\Eloquent\Collection
201     */
202    public function getAnswers(Service $service): Collection
203    {
204        return Answer::query()->whereHas('customers', function (Builder $q) {
205            $q->where('id', $this->id);
206        })->whereHas('question', function (Builder $q) use ($service) {
207            $q->whereHas('brand', function (Builder $q) use ($service) {
208                $q->where('id', $service->brand->id);
209            });
210        })->get();
211    }
212}
- - - -
- - - - - - diff --git a/tests/coverage/Models/CustomerAnswer.php.html b/tests/coverage/Models/CustomerAnswer.php.html deleted file mode 100644 index e903243..0000000 --- a/tests/coverage/Models/CustomerAnswer.php.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/CustomerAnswer.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CustomerAnswer
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 getUpdatedAtColumn
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 customer
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 answer
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Relations\Pivot;
6
7class CustomerAnswer extends Pivot
8{
9    /**
10     * Inhibit update_at column as we only have
11     * created_at
12     *
13     * @return null
14     */
15    public function getUpdatedAtColumn()
16    {
17        return null;
18    }
19
20    // * RELATIONS
21
22    /**
23     * Customer belongsTo relation
24     *
25     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
26     */
27    public function customer()
28    {
29        return $this->belongsTo(Customer::class);
30    }
31
32    /**
33     * Customer belongsTo relation
34     *
35     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
36     */
37    public function answer()
38    {
39        return $this->belongsTo(Answer::class);
40    }
41}
- - - -
- - - - - - diff --git a/tests/coverage/Models/CustomerInfo.php.html b/tests/coverage/Models/CustomerInfo.php.html deleted file mode 100644 index b296e8d..0000000 --- a/tests/coverage/Models/CustomerInfo.php.html +++ /dev/null @@ -1,398 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/CustomerInfo.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 77.78% covered (warning) -
-
-
77.78%
14 / 18
-
- 66.67% covered (warning) -
-
-
66.67%
4 / 6
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CustomerInfo
-
- 77.78% covered (warning) -
-
-
77.78%
14 / 18
-
- 66.67% covered (warning) -
-
-
66.67%
4 / 6
6.40
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 booted
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 customer
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 login
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getFromEmail
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getFromHashcode
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use App\Enums\Country;
6use Illuminate\Database\Eloquent\Builder;
7use Illuminate\Support\Str;
8use Illuminate\Database\Eloquent\Factories\HasFactory;
9use Illuminate\Database\Eloquent\Model;
10use Kwaadpepper\Enum\Traits\CastsEnums;
11
12class CustomerInfo extends Model
13{
14    use HasFactory;
15    use CastsEnums;
16
17    /**
18     * Map attribute names to enum classes.
19     *
20     * @var array
21     */
22    protected $enumCasts = [
23        'country' => Country::class
24    ];
25
26    /**
27     * The attributes that should be cast to native types.
28     *
29     * @var array
30     */
31    protected $casts = [
32        'opt_in' => 'bool'
33    ];
34
35    /**
36     * The attributes that are mass assignable.
37     *
38     * @var string[]
39     */
40    protected $fillable = [
41        'firstname',
42        'lastname',
43        'birth_date',
44        'address',
45        'address2',
46        'phone',
47        'country',
48        'city',
49        'postcode',
50        'opt_in'
51    ];
52
53    /**
54     * Perform any actions required after the model boots.
55     *
56     * @return void
57     */
58    protected static function booted()
59    {
60        static::creating(function (self $model) {
61            $model->hashcode = Str::uuid();
62        });
63        static::deleting(function (self $model) {
64            CustomerOptInHistory::query()->whereHas('customer', function (Builder $q) use ($model) {
65                $q->where('id', $model->customer->id);
66            })->whereHas('brand', function (Builder $q) use ($model) {
67                $q->where('id', $model->brand->id);
68            })->delete();
69        });
70    }
71
72    // * RELATIONS
73
74    /**
75     * Customer BelongsTo relation
76     *
77     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
78     */
79    public function customer()
80    {
81        return $this->belongsTo(Customer::class);
82    }
83
84    /**
85     * get Login
86     *
87     * @return \App\Models\CustomerLogin
88     */
89    public function login()
90    {
91        return $this->customer->login($this->brand);
92    }
93
94    /**
95     * Brand BelongsTo relation
96     *
97     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
98     */
99    public function brand()
100    {
101        return $this->belongsTo(Brand::class);
102    }
103
104    // * METHODS
105
106    /**
107     * Get customer info from email
108     *
109     * @param \App\Models\Brand $brand
110     * @param string            $email
111     * @return \Illuminate\Database\Eloquent\Builder
112     */
113    public static function getFromEmail(Brand $brand, string $email)
114    {
115        return CustomerInfo::query()->whereHas('brand', function (Builder $q) use ($brand) {
116            $q->where('id', $brand->id);
117        })->whereHas('customer', function (Builder $q) use ($email) {
118            $q->where('email', $email);
119        });
120    }
121
122    /**
123     * Get customer info from hashcode
124     *
125     * @param \App\Models\Brand $brand
126     * @param string            $hashcode
127     * @return \Illuminate\Database\Eloquent\Builder
128     */
129    public static function getFromHashcode(Brand $brand, string $hashcode)
130    {
131        return CustomerInfo::query()->whereHas('brand', function (Builder $q) use ($brand) {
132            $q->where('id', $brand->id);
133        })->where('hashcode', $hashcode);
134    }
135}
- - - -
- - - - - - diff --git a/tests/coverage/Models/CustomerLogin.php.html b/tests/coverage/Models/CustomerLogin.php.html deleted file mode 100644 index 6dca501..0000000 --- a/tests/coverage/Models/CustomerLogin.php.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/CustomerLogin.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
9 / 9
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CustomerLogin
-
- 100.00% covered (success) -
-
-
100.00%
9 / 9
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 booted
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
 customer
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 passwordMatch
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Support\Facades\Hash;
8
9class CustomerLogin extends Model
10{
11    use HasFactory;
12
13    /**
14     * The model's attributes.
15     *
16     * @var array
17     */
18    protected $attributes = [
19        'status' => false
20    ];
21
22    /**
23     * The attributes that are mass assignable.
24     *
25     * @var string[]
26     */
27    protected $fillable = [
28        'password'
29    ];
30
31    /**
32     * The attributes that should be cast to native types.
33     *
34     * @var array
35     */
36    protected $casts = [
37        'status' => 'bool'
38    ];
39
40    /**
41     * Perform any actions required after the model boots.
42     *
43     * @return void
44     */
45    protected static function booted()
46    {
47        static::creating(function (self $model) {
48            if (Hash::needsRehash($model->password)) {
49                $model->password = Hash::make(trim($model->password));
50            }
51        });
52        static::updating(function (self $model) {
53            if (Hash::needsRehash($model->password)) {
54                $model->password = Hash::make(trim($model->password));
55            }
56        });
57    }
58
59    // * RELATIONS
60
61    /**
62     * Customer BelongsTo relation
63     *
64     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
65     */
66    public function customer()
67    {
68        return $this->belongsTo(Customer::class);
69    }
70
71    /**
72     * Brand BelongsTo relation
73     *
74     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
75     */
76    public function brand()
77    {
78        return $this->belongsTo(Brand::class);
79    }
80
81    // * METHODS
82    /**
83     * Check if the user password is correct
84     *
85     * @param string $password
86     * @return boolean
87     */
88    public function passwordMatch(string $password): bool
89    {
90        return Hash::check($password, $this->password);
91    }
92}
- - - -
- - - - - - diff --git a/tests/coverage/Models/CustomerOptInHistory.php.html b/tests/coverage/Models/CustomerOptInHistory.php.html deleted file mode 100644 index e379052..0000000 --- a/tests/coverage/Models/CustomerOptInHistory.php.html +++ /dev/null @@ -1,215 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/CustomerOptInHistory.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CustomerOptInHistory
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 customer
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7
8class CustomerOptInHistory extends Model
9{
10    use HasFactory;
11
12    /**
13     * The table associated with the model.
14     *
15     * @var string
16     */
17    protected $table = "customer_opt_in_history";
18
19    // * RELATIONS
20
21    /**
22     * Customer BelongsTo relation
23     *
24     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
25     */
26    public function customer()
27    {
28        return $this->belongsTo(Customer::class);
29    }
30
31    /**
32     * Brand BelongsTo relation
33     *
34     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
35     */
36    public function brand()
37    {
38        return $this->belongsTo(Brand::class);
39    }
40}
- - - -
- - - - - - diff --git a/tests/coverage/Models/Question.php.html b/tests/coverage/Models/Question.php.html deleted file mode 100644 index c642f41..0000000 --- a/tests/coverage/Models/Question.php.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/Question.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Question
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 answers
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use App\Enums\QuestionType;
6use Illuminate\Database\Eloquent\Factories\HasFactory;
7use Illuminate\Database\Eloquent\Model;
8use Kwaadpepper\Enum\Traits\CastsEnums;
9
10class Question extends Model
11{
12    use CastsEnums;
13    use HasFactory;
14
15    /**
16     * Map attribute names to enum classes.
17     *
18     * @var array
19     */
20    protected $enumCasts = [
21        'type' => QuestionType::class
22    ];
23
24    /**
25     * The attributes that are mass assignable.
26     *
27     * @var string[]
28     */
29    protected $fillable = [
30        'question',
31        'type',
32        'brand_id'
33    ];
34
35    // * RELATIONS
36
37    /**
38     * Brand BelongsTo relation
39     *
40     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
41     */
42    public function brand()
43    {
44        return $this->belongsTo(Brand::class);
45    }
46
47    /**
48     * Answer Has many relation
49     *
50     * @return \Illuminate\Database\Eloquent\Relations\HasMany
51     */
52    public function answers()
53    {
54        return $this->hasMany(Answer::class);
55    }
56}
- - - -
- - - - - - diff --git a/tests/coverage/Models/Service.php.html b/tests/coverage/Models/Service.php.html deleted file mode 100644 index 58619c8..0000000 --- a/tests/coverage/Models/Service.php.html +++ /dev/null @@ -1,585 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/Service.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 70.73% covered (warning) -
-
-
70.73%
29 / 41
-
- 60.00% covered (warning) -
-
-
60.00%
6 / 10
CRAP
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Service
-
- 70.73% covered (warning) -
-
-
70.73%
29 / 41
-
- 60.00% covered (warning) -
-
-
60.00%
6 / 10
22.42
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
 __construct
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getCacheBaseTags
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 booted
-
- 44.44% covered (danger) -
-
-
44.44%
4 / 9
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
1.17
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 servicesStats
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 generateBearerToken
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 generateBearerPassword
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 3
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
2
 incrementStat
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 getFromBearerToken
-
- 100.00% covered (success) -
-
-
100.00%
11 / 11
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
 authenticateApi
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use App\Jobs\SetServiceStats;
6use App\Lib\Helper;
7use Illuminate\Database\Eloquent\Factories\HasFactory;
8use Illuminate\Database\Eloquent\Model;
9use Illuminate\Database\Eloquent\ModelNotFoundException;
10use Illuminate\Http\Request;
11use Illuminate\Support\Facades\Crypt;
12use Illuminate\Support\Facades\Hash;
13use Illuminate\Support\Str;
14use Rennokki\QueryCache\Traits\QueryCacheable;
15use RuntimeException;
16
17class Service extends Model
18{
19    use HasFactory;
20    use QueryCacheable;
21
22    /**
23     * The timeout cache for this model
24     *
25     * @var integer
26     */
27    protected $cacheFor = 0;
28
29    /**
30     * The attributes that are mass assignable.
31     *
32     * @var string[]
33     */
34    protected $fillable = [
35        'name', 'brand_id'
36    ];
37
38    /**
39     * Service
40     *
41     * @param array $attributes
42     */
43    public function __construct(array $attributes = [])
44    {
45        parent::__construct($attributes);
46        $this->cacheFor = config('api.servicesDBCache') * 60;
47    }
48
49    /**
50     * Set the base cache tags that will be present
51     * on all queries.
52     *
53     * ! This is used to flush caching
54     *
55     * @return array
56     */
57    protected function getCacheBaseTags(): array
58    {
59        return [
60            'crm_services',
61        ];
62    }
63
64    /**
65     * Perform any actions required after the model boots.
66     *
67     * @return void
68     */
69    protected static function booted()
70    {
71        static::creating(function (self $model) {
72            $model->generateBearerToken();
73            $model->generateBearerPassword();
74        });
75        static::created(function () {
76            // * Flush Eloquent cache.
77            self::flushQueryCache();
78        });
79        static::updated(function () {
80            // * Flush Eloquent cache.
81            self::flushQueryCache();
82        });
83        static::deleted(function () {
84            // * Flush Eloquent cache.
85            self::flushQueryCache();
86        });
87    }
88
89    // * RELATIONS
90
91    /**
92     * Brand BelongsTo relation
93     *
94     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
95     */
96    public function brand()
97    {
98        return $this->belongsTo(Brand::class);
99    }
100
101    /**
102     * ServiceStat hasMany relation
103     *
104     * @return \Illuminate\Database\Eloquent\Relations\HasMany
105     */
106    public function servicesStats()
107    {
108        return $this->hasMany(ServiceStat::class);
109    }
110
111    // * METHODS
112
113    /**
114     * Generates new bearer token
115     *
116     * @return string
117     */
118    public function generateBearerToken(): string
119    {
120        $this->bearer_token      = hash('ripemd160', Str::uuid());
121        $this->bearer_token_date = now();
122        return $this->bearer_token;
123    }
124
125    /**
126     * Generates new bearer password
127     * It store the crypted password in model
128     * and returns the plain password
129     *
130     * @return string
131     */
132    public function generateBearerPassword(): string
133    {
134        $pwd              = hash('sha256', Str::uuid());
135        $this->bearer_pwd = Crypt::encryptString($pwd);
136        return $pwd;
137    }
138
139    /**
140     * Increment API CRM Stat
141     *
142     * @return void
143     */
144    public function incrementStat(): void
145    {
146        SetServiceStats::dispatch($this)->afterResponse()
147            ->onConnection('database')->onQueue('stats');
148    }
149
150    /**
151     * Get Service from bearerToken with its brand
152     *
153     * TODO remove this method when dropping V1
154     *
155     * @param string $bearerToken
156     * @return \App\Models\Service
157     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException If the token matches no known service.
158     */
159    public static function getFromBearerToken(string $bearerToken): self
160    {
161        $services = static::all();
162        /** @var \App\Models\Service $service */
163        foreach ($services as $service) {
164            // * Keep compatibility with V3.
165            $tokenEquals = false;
166            try {
167                $tokenEquals = Hash::check($bearerToken, $service->bearer_token);
168            } catch (RuntimeException $e) {
169                $tokenEquals = $bearerToken === $service->bearer_token;
170            }
171            if ($service->bearer_token and $tokenEquals) {
172                return $service->load([
173                    'brand' => function ($query) use ($service) {
174                        return $query->cacheTags(["service:{$service->id}:brand"]);
175                    },
176                ]);
177            }
178        }
179        throw new ModelNotFoundException('Could not find service from Service Token');
180    }
181
182    /**
183     * Authenticate the request for this service
184     *
185     * This will check hash-mac and that the request is wellformed
186     *
187     * The request should have the minimum following parameters
188     *  - timestamp using format Y-m-d H:i:s, and must be UTC (Greenwich Mean Time)
189     *  - bearerToken the public token generated by the crm
190     *  - signature the hashmac signature of the request
191     *
192     * ! Signature must be calculated with every parameter urlencode once only
193     * ! All parameters to calculate the signature must be sorted (natural sort asc)
194     *
195     * ! Signature should look like
196     * * httpVerb\n
197     * * uri\n
198     * * queryString
199     *
200     * ! httpVerb as well as uri must be lower case
201     * ! uri should include scheme, host and path and nothing else, if path is empty a trailing slash should be added
202     * ! eg: https://ldc-crm.fr/customer/1  or  https://ldc-crm.fr/
203     * ! The query string shall be separated with '&', eg param1=value1&param2=value2
204     * ! Key names and values of the query string must be url encoded using RFC 3986
205     * ! http://www.faqs.org/rfcs/rfc3986.html
206     * ! Signature must be hmac sha256 using the private key generatedby the crm
207     *
208     *     * ! They should then be concat in a string separated by \n
209     *
210     *
211     *
212     * @param \Illuminate\Http\Request $request
213     * @return boolean
214     * @throws \App\Exceptions\Api\V2\CrmException If an error occurs.
215     */
216    public function authenticateApi(Request $request): bool
217    {
218        // ! Hack to prevent Laravel to cast empty strings to null or the opposite.
219        // ! If cannot decode json user legacy all() method.
220        $data = \json_decode($request->getContent(), true);
221        $data = (\is_array($data) and \json_last_error() === \JSON_ERROR_NONE) ?
222            array_replace_recursive($request->all(), $data) : $request->all();
223
224        $calculatedHash = Helper::signRequest(
225            $this->bearer_pwd,
226            $request->getUri(),
227            $request->getMethod(),
228            $data
229        );
230
231        // ! Très important hash_equals https://fr.wikipedia.org/wiki/Attaque_temporelle
232        return \hash_equals($calculatedHash, $request->signature);
233    }
234}
- - - -
- - - - - - diff --git a/tests/coverage/Models/ServiceStat.php.html b/tests/coverage/Models/ServiceStat.php.html deleted file mode 100644 index 11b9004..0000000 --- a/tests/coverage/Models/ServiceStat.php.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/ServiceStat.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ServiceStat
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 service
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7
8class ServiceStat extends Model
9{
10    use HasFactory;
11
12    /**
13     * The attributes that are mass assignable.
14     *
15     * @var string[]
16     */
17    protected $fillable = ['date'];
18
19    /**
20     * The model's attributes.
21     *
22     * @var array
23     */
24    protected $attributes = [
25        'nb_calls' => 0
26    ];
27
28    // * RELATIONS
29
30    /**
31     * Brand BelongsTo relation
32     *
33     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
34     */
35    public function brand()
36    {
37        return $this->belongsTo(Brand::class);
38    }
39
40    /**
41     * Service BelongsTo relation
42     *
43     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
44     */
45    public function service()
46    {
47        return $this->belongsTo(Service::class);
48    }
49}
- - - -
- - - - - - diff --git a/tests/coverage/Models/SingleOptin.php.html b/tests/coverage/Models/SingleOptin.php.html deleted file mode 100644 index 3e45825..0000000 --- a/tests/coverage/Models/SingleOptin.php.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/SingleOptin.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
SingleOptin
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 booted
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 brand
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Support\Str;
8
9class SingleOptin extends Model
10{
11    use HasFactory;
12
13    /**
14     * The primary key for the model.
15     *
16     * @var string
17     */
18    protected $primaryKey = 'hashcode';
19
20    /**
21     * The "type" of the primary key ID.
22     *
23     * @var string
24     */
25    protected $keyType = 'uuid';
26
27    /**
28     * Indicates if the IDs are auto-incrementing.
29     *
30     * @var boolean
31     */
32    public $incrementing = false;
33
34    /**
35     * The attributes that are mass assignable.
36     *
37     * @var string[]
38     */
39    protected $fillable = [
40        'email'
41    ];
42
43    /**
44     * Perform any actions required after the model boots.
45     *
46     * @return void
47     */
48    protected static function booted()
49    {
50        static::creating(function (self $model) {
51            $model->hashcode = Str::uuid();
52        });
53    }
54
55    // * RELATIONS
56
57    /**
58     * Brand BelongsTo relation
59     *
60     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
61     */
62    public function brand()
63    {
64        return $this->belongsTo(Brand::class);
65    }
66}
- - - -
- - - - - - diff --git a/tests/coverage/Models/User.php.html b/tests/coverage/Models/User.php.html deleted file mode 100644 index 5ff0893..0000000 --- a/tests/coverage/Models/User.php.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models/User.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
User
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6use Illuminate\Foundation\Auth\User as Authenticatable;
7use Illuminate\Notifications\Notifiable;
8
9class User extends Authenticatable
10{
11    use HasFactory;
12    use Notifiable;
13
14    /**
15     * The attributes that are mass assignable.
16     *
17     * @var array
18     */
19    protected $fillable = [
20        'name',
21        'email',
22        'password',
23    ];
24
25    /**
26     * The attributes that should be hidden for arrays.
27     *
28     * @var array
29     */
30    protected $hidden = [
31        'password',
32        'remember_token',
33    ];
34
35    /**
36     * The attributes that should be cast to native types.
37     *
38     * @var array
39     */
40    protected $casts = [
41        'email_verified_at' => 'datetime',
42    ];
43}
- - - -
- - - - - - diff --git a/tests/coverage/Models/dashboard.html b/tests/coverage/Models/dashboard.html deleted file mode 100644 index 1164f86..0000000 --- a/tests/coverage/Models/dashboard.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Models - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - - - - - -
ClassCoverage
App\Models\BrandStat25%
App\Models\Brand50%
App\Models\Service70%
App\Models\CustomerInfo77%
-
-
-
-

Project Risks

-
- - - - - - - - - - - - -
ClassCRAP
App\Models\Service22
App\Models\BrandStat6
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

- -
-
-

Project Risks

-
- - - - - - - - - - - - -
MethodCRAP
loadBrandData5
booted3
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Models/index.html b/tests/coverage/Models/index.html deleted file mode 100644 index d3322c9..0000000 --- a/tests/coverage/Models/index.html +++ /dev/null @@ -1,440 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Models - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 80.00% covered (warning) -
-
-
80.00%
124 / 155
-
- 69.09% covered (warning) -
-
-
69.09%
38 / 55
-
- 58.33% covered (warning) -
-
-
58.33%
7 / 12
Answer.php
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Brand.php
-
- 50.00% covered (danger) -
-
-
50.00%
8 / 16
-
- 40.00% covered (danger) -
-
-
40.00%
4 / 10
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
BrandStat.php
-
- 25.00% covered (danger) -
-
-
25.00%
1 / 4
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 2
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
Customer.php
-
- 92.45% covered (success) -
-
-
92.45%
49 / 53
-
- 70.00% covered (warning) -
-
-
70.00%
7 / 10
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CustomerAnswer.php
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CustomerInfo.php
-
- 77.78% covered (warning) -
-
-
77.78%
14 / 18
-
- 66.67% covered (warning) -
-
-
66.67%
4 / 6
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
CustomerLogin.php
-
- 100.00% covered (success) -
-
-
100.00%
9 / 9
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CustomerOptInHistory.php
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Question.php
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
Service.php
-
- 70.73% covered (warning) -
-
-
70.73%
29 / 41
-
- 60.00% covered (warning) -
-
-
60.00%
6 / 10
-
- 0.00% covered (danger) -
-
-
0.00%
0 / 1
ServiceStat.php
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
SingleOptin.php
-
- 100.00% covered (success) -
-
-
100.00%
3 / 3
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
User.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
-
- -
- - diff --git a/tests/coverage/Policies/AnswerPolicy.php.html b/tests/coverage/Policies/AnswerPolicy.php.html deleted file mode 100644 index 7e4b71f..0000000 --- a/tests/coverage/Policies/AnswerPolicy.php.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/AnswerPolicy.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
AnswerPolicy
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 view
n/a
0 / 0
n/a
0 / 0
1
 viewAny
n/a
0 / 0
n/a
0 / 0
1
 create
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies;
4
5use App\Policies\Rules\UserStaticRules as USR;
6
7/**
8 * @codeCoverageIgnore
9 */
10class AnswerPolicy
11{
12    /**
13     * View an Answer.
14     *
15     * @return boolean
16     */
17    public function view(): bool
18    {
19        return USR::noOne();
20    }
21
22    /**
23     * viewAny an Answer.
24     * @return boolean
25     */
26    public function viewAny(): bool
27    {
28        return USR::everyOne();
29    }
30
31    /**
32     * Create an Answer.
33     *
34     * @return boolean
35     */
36    public function create(): bool
37    {
38        return USR::everyOne();
39    }
40
41    /**
42     * Update an Answer.
43     *
44     * @return boolean
45     */
46    public function update(): bool
47    {
48        return USR::everyOne();
49    }
50
51    /**
52     * Delete an Answer.
53     *
54     * @return boolean
55     */
56    public function delete(): bool
57    {
58        return USR::everyOne();
59    }
60}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/BrandPolicy.php.html b/tests/coverage/Policies/BrandPolicy.php.html deleted file mode 100644 index 04b7b78..0000000 --- a/tests/coverage/Policies/BrandPolicy.php.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/BrandPolicy.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
BrandPolicy
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 view
n/a
0 / 0
n/a
0 / 0
1
 viewAny
n/a
0 / 0
n/a
0 / 0
1
 create
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies;
4
5use App\Policies\Rules\UserStaticRules as USR;
6
7/**
8 * @codeCoverageIgnore
9 */
10class BrandPolicy
11{
12    /**
13     * View a Brand.
14     *
15     * @return boolean
16     */
17    public function view(): bool
18    {
19        return USR::noOne();
20    }
21
22    /**
23     * viewAny a Brand.
24     * @return boolean
25     */
26    public function viewAny(): bool
27    {
28        return USR::everyOne();
29    }
30
31    /**
32     * Create a Brand.
33     *
34     * @return boolean
35     */
36    public function create(): bool
37    {
38        return USR::everyOne();
39    }
40
41    /**
42     * Update a Brand.
43     *
44     * @return boolean
45     */
46    public function update(): bool
47    {
48        return USR::everyOne();
49    }
50
51    /**
52     * Delete a Brand.
53     *
54     * @return boolean
55     */
56    public function delete(): bool
57    {
58        return USR::noOne();
59    }
60}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/CustomerPolicy.php.html b/tests/coverage/Policies/CustomerPolicy.php.html deleted file mode 100644 index ec07667..0000000 --- a/tests/coverage/Policies/CustomerPolicy.php.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/CustomerPolicy.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
CustomerPolicy
n/a
0 / 0
n/a
0 / 0
6
n/a
0 / 0
 view
n/a
0 / 0
n/a
0 / 0
1
 viewAny
n/a
0 / 0
n/a
0 / 0
1
 create
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
1
 customerBelongsToService
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies;
4
5use App\Models\Customer;
6use App\Models\Service;
7use Illuminate\Auth\Access\HandlesAuthorization;
8use App\Policies\Rules\UserStaticRules as USR;
9
10/**
11 * @codeCoverageIgnore
12 */
13class CustomerPolicy
14{
15    use HandlesAuthorization;
16
17    /**
18     * View a Customer.
19     *
20     * @return boolean
21     */
22    public function view(): bool
23    {
24        return USR::everyOne();
25    }
26
27    /**
28     * View any a Customer.
29     *
30     * @return boolean
31     */
32    public function viewAny(): bool
33    {
34        return USR::everyOne();
35    }
36
37    /**
38     * Create a Customer.
39     *
40     * @return boolean
41     */
42    public function create(): bool
43    {
44        return USR::noOne();
45    }
46
47    /**
48     * Update a Customer.
49     *
50     * @return boolean
51     */
52    public function update(): bool
53    {
54        return USR::noOne();
55    }
56
57    /**
58     * Delete a Customer.
59     *
60     * @return boolean
61     */
62    public function delete(): bool
63    {
64        return USR::everyOne();
65    }
66
67    /**
68     * Check if the customer belongs to the service
69     *
70     * @param \App\Models\Service  $service
71     * @param \App\Models\Customer $customer
72     * @return boolean
73     */
74    public function customerBelongsToService(Service $service, Customer $customer)
75    {
76        return !\is_null($customer->login($service->brand));
77    }
78}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/QuestionPolicy.php.html b/tests/coverage/Policies/QuestionPolicy.php.html deleted file mode 100644 index 9db5ac2..0000000 --- a/tests/coverage/Policies/QuestionPolicy.php.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/QuestionPolicy.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
QuestionPolicy
n/a
0 / 0
n/a
0 / 0
6
n/a
0 / 0
 view
n/a
0 / 0
n/a
0 / 0
1
 viewAny
n/a
0 / 0
n/a
0 / 0
1
 create
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
1
 questionBelongsToService
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies;
4
5use App\Models\Question;
6use App\Models\Service;
7use App\Policies\Rules\UserStaticRules as USR;
8
9/**
10 * @codeCoverageIgnore
11 */
12class QuestionPolicy
13{
14    /**
15     * View a Question.
16     *
17     * @return boolean
18     */
19    public function view(): bool
20    {
21        return USR::everyOne();
22    }
23
24    /**
25     * viewAny a Question.
26     * @return boolean
27     */
28    public function viewAny(): bool
29    {
30        return USR::everyOne();
31    }
32
33    /**
34     * Create a Question.
35     *
36     * @return boolean
37     */
38    public function create(): bool
39    {
40        return USR::everyOne();
41    }
42
43    /**
44     * Update a Question.
45     *
46     * @return boolean
47     */
48    public function update(): bool
49    {
50        return USR::everyOne();
51    }
52
53    /**
54     * Delete a Question.
55     *
56     * @return boolean
57     */
58    public function delete(): bool
59    {
60        return USR::everyOne();
61    }
62
63    /**
64     * Check if the question belongs to the service
65     *
66     * @param \App\Models\Service  $service
67     * @param \App\Models\Question $question
68     * @return boolean
69     */
70    public function questionBelongsToService(Service $service, Question $question)
71    {
72        return $service->brand->id === $question->brand_id;
73    }
74}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/Rules/UserStaticRules.php.html b/tests/coverage/Policies/Rules/UserStaticRules.php.html deleted file mode 100644 index d59976c..0000000 --- a/tests/coverage/Policies/Rules/UserStaticRules.php.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/Rules/UserStaticRules.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
UserStaticRules
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 everyOne
n/a
0 / 0
n/a
0 / 0
1
 noOne
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies\Rules;
4
5/**
6 * Userlevel Policiy Static Rules
7 *
8 * @codeCoverageIgnore
9 */
10abstract class UserStaticRules
11{
12    /**
13     * Allow everyone
14     *
15     * @return boolean
16     */
17    public static function everyOne(): bool
18    {
19        return true;
20    }
21    /**
22     * Allow no one
23     *
24     * @return boolean
25     */
26    public static function noOne(): bool
27    {
28        return false;
29    }
30}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/ServicePolicy.php.html b/tests/coverage/Policies/ServicePolicy.php.html deleted file mode 100644 index fc80965..0000000 --- a/tests/coverage/Policies/ServicePolicy.php.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/ServicePolicy.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ServicePolicy
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 view
n/a
0 / 0
n/a
0 / 0
1
 viewAny
n/a
0 / 0
n/a
0 / 0
1
 create
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies;
4
5use App\Policies\Rules\UserStaticRules as USR;
6
7/**
8 * @codeCoverageIgnore
9 */
10class ServicePolicy
11{
12    /**
13     * View an Answer.
14     *
15     * @return boolean
16     */
17    public function view(): bool
18    {
19        return USR::everyOne();
20    }
21
22    /**
23     * viewAny an Service.
24     *
25     * @return boolean
26     */
27    public function viewAny(): bool
28    {
29        return USR::everyOne();
30    }
31
32    /**
33     * Create an Service.
34     *
35     * @return boolean
36     */
37    public function create(): bool
38    {
39        return USR::everyOne();
40    }
41
42    /**
43     * Update an Service.
44     *
45     * @return boolean
46     */
47    public function update(): bool
48    {
49        return USR::everyOne();
50    }
51
52    /**
53     * Delete an Service.
54     *
55     * @return boolean
56     */
57    public function delete(): bool
58    {
59        return USR::noOne();
60    }
61}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/SingleOptinPolicy.php.html b/tests/coverage/Policies/SingleOptinPolicy.php.html deleted file mode 100644 index ee0c01c..0000000 --- a/tests/coverage/Policies/SingleOptinPolicy.php.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Policies/SingleOptinPolicy.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
SingleOptinPolicy
n/a
0 / 0
n/a
0 / 0
5
n/a
0 / 0
 view
n/a
0 / 0
n/a
0 / 0
1
 viewAny
n/a
0 / 0
n/a
0 / 0
1
 create
n/a
0 / 0
n/a
0 / 0
1
 update
n/a
0 / 0
n/a
0 / 0
1
 delete
n/a
0 / 0
n/a
0 / 0
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Policies;
4
5use App\Policies\Rules\UserStaticRules as USR;
6
7/**
8 * @codeCoverageIgnore
9 */
10class SingleOptinPolicy
11{
12    /**
13     * View an Answer.
14     *
15     * @return boolean
16     */
17    public function view(): bool
18    {
19        return USR::noOne();
20    }
21
22    /**
23     * viewAny an Answer.
24     * @return boolean
25     */
26    public function viewAny(): bool
27    {
28        return USR::everyOne();
29    }
30
31    /**
32     * Create an Answer.
33     *
34     * @return boolean
35     */
36    public function create(): bool
37    {
38        return USR::noOne();
39    }
40
41    /**
42     * Update an Answer.
43     *
44     * @return boolean
45     */
46    public function update(): bool
47    {
48        return USR::noOne();
49    }
50
51    /**
52     * Delete an Answer.
53     *
54     * @return boolean
55     */
56    public function delete(): bool
57    {
58        return USR::everyOne();
59    }
60}
- - - -
- - - - - - diff --git a/tests/coverage/Policies/dashboard.html b/tests/coverage/Policies/dashboard.html deleted file mode 100644 index fb914e4..0000000 --- a/tests/coverage/Policies/dashboard.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Policies - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Providers/AppServiceProvider.php.html b/tests/coverage/Providers/AppServiceProvider.php.html deleted file mode 100644 index de4aea9..0000000 --- a/tests/coverage/Providers/AppServiceProvider.php.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers/AppServiceProvider.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
AppServiceProvider
n/a
0 / 0
n/a
0 / 0
2
n/a
0 / 0
 boot
n/a
0 / 0
n/a
0 / 0
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Providers;
4
5use Illuminate\Cache\RateLimiting\Limit;
6use Illuminate\Http\Resources\Json\JsonResource;
7use Illuminate\Support\Carbon;
8use Illuminate\Support\Facades\App;
9use Illuminate\Support\Facades\RateLimiter;
10use Illuminate\Support\Facades\URL;
11use Illuminate\Support\ServiceProvider;
12
13/**
14 * @codeCoverageIgnore
15 */
16class AppServiceProvider extends ServiceProvider
17{
18    /**
19     * Bootstrap any application services.
20     *
21     * @return void
22     */
23    public function boot()
24    {
25        // * STATS jobs Rate Limiting.
26        RateLimiter::for('stats', function () {
27            return Limit::perMinute(60)->by(2);
28        });
29
30        // ! Important pourra etre retiré quand la V1 sera supprimé
31        // empeche l'encapsulation dans data
32        JsonResource::withoutWrapping();
33
34        // * LOCAL HTTPS FIX.
35        URL::forceScheme('https');
36
37        // * HA PROXY FIX.
38        App::singleton('clientIp', function () {
39            if (config('app.env') === 'local') {
40                return request()->getClientIp();
41            } else {
42                return request()->server('HTTP_X_FORWARDED_FOR') ??
43                request()->getClientIp();
44            }
45        });
46        // * LOCAL FORCE eg: for Carbon.
47        setlocale(LC_ALL, \sprintf(
48            '%s_%s.UTF-8',
49            config('app.locale'),
50            \strtoupper(config('app.locale'))
51        ));
52        Carbon::setLocale(config('app.locale'));
53    }
54}
- - - -
- - - - - - diff --git a/tests/coverage/Providers/AuthServiceProvider.php.html b/tests/coverage/Providers/AuthServiceProvider.php.html deleted file mode 100644 index 7933d6c..0000000 --- a/tests/coverage/Providers/AuthServiceProvider.php.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers/AuthServiceProvider.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
15 / 15
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
AuthServiceProvider
-
- 100.00% covered (success) -
-
-
100.00%
15 / 15
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 boot
-
- 100.00% covered (success) -
-
-
100.00%
15 / 15
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Providers;
4
5use App\Lib\Helper;
6use App\Models\Service;
7use Illuminate\Database\Eloquent\ModelNotFoundException;
8use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
9use Illuminate\Http\Request;
10use Illuminate\Support\Facades\Auth;
11
12class AuthServiceProvider extends ServiceProvider
13{
14    /**
15     * The policy mappings for the application.
16     *
17     * @var array
18     */
19    protected $policies = [];
20
21    /**
22     * Register any authentication / authorization services.
23     *
24     * @return void
25     */
26    public function boot()
27    {
28        // Register policies.
29        $this->registerPolicies();
30
31        Auth::viaRequest('api-hmac', function (Request $request) {
32
33            /**
34             * Un token devrais Ãªtre fourni pour chaque requete
35             */
36            if (is_null($request->bearer_token)) {
37                return null;
38            }
39
40            // * Si la route actuelle n'as pas de nom alors on refuse de la traiter.
41            if (!(Helper::getRoute($request))) {
42                // @codeCoverageIgnoreStart
43                return null;
44                // @codeCoverageIgnoreEnd
45            }
46
47            $service = null;
48
49            try {
50                // * On récupère le service actuel correspondant au token fourni.
51                $service = Service::getFromBearerToken($request->bearer_token ?? '');
52            } catch (ModelNotFoundException $e) {
53                return null;
54            }
55
56            // * Preload the brand as we will access it often later.
57            $service = $service->load('brand');
58
59            /**
60             ** All ApiV2 routes must pass Brand model to controller action since a Brand owns
61             ** a service token, otherwise the api cannot be called.
62             ** This hack is a custom binder instead of passing
63             ** this to route as a parameter
64             */
65            app()->bind(Service::class, function () use ($service) {
66                return $service;
67            });
68
69            // * On authenticate la request en vérifiant sa signature.
70            if ($service->authenticateApi($request)) {
71                return $service;
72            }
73
74            return null;
75        });
76    }
77}
- - - -
- - - - - - diff --git a/tests/coverage/Providers/EventServiceProvider.php.html b/tests/coverage/Providers/EventServiceProvider.php.html deleted file mode 100644 index 26d5e54..0000000 --- a/tests/coverage/Providers/EventServiceProvider.php.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers/EventServiceProvider.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
EventServiceProvider
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Providers;
4
5use Illuminate\Auth\Events\Registered;
6use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
7use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
8
9/**
10 * codeCoverageIgnore
11 */
12class EventServiceProvider extends ServiceProvider
13{
14    /**
15     * The event listener mappings for the application.
16     *
17     * @var array
18     */
19    protected $listen = [
20        Registered::class => [
21            SendEmailVerificationNotification::class,
22        ],
23    ];
24}
- - - -
- - - - - - diff --git a/tests/coverage/Providers/RouteServiceProvider.php.html b/tests/coverage/Providers/RouteServiceProvider.php.html deleted file mode 100644 index 1a79682..0000000 --- a/tests/coverage/Providers/RouteServiceProvider.php.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers/RouteServiceProvider.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
27 / 27
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
RouteServiceProvider
-
- 100.00% covered (success) -
-
-
100.00%
27 / 27
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
7
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 boot
-
- 100.00% covered (success) -
-
-
100.00%
23 / 23
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
5
 configureRateLimiting
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Providers;
4
5use App\Models\Answer;
6use App\Models\Brand;
7use App\Models\Customer;
8use App\Models\CustomerInfo;
9use App\Models\Question;
10use App\Models\Service;
11use App\Models\SingleOptin;
12use Illuminate\Cache\RateLimiting\Limit;
13use Illuminate\Database\Eloquent\ModelNotFoundException;
14use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
15use Illuminate\Http\Request;
16use Illuminate\Support\Facades\RateLimiter;
17use Illuminate\Support\Facades\Route;
18
19class RouteServiceProvider extends ServiceProvider
20{
21    /**
22     * The path to the "home" route for your application.
23     *
24     * This is used by Laravel authentication to redirect users after login.
25     *
26     * @var string
27     */
28    public const HOME = '/customers';
29
30    /**
31     * Define your route model bindings, pattern filters, etc.
32     *
33     * @return void
34     */
35    public function boot()
36    {
37        $this->configureRateLimiting();
38
39        $this->routes(function () {
40            // @codeCoverageIgnoreStart
41            Route::prefix('api')->middleware('api')->namespace($this->namespace)->group(base_path('routes/api.php'));
42            Route::middleware('web')->namespace($this->namespace)->group(base_path('routes/web.php'));
43            // @codeCoverageIgnoreEnd
44        });
45
46        Route::model('brand', Brand::class);
47        Route::model('service', Service::class);
48        Route::model('single_optin', SingleOptin::class);
49        Route::model('question', Question::class);
50        Route::model('answer', Answer::class);
51        Route::model('customer', Customer::class);
52
53        // * Api parameters Bind.
54        Route::model('serviceQuestion', Question::class);
55        Route::bind('serviceCustomer', function ($value) {
56            // * Load brand
57            /** @var \App\Models\Brand $brand */
58            $brand = app(Service::class)->brand;
59
60            // ? Parameter is email
61            if (strpos($value, '@') !== false) {
62                $customer = Customer::whereHas('brands', function ($q) use ($brand) {
63                    $q->where('brands.id', '=', $brand->id);
64                })->where('email', '=', $value)->firstOrFail();
65            }
66
67            try {
68                // ? Have we found a customer yet
69                if (!isset($customer) or \get_class($customer) !== Customer::class) {
70                    // ? Parameter is hashcode
71                    $customer = CustomerInfo::whereHas('brand', function ($q) use ($brand) {
72                        $q->where('brands.id', '=', $brand->id);
73                    })->where('hashcode', $value)
74                        ->with('customer')->firstOrFail()->customer;
75                }
76
77                // * Load Customer Login and Info for this brand.
78                /** @var \App\Models\Customer $customer */
79
80                return $customer->loadBrandData($brand);
81            } catch (ModelNotFoundException $e) {
82                // * Uniform Not found exception using model Customer.
83                throw (new ModelNotFoundException())->setModel(Customer::class);
84            }
85        });
86    }
87
88    /**
89     * Configure the rate limiters for the application.
90     *
91     * @return void
92     */
93    protected function configureRateLimiting()
94    {
95        RateLimiter::for('web', function (Request $request) {
96            // @codeCoverageIgnoreStart
97            return Limit::perMinute(25)
98                ->by(optional($request->user())->id ?: app('clientIp'));
99            // @codeCoverageIgnoreEnd
100        });
101        // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
102        RateLimiter::for('api', function (Request $request) {
103            return Limit::perMinute(config('api.apiRequestRatePerMinute', 100))
104            ->by(app('clientIp'));
105        });
106    }
107}
- - - -
- - - - - - diff --git a/tests/coverage/Providers/TranslationServiceProvider.php.html b/tests/coverage/Providers/TranslationServiceProvider.php.html deleted file mode 100644 index af92658..0000000 --- a/tests/coverage/Providers/TranslationServiceProvider.php.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers/TranslationServiceProvider.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
18 / 18
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
TranslationServiceProvider
-
- 100.00% covered (success) -
-
-
100.00%
18 / 18
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
8
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 boot
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
 collectLocalesStrings
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
2
 phpTranslations
-
- 100.00% covered (success) -
-
-
100.00%
5 / 5
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
 jsonTranslations
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
3
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Providers;
4
5use Illuminate\Support\Collection;
6use Illuminate\Support\Facades\File;
7use Illuminate\Support\Facades\Cache;
8use Illuminate\Support\ServiceProvider;
9
10class TranslationServiceProvider extends ServiceProvider
11{
12    /**
13     * Bootstrap the application services.
14     *
15     * @return void
16     */
17    public function boot()
18    {
19        foreach ($this->collectLocalesStrings() as $locale) {
20            // * Suported locales
21            Cache::rememberForever(sprintf('translations.%s', $locale), function () use ($locale) {
22                $translations = [
23                    'php' => $this->phpTranslations($locale),
24                    'json' => $this->jsonTranslations($locale),
25                ];
26                return $translations;
27            });
28        }
29    }
30
31    /**
32     * Gather provided langs checking from files
33     *
34     * @return \Illuminate\Support\Collection
35     */
36    private function collectLocalesStrings(): Collection
37    {
38        return collect(File::allFiles(resource_path('lang/')))->flatMap(function ($file) {
39            if ($file->getRelativePath()) {
40                return [$file->getRelativePath() => ''];
41            }
42        })->keys();
43    }
44
45    /**
46     * Gather all php translations
47     *
48     * @param string $locale
49     * @return array
50     */
51    private function phpTranslations(string $locale): array
52    {
53        $path = resource_path("lang/$locale");
54
55        return collect(File::allFiles($path))->flatMap(function ($file) use ($locale) {
56            $translation = $file->getBasename('.php');
57            return [$translation => trans($translation, [], $locale)];
58        })->all();
59    }
60
61    /**
62     * Gather json translations
63     *
64     * @param string $locale
65     * @return array
66     */
67    private function jsonTranslations(string $locale): array
68    {
69        $path = resource_path("lang/$locale.json");
70
71        if (is_string($path) && is_readable($path)) {
72            return json_decode(file_get_contents($path), true) ?? [];
73        }
74
75        return [];
76    }
77}
- - - -
- - - - - - diff --git a/tests/coverage/Providers/ViewServiceProvider.php.html b/tests/coverage/Providers/ViewServiceProvider.php.html deleted file mode 100644 index a7f314c..0000000 --- a/tests/coverage/Providers/ViewServiceProvider.php.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers/ViewServiceProvider.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
CRAP
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ViewServiceProvider
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
 boot
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
1
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Providers;
4
5use Illuminate\Pagination\Paginator;
6use Illuminate\Support\Facades\Cache;
7use Illuminate\Support\Facades\File;
8use Illuminate\Support\Facades\View;
9use Illuminate\Support\ServiceProvider;
10
11class ViewServiceProvider extends ServiceProvider
12{
13    /**
14     * Bootstrap any application services.
15     *
16     * @return void
17     */
18    public function boot(): void
19    {
20        // * Set default view of the paginator.
21        Paginator::defaultView('modules.pagination');
22
23        // * Shares this data with all views.
24        // Get data from composer.json.
25        $appInfos = Cache::remember('composer', 360, function () {
26            return json_decode(File::get(\app_path('../composer.json')));
27        });
28        View::share('name', $appInfos->name);
29        View::share('version', $appInfos->version);
30        View::share('license', $appInfos->license);
31    }
32}
- - - -
- - - - - - diff --git a/tests/coverage/Providers/dashboard.html b/tests/coverage/Providers/dashboard.html deleted file mode 100644 index bb5c013..0000000 --- a/tests/coverage/Providers/dashboard.html +++ /dev/null @@ -1,284 +0,0 @@ - - - - - Dashboard for /home/jeremy/projets/crm/app/Providers - - - - - - - -
-
-
-
- -
-
-
-
-
-
-
-

Classes

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
ClassCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
ClassCRAP
-
-
-
-
-
-

Methods

-
-
-
-
-

Coverage Distribution

-
- -
-
-
-

Complexity

-
- -
-
-
-
-
-

Insufficient Coverage

-
- - - - - - - - - - -
MethodCoverage
-
-
-
-

Project Risks

-
- - - - - - - - - - -
MethodCRAP
-
-
-
- -
- - - - - - diff --git a/tests/coverage/Providers/index.html b/tests/coverage/Providers/index.html deleted file mode 100644 index f94f592..0000000 --- a/tests/coverage/Providers/index.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - Code Coverage for /home/jeremy/projets/crm/app/Providers - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
-
- 100.00% covered (success) -
-
-
100.00%
66 / 66
-
- 100.00% covered (success) -
-
-
100.00%
8 / 8
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
AppServiceProvider.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
AuthServiceProvider.php
-
- 100.00% covered (success) -
-
-
100.00%
15 / 15
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
EventServiceProvider.php
n/a
0 / 0
n/a
0 / 0
n/a
0 / 0
RouteServiceProvider.php
-
- 100.00% covered (success) -
-
-
100.00%
27 / 27
-
- 100.00% covered (success) -
-
-
100.00%
2 / 2
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
TranslationServiceProvider.php
-
- 100.00% covered (success) -
-
-
100.00%
18 / 18
-
- 100.00% covered (success) -
-
-
100.00%
4 / 4
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
ViewServiceProvider.php
-
- 100.00% covered (success) -
-
-
100.00%
6 / 6
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- 100.00% covered (success) -
-
-
100.00%
1 / 1
-
- -
- - diff --git a/tests/coverage/_css/bootstrap.min.css b/tests/coverage/_css/bootstrap.min.css index 6ee5956..83a71b1 100644 --- a/tests/coverage/_css/bootstrap.min.css +++ b/tests/coverage/_css/bootstrap.min.css @@ -1,7 +1,7 @@ /*! - * Bootstrap v4.6.1 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:.875em;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-sm-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-md-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-md-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-md-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-md-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-md-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-lg-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.row-cols-xl-1>*{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{-ms-flex:0 0 20%;flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;font-size:1rem;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#28a745}.valid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;left:0;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem)!important;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{padding-right:3rem!important;background-position:right 1.5rem center}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc(.75em + 2.3125rem)!important;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#0069d9;border-color:#0062cc;box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{color:#fff;background-color:#5a6268;border-color:#545b62;box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#218838;border-color:#1e7e34;box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#138496;border-color:#117a8b;box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{color:#212529;background-color:#e0a800;border-color:#d39e00;box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c82333;border-color:#bd2130;box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{color:#212529;background-color:#e2e6ea;border-color:#dae0e5;box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{color:#fff;background-color:#23272b;border-color:#1d2124;box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label::after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label::after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;z-index:1;display:block;min-height:1.5rem;padding-left:1.5rem;-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;left:0;z-index:-1;width:1rem;height:1.25rem;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before,.custom-control-input[disabled]~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:1px solid #adb5bd}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;overflow:hidden;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;overflow:hidden;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:1.4rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:50%/100% 100% no-repeat}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{-ms-flex-negative:0;flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{-ms-flex:1 0 0%;flex:1 0 0%;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:3;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;z-index:2;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;line-height:0;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0}a.close.disabled{pointer-events:none}.toast{-ms-flex-preferred-size:350px;flex-basis:350px;max-width:350px;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);height:-webkit-min-content;height:-moz-min-content;height:min-content;content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem);height:-webkit-min-content;height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:50%/100% 100% no-repeat}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentcolor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentcolor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;word-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} /*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/tests/coverage/_css/style.css b/tests/coverage/_css/style.css index 0834b5b..4d2766f 100644 --- a/tests/coverage/_css/style.css +++ b/tests/coverage/_css/style.css @@ -1,4 +1,9 @@ body { + font-family: sans-serif; + font-size: 1em; + font-kerning: normal; + font-variant-ligatures: common-ligatures; + text-rendering: optimizeLegibility; padding-top: 10px; } @@ -8,6 +13,8 @@ body { .octicon { margin-right:.25em; + vertical-align: baseline; + width: 0.75em; } .table-bordered>thead>tr>td { @@ -44,19 +51,20 @@ body { background-color: #99cb84; } -.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { - background-color: #f2dede; -} - .table tbody tr.warning, .table tbody td.warning, li.warning, span.warning { background-color: #fcf8e3; } +.table tbody tr.danger, .table tbody td.danger, li.danger, span.danger { + background-color: #f2dede; +} + .table tbody td.info { background-color: #d9edf7; } td.big { + vertical-align: middle; width: 117px; } @@ -120,3 +128,31 @@ table + .structure-heading { border-top: 1px solid lightgrey; padding-top: 0.5em; } + +.legend { + font-weight: bold; + margin-right: 2px; + padding-left: 10px; + padding-right: 10px; + text-align: center; +} + +.covered-by-small-tests { + background-color: #99cb84; +} + +.covered-by-medium-tests { + background-color: #c3e3b5; +} + +.covered-by-large-tests { + background-color: #dff0d8; +} + +.not-covered { + background-color: #f2dede; +} + +.not-coverable { + background-color: #fcf8e3; +} diff --git a/tests/coverage/_js/bootstrap.min.js b/tests/coverage/_js/bootstrap.min.js index 50720ea..97206dc 100644 --- a/tests/coverage/_js/bootstrap.min.js +++ b/tests/coverage/_js/bootstrap.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v4.6.1 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=i(e),a=i(n);function s(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};d.jQueryDetection(),o.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return o.default(this).one(d.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||d.triggerTransitionEnd(e)}),t),this},o.default.event.special[d.TRANSITION_END]={bindType:f,delegateType:f,handle:function(t){if(o.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var c="bs.alert",h=o.default.fn.alert,g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.default.removeData(this._element,c),this._element=null},e._getRootElement=function(t){var e=d.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=o.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=o.default.Event("close.bs.alert");return o.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(o.default(t).removeClass("show"),o.default(t).hasClass("fade")){var n=d.getTransitionDurationFromElement(t);o.default(t).one(d.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){o.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(c);i||(i=new t(this),n.data(c,i)),"close"===e&&i[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();o.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',g._handleDismiss(new g)),o.default.fn.alert=g._jQueryInterface,o.default.fn.alert.Constructor=g,o.default.fn.alert.noConflict=function(){return o.default.fn.alert=h,g._jQueryInterface};var m="bs.button",p=o.default.fn.button,_="active",v='[data-toggle^="button"]',y='input:not([type="hidden"])',b=".btn",E=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=o.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var i=this._element.querySelector(y);if(i){if("radio"===i.type)if(i.checked&&this._element.classList.contains(_))t=!1;else{var a=n.querySelector(".active");a&&o.default(a).removeClass(_)}t&&("checkbox"!==i.type&&"radio"!==i.type||(i.checked=!this._element.classList.contains(_)),this.shouldAvoidTriggerChange||o.default(i).trigger("change")),i.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(_)),t&&o.default(this._element).toggleClass(_))},e.dispose=function(){o.default.removeData(this._element,m),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var i=o.default(this),a=i.data(m);a||(a=new t(this),i.data(m,a)),a.shouldAvoidTriggerChange=n,"toggle"===e&&a[e]()}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();o.default(document).on("click.bs.button.data-api",v,(function(t){var e=t.target,n=e;if(o.default(e).hasClass("btn")||(e=o.default(e).closest(b)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var i=e.querySelector(y);if(i&&(i.hasAttribute("disabled")||i.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||E._jQueryInterface.call(o.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",v,(function(t){var e=o.default(t.target).closest(b)[0];o.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),o.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(N)},e.nextWhenVisible=function(){var t=o.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(D)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(d.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(I);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)o.default(this._element).one(A,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?N:D;this._slide(i,this._items[t])}},e.dispose=function(){o.default(this._element).off(".bs.carousel"),o.default.removeData(this._element,w),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},k,t),d.typeCheckConfig(T,t,O),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&o.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&o.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};o.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(o.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(o.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),o.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===N,i=t===D,o=this._getItemIndex(e),a=this._items.length-1;if((i&&0===o||n&&o===a)&&!this._config.wrap)return e;var s=(o+(t===D?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(this._element.querySelector(I)),a=o.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n});return o.default(this._element).trigger(a),a},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));o.default(e).removeClass(S);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&o.default(n).addClass(S)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(I);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,i,a,s=this,l=this._element.querySelector(I),r=this._getItemIndex(l),u=e||l&&this._getItemByDirection(t,l),f=this._getItemIndex(u),c=Boolean(this._interval);if(t===N?(n="carousel-item-left",i="carousel-item-next",a="left"):(n="carousel-item-right",i="carousel-item-prev",a="right"),u&&o.default(u).hasClass(S))this._isSliding=!1;else if(!this._triggerSlideEvent(u,a).isDefaultPrevented()&&l&&u){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(u),this._activeElement=u;var h=o.default.Event(A,{relatedTarget:u,direction:a,from:r,to:f});if(o.default(this._element).hasClass("slide")){o.default(u).addClass(i),d.reflow(u),o.default(l).addClass(n),o.default(u).addClass(n);var g=d.getTransitionDurationFromElement(l);o.default(l).one(d.TRANSITION_END,(function(){o.default(u).removeClass(n+" "+i).addClass(S),o.default(l).removeClass("active "+i+" "+n),s._isSliding=!1,setTimeout((function(){return o.default(s._element).trigger(h)}),0)})).emulateTransitionEnd(g)}else o.default(l).removeClass(S),o.default(u).addClass(S),this._isSliding=!1,o.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(w),i=r({},k,o.default(this).data());"object"==typeof e&&(i=r({},i,e));var a="string"==typeof e?e:i.slide;if(n||(n=new t(this,i),o.default(this).data(w,n)),"number"==typeof e)n.to(e);else if("string"==typeof a){if("undefined"==typeof n[a])throw new TypeError('No method named "'+a+'"');n[a]()}else i.interval&&i.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=d.getSelectorFromElement(this);if(n){var i=o.default(n)[0];if(i&&o.default(i).hasClass("carousel")){var a=r({},o.default(i).data(),o.default(this).data()),s=this.getAttribute("data-slide-to");s&&(a.interval=!1),t._jQueryInterface.call(o.default(i),a),s&&o.default(i).data(w).to(s),e.preventDefault()}}},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return k}}]),t}();o.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",P._dataApiClickHandler),o.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=s,this._triggerArray.push(a))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){o.default(this._element).hasClass(q)?this.hide():this.show()},e.show=function(){var e,n,i=this;if(!(this._isTransitioning||o.default(this._element).hasClass(q)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof i._config.parent?t.getAttribute("data-parent")===i._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=o.default(e).not(this._selector).data(R))&&n._isTransitioning))){var a=o.default.Event("show.bs.collapse");if(o.default(this._element).trigger(a),!a.isDefaultPrevented()){e&&(t._jQueryInterface.call(o.default(e).not(this._selector),"hide"),n||o.default(e).data(R,null));var s=this._getDimension();o.default(this._element).removeClass(F).addClass(Q),this._element.style[s]=0,this._triggerArray.length&&o.default(this._triggerArray).removeClass(B).attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(s[0].toUpperCase()+s.slice(1)),r=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,(function(){o.default(i._element).removeClass(Q).addClass("collapse show"),i._element.style[s]="",i.setTransitioning(!1),o.default(i._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(r),this._element.style[s]=this._element[l]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&o.default(this._element).hasClass(q)){var e=o.default.Event("hide.bs.collapse");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",d.reflow(this._element),o.default(this._element).addClass(Q).removeClass("collapse show");var i=this._triggerArray.length;if(i>0)for(var a=0;a0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(K);if(n||(n=new t(this,"object"==typeof e?e:null),o.default(this).data(K,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(it)),i=0,a=n.length;i0&&s--,40===e.which&&sdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ht);var i=d.getTransitionDurationFromElement(this._dialog);o.default(this._element).off(d.TRANSITION_END),o.default(this._element).one(d.TRANSITION_END,(function(){t._element.classList.remove(ht),n||o.default(t._element).one(d.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,i)})).emulateTransitionEnd(i),this._element.focus()}},e._showElement=function(t){var e=this,n=o.default(this._element).hasClass(dt),i=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),o.default(this._dialog).hasClass("modal-dialog-scrollable")&&i?i.scrollTop=0:this._element.scrollTop=0,n&&d.reflow(this._element),o.default(this._element).addClass(ct),this._config.focus&&this._enforceFocus();var a=o.default.Event("shown.bs.modal",{relatedTarget:t}),s=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,o.default(e._element).trigger(a)};if(n){var l=d.getTransitionDurationFromElement(this._dialog);o.default(this._dialog).one(d.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._enforceFocus=function(){var t=this;o.default(document).off(pt).on(pt,(function(e){document!==e.target&&t._element!==e.target&&0===o.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?o.default(this._element).on(yt,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||o.default(this._element).off(yt)},e._setResizeEvent=function(){var t=this;this._isShown?o.default(window).on(_t,(function(e){return t.handleUpdate(e)})):o.default(window).off(_t)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){o.default(document.body).removeClass(ft),t._resetAdjustments(),t._resetScrollbar(),o.default(t._element).trigger(gt)}))},e._removeBackdrop=function(){this._backdrop&&(o.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=o.default(this._element).hasClass(dt)?dt:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),o.default(this._backdrop).appendTo(document.body),o.default(this._element).on(vt,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&d.reflow(this._backdrop),o.default(this._backdrop).addClass(ct),!t)return;if(!n)return void t();var i=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,t).emulateTransitionEnd(i)}else if(!this._isShown&&this._backdrop){o.default(this._backdrop).removeClass(ct);var a=function(){e._removeBackdrop(),t&&t()};if(o.default(this._element).hasClass(dt)){var s=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ut={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Mt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Wt=function(){function t(t,e){if("undefined"==typeof a.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=o.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(o.default(this.getTipElement()).hasClass(Rt))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),o.default.removeData(this.element,this.constructor.DATA_KEY),o.default(this.element).off(this.constructor.EVENT_KEY),o.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&o.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===o.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=o.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){o.default(this.element).trigger(e);var n=d.findShadowRoot(this.element),i=o.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!i)return;var s=this.getTipElement(),l=d.getUID(this.constructor.NAME);s.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&o.default(s).addClass(Lt);var r="function"==typeof this.config.placement?this.config.placement.call(this,s,this.element):this.config.placement,u=this._getAttachment(r);this.addAttachmentClass(u);var f=this._getContainer();o.default(s).data(this.constructor.DATA_KEY,this),o.default.contains(this.element.ownerDocument.documentElement,this.tip)||o.default(s).appendTo(f),o.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new a.default(this.element,s,this._getPopperConfig(u)),o.default(s).addClass(Rt),o.default(s).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&o.default(document.body).children().on("mouseover",null,o.default.noop);var c=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,o.default(t.element).trigger(t.constructor.Event.SHOWN),e===qt&&t._leave(null,t)};if(o.default(this.tip).hasClass(Lt)){var h=d.getTransitionDurationFromElement(this.tip);o.default(this.tip).one(d.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},e.hide=function(t){var e=this,n=this.getTipElement(),i=o.default.Event(this.constructor.Event.HIDE),a=function(){e._hoverState!==xt&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),o.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(o.default(this.element).trigger(i),!i.isDefaultPrevented()){if(o.default(n).removeClass(Rt),"ontouchstart"in document.documentElement&&o.default(document.body).children().off("mouseover",null,o.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,o.default(this.tip).hasClass(Lt)){var s=d.getTransitionDurationFromElement(n);o.default(n).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(o.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),o.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=At(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?o.default(e).parent().is(t)||t.empty().append(e):t.text(o.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:d.isElement(this.config.container)?o.default(this.config.container):o.default(document).find(this.config.container)},e._getAttachment=function(t){return Bt[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)o.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Ft?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i=e===Ft?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;o.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},o.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Qt:Ft]=!0),o.default(e.getTipElement()).hasClass(Rt)||e._hoverState===xt?e._hoverState=xt:(clearTimeout(e._timeout),e._hoverState=xt,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===xt&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Qt:Ft]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=qt,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===qt&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=o.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Pt.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d.typeCheckConfig(It,t,this.constructor.DefaultType),t.sanitize&&(t.template=At(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(jt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(o.default(t).removeClass(Lt),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(kt),a="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new t(this,a),n.data(kt,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Ht}},{key:"NAME",get:function(){return It}},{key:"DATA_KEY",get:function(){return kt}},{key:"Event",get:function(){return Mt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Ut}}]),t}();o.default.fn.tooltip=Wt._jQueryInterface,o.default.fn.tooltip.Constructor=Wt,o.default.fn.tooltip.noConflict=function(){return o.default.fn.tooltip=Ot,Wt._jQueryInterface};var Vt="bs.popover",zt=o.default.fn.popover,Kt=new RegExp("(^|\\s)bs-popover\\S+","g"),Xt=r({},Wt.Default,{placement:"right",trigger:"click",content:"",template:''}),Yt=r({},Wt.DefaultType,{content:"(string|element|function)"}),$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Jt=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),e.prototype.constructor=e,u(e,n);var a=i.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},a.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(Kt);null!==e&&e.length>0&&t.removeClass(e.join(""))},i._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(Vt),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n),o.default(this).data(Vt,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},l(i,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"Default",get:function(){return Xt}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Vt}},{key:"Event",get:function(){return $t}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Yt}}]),i}(Wt);o.default.fn.popover=Jt._jQueryInterface,o.default.fn.popover.Constructor=Jt,o.default.fn.popover.noConflict=function(){return o.default.fn.popover=zt,Jt._jQueryInterface};var Gt="scrollspy",Zt="bs.scrollspy",te=o.default.fn[Gt],ee="active",ne="position",ie=".nav, .list-group",oe={offset:10,method:"auto",target:""},ae={offset:"number",method:"string",target:"(string|element)"},se=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,o.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":ne,n="auto"===this._config.method?e:this._config.method,i=n===ne?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,a=d.getSelectorFromElement(t);if(a&&(e=document.querySelector(a)),e){var s=e.getBoundingClientRect();if(s.width||s.height)return[o.default(e)[n]().top+i,a]}return null})).filter((function(t){return t})).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){o.default.removeData(this._element,Zt),o.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},oe,"object"==typeof t&&t?t:{})).target&&d.isElement(t.target)){var e=o.default(t.target).attr("id");e||(e=d.getUID(Gt),o.default(t.target).attr("id",e)),t.target="#"+e}return d.typeCheckConfig(Gt,t,ae),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",ge=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&o.default(this._element).hasClass(ue)||o.default(this._element).hasClass("disabled"))){var e,n,i=o.default(this._element).closest(".nav, .list-group")[0],a=d.getSelectorFromElement(this._element);if(i){var s="UL"===i.nodeName||"OL"===i.nodeName?he:ce;n=(n=o.default.makeArray(o.default(i).find(s)))[n.length-1]}var l=o.default.Event("hide.bs.tab",{relatedTarget:this._element}),r=o.default.Event("show.bs.tab",{relatedTarget:n});if(n&&o.default(n).trigger(l),o.default(this._element).trigger(r),!r.isDefaultPrevented()&&!l.isDefaultPrevented()){a&&(e=document.querySelector(a)),this._activate(this._element,i);var u=function(){var e=o.default.Event("hidden.bs.tab",{relatedTarget:t._element}),i=o.default.Event("shown.bs.tab",{relatedTarget:n});o.default(n).trigger(e),o.default(t._element).trigger(i)};e?this._activate(e,e.parentNode,u):u()}}},e.dispose=function(){o.default.removeData(this._element,le),this._element=null},e._activate=function(t,e,n){var i=this,a=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?o.default(e).children(ce):o.default(e).find(he))[0],s=n&&a&&o.default(a).hasClass(fe),l=function(){return i._transitionComplete(t,a,n)};if(a&&s){var r=d.getTransitionDurationFromElement(a);o.default(a).removeClass(de).one(d.TRANSITION_END,l).emulateTransitionEnd(r)}else l()},e._transitionComplete=function(t,e,n){if(e){o.default(e).removeClass(ue);var i=o.default(e.parentNode).find("> .dropdown-menu .active")[0];i&&o.default(i).removeClass(ue),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}o.default(t).addClass(ue),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),d.reflow(t),t.classList.contains(fe)&&t.classList.add(de);var a=t.parentNode;if(a&&"LI"===a.nodeName&&(a=a.parentNode),a&&o.default(a).hasClass("dropdown-menu")){var s=o.default(t).closest(".dropdown")[0];if(s){var l=[].slice.call(s.querySelectorAll(".dropdown-toggle"));o.default(l).addClass(ue)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(le);if(i||(i=new t(this),n.data(le,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}}]),t}();o.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ge._jQueryInterface.call(o.default(this),"show")})),o.default.fn.tab=ge._jQueryInterface,o.default.fn.tab.Constructor=ge,o.default.fn.tab.noConflict=function(){return o.default.fn.tab=re,ge._jQueryInterface};var me="bs.toast",pe=o.default.fn.toast,_e="hide",ve="show",ye="showing",be="click.dismiss.bs.toast",Ee={animation:!0,autohide:!0,delay:500},Te={animation:"boolean",autohide:"boolean",delay:"number"},we=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=o.default.Event("show.bs.toast");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(ye),t._element.classList.add(ve),o.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(_e),d.reflow(this._element),this._element.classList.add(ye),this._config.animation){var i=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},e.hide=function(){if(this._element.classList.contains(ve)){var t=o.default.Event("hide.bs.toast");o.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(ve)&&this._element.classList.remove(ve),o.default(this._element).off(be),o.default.removeData(this._element,me),this._element=null,this._config=null},e._getConfig=function(t){return t=r({},Ee,o.default(this._element).data(),"object"==typeof t&&t?t:{}),d.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;o.default(this._element).on(be,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(_e),o.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(ve),this._config.animation){var n=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(me);if(i||(i=new t(this,"object"==typeof e&&e),n.data(me,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e](this)}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.1"}},{key:"DefaultType",get:function(){return Te}},{key:"Default",get:function(){return Ee}}]),t}();o.default.fn.toast=we._jQueryInterface,o.default.fn.toast.Constructor=we,o.default.fn.toast.noConflict=function(){return o.default.fn.toast=pe,we._jQueryInterface},t.Alert=g,t.Button=E,t.Carousel=P,t.Collapse=V,t.Dropdown=lt,t.Modal=Ct,t.Popover=Jt,t.Scrollspy=se,t.Tab=ge,t.Toast=we,t.Tooltip=Wt,t.Util=d,Object.defineProperty(t,"__esModule",{value:!0})})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap={},t.jQuery,t.Popper)}(this,(function(t,e,n){"use strict";function i(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var o=i(e),a=i(n);function s(t,e){for(var n=0;n=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};d.jQueryDetection(),o.default.fn.emulateTransitionEnd=function(t){var e=this,n=!1;return o.default(this).one(d.TRANSITION_END,(function(){n=!0})),setTimeout((function(){n||d.triggerTransitionEnd(e)}),t),this},o.default.event.special[d.TRANSITION_END]={bindType:f,delegateType:f,handle:function(t){if(o.default(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}};var c="bs.alert",h=o.default.fn.alert,g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.default.removeData(this._element,c),this._element=null},e._getRootElement=function(t){var e=d.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=o.default(t).closest(".alert")[0]),n},e._triggerCloseEvent=function(t){var e=o.default.Event("close.bs.alert");return o.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(o.default(t).removeClass("show"),o.default(t).hasClass("fade")){var n=d.getTransitionDurationFromElement(t);o.default(t).one(d.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){o.default(t).detach().trigger("closed.bs.alert").remove()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(c);i||(i=new t(this),n.data(c,i)),"close"===e&&i[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.alert.data-api",'[data-dismiss="alert"]',g._handleDismiss(new g)),o.default.fn.alert=g._jQueryInterface,o.default.fn.alert.Constructor=g,o.default.fn.alert.noConflict=function(){return o.default.fn.alert=h,g._jQueryInterface};var m="bs.button",p=o.default.fn.button,_="active",v='[data-toggle^="button"]',y='input:not([type="hidden"])',b=".btn",E=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=o.default(this._element).closest('[data-toggle="buttons"]')[0];if(n){var i=this._element.querySelector(y);if(i){if("radio"===i.type)if(i.checked&&this._element.classList.contains(_))t=!1;else{var a=n.querySelector(".active");a&&o.default(a).removeClass(_)}t&&("checkbox"!==i.type&&"radio"!==i.type||(i.checked=!this._element.classList.contains(_)),this.shouldAvoidTriggerChange||o.default(i).trigger("change")),i.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(_)),t&&o.default(this._element).toggleClass(_))},e.dispose=function(){o.default.removeData(this._element,m),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var i=o.default(this),a=i.data(m);a||(a=new t(this),i.data(m,a)),a.shouldAvoidTriggerChange=n,"toggle"===e&&a[e]()}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.button.data-api",v,(function(t){var e=t.target,n=e;if(o.default(e).hasClass("btn")||(e=o.default(e).closest(b)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var i=e.querySelector(y);if(i&&(i.hasAttribute("disabled")||i.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||E._jQueryInterface.call(o.default(e),"toggle","INPUT"===n.tagName)}})).on("focus.bs.button.data-api blur.bs.button.data-api",v,(function(t){var e=o.default(t.target).closest(b)[0];o.default(e).toggleClass("focus",/^focus(in)?$/.test(t.type))})),o.default(window).on("load.bs.button.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-toggle="buttons"] .btn')),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(N)},e.nextWhenVisible=function(){var t=o.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(D)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(".carousel-item-next, .carousel-item-prev")&&(d.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(I);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)o.default(this._element).one(A,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var i=t>n?N:D;this._slide(i,this._items[t])}},e.dispose=function(){o.default(this._element).off(".bs.carousel"),o.default.removeData(this._element,w),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},k,t),d.typeCheckConfig(T,t,O),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=40)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&o.default(this._element).on("keydown.bs.carousel",(function(e){return t._keydown(e)})),"hover"===this._config.pause&&o.default(this._element).on("mouseenter.bs.carousel",(function(e){return t.pause(e)})).on("mouseleave.bs.carousel",(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t._pointerEvent&&j[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),500+t._config.interval))};o.default(this._element.querySelectorAll(".carousel-item img")).on("dragstart.bs.carousel",(function(t){return t.preventDefault()})),this._pointerEvent?(o.default(this._element).on("pointerdown.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("pointerup.bs.carousel",(function(t){return n(t)})),this._element.classList.add("pointer-event")):(o.default(this._element).on("touchstart.bs.carousel",(function(t){return e(t)})),o.default(this._element).on("touchmove.bs.carousel",(function(e){return function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX}(e)})),o.default(this._element).on("touchend.bs.carousel",(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(".carousel-item")):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===N,i=t===D,o=this._getItemIndex(e),a=this._items.length-1;if((i&&0===o||n&&o===a)&&!this._config.wrap)return e;var s=(o+(t===D?-1:1))%this._items.length;return-1===s?this._items[this._items.length-1]:this._items[s]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),i=this._getItemIndex(this._element.querySelector(I)),a=o.default.Event("slide.bs.carousel",{relatedTarget:t,direction:e,from:i,to:n});return o.default(this._element).trigger(a),a},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(".active"));o.default(e).removeClass(S);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&o.default(n).addClass(S)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(I);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,i,a,s=this,l=this._element.querySelector(I),r=this._getItemIndex(l),u=e||l&&this._getItemByDirection(t,l),f=this._getItemIndex(u),c=Boolean(this._interval);if(t===N?(n="carousel-item-left",i="carousel-item-next",a="left"):(n="carousel-item-right",i="carousel-item-prev",a="right"),u&&o.default(u).hasClass(S))this._isSliding=!1;else if(!this._triggerSlideEvent(u,a).isDefaultPrevented()&&l&&u){this._isSliding=!0,c&&this.pause(),this._setActiveIndicatorElement(u),this._activeElement=u;var h=o.default.Event(A,{relatedTarget:u,direction:a,from:r,to:f});if(o.default(this._element).hasClass("slide")){o.default(u).addClass(i),d.reflow(u),o.default(l).addClass(n),o.default(u).addClass(n);var g=d.getTransitionDurationFromElement(l);o.default(l).one(d.TRANSITION_END,(function(){o.default(u).removeClass(n+" "+i).addClass(S),o.default(l).removeClass("active "+i+" "+n),s._isSliding=!1,setTimeout((function(){return o.default(s._element).trigger(h)}),0)})).emulateTransitionEnd(g)}else o.default(l).removeClass(S),o.default(u).addClass(S),this._isSliding=!1,o.default(this._element).trigger(h);c&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(w),i=r({},k,o.default(this).data());"object"==typeof e&&(i=r({},i,e));var a="string"==typeof e?e:i.slide;if(n||(n=new t(this,i),o.default(this).data(w,n)),"number"==typeof e)n.to(e);else if("string"==typeof a){if("undefined"==typeof n[a])throw new TypeError('No method named "'+a+'"');n[a]()}else i.interval&&i.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=d.getSelectorFromElement(this);if(n){var i=o.default(n)[0];if(i&&o.default(i).hasClass("carousel")){var a=r({},o.default(i).data(),o.default(this).data()),s=this.getAttribute("data-slide-to");s&&(a.interval=!1),t._jQueryInterface.call(o.default(i),a),s&&o.default(i).data(w).to(s),e.preventDefault()}}},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return k}}]),t}();o.default(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",P._dataApiClickHandler),o.default(window).on("load.bs.carousel.data-api",(function(){for(var t=[].slice.call(document.querySelectorAll('[data-ride="carousel"]')),e=0,n=t.length;e0&&(this._selector=s,this._triggerArray.push(a))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){o.default(this._element).hasClass(q)?this.hide():this.show()},e.show=function(){var e,n,i=this;if(!(this._isTransitioning||o.default(this._element).hasClass(q)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(".show, .collapsing")).filter((function(t){return"string"==typeof i._config.parent?t.getAttribute("data-parent")===i._config.parent:t.classList.contains(F)}))).length&&(e=null),e&&(n=o.default(e).not(this._selector).data(R))&&n._isTransitioning))){var a=o.default.Event("show.bs.collapse");if(o.default(this._element).trigger(a),!a.isDefaultPrevented()){e&&(t._jQueryInterface.call(o.default(e).not(this._selector),"hide"),n||o.default(e).data(R,null));var s=this._getDimension();o.default(this._element).removeClass(F).addClass(Q),this._element.style[s]=0,this._triggerArray.length&&o.default(this._triggerArray).removeClass(B).attr("aria-expanded",!0),this.setTransitioning(!0);var l="scroll"+(s[0].toUpperCase()+s.slice(1)),r=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,(function(){o.default(i._element).removeClass(Q).addClass("collapse show"),i._element.style[s]="",i.setTransitioning(!1),o.default(i._element).trigger("shown.bs.collapse")})).emulateTransitionEnd(r),this._element.style[s]=this._element[l]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&o.default(this._element).hasClass(q)){var e=o.default.Event("hide.bs.collapse");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",d.reflow(this._element),o.default(this._element).addClass(Q).removeClass("collapse show");var i=this._triggerArray.length;if(i>0)for(var a=0;a0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this).data(K);if(n||(n=new t(this,"object"==typeof e?e:null),o.default(this).data(K,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=[].slice.call(document.querySelectorAll(it)),i=0,a=n.length;i0&&s--,40===e.which&&sdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ht);var i=d.getTransitionDurationFromElement(this._dialog);o.default(this._element).off(d.TRANSITION_END),o.default(this._element).one(d.TRANSITION_END,(function(){t._element.classList.remove(ht),n||o.default(t._element).one(d.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,i)})).emulateTransitionEnd(i),this._element.focus()}},e._showElement=function(t){var e=this,n=o.default(this._element).hasClass(dt),i=this._dialog?this._dialog.querySelector(".modal-body"):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),o.default(this._dialog).hasClass("modal-dialog-scrollable")&&i?i.scrollTop=0:this._element.scrollTop=0,n&&d.reflow(this._element),o.default(this._element).addClass(ct),this._config.focus&&this._enforceFocus();var a=o.default.Event("shown.bs.modal",{relatedTarget:t}),s=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,o.default(e._element).trigger(a)};if(n){var l=d.getTransitionDurationFromElement(this._dialog);o.default(this._dialog).one(d.TRANSITION_END,s).emulateTransitionEnd(l)}else s()},e._enforceFocus=function(){var t=this;o.default(document).off(pt).on(pt,(function(e){document!==e.target&&t._element!==e.target&&0===o.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?o.default(this._element).on(yt,(function(e){t._config.keyboard&&27===e.which?(e.preventDefault(),t.hide()):t._config.keyboard||27!==e.which||t._triggerBackdropTransition()})):this._isShown||o.default(this._element).off(yt)},e._setResizeEvent=function(){var t=this;this._isShown?o.default(window).on(_t,(function(e){return t.handleUpdate(e)})):o.default(window).off(_t)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){o.default(document.body).removeClass(ft),t._resetAdjustments(),t._resetScrollbar(),o.default(t._element).trigger(gt)}))},e._removeBackdrop=function(){this._backdrop&&(o.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=o.default(this._element).hasClass(dt)?dt:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className="modal-backdrop",n&&this._backdrop.classList.add(n),o.default(this._backdrop).appendTo(document.body),o.default(this._element).on(vt,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&d.reflow(this._backdrop),o.default(this._backdrop).addClass(ct),!t)return;if(!n)return void t();var i=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,t).emulateTransitionEnd(i)}else if(!this._isShown&&this._backdrop){o.default(this._backdrop).removeClass(ct);var a=function(){e._removeBackdrop(),t&&t()};if(o.default(this._element).hasClass(dt)){var s=d.getTransitionDurationFromElement(this._backdrop);o.default(this._backdrop).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ut={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},Mt={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},Wt=function(){function t(t,e){if("undefined"==typeof a.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=o.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(o.default(this.getTipElement()).hasClass(Rt))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),o.default.removeData(this.element,this.constructor.DATA_KEY),o.default(this.element).off(this.constructor.EVENT_KEY),o.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&o.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===o.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=o.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){o.default(this.element).trigger(e);var n=d.findShadowRoot(this.element),i=o.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!i)return;var s=this.getTipElement(),l=d.getUID(this.constructor.NAME);s.setAttribute("id",l),this.element.setAttribute("aria-describedby",l),this.setContent(),this.config.animation&&o.default(s).addClass(Lt);var r="function"==typeof this.config.placement?this.config.placement.call(this,s,this.element):this.config.placement,u=this._getAttachment(r);this.addAttachmentClass(u);var f=this._getContainer();o.default(s).data(this.constructor.DATA_KEY,this),o.default.contains(this.element.ownerDocument.documentElement,this.tip)||o.default(s).appendTo(f),o.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new a.default(this.element,s,this._getPopperConfig(u)),o.default(s).addClass(Rt),o.default(s).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&o.default(document.body).children().on("mouseover",null,o.default.noop);var c=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,o.default(t.element).trigger(t.constructor.Event.SHOWN),e===qt&&t._leave(null,t)};if(o.default(this.tip).hasClass(Lt)){var h=d.getTransitionDurationFromElement(this.tip);o.default(this.tip).one(d.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},e.hide=function(t){var e=this,n=this.getTipElement(),i=o.default.Event(this.constructor.Event.HIDE),a=function(){e._hoverState!==xt&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),o.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(o.default(this.element).trigger(i),!i.isDefaultPrevented()){if(o.default(n).removeClass(Rt),"ontouchstart"in document.documentElement&&o.default(document.body).children().off("mouseover",null,o.default.noop),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1,o.default(this.tip).hasClass(Lt)){var s=d.getTransitionDurationFromElement(n);o.default(n).one(d.TRANSITION_END,a).emulateTransitionEnd(s)}else a();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-tooltip-"+t)},e.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(o.default(t.querySelectorAll(".tooltip-inner")),this.getTitle()),o.default(t).removeClass("fade show")},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=At(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?o.default(e).parent().is(t)||t.empty().append(e):t.text(o.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:".arrow"},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:d.isElement(this.config.container)?o.default(this.config.container):o.default(document).find(this.config.container)},e._getAttachment=function(t){return Bt[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)o.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if("manual"!==e){var n=e===Ft?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,i=e===Ft?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;o.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(i,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},o.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Qt:Ft]=!0),o.default(e.getTipElement()).hasClass(Rt)||e._hoverState===xt?e._hoverState=xt:(clearTimeout(e._timeout),e._hoverState=xt,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===xt&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||o.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),o.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Qt:Ft]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=qt,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===qt&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=o.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Pt.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),d.typeCheckConfig(It,t,this.constructor.DefaultType),t.sanitize&&(t.template=At(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(jt);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(o.default(t).removeClass(Lt),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(kt),a="object"==typeof e&&e;if((i||!/dispose|hide/.test(e))&&(i||(i=new t(this,a),n.data(kt,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Ht}},{key:"NAME",get:function(){return It}},{key:"DATA_KEY",get:function(){return kt}},{key:"Event",get:function(){return Mt}},{key:"EVENT_KEY",get:function(){return".bs.tooltip"}},{key:"DefaultType",get:function(){return Ut}}]),t}();o.default.fn.tooltip=Wt._jQueryInterface,o.default.fn.tooltip.Constructor=Wt,o.default.fn.tooltip.noConflict=function(){return o.default.fn.tooltip=Ot,Wt._jQueryInterface};var Vt="bs.popover",zt=o.default.fn.popover,Kt=new RegExp("(^|\\s)bs-popover\\S+","g"),Xt=r({},Wt.Default,{placement:"right",trigger:"click",content:"",template:''}),Yt=r({},Wt.DefaultType,{content:"(string|element|function)"}),$t={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"},Jt=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),e.prototype.constructor=e,u(e,n);var a=i.prototype;return a.isWithContent=function(){return this.getTitle()||this._getContent()},a.addAttachmentClass=function(t){o.default(this.getTipElement()).addClass("bs-popover-"+t)},a.getTipElement=function(){return this.tip=this.tip||o.default(this.config.template)[0],this.tip},a.setContent=function(){var t=o.default(this.getTipElement());this.setElementContent(t.find(".popover-header"),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(".popover-body"),e),t.removeClass("fade show")},a._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},a._cleanTipClass=function(){var t=o.default(this.getTipElement()),e=t.attr("class").match(Kt);null!==e&&e.length>0&&t.removeClass(e.join(""))},i._jQueryInterface=function(t){return this.each((function(){var e=o.default(this).data(Vt),n="object"==typeof t?t:null;if((e||!/dispose|hide/.test(t))&&(e||(e=new i(this,n),o.default(this).data(Vt,e)),"string"==typeof t)){if("undefined"==typeof e[t])throw new TypeError('No method named "'+t+'"');e[t]()}}))},l(i,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"Default",get:function(){return Xt}},{key:"NAME",get:function(){return"popover"}},{key:"DATA_KEY",get:function(){return Vt}},{key:"Event",get:function(){return $t}},{key:"EVENT_KEY",get:function(){return".bs.popover"}},{key:"DefaultType",get:function(){return Yt}}]),i}(Wt);o.default.fn.popover=Jt._jQueryInterface,o.default.fn.popover.Constructor=Jt,o.default.fn.popover.noConflict=function(){return o.default.fn.popover=zt,Jt._jQueryInterface};var Gt="scrollspy",Zt="bs.scrollspy",te=o.default.fn[Gt],ee="active",ne="position",ie=".nav, .list-group",oe={offset:10,method:"auto",target:""},ae={offset:"number",method:"string",target:"(string|element)"},se=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" .nav-link,"+this._config.target+" .list-group-item,"+this._config.target+" .dropdown-item",this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,o.default(this._scrollElement).on("scroll.bs.scrollspy",(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?"offset":ne,n="auto"===this._config.method?e:this._config.method,i=n===ne?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,a=d.getSelectorFromElement(t);if(a&&(e=document.querySelector(a)),e){var s=e.getBoundingClientRect();if(s.width||s.height)return[o.default(e)[n]().top+i,a]}return null})).filter(Boolean).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){o.default.removeData(this._element,Zt),o.default(this._scrollElement).off(".bs.scrollspy"),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},oe,"object"==typeof t&&t?t:{})).target&&d.isElement(t.target)){var e=o.default(t.target).attr("id");e||(e=d.getUID(Gt),o.default(t.target).attr("id",e)),t.target="#"+e}return d.typeCheckConfig(Gt,t,ae),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;)this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t li > .active",ge=function(){function t(t){this._element=t}var e=t.prototype;return e.show=function(){var t=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&o.default(this._element).hasClass(ue)||o.default(this._element).hasClass("disabled")||this._element.hasAttribute("disabled"))){var e,n,i=o.default(this._element).closest(".nav, .list-group")[0],a=d.getSelectorFromElement(this._element);if(i){var s="UL"===i.nodeName||"OL"===i.nodeName?he:ce;n=(n=o.default.makeArray(o.default(i).find(s)))[n.length-1]}var l=o.default.Event("hide.bs.tab",{relatedTarget:this._element}),r=o.default.Event("show.bs.tab",{relatedTarget:n});if(n&&o.default(n).trigger(l),o.default(this._element).trigger(r),!r.isDefaultPrevented()&&!l.isDefaultPrevented()){a&&(e=document.querySelector(a)),this._activate(this._element,i);var u=function(){var e=o.default.Event("hidden.bs.tab",{relatedTarget:t._element}),i=o.default.Event("shown.bs.tab",{relatedTarget:n});o.default(n).trigger(e),o.default(t._element).trigger(i)};e?this._activate(e,e.parentNode,u):u()}}},e.dispose=function(){o.default.removeData(this._element,le),this._element=null},e._activate=function(t,e,n){var i=this,a=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?o.default(e).children(ce):o.default(e).find(he))[0],s=n&&a&&o.default(a).hasClass(fe),l=function(){return i._transitionComplete(t,a,n)};if(a&&s){var r=d.getTransitionDurationFromElement(a);o.default(a).removeClass(de).one(d.TRANSITION_END,l).emulateTransitionEnd(r)}else l()},e._transitionComplete=function(t,e,n){if(e){o.default(e).removeClass(ue);var i=o.default(e.parentNode).find("> .dropdown-menu .active")[0];i&&o.default(i).removeClass(ue),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}o.default(t).addClass(ue),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),d.reflow(t),t.classList.contains(fe)&&t.classList.add(de);var a=t.parentNode;if(a&&"LI"===a.nodeName&&(a=a.parentNode),a&&o.default(a).hasClass("dropdown-menu")){var s=o.default(t).closest(".dropdown")[0];if(s){var l=[].slice.call(s.querySelectorAll(".dropdown-toggle"));o.default(l).addClass(ue)}t.setAttribute("aria-expanded",!0)}n&&n()},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(le);if(i||(i=new t(this),n.data(le,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}}]),t}();o.default(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',(function(t){t.preventDefault(),ge._jQueryInterface.call(o.default(this),"show")})),o.default.fn.tab=ge._jQueryInterface,o.default.fn.tab.Constructor=ge,o.default.fn.tab.noConflict=function(){return o.default.fn.tab=re,ge._jQueryInterface};var me="bs.toast",pe=o.default.fn.toast,_e="hide",ve="show",ye="showing",be="click.dismiss.bs.toast",Ee={animation:!0,autohide:!0,delay:500},Te={animation:"boolean",autohide:"boolean",delay:"number"},we=function(){function t(t,e){this._element=t,this._config=this._getConfig(e),this._timeout=null,this._setListeners()}var e=t.prototype;return e.show=function(){var t=this,e=o.default.Event("show.bs.toast");if(o.default(this._element).trigger(e),!e.isDefaultPrevented()){this._clearTimeout(),this._config.animation&&this._element.classList.add("fade");var n=function(){t._element.classList.remove(ye),t._element.classList.add(ve),o.default(t._element).trigger("shown.bs.toast"),t._config.autohide&&(t._timeout=setTimeout((function(){t.hide()}),t._config.delay))};if(this._element.classList.remove(_e),d.reflow(this._element),this._element.classList.add(ye),this._config.animation){var i=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,n).emulateTransitionEnd(i)}else n()}},e.hide=function(){if(this._element.classList.contains(ve)){var t=o.default.Event("hide.bs.toast");o.default(this._element).trigger(t),t.isDefaultPrevented()||this._close()}},e.dispose=function(){this._clearTimeout(),this._element.classList.contains(ve)&&this._element.classList.remove(ve),o.default(this._element).off(be),o.default.removeData(this._element,me),this._element=null,this._config=null},e._getConfig=function(t){return t=r({},Ee,o.default(this._element).data(),"object"==typeof t&&t?t:{}),d.typeCheckConfig("toast",t,this.constructor.DefaultType),t},e._setListeners=function(){var t=this;o.default(this._element).on(be,'[data-dismiss="toast"]',(function(){return t.hide()}))},e._close=function(){var t=this,e=function(){t._element.classList.add(_e),o.default(t._element).trigger("hidden.bs.toast")};if(this._element.classList.remove(ve),this._config.animation){var n=d.getTransitionDurationFromElement(this._element);o.default(this._element).one(d.TRANSITION_END,e).emulateTransitionEnd(n)}else e()},e._clearTimeout=function(){clearTimeout(this._timeout),this._timeout=null},t._jQueryInterface=function(e){return this.each((function(){var n=o.default(this),i=n.data(me);if(i||(i=new t(this,"object"==typeof e&&e),n.data(me,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e](this)}}))},l(t,null,[{key:"VERSION",get:function(){return"4.6.2"}},{key:"DefaultType",get:function(){return Te}},{key:"Default",get:function(){return Ee}}]),t}();o.default.fn.toast=we._jQueryInterface,o.default.fn.toast.Constructor=we,o.default.fn.toast.noConflict=function(){return o.default.fn.toast=pe,we._jQueryInterface},t.Alert=g,t.Button=E,t.Carousel=P,t.Collapse=V,t.Dropdown=lt,t.Modal=Ct,t.Popover=Jt,t.Scrollspy=se,t.Tab=ge,t.Toast=we,t.Tooltip=Wt,t.Util=d,Object.defineProperty(t,"__esModule",{value:!0})})); //# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file diff --git a/tests/coverage/_js/jquery.min.js b/tests/coverage/_js/jquery.min.js index c4c6022..2c69bc9 100644 --- a/tests/coverage/_js/jquery.min.js +++ b/tests/coverage/_js/jquery.min.js @@ -1,2 +1,2 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0 - Code Coverage for /home/kwaadpepper/projets/crm/app/Console/Commands/GenerateCrmClient.php + Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/CreateAdmin.php - - - + + + @@ -16,11 +16,11 @@
@@ -38,9 +38,9 @@   -
Classes and Traits
-
Functions and Methods
Lines
+
Functions and Methods
+
Classes and Traits
@@ -59,94 +59,77 @@ - GenerateCrmClient + CreateAdmin
n/a
0 / 0
n/a
0 / 0
- 16 + 13
n/a
0 / 0
-  handle +  handle + +
n/a
+
0 / 0
n/a
0 / 0
1 - -
n/a
-
0 / 0
+ -  copyApiConfigInfo +  addFirstname
n/a
0 / 0
- 2 - -
n/a
-
0 / 0
- - - -  copyRoutesList - -
n/a
-
0 / 0
- 4 - -
n/a
-
0 / 0
- - - -  copyExceptions - -
n/a
-
0 / 0
- 1 - -
n/a
-
0 / 0
- - - -  copyEnums - -
n/a
-
0 / 0
- 2 - -
n/a
-
0 / 0
- - - -  copyToFolder
n/a
0 / 0
3 - -
n/a
-
0 / 0
+ -  replaceInPhpFile +  addLastname + +
n/a
+
0 / 0
n/a
0 / 0
3 + + + + +  addEmail
n/a
0 / 0
+ +
n/a
+
0 / 0
+ 3 + + + + +  addPassword + +
n/a
+
0 / 0
+ +
n/a
+
0 / 0
+ 3 + @@ -157,193 +140,186 @@ 1<?php 2 - 3namespace App\Console\Commands; + 3namespace App\Console\Commands; 4 - 5use App\Enums\ApiError; - 6use App\Enums\ApiStatus; - 7use App\Enums\ArcadiaOrderType; - 8use App\Enums\Country; - 9use App\Enums\QuestionType; - 10use App\Exceptions\Api\V2\ApiErrorException; - 11use App\Exceptions\Api\V2\CrmException; - 12use Illuminate\Console\Command; - 13use Illuminate\Support\Facades\File; - 14use Illuminate\Support\Facades\Route; - 15 - 16/** - 17 * @codeCoverageIgnore - 18 */ - 19class GenerateCrmClient extends Command - 20{ - 21    private const TARGET          = 'clients/V2/src'; - 22    private const CLIENTNAMESPACE = 'LDC\\LDCCRM'; + 5use App\Models\User; + 6use Illuminate\Console\Command; + 7use Illuminate\Support\Facades\Validator; + 8use Illuminate\Validation\ValidationException; + 9 + 10/** + 11 * Creates a new user + 12 * + 13 * @codeCoverageIgnore + 14 */ + 15class CreateAdmin extends Command + 16{ + 17    /** + 18     * The name and signature of the console command. + 19     * + 20     * @var string + 21     */ + 22    protected $signature = 'user:create'; 23 24    /** - 25     * The name and signature of the console command. + 25     * The console command description. 26     * 27     * @var string 28     */ - 29    protected $signature = 'client:generate'; + 29    protected $description = 'Create a backend admin user'; 30 31    /** - 32     * The console command description. + 32     * The firstname of the new user. 33     * 34     * @var string 35     */ - 36    protected $description = 'Generate the crm v2 client in clients/V2 folder'; + 36    protected $firstname; 37 38    /** - 39     * Execute the console command. + 39     * The lastname of the new user. 40     * - 41     * @return integer + 41     * @var string 42     */ - 43    public function handle() - 44    { - 45        $this->copyEnums(); - 46        $this->copyExceptions(); - 47        $this->copyRoutesList(); - 48        $this->copyApiConfigInfo(); - 49        return 0; - 50    } + 43    protected $lastname; + 44 + 45    /** + 46     * The email of the new user. + 47     * + 48     * @var string + 49     */ + 50    protected $email; 51 52    /** - 53     * Copy config files + 53     * The password of the new user. 54     * - 55     * @return void + 55     * @var string 56     */ - 57    private function copyApiConfigInfo() - 58    { - 59        $this->info('Copy Api Config'); - 60        $rootTarget = app_path('../' . self::TARGET); - 61        if (File::exists("$rootTarget/../apiConfig.readonly.json")) { - 62            File::delete("$rootTarget/../apiConfig.readonly.json"); - 63        } - 64        File::put( - 65            "$rootTarget/../apiConfig.readonly.json", - 66            \json_encode(config('api'), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - 67        ); - 68    } - 69 - 70    /** - 71     * Copy routes into a json file - 72     * - 73     * @return void - 74     */ - 75    private function copyRoutesList() - 76    { - 77        $this->info('Copy Routes'); - 78        $routes = []; - 79        /** @var \Illuminate\Routing\Route */ - 80        foreach (Route::getRoutes() as $route) { - 81            if (\strpos($route->getPrefix(), 'v2') !== false) { - 82                $routes[$route->getName()] = [ - 83                    'uri' => $route->uri(), - 84                    'methods' => $route->methods() - 85                ]; - 86            } - 87        } - 88        $rootTarget = app_path('../' . self::TARGET); - 89        if (File::exists("$rootTarget/routes.json")) { - 90            File::delete("$rootTarget/routes.json"); - 91        } - 92        File::put( - 93            "$rootTarget/routes.json", - 94            \json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - 95        ); - 96    } - 97 - 98    /** - 99     * Copy Exeption classes - 100     * - 101     * @return void - 102     */ - 103    private function copyExceptions(): void - 104    { - 105        $this->info('Copy Exceptions'); - 106        $rootTarget = app_path('../' . self::TARGET); - 107        $this->copyToFolder([ - 108            CrmException::class, - 109            ApiErrorException::class - 110        ], 'Exceptions'); - 111        $this->replaceInPhpFile( - 112            "$rootTarget/Exceptions/ApiErrorException.php", - 113            'App\Enums\ApiError', - 114            'LDC\LDCCRM\Enums\ApiError' - 115        ); - 116    } - 117 - 118    /** - 119     * Copy enums classes - 120     * - 121     * @return void - 122     */ - 123    private function copyEnums(): void - 124    { - 125        $this->info('Copy Enums'); - 126        $classes = [ - 127            ApiError::class, - 128            ApiStatus::class, - 129            ArcadiaOrderType::class, - 130            Country::class, - 131            QuestionType::class - 132        ]; - 133        $this->copyToFolder($classes, 'Enums'); - 134        $rootTarget = app_path('../' . self::TARGET); - 135        foreach ($classes as $class) { - 136            $classBasename = class_basename($class); - 137            $dest          = "$rootTarget/Enums/$classBasename.php"; - 138            $this->replaceInPhpFile($dest, 'BaseEnumRoutable', 'BaseEnum'); - 139        } + 57    protected $password; + 58 + 59    /** + 60     * Execute the console command. + 61     * + 62     * @return integer + 63     */ + 64    public function handle(): int + 65    { + 66        $this->firstname = null; + 67        $this->addFirstname(); + 68 + 69        $this->lastname = null; + 70        $this->addLastname(); + 71 + 72        $this->email = null; + 73        $this->addEmail(); + 74 + 75        $this->password = null; + 76        $this->addPassword(); + 77 + 78        $user = new User([ + 79            'firstname' => $this->firstname, + 80            'lastname' => $this->lastname, + 81            'email' => $this->email, + 82            'password' => $this->password, + 83            'remember_token' => null, + 84        ]); + 85        $user->saveOrFail(); + 86 + 87        $this->info('User created ðŸ‘Œ'); + 88 + 89        return 0; + 90    } + 91 + 92    /** + 93     * Add a firstname to the user. + 94     * + 95     * @return void + 96     */ + 97    private function addFirstname(): void + 98    { + 99        while (is_null($this->firstname)) { + 100            try { + 101                $tmp       = $this->ask('Type the wanted user firstname', 'Admin'); + 102                $validator = Validator::make(['firstname' => $tmp], [ + 103                    'firstname' => 'required|min:3|max:255' + 104                ]); + 105                $validator->validate(); + 106                $this->firstname = $tmp; + 107            } catch (ValidationException $e) { + 108                $this->error(sprintf( + 109                    'Please provide a valid firstname %s', + 110                    \implode(',', collect($e->errors())->flatten()->all()) + 111                )); + 112                continue; + 113            } //end try + 114        }; //end while + 115    } + 116 + 117    /** + 118     * Add a lastname to the user. + 119     * + 120     * @return void + 121     */ + 122    private function addLastname(): void + 123    { + 124        while (is_null($this->lastname)) { + 125            try { + 126                $tmp       = $this->ask('Type the wanted user lastname', 'Saïtama'); + 127                $validator = Validator::make(['lastname' => $tmp], [ + 128                    'lastname' => 'required|min:3|max:255' + 129                ]); + 130                $validator->validate(); + 131                $this->lastname = $tmp; + 132            } catch (ValidationException $e) { + 133                $this->error(sprintf( + 134                    'Please provide a valid lastname %s', + 135                    \implode(',', collect($e->errors())->flatten()->all()) + 136                )); + 137                continue; + 138            } //end try + 139        }; //end while 140    } 141 142    /** - 143     * Copy Classes to specified folder + 143     * Add an email to the user. 144     * - 145     * @param array  $classes - 146     * @param string $folder - 147     * @return void - 148     */ - 149    private function copyToFolder(array $classes, string $folder): void - 150    { - 151        $rootTarget = app_path('../' . self::TARGET); - 152        foreach ($classes as $class) { - 153            $classFileName = \str_replace("App\\$folder\\", '', sprintf('%s.php', $class)); - 154            $classFileName = \str_replace('\\', '/', $classFileName); - 155            $classBasename = class_basename($class); - 156            $source        = \app_path("$folder/$classFileName"); - 157            $dest          = "$rootTarget/$folder/$classBasename.php"; - 158            if (File::exists($dest)) { - 159                File::delete($dest); - 160            } - 161            File::copy($source, $dest); - 162            $this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE); - 163        } - 164    } - 165 - 166    /** - 167     * Replaces a specific string with another on a file - 168     * - 169     * @param string $source - 170     * @param string $search - 171     * @param string $replace - 172     * @return void - 173     */ - 174    private function replaceInPhpFile(string $source, string $search, string $replace): void - 175    { - 176        $fContent = File::get($source); - 177 - 178        // Remove trailing \Api\V2 . - 179        if ($pos = strpos($fContent, '\Api\V2')) { - 180            $fContent = substr_replace($fContent, '', $pos, strlen('\Api\V2')); - 181        } - 182 - 183        // Replace namespace top level. - 184        $pos = strpos($fContent, $search); - 185        if ($pos !== false) { - 186            File::put($source, \str_replace($search, $replace, $fContent)); - 187        } - 188    } - 189} + 145     * @return void + 146     */ + 147    private function addEmail(): void + 148    { + 149        while (is_null($this->email)) { + 150            try { + 151                $tmp       = $this->ask('Type the wanted user email', 'equipe.web@ldc.fr'); + 152                $validator = Validator::make(['email' => $tmp], [ + 153                    'email' => 'required|unique:users,email|email:rfc,dns' + 154                ]); + 155                $validator->validate(); + 156                $this->email = $tmp; + 157            } catch (ValidationException $e) { + 158                $this->error(sprintf( + 159                    'Please provide a valid email %s', + 160                    \implode(',', collect($e->errors())->flatten()->all()) + 161                )); + 162                continue; + 163            } //end try + 164        }; //end while + 165    } + 166 + 167    /** + 168     * Add a password to the user. + 169     * + 170     * @return void + 171     */ + 172    private function addPassword(): void + 173    { + 174        while ( + 175            ($this->password = $this->secret( + 176                (!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password' + 177            )) !== $this->secret('Confirm the password') + 178        ) { + 179            continue; + 180        }; //end while + 181    } + 182} @@ -352,18 +328,18 @@
- - - - + + + + diff --git a/tests/coverage/app/Console/Commands/DeleteUnconfirmedCustomers.php.html b/tests/coverage/app/Console/Commands/DeleteUnconfirmedCustomers.php.html index 7170e8e..61396e0 100644 --- a/tests/coverage/app/Console/Commands/DeleteUnconfirmedCustomers.php.html +++ b/tests/coverage/app/Console/Commands/DeleteUnconfirmedCustomers.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Console/Commands/DeleteUnconfirmedCustomers.php + Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/DeleteUnconfirmedCustomers.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/Console/Commands/GenerateCrmClient.php.html b/tests/coverage/app/Console/Commands/GenerateClient.php.html similarity index 73% rename from tests/coverage/Console/Commands/GenerateCrmClient.php.html rename to tests/coverage/app/Console/Commands/GenerateClient.php.html index 3617fde..90c4c61 100644 --- a/tests/coverage/Console/Commands/GenerateCrmClient.php.html +++ b/tests/coverage/app/Console/Commands/GenerateClient.php.html @@ -2,12 +2,12 @@ - Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/GenerateCrmClient.php + Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/GenerateClient.php - - - - + + + +
@@ -16,10 +16,11 @@
@@ -58,21 +59,21 @@ - GenerateCrmClient + GenerateClient
n/a
0 / 0
n/a
0 / 0
- 16 + 19
n/a
0 / 0
-  handle +  handle
n/a
0 / 0
@@ -84,7 +85,7 @@ -  copyApiConfigInfo +  copyApiConfigInfo
n/a
0 / 0
@@ -96,7 +97,7 @@ -  copyRoutesList +  copyRoutesList
n/a
0 / 0
@@ -108,19 +109,7 @@ -  copyExceptions - -
n/a
-
0 / 0
- -
n/a
-
0 / 0
- 1 - - - - -  copyEnums +  copyExceptions
n/a
0 / 0
@@ -132,7 +121,31 @@ -  copyToFolder +  copyTraits + +
n/a
+
0 / 0
+ +
n/a
+
0 / 0
+ 2 + + + + +  copyEnums + +
n/a
+
0 / 0
+ +
n/a
+
0 / 0
+ 2 + + + + +  copyToFolder
n/a
0 / 0
@@ -144,7 +157,7 @@ -  replaceInPhpFile +  replaceInPhpFile
n/a
0 / 0
@@ -163,193 +176,222 @@ 1<?php 2 - 3namespace App\Console\Commands; + 3namespace App\Console\Commands; 4 - 5use App\Enums\ApiError; - 6use App\Enums\ApiStatus; - 7use App\Enums\ArcadiaOrderType; - 8use App\Enums\Country; - 9use App\Enums\QuestionType; - 10use App\Exceptions\Api\V2\ApiErrorException; - 11use App\Exceptions\Api\V2\CrmException; - 12use Illuminate\Console\Command; - 13use Illuminate\Support\Facades\File; - 14use Illuminate\Support\Facades\Route; - 15 - 16/** - 17 * @codeCoverageIgnore - 18 */ - 19class GenerateCrmClient extends Command - 20{ - 21    private const TARGET          = 'clients/V2/src'; - 22    private const CLIENTNAMESPACE = 'LDC\\LDCCRM'; - 23 - 24    /** - 25     * The name and signature of the console command. - 26     * - 27     * @var string - 28     */ - 29    protected $signature = 'client:generate'; - 30 - 31    /** - 32     * The console command description. - 33     * - 34     * @var string - 35     */ - 36    protected $description = 'Generate the crm v2 client in clients/V2 folder'; - 37 - 38    /** - 39     * Execute the console command. - 40     * - 41     * @return integer - 42     */ - 43    public function handle() - 44    { - 45        $this->copyEnums(); - 46        $this->copyExceptions(); - 47        $this->copyRoutesList(); - 48        $this->copyApiConfigInfo(); - 49        return 0; - 50    } - 51 - 52    /** - 53     * Copy config files - 54     * - 55     * @return void - 56     */ - 57    private function copyApiConfigInfo() - 58    { - 59        $this->info('Copy Api Config'); - 60        $rootTarget = app_path('../' . self::TARGET); - 61        if (File::exists("$rootTarget/../apiConfig.readonly.json")) { - 62            File::delete("$rootTarget/../apiConfig.readonly.json"); - 63        } - 64        File::put( - 65            "$rootTarget/../apiConfig.readonly.json", - 66            \json_encode(config('api'), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - 67        ); - 68    } - 69 - 70    /** - 71     * Copy routes into a json file - 72     * - 73     * @return void - 74     */ - 75    private function copyRoutesList() - 76    { - 77        $this->info('Copy Routes'); - 78        $routes = []; - 79        /** @var \Illuminate\Routing\Route */ - 80        foreach (Route::getRoutes() as $route) { - 81            if (\strpos($route->getPrefix(), 'v2') !== false) { - 82                $routes[$route->getName()] = [ - 83                    'uri' => $route->uri(), - 84                    'methods' => $route->methods() - 85                ]; - 86            } - 87        } - 88        $rootTarget = app_path('../' . self::TARGET); - 89        if (File::exists("$rootTarget/routes.json")) { - 90            File::delete("$rootTarget/routes.json"); - 91        } - 92        File::put( - 93            "$rootTarget/routes.json", - 94            \json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - 95        ); - 96    } - 97 - 98    /** - 99     * Copy Exeption classes - 100     * - 101     * @return void - 102     */ - 103    private function copyExceptions(): void - 104    { - 105        $this->info('Copy Exceptions'); - 106        $rootTarget = app_path('../' . self::TARGET); - 107        $this->copyToFolder([ - 108            CrmException::class, - 109            ApiErrorException::class - 110        ], 'Exceptions'); - 111        $this->replaceInPhpFile( - 112            "$rootTarget/Exceptions/ApiErrorException.php", - 113            'App\Enums\ApiError', - 114            'LDC\LDCCRM\Enums\ApiError' - 115        ); - 116    } - 117 - 118    /** - 119     * Copy enums classes - 120     * - 121     * @return void - 122     */ - 123    private function copyEnums(): void - 124    { - 125        $this->info('Copy Enums'); - 126        $classes = [ - 127            ApiError::class, - 128            ApiStatus::class, - 129            ArcadiaOrderType::class, - 130            Country::class, - 131            QuestionType::class - 132        ]; - 133        $this->copyToFolder($classes, 'Enums'); - 134        $rootTarget = app_path('../' . self::TARGET); + 5use App\Enums\ApiError; + 6use App\Enums\ApiStatus; + 7use App\Enums\ArcadiaOrderType; + 8use App\Enums\Country; + 9use App\Enums\QuestionType; + 10use App\Exceptions\Api\V4\ApiErrorException; + 11use App\Exceptions\Api\V4\CrmException; + 12use App\Traits\Enums\BaseEnum; + 13use Illuminate\Console\Command; + 14use Illuminate\Support\Facades\File; + 15use Illuminate\Support\Facades\Route; + 16use Illuminate\Support\Str; + 17 + 18/** + 19 * @codeCoverageIgnore + 20 */ + 21class GenerateClient extends Command + 22{ + 23    private const TARGET          = 'client/src'; + 24    private const CLIENTNAMESPACE = 'LDC\\LDCCRM'; + 25 + 26    /** + 27     * The name and signature of the console command. + 28     * + 29     * @var string + 30     */ + 31    protected $signature = 'client:generate'; + 32 + 33    /** + 34     * The console command description. + 35     * + 36     * @var string + 37     */ + 38    protected $description = 'Generate the crm V4 client in client folder'; + 39 + 40    /** + 41     * Execute the console command. + 42     * + 43     * @return integer + 44     */ + 45    public function handle(): int + 46    { + 47        $this->copyTraits(); + 48        $this->copyEnums(); + 49        $this->copyExceptions(); + 50        $this->copyRoutesList(); + 51        $this->copyApiConfigInfo(); + 52        return 0; + 53    } + 54 + 55    /** + 56     * Copy config files + 57     * + 58     * @return void + 59     */ + 60    private function copyApiConfigInfo(): void + 61    { + 62        $this->info('Copy Api Config'); + 63        $rootTarget = app_path('../' . self::TARGET); + 64        if (File::exists("$rootTarget/../apiConfig.readonly.json")) { + 65            File::delete("$rootTarget/../apiConfig.readonly.json"); + 66        } + 67        File::put( + 68            "$rootTarget/../apiConfig.readonly.json", + 69            \json_encode(config('api'), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + 70        ); + 71    } + 72 + 73    /** + 74     * Copy routes into a json file + 75     * + 76     * @return void + 77     */ + 78    private function copyRoutesList(): void + 79    { + 80        $this->info('Copy Routes'); + 81        $routes = []; + 82        /** @var \Illuminate\Routing\Route */ + 83        foreach (Route::getRoutes() as $route) { + 84            if (\strpos($route->getPrefix(), 'v4') !== false) { + 85                $routes[$route->getName()] = [ + 86                    'uri' => $route->uri(), + 87                    'methods' => $route->methods() + 88                ]; + 89            } + 90        } + 91        $rootTarget = app_path('../' . self::TARGET); + 92        if (File::exists("$rootTarget/routes.json")) { + 93            File::delete("$rootTarget/routes.json"); + 94        } + 95        File::put( + 96            "$rootTarget/routes.json", + 97            \json_encode($routes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + 98        ); + 99    } + 100 + 101    /** + 102     * Copy Exeption classes + 103     * + 104     * @return void + 105     */ + 106    private function copyExceptions(): void + 107    { + 108        $this->info('Copy Exceptions'); + 109        $rootTarget = app_path('../' . self::TARGET); + 110        $classes    = [ + 111            CrmException::class, + 112            ApiErrorException::class + 113        ]; + 114        $this->copyToFolder($classes, 'Exceptions'); + 115        foreach ($classes as $class) { + 116            $classBasename = class_basename($class); + 117            $dest          = "$rootTarget/Exceptions/$classBasename.php"; + 118            $this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\'); + 119        } + 120    } + 121 + 122    /** + 123     * Copy base enums classes + 124     * + 125     * @return void + 126     */ + 127    private function copyTraits(): void + 128    { + 129        $this->info('Copy Traits'); + 130        $rootTarget = app_path('../' . self::TARGET); + 131        $classes    = [ + 132            BaseEnum::class + 133        ]; + 134        $this->copyToFolder($classes, 'Traits'); 135        foreach ($classes as $class) { 136            $classBasename = class_basename($class); - 137            $dest          = "$rootTarget/Enums/$classBasename.php"; - 138            $this->replaceInPhpFile($dest, 'BaseEnumRoutable', 'BaseEnum'); + 137            $dest          = "$rootTarget/Traits/$classBasename.php"; + 138            $this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\'); 139        } 140    } 141 142    /** - 143     * Copy Classes to specified folder + 143     * Copy enums classes 144     * - 145     * @param array  $classes - 146     * @param string $folder - 147     * @return void - 148     */ - 149    private function copyToFolder(array $classes, string $folder): void - 150    { - 151        $rootTarget = app_path('../' . self::TARGET); - 152        foreach ($classes as $class) { - 153            $classFileName = \str_replace("App\\$folder\\", '', sprintf('%s.php', $class)); - 154            $classFileName = \str_replace('\\', '/', $classFileName); - 155            $classBasename = class_basename($class); - 156            $source        = \app_path("$folder/$classFileName"); - 157            $dest          = "$rootTarget/$folder/$classBasename.php"; - 158            if (File::exists($dest)) { - 159                File::delete($dest); - 160            } - 161            File::copy($source, $dest); - 162            $this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE); + 145     * @return void + 146     */ + 147    private function copyEnums(): void + 148    { + 149        $this->info('Copy Enums'); + 150        $rootTarget = app_path('../' . self::TARGET); + 151        $classes    = [ + 152            ApiError::class, + 153            ApiStatus::class, + 154            ArcadiaOrderType::class, + 155            Country::class, + 156            QuestionType::class + 157        ]; + 158        $this->copyToFolder($classes, 'Enums'); + 159        foreach ($classes as $class) { + 160            $classBasename = class_basename($class); + 161            $dest          = "$rootTarget/Enums/$classBasename.php"; + 162            $this->replaceInPhpFile($dest, 'App\\', self::CLIENTNAMESPACE . '\\'); 163        } 164    } 165 166    /** - 167     * Replaces a specific string with another on a file + 167     * Copy Classes to specified folder 168     * - 169     * @param string $source - 170     * @param string $search - 171     * @param string $replace - 172     * @return void - 173     */ - 174    private function replaceInPhpFile(string $source, string $search, string $replace): void - 175    { - 176        $fContent = File::get($source); - 177 - 178        // Remove trailing \Api\V2 . - 179        if ($pos = strpos($fContent, '\Api\V2')) { - 180            $fContent = substr_replace($fContent, '', $pos, strlen('\Api\V2')); - 181        } - 182 - 183        // Replace namespace top level. - 184        $pos = strpos($fContent, $search); - 185        if ($pos !== false) { - 186            File::put($source, \str_replace($search, $replace, $fContent)); - 187        } - 188    } - 189} + 169     * @param array  $classes + 170     * @param string $folder + 171     * @return void + 172     */ + 173    private function copyToFolder(array $classes, string $folder): void + 174    { + 175        $rootTarget      = app_path('../' . self::TARGET); + 176        $folderNamespace = \str_replace('/', '\\', $folder); + 177        foreach ($classes as $class) { + 178            $classFileName = \str_replace("App\\$folderNamespace\\", '', sprintf('%s.php', $class)); + 179            $classFileName = \str_replace('\\', '/', $classFileName); + 180            $classBasename = class_basename($class); + 181            $source        = \app_path("$folder/$classFileName"); + 182            $dest          = "$rootTarget/$folder/$classBasename.php"; + 183            if (File::exists($dest)) { + 184                File::delete($dest); + 185            } + 186            File::copy($source, $dest); + 187            $this->replaceInPhpFile($dest, 'namespace App', 'namespace ' . self::CLIENTNAMESPACE); + 188        } + 189    } + 190 + 191    /** + 192     * Replaces a specific string with another on a file + 193     * + 194     * @param string $source + 195     * @param string $search + 196     * @param string $replace + 197     * @return void + 198     */ + 199    private function replaceInPhpFile(string $source, string $search, string $replace): void + 200    { + 201        $fContent = File::get($source); + 202 + 203        // Remove trailing \Api\V4 . + 204        if ($pos = strpos($fContent, '\Api\V4')) { + 205            $fContent = substr_replace($fContent, '', $pos, strlen('\Api\V4')); + 206        } + 207 + 208        // Remove trailing \Api . + 209        $fContent = Str::of($fContent)->replaceMatches("/\\\(Api)([\s;\\\])/", '$2'); + 210        $fContent = Str::of($fContent)->replaceMatches("/\\\Traits\\\Enums/", '\Traits'); + 211 + 212        // Replace namespace top level. + 213        $pos = strpos($fContent, $search); + 214        if ($pos !== false) { + 215            File::put($source, \str_replace($search, $replace, $fContent)); + 216        } + 217    } + 218} @@ -358,18 +400,18 @@
- - - - + + + + diff --git a/tests/coverage/app/Console/Commands/LdcWebStatusChecker.php.html b/tests/coverage/app/Console/Commands/LdcWebStatusChecker.php.html index 97c2b73..8a84c0c 100644 --- a/tests/coverage/app/Console/Commands/LdcWebStatusChecker.php.html +++ b/tests/coverage/app/Console/Commands/LdcWebStatusChecker.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Console/Commands/LdcWebStatusChecker.php + Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/LdcWebStatusChecker.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/app/Console/Commands/MigrateCrmUsers.php.html b/tests/coverage/app/Console/Commands/MigrateCrmUsers.php.html index 9de3485..6433494 100644 --- a/tests/coverage/app/Console/Commands/MigrateCrmUsers.php.html +++ b/tests/coverage/app/Console/Commands/MigrateCrmUsers.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Console/Commands/MigrateCrmUsers.php + Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/MigrateCrmUsers.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/app/Console/Commands/StartStatJobs.php.html b/tests/coverage/app/Console/Commands/StartStatJobs.php.html index 733fff9..995aea1 100644 --- a/tests/coverage/app/Console/Commands/StartStatJobs.php.html +++ b/tests/coverage/app/Console/Commands/StartStatJobs.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Console/Commands/StartStatJobs.php + Code Coverage for /home/jeremy/projets/crm/app/Console/Commands/StartStatJobs.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/app/Console/Commands/UserCreate.php.html b/tests/coverage/app/Console/Commands/UserCreate.php.html deleted file mode 100644 index 82ca32e..0000000 --- a/tests/coverage/app/Console/Commands/UserCreate.php.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Code Coverage for /home/kwaadpepper/projets/crm/app/Console/Commands/UserCreate.php - - - - - - - -
-
-
-
- -
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
UserCreate
n/a
0 / 0
n/a
0 / 0
1
n/a
0 / 0
 handle
n/a
0 / 0
1
n/a
0 / 0
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1<?php
2
3namespace App\Console\Commands;
4
5use App\Models\User;
6use Illuminate\Console\Command;
7use Illuminate\Support\Facades\Hash;
8
9/**
10 * @codeCoverageIgnore
11 */
12class UserCreate extends Command
13{
14    /**
15     * The name and signature of the console command.
16     *
17     * @var string
18     */
19    protected $signature = 'user:create';
20
21    /**
22     * The console command description.
23     *
24     * @var string
25     */
26    protected $description = 'Command description';
27
28    /**
29     * Execute the console command.
30     *
31     * @return integer
32     */
33    public function handle()
34    {
35        $firstname = $this->ask('What is the firstname?');
36        $lastname  = $this->ask('What is the lastname?');
37        $email     = $this->ask('What is the email?');
38        $pwd       = $this->secret('What is the password?');
39
40        $user            = new User();
41        $user->firstname = $firstname;
42        $user->lastname  = $lastname;
43        $user->email     = $email;
44        $user->password  = Hash::make($pwd);
45        $user->save();
46
47        $this->info('User created.');
48        return 0;
49    }
50}
- - - -
- - - - - - diff --git a/tests/coverage/app/Console/Commands/dashboard.html b/tests/coverage/app/Console/Commands/dashboard.html index c0305e9..f18692a 100644 --- a/tests/coverage/app/Console/Commands/dashboard.html +++ b/tests/coverage/app/Console/Commands/dashboard.html @@ -2,11 +2,11 @@ - Dashboard for /home/kwaadpepper/projets/crm/app/Console/Commands + Dashboard for /home/jeremy/projets/crm/app/Console/Commands - - - + + + @@ -16,7 +16,7 @@
- - - + + + - - - + + + + diff --git a/tests/coverage/app/Console/dashboard.html b/tests/coverage/app/Console/dashboard.html index 02a560e..8a351e2 100644 --- a/tests/coverage/app/Console/dashboard.html +++ b/tests/coverage/app/Console/dashboard.html @@ -2,11 +2,11 @@ - Dashboard for /home/kwaadpepper/projets/crm/app/Console + Dashboard for /home/jeremy/projets/crm/app/Console - - - + + + @@ -16,7 +16,7 @@
- - - + + + - - - + + + + diff --git a/tests/coverage/app/Enums/ApiStatus.php.html b/tests/coverage/app/Enums/ApiStatus.php.html index a610092..acbccf4 100644 --- a/tests/coverage/app/Enums/ApiStatus.php.html +++ b/tests/coverage/app/Enums/ApiStatus.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Enums/ApiStatus.php + Code Coverage for /home/jeremy/projets/crm/app/Enums/ApiStatus.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/app/Enums/ArcadiaMailType.php.html b/tests/coverage/app/Enums/ArcadiaMailType.php.html index 7263690..8954616 100644 --- a/tests/coverage/app/Enums/ArcadiaMailType.php.html +++ b/tests/coverage/app/Enums/ArcadiaMailType.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Enums/ArcadiaMailType.php + Code Coverage for /home/jeremy/projets/crm/app/Enums/ArcadiaMailType.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/app/Enums/ArcadiaOrderType.php.html b/tests/coverage/app/Enums/ArcadiaOrderType.php.html index 19e44fb..399a3f7 100644 --- a/tests/coverage/app/Enums/ArcadiaOrderType.php.html +++ b/tests/coverage/app/Enums/ArcadiaOrderType.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Enums/ArcadiaOrderType.php + Code Coverage for /home/jeremy/projets/crm/app/Enums/ArcadiaOrderType.php - - - + + + @@ -16,7 +16,7 @@
- - - - + + + + diff --git a/tests/coverage/app/Enums/Country.php.html b/tests/coverage/app/Enums/Country.php.html index 15e349f..7548004 100644 --- a/tests/coverage/app/Enums/Country.php.html +++ b/tests/coverage/app/Enums/Country.php.html @@ -2,11 +2,11 @@ - Code Coverage for /home/kwaadpepper/projets/crm/app/Enums/Country.php + Code Coverage for /home/jeremy/projets/crm/app/Enums/Country.php - - - + + + @@ -16,7 +16,7 @@