diff --git a/.env.example b/.env.example index d5ece6b1..8ece2e85 100644 --- a/.env.example +++ b/.env.example @@ -45,6 +45,9 @@ CRM_ENDPOINT= CRM_TOKEN= CRM_PASSWORD= CRM_QUESTION_ID= +CRM_BRAND_ID= +# - JSON ONLY (like this: {"subject1":44,"subject2":55,"subject3":66}) +CRM_SUBJECTS= # DIGITAL RECRUITER DIGITAL_RECRUITER_URL= diff --git a/.gitignore b/.gitignore index 3582adbc..7b4ed48c 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,10 @@ oldAssets /public/* !/public/newsletters/ !/public/tdf-downloads + +# Folders from new VMs +/.npm +/.composer +psysh_history +.zshrc +.viminfo diff --git a/app/Enums/ContactFormType.php b/app/Enums/ContactFormType.php index 6fef5d3d..44ee40f7 100644 --- a/app/Enums/ContactFormType.php +++ b/app/Enums/ContactFormType.php @@ -113,7 +113,43 @@ enum ContactFormType: int self::complain, self::club, self::games => true, - default => false, + default => false, }; } + + // * CRM CUBJECTS + + /** + * Get the CRM subject ID for the current enum case. + * + * @return ?string + */ + public function crmId(): ?string + { + return self::getCrmSubjects()[$this->name] ?? null; + } + + /** + * Get all CRM subject IDs keyed by enum case name. + * + * @return array + */ + public static function crmIds(): array + { + $subjects = self::getCrmSubjects(); + $subjectIds = collect(self::cases()) + ->mapWithKeys(fn($case) => [$case->name => $subjects[$case->name] ?? null]) + ->all(); + return array_filter($subjectIds, fn($v) => !is_null($v)); + } + + /** + * Get CRM subjects config as an array. + * + * @return array + */ + private static function getCrmSubjects(): array + { + return json_decode(config('crm.subjects'), true) ?? []; + } } diff --git a/app/Http/Controllers/Fo/ContactController.php b/app/Http/Controllers/Fo/ContactController.php index 1e385132..50b7c240 100644 --- a/app/Http/Controllers/Fo/ContactController.php +++ b/app/Http/Controllers/Fo/ContactController.php @@ -7,11 +7,12 @@ use App\Enums\StaticPageType; use App\Exceptions\AbortContactMailer; use App\Http\Controllers\Controller; use App\Http\Requests\Fo\ContactFormRequest; -use App\Jobs\Mails\SendArcadiaMail; +use App\Jobs\Mails\SendClaimMail; use App\Jobs\Mails\SendNLMail; use App\Jobs\Mails\SendPasserelleMail; use App\Models\MailContactData; use App\Models\StaticPage; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; @@ -38,37 +39,59 @@ class ContactController extends Controller */ public function contactSubmit(ContactFormRequest $request): \Illuminate\Http\RedirectResponse { - return DB::transaction(function () use ($request) { + /** @var array $validatedData */ + $validatedData = $request->validated(); + return DB::transaction(function () use ($validatedData) { try { - $joinFilePath = ContactFormType::make($request->type)->hasFileInput() - ? $this->storeFile($request->file('joinFile')) + $joinFilePath = ContactFormType::make($validatedData['type'])->hasFileInput() + ? $this->storeFile($validatedData['joinFile']) : null; $data = new MailContactData( - $request->safe()->except(['g-recaptcha-response', 'joinFile', 'locale']), + Arr::except($validatedData, ['g-recaptcha-response', 'joinFile', 'locale', 'crmsubject']), $joinFilePath ); - $locale = $request->input('locale'); - // Send to Passerelle if it is a complain about a product. - if (isset($locale) && ContactFormType::make($request->input('locale'))->equals(ContactFormType::nl)) { - app()->setLocale('nl'); - \dispatch(new SendNLMail($data, $joinFilePath)); - } elseif (ContactFormType::make($request->type)->equals(ContactFormType::complain)) { + if (ContactFormType::make($validatedData['type'])->equals(ContactFormType::complain)) { \dispatch(new SendPasserelleMail($data, $joinFilePath)); } else { - \dispatch(new SendArcadiaMail($data, $joinFilePath)); - } + // Send to the CRM. + if ($joinFilePath) { + $relativePath = str_replace(storage_path('app/public') . '/', '', $joinFilePath); + $binary = Storage::disk('public')->get($relativePath); + $base64 = base64_encode($binary); + } + /** @var \LDC\LDCCRM\CrmApiV4 */ + $crm = app('crmAPI'); + $crm->setClaim( + $validatedData['lastname'], + $validatedData['firstname'], + $validatedData['email'], + $validatedData['message'], + config('crm.brand'), + $validatedData['crmsubject'], + $validatedData['address'], + $validatedData['town'], + $validatedData['postal'], + $base64 ?? null, + ); + + // Send mail. + $locale = $validatedData['locale'] ?? null; + if (isset($locale) && ContactFormType::make($locale)->equals(ContactFormType::nl)) { + app()->setLocale('nl'); + \dispatch(new SendNLMail($data, $joinFilePath)); + } else { + \dispatch(new SendClaimMail($data, $joinFilePath)); + } + } //end if return back()->with('success', trans('contact.message_contact_send')); } catch (AbortContactMailer $e) { Log::error('Fail to build mail from complain page ', ['exception' => $e]); } //end try - return back()->withInput()->with( - 'error', - trans('contact.message_contact_failed') - ); + return back()->withInput()->with('error', trans('contact.message_contact_failed')); }); } @@ -91,7 +114,6 @@ class ContactController extends Controller ); $joinFilePath = storage_path('app/' . $joinFile); } - return $joinFilePath; } } diff --git a/app/Http/Requests/Fo/ContactFormRequest.php b/app/Http/Requests/Fo/ContactFormRequest.php index a6e617fa..ae631ff8 100644 --- a/app/Http/Requests/Fo/ContactFormRequest.php +++ b/app/Http/Requests/Fo/ContactFormRequest.php @@ -15,7 +15,7 @@ class ContactFormRequest extends FormRequest * * @return boolean */ - public function authorize() + public function authorize(): bool { return true; } @@ -25,31 +25,29 @@ class ContactFormRequest extends FormRequest * * @return array */ - public function rules() + public function rules(): array { ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version')); $rules = [ - 'locale' => [ + 'locale' => [ 'nullable', Rule::enum(ContactFormType::class)->only(ContactFormType::getLocalesContactType()) ], - 'type' => [ + 'type' => [ 'required', Rule::enum(ContactFormType::class)->only(ContactFormType::getContactTypes()) ], - 'civility' => ['required', Rule::enum(ContactFormCivility::class)], - // Max arcadia 50 chars. - 'lastname' => 'required|min:2|max:50', - // Max arcadia 50 chars. - 'firstname' => 'required|min:2|max:50', - 'address' => 'required|min:4', - 'email' => 'required|email:rfc,dns,spoof', - 'postal' => 'required|postal_code:FR', - // Max arcadia 10 chars. - 'town' => 'required|min:3|max:90', - 'phone' => 'required|phone:FR', - 'message' => 'required|min:20|max:1900' + 'civility' => ['required', Rule::enum(ContactFormCivility::class)], + 'lastname' => 'required|min:2|max:50', + 'firstname' => 'required|min:2|max:50', + 'address' => 'required|min:4', + 'email' => 'required|email:rfc,dns,spoof', + 'postal' => 'required|postal_code:FR', + 'town' => 'required|min:3|max:90', + 'phone' => 'required|phone:FR', + 'message' => 'required|min:20|max:1900', + 'crmsubject' => ['required', Rule::in(array_values(ContactFormType::crmIds()))], ]; if (ContactFormType::make($this->input('type'))->equals(ContactFormType::complain)) { @@ -74,7 +72,7 @@ class ContactFormRequest extends FormRequest $rules = array_merge( $rules, [ - 'joinFile' => 'nullable|mimes:jpg,jpeg,png,pdf|max:5120' + 'joinFile' => 'nullable|mimes:jpg,jpeg,png,pdf|max:5120' ], ); } @@ -103,7 +101,8 @@ class ContactFormRequest extends FormRequest 'consumptionDateDay' => sprintf('%s - %s', trans('validation.custom.consumption_date'), trans('day')), 'consumptionDateMonth' => sprintf('%s - %s', trans('validation.custom.consumption_date'), trans('month')), 'consumptionDateYear' => sprintf('%s - %s', trans('validation.custom.consumption_date'), trans('year')), - 'joinFile' => trans('validation.custom.attachment') + 'joinFile' => trans('validation.custom.attachment'), + 'crmsubject' => trans('validation.custom.crmsubject'), ]; } } diff --git a/app/Jobs/Mails/SendArcadiaMail.php b/app/Jobs/Mails/SendClaimMail.php similarity index 94% rename from app/Jobs/Mails/SendArcadiaMail.php rename to app/Jobs/Mails/SendClaimMail.php index 3be07d53..45641a76 100644 --- a/app/Jobs/Mails/SendArcadiaMail.php +++ b/app/Jobs/Mails/SendClaimMail.php @@ -11,7 +11,7 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; -class SendArcadiaMail implements ShouldQueue +class SendClaimMail implements ShouldQueue { use Dispatchable; use InteractsWithQueue; @@ -49,7 +49,7 @@ class SendArcadiaMail implements ShouldQueue * * @return void */ - public function handle() + public function handle(): void { Mail::to(config('mail.contactsList.contact'))->send(new ContactMail($this->data, $this->file)); if (file_exists($this->file)) { diff --git a/app/Mail/ContactMail.php b/app/Mail/ContactMail.php index 2d7d35b4..87cb8f85 100644 --- a/app/Mail/ContactMail.php +++ b/app/Mail/ContactMail.php @@ -7,9 +7,6 @@ use Illuminate\Bus\Queueable; use Illuminate\Mail\Attachment; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; -use League\Csv\Bom; -use League\Csv\Writer; -use SplTempFileObject; class ContactMail extends Mailable { @@ -54,12 +51,7 @@ class ContactMail extends Mailable ->subject(sprintf('Nouveau message consommateur Le Gaulois (%s)', $this->data->type->label())) ->view('emails.contact') ->text('emails.contact-plain') - ->with('data', $this->data) - ->attachData( - $this->makeCsvFile(), - sprintf('contact_le_gaulois_%d.csv', time()), - ['mime' => 'text/csv'] - ); + ->with('data', $this->data); } /** @@ -76,39 +68,4 @@ class ContactMail extends Mailable Attachment::fromPath($this->file)->as($this->data->joinFile), ]; } - - /** - * @return string - */ - protected function makeCsvFile(): string - { - // Creation of the CSV into memory. - $csv = Writer::createFromFileObject(new SplTempFileObject()); - $csv->setDelimiter(';'); - $csv->setEnclosure('"'); - $csv->setOutputBOM(Bom::Utf8); - - // Insert the CSV header. - $csv->insertOne([ - 'EMETTEUR_DATE', - 'EMETTEUR_HEURE', - 'EMETTEUR_CIVILITE', - 'EMETTEUR_NOM', - 'EMETTEUR_PRENOM', - 'EMETTEUR_ADRESSE', - 'EMETTEUR_CP', - 'EMETTEUR_VILLE', - 'EMETTEUR_EMAIL', - 'EMETTEUR_OBJETDEMANDE', - 'EMETTEUR_MESSAGE', - 'EMETTEUR_CATEGORIE', - 'EMETTEUR_ACTIVITE', - 'REPONSE_ETAT', - 'TYPE_REPONSE' - ]); - - $csv->insertOne($this->data->exportToArcadiaDataArray()); - - return $csv->toString(); - } } diff --git a/app/Models/MailContactData.php b/app/Models/MailContactData.php index 16d69a59..5408e06c 100644 --- a/app/Models/MailContactData.php +++ b/app/Models/MailContactData.php @@ -112,32 +112,6 @@ class MailContactData extends Model $this->joinFile = self::getFileName($filePath ?? ''); } - /** - * Export the model data to a Formatted Arcadia Data array. - * - * @return array - */ - public function exportToArcadiaDataArray(): array - { - return [ - date("Ymd"), - date("Gis"), - $this->civility->value, - $this->lastname, - $this->firstname, - $this->address, - $this->postal, - $this->town, - $this->email, - $this->type->value, - $this->message, - '0', - '0', - '0', - '0' - ]; - } - /** * Export the model data to a Formatted Passerelle Data array. * diff --git a/composer.json b/composer.json index e9233e0c..02444361 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "laravel/scout": "^10.11", "laravel/tinker": "^2.8", "laravel/ui": "^4.2", - "ldc/ldccrmv4client": "^4.0", + "ldc/ldccrmv4client": "^4.1", "league/csv": "^9.16", "meilisearch/meilisearch-php": "^1.11", "mews/purifier": "^3.4", diff --git a/composer.lock b/composer.lock index 5c279c6d..c3def65e 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": "8fc50392358e865d6aaa16358da98cd5", + "content-hash": "429c24f83ed59ad888013ce2787525ed", "packages": [ { "name": "24slides/laravel-saml2", @@ -2659,16 +2659,16 @@ }, { "name": "ldc/ldccrmv4client", - "version": "4.0.9", + "version": "4.1.4", "source": { "type": "git", "url": "git@lab.s-web.fr:ldc-web/projets/crm-client.git", - "reference": "b54f61add50e93c68d26cadbe55d29216fcfc666" + "reference": "9aa66a8b3f62c401b0fd43616ba5fd52fe15541a" }, "dist": { "type": "zip", - "url": "https://lab.s-web.fr/api/v4/projects/26/packages/composer/archives/ldc/ldccrmv4client.zip?sha=b54f61add50e93c68d26cadbe55d29216fcfc666", - "reference": "b54f61add50e93c68d26cadbe55d29216fcfc666", + "url": "https://lab.s-web.fr/api/v4/projects/26/packages/composer/archives/ldc/ldccrmv4client.zip?sha=9aa66a8b3f62c401b0fd43616ba5fd52fe15541a", + "reference": "9aa66a8b3f62c401b0fd43616ba5fd52fe15541a", "shasum": "" }, "require": { @@ -13946,13 +13946,13 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.3", "ext-bcmath": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/config/crm.php b/config/crm.php index 0c4f0644..9a3f6111 100644 --- a/config/crm.php +++ b/config/crm.php @@ -5,4 +5,6 @@ return [ 'token' => env('CRM_TOKEN'), 'password' => env('CRM_PASSWORD'), 'question' => env('CRM_QUESTION_ID'), + 'brand' => env('CRM_BRAND_ID'), + 'subjects' => env('CRM_SUBJECTS'), ]; diff --git a/manifest.yaml b/manifest.yaml index 17564d8b..f071104a 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -28,6 +28,8 @@ data: CRM_TOKEN: "${APP_CRM_TOKEN}" CRM_PASSWORD: "${APP_CRM_PASSWORD}" CRM_QUESTION_ID: "${APP_CRM_QUESTION_ID}" + CRM_BRAND_ID: "${APP_CRM_BRAND_ID}" + CRM_SUBJECTS=: "${APP_CRM_SUBJECTS}" MESBR_REF_SITE: "${APP_MESBR_REF_SITE}" MESBR_REF_CAMP: "${APP_MESBR_REF_CAMP}" MESBR_ENDPOINT: "${APP_MESBR_ENDPOINT}" diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 617bc219..600bc8e4 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -256,6 +256,7 @@ return [ 'color' => 'color', 'contract' => 'contract', 'cooking_time' => 'cooking time (in minutes)', + 'crmsubject' => 'claim subject', 'demarche' => 'demarche', 'details' => 'details', 'difficulty' => 'difficulty', diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php index 2dab5247..4d2b6c17 100644 --- a/resources/lang/fr/validation.php +++ b/resources/lang/fr/validation.php @@ -259,6 +259,7 @@ return [ 'color' => 'couleur', 'contract' => 'contrat', 'cooking_time' => 'temps de cuisson (en minutes)', + 'crmsubject' => 'sujet de réclamation', 'demarche' => 'démarche', 'details' => 'détails', 'difficulty' => 'difficulté', diff --git a/resources/ts/fo/partials/contact-form.ts b/resources/ts/fo/partials/contact-form.ts index 8d5dbf04..05246bc7 100644 --- a/resources/ts/fo/partials/contact-form.ts +++ b/resources/ts/fo/partials/contact-form.ts @@ -2,21 +2,34 @@ document.addEventListener("DOMContentLoaded", function () { const checkedSubject = document.querySelector("input[name=\"type\"]:checked") as HTMLInputElement; const typeRadioInputs = document.querySelectorAll("input[name=\"type\"]") as NodeListOf; + const crmSubjectInput = document.querySelector("input[name=\"crmsubject\"]") as HTMLInputElement; if (!document.querySelector("#contact-form") || !checkedSubject || !typeRadioInputs) { return; } checkRadioChecked(checkedSubject.value); + updateCrmSubjectValue(checkedSubject, crmSubjectInput); for (const typeRadioInput of typeRadioInputs) { typeRadioInput.addEventListener("change", (e: Event) => { if (e.currentTarget instanceof HTMLInputElement) { checkRadioChecked(e.currentTarget.value); + updateCrmSubjectValue(e.currentTarget, crmSubjectInput); } }); } }); +/** + * Update the crm subject value depending on checked radio input. + * @param input HTMLInputElement + * @param crmSubjectInput HTMLInputElement + * @return void + */ +function updateCrmSubjectValue(input: HTMLInputElement, crmSubjectInput: HTMLInputElement): void { + crmSubjectInput.value = input.getAttribute("data-crm-subject") ?? ""; +} + function checkRadioChecked(value: string) { // Display complain form if the current checked radio is equal to ContactFormType::complain. displayComplainForm(value === "3"); @@ -28,8 +41,10 @@ function checkRadioChecked(value: string) { /** * Display the complain part if shouldDisplay is true. + * @param shouldDisplay boolean + * @return void */ -function displayComplainForm(shouldDisplay: boolean) { +function displayComplainForm(shouldDisplay: boolean): void { const complainForm = document.querySelector("#complain-part"); if (!complainForm) { return; @@ -51,8 +66,10 @@ function displayComplainForm(shouldDisplay: boolean) { /** * Display the file input if shouldDisplay is true. + * @param shouldDisplay boolean + * @return void */ -function displayFileInput(shouldDisplay: boolean) { +function displayFileInput(shouldDisplay: boolean): void { const fileInputContainer = document.querySelector("#file-input") as HTMLInputElement; if (!fileInputContainer) { return; diff --git a/resources/views/components/fo/contact/contact-form.blade.php b/resources/views/components/fo/contact/contact-form.blade.php index 6cbf55ef..e0e4019a 100644 --- a/resources/views/components/fo/contact/contact-form.blade.php +++ b/resources/views/components/fo/contact/contact-form.blade.php @@ -34,8 +34,8 @@ - + @include('modules.input-error', [ 'inputName' => 'message', 'helperId' => 'messageHelp', @@ -59,7 +59,9 @@ sprintf( '%s
%s', __('contact.attachment.allowed-formats', ['formats' => 'jpg, png, pdf']), - __('contact.attachment.max-size', ['max' => '5 Mo']))), + __('contact.attachment.max-size', ['max' => '5 Mo']) + ) + ), ]) @@ -74,7 +76,8 @@ @endif - + {{-- CRM SUBJECT --}} + {{-- SUBMIT BUTTON --}}