-
Notifications
You must be signed in to change notification settings - Fork 55
Adds product/save to message queue #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 11 commits
5aba402
ff657f6
b60705e
a140aa6
43c53ee
905c644
682cc17
fcfe92b
b9c26fc
67d5e2d
d2b1ea9
dae44cb
2699a28
cd37f4d
440ebc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| <?php | ||
|
|
||
| namespace Klaviyo\Reclaim\Cron; | ||
|
|
||
| use Klaviyo\Reclaim\Helper\Logger; | ||
| use Klaviyo\Reclaim\Model\SyncsFactory; | ||
| use Klaviyo\Reclaim\Model\ResourceModel\Products; | ||
| use Klaviyo\Reclaim\Model\ResourceModel\Products\CollectionFactory; | ||
|
|
||
| use Magento\Catalog\Model\CategoryFactory; | ||
|
|
||
| class ProductsTopic | ||
| { | ||
| /** | ||
| * Klaviyo Logger | ||
| * @var Logger | ||
| */ | ||
| protected $_klaviyoLogger; | ||
|
|
||
| /** | ||
| * Magento product category helper | ||
| * @var CategoryFactory $categoryFactory | ||
| */ | ||
| protected $_categoryFactory; | ||
|
|
||
| /** | ||
| * Klaviyo Products Resource Model | ||
| * @var Products | ||
| */ | ||
| protected $_klProduct; | ||
|
|
||
| /** | ||
| * Klaviyo Products Collection | ||
| * @var CollectionFactory | ||
| */ | ||
| protected $_klProductCollectionFactory; | ||
|
|
||
| /** | ||
| * Klaviyo Syncs Model | ||
| * @var SyncsFactory | ||
| */ | ||
| protected $_klSyncFactory; | ||
|
|
||
| /** | ||
| * @param Logger $klaviyoLogger | ||
| * @param Products $klProduct | ||
| * @param SyncsFactory $klSyncFactory | ||
| * @param CollectionFactory $klProductCollectionFactory | ||
| */ | ||
| public function __construct( | ||
| Logger $klaviyoLogger, | ||
| Products $klProduct, | ||
| CategoryFactory $categoryFactory, | ||
| SyncsFactory $klSyncFactory, | ||
| CollectionFactory $klProductCollectionFactory | ||
| ) | ||
| { | ||
| $this->_klaviyoLogger = $klaviyoLogger; | ||
| $this->_klProduct = $klProduct; | ||
| $this->_categoryFactory = $categoryFactory; | ||
| $this->_klSyncFactory = $klSyncFactory; | ||
| $this->_klProductCollectionFactory = $klProductCollectionFactory; | ||
| } | ||
|
|
||
| public function queueKlProductsForSync() | ||
| { | ||
| $klProductsCollection = $this->_klProductCollectionFactory->create(); | ||
| $klProductsToSync = $klProductsCollection->getRowsForSync('NEW') | ||
| ->addFieldToSelect(['id','payload','status','topic', 'klaviyo_id']) | ||
| ->getData(); | ||
|
|
||
| if (empty($klProductsToSync)) {return;} | ||
|
|
||
| $idsToUpdate = []; | ||
|
|
||
| foreach ($klProductsToSync as $klProductToSync) | ||
| { | ||
| $klProductToSync['payload'] = json_encode($this->addCategoryNames($klProductToSync['payload'])); | ||
| $klSync = $this->_klSyncFactory->create(); | ||
| $klSync->setData([ | ||
| 'payload'=> $klProductToSync['payload'], | ||
| 'topic'=> $klProductToSync['topic'], | ||
| 'klaviyo_id'=>$klProductToSync['klaviyo_id'], | ||
| 'status'=> 'NEW' | ||
| ]); | ||
| try { | ||
| $klSync->save(); | ||
| array_push($idsToUpdate, $klProductToSync['id']); | ||
| } catch (\Exception $e) { | ||
| $this->_klaviyoLogger->log(sprintf('Unable to move row: %s', $e->getMessage())); | ||
| } | ||
| } | ||
|
|
||
| $klProductsCollection->updateRowStatus($idsToUpdate, 'MOVED'); | ||
| } | ||
|
|
||
| public function clean() | ||
| { | ||
| $klProductsCollection = $this->_klProductCollectionFactory->create(); | ||
| $idsToDelete = $klProductsCollection->getIdsToDelete('MOVED'); | ||
|
|
||
| $klProductsCollection->deleteRows($idsToDelete); | ||
| } | ||
|
|
||
| /** | ||
| * Helper function to associate category names with their ids | ||
| * @param string $payload | ||
| * @return array | ||
| */ | ||
| public function addCategoryNames(string $payload): array | ||
| { | ||
| $decoded_payload = json_decode($payload, true); | ||
| $category_ids = $decoded_payload['product']['categories']; | ||
| if (empty($category_ids)) {return $decoded_payload;} | ||
| $decoded_payload['product']['categories'] = []; | ||
| $category_factory = $this->_categoryFactory->create(); | ||
| foreach ($category_ids as $category_id) { | ||
| $category = $category_factory->load($category_id); | ||
| $decoded_payload['product']['categories'][$category_id] = $category->getName(); | ||
| } | ||
| return $decoded_payload; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,14 +33,13 @@ public function __construct( | |||||||
|
|
||||||||
| /** | ||||||||
| * @param string $webhookType | ||||||||
| * @param array $data | ||||||||
| * @param string $data json payload to be sent in the body of the request | ||||||||
| * @param string $klaviyoId | ||||||||
| * @return string | ||||||||
| * @throws Exception | ||||||||
| */ | ||||||||
| public function makeWebhookRequest($webhookType, $data, $klaviyoId=null) | ||||||||
| { | ||||||||
|
|
||||||||
| if (!$klaviyoId) { | ||||||||
| $klaviyoId = $this->_klaviyoScopeSetting->getPublicApiKey(); | ||||||||
| } | ||||||||
|
|
@@ -51,12 +50,12 @@ public function makeWebhookRequest($webhookType, $data, $klaviyoId=null) | |||||||
| CURLOPT_URL => $url, | ||||||||
| CURLOPT_RETURNTRANSFER => true, | ||||||||
| CURLOPT_CUSTOMREQUEST => 'POST', | ||||||||
| CURLOPT_POSTFIELDS => json_encode($data), | ||||||||
| CURLOPT_POSTFIELDS => $data, | ||||||||
| CURLOPT_USERAGENT => self::USER_AGENT, | ||||||||
| CURLOPT_HTTPHEADER => array( | ||||||||
| 'Content-Type: application/json', | ||||||||
| 'Magento-two-signature: ' . $this->createWebhookSecurity($data), | ||||||||
| 'Content-Length: '. strlen(json_encode($data)), | ||||||||
| 'Content-Length: '. strlen($data), | ||||||||
| 'Topic: ' . $webhookType | ||||||||
| ), | ||||||||
| ]); | ||||||||
|
|
@@ -66,24 +65,23 @@ public function makeWebhookRequest($webhookType, $data, $klaviyoId=null) | |||||||
| $err = curl_errno($curl); | ||||||||
|
|
||||||||
| if ($err) { | ||||||||
| $this->_klaviyoLogger->log(sprintf('Unable to send webhook to %s with data: %s', $url, json_encode($data))); | ||||||||
| $this->_klaviyoLogger->log(sprintf("Unable to send webhook to $url with data: $data")); | ||||||||
|
||||||||
| $this->_klaviyoLogger->log(sprintf("Unable to send webhook to $url with data: $data")); | |
| $this->_klaviyoLogger->log("Unable to send webhook to $url with data: $data"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops haha!
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add a description to this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work?
* @param string $data json payload used to create hmac signature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * @param string data | |
| * Returns an HMAC signature for webhooks | |
| * @param string data |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| <?php | ||
|
|
||
| namespace Klaviyo\Reclaim\Observer; | ||
|
|
||
| use Exception; | ||
| use Klaviyo\Reclaim\Helper\ScopeSetting; | ||
| use Klaviyo\Reclaim\Model\ProductsFactory; | ||
|
|
||
| use Magento\CatalogInventory\Api\StockRegistryInterface; | ||
| use Magento\Framework\Event\Observer; | ||
| use Magento\Framework\Event\ObserverInterface; | ||
|
|
||
|
|
||
| class ProductSaveAfter implements ObserverInterface | ||
| { | ||
| /** | ||
| * Klaviyo scope setting helper | ||
| * @var ScopeSetting $klaviyoScopeSetting | ||
| */ | ||
| protected $_klaviyoScopeSetting; | ||
|
|
||
| /** | ||
| * Klaviyo product factory | ||
| * @var klProductFactory | ||
| */ | ||
| protected $_klProductFactory; | ||
|
|
||
| /** | ||
| * Magento stock registry api interface | ||
| * @var $stockRegistry | ||
| */ | ||
| protected $_stockRegistry; | ||
|
|
||
| /** | ||
| * @param ScopeSetting $klaviyoScopeSetting | ||
| * @param CategoryFactory $categoryFactory | ||
| * @param ProductsFactory $klProductFactory | ||
| * @param StockRegistryInterface $stockRegistry | ||
| */ | ||
| public function __construct( | ||
| ScopeSetting $klaviyoScopeSetting, | ||
| ProductsFactory $klProductFactory, | ||
| StockRegistryInterface $stockRegistry | ||
| ) { | ||
| $this->_klaviyoScopeSetting = $klaviyoScopeSetting; | ||
| $this->_klProductFactory = $klProductFactory; | ||
| $this->_stockRegistry = $stockRegistry; | ||
| } | ||
|
|
||
| /** | ||
| * customer register event handler | ||
| * | ||
| * @param Observer $observer | ||
| * @return void | ||
| * @throws Exception | ||
| */ | ||
| public function execute(Observer $observer) | ||
| { | ||
| $product = $observer->getEvent()->getProduct(); | ||
| $storeIds = $product->getStoreIds(); | ||
| $storeIdKlaviyoMap = $this->_klaviyoScopeSetting->getStoreIdKlaviyoAccountSetMap($storeIds); | ||
|
|
||
| foreach ($storeIdKlaviyoMap as $klaviyoId => $storeIds) { | ||
| if (empty($storeIds)) {continue;} | ||
|
|
||
| if ($this->_klaviyoScopeSetting->getWebhookSecret() && $this->_klaviyoScopeSetting->getProductSaveWebhookSetting($storeIds[0])) { | ||
| $normalizedProduct = $this->normalizeProduct($product); | ||
| $data = [ | ||
| 'status'=>'NEW', | ||
| 'topic'=>'product/save', | ||
| 'klaviyo_id'=>$klaviyoId, | ||
| 'payload'=>json_encode($normalizedProduct) | ||
| ]; | ||
| $klProduct = $this->_klProductFactory->create(); | ||
| $klProduct->setData($data); | ||
| $klProduct->save(); | ||
siddwarkhedkar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
|
|
||
| private function normalizeProduct($product=null) | ||
| { | ||
| if ($product == null) {return;} | ||
|
|
||
| $product_id = $product->getId(); | ||
|
|
||
| $product_info = array( | ||
| 'store_ids' => $product->getStoreIds(), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are all array_keys supposed to start lower case/upper case? Could we standardize across the payload?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I only use uppercase letters on some of the properties because Klaviyo wants them that way so this avoids normalizing the payload in the app. otherwise I user lowercase where I can. Let me know if that's alright or not!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any restriction on making other start upper case too? Just looking for standardize keys. it's a nitpick so if it is too much work to accommodate this, we can skip it |
||
| 'product' => array( | ||
| 'ID' => $product_id, | ||
| 'TypeID' => $product->getTypeId(), | ||
| 'Name' => $product->getName(), | ||
| 'qty' => $this->_stockRegistry->getStockItem($product_id)->getQty(), | ||
| 'Visibility' => $product->getVisibility(), | ||
| 'IsInStock' => $product->isInStock(), | ||
| 'Status' => $product->getStatus(), | ||
| 'CreatedAt' => $product->getCreatedAt(), | ||
| 'UpdatedAt' => $product->getUpdatedAt(), | ||
| 'FirstImageURL' => $product->getImage(), | ||
| 'ThumbnailImageURL' => $product->getThumbnail(), | ||
| 'metadata' => array( | ||
| 'price' => $product->getPrice(), | ||
| 'sku' => $product->getSku() | ||
| ), | ||
| 'categories' => $product->getCategoryIds() | ||
| ) | ||
| ); | ||
|
|
||
| if ($product->getSpecialPrice()) { | ||
| $product_info['metadata']['special_price'] = $product->getSpecialPrice(); | ||
| $product_info['metadata']['special_from_date'] = $product->getSpecialFromDate(); | ||
| $product_info['metadata']['special_to_date'] = $product->getSpecialToDate(); | ||
| } | ||
| return $product_info; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Over here, we will be iterating over each categoryId for all payloads passed, so we will be running the for loop 500 times, if categoryIds, exist and possibly loading duplicate categroyModels since it is likely more than one product will have a category repeated. Can we make it such that we create a class level attribute to have Ids and categoryNames associated so don't duplicate the effort?
#142 (comment) > as advised here?
Or do you think it is worth having a method in the helper class which can be used by both the Cron?