diff --git a/Api/BrevoClient.php b/Api/BrevoClient.php
index 165ba54..f33f748 100644
--- a/Api/BrevoClient.php
+++ b/Api/BrevoClient.php
@@ -27,14 +27,14 @@
use Brevo\Client\Model\RemoveContactFromList;
use Brevo\Client\Model\UpdateContact;
use Brevo\Model\BrevoNewsletterQuery;
+use Brevo\Trait\DataExtractorTrait;
use GuzzleHttp\Client;
-use Propel\Runtime\Connection\ConnectionWrapper;
-use Propel\Runtime\Propel;
use Thelia\Core\Event\Newsletter\NewsletterEvent;
use Thelia\Exception\TheliaProcessException;
-use Thelia\Log\Tlog;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Customer;
+use Thelia\Model\CustomerQuery;
+use Thelia\Model\NewsletterQuery;
/**
* Class BrevoClient.
@@ -43,6 +43,8 @@
*/
class BrevoClient
{
+ use DataExtractorTrait;
+
protected ContactsApi $contactApi;
private mixed $newsletterId;
@@ -65,7 +67,8 @@ public function subscribe(NewsletterEvent $event)
if ($apiException->getCode() !== 404) {
throw $apiException;
}
- $contact = $this->createContact($event->getId());
+
+ return $this->createContact($event->getEmail());
}
$this->update($event, $contact);
@@ -78,16 +81,21 @@ public function checkIfContactExist($email)
return $this->contactApi->getContactInfoWithHttpInfo($email);
}
- public function createContact(Customer $customer)
+ public function createContact(string $email)
{
- $contactAttribute = $this->getCustomerAttribute($customer->getId());
+ $contactAttribute = [];
+
+ if (null !== $customer = CustomerQuery::create()->findOneByEmail($email)) {
+ $contactAttribute = $this->getCustomerAttribute($customer->getId());
+ }
+
$createContact = new CreateContact();
- $createContact['email'] = $customer->getEmail();
+ $createContact['email'] = $email;
$createContact['attributes'] = $contactAttribute;
$createContact['listIds'] = [$this->newsletterId];
$this->contactApi->createContactWithHttpInfo($createContact);
- return $this->contactApi->getContactInfoWithHttpInfo($customer->getEmail());
+ return $this->contactApi->getContactInfoWithHttpInfo($email);
}
public function updateContact($identifier, Customer $customer)
@@ -110,13 +118,16 @@ public function update(NewsletterEvent $event, $contact = null)
if (!$contact) {
$sibObject = BrevoNewsletterQuery::create()->findPk($event->getId());
if (null === $sibObject) {
- $sibObject = BrevoNewsletterQuery::create()->findOneByEmail($previousEmail);
+ $sibObject = NewsletterQuery::create()->findPk($event->getId());
}
- $previousEmail = $sibObject->getEmail();
- $contact = $this->contactApi->getContactInfoWithHttpInfo($previousEmail);
- $updateContact['email'] = $event->getEmail();
- $updateContact['attributes'] = ['PRENOM' => $event->getFirstname(), 'NOM' => $event->getLastname()];
+ if (null !== $sibObject) {
+ $previousEmail = $sibObject->getEmail();
+ $contact = $this->contactApi->getContactInfoWithHttpInfo($previousEmail);
+
+ $updateContact['email'] = $event->getEmail();
+ $updateContact['attributes'] = ['PRENOM' => $event->getFirstname(), 'NOM' => $event->getLastname()];
+ }
}
$updateContact['listIds'] = [$this->newsletterId];
@@ -125,80 +136,42 @@ public function update(NewsletterEvent $event, $contact = null)
return $this->contactApi->getContactInfoWithHttpInfo($previousEmail);
}
- public function unsubscribe(NewsletterEvent $event)
+ public function unsubscribe(string $email)
{
- $contact = $this->contactApi->getContactInfoWithHttpInfo($event->getEmail());
+ $contact = $this->contactApi->getContactInfoWithHttpInfo($email);
$change = false;
if (\in_array($this->newsletterId, $contact[0]['listIds'], true)) {
$contactIdentifier = new RemoveContactFromList();
- $contactIdentifier['emails'] = [$event->getEmail()];
+ $contactIdentifier['emails'] = [$email];
$this->contactApi->removeContactFromList($this->newsletterId, $contactIdentifier);
$change = true;
}
- return $change ? $this->contactApi->getContactInfoWithHttpInfo($event->getEmail()) : $contact;
+ return $change ? $this->contactApi->getContactInfoWithHttpInfo($email) : $contact;
}
- public function getCustomerAttribute($customerId)
+ /**
+ * @throws \JsonException
+ */
+ public function getCustomerAttribute($customerId): array
{
- try {
- if (null === $mapping = json_decode(ConfigQuery::read(Brevo::BREVO_ATTRIBUTES_MAPPING), true, 512, \JSON_THROW_ON_ERROR)) {
- throw new TheliaProcessException("Customer attribute mapping error: JSON data seems invalid, pleas echeck syntax.");
- }
-
- if (empty($mapping)) {
- return [];
- }
+ $mappingString = ConfigQuery::read(Brevo::BREVO_ATTRIBUTES_MAPPING);
- if (!\array_key_exists('customer_query', $mapping)) {
- throw new TheliaProcessException("Customer attribute mapping error : 'customer_query' element is missing in JSON data");
- }
-
- $attributes = [];
-
- /** @var ConnectionWrapper $con */
- $con = Propel::getConnection();
-
- foreach ($mapping['customer_query'] as $key => $customerDataQuery) {
- if (!\array_key_exists('select', $customerDataQuery)) {
- throw new \Exception("Customer attribute mapping error : 'select' element missing in ".$key.' query');
- }
-
- try {
- $sql = 'SELECT '.$customerDataQuery['select'].' AS '.$key.' FROM customer';
-
- if (\array_key_exists('join', $customerDataQuery)) {
- foreach ($customerDataQuery['join'] as $join) {
- $sql .= ' LEFT JOIN '.$join;
- }
- }
-
- $sql .= ' WHERE customer.id = :customerId';
-
- if (\array_key_exists('groupBy', $customerDataQuery)) {
- $sql .= ' GROUP BY '.$customerDataQuery['groupBy'];
- }
-
- $stmt = $con->prepare($sql);
- $stmt->bindValue(':customerId', $customerId, \PDO::PARAM_INT);
- $stmt->execute();
-
- while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
- $attributes[$key] = $row[$key];
- if (\array_key_exists($key, $mapping) && \array_key_exists($row[$key], $mapping[$key])) {
- $attributes[$key] = $mapping[$key][$row[$key]];
- }
- }
- } catch (\Exception $ex) {
- Tlog::getInstance()->error(
- 'Failed to execute SQL request to map Brevo attribute. Error is '.$ex->getMessage().", request is : $sql");
- }
- }
+ if (empty($mappingString)) {
+ return [];
+ }
- return $attributes;
- } catch (\Exception $ex) {
- throw new TheliaProcessException('Customer attribute mapping error : configuration is missing or invalid, please go to the module configuration and define the JSON mapping to match thelia attribute with brevo attribute');
+ if (null === $mapping = json_decode($mappingString, true)) {
+ throw new TheliaProcessException('Customer attribute mapping error: JSON data seems invalid, pleas echeck syntax.');
}
+
+ return $this->getMappedValues(
+ $mapping,
+ 'customer_query',
+ 'customer',
+ 'customer.id',
+ $customerId,
+ );
}
}
diff --git a/Brevo.php b/Brevo.php
index ea056c7..c915ec0 100644
--- a/Brevo.php
+++ b/Brevo.php
@@ -34,6 +34,7 @@ class Brevo extends BaseModule
const CONFIG_AUTOMATION_KEY= "brevo.automation.key";
const CONFIG_THROW_EXCEPTION_ON_ERROR = "brevo.throw_exception_on_error";
const BREVO_ATTRIBUTES_MAPPING = "brevo.brevo_attributes_mapping";
+ const BREVO_METADATA_MAPPING = "brevo.brevo_metadata_mapping";
public function postActivation(ConnectionInterface $con = null): void
{
diff --git a/Config/module.xml b/Config/module.xml
index f807240..d2e6f3a 100644
--- a/Config/module.xml
+++ b/Config/module.xml
@@ -13,7 +13,7 @@
en_US
fr_FR
- 1.1.10
+ 1.1.11
Chabreuil Antoine
diff --git a/Controller/BrevoConfigController.php b/Controller/BrevoConfigController.php
index f100278..b047534 100644
--- a/Controller/BrevoConfigController.php
+++ b/Controller/BrevoConfigController.php
@@ -52,6 +52,7 @@ public function saveAction(Request $request, ParserContext $parserContext, Brevo
ConfigQuery::write(Brevo::CONFIG_NEWSLETTER_ID, $data['newsletter_list']);
ConfigQuery::write(Brevo::CONFIG_THROW_EXCEPTION_ON_ERROR, (bool) $data['exception_on_errors']);
ConfigQuery::write(Brevo::BREVO_ATTRIBUTES_MAPPING, $data['attributes_mapping']);
+ ConfigQuery::write(Brevo::BREVO_METADATA_MAPPING, $data['metadata_mapping']);
$brevoApiService->enableEcommerce();
diff --git a/EventListeners/NewsletterListener.php b/EventListeners/NewsletterListener.php
index 056f247..5656d1c 100644
--- a/EventListeners/NewsletterListener.php
+++ b/EventListeners/NewsletterListener.php
@@ -73,7 +73,7 @@ public function subscribe(NewsletterEvent $event)
public function update(NewsletterEvent $event)
{
- if (null === BrevoNewsletterQuery::create()->findPk($event->getId()) || null !== NewsletterQuery::create()->findPk($event->getId())) {
+ if (null === BrevoNewsletterQuery::create()->findPk($event->getId()) && null === NewsletterQuery::create()->findPk($event->getId())) {
return;
}
@@ -107,21 +107,14 @@ public function update(NewsletterEvent $event)
public function unsubscribe(NewsletterEvent $event)
{
- if ((null === $model = BrevoNewsletterQuery::create()->findPk($event->getId())) || null !== NewsletterQuery::create()->findPk($event->getId())) {
- return;
- }
-
try {
- $contact = $this->api->unsubscribe($event);
- $status = $contact[1];
- if (null === $model) {
- $model = BrevoNewsletterQuery::create()->findOneByEmail($event->getEmail());
- }
+ $contact = $this->api->unsubscribe($event->getEmail());
- if (null === $model) {
+ if (null === $model = BrevoNewsletterQuery::create()->findOneByEmail($event->getEmail())) {
return;
}
+ $status = $contact[1];
$data = ["id" => $model->getRelationId()];
$logMessage = $this->logAfterAction(
sprintf("The email address '%s' was successfully unsubscribed from the list", $event->getEmail()),
diff --git a/Form/BrevoConfigurationForm.php b/Form/BrevoConfigurationForm.php
index 6a7c0f2..269a1f2 100644
--- a/Form/BrevoConfigurationForm.php
+++ b/Form/BrevoConfigurationForm.php
@@ -58,13 +58,19 @@ protected function buildForm(): void
{
$translator = Translator::getInstance();
- $defaultMapping = <<< END
+ $defaultCustomerMapping = <<< END
{
"customer_query": {
- "EMAIL" : {
- "select" : "customer.email"
- }
+ "EMAIL" : {
+ "select" : "customer.email"
}
+ }
+}
+END;
+ $defaultMetadataMapping = <<< END
+{
+ "product_query": {
+ }
}
END;
$this->formBuilder
@@ -131,9 +137,29 @@ protected function buildForm(): void
'required' => false,
'constraints' => [
new NotBlank(),
- new Callback([$this, 'checkJsonValidity']),
+ new Callback([$this, 'checkCustomerJsonValidity']),
],
- 'data' => ConfigQuery::read(Brevo::BREVO_ATTRIBUTES_MAPPING, $defaultMapping),
+ 'data' => ConfigQuery::read(Brevo::BREVO_ATTRIBUTES_MAPPING, $defaultCustomerMapping),
+ ])
+ ->add('metadata_mapping', TextareaType::class, [
+ 'label' => $translator->trans('Products metadata attributes mapping', [], Brevo::MESSAGE_DOMAIN),
+ 'attr' => [
+ 'rows' => 10
+ ],
+ 'label_attr' => [
+ 'for' => 'attributes_mapping',
+ 'help' => Translator::getInstance()->trans(
+ 'This is a mapping of Brevo products meta-data attributes with Thelia products attributes. Do not change anything here if you do not know exactly what you are doing',
+ [],
+ Brevo::MESSAGE_DOMAIN
+ )
+ ],
+ 'required' => false,
+ 'constraints' => [
+ new NotBlank(),
+ new Callback([$this, 'checkProductJsonValidity']),
+ ],
+ 'data' => ConfigQuery::read(Brevo::BREVO_METADATA_MAPPING, $defaultMetadataMapping),
])
->add('exception_on_errors', CheckboxType::class, [
'label' => $translator->trans('Throw exception on Brevo error', [], Brevo::MESSAGE_DOMAIN),
@@ -149,7 +175,17 @@ protected function buildForm(): void
])
;
}
- public function checkJsonValidity($value, ExecutionContextInterface $context): void
+
+ public function checkCustomerJsonValidity($value, ExecutionContextInterface $context): void
+ {
+ $this->checkJsonValidity('customer_query', $value, $context);
+ }
+ public function checkProductJsonValidity($value, ExecutionContextInterface $context): void
+ {
+ $this->checkJsonValidity('product_query', $value, $context);
+ }
+
+ public function checkJsonValidity(string $expectedNode, $value, ExecutionContextInterface $context): void
{
if (empty($value)) {
return;
@@ -165,10 +201,10 @@ public function checkJsonValidity($value, ExecutionContextInterface $context): v
);
}
- if (! isset($jsonData['customer_query'])) {
+ if (! isset($jsonData[$expectedNode])) {
$context->addViolation(
Translator::getInstance()->trans(
- "The customer attributes mapping JSON should contain a 'customer_query' field.",
+ "The customer attributes mapping JSON should contain a '$expectedNode' field.",
[],
Brevo::MESSAGE_DOMAIN
)
diff --git a/Services/BrevoCustomerService.php b/Services/BrevoCustomerService.php
index 3a55743..05e7b0f 100644
--- a/Services/BrevoCustomerService.php
+++ b/Services/BrevoCustomerService.php
@@ -25,10 +25,6 @@ public function __construct(private BrevoClient $brevoClient)
public function createUpdateContact($customerId)
{
- if (empty(ConfigQuery::read(Brevo::BREVO_ATTRIBUTES_MAPPING, ''))) {
- return null;
- }
-
$customer = CustomerQuery::create()->findPk($customerId);
try {
@@ -40,7 +36,7 @@ public function createUpdateContact($customerId)
throw $exception;
}
- return $this->brevoClient->createContact($customer);
+ return $this->brevoClient->createContact($customer->getEmail());
}
}
}
diff --git a/Services/BrevoProductService.php b/Services/BrevoProductService.php
index 2f97dcd..02613b6 100644
--- a/Services/BrevoProductService.php
+++ b/Services/BrevoProductService.php
@@ -12,10 +12,16 @@
namespace Brevo\Services;
+use Brevo\Brevo;
+use Brevo\Trait\DataExtractorTrait;
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Thelia\Core\Event\Image\ImageEvent;
+use Thelia\Core\Event\TheliaEvents;
+use Thelia\Exception\TheliaProcessException;
use Thelia\Log\Tlog;
use Thelia\Model\Base\ProductQuery;
use Thelia\Model\Cart;
-use Thelia\Model\CategoryQuery;
+use Thelia\Model\ConfigQuery;
use Thelia\Model\Country;
use Thelia\Model\Currency;
use Thelia\Model\Order;
@@ -27,8 +33,25 @@
class BrevoProductService
{
- public function __construct(private BrevoApiService $brevoApiService)
+ use DataExtractorTrait;
+
+ protected string $baseSourceFilePath;
+
+ protected array $metaDataMapping = [];
+
+ public function __construct(private BrevoApiService $brevoApiService, protected EventDispatcherInterface $dispatcher)
{
+ if (null === $this->baseSourceFilePath = ConfigQuery::read('images_library_path', null)) {
+ $this->baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'images';
+ } else {
+ $this->baseSourceFilePath = THELIA_ROOT.$this->baseSourceFilePath;
+ }
+
+ $mappingString = ConfigQuery::read(Brevo::BREVO_METADATA_MAPPING);
+
+ if (!empty($mappingString) && null === $this->metaDataMapping = json_decode($mappingString, true)) {
+ throw new TheliaProcessException('Product metadata mapping error: JSON data seems invalid, please check syntax.');
+ }
}
public function getObjName()
@@ -80,22 +103,61 @@ public function exportInBatch($limit, $offset, $locale, Currency $currency, Coun
public function getData(Product $product, $locale, Currency $currency, Country $country)
{
$product->setLocale($locale);
- $imagePath = ProductImageQuery::create()->filterByProductId($product->getId())->orderByPosition()->findOne()?->getFile();
$productPrice = $product->getDefaultSaleElements()->getPricesByCurrency($currency);
+ $productSku = $product->getDefaultSaleElements()->getEanCode();
$categories = $product->getCategories();
$categoryIds = array_map(function ($category) {
return (string) $category['Id'];
}, $categories->toArray());
+ // Get first product image
+ $imageUrl = null;
+
+ if (null !== $productImage = ProductImageQuery::create()
+ ->filterByProductId($product->getId())
+ ->filterByVisible(1)
+ ->orderBy('position')->findOne()
+ ) {
+ // Put source image file path
+ $sourceFilePath = sprintf(
+ '%s/%s/%s',
+ $this->baseSourceFilePath,
+ 'product',
+ $productImage->getFile()
+ );
+
+ // Create image processing event
+ $event = (new ImageEvent())
+ ->setSourceFilepath($sourceFilePath)
+ ->setCacheSubdirectory('product')
+ ;
+
+ try {
+ // Dispatch image processing event
+ $this->dispatcher->dispatch($event, TheliaEvents::IMAGE_PROCESS);
+ $imageUrl = $event->getFileUrl();
+ } catch (\Exception $ex) {
+ // Ignore the result
+ $a = 1;
+ }
+ }
+
return [
- 'categories' => $categoryIds,
- 'id' => (string) $product->getId(),
- 'name' => $product->getTitle(),
- 'url' => URL::getInstance()?->absoluteUrl($product->getUrl()),
- 'image' => $imagePath ? URL::getInstance()?->absoluteUrl('/cache/images/product/'.$imagePath) : null,
- 'sku' => $product->getRef(),
- 'price' => round((float) $product->getTaxedPrice($country, $productPrice->getPrice()), 2),
+ 'categories' => $categoryIds,
+ 'id' => (string) $product->getId(),
+ 'name' => $product->getTitle(),
+ 'url' => $product->getUrl($locale),
+ 'image' => $imageUrl,
+ 'sku' => $productSku ?? $product->getRef(),
+ 'price' => round((float) $product->getTaxedPrice($country, $productPrice->getPrice()), 2),
+ 'metaInfo' => $this->getMappedValues(
+ $this->metaDataMapping,
+ 'product_query',
+ 'product',
+ 'product.id',
+ $product->getId(),
+ ),
];
}
diff --git a/Trait/DataExtractorTrait.php b/Trait/DataExtractorTrait.php
new file mode 100644
index 0000000..7b53003
--- /dev/null
+++ b/Trait/DataExtractorTrait.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/* web : https://www.openstudio.fr */
+
+/* For the full copyright and license information, please view the LICENSE */
+/* file that was distributed with this source code. */
+
+/**
+ * Created by Franck Allimant, OpenStudio
+ * Projet: thelia25
+ * Date: 17/11/2023.
+ */
+
+namespace Brevo\Trait;
+
+use Propel\Runtime\Connection\ConnectionWrapper;
+use Propel\Runtime\Propel;
+use Thelia\Exception\TheliaProcessException;
+use Thelia\Log\Tlog;
+
+trait DataExtractorTrait
+{
+ public function getMappedValues(
+ array $jsonMapping,
+ string $mapKey,
+ string $sourceTableName,
+ string $selectorFieldName,
+ mixed $selector,
+ int $selectorType = \PDO::PARAM_INT
+ ): array {
+ try {
+ if (empty($jsonMapping)) {
+ return [];
+ }
+
+ if (!\array_key_exists($mapKey, $jsonMapping)) {
+ throw new TheliaProcessException("Mapping error : '$mapKey' element is missing in JSON data");
+ }
+
+ $attributes = [];
+
+ /** @var ConnectionWrapper $con */
+ $con = Propel::getConnection();
+
+ foreach ($jsonMapping[$mapKey] as $key => $dataQuery) {
+ if (!\array_key_exists('select', $dataQuery)) {
+ throw new \Exception("Mapping error : 'select' element missing in ".$key.' query');
+ }
+
+ try {
+ $sql = 'SELECT '.$dataQuery['select'].' AS '.$key.' FROM '.$sourceTableName;
+
+ if (\array_key_exists('join', $dataQuery)) {
+ if (!\is_array($dataQuery['join'])) {
+ $dataQuery['join'] = [$dataQuery['join']];
+ }
+
+ foreach ($dataQuery['join'] as $join) {
+ $sql .= ' LEFT JOIN '.$join;
+ }
+ }
+
+ $sql .= ' WHERE '.$selectorFieldName.' = :selector';
+
+ if (\array_key_exists('groupBy', $dataQuery)) {
+ $sql .= ' GROUP BY '.$dataQuery['groupBy'];
+ }
+
+ $stmt = $con->prepare($sql);
+ $stmt->bindValue(':selector', $selector, $selectorType);
+ $stmt->execute();
+
+ while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
+ $attributes[$key] = $row[$key];
+ if (\array_key_exists($key, $jsonMapping) && \array_key_exists($row[$key], $jsonMapping[$key])) {
+ $attributes[$key] = $jsonMapping[$key][$row[$key]];
+ }
+ }
+ } catch (\Exception $ex) {
+ Tlog::getInstance()->error(
+ 'Failed to execute SQL request to map Brevo attribute. Error is '.$ex->getMessage().", request is : $sql");
+ }
+ }
+
+ return $attributes;
+ } catch (\Exception $ex) {
+ throw new TheliaProcessException(
+ 'Mapping error : configuration is missing or invalid, please go to the module configuration and define the JSON mapping to match thelia attribute with brevo attribute. Error is : '.$ex->getMessage()
+ );
+ }
+ }
+}
diff --git a/templates/backOffice/default/brevo-configuration.html b/templates/backOffice/default/brevo-configuration.html
index bd25077..4fc74ac 100644
--- a/templates/backOffice/default/brevo-configuration.html
+++ b/templates/backOffice/default/brevo-configuration.html
@@ -24,8 +24,9 @@
{render_form_field field="api_key"}
{render_form_field field="automation_key"}
{render_form_field field="newsletter_list"}
- {render_form_field field="attributes_mapping" extra_class="fixedfont"}
{render_form_field field="exception_on_errors"}
+ {render_form_field field="attributes_mapping" extra_class="fixedfont"}
+ {render_form_field field="metadata_mapping" extra_class="fixedfont"}
{include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'} page_bottom=1}