Compare commits
10 commits
master
...
57-sync-op
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cec2ab5343 | ||
|
|
174171bd34 | ||
|
|
45ab798b59 | ||
|
|
8170bb395a | ||
|
|
b04ca114ae | ||
|
|
27d0c1fc21 | ||
|
|
d0828ae213 | ||
|
|
b30af29d0f | ||
|
|
6fabb40ab6 | ||
|
|
98ef3c826e |
25 changed files with 1010 additions and 96 deletions
|
|
@ -49,3 +49,7 @@ SAML2_SP_CERT_x509=
|
|||
SAML2_SP_CERT_PRIVATEKEY=
|
||||
SAML2_CONTACT_TECHNICAL_EMAIL=
|
||||
SAML2_CONTACT_SUPPORT_EMAIL=
|
||||
|
||||
# MJ - API
|
||||
MJ_APIKEY_PUBLIC=
|
||||
MJ_APIKEY_PRIVATE=
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class Kernel extends ConsoleKernel
|
|||
// @codeCoverageIgnoreStart
|
||||
$schedule->job(StartStatJobs::class)->dailyAt('03:30');
|
||||
$schedule->job(SendTransferredToDigitalCountMail::class)->weekdays()->at('07:00');
|
||||
$schedule->job(SyncEmailOptOuts::class)->dailyAt('00:00');
|
||||
$schedule->command(DeleteUnconfirmedCustomers::class)->dailyAt('05:30');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
|
|
|||
32
app/Exceptions/MailjetException.php
Normal file
32
app/Exceptions/MailjetException.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Mailjet\Response;
|
||||
use RuntimeException;
|
||||
|
||||
class MailjetException extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Error message returned by the Mailjet API
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $returnedMessage;
|
||||
|
||||
/**
|
||||
* Create a new Mailjet exception
|
||||
*
|
||||
* @param \Mailjet\Response $response
|
||||
* @param string $message
|
||||
*/
|
||||
public function __construct(Response $response, string $message)
|
||||
{
|
||||
$this->returnedMessage = $response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase();
|
||||
|
||||
parent::__construct(
|
||||
$message,
|
||||
$response->getStatus()
|
||||
);
|
||||
}
|
||||
}
|
||||
46
app/Http/Controllers/Api/WebhookController.php
Normal file
46
app/Http/Controllers/Api/WebhookController.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Lib\Helpers\MailjetHelper;
|
||||
use App\Models\Brand;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Exceptions\MailjetException;
|
||||
|
||||
class WebhookController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handles incoming Mailjet webhook events
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function handleMailjetWebhook(Request $request): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
if ($data["event"] === "spam") {
|
||||
try {
|
||||
$campaign = MailjetHelper::getCampaign($data['mj_campaign_id']);
|
||||
|
||||
if (!$campaign) {
|
||||
return response()->json(['status' => 'error', 'message' => 'Campaign not found']);
|
||||
}
|
||||
|
||||
$brand = Brand::getBrandByNewsletterEmail($campaign[0]['FromEmail']);
|
||||
if (!$brand) {
|
||||
return response()->json(['status' => 'error', 'message' => 'Brand not found']);
|
||||
}
|
||||
|
||||
MailjetHelper::unsubscribeContact($brand, $data['email']);
|
||||
|
||||
return response()->json(['status' => 'ok']);
|
||||
} catch (MailjetException $e) {
|
||||
report($e);
|
||||
|
||||
return response()->json(['status' => 'error', 'message' => $e->returnedMessage]);
|
||||
}
|
||||
}//end if
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ use App\Models\CustomerInfo;
|
|||
use App\Models\CustomerLogin;
|
||||
use App\Models\Question;
|
||||
use App\Models\SingleOptin;
|
||||
use App\Models\BrandContactList;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -23,6 +24,8 @@ use Illuminate\Support\Facades\Session;
|
|||
use Illuminate\Support\Str;
|
||||
use League\Csv\Writer;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use App\Lib\Helpers\MailjetHelper;
|
||||
use App\Exceptions\MailjetException;
|
||||
|
||||
class CustomerController extends Controller
|
||||
{
|
||||
|
|
@ -168,34 +171,19 @@ class CustomerController extends Controller
|
|||
* Exports customers following the requested export type.
|
||||
*
|
||||
* @param \App\Http\Requests\Bo\CustomerExport $request
|
||||
* @return \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
* @return \Symfony\Component\HttpFoundation\StreamedResponse|\Illuminate\Http\RedirectResponse
|
||||
* @throws \Exception If the request export type in not handled.
|
||||
*/
|
||||
public function exportSubmit(CustomerExport $request): \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
{
|
||||
public function exportSubmit(
|
||||
CustomerExport $request
|
||||
): \Symfony\Component\HttpFoundation\StreamedResponse|\Illuminate\Http\RedirectResponse {
|
||||
$pagination = 2000;
|
||||
$header = '"lastname";"firstname";"email";"hashcode"';
|
||||
|
||||
$w = Writer::createFromString($header . PHP_EOL);
|
||||
$w->setDelimiter(';');
|
||||
$w->setEnclosure('"');
|
||||
$w->setOutputBOM(Writer::BOM_UTF8);
|
||||
$w = $this->buildCsvWriter($request->action);
|
||||
|
||||
$exportType = CustomerExportType::make(\intval($request->export_type));
|
||||
|
||||
// * We send the CSV using Symfony StreamedResponse.
|
||||
$response = new StreamedResponse();
|
||||
$response->headers->set('Content-Encoding', 'none');
|
||||
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
|
||||
$response->headers->set('Description', 'File Transfer');
|
||||
$response->headers->set(
|
||||
'Content-Disposition',
|
||||
sprintf(
|
||||
'attachment; filename="Export pour newsletter %s %s.csv"',
|
||||
Carbon::now()->isoFormat('DD-MM-YYYY_HH-mm'),
|
||||
Str::slug($exportType->label())
|
||||
)
|
||||
);
|
||||
$response = $this->buildStreamedResponse($exportType);
|
||||
|
||||
switch ($exportType) {
|
||||
case CustomerExportType::both:
|
||||
|
|
@ -247,24 +235,11 @@ class CustomerController extends Controller
|
|||
throw new \Exception(sprintf('Unhandled CustomerExportType %s', $exportType->label()));
|
||||
} //end switch
|
||||
|
||||
/*
|
||||
We create a callable to output the CSV in chunk
|
||||
with Symfony StreamResponse you can flush the body content if necessary
|
||||
see Symfony documentation for more information
|
||||
*/
|
||||
if ($request->action === "export") {
|
||||
return $this->buildCsvDownloadResponse($response, $w);
|
||||
}
|
||||
|
||||
// * The flush value should depend on your CSV size.
|
||||
$flush_threshold = 1000;
|
||||
|
||||
$response->setCallback(function () use ($w, $flush_threshold) {
|
||||
foreach ($w->chunk(1024) as $offset => $chunk) {
|
||||
echo $chunk;
|
||||
if ($offset % $flush_threshold === 0) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
});
|
||||
return $response;
|
||||
return $this->importToMailjet($request, $w);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -313,6 +288,123 @@ class CustomerController extends Controller
|
|||
->join('brands', fn($join) => $join->on('brands.id', '=', 'customer_infos.brand_id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the streamed HTTP response.
|
||||
*
|
||||
* @param CustomerExportType $exportType
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
private function buildStreamedResponse(CustomerExportType $exportType): StreamedResponse
|
||||
{
|
||||
// * We send the CSV using Symfony StreamedResponse.
|
||||
$response = new StreamedResponse();
|
||||
$response->headers->set('Content-Encoding', 'none');
|
||||
$response->headers->set('Content-Type', 'text/csv; charset=UTF-8');
|
||||
$response->headers->set('Description', 'File Transfer');
|
||||
$response->headers->set(
|
||||
'Content-Disposition',
|
||||
sprintf(
|
||||
'attachment; filename="Export pour newsletter %s %s.csv"',
|
||||
Carbon::now()->isoFormat('DD-MM-YYYY_HH-mm'),
|
||||
Str::slug($exportType->label())
|
||||
)
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the CSV writer.
|
||||
*
|
||||
* @param string $action
|
||||
* @return Writer
|
||||
*/
|
||||
private function buildCsvWriter(string $action): Writer
|
||||
{
|
||||
if ($action === 'export') {
|
||||
$header = '"lastname";"firstname";"email";"hashcode"';
|
||||
|
||||
$w = Writer::createFromString($header . PHP_EOL);
|
||||
$w->setDelimiter(';');
|
||||
} else {
|
||||
$header = 'lastname,firstname,email,hashcode';
|
||||
|
||||
$w = Writer::createFromString($header . PHP_EOL);
|
||||
$w->setDelimiter(',');
|
||||
}
|
||||
|
||||
$w->setEnclosure('"');
|
||||
$w->setOutputBOM(Writer::BOM_UTF8);
|
||||
|
||||
return $w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a download callback to the response.
|
||||
*
|
||||
* @param StreamedResponse $response
|
||||
* @param Writer $writer
|
||||
* @return StreamedResponse
|
||||
*/
|
||||
private function buildCsvDownloadResponse(StreamedResponse $response, Writer $writer): StreamedResponse
|
||||
{
|
||||
/*
|
||||
We create a callable to output the CSV in chunk
|
||||
with Symfony StreamResponse you can flush the body content if necessary
|
||||
see Symfony documentation for more information
|
||||
*/
|
||||
|
||||
// * The flush value should depend on your CSV size.
|
||||
$flush_threshold = 1000;
|
||||
|
||||
$response->setCallback(function () use ($writer, $flush_threshold) {
|
||||
foreach ($writer->chunk(1024) as $offset => $chunk) {
|
||||
echo $chunk;
|
||||
if ($offset % $flush_threshold === 0) {
|
||||
flush();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import contacts to Mailjet
|
||||
*
|
||||
* @param CustomerExport $request
|
||||
* @param Writer $writer
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
private function importToMailjet(CustomerExport $request, Writer $writer): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$brandModel = Brand::find($request->brand);
|
||||
|
||||
try {
|
||||
$contactList = MailjetHelper::createContactList(
|
||||
sprintf("%s - %s", now()->format('d/m/Y'), $brandModel->name)
|
||||
);
|
||||
|
||||
$mailjetListId = $contactList[0]['ID'];
|
||||
|
||||
$csvData = MailjetHelper::uploadCsvFile($mailjetListId, $writer->toString());
|
||||
MailjetHelper::importContacts($mailjetListId, $csvData['ID']);
|
||||
|
||||
BrandContactList::createBrandContactList(
|
||||
$brandModel,
|
||||
$mailjetListId,
|
||||
);
|
||||
|
||||
return back()->with('success', 'Liste créée et contacts importés avec succès.');
|
||||
} catch (MailjetException $e) {
|
||||
report($e);
|
||||
return back()->withErrors(sprintf(
|
||||
'Impossible de créer la liste de contacts Mailjet : %s.',
|
||||
$e->returnedMessage
|
||||
));
|
||||
}//end try
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all filters (brand, question, answer).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -37,10 +37,11 @@ class StoreBrandRequest extends FormRequest
|
|||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'name' => 'required|unique:brands,name|string|min:5|max:255',
|
||||
'url' => 'required|url|min:5|max:255',
|
||||
'url_claim' => 'required|url|min:5|max:255',
|
||||
'domain' => 'required|unique:brands,domain|string|min:5|max:255',
|
||||
'name' => 'required|unique:brands,name|string|min:5|max:255',
|
||||
'url' => 'required|url|min:5|max:255',
|
||||
'url_claim' => 'required|url|min:5|max:255',
|
||||
'domain' => 'required|unique:brands,domain|string|min:5|max:255',
|
||||
'newsletter_email' => 'nullable|email:rfc,strict,dns,spoof,filter',
|
||||
];
|
||||
return \array_merge(
|
||||
$rules,
|
||||
|
|
@ -56,11 +57,12 @@ class StoreBrandRequest extends FormRequest
|
|||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => trans('validation.attributes.name'),
|
||||
'logo' => trans('validation.custom.logo'),
|
||||
'url' => trans('validation.custom.url'),
|
||||
'url_claim' => trans('validation.custom.url_claim'),
|
||||
'domain' => trans('validation.custom.domain'),
|
||||
'name' => trans('validation.attributes.name'),
|
||||
'logo' => trans('validation.custom.logo'),
|
||||
'url' => trans('validation.custom.url'),
|
||||
'url_claim' => trans('validation.custom.url_claim'),
|
||||
'domain' => trans('validation.custom.domain'),
|
||||
'newsletter_email' => trans('validation.custom.newsletter_email'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ class CustomerExport extends FormRequest
|
|||
$fail(trans('Le type d\'export invalide'));
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
'action' => 'required|in:export,create_mailjet'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
app/Jobs/SyncEmailOptOuts.php
Normal file
53
app/Jobs/SyncEmailOptOuts.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Lib\Helpers\MailjetHelper;
|
||||
use App\Models\BrandContactList;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use App\Exceptions\MailjetException;
|
||||
|
||||
class SyncEmailOptOuts implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$contactLists = MailjetHelper::getContactLists();
|
||||
|
||||
if (empty($contactLists)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($contactLists as $contactList) {
|
||||
$listId = $contactList['ID'];
|
||||
$brandModel = BrandContactList::getBrandContactListByMailjetListId($listId);
|
||||
|
||||
if ($brandModel) {
|
||||
$unsubscribedContacts = MailjetHelper::getUnsubscribedContacts($listId, Carbon::now()->subDay());
|
||||
|
||||
if (empty($unsubscribedContacts)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($unsubscribedContacts as $unsubscribedContact) {
|
||||
$contact = MailjetHelper::getContact($unsubscribedContact['ContactID']);
|
||||
|
||||
MailjetHelper::unsubscribeContact($brandModel->brand, $contact[0]["Email"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MailjetException $e) {
|
||||
report($e);
|
||||
return;
|
||||
}//end try
|
||||
}
|
||||
}
|
||||
286
app/Lib/Helpers/MailjetHelper.php
Normal file
286
app/Lib/Helpers/MailjetHelper.php
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use App\Models\Brand;
|
||||
use App\Models\CustomerInfo;
|
||||
use App\Models\SingleOptin;
|
||||
use App\Exceptions\MailjetException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Mailjet\Resources;
|
||||
|
||||
class MailjetHelper
|
||||
{
|
||||
/**
|
||||
* Get a campaign by ID
|
||||
*
|
||||
* @param integer $campaignId
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function getCampaign(int $campaignId): array
|
||||
{
|
||||
return Cache::remember("mailjet_campaign_{$campaignId}", 86400, function () use ($campaignId) {
|
||||
$response = app('mailjetClient')->get(Resources::$Campaign, [
|
||||
'id' => $campaignId
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to retrieve campaign
|
||||
(Campaign ID: %d | Error Code: %d | Message: %s)',
|
||||
$campaignId,
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves contact lists
|
||||
*
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function getContactLists(): array
|
||||
{
|
||||
$filters = [
|
||||
'Limit' => 1000,
|
||||
'Sort' => 'CreatedAt DESC',
|
||||
];
|
||||
|
||||
$response = app('mailjetClient')->get(Resources::$Contactslist, [
|
||||
'filters' => $filters
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to retrieve contact lists (Code: %d | Message: %s)',
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unsubscribed contacts for a given contact list
|
||||
*
|
||||
* @param integer $contactListId
|
||||
* @param \Carbon\Carbon|null $lastActivity
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function getUnsubscribedContacts(int $contactListId, \Carbon\Carbon|null $lastActivity = null): array
|
||||
{
|
||||
$filters = [
|
||||
'ContactsList' => $contactListId,
|
||||
'Unsub' => true,
|
||||
'Limit' => 1000,
|
||||
];
|
||||
|
||||
if (!is_null($lastActivity)) {
|
||||
$filters = array_merge(
|
||||
$filters,
|
||||
[
|
||||
'LastActivityAt' => $lastActivity->toRfc3339String()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$response = app('mailjetClient')->get(Resources::$Listrecipient, [
|
||||
'filters' => $filters
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to retrieve unsubscribed contacts for list %d (Code: %d | Message: %s)',
|
||||
$contactListId,
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contact for a given contact id
|
||||
*
|
||||
* @param integer $contactId
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function getContact(int $contactId): array
|
||||
{
|
||||
$response = app('mailjetClient')->get(Resources::$Contact, [
|
||||
'id' => $contactId
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to retrieve contact (Contact ID: %d | Error Code: %d | Message: %s)',
|
||||
$contactId,
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Mailjet contact list
|
||||
*
|
||||
* @param string $listName
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function createContactList(string $listName): array
|
||||
{
|
||||
$response = app('mailjetClient')->post(Resources::$Contactslist, [
|
||||
'body' => [
|
||||
'Name' => $listName,
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to create contact list (List Name: %s | Error Code: %d | Message: %s)',
|
||||
$listName,
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a CSV file
|
||||
*
|
||||
* @param integer $mailjetListId
|
||||
* @param string $csvFile
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function uploadCsvFile(int $mailjetListId, string $csvFile): array
|
||||
{
|
||||
$response = app('mailjetClient')->post(Resources::$ContactslistCsvdata, [
|
||||
'id' => $mailjetListId,
|
||||
'body' => $csvFile,
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to upload csv file (List ID: %d | Error Code: %d | Message: %s)',
|
||||
$mailjetListId,
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports contacts into a Mailjet contact list
|
||||
*
|
||||
* @param integer $listId
|
||||
* @param integer $dataId
|
||||
* @return array
|
||||
* @throws \App\Exceptions\MailjetException If the Mailjet API call fails.
|
||||
*/
|
||||
public static function importContacts(int $listId, int $dataId): array
|
||||
{
|
||||
$response = app('mailjetClient')->post(Resources::$Csvimport, [
|
||||
'body' => [
|
||||
'ContactsListID' => $listId,
|
||||
'DataID' => $dataId,
|
||||
'Method' => "addnoforce"
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->success()) {
|
||||
return $response->getData();
|
||||
} else {
|
||||
throw new MailjetException(
|
||||
$response,
|
||||
sprintf(
|
||||
'Mailjet API Error: Unable to import contacts
|
||||
(List ID: %d | Data ID: %d | Error Code: %d | Message: %s)',
|
||||
$listId,
|
||||
$dataId,
|
||||
$response->getStatus(),
|
||||
$response->getBody()['ErrorMessage'] ?? $response->getReasonPhrase()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe a contact from a brand
|
||||
*
|
||||
* @param \App\Models\Brand $brandModel
|
||||
* @param string $email
|
||||
* @return void
|
||||
* @throws \Exception When optin status cannot be changed.
|
||||
*/
|
||||
public static function unsubscribeContact(Brand $brandModel, string $email): void
|
||||
{
|
||||
$crmCustomer = CustomerInfo::getFromEmail($brandModel, $email)->first();
|
||||
|
||||
if ($crmCustomer) {
|
||||
$crmCustomer->opt_in = false;
|
||||
|
||||
if (!$crmCustomer->saveOrFail()) {
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'Optin status not changed for customer (%s) and brand (%d).',
|
||||
$crmCustomer->hashcode,
|
||||
$brandModel->name
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$singleOptin = SingleOptin::query()->where('email', $email)
|
||||
->whereHas('brand', function ($q) use ($brandModel) {
|
||||
$q->whereId($brandModel->getKey());
|
||||
})->first();
|
||||
|
||||
if ($singleOptin) {
|
||||
if (!$singleOptin->delete()) {
|
||||
throw new \Exception(
|
||||
sprintf(
|
||||
'SingleOptin (%s) not deleted for brand (%d).',
|
||||
$singleOptin->hashcode,
|
||||
$brandModel->name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} //end if
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Enums\Users\RoleEnum;
|
||||
use App\Models\BrandContactList;
|
||||
use App\Traits\Models\ActivityLog;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -10,14 +11,15 @@ use Illuminate\Support\Facades\Cache;
|
|||
use Uwil\LaravelCommon\Helpers\FileHelper;
|
||||
|
||||
/**
|
||||
* @property integer $id Brand id.
|
||||
* @property string $name Brand name.
|
||||
* @property string $logo Brand logo.
|
||||
* @property string $url Brand url link.
|
||||
* @property string $url_claim Brand associative url claim.
|
||||
* @property string $domain Brand domain.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Brand created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Brand updated date.
|
||||
* @property integer $id Brand id.
|
||||
* @property string $name Brand name.
|
||||
* @property string $logo Brand logo.
|
||||
* @property string $url Brand url link.
|
||||
* @property string $url_claim Brand associative url claim.
|
||||
* @property string $domain Brand domain.
|
||||
* @property string $newsletter_email Brand associative newsletter email.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Brand created date.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Brand updated date.
|
||||
*
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<App\Models\Service>
|
||||
* $services Service HasMany relation.
|
||||
|
|
@ -46,6 +48,7 @@ class Brand extends Model
|
|||
'url',
|
||||
'url_claim',
|
||||
'domain',
|
||||
'newsletter_email',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -141,6 +144,19 @@ class Brand extends Model
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get brand by newsletter_email.
|
||||
*
|
||||
* @param string $newsletter_email
|
||||
* @return Brand|null
|
||||
*/
|
||||
public static function getBrandByNewsletterEmail(string $newsletter_email): ?Brand
|
||||
{
|
||||
return Cache::remember("brand_{$newsletter_email}", 86400, function () use ($newsletter_email) {
|
||||
return self::query()->where('newsletter_email', $newsletter_email)->first();
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
|
|
@ -232,4 +248,15 @@ class Brand extends Model
|
|||
{
|
||||
return $this->hasMany(ClaimMailTemplate::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* BrandContactList has many relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function contactLists(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(BrandContactList::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
65
app/Models/BrandContactList.php
Normal file
65
app/Models/BrandContactList.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class BrandContactList extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'brand_id',
|
||||
'mailjet_list_id',
|
||||
];
|
||||
|
||||
// * METHODS
|
||||
|
||||
/**
|
||||
* Create a brand contact list.
|
||||
*
|
||||
* @param Brand $brand
|
||||
* @param integer $mailjetListId
|
||||
* @return BrandContactList
|
||||
*/
|
||||
public static function createBrandContactList(Brand $brand, int $mailjetListId): BrandContactList
|
||||
{
|
||||
$brandContactList = self::create([
|
||||
'brand_id' => $brand->getKey(),
|
||||
'mailjet_list_id' => $mailjetListId,
|
||||
]);
|
||||
|
||||
Cache::put("brand_contact_list_{$mailjetListId}", $brandContactList, 86400);
|
||||
|
||||
return $brandContactList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get brand contact list by mailjet_list_id.
|
||||
*
|
||||
* @param integer $mailjetListId
|
||||
* @return BrandContactList|null
|
||||
*/
|
||||
public static function getBrandContactListByMailjetListId(int $mailjetListId): ?BrandContactList
|
||||
{
|
||||
return Cache::remember("brand_contact_list_{$mailjetListId}", 86400, function () use ($mailjetListId) {
|
||||
return self::query()->where('mailjet_list_id', $mailjetListId)->with('brand')->first();
|
||||
});
|
||||
}
|
||||
|
||||
// * RELATIONS
|
||||
|
||||
/**
|
||||
* Brand belongs to relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function brand(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Brand::class);
|
||||
}
|
||||
}
|
||||
34
app/Providers/MailjetServiceProvider.php
Normal file
34
app/Providers/MailjetServiceProvider.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class MailjetServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Mailjet client to call the API.
|
||||
App::singleton('mailjetClient', function () {
|
||||
$mailjetClient = new \Mailjet\Client(
|
||||
config('services.mailjet.public'),
|
||||
config('services.mailjet.secret'),
|
||||
true,
|
||||
[
|
||||
'version' => 'v3',
|
||||
]
|
||||
);
|
||||
|
||||
$mailjetClient->setConnectionTimeout(200);
|
||||
$mailjetClient->setTimeout(200);
|
||||
|
||||
return $mailjetClient;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^4.2",
|
||||
"league/csv": "^9.7",
|
||||
"mailjet/mailjet-apiv3-php": "^1.6",
|
||||
"mews/purifier": "^3.4",
|
||||
"propaganistas/laravel-phone": "^5.0",
|
||||
"sentry/sentry-laravel": "^4.10",
|
||||
|
|
|
|||
167
composer.lock
generated
167
composer.lock
generated
|
|
@ -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": "1417f675b37867e72bdd34d5b0fcf629",
|
||||
"content-hash": "70aad663cba0f4125a01bc8e53999abc",
|
||||
"packages": [
|
||||
{
|
||||
"name": "24slides/laravel-saml2",
|
||||
|
|
@ -3562,6 +3562,67 @@
|
|||
],
|
||||
"time": "2024-12-08T08:18:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mailjet/mailjet-apiv3-php",
|
||||
"version": "v1.6.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mailjet/mailjet-apiv3-php.git",
|
||||
"reference": "9d5cea25f347719d7df7909fb4a43a72bd4e5011"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mailjet/mailjet-apiv3-php/zipball/9d5cea25f347719d7df7909fb4a43a72bd4e5011",
|
||||
"reference": "9d5cea25f347719d7df7909fb4a43a72bd4e5011",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/guzzle": "^7.4.4",
|
||||
"php": "^8.1",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/validator": "^6.3|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.4",
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"phpcompatibility/php-compatibility": "*",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpunit/phpunit": "^11.4.0",
|
||||
"squizlabs/php_codesniffer": "*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Mailjet": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mailjet",
|
||||
"email": "dev@mailjet.com",
|
||||
"homepage": "https://dev.mailjet.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP wrapper for the Mailjet API",
|
||||
"homepage": "https://github.com/mailjet/mailjet-apiv3-php/",
|
||||
"keywords": [
|
||||
"Mailjet",
|
||||
"api",
|
||||
"email",
|
||||
"php",
|
||||
"v3"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mailjet/mailjet-apiv3-php/issues",
|
||||
"source": "https://github.com/mailjet/mailjet-apiv3-php/tree/v1.6.6"
|
||||
},
|
||||
"time": "2025-12-24T15:55:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
"version": "2.10.0",
|
||||
|
|
@ -7953,6 +8014,110 @@
|
|||
],
|
||||
"time": "2024-09-25T14:21:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/validator",
|
||||
"version": "v7.4.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/validator.git",
|
||||
"reference": "8f73cbddae916756f319b3e195088da216f0f12f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/validator/zipball/8f73cbddae916756f319b3e195088da216f0f12f",
|
||||
"reference": "8f73cbddae916756f319b3e195088da216f0f12f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-ctype": "~1.8",
|
||||
"symfony/polyfill-mbstring": "~1.0",
|
||||
"symfony/polyfill-php83": "^1.27",
|
||||
"symfony/translation-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/lexer": "<1.1",
|
||||
"symfony/dependency-injection": "<6.4",
|
||||
"symfony/doctrine-bridge": "<7.0",
|
||||
"symfony/expression-language": "<6.4",
|
||||
"symfony/http-kernel": "<6.4",
|
||||
"symfony/intl": "<6.4",
|
||||
"symfony/property-info": "<6.4",
|
||||
"symfony/translation": "<6.4.3|>=7.0,<7.0.3",
|
||||
"symfony/var-exporter": "<6.4.25|>=7.0,<7.3.3",
|
||||
"symfony/yaml": "<6.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"egulias/email-validator": "^2.1.10|^3|^4",
|
||||
"symfony/cache": "^6.4|^7.0|^8.0",
|
||||
"symfony/config": "^6.4|^7.0|^8.0",
|
||||
"symfony/console": "^6.4|^7.0|^8.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
|
||||
"symfony/expression-language": "^6.4|^7.0|^8.0",
|
||||
"symfony/finder": "^6.4|^7.0|^8.0",
|
||||
"symfony/http-client": "^6.4|^7.0|^8.0",
|
||||
"symfony/http-foundation": "^6.4|^7.0|^8.0",
|
||||
"symfony/http-kernel": "^6.4|^7.0|^8.0",
|
||||
"symfony/intl": "^6.4|^7.0|^8.0",
|
||||
"symfony/mime": "^6.4|^7.0|^8.0",
|
||||
"symfony/process": "^6.4|^7.0|^8.0",
|
||||
"symfony/property-access": "^6.4|^7.0|^8.0",
|
||||
"symfony/property-info": "^6.4|^7.0|^8.0",
|
||||
"symfony/string": "^6.4|^7.0|^8.0",
|
||||
"symfony/translation": "^6.4.3|^7.0.3|^8.0",
|
||||
"symfony/type-info": "^7.1.8",
|
||||
"symfony/yaml": "^6.4|^7.0|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Validator\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/",
|
||||
"/Resources/bin/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides tools to validate values",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/validator/tree/v7.4.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-30T12:55:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/var-dumper",
|
||||
"version": "v7.2.0",
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ return [
|
|||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\TranslationServiceProvider::class,
|
||||
App\Providers\ViewServiceProvider::class,
|
||||
App\Providers\MailjetServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -31,4 +31,8 @@ return [
|
|||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'mailjet' => [
|
||||
'public' => env('MJ_APIKEY_PUBLIC'),
|
||||
'secret' => env('MJ_APIKEY_PRIVATE'),
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@ class BrandFactory extends Factory
|
|||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->unique()->company,
|
||||
'url' => $this->faker->url,
|
||||
'url_claim' => $this->faker->url,
|
||||
'domain' => $this->faker->unique()->domainName,
|
||||
'name' => $this->faker->unique()->company,
|
||||
'url' => $this->faker->url,
|
||||
'url_claim' => $this->faker->url,
|
||||
'domain' => $this->faker->unique()->domainName,
|
||||
'newsletter_email' => $this->faker->email
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
28
database/migrations/2026_04_03_152802_alter_brands_table.php
Normal file
28
database/migrations/2026_04_03_152802_alter_brands_table.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('brands', function (Blueprint $table) {
|
||||
$table->string('newsletter_email')->nullable()->comment('Brand associative newsletter email.')->after('domain');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('brands', function (Blueprint $table) {
|
||||
$table->dropColumn('newsletter_email');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('brand_contact_lists', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('brand_id')->comment('Brand associated.')->constrained('brands')
|
||||
->onUpdate('cascade')->onDelete('cascade');
|
||||
$table->unsignedBigInteger('mailjet_list_id')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('brand_contact_lists');
|
||||
}
|
||||
};
|
||||
|
|
@ -32,6 +32,8 @@ data:
|
|||
SAML2_CONTACT_TECHNICAL_EMAIL: "${APP_SAML2_CONTACT_TECHNICAL_EMAIL}"
|
||||
SAML2_CONTACT_SUPPORT_EMAIL: "${APP_SAML2_CONTACT_SUPPORT_EMAIL}"
|
||||
UWIL_ADMIN_SECURE_CIDR_LIST: "${APP_GLOBAL_UWIL_ADMIN_SECURE_CIDR_LIST}"
|
||||
MJ_APIKEY_PUBLIC: "${APP_MJ_APIKEY_PUBLIC}"
|
||||
MJ_APIKEY_PRIVATE: "${APP_MJ_APIKEY_PRIVATE}"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
|
@ -82,7 +84,12 @@ spec:
|
|||
lifecycle:
|
||||
preStop:
|
||||
exec:
|
||||
command: ["/bin/sh","-c","kill -s TERM 1; while test $(ps ax | grep -v grep | grep bash | wc -l) -gt 0; do sleep 2s; done"]
|
||||
command:
|
||||
[
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"kill -s TERM 1; while test $(ps ax | grep -v grep | grep bash | wc -l) -gt 0; do sleep 2s; done",
|
||||
]
|
||||
resources:
|
||||
limits:
|
||||
cpu: "${APP_CI_CPU_LIMIT}"
|
||||
|
|
|
|||
|
|
@ -222,20 +222,21 @@ return [
|
|||
'year' => 'year',
|
||||
],
|
||||
'custom' => [
|
||||
'event' => 'event',
|
||||
'model' => 'target element',
|
||||
'order' => 'order',
|
||||
'publication' => 'publication',
|
||||
'activated' => 'activated',
|
||||
'target' => 'target',
|
||||
'response_type' => 'response type',
|
||||
'reference' => 'reference',
|
||||
'type' => 'type',
|
||||
'status' => 'status',
|
||||
'label' => 'label',
|
||||
'domain' => 'domain',
|
||||
'logo' => 'logo',
|
||||
'url' => 'url',
|
||||
'url_claim' => 'claim url',
|
||||
'event' => 'event',
|
||||
'model' => 'target element',
|
||||
'order' => 'order',
|
||||
'publication' => 'publication',
|
||||
'activated' => 'activated',
|
||||
'target' => 'target',
|
||||
'response_type' => 'response type',
|
||||
'reference' => 'reference',
|
||||
'type' => 'type',
|
||||
'status' => 'status',
|
||||
'label' => 'label',
|
||||
'domain' => 'domain',
|
||||
'logo' => 'logo',
|
||||
'url' => 'url',
|
||||
'url_claim' => 'claim url',
|
||||
'newsletter_email' => 'email address for the newsletter',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -222,20 +222,21 @@ return [
|
|||
'year' => 'année',
|
||||
],
|
||||
'custom' => [
|
||||
'event' => 'évènement',
|
||||
'model' => 'élément ciblé',
|
||||
'order' => 'order',
|
||||
'publication' => 'publication',
|
||||
'activated' => 'activé',
|
||||
'target' => 'cible',
|
||||
'response_type' => 'type de réponse',
|
||||
'reference' => 'référence',
|
||||
'type' => 'type',
|
||||
'status' => 'status',
|
||||
'label' => 'libellé',
|
||||
'domain' => 'domaine',
|
||||
'logo' => 'logo',
|
||||
'url' => 'lien',
|
||||
'url_claim' => 'lien réclamation',
|
||||
'event' => 'évènement',
|
||||
'model' => 'élément ciblé',
|
||||
'order' => 'order',
|
||||
'publication' => 'publication',
|
||||
'activated' => 'activé',
|
||||
'target' => 'cible',
|
||||
'response_type' => 'type de réponse',
|
||||
'reference' => 'référence',
|
||||
'type' => 'type',
|
||||
'status' => 'status',
|
||||
'label' => 'libellé',
|
||||
'domain' => 'domaine',
|
||||
'logo' => 'logo',
|
||||
'url' => 'lien',
|
||||
'url_claim' => 'lien réclamation',
|
||||
'newsletter_email' => 'adresse email pour la newsletter',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -89,8 +89,31 @@
|
|||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{{-- NEWSLETTER_EMAIL --}}
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ str(__('validation.custom.newsletter_email'))->ucfirst() }}</legend>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3">
|
||||
@include('bo.partials.inputs.varchar', [
|
||||
'id' => 'brandNewsletterEmail',
|
||||
'label' => __(':field de la :model', [
|
||||
'field' => str(__('validation.custom.newsletter_email'))->ucfirst(),
|
||||
'model' => str(__('models.classes.brands'))->singular(),
|
||||
]),
|
||||
'hasWordCounter' => true,
|
||||
'name' => 'newsletter_email',
|
||||
'required' => false,
|
||||
'model' => $brandModel ?? null,
|
||||
'placeholder' => 'no-reply@example.com',
|
||||
'helper' => __('Adresse email utilisée pour l\'envoi de la newsletter.'),
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{{-- IMAGE --}}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<fieldset class="h-100 border p-3">
|
||||
<legend>{{ str(__('validation.attributes.image'))->ucfirst() }}</legend>
|
||||
<div class="row">
|
||||
|
|
|
|||
|
|
@ -58,10 +58,16 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center mt-4">
|
||||
<div class="col-lg-6 col-md-10 col-12 text-center">
|
||||
<button class="btn btn-primary" data-bs-tooltip="tooltip" type="submit"
|
||||
<div class="col-lg-6 col-md-10 col-12 text-center text-lg-end">
|
||||
<button class="btn btn-primary" data-bs-tooltip="tooltip" type="submit" name="action" value="export"
|
||||
title="{{ __('Cliquer pour exporter la liste de client pour une newsletter') }}">
|
||||
{{ __('Exporter') }}
|
||||
{{ __('Exporter au format CSV') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-10 col-12 text-center text-lg-start">
|
||||
<button class="btn btn-success" data-bs-tooltip="tooltip" type="submit" name="action" value="create_mailjet"
|
||||
title="{{ __('Cliquer pour importer la liste de client sur mailjet') }}">
|
||||
{{ __('Importer sur Mailjet') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use App\Http\Controllers\Api\V4\CustomerController as V4CustomerController;
|
|||
use App\Http\Controllers\Api\V4\OptinController as V4OptinController;
|
||||
use App\Http\Controllers\Api\V4\QuestionController as V4QuestionController;
|
||||
use App\Http\Controllers\Api\V4\StatsController as V4StatsController;
|
||||
use App\Http\Controllers\Api\WebhookController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|
|
@ -140,3 +141,5 @@ Route::prefix('v4')->name('apiV4.')->group(function () {
|
|||
Route::post('/claim', [V4ClaimController::class, 'store'])->name('claim.store');
|
||||
});
|
||||
});
|
||||
|
||||
Route::post('/webhook/mailjet', [WebhookController::class, 'handleMailjetWebhook'])->name('webhook.mailjet');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue