Skip to content
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

3.0.11 - V3 patch #268

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

<!-- BEGIN RELEASE NOTES -->
### [Unreleased]

### [3.1.0] - 2023-09-29
jordanleslie marked this conversation as resolved.
Show resolved Hide resolved

#### Added
- New Klaviyo onsite object
- New X-Klaviyo-User-Agent to headers to collect plugin usage meta data
- Added support for Klaviyo V3 APIs

#### Removed
- Support for V2 APIs: /track and /identify
- Removed _learnq onsite object in favor of klaviyo object

### [3.0.11] - 2021-12-21

#### Fixed
Expand Down Expand Up @@ -137,9 +148,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
##### Changed
- Removes unused variable and DI from Reclaim.php
- CSP now uses report-only mode


[Unreleased]: https://github.com/klaviyo/magento2-klaviyo/compare/3.0.11...HEAD
<!-- END RELEASE NOTES -->
<!-- BEGIN LINKS -->
[Unreleased]: https://github.com/klaviyo/magento2-klaviyo/compare/3.1.0...HEAD
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Unreleased]: https://github.com/klaviyo/magento2-klaviyo/compare/3.1.0...HEAD
[Unreleased]: https://github.com/klaviyo/magento2-klaviyo/compare/3.0.11+v3api...HEAD

[3.1.0]: https://github.com/klaviyo/magento2-klaviyo/compare/3.0.11...3.1.0
jordanleslie marked this conversation as resolved.
Show resolved Hide resolved
[3.0.11]: https://github.com/klaviyo/magento2-klaviyo/compare/3.0.10...3.0.11
[3.0.10]: https://github.com/klaviyo/magento2-klaviyo/compare/3.0.9...3.0.10
[3.0.9]: https://github.com/klaviyo/magento2-klaviyo/compare/3.0.8...3.0.9
Expand All @@ -158,7 +170,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[2.0.0]: https://github.com/klaviyo/magento2-klaviyo/compare/1.2.4...2.0.0
[1.2.4]: https://github.com/klaviyo/magento2-klaviyo/compare/1.2.3...1.2.4
[1.2.3]: https://github.com/klaviyo/magento2-klaviyo/compare/1.2.2...1.2.3

<!-- END LINKS -->

#### NOTE
- The CHANGELOG was created on 2020-11-20 and does not contain information about earlier releases
210 changes: 96 additions & 114 deletions Helper/Data.php
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<?php

namespace Klaviyo\Reclaim\Helper;

use \Klaviyo\Reclaim\Helper\ScopeSetting;
use \Magento\Framework\App\Helper\Context;
use \Klaviyo\Reclaim\Helper\Logger;
use Klaviyo\Reclaim\KlaviyoV3Sdk\KlaviyoV3Api;
use Magento\Framework\App\Helper\Context;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
const USER_AGENT = 'Klaviyo/1.0';
const KLAVIYO_HOST = 'https://a.klaviyo.com/';
const LIST_V2_API = 'api/v2/list/';
const LIST_V3_API = 'api/list';

/**
* Klaviyo logger helper
Expand All @@ -23,6 +23,18 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
*/
protected $_klaviyoScopeSetting;

/**
* Variable used for storage of klAddedToCartPayload between observers
* @var
*/
private $observerAtcPayload;
siddwarkhedkar marked this conversation as resolved.
Show resolved Hide resolved

/**
* V3 API Wrapper
* @var KlaviyoV3Api $api
*/
protected $api;

public function __construct(
Context $context,
Logger $klaviyoLogger,
Expand All @@ -31,72 +43,101 @@ public function __construct(
parent::__construct($context);
$this->_klaviyoLogger = $klaviyoLogger;
$this->_klaviyoScopeSetting = $klaviyoScopeSetting;
$this->observerAtcPayload = null;
$this->api = new KlaviyoV3Api($this->_klaviyoScopeSetting->getPublicApiKey(), $this->_klaviyoScopeSetting->getPrivateApiKey(), $klaviyoScopeSetting);
}

public function getKlaviyoLists($api_key=null){
if (!$api_key) $api_key = $this->_klaviyoScopeSetting->getPrivateApiKey();

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://a.klaviyo.com/api/v2/lists?api_key=' . $api_key);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
public function getObserverAtcPayload()
{
return $this->observerAtcPayload;
}
siddwarkhedkar marked this conversation as resolved.
Show resolved Hide resolved

public function setObserverAtcPayload($data)
{
$this->observerAtcPayload = $data;
}
siddwarkhedkar marked this conversation as resolved.
Show resolved Hide resolved

$output = json_decode(curl_exec($ch));
$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
public function unsetObserverAtcPayload()
{
$this->observerAtcPayload = null;
}
siddwarkhedkar marked this conversation as resolved.
Show resolved Hide resolved

if ($statusCode !== 200) {
if ($statusCode === 403) {
$reason = 'The Private Klaviyo API Key you have set is invalid.';
} elseif ($statusCode === 401) {
$reason = 'The Private Klaviyo API key you have set is no longer valid.';
} else {
$reason = 'Unable to verify Klaviyo Private API Key.';
public function getKlaviyoLists()
{
try {
$lists_response = $this->api->getLists();
$lists = array();

foreach ($lists_response as $list) {
$lists[] = array(
'id' => $list['id'],
'name' => $list['attributes']['name']
);
}

$result = [
'success' => false,
'reason' => $reason
];
} else {
usort($output, function($a, $b) {
return strtolower($a->list_name) > strtolower($b->list_name) ? 1 : -1;
});

$result = [
return [
'success' => true,
'lists' => $output
'lists' => $lists
];
} catch (\Exception $e) {
$this->_klaviyoLogger->log(sprintf('Unable to get list: %s', $e["detail"]));
return [
'success' => false,
'reason' => $e["detail"]
];
}

return $result;
}

/**
* @param string $email
* @param string $firstName
* @param string $lastName
* @param string $source
* @return bool|string
* @param string|null $firstName
* @param string|null $lastName
* @param string|null $source
* @return array|false|null|string
*/
public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName = null, $source = null)
public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName = null)
{
$listId = $this->_klaviyoScopeSetting->getNewsletter();
$optInSetting = $this->_klaviyoScopeSetting->getOptInSetting();

$properties = [];
$properties['email'] = $email;
if ($firstName) $properties['$first_name'] = $firstName;
if ($lastName) $properties['$last_name'] = $lastName;
if ($source) $properties['$source'] = $source;
if ($optInSetting == ScopeSetting::API_SUBSCRIBE) $properties['$consent'] = ['email'];

$propertiesVal = ['profiles' => $properties];

$path = self::LIST_V2_API . $listId . $optInSetting;
if ($firstName) {
$properties['first_name'] = $firstName;
}
if ($lastName) {
$properties['last_name'] = $lastName;
}

try {
$response = $this->sendApiRequest($path, $propertiesVal, 'POST');
if ($optInSetting == ScopeSetting::API_SUBSCRIBE) {
// Subscribe profile using the profile creation endpoint for lists
$consent_profile_object = array(
'type' => 'profile',
'attributes' => array(
'email' => $email,
'subscriptions' => array(
'email' => [
'MARKETING'
]
)
)
);

$response = $this->api->subscribeMembersToList($listId, array($consent_profile_object));
} else {
// Search for profile by email using the api/profiles endpoint
$response = $this->api->searchProfileByEmail($email);
$profile_id = $response["profile_id"];
// If the profile exists, use the ID to add to a list
// If the profile does not exist, create
if ($profile_id) {
$this->api->addProfileToList($listId, $profile_id);
} else {
$new_profile = $this->api->createProfile($properties);
$this->api->addProfileToList($listId, $new_profile["profile_id"]);
}
}
} catch (\Exception $e) {
$this->_klaviyoLogger->log(sprintf('Unable to subscribe %s to list %s: %s', $email, $listId, $e));
$response = false;
Expand All @@ -107,19 +148,13 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName

/**
* @param string $email
* @return bool|string
* @return array|string|null
*/
public function unsubscribeEmailFromKlaviyoList($email)
{
$listId = $this->_klaviyoScopeSetting->getNewsletter();

$path = self::LIST_V2_API . $listId . ScopeSetting::API_SUBSCRIBE;
$fields = [
'emails' => [(string)$email],
];

try {
$response = $this->sendApiRequest($path, $fields, 'DELETE');
$response = $this->api->unsubscribeEmailFromKlaviyoList($email, $listId);
} catch (\Exception $e) {
$this->_klaviyoLogger->log(sprintf('Unable to unsubscribe %s from list %s: %s', $email, $listId, $e));
$response = false;
Expand All @@ -130,13 +165,14 @@ public function unsubscribeEmailFromKlaviyoList($email)

public function klaviyoTrackEvent($event, $customer_properties = [], $properties = [], $timestamp = null, $storeId = null)
{
if ((!array_key_exists('$email', $customer_properties) || empty($customer_properties['$email']))
&& (!array_key_exists('$id', $customer_properties) || empty($customer_properties['$id']))) {

if (
(!array_key_exists('$email', $customer_properties) || empty($customer_properties['$email']))
&& (!array_key_exists('$id', $customer_properties) || empty($customer_properties['$id']))
&& (!array_key_exists('$exchange_id', $customer_properties) || empty($customer_properties['$exchange_id']))
) {
return 'You must identify a user by email or ID.';
}
$params = array(
'token' => $this->_klaviyoScopeSetting->getPublicApiKey($storeId),
'event' => $event,
'properties' => $properties,
'customer_properties' => $customer_properties
Expand All @@ -145,60 +181,6 @@ public function klaviyoTrackEvent($event, $customer_properties = [], $properties
if (!is_null($timestamp)) {
$params['time'] = $timestamp;
}
$encoded_params = $this->build_params($params);
return $this->make_request('api/track', $encoded_params);

}
protected function build_params($params) {
return 'data=' . urlencode(base64_encode(json_encode($params)));
}

protected function make_request($path, $params) {
$url = self::KLAVIYO_HOST . $path . '?' . $params;
$response = file_get_contents($url);
return $response == '1';
}

/**
* @param string $path
* @param array $params
* @param string $method
* @return mixed[]
* @throws \Exception
*/
private function sendApiRequest(string $path, array $params, string $method = null)
{
$url = self::KLAVIYO_HOST . $path;

//Add API Key to params
$params['api_key'] = $this->_klaviyoScopeSetting->getPrivateApiKey();

$curl = curl_init();
$encodedParams = json_encode($params);

curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => (!empty($method)) ? $method : 'POST',
CURLOPT_POSTFIELDS => $encodedParams,
CURLOPT_USERAGENT => self::USER_AGENT,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($encodedParams)
],
]);

// Submit the request
$response = curl_exec($curl);
$err = curl_errno($curl);

if ($err) {
throw new \Exception(curl_error($curl));
}

// Close cURL session handle
curl_close($curl);

return $response;
return $this->api->track($params);
}
}
7 changes: 7 additions & 0 deletions KlaviyoV3Sdk/Exception/KlaviyoApiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception;

class KlaviyoApiException extends KlaviyoException
{
}
7 changes: 7 additions & 0 deletions KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception;

class KlaviyoAuthenticationException extends KlaviyoApiException
{
}
9 changes: 9 additions & 0 deletions KlaviyoV3Sdk/Exception/KlaviyoException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception;

use Exception;

class KlaviyoException extends Exception
{
}
7 changes: 7 additions & 0 deletions KlaviyoV3Sdk/Exception/KlaviyoRateLimitException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception;

class KlaviyoRateLimitException extends KlaviyoApiException
{
}
7 changes: 7 additions & 0 deletions KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception;

class KlaviyoResourceNotFoundException extends KlaviyoApiException
{
}
9 changes: 9 additions & 0 deletions KlaviyoV3Sdk/Exception/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;
Loading