Version Notes
Adding support for configuring Klaviyo settings at the store and website level.
Download this release
Release Info
| Developer | Klaviyo Team |
| Extension | Klaviyo_Reclaim |
| Version | 1.6.2 |
| Comparing to | |
| See all releases | |
Code changes from version 1.3.5 to 1.6.2
- app/code/community/Klaviyo/Reclaim/Block/Adminhtml/System/Config/Fieldset/Info.php +11 -2
- app/code/community/Klaviyo/Reclaim/Block/Adminhtml/System/Config/Form/Button.php +23 -0
- app/code/community/Klaviyo/Reclaim/Helper/Data.php +167 -6
- app/code/community/Klaviyo/Reclaim/Model/Api.php +1 -1
- app/code/community/Klaviyo/Reclaim/Model/Api2/Coupon.php +13 -0
- app/code/community/Klaviyo/Reclaim/Model/Api2/Coupon/Rest/Admin/V1.php +167 -0
- app/code/community/Klaviyo/Reclaim/Model/Api2/Rule.php +17 -0
- app/code/community/Klaviyo/Reclaim/Model/Api2/Rule/Rest/Admin/V1.php +26 -0
- app/code/community/Klaviyo/Reclaim/Model/KlaviyoApi.php +2 -1
- app/code/community/Klaviyo/Reclaim/Model/Observer.php +291 -165
- app/code/community/Klaviyo/Reclaim/controllers/IndexController.php +128 -16
- app/code/community/Klaviyo/Reclaim/controllers/KlaviyoController.php +49 -0
- app/code/community/Klaviyo/Reclaim/etc/adminhtml.xml +21 -21
- app/code/community/Klaviyo/Reclaim/etc/api2.xml +72 -0
- app/code/community/Klaviyo/Reclaim/etc/config.xml +15 -12
- app/code/community/Klaviyo/Reclaim/etc/system.xml +56 -4
- app/design/frontend/base/default/layout/klaviyoreclaim.xml +6 -1
- app/design/frontend/base/default/template/klaviyoreclaim/tracking/product.phtml +21 -7
- package.xml +15 -11
app/code/community/Klaviyo/Reclaim/Block/Adminhtml/System/Config/Fieldset/Info.php
CHANGED
|
@@ -37,8 +37,17 @@ class Klaviyo_Reclaim_Block_Adminhtml_System_Config_Fieldset_Info extends Mage_A
|
|
| 37 |
|
| 38 |
$has_reclaim_entries = Mage::getModel('klaviyo_reclaim/checkout')->getCollection()->count() > 0;
|
| 39 |
|
| 40 |
-
$is_extension_failing = $is_enabled
|
| 41 |
|
| 42 |
return array($is_extension_failing, $is_api_key_set, $is_cron_running, $has_reclaim_entries);
|
| 43 |
}
|
| 44 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
$has_reclaim_entries = Mage::getModel('klaviyo_reclaim/checkout')->getCollection()->count() > 0;
|
| 39 |
|
| 40 |
+
$is_extension_failing = $is_enabled && !($is_api_key_set || $is_cron_running || $has_reclaim_entries);
|
| 41 |
|
| 42 |
return array($is_extension_failing, $is_api_key_set, $is_cron_running, $has_reclaim_entries);
|
| 43 |
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
class Klaviyo_Reclaim_Block_Oauth_Credential_Renderer extends Mage_Adminhtml_Block_System_Config_Form_Field
|
| 47 |
+
{
|
| 48 |
+
protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element) {
|
| 49 |
+
$element->setDisabled('disabled');
|
| 50 |
+
|
| 51 |
+
return parent::_getElementHtml($element);
|
| 52 |
+
}
|
| 53 |
+
}
|
app/code/community/Klaviyo/Reclaim/Block/Adminhtml/System/Config/Form/Button.php
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
class Klaviyo_Reclaim_Block_Adminhtml_System_Config_Form_Button extends Mage_Adminhtml_Block_System_Config_Form_Field
|
| 3 |
+
{
|
| 4 |
+
/**
|
| 5 |
+
* Return element html
|
| 6 |
+
*
|
| 7 |
+
* @param Varien_Data_Form_Element_Abstract $element
|
| 8 |
+
* @return string
|
| 9 |
+
*/
|
| 10 |
+
protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
|
| 11 |
+
{
|
| 12 |
+
$oauthKeysUrl = Mage::helper('adminhtml')->getUrl('adminhtml/klaviyo/oauthkeys');
|
| 13 |
+
|
| 14 |
+
$button = $this->getLayout()->createBlock('adminhtml/widget_button')
|
| 15 |
+
->setData(array(
|
| 16 |
+
'id' => 'klaviyo_reclaim_generate_oauth_tokens_button',
|
| 17 |
+
'label' => 'Generate OAuth Tokens',
|
| 18 |
+
'onclick' => 'setLocation(\'' . $oauthKeysUrl . '\');'
|
| 19 |
+
));
|
| 20 |
+
|
| 21 |
+
return $button->toHtml();
|
| 22 |
+
}
|
| 23 |
+
}
|
app/code/community/Klaviyo/Reclaim/Helper/Data.php
CHANGED
|
@@ -24,7 +24,31 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 24 |
* @var string
|
| 25 |
*/
|
| 26 |
const XML_PATH_PRIVATE_API_KEY = 'reclaim/general/private_api_key';
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
/**
|
| 29 |
* Path to store config for Klaviyo list to sync Magento general subscription list with.
|
| 30 |
* @var string
|
|
@@ -37,6 +61,12 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 37 |
*/
|
| 38 |
const XML_PATH_USE_KLAVIYO_LIST_NAME = 'reclaim/general/use_klaviyo_list_name';
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
/* For the "etc/adminthtml.xml" file when we implement:
|
| 41 |
<use_klaviyo_list_name translate="label comment">
|
| 42 |
<label>Use Klaviyo List Name</label>
|
|
@@ -51,6 +81,52 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 51 |
</use_klaviyo_list_name>
|
| 52 |
*/
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
/**
|
| 55 |
* Utility for fetching settings for our extension.
|
| 56 |
* @param integer|string|Mage_Core_Model_Store $store
|
|
@@ -78,9 +154,9 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 78 |
*/
|
| 79 |
public function isEnabled($store=null)
|
| 80 |
{
|
| 81 |
-
return
|
| 82 |
}
|
| 83 |
-
|
| 84 |
/**
|
| 85 |
* Return the Klaviyo Public API key
|
| 86 |
* @param integer|string|Mage_Core_Model_Store $store
|
|
@@ -88,7 +164,7 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 88 |
*/
|
| 89 |
public function getPublicApiKey($store=null)
|
| 90 |
{
|
| 91 |
-
return
|
| 92 |
}
|
| 93 |
|
| 94 |
/**
|
|
@@ -98,7 +174,47 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 98 |
*/
|
| 99 |
public function getPrivateApiKey($store=null)
|
| 100 |
{
|
| 101 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
public function getSubscriptionList($store)
|
|
@@ -141,10 +257,55 @@ class Klaviyo_Reclaim_Helper_Data extends Mage_Core_Helper_Data
|
|
| 141 |
return $checkout;
|
| 142 |
}
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
public function log($data, $filename)
|
| 145 |
{
|
| 146 |
if ($this->config('enable_log') != 0) {
|
| 147 |
return Mage::getModel('core/log_adapter', $filename)->log($data);
|
| 148 |
}
|
| 149 |
}
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
* @var string
|
| 25 |
*/
|
| 26 |
const XML_PATH_PRIVATE_API_KEY = 'reclaim/general/private_api_key';
|
| 27 |
+
|
| 28 |
+
/**
|
| 29 |
+
* Path to store config where OAuth consumer key is stored
|
| 30 |
+
* @var string
|
| 31 |
+
*/
|
| 32 |
+
const XML_PATH_CONSUMER_KEY = 'reclaim/general/consumer_key';
|
| 33 |
+
|
| 34 |
+
/**
|
| 35 |
+
* Path to store config where OAuth consumer key is stored
|
| 36 |
+
* @var string
|
| 37 |
+
*/
|
| 38 |
+
const XML_PATH_CONSUMER_SECRET = 'reclaim/general/consumer_secret';
|
| 39 |
+
|
| 40 |
+
/**
|
| 41 |
+
* Path to store config where OAuth consumer key is stored
|
| 42 |
+
* @var string
|
| 43 |
+
*/
|
| 44 |
+
const XML_PATH_AUTHORIZATION_TOKEN = 'reclaim/general/authorization_token';
|
| 45 |
+
|
| 46 |
+
/**
|
| 47 |
+
* Path to store config where OAuth consumer key is stored
|
| 48 |
+
* @var string
|
| 49 |
+
*/
|
| 50 |
+
const XML_PATH_AUTHORIZATION_SECRET = 'reclaim/general/authorization_secret';
|
| 51 |
+
|
| 52 |
/**
|
| 53 |
* Path to store config for Klaviyo list to sync Magento general subscription list with.
|
| 54 |
* @var string
|
| 61 |
*/
|
| 62 |
const XML_PATH_USE_KLAVIYO_LIST_NAME = 'reclaim/general/use_klaviyo_list_name';
|
| 63 |
|
| 64 |
+
/**
|
| 65 |
+
* Path for klaviyo log file.
|
| 66 |
+
* @var string
|
| 67 |
+
*/
|
| 68 |
+
const LOG_FILE_PATH = 'klaviyo.log';
|
| 69 |
+
|
| 70 |
/* For the "etc/adminthtml.xml" file when we implement:
|
| 71 |
<use_klaviyo_list_name translate="label comment">
|
| 72 |
<label>Use Klaviyo List Name</label>
|
| 81 |
</use_klaviyo_list_name>
|
| 82 |
*/
|
| 83 |
|
| 84 |
+
/**
|
| 85 |
+
* Get configuration value by searching for the most specific setting moving from
|
| 86 |
+
* store scope to website scope to global scope.
|
| 87 |
+
*
|
| 88 |
+
* @param $path
|
| 89 |
+
* @param integer|string|Mage_Core_Model_Store $store
|
| 90 |
+
* @param bool $returnParentValueIfNull
|
| 91 |
+
* @return mixed|null
|
| 92 |
+
* @throws Mage_Core_Exception
|
| 93 |
+
*/
|
| 94 |
+
public function getConfigSettingIncludingParents($path, $store=null)
|
| 95 |
+
{
|
| 96 |
+
$value = null;
|
| 97 |
+
|
| 98 |
+
if (!is_null($store)) {
|
| 99 |
+
$possible_value = Mage::getStoreConfig($path, $store);
|
| 100 |
+
if (!is_null($possible_value)) {
|
| 101 |
+
$value = $possible_value;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// If we didn't find a value at the store level, check the website config.
|
| 105 |
+
if (is_null($value)) {
|
| 106 |
+
$website = $store->getWebsite();
|
| 107 |
+
|
| 108 |
+
// `getWebsite` could return `false` if there's no website associated with the store.
|
| 109 |
+
// In practice, I'm not sure why this would happen, but Magento allows it.
|
| 110 |
+
if ($website) {
|
| 111 |
+
$possible_value = $website->getConfig($path);
|
| 112 |
+
if (!is_null($possible_value)) {
|
| 113 |
+
$value = $possible_value;
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// If we didn't find a value at the store or website level, check the global config.
|
| 120 |
+
if (is_null($value)) {
|
| 121 |
+
$possible_value = Mage::getStoreConfig($path);
|
| 122 |
+
if (!is_null($possible_value)) {
|
| 123 |
+
$value = $possible_value;
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
return $value;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
/**
|
| 131 |
* Utility for fetching settings for our extension.
|
| 132 |
* @param integer|string|Mage_Core_Model_Store $store
|
| 154 |
*/
|
| 155 |
public function isEnabled($store=null)
|
| 156 |
{
|
| 157 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_ENABLED, $store);
|
| 158 |
}
|
| 159 |
+
|
| 160 |
/**
|
| 161 |
* Return the Klaviyo Public API key
|
| 162 |
* @param integer|string|Mage_Core_Model_Store $store
|
| 164 |
*/
|
| 165 |
public function getPublicApiKey($store=null)
|
| 166 |
{
|
| 167 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_PUBLIC_API_KEY, $store);
|
| 168 |
}
|
| 169 |
|
| 170 |
/**
|
| 174 |
*/
|
| 175 |
public function getPrivateApiKey($store=null)
|
| 176 |
{
|
| 177 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_PRIVATE_API_KEY, $store);
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
/**
|
| 181 |
+
* Return the store's OAuth Consumer Key
|
| 182 |
+
* @param integer|string|Mage_Core_Model_Store $store
|
| 183 |
+
* @return string
|
| 184 |
+
*/
|
| 185 |
+
public function getConsumerKey($store=null)
|
| 186 |
+
{
|
| 187 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_CONSUMER_KEY, $store);
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/**
|
| 191 |
+
* Return the store's OAuth Consumer Secret
|
| 192 |
+
* @param integer|string|Mage_Core_Model_Store $store
|
| 193 |
+
* @return string
|
| 194 |
+
*/
|
| 195 |
+
public function getConsumerSecret($store=null)
|
| 196 |
+
{
|
| 197 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_CONSUMER_SECRET, $store);
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/**
|
| 201 |
+
* Return the store's OAuth Authorization Token
|
| 202 |
+
* @param integer|string|Mage_Core_Model_Store $store
|
| 203 |
+
* @return string
|
| 204 |
+
*/
|
| 205 |
+
public function getAuthorizationToken($store=null)
|
| 206 |
+
{
|
| 207 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_AUTHORIZATION_TOKEN, $store);
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
/**
|
| 211 |
+
* Return the store's OAuth Authorization Secret
|
| 212 |
+
* @param integer|string|Mage_Core_Model_Store $store
|
| 213 |
+
* @return string
|
| 214 |
+
*/
|
| 215 |
+
public function getAuthorizationSecret($store=null)
|
| 216 |
+
{
|
| 217 |
+
return $this->getConfigSettingIncludingParents(self::XML_PATH_AUTHORIZATION_SECRET, $store);
|
| 218 |
}
|
| 219 |
|
| 220 |
public function getSubscriptionList($store)
|
| 257 |
return $checkout;
|
| 258 |
}
|
| 259 |
|
| 260 |
+
/**
|
| 261 |
+
* Set the store's OAuth Consumer Key
|
| 262 |
+
* @param string
|
| 263 |
+
* @return void
|
| 264 |
+
*/
|
| 265 |
+
public function setConsumerKey($consumerKey)
|
| 266 |
+
{
|
| 267 |
+
Mage::getModel('core/config')->saveConfig(self::XML_PATH_CONSUMER_KEY, $consumerKey);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
/**
|
| 271 |
+
* Set the store's OAuth Consumer Secret
|
| 272 |
+
* @param string
|
| 273 |
+
* @return void
|
| 274 |
+
*/
|
| 275 |
+
public function setConsumerSecret($consumerSecret)
|
| 276 |
+
{
|
| 277 |
+
Mage::getModel('core/config')->saveConfig(self::XML_PATH_CONSUMER_SECRET, $consumerSecret);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
/**
|
| 281 |
+
* Set the store's OAuth Authorization Token
|
| 282 |
+
* @param string
|
| 283 |
+
* @return void
|
| 284 |
+
*/
|
| 285 |
+
public function setAuthorizationToken($authorizationToken)
|
| 286 |
+
{
|
| 287 |
+
Mage::getModel('core/config')->saveConfig(self::XML_PATH_AUTHORIZATION_TOKEN, $authorizationToken);
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
/**
|
| 291 |
+
* Set the store's OAuth Authroization Secret
|
| 292 |
+
* @param string
|
| 293 |
+
* @return void
|
| 294 |
+
*/
|
| 295 |
+
public function setAuthorizationSecret($authorizationSecret)
|
| 296 |
+
{
|
| 297 |
+
Mage::getModel('core/config')->saveConfig(self::XML_PATH_AUTHORIZATION_SECRET, $authorizationSecret);
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
public function log($data, $filename)
|
| 301 |
{
|
| 302 |
if ($this->config('enable_log') != 0) {
|
| 303 |
return Mage::getModel('core/log_adapter', $filename)->log($data);
|
| 304 |
}
|
| 305 |
}
|
| 306 |
+
|
| 307 |
+
public function getLogFile()
|
| 308 |
+
{
|
| 309 |
+
return self::LOG_FILE_PATH;
|
| 310 |
+
}
|
| 311 |
+
}
|
app/code/community/Klaviyo/Reclaim/Model/Api.php
CHANGED
|
@@ -123,7 +123,7 @@ class Klaviyo_Reclaim_Model_Api
|
|
| 123 |
* @return void
|
| 124 |
*/
|
| 125 |
protected function _logApiRequest($data) {
|
| 126 |
-
Mage::log($data);
|
| 127 |
}
|
| 128 |
|
| 129 |
}
|
| 123 |
* @return void
|
| 124 |
*/
|
| 125 |
protected function _logApiRequest($data) {
|
| 126 |
+
Mage::log($data, Zend_Log::INFO, Mage::helper('klaviyo_reclaim')->getLogFile());
|
| 127 |
}
|
| 128 |
|
| 129 |
}
|
app/code/community/Klaviyo/Reclaim/Model/Api2/Coupon.php
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/**
|
| 3 |
+
* Coupon Api
|
| 4 |
+
*
|
| 5 |
+
* @category Klaviyo
|
| 6 |
+
* @package Klaviyo_Reclaim
|
| 7 |
+
* @author Klaviyo Team <support@klaviyo.com>
|
| 8 |
+
*/
|
| 9 |
+
class Klaviyo_Reclaim_Model_Api2_Coupon extends Mage_Api2_Model_Resource
|
| 10 |
+
{
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
}
|
app/code/community/Klaviyo/Reclaim/Model/Api2/Coupon/Rest/Admin/V1.php
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/* Coupon REST API
|
| 3 |
+
*
|
| 4 |
+
* @category Klaviyo
|
| 5 |
+
* @package Klaviyo_Reclaim
|
| 6 |
+
* @author Klaviyo Team <support@klaviyo.com>
|
| 7 |
+
*/
|
| 8 |
+
class Klaviyo_Reclaim_Model_Api2_Coupon_Rest_Admin_V1 extends Klaviyo_Reclaim_Model_Api2_Coupon
|
| 9 |
+
{
|
| 10 |
+
const COUPON_CREATE_SUCCESS = 'Coupon created';
|
| 11 |
+
const COUPON_CODE_GENERATE_FAILURE = 'Unable to generate Coupon code. Please check your parameters and try again.';
|
| 12 |
+
|
| 13 |
+
/**
|
| 14 |
+
* Generate one or more coupon codes using the Generate Coupons rule provided.
|
| 15 |
+
* This must be a _multiCreate so we can return messages, _creates cannot return messages.
|
| 16 |
+
* In order for this endpoint to be hit, params must not be an associative array.
|
| 17 |
+
*
|
| 18 |
+
* Expected parameters are...
|
| 19 |
+
* qty: (int) number of coupon codes to instruct Magento to generate
|
| 20 |
+
* length: (int) length of each generated coupon code
|
| 21 |
+
* format: (string) coupon code format, one of 'alphanum', 'alpha', and 'num'
|
| 22 |
+
*
|
| 23 |
+
* @return string
|
| 24 |
+
*/
|
| 25 |
+
protected function _multiCreate($data)
|
| 26 |
+
{
|
| 27 |
+
$ruleId = $this->getRequest()->getParam('rule_id');
|
| 28 |
+
$rule = $this->_loadSalesRule($ruleId);
|
| 29 |
+
|
| 30 |
+
$couponData = $data[0];
|
| 31 |
+
$couponData['rule_id'] = $ruleId;
|
| 32 |
+
|
| 33 |
+
$couponGenerator = $rule->getCouponMassGenerator();
|
| 34 |
+
|
| 35 |
+
// validate coupon attributes
|
| 36 |
+
if (!$couponGenerator->validateData($couponData)) {
|
| 37 |
+
$this->_critical(Mage::helper('salesrule')->__('Coupon API: Invalid parameters provided.'),
|
| 38 |
+
Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
$couponGenerator->setData($couponData);
|
| 42 |
+
|
| 43 |
+
if ($couponGenerator->getMaxAttempts()) {
|
| 44 |
+
$maxGenerateAttempts = $couponGenerator->getMaxAttempts();
|
| 45 |
+
} else {
|
| 46 |
+
$maxGenerateAttempts = $couponGenerator::MAX_GENERATE_ATTEMPTS;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
$now = $couponGenerator->getResource()->formatDate(
|
| 50 |
+
Mage::getSingleton('core/date')->gmtTimestamp()
|
| 51 |
+
);
|
| 52 |
+
|
| 53 |
+
$coupon = Mage::getModel('salesrule/coupon');
|
| 54 |
+
|
| 55 |
+
for ($i = 0; $i < $couponData['qty']; $i++) {
|
| 56 |
+
$attempt = 0;
|
| 57 |
+
$breakOut = false;
|
| 58 |
+
do {
|
| 59 |
+
if ($attempt >= $maxGenerateAttempts) {
|
| 60 |
+
// Report this failure for all coupons that still need to be created,
|
| 61 |
+
// and flag to end trying to generation coupon codes.
|
| 62 |
+
for ($failedCouponIndex = 0; $failedCouponIndex < $couponData['qty'] - $i; $failedCouponIndex++) {
|
| 63 |
+
$this->getResponse()->addMessage(self::COUPON_CODE_GENERATE_FAILURE, 0);
|
| 64 |
+
}
|
| 65 |
+
$breakOut = true;
|
| 66 |
+
} else {
|
| 67 |
+
$code = $couponGenerator->generateCode();
|
| 68 |
+
$attempt++;
|
| 69 |
+
}
|
| 70 |
+
} while ($couponGenerator->getResource()->exists($code));
|
| 71 |
+
|
| 72 |
+
if ($breakOut) {
|
| 73 |
+
break;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
$expirationDate = $couponGenerator->getToDate();
|
| 77 |
+
if ($expirationDate instanceof Zend_Date) {
|
| 78 |
+
$expirationDate = $expirationDate->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
$coupon->setId(null)
|
| 82 |
+
->setRuleId($ruleId)
|
| 83 |
+
->setUsageLimit($couponGenerator->getUsesPerCoupon())
|
| 84 |
+
->setUsagePerCustomer($couponGenerator->getUsesPerCustomer())
|
| 85 |
+
->setExpirationDate($expirationDate)
|
| 86 |
+
->setCreatedAt($now)
|
| 87 |
+
->setType(Mage_SalesRule_Helper_Coupon::COUPON_TYPE_SPECIFIC_AUTOGENERATED)
|
| 88 |
+
->setCode($code)
|
| 89 |
+
->save();
|
| 90 |
+
|
| 91 |
+
$this->getResponse()->addMessage(
|
| 92 |
+
self::COUPON_CREATE_SUCCESS, $coupon['code'],
|
| 93 |
+
array('id' => $coupon->getId()),
|
| 94 |
+
Mage_Api2_Model_Response::MESSAGE_TYPE_SUCCESS
|
| 95 |
+
);
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/**
|
| 100 |
+
* Delete a Coupon associated with a rule.
|
| 101 |
+
*
|
| 102 |
+
* @return void
|
| 103 |
+
*/
|
| 104 |
+
protected function _delete()
|
| 105 |
+
{
|
| 106 |
+
$ruleId = $this->getRequest()->getParam('rule_id');
|
| 107 |
+
$rule = $this->_loadSalesRule($ruleId);
|
| 108 |
+
|
| 109 |
+
$couponId = $this->getRequest()->getParam('coupon_id');
|
| 110 |
+
$coupon = Mage::getModel('salesrule/coupon')->load($couponId);
|
| 111 |
+
|
| 112 |
+
// perform validation to ensure the coupon we want to delete is associated with the rule in mind
|
| 113 |
+
if ($coupon->getRuleId() != $rule->getId()) {
|
| 114 |
+
$this->_critical(Mage::helper('salesrule/coupon')->__('Coupon API: Invalid parameters provided, rule not associated with coupon.'),
|
| 115 |
+
Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
try {
|
| 119 |
+
$coupon->delete();
|
| 120 |
+
} catch (Mage_Core_Exception $e) {
|
| 121 |
+
$this->_critical($e->getMessage(), Mage_Api2_Model_Server::HTTP_INTERNAL_ERROR);
|
| 122 |
+
} catch (Exception $e) {
|
| 123 |
+
$this->_critical(self::RESOURCE_INTERNAL_ERROR);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
echo 1;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/**
|
| 130 |
+
* Retrieve list of coupon codes.
|
| 131 |
+
*
|
| 132 |
+
* @return array
|
| 133 |
+
*/
|
| 134 |
+
protected function _retrieveCollection()
|
| 135 |
+
{
|
| 136 |
+
$ruleId = $this->getRequest()->getParam('rule_id');
|
| 137 |
+
$rule = $this->_loadSalesRule($ruleId);
|
| 138 |
+
|
| 139 |
+
$couponCollection = Mage::getResourceModel('salesrule/coupon_collection');
|
| 140 |
+
$couponCollection->addRuleToFilter($rule);
|
| 141 |
+
|
| 142 |
+
$this->_applyCollectionModifiers($couponCollection);
|
| 143 |
+
$data = $couponCollection->load()->toArray();
|
| 144 |
+
|
| 145 |
+
return $data['items'];
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
/**
|
| 149 |
+
* Load sales rule by ID.
|
| 150 |
+
*
|
| 151 |
+
* @param int $ruleId
|
| 152 |
+
* @return Mage_SalesRule_Model_Rule
|
| 153 |
+
*/
|
| 154 |
+
protected function _loadSalesRule($ruleId)
|
| 155 |
+
{
|
| 156 |
+
if (!$ruleId) {
|
| 157 |
+
$this->_critical(Mage::helper('salesrule')
|
| 158 |
+
->__('Rule ID is required.'), Mage_Api2_Model_Server::HTTP_BAD_REQUEST);
|
| 159 |
+
}
|
| 160 |
+
$rule = Mage::getModel('salesrule/rule')->load($ruleId);
|
| 161 |
+
if (!$rule->getId()) {
|
| 162 |
+
$this->_critical(Mage::helper('salesrule')
|
| 163 |
+
->__('Rule was not found.'), Mage_Api2_Model_Server::HTTP_NOT_FOUND);
|
| 164 |
+
}
|
| 165 |
+
return $rule;
|
| 166 |
+
}
|
| 167 |
+
}
|
app/code/community/Klaviyo/Reclaim/Model/Api2/Rule.php
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/**
|
| 3 |
+
* @category Klaviyo
|
| 4 |
+
* @package Klaviyo_Reclaim
|
| 5 |
+
* @copyright Copyright (c) 2013 Klaviyo Inc. (http://www.klaviyo.com)
|
| 6 |
+
*/
|
| 7 |
+
/**
|
| 8 |
+
* Rule Api
|
| 9 |
+
*
|
| 10 |
+
* @category Klaviyo
|
| 11 |
+
* @package Klaviyo_Reclaim
|
| 12 |
+
* @author Klaviyo Team <support@klaviyo.com>
|
| 13 |
+
*/
|
| 14 |
+
class Klaviyo_Reclaim_Model_Api2_Rule extends Mage_Api2_Model_Resource
|
| 15 |
+
{
|
| 16 |
+
}
|
| 17 |
+
|
app/code/community/Klaviyo/Reclaim/Model/Api2/Rule/Rest/Admin/V1.php
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
/* Rule REST API
|
| 3 |
+
*
|
| 4 |
+
* @category Klaviyo
|
| 5 |
+
* @package Klaviyo_Reclaim
|
| 6 |
+
* @author Klaviyo Team <support@klaviyo.com>
|
| 7 |
+
*/
|
| 8 |
+
class Klaviyo_Reclaim_Model_Api2_Rule_Rest_Admin_V1 extends Klaviyo_Reclaim_Model_Api2_Rule
|
| 9 |
+
{
|
| 10 |
+
/**
|
| 11 |
+
* Retrieve list of coupon codes.
|
| 12 |
+
*
|
| 13 |
+
* @return array
|
| 14 |
+
*/
|
| 15 |
+
protected function _retrieveCollection()
|
| 16 |
+
{
|
| 17 |
+
$data = array();
|
| 18 |
+
|
| 19 |
+
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
|
| 20 |
+
foreach ($rules as $rule) {
|
| 21 |
+
array_push($data, $rule->getData());
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
return $data;
|
| 25 |
+
}
|
| 26 |
+
}
|
app/code/community/Klaviyo/Reclaim/Model/KlaviyoApi.php
CHANGED
|
@@ -66,6 +66,7 @@ class Klaviyo_Reclaim_Model_KlaviyoApi
|
|
| 66 |
|
| 67 |
function lists($page=0, $count=50) {
|
| 68 |
$params = array();
|
|
|
|
| 69 |
$params['page'] = $page;
|
| 70 |
$params['count'] = $count;
|
| 71 |
return $this->callServer('GET', 'lists', $params);
|
|
@@ -107,7 +108,7 @@ class Klaviyo_Reclaim_Model_KlaviyoApi
|
|
| 107 |
$client->setParameterPost($params);
|
| 108 |
} else {
|
| 109 |
$client->setRawData(http_build_query($params));
|
| 110 |
-
}
|
| 111 |
|
| 112 |
try {
|
| 113 |
$response = $client->request();
|
| 66 |
|
| 67 |
function lists($page=0, $count=50) {
|
| 68 |
$params = array();
|
| 69 |
+
$params['type'] = 'list';
|
| 70 |
$params['page'] = $page;
|
| 71 |
$params['count'] = $count;
|
| 72 |
return $this->callServer('GET', 'lists', $params);
|
| 108 |
$client->setParameterPost($params);
|
| 109 |
} else {
|
| 110 |
$client->setRawData(http_build_query($params));
|
| 111 |
+
}
|
| 112 |
|
| 113 |
try {
|
| 114 |
$response = $client->request();
|
app/code/community/Klaviyo/Reclaim/Model/Observer.php
CHANGED
|
@@ -15,7 +15,18 @@
|
|
| 15 |
*/
|
| 16 |
class Klaviyo_Reclaim_Model_Observer
|
| 17 |
{
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
var $tracker_cache = array();
|
| 20 |
|
| 21 |
/**
|
|
@@ -31,196 +42,304 @@ class Klaviyo_Reclaim_Model_Observer
|
|
| 31 |
* @return Klaviyo_Reclaim_Model_Observer
|
| 32 |
*/
|
| 33 |
public function trackQuotes() {
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
}
|
| 38 |
|
| 39 |
-
$this->_errors = array();
|
| 40 |
try {
|
| 41 |
-
$adapter = Mage::getSingleton('core/resource')->getConnection('sales_read');
|
| 42 |
-
|
| 43 |
// Find quotes that are at least 15 minutes old and have been updated in the last 60 minutes.
|
| 44 |
$created_window_minutes = 15;
|
| 45 |
-
|
| 46 |
-
$created_before = Zend_Date::now();
|
| 47 |
-
$created_before->sub($created_window_minutes, Zend_Date::MINUTE);
|
| 48 |
-
$created_before = $adapter->convertDateTime($created_before);
|
| 49 |
-
|
| 50 |
$updated_window_minutes = 60;
|
| 51 |
|
| 52 |
-
$
|
| 53 |
-
$updated_after->sub($updated_window_minutes, Zend_Date::MINUTE);
|
| 54 |
-
$updated_after = $adapter->convertDateTime($updated_after);
|
| 55 |
-
|
| 56 |
-
$quotes = Mage::getResourceModel('sales/quote_collection')
|
| 57 |
-
->addFieldToFilter('converted_at', array('null' => true))
|
| 58 |
-
->addFieldToFilter('created_at', array('lteq' => $created_before))
|
| 59 |
-
->addFieldToFilter('updated_at', array('gteq' => $updated_after));
|
| 60 |
|
|
|
|
| 61 |
$quotes_tracked = 0;
|
| 62 |
|
| 63 |
foreach ($quotes as $quote) {
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
if (!$billing_address || !$tracker) {
|
| 68 |
-
continue;
|
| 69 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
$checkout = Mage::helper('klaviyo_reclaim')->getCheckout($quote_id);
|
| 81 |
-
|
| 82 |
-
$customer_properties = array(
|
| 83 |
-
'$email' => $quote->getCustomerEmail(),
|
| 84 |
-
'$first_name' => $quote->getCustomerFirstname(),
|
| 85 |
-
'$last_name' => $quote->getCustomerLastname(),
|
| 86 |
-
'location' => array(
|
| 87 |
-
'source' => 'magento',
|
| 88 |
-
'address1' => $billing_address->getStreet(1),
|
| 89 |
-
'city' => $billing_address->getCity(),
|
| 90 |
-
'region' => $billing_address->getRegion(),
|
| 91 |
-
'zip' => $billing_address->getPostcode(),
|
| 92 |
-
'country' => $billing_address->getCountry()
|
| 93 |
-
)
|
| 94 |
-
);
|
| 95 |
-
|
| 96 |
-
if ($billing_address->getStreet(2)) {
|
| 97 |
-
$customer_properties['location']['address2'] = $billing_address->getStreet(2);
|
| 98 |
-
}
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
$quote_item_product = Mage::getModel('catalog/product')->load($quote_item->getProduct()->getId());
|
| 110 |
-
$quote_item_product_name = $quote_item_product->getName();
|
| 111 |
-
|
| 112 |
-
for ($i = 0; $i < $quote_item_quantity; $i++) {
|
| 113 |
-
$item_count++;
|
| 114 |
-
}
|
| 115 |
-
$item_descriptions[] = $quote_item_product_name;
|
| 116 |
-
$line_item_count++;
|
| 117 |
-
|
| 118 |
-
$product_images = array();
|
| 119 |
-
foreach ($quote_item_product->getMediaGalleryImages() as $product_image) {
|
| 120 |
-
if (!$product_image->getDisabled()) {
|
| 121 |
-
$product_images[] = array(
|
| 122 |
-
'url' => $product_image->getUrl()
|
| 123 |
-
);
|
| 124 |
-
}
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
if ($quote_item_product->isConfigurable()) {
|
| 128 |
-
$configurable_product_ids[] = $quote_item_product->getId();
|
| 129 |
-
}
|
| 130 |
-
|
| 131 |
-
if ($line_item_count <= self::MAX_LINE_ITEMS_WITH_DETAILS) {
|
| 132 |
-
$item_details[] = array(
|
| 133 |
-
'quantity' => (float) $quote_item_quantity,
|
| 134 |
-
'row_total' => (float) $quote_item->getBaseRowTotal(),
|
| 135 |
-
'row_discount' => (float) $quote_item->getBaseDiscountAmount(),
|
| 136 |
-
'product' => array(
|
| 137 |
-
'id' => $quote_item_product->getId(),
|
| 138 |
-
'sku' => $quote_item_product->getSKU(),
|
| 139 |
-
'name' => $quote_item_product->getName(),
|
| 140 |
-
'price' => (float) $quote_item_product->getPrice(),
|
| 141 |
-
'images' => $product_images
|
| 142 |
-
)
|
| 143 |
-
);
|
| 144 |
-
}
|
| 145 |
-
}
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
// Look for simple products that are really represented by a configurable product.
|
| 152 |
-
foreach ($item_details as $line_index => $line) {
|
| 153 |
-
$product_id = $line['product']['id'];
|
| 154 |
-
if (in_array($product_id, $configurable_product_ids) || $line['row_total']) {
|
| 155 |
-
$tmp[] = $line;
|
| 156 |
-
continue;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
$configurable_parent_ids = $model_configurable->getParentIdsByChild($product_id);
|
| 160 |
-
$common_ids = array_intersect($configurable_product_ids, $configurable_parent_ids);
|
| 161 |
-
|
| 162 |
-
// If it's a simple product placeholder for a configurable product, discard it. Otherwise, keep it.
|
| 163 |
-
if (empty($common_ids)) {
|
| 164 |
-
$tmp[] = $line;
|
| 165 |
-
}
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
// We use the temporary array so when we json_encode we get an array, not an object.
|
| 169 |
-
$item_details = $tmp;
|
| 170 |
-
}
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
}
|
| 176 |
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
-
|
| 202 |
|
| 203 |
-
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
}
|
|
|
|
|
|
|
| 208 |
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
}
|
| 211 |
-
} catch (Exception $e) {
|
| 212 |
-
$this->_errors[] = $e->getMessage();
|
| 213 |
-
$this->_errors[] = $e->getTrace();
|
| 214 |
-
Mage::log($e->getMessage(), Zend_Log::ERR);
|
| 215 |
-
Mage::logException($e);
|
| 216 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
}
|
| 220 |
|
| 221 |
public function syncSubscriber (Varien_Event_Observer $observer) {
|
| 222 |
-
// Mage::getSingleton('klaviyo_reclaim/api')->lists();
|
| 223 |
-
|
| 224 |
if (!Mage::helper('klaviyo_reclaim')->isEnabled()) {
|
| 225 |
return $observer;
|
| 226 |
}
|
|
@@ -255,7 +374,8 @@ class Klaviyo_Reclaim_Model_Observer
|
|
| 255 |
$subscriber_status = $subscriber->getStatus();
|
| 256 |
|
| 257 |
if ($subscriber_status == Mage_Newsletter_Model_Subscriber::STATUS_UNCONFIRMED ||
|
| 258 |
-
$subscriber_status == Mage_Newsletter_Model_Subscriber::STATUS_SUBSCRIBED
|
|
|
|
| 259 |
|
| 260 |
$response = Mage::getSingleton('klaviyo_reclaim/api')->listSubscriberAdd($list_id, $email, $is_requiring_confirmation);
|
| 261 |
|
|
@@ -303,8 +423,14 @@ class Klaviyo_Reclaim_Model_Observer
|
|
| 303 |
Mage::getSingleton('klaviyo_reclaim/api')->listSubscriberDelete($list_id, $email);
|
| 304 |
}
|
| 305 |
|
| 306 |
-
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
}
|
| 309 |
|
| 310 |
public function getTracker($quote) {
|
| 15 |
*/
|
| 16 |
class Klaviyo_Reclaim_Model_Observer
|
| 17 |
{
|
| 18 |
+
/**
|
| 19 |
+
* Cache for booleans for whether the Klaviyo is enabled for each store.
|
| 20 |
+
*
|
| 21 |
+
* @var array
|
| 22 |
+
*/
|
| 23 |
+
var $is_enabled_cache = array();
|
| 24 |
+
|
| 25 |
+
/**
|
| 26 |
+
* Cache for tracker objects for each store.
|
| 27 |
+
*
|
| 28 |
+
* @var array
|
| 29 |
+
*/
|
| 30 |
var $tracker_cache = array();
|
| 31 |
|
| 32 |
/**
|
| 42 |
* @return Klaviyo_Reclaim_Model_Observer
|
| 43 |
*/
|
| 44 |
public function trackQuotes() {
|
| 45 |
+
$this->_errors = array();
|
| 46 |
|
| 47 |
+
$quotes_found = 0;
|
| 48 |
+
$quotes_tracked = 0;
|
|
|
|
| 49 |
|
|
|
|
| 50 |
try {
|
|
|
|
|
|
|
| 51 |
// Find quotes that are at least 15 minutes old and have been updated in the last 60 minutes.
|
| 52 |
$created_window_minutes = 15;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
$updated_window_minutes = 60;
|
| 54 |
|
| 55 |
+
$quotes = $this->_fetchRecentQuotes($created_window_minutes, $updated_window_minutes);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
$quotes_found = count($quotes);
|
| 58 |
$quotes_tracked = 0;
|
| 59 |
|
| 60 |
foreach ($quotes as $quote) {
|
| 61 |
+
if ($this->_maybeTrackQuote($quote)) {
|
| 62 |
+
$quotes_tracked++;
|
|
|
|
|
|
|
|
|
|
| 63 |
}
|
| 64 |
+
}
|
| 65 |
+
} catch (Exception $e) {
|
| 66 |
+
$this->_errors[] = $e->getMessage();
|
| 67 |
+
$this->_errors[] = $e->getTrace();
|
| 68 |
+
Mage::log($e->getMessage(), Zend_Log::ERR, Mage::helper('klaviyo_reclaim')->getLogFile());
|
| 69 |
+
Mage::logException($e);
|
| 70 |
+
}
|
| 71 |
|
| 72 |
+
// Log results in case we need to debug.
|
| 73 |
+
$json = Mage::helper('core')->jsonEncode(array(
|
| 74 |
+
'cron_job' => 'klaviyo_track_quotes',
|
| 75 |
+
'found' => $quotes_found,
|
| 76 |
+
'tracked' => $quotes_tracked
|
| 77 |
+
));
|
| 78 |
+
Mage::log($json, Zend_Log::INFO, Mage::helper('klaviyo_reclaim')->getLogFile());
|
| 79 |
|
| 80 |
+
return $this;
|
| 81 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
/**
|
| 84 |
+
* Read quotes that have been updated recently, but were created at least X minutes ago.
|
| 85 |
+
*
|
| 86 |
+
* @param $created_window_minutes (int)
|
| 87 |
+
* @param $updated_window_minutes (int)
|
| 88 |
+
* @return Mage_Sales_Model_Resource_Quote_Collection
|
| 89 |
+
*/
|
| 90 |
+
protected function _fetchRecentQuotes ($created_window_minutes, $updated_window_minutes) {
|
| 91 |
+
$adapter = Mage::getSingleton('core/resource')->getConnection('sales_read');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
+
$created_before = Zend_Date::now();
|
| 94 |
+
$created_before->sub($created_window_minutes, Zend_Date::MINUTE);
|
| 95 |
+
$created_before = $adapter->convertDateTime($created_before);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
$updated_after = Zend_Date::now();
|
| 98 |
+
$updated_after->sub($updated_window_minutes, Zend_Date::MINUTE);
|
| 99 |
+
$updated_after = $adapter->convertDateTime($updated_after);
|
|
|
|
| 100 |
|
| 101 |
+
return Mage::getResourceModel('sales/quote_collection')
|
| 102 |
+
->addFieldToFilter('converted_at', array('null' => true))
|
| 103 |
+
->addFieldToFilter('created_at', array('lteq' => $created_before))
|
| 104 |
+
->addFieldToFilter('updated_at', array('gteq' => $updated_after));
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
/**
|
| 108 |
+
* Send tracking information to Klaviyo for a quote if it has the necessary information.
|
| 109 |
+
*
|
| 110 |
+
* @param $quote (Mage_Sales_Model_Quote)
|
| 111 |
+
* @return bool
|
| 112 |
+
*/
|
| 113 |
+
protected function _maybeTrackQuote ($quote) {
|
| 114 |
+
if (!$this->_isEnabledForStore($quote->getStore())) {
|
| 115 |
+
return false;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
$tracker = $this->getTracker($quote);
|
| 119 |
+
$billing_address = $quote->getBillingAddress();
|
| 120 |
+
|
| 121 |
+
if (!$billing_address || !$tracker) {
|
| 122 |
+
return false;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
// Skip quotes that don't have an email or remote_ip set. Checking the IP is our best guess
|
| 126 |
+
// at not sending emails for quotes created on the Magento backend. There
|
| 127 |
+
// doesn't seem a reliable way to tell if a quote is from the frontend or backend.
|
| 128 |
+
if (!$quote->getCustomerEmail() || is_null($quote->getRemoteIp())) {
|
| 129 |
+
return false;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
$quote_id = $quote->getId();
|
| 133 |
+
|
| 134 |
+
$checkout = Mage::helper('klaviyo_reclaim')->getCheckout($quote_id);
|
| 135 |
+
|
| 136 |
+
$customer_properties = $this->_customerPropertiesForQuote($quote, $billing_address);
|
| 137 |
|
| 138 |
+
$item_details = array();
|
| 139 |
|
| 140 |
+
$configurable_product_ids = array();
|
| 141 |
|
| 142 |
+
foreach ($quote->getItemsCollection() as $quote_item) {
|
| 143 |
+
$response = $this->_quoteItemData($quote_item);
|
| 144 |
+
$quote_item_data = $response['quote_item'];
|
| 145 |
+
$is_product_configurable = $response['is_product_configurable'];
|
| 146 |
+
|
| 147 |
+
// Configurable product - The "product" when the product has multiple SKUs, e.g. "t-shirt"
|
| 148 |
+
// Simple product - The "SKU," e.g. "red, medium t-shirt." For products that don't have options or variations,
|
| 149 |
+
// this is the "product."
|
| 150 |
+
// http://docs.magento.com/m1/ce/user_guide/catalog/product-create.html
|
| 151 |
+
if ($is_product_configurable) {
|
| 152 |
+
$configurable_product_ids[] = $quote_item_data['product']['id'];
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
$item_details[] = $quote_item_data;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
$response = $this->_mergeConfigurableAndSimpleQuoteItems($item_details, $configurable_product_ids);
|
| 159 |
+
$item_details = $response['items'];
|
| 160 |
+
$item_count = $response['item_count'];
|
| 161 |
+
$item_descriptions = $response['item_descriptions'];
|
| 162 |
+
|
| 163 |
+
// Skip quotes that don't have items.
|
| 164 |
+
if (empty($item_details)) {
|
| 165 |
+
return false;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
$checkout_id = $checkout->getId();
|
| 169 |
+
$checkout_url = $quote->getStore()->getUrl('reclaim/index/view', array('_query' => array('id' => $checkout_id)));
|
| 170 |
+
|
| 171 |
+
$properties = array(
|
| 172 |
+
'$event_id' => $quote_id,
|
| 173 |
+
'$value' => (float) $quote->getGrandTotal(),
|
| 174 |
+
'Items' => $item_descriptions,
|
| 175 |
+
'Items Count' => $item_count,
|
| 176 |
+
'$extra' => array(
|
| 177 |
+
'checkout_url' => $checkout_url,
|
| 178 |
+
'checkout_id' => $checkout_id,
|
| 179 |
+
'line_items' => $item_details,
|
| 180 |
+
)
|
| 181 |
+
);
|
| 182 |
+
|
| 183 |
+
$coupon_code = $quote->getCouponCode();
|
| 184 |
+
if ($coupon_code) {
|
| 185 |
+
$totals = $quote->getTotals();
|
| 186 |
+
$properties['Discount Codes'] = array($coupon_code);
|
| 187 |
+
if (array_key_exists('discount', $totals) && is_object($totals['discount'])) {
|
| 188 |
+
$properties['Total Discounts'] = (float) $totals['discount']->getValue() * -1;
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
$timestamp = strtotime($quote->getUpdatedAt());
|
| 193 |
+
|
| 194 |
+
$tracker->track('Checkout Started', $customer_properties, $properties, $timestamp);
|
| 195 |
+
|
| 196 |
+
if (!$quote->getIsActive()) {
|
| 197 |
+
$tracker->track('Checkout Completed', $customer_properties, $properties, $timestamp);
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
return true;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
/**
|
| 204 |
+
* Normalize customer and billing address data to an array that's JSON serializable.
|
| 205 |
+
*
|
| 206 |
+
* @param $quote (Mage_Sales_Model_Quote)
|
| 207 |
+
* @param $billing_address (Mage_Sales_Model_Quote_Address)
|
| 208 |
+
* @return array
|
| 209 |
+
*/
|
| 210 |
+
protected function _customerPropertiesForQuote($quote, $billing_address) {
|
| 211 |
+
$properties = array(
|
| 212 |
+
'$email' => $quote->getCustomerEmail(),
|
| 213 |
+
'$first_name' => $quote->getCustomerFirstname(),
|
| 214 |
+
'$last_name' => $quote->getCustomerLastname(),
|
| 215 |
+
'Magento Store' => $quote->getStore()->getName(),
|
| 216 |
+
'Magento WebsiteID' => $quote->getStore()->getWebsiteId(),
|
| 217 |
+
'location' => array(
|
| 218 |
+
'source' => 'magento',
|
| 219 |
+
'address1' => $billing_address->getStreet(1),
|
| 220 |
+
'city' => $billing_address->getCity(),
|
| 221 |
+
'region' => $billing_address->getRegion(),
|
| 222 |
+
'zip' => $billing_address->getPostcode(),
|
| 223 |
+
'country' => $billing_address->getCountry()
|
| 224 |
+
)
|
| 225 |
+
);
|
| 226 |
+
|
| 227 |
+
if ($billing_address->getStreet(2)) {
|
| 228 |
+
$properties['location']['address2'] = $billing_address->getStreet(2);
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
return $properties;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/**
|
| 235 |
+
* Normalize quote item (line item) to an array that's JSON serializable.
|
| 236 |
+
*
|
| 237 |
+
* @param $quote_item (Mage_Sales_Model_Quote_Item)
|
| 238 |
+
* @return array
|
| 239 |
+
*/
|
| 240 |
+
protected function _quoteItemData ($quote_item) {
|
| 241 |
+
$data = array();
|
| 242 |
+
|
| 243 |
+
$quote_item_quantity = $quote_item->getQty();
|
| 244 |
+
$quote_item_product = Mage::getModel('catalog/product')->load($quote_item->getProduct()->getId());
|
| 245 |
+
|
| 246 |
+
$product_details = array(
|
| 247 |
+
'id' => $quote_item_product->getId(),
|
| 248 |
+
'sku' => $quote_item_product->getSKU(),
|
| 249 |
+
'name' => $quote_item_product->getName(),
|
| 250 |
+
'price' => (float) $quote_item_product->getPrice(),
|
| 251 |
+
'special_price' => (float) $quote_item_product->getFinalPrice()
|
| 252 |
+
);
|
| 253 |
+
|
| 254 |
+
$product_images = array();
|
| 255 |
+
// If we have a simple product for this configurable product, get the simple name
|
| 256 |
+
// and also attempt to get the images
|
| 257 |
+
if ($option = $quote_item->getOptionByCode('simple_product')) {
|
| 258 |
+
$simple_product = Mage::getModel('catalog/product')->load($option->getProduct()->getId());
|
| 259 |
+
|
| 260 |
+
$product_details['simple_name'] = $simple_product->getName();
|
| 261 |
+
foreach ($simple_product->getMediaGalleryImages() as $product_image) {
|
| 262 |
+
if (!$product_image->getDisabled()) {
|
| 263 |
+
$product_images[] = array(
|
| 264 |
+
'url' => $product_image->getUrl()
|
| 265 |
+
);
|
| 266 |
}
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
|
| 270 |
+
// if there is no simple product or we tried but couldn't find any
|
| 271 |
+
// images for the simple product just grab the configurable images
|
| 272 |
+
if (empty($product_images)) {
|
| 273 |
+
foreach ($quote_item_product->getMediaGalleryImages() as $product_image) {
|
| 274 |
+
if (!$product_image->getDisabled()) {
|
| 275 |
+
$product_images[] = array(
|
| 276 |
+
'url' => $product_image->getUrl()
|
| 277 |
+
);
|
| 278 |
+
}
|
| 279 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
}
|
| 281 |
+
$product_details['images'] = $product_images;
|
| 282 |
+
|
| 283 |
+
return array(
|
| 284 |
+
'is_product_configurable' => $quote_item_product->isConfigurable(),
|
| 285 |
+
'quote_item' => array(
|
| 286 |
+
'quantity' => (float) $quote_item_quantity,
|
| 287 |
+
'row_total' => (float) $quote_item->getBaseRowTotal(),
|
| 288 |
+
'row_discount' => (float) $quote_item->getBaseDiscountAmount(),
|
| 289 |
+
'product' => $product_details
|
| 290 |
+
)
|
| 291 |
+
);
|
| 292 |
+
}
|
| 293 |
|
| 294 |
+
/**
|
| 295 |
+
* Merged quote item (line item) details in cases where there are two line items to represent
|
| 296 |
+
* a configurable product and its options.
|
| 297 |
+
*
|
| 298 |
+
* @param $item_details (array)
|
| 299 |
+
* @param $configurable_product_ids (array)
|
| 300 |
+
* @return array
|
| 301 |
+
*/
|
| 302 |
+
protected function _mergeConfigurableAndSimpleQuoteItems($item_details, $configurable_product_ids) {
|
| 303 |
+
$model_configurable = Mage::getModel('catalog/product_type_configurable');
|
| 304 |
+
|
| 305 |
+
$merged = array();
|
| 306 |
+
$item_descriptions = array();
|
| 307 |
+
$item_count = 0;
|
| 308 |
+
|
| 309 |
+
// Look for simple products that are really represented by a configurable product.
|
| 310 |
+
foreach ($item_details as $line_index => $line) {
|
| 311 |
+
$product_id = $line['product']['id'];
|
| 312 |
+
if (in_array($product_id, $configurable_product_ids) || $line['row_total']) {
|
| 313 |
+
$merged[] = $line;
|
| 314 |
+
} else {
|
| 315 |
+
$configurable_parent_ids = $model_configurable->getParentIdsByChild($product_id);
|
| 316 |
+
$common_ids = array_intersect($configurable_product_ids, $configurable_parent_ids);
|
| 317 |
+
|
| 318 |
+
// If it's a simple product placeholder for a configurable product, discard it. Otherwise, keep it.
|
| 319 |
+
if (empty($common_ids)) {
|
| 320 |
+
$merged[] = $line;
|
| 321 |
+
} else {
|
| 322 |
+
continue;
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
if (array_key_exists('simple_name', $line['product'])) {
|
| 327 |
+
$item_descriptions[] = $line['product']['simple_name'];
|
| 328 |
+
} else {
|
| 329 |
+
$item_descriptions[] = $line['product']['name'];
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
$item_count += $line['quantity'];
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
return array(
|
| 336 |
+
'items' => $merged,
|
| 337 |
+
'item_count' => $item_count,
|
| 338 |
+
'item_descriptions' => $item_descriptions
|
| 339 |
+
);
|
| 340 |
}
|
| 341 |
|
| 342 |
public function syncSubscriber (Varien_Event_Observer $observer) {
|
|
|
|
|
|
|
| 343 |
if (!Mage::helper('klaviyo_reclaim')->isEnabled()) {
|
| 344 |
return $observer;
|
| 345 |
}
|
| 374 |
$subscriber_status = $subscriber->getStatus();
|
| 375 |
|
| 376 |
if ($subscriber_status == Mage_Newsletter_Model_Subscriber::STATUS_UNCONFIRMED ||
|
| 377 |
+
$subscriber_status == Mage_Newsletter_Model_Subscriber::STATUS_SUBSCRIBED ||
|
| 378 |
+
$subscriber_status == Mage_Newsletter_Model_Subscriber::STATUS_NOT_ACTIVE) {
|
| 379 |
|
| 380 |
$response = Mage::getSingleton('klaviyo_reclaim/api')->listSubscriberAdd($list_id, $email, $is_requiring_confirmation);
|
| 381 |
|
| 423 |
Mage::getSingleton('klaviyo_reclaim/api')->listSubscriberDelete($list_id, $email);
|
| 424 |
}
|
| 425 |
|
| 426 |
+
protected function _isEnabledForStore($store) {
|
| 427 |
+
$store_id = $store->getId();
|
| 428 |
+
|
| 429 |
+
if (!isset($this->is_enabled_cache[$store_id])) {
|
| 430 |
+
$this->is_enabled_cache[$store_id] = Mage::helper('klaviyo_reclaim')->isEnabled($store);
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
return $this->is_enabled_cache[$store_id];
|
| 434 |
}
|
| 435 |
|
| 436 |
public function getTracker($quote) {
|
app/code/community/Klaviyo/Reclaim/controllers/IndexController.php
CHANGED
|
@@ -47,7 +47,7 @@ class Klaviyo_Reclaim_IndexController extends Mage_Core_Controller_Front_Action
|
|
| 47 |
}
|
| 48 |
}
|
| 49 |
}
|
| 50 |
-
|
| 51 |
$params = array();
|
| 52 |
foreach (self::$_preservableRequestParams as $key) {
|
| 53 |
$value = $this->getRequest()->getParam($key);
|
|
@@ -91,7 +91,7 @@ class Klaviyo_Reclaim_IndexController extends Mage_Core_Controller_Front_Action
|
|
| 91 |
}
|
| 92 |
|
| 93 |
/**
|
| 94 |
-
*
|
| 95 |
*/
|
| 96 |
public function statusAction()
|
| 97 |
{
|
|
@@ -102,28 +102,140 @@ class Klaviyo_Reclaim_IndexController extends Mage_Core_Controller_Front_Action
|
|
| 102 |
} else {
|
| 103 |
$helper = Mage::helper('klaviyo_reclaim');
|
| 104 |
|
| 105 |
-
$
|
| 106 |
-
$is_api_key_set = $helper->getPublicApiKey() != NULL;
|
| 107 |
|
| 108 |
-
$
|
| 109 |
-
$hour_ago = Zend_Date::now();
|
| 110 |
-
$hour_ago->sub(60, Zend_Date::MINUTE);
|
| 111 |
-
$hour_ago = $adapter->convertDateTime($hour_ago);
|
| 112 |
|
| 113 |
-
$
|
| 114 |
-
|
| 115 |
-
->addFieldToFilter('finished_at', array('gteq' => $hour_ago))
|
| 116 |
-
->count() > 0;
|
| 117 |
|
| 118 |
-
$
|
|
|
|
| 119 |
|
| 120 |
$response = array(
|
| 121 |
-
'data' => array(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
);
|
| 123 |
}
|
| 124 |
|
| 125 |
$this->getResponse()->setHeader('Content-type', 'application/json');
|
| 126 |
$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));
|
| 127 |
-
return;
|
| 128 |
}
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
}
|
| 48 |
}
|
| 49 |
}
|
| 50 |
+
|
| 51 |
$params = array();
|
| 52 |
foreach (self::$_preservableRequestParams as $key) {
|
| 53 |
$value = $this->getRequest()->getParam($key);
|
| 91 |
}
|
| 92 |
|
| 93 |
/**
|
| 94 |
+
* Klaviyo extension status action
|
| 95 |
*/
|
| 96 |
public function statusAction()
|
| 97 |
{
|
| 102 |
} else {
|
| 103 |
$helper = Mage::helper('klaviyo_reclaim');
|
| 104 |
|
| 105 |
+
$version = (string) Mage::getConfig()->getNode('modules/Klaviyo_Reclaim/version');
|
|
|
|
| 106 |
|
| 107 |
+
$config_details = $this->_getExtensionConfigDetails();
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
+
$since_minutes = 60;
|
| 110 |
+
$cron_details = $this->_getCronScheduleDetails($since_minutes);
|
|
|
|
|
|
|
| 111 |
|
| 112 |
+
$num_quotes = 5;
|
| 113 |
+
$quote_details = $this->_getQuoteDetails($num_quotes);
|
| 114 |
|
| 115 |
$response = array(
|
| 116 |
+
'data' => array(
|
| 117 |
+
'version' => $version,
|
| 118 |
+
'config' => $config_details,
|
| 119 |
+
'cron' => $cron_details,
|
| 120 |
+
'quotes' => $quote_details
|
| 121 |
+
)
|
| 122 |
);
|
| 123 |
}
|
| 124 |
|
| 125 |
$this->getResponse()->setHeader('Content-type', 'application/json');
|
| 126 |
$this->getResponse()->setBody(Mage::helper('core')->jsonEncode($response));
|
|
|
|
| 127 |
}
|
| 128 |
+
|
| 129 |
+
protected function _getExtensionConfigDetails () {
|
| 130 |
+
$helper = Mage::helper('klaviyo_reclaim');
|
| 131 |
+
|
| 132 |
+
return array(
|
| 133 |
+
'global' => array(
|
| 134 |
+
'enabled' => $helper->isEnabled(),
|
| 135 |
+
'api_key' => $helper->getPublicApiKey() != NULL
|
| 136 |
+
)
|
| 137 |
+
);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
protected function _getCronScheduleDetails ($since_minutes) {
|
| 141 |
+
$adapter = Mage::getSingleton('core/resource')->getConnection('sales_read');
|
| 142 |
+
$since = Zend_Date::now();
|
| 143 |
+
$since->sub($since_minutes, Zend_Date::MINUTE);
|
| 144 |
+
$since = $adapter->convertDateTime($since);
|
| 145 |
+
|
| 146 |
+
$query = $this->_getKlaviyoCronScheduleBaseQuery()
|
| 147 |
+
->addFieldToFilter('status', Mage_Cron_Model_Schedule::STATUS_SUCCESS)
|
| 148 |
+
->addFieldToFilter('finished_at', array('gteq' => $since))
|
| 149 |
+
->addOrder('finished_at', $direction='desc');
|
| 150 |
+
|
| 151 |
+
$has_suceeded = $query->count() > 0;
|
| 152 |
+
$last_success = array();
|
| 153 |
+
|
| 154 |
+
if ($has_suceeded) {
|
| 155 |
+
$job = $query->getFirstItem();
|
| 156 |
+
|
| 157 |
+
$last_success = array(
|
| 158 |
+
'id' => $job->getScheduleId(),
|
| 159 |
+
'finished_at' => Mage::getSingleton('core/date')->gmtDate($job->getFinishedAt()),
|
| 160 |
+
'messages' => $job->getMessages()
|
| 161 |
+
);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
$query = $this->_getKlaviyoCronScheduleBaseQuery()
|
| 165 |
+
->addFieldToFilter('status', array('in' => array(
|
| 166 |
+
Mage_Cron_Model_Schedule::STATUS_MISSED,
|
| 167 |
+
Mage_Cron_Model_Schedule::STATUS_ERROR
|
| 168 |
+
)))
|
| 169 |
+
->addFieldToFilter('created_at', array('gteq' => $since))
|
| 170 |
+
->addOrder('finished_at', $direction='desc');
|
| 171 |
+
|
| 172 |
+
$has_failed = $query->count() > 0;
|
| 173 |
+
$failures = array();
|
| 174 |
+
|
| 175 |
+
if ($has_failed) {
|
| 176 |
+
foreach ($query as $job) {
|
| 177 |
+
$failures[] = array(
|
| 178 |
+
'id' => $job->getScheduleId(),
|
| 179 |
+
'status' => $job->getStatus(),
|
| 180 |
+
'messages' => $job->getMessage()
|
| 181 |
+
);
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
return array(
|
| 186 |
+
'last_success' => $last_success,
|
| 187 |
+
'failures' => $failures
|
| 188 |
+
);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
protected function _getKlaviyoCronScheduleBaseQuery () {
|
| 192 |
+
return Mage::getModel('cron/schedule')->getCollection()
|
| 193 |
+
->addFieldToFilter('job_code', 'klaviyo_track_quotes');
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
protected function _getQuoteDetails ($num_quotes) {
|
| 197 |
+
$helper = Mage::helper('klaviyo_reclaim');
|
| 198 |
+
|
| 199 |
+
$has_checkout_ids = Mage::getModel('klaviyo_reclaim/checkout')->getCollection()->count() > 0;
|
| 200 |
+
|
| 201 |
+
$query = Mage::getResourceModel('sales/quote_collection')
|
| 202 |
+
->addFieldToFilter('converted_at', array('null' => true))
|
| 203 |
+
->addOrder('updated_at', $direction='desc')
|
| 204 |
+
->setPageSize($num_quotes)
|
| 205 |
+
->setCurPage(1);
|
| 206 |
+
|
| 207 |
+
$quotes = array();
|
| 208 |
+
foreach ($query as $quote) {
|
| 209 |
+
$store = $quote->getStore();
|
| 210 |
+
$email = $quote->getCustomerEmail();
|
| 211 |
+
|
| 212 |
+
if ($email) {
|
| 213 |
+
$pieces = explode('@', $email, 2);
|
| 214 |
+
|
| 215 |
+
// Obfuscates the email address from `someone@example.com` to `so******@example.com`.
|
| 216 |
+
$email = substr($pieces[0], 0, 2) . str_repeat('*', 6) . '@' . $pieces[1];
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
$quotes[] = array(
|
| 220 |
+
'id' => $quote->getEntityId(),
|
| 221 |
+
'store_id' => $quote->getStoreId(),
|
| 222 |
+
'gmt_created_at' => Mage::getSingleton('core/date')->gmtDate($quote->getCreatedAt()),
|
| 223 |
+
'gmt_updated_at' => Mage::getSingleton('core/date')->gmtDate($quote->getUpdatedAt()),
|
| 224 |
+
'customer_email' => $email,
|
| 225 |
+
'remote_ip' => $quote->getRemoteIp(),
|
| 226 |
+
'num_items' => count($quote->getItemsCollection()),
|
| 227 |
+
'is_active' => $quote->getIsActive(),
|
| 228 |
+
|
| 229 |
+
'config' => array(
|
| 230 |
+
'is_enabled' => $helper->isEnabled($store),
|
| 231 |
+
'api_key' => $helper->getPublicApiKey($store) != NULL
|
| 232 |
+
)
|
| 233 |
+
);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
return array(
|
| 237 |
+
'has_checkout_ids' => $has_checkout_ids,
|
| 238 |
+
'quotes' => $quotes
|
| 239 |
+
);
|
| 240 |
+
}
|
| 241 |
+
}
|
app/code/community/Klaviyo/Reclaim/controllers/KlaviyoController.php
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?php
|
| 2 |
+
class Klaviyo_Reclaim_KlaviyoController extends Mage_Adminhtml_Controller_Action
|
| 3 |
+
{
|
| 4 |
+
const OAUTH_CALLBACK_URL = 'https://www.klaviyo.com/integrations/auth/magento';
|
| 5 |
+
|
| 6 |
+
const OAUTH_CONSUMER_NAME = 'Klaviyo REST';
|
| 7 |
+
|
| 8 |
+
/**
|
| 9 |
+
* Generate OAuth keys, store them into the Klaviyo configuration, and display them.
|
| 10 |
+
*/
|
| 11 |
+
public function oauthkeysAction()
|
| 12 |
+
{
|
| 13 |
+
$oauthHelper = Mage::helper('oauth');
|
| 14 |
+
|
| 15 |
+
$consumerKey = $oauthHelper->generateConsumerKey();
|
| 16 |
+
$consumerSecret = $oauthHelper->generateConsumerSecret();
|
| 17 |
+
|
| 18 |
+
$consumerModel = Mage::getModel('oauth/consumer');
|
| 19 |
+
|
| 20 |
+
$consumerData = array(
|
| 21 |
+
'name' => self::OAUTH_CONSUMER_NAME,
|
| 22 |
+
'key' => $consumerKey,
|
| 23 |
+
'secret' => $consumerSecret);
|
| 24 |
+
|
| 25 |
+
$consumerModel->addData($consumerData);
|
| 26 |
+
$consumerModel->save();
|
| 27 |
+
|
| 28 |
+
$token = Mage::getModel('oauth/token');
|
| 29 |
+
|
| 30 |
+
$token->createRequestToken($consumerModel->getId(), self::OAUTH_CALLBACK_URL);
|
| 31 |
+
$token->convertToAccess();
|
| 32 |
+
|
| 33 |
+
$requestToken = $token->getToken();
|
| 34 |
+
$requestTokenSecret = $token->getSecret();
|
| 35 |
+
|
| 36 |
+
$user = Mage::getSingleton('admin/session')->getUser();
|
| 37 |
+
$token->authorize($user->getId(), Mage_Oauth_Model_Token::USER_TYPE_ADMIN);
|
| 38 |
+
|
| 39 |
+
$reclaimHelper = Mage::helper('klaviyo_reclaim');
|
| 40 |
+
$reclaimHelper->setConsumerKey($consumerKey);
|
| 41 |
+
$reclaimHelper->setConsumerSecret($consumerSecret);
|
| 42 |
+
$reclaimHelper->setAuthorizationToken($requestToken);
|
| 43 |
+
$reclaimHelper->setAuthorizationSecret($requestTokenSecret);
|
| 44 |
+
|
| 45 |
+
// redirect to the extension configuration page
|
| 46 |
+
$this->_redirectUrl(Mage::helper('adminhtml')->getUrl('adminhtml/system_config/edit/section/reclaim'));
|
| 47 |
+
return $this;
|
| 48 |
+
}
|
| 49 |
+
}
|
app/code/community/Klaviyo/Reclaim/etc/adminhtml.xml
CHANGED
|
@@ -1,23 +1,23 @@
|
|
| 1 |
<?xml version="1.0"?>
|
| 2 |
<config>
|
| 3 |
-
<acl>
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
</acl>
|
| 23 |
-
</config>
|
| 1 |
<?xml version="1.0"?>
|
| 2 |
<config>
|
| 3 |
+
<acl>
|
| 4 |
+
<resources>
|
| 5 |
+
<admin>
|
| 6 |
+
<children>
|
| 7 |
+
<system>
|
| 8 |
+
<children>
|
| 9 |
+
<config>
|
| 10 |
+
<children>
|
| 11 |
+
<reclaim translate="title">
|
| 12 |
+
<title>Klaviyo</title>
|
| 13 |
+
<sort_order>200</sort_order>
|
| 14 |
+
</reclaim>
|
| 15 |
+
</children>
|
| 16 |
+
</config>
|
| 17 |
+
</children>
|
| 18 |
+
</system>
|
| 19 |
+
</children>
|
| 20 |
+
</admin>
|
| 21 |
+
</resources>
|
| 22 |
+
</acl>
|
| 23 |
+
</config>
|
app/code/community/Klaviyo/Reclaim/etc/api2.xml
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<config>
|
| 2 |
+
<api2>
|
| 3 |
+
<resource_groups>
|
| 4 |
+
<reclaim translate="title" module="Klaviyo_Reclaim">
|
| 5 |
+
<title>Coupon API</title>
|
| 6 |
+
<sort_order>10</sort_order>
|
| 7 |
+
</reclaim>
|
| 8 |
+
</resource_groups>
|
| 9 |
+
<resources>
|
| 10 |
+
<reclaim translate="title" module="Klaviyo_Reclaim">
|
| 11 |
+
<group>reclaim</group>
|
| 12 |
+
<model>reclaim/api2_coupon</model>
|
| 13 |
+
<title>Coupon Code Auto Generation</title>
|
| 14 |
+
<sort_order>10</sort_order>
|
| 15 |
+
<privileges>
|
| 16 |
+
<admin>
|
| 17 |
+
<create>1</create>
|
| 18 |
+
<retrieve>1</retrieve>
|
| 19 |
+
<delete>1</delete>
|
| 20 |
+
</admin>
|
| 21 |
+
</privileges>
|
| 22 |
+
<attributes>
|
| 23 |
+
<coupon_id>Coupon ID</coupon_id>
|
| 24 |
+
<code>Code</code>
|
| 25 |
+
<qty>Quantity</qty>
|
| 26 |
+
<length>Length</length>
|
| 27 |
+
<format>Format</format>
|
| 28 |
+
</attributes>
|
| 29 |
+
<routes>
|
| 30 |
+
<route_collection>
|
| 31 |
+
<route>/klaviyo/rules/:rule_id/codes</route>
|
| 32 |
+
<action_type>collection</action_type>
|
| 33 |
+
</route_collection>
|
| 34 |
+
<route_entity>
|
| 35 |
+
<route>/klaviyo/rules/:rule_id/codes/:coupon_id</route>
|
| 36 |
+
<action_type>entity</action_type>
|
| 37 |
+
</route_entity>
|
| 38 |
+
</routes>
|
| 39 |
+
<versions>1</versions>
|
| 40 |
+
</reclaim>
|
| 41 |
+
<rules translate="title" module="Klaviyo_Reclaim">
|
| 42 |
+
<group>reclaim</group>
|
| 43 |
+
<model>reclaim/api2_rule</model>
|
| 44 |
+
<title>Fetch Rule Information</title>
|
| 45 |
+
<sort_order>20</sort_order>
|
| 46 |
+
<privileges>
|
| 47 |
+
<admin>
|
| 48 |
+
<retrieve>1</retrieve>
|
| 49 |
+
</admin>
|
| 50 |
+
</privileges>
|
| 51 |
+
<attributes>
|
| 52 |
+
<rule_id>Rule Id</rule_id>
|
| 53 |
+
<name>Rule Name</name>
|
| 54 |
+
<description>Description</description>
|
| 55 |
+
<from_date>From Date</from_date>
|
| 56 |
+
<to_date>To Date</to_date>
|
| 57 |
+
<uses_per_coupon>Uses Per Coupon</uses_per_coupon>
|
| 58 |
+
<uses_per_customer>Uses Per Customer</uses_per_customer>
|
| 59 |
+
<simple_action>Apply</simple_action>
|
| 60 |
+
<discount_amount>Discount Amount</discount_amount>
|
| 61 |
+
</attributes>
|
| 62 |
+
<routes>
|
| 63 |
+
<route_collection>
|
| 64 |
+
<route>/klaviyo/rules</route>
|
| 65 |
+
<action_type>collection</action_type>
|
| 66 |
+
</route_collection>
|
| 67 |
+
</routes>
|
| 68 |
+
<versions>1</versions>
|
| 69 |
+
</rules>
|
| 70 |
+
</resources>
|
| 71 |
+
</api2>
|
| 72 |
+
</config>
|
app/code/community/Klaviyo/Reclaim/etc/config.xml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
<config>
|
| 3 |
<modules>
|
| 4 |
<Klaviyo_Reclaim>
|
| 5 |
-
<version>1.
|
| 6 |
</Klaviyo_Reclaim>
|
| 7 |
</modules>
|
| 8 |
<global>
|
|
@@ -25,15 +25,6 @@
|
|
| 25 |
</klaviyo_subscribe_delete_observer>
|
| 26 |
</observers>
|
| 27 |
</newsletter_subscriber_delete_after>
|
| 28 |
-
<customer_save_after>
|
| 29 |
-
<observers>
|
| 30 |
-
<klaviyo_update_customer>
|
| 31 |
-
<type>singleton</type>
|
| 32 |
-
<class>Klaviyo_Reclaim_Model_Observer</class>
|
| 33 |
-
<method>syncCustomer</method>
|
| 34 |
-
</klaviyo_update_customer>
|
| 35 |
-
</observers>
|
| 36 |
-
</customer_save_after>
|
| 37 |
</events>
|
| 38 |
<models>
|
| 39 |
<klaviyo_reclaim>
|
|
@@ -48,6 +39,9 @@
|
|
| 48 |
</checkout>
|
| 49 |
</entities>
|
| 50 |
</reclaim_mysql4>
|
|
|
|
|
|
|
|
|
|
| 51 |
</models>
|
| 52 |
<helpers>
|
| 53 |
<klaviyo_reclaim>
|
|
@@ -68,7 +62,17 @@
|
|
| 68 |
</klaviyo_reclaim_setup>
|
| 69 |
</resources>
|
| 70 |
</global>
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
<adminhtml>
|
| 73 |
<layout>
|
| 74 |
<updates>
|
|
@@ -78,7 +82,6 @@
|
|
| 78 |
</updates>
|
| 79 |
</layout>
|
| 80 |
</adminhtml>
|
| 81 |
-
|
| 82 |
<frontend>
|
| 83 |
<routers>
|
| 84 |
<klaviyo_reclaim>
|
| 2 |
<config>
|
| 3 |
<modules>
|
| 4 |
<Klaviyo_Reclaim>
|
| 5 |
+
<version>1.6.2</version>
|
| 6 |
</Klaviyo_Reclaim>
|
| 7 |
</modules>
|
| 8 |
<global>
|
| 25 |
</klaviyo_subscribe_delete_observer>
|
| 26 |
</observers>
|
| 27 |
</newsletter_subscriber_delete_after>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
</events>
|
| 29 |
<models>
|
| 30 |
<klaviyo_reclaim>
|
| 39 |
</checkout>
|
| 40 |
</entities>
|
| 41 |
</reclaim_mysql4>
|
| 42 |
+
<reclaim>
|
| 43 |
+
<class>Klaviyo_Reclaim_Model</class>
|
| 44 |
+
</reclaim>
|
| 45 |
</models>
|
| 46 |
<helpers>
|
| 47 |
<klaviyo_reclaim>
|
| 62 |
</klaviyo_reclaim_setup>
|
| 63 |
</resources>
|
| 64 |
</global>
|
| 65 |
+
<admin>
|
| 66 |
+
<routers>
|
| 67 |
+
<adminhtml>
|
| 68 |
+
<args>
|
| 69 |
+
<modules>
|
| 70 |
+
<Klaviyo_Reclaim after="Mage_Adminhtml">Klaviyo_Reclaim</Klaviyo_Reclaim>
|
| 71 |
+
</modules>
|
| 72 |
+
</args>
|
| 73 |
+
</adminhtml>
|
| 74 |
+
</routers>
|
| 75 |
+
</admin>
|
| 76 |
<adminhtml>
|
| 77 |
<layout>
|
| 78 |
<updates>
|
| 82 |
</updates>
|
| 83 |
</layout>
|
| 84 |
</adminhtml>
|
|
|
|
| 85 |
<frontend>
|
| 86 |
<routers>
|
| 87 |
<klaviyo_reclaim>
|
app/code/community/Klaviyo/Reclaim/etc/system.xml
CHANGED
|
@@ -26,7 +26,6 @@
|
|
| 26 |
<show_in_default>1</show_in_default>
|
| 27 |
<show_in_website>1</show_in_website>
|
| 28 |
<show_in_store>1</show_in_store>
|
| 29 |
-
|
| 30 |
<fields>
|
| 31 |
<enabled translate="label">
|
| 32 |
<label>Enabled</label>
|
|
@@ -45,7 +44,7 @@
|
|
| 45 |
<show_in_default>1</show_in_default>
|
| 46 |
<show_in_website>1</show_in_website>
|
| 47 |
<show_in_store>1</show_in_store>
|
| 48 |
-
<comment><![CDATA[You can get your <strong>public</strong> API key from <a href="https://www.klaviyo.com/
|
| 49 |
</public_api_key>
|
| 50 |
<private_api_key translate="label">
|
| 51 |
<label>Klaviyo Private API Key</label>
|
|
@@ -54,7 +53,7 @@
|
|
| 54 |
<show_in_default>1</show_in_default>
|
| 55 |
<show_in_website>1</show_in_website>
|
| 56 |
<show_in_store>1</show_in_store>
|
| 57 |
-
<comment><![CDATA[You can get your <strong>private</strong> API key from <a href="https://www.klaviyo.com/account#api-keys" target="_blank">https://www.klaviyo.com/account#api-keys</a>.]]></comment>
|
| 58 |
</private_api_key>
|
| 59 |
<subscription_list translate="label comment">
|
| 60 |
<label>General Subscription List</label>
|
|
@@ -76,9 +75,62 @@
|
|
| 76 |
<show_in_store>1</show_in_store>
|
| 77 |
<comment><![CDATA[Normally, this should be <strong>Yes</strong>. Abuse of the double opt-in option may cause your account to be suspended.]]></comment>
|
| 78 |
</require_double_optin>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
</fields>
|
| 80 |
</general>
|
| 81 |
</groups>
|
| 82 |
</reclaim>
|
| 83 |
</sections>
|
| 84 |
-
</config>
|
| 26 |
<show_in_default>1</show_in_default>
|
| 27 |
<show_in_website>1</show_in_website>
|
| 28 |
<show_in_store>1</show_in_store>
|
|
|
|
| 29 |
<fields>
|
| 30 |
<enabled translate="label">
|
| 31 |
<label>Enabled</label>
|
| 44 |
<show_in_default>1</show_in_default>
|
| 45 |
<show_in_website>1</show_in_website>
|
| 46 |
<show_in_store>1</show_in_store>
|
| 47 |
+
<comment><![CDATA[You can get your <strong>public</strong> API key from <a href="https://www.klaviyo.com/account#api-keys-tab" target="_blank">https://www.klaviyo.com/account#api-keys-tab</a>.]]></comment>
|
| 48 |
</public_api_key>
|
| 49 |
<private_api_key translate="label">
|
| 50 |
<label>Klaviyo Private API Key</label>
|
| 53 |
<show_in_default>1</show_in_default>
|
| 54 |
<show_in_website>1</show_in_website>
|
| 55 |
<show_in_store>1</show_in_store>
|
| 56 |
+
<comment><![CDATA[You can get your <strong>private</strong> API key from <a href="https://www.klaviyo.com/account#api-keys-tab" target="_blank">https://www.klaviyo.com/account#api-keys-tab</a>.]]></comment>
|
| 57 |
</private_api_key>
|
| 58 |
<subscription_list translate="label comment">
|
| 59 |
<label>General Subscription List</label>
|
| 75 |
<show_in_store>1</show_in_store>
|
| 76 |
<comment><![CDATA[Normally, this should be <strong>Yes</strong>. Abuse of the double opt-in option may cause your account to be suspended.]]></comment>
|
| 77 |
</require_double_optin>
|
| 78 |
+
<consumer_key translate="label">
|
| 79 |
+
<label>Consumer Key</label>
|
| 80 |
+
<frontend_model>klaviyo_reclaim/oauth_credential_renderer</frontend_model>
|
| 81 |
+
<frontend_type>text</frontend_type>
|
| 82 |
+
<disabled>true</disabled>
|
| 83 |
+
<sort_order>60</sort_order>
|
| 84 |
+
<show_in_default>1</show_in_default>
|
| 85 |
+
<show_in_website>1</show_in_website>
|
| 86 |
+
<show_in_store>1</show_in_store>
|
| 87 |
+
<can_be_empty>1</can_be_empty>
|
| 88 |
+
</consumer_key>
|
| 89 |
+
<consumer_secret translate="label">
|
| 90 |
+
<label>Consumer Secret</label>
|
| 91 |
+
<frontend_model>klaviyo_reclaim/oauth_credential_renderer</frontend_model>
|
| 92 |
+
<frontend_type>text</frontend_type>
|
| 93 |
+
<disabled>true</disabled>
|
| 94 |
+
<sort_order>70</sort_order>
|
| 95 |
+
<show_in_default>1</show_in_default>
|
| 96 |
+
<show_in_website>1</show_in_website>
|
| 97 |
+
<show_in_store>1</show_in_store>
|
| 98 |
+
<can_be_empty>1</can_be_empty>
|
| 99 |
+
</consumer_secret>
|
| 100 |
+
<authorization_token translate="label">
|
| 101 |
+
<label>Authorization Token</label>
|
| 102 |
+
<frontend_model>klaviyo_reclaim/oauth_credential_renderer</frontend_model>
|
| 103 |
+
<frontend_type>text</frontend_type>
|
| 104 |
+
<disabled>true</disabled>
|
| 105 |
+
<sort_order>80</sort_order>
|
| 106 |
+
<show_in_default>1</show_in_default>
|
| 107 |
+
<show_in_website>1</show_in_website>
|
| 108 |
+
<show_in_store>1</show_in_store>
|
| 109 |
+
<can_be_empty>1</can_be_empty>
|
| 110 |
+
</authorization_token>
|
| 111 |
+
<authorization_secret translate="label">
|
| 112 |
+
<label>Authorization Secret</label>
|
| 113 |
+
<frontend_model>klaviyo_reclaim/oauth_credential_renderer</frontend_model>
|
| 114 |
+
<frontend_type>text</frontend_type>
|
| 115 |
+
<disabled>true</disabled>
|
| 116 |
+
<sort_order>90</sort_order>
|
| 117 |
+
<show_in_default>1</show_in_default>
|
| 118 |
+
<show_in_website>1</show_in_website>
|
| 119 |
+
<show_in_store>1</show_in_store>
|
| 120 |
+
<can_be_empty>1</can_be_empty>
|
| 121 |
+
</authorization_secret>
|
| 122 |
+
<generate_tokens_button translate="label">
|
| 123 |
+
<label></label>
|
| 124 |
+
<frontend_type>button</frontend_type>
|
| 125 |
+
<frontend_model>klaviyo_reclaim/adminhtml_system_config_form_button</frontend_model>
|
| 126 |
+
<sort_order>100</sort_order>
|
| 127 |
+
<show_in_default>1</show_in_default>
|
| 128 |
+
<show_in_website>1</show_in_website>
|
| 129 |
+
<show_in_store>1</show_in_store>
|
| 130 |
+
</generate_tokens_button>
|
| 131 |
</fields>
|
| 132 |
</general>
|
| 133 |
</groups>
|
| 134 |
</reclaim>
|
| 135 |
</sections>
|
| 136 |
+
</config>
|
app/design/frontend/base/default/layout/klaviyoreclaim.xml
CHANGED
|
@@ -40,4 +40,9 @@
|
|
| 40 |
<block type="klaviyo_reclaim/checkout_monitor" name="klaviyo_reclaim.checkout_monitor" template="klaviyoreclaim/checkout/monitor.phtml" />
|
| 41 |
</reference>
|
| 42 |
</fancycheckout_index_index>
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
<block type="klaviyo_reclaim/checkout_monitor" name="klaviyo_reclaim.checkout_monitor" template="klaviyoreclaim/checkout/monitor.phtml" />
|
| 41 |
</reference>
|
| 42 |
</fancycheckout_index_index>
|
| 43 |
+
<anattadesign_awesomecheckout_onepage_index>
|
| 44 |
+
<reference name="before_body_end">
|
| 45 |
+
<block type="klaviyo_reclaim/checkout_monitor" name="klaviyo_reclaim.checkout_monitor" template="klaviyoreclaim/checkout/monitor.phtml" />
|
| 46 |
+
</reference>
|
| 47 |
+
</anattadesign_awesomecheckout_onepage_index>
|
| 48 |
+
</layout>
|
app/design/frontend/base/default/template/klaviyoreclaim/tracking/product.phtml
CHANGED
|
@@ -5,19 +5,33 @@ $_product_image_url = NULL;
|
|
| 5 |
// Check to see if we have an image for this product.
|
| 6 |
foreach ($_product->getMediaGalleryImages() as $_product_image) {
|
| 7 |
if (!$_product_image->getDisabled()) {
|
| 8 |
-
|
| 9 |
-
|
| 10 |
}
|
| 11 |
}
|
| 12 |
?>
|
| 13 |
<script text="text/javascript">
|
| 14 |
var _learnq = _learnq || [];
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
| 18 |
SKU: "<?php echo $_product->getSku(); ?>",
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
Categories: <?php echo $this->getProductCategoriesAsJson(); ?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
}]);
|
| 23 |
</script>
|
| 5 |
// Check to see if we have an image for this product.
|
| 6 |
foreach ($_product->getMediaGalleryImages() as $_product_image) {
|
| 7 |
if (!$_product_image->getDisabled()) {
|
| 8 |
+
$_product_image_url = $_product_image->getUrl();
|
| 9 |
+
break;
|
| 10 |
}
|
| 11 |
}
|
| 12 |
?>
|
| 13 |
<script text="text/javascript">
|
| 14 |
var _learnq = _learnq || [];
|
| 15 |
+
|
| 16 |
+
var item = {
|
| 17 |
+
ProductID: "<?php echo $_product->getId(); ?>",
|
| 18 |
+
Name: <?php echo json_encode($_product->getName()); ?>,
|
| 19 |
SKU: "<?php echo $_product->getSku(); ?>",
|
| 20 |
+
Price: "<?php echo number_format($_product->getPrice(), 2); ?>",
|
| 21 |
+
SpecialPrice: "<?php echo number_format($_product->getFinalPrice(), 2); ?>",
|
| 22 |
+
URL: "<?php echo $_product->getProductUrl(); ?>",
|
| 23 |
+
<?php if ($_product_image_url) { ?>ImageURL: "<?php echo $_product_image_url; ?>", <?php } ?>
|
| 24 |
Categories: <?php echo $this->getProductCategoriesAsJson(); ?>
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
_learnq.push(['track', 'Viewed Product', item]);
|
| 28 |
+
_learnq.push(['trackViewedItem', {
|
| 29 |
+
Title: item.Name,
|
| 30 |
+
ItemId: item.ProductID,
|
| 31 |
+
Price: item.Price,
|
| 32 |
+
SpecialPrice: item.SpecialPrice,
|
| 33 |
+
Categories: item.Categories,
|
| 34 |
+
<?php if ($_product_image_url) { ?>ImageUrl: item.ImageURL,<?php } ?>
|
| 35 |
+
Url: item.URL
|
| 36 |
}]);
|
| 37 |
</script>
|
package.xml
CHANGED
|
@@ -1,23 +1,27 @@
|
|
| 1 |
<?xml version="1.0"?>
|
| 2 |
<package>
|
| 3 |
<name>Klaviyo_Reclaim</name>
|
| 4 |
-
<version>1.
|
| 5 |
<stability>stable</stability>
|
| 6 |
<license>Open Software License (OSL)</license>
|
| 7 |
<channel>community</channel>
|
| 8 |
<extends/>
|
| 9 |
-
<summary>
|
| 10 |
<description>With this extension, you can:
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
<authors><author><name>Klaviyo Team</name><user>klaviyo</user><email>support@klaviyo.com</email></author></authors>
|
| 18 |
-
<date>
|
| 19 |
-
<time>
|
| 20 |
-
<contents><target name="magecommunity"><dir name="Klaviyo"><dir name="Reclaim"><dir name="Block"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Fieldset"><file name="Info.php" hash="
|
| 21 |
<compatible/>
|
| 22 |
<dependencies><required><php><min>5.1.0</min><max>6.0.0</max></php></required></dependencies>
|
| 23 |
</package>
|
| 1 |
<?xml version="1.0"?>
|
| 2 |
<package>
|
| 3 |
<name>Klaviyo_Reclaim</name>
|
| 4 |
+
<version>1.6.2</version>
|
| 5 |
<stability>stable</stability>
|
| 6 |
<license>Open Software License (OSL)</license>
|
| 7 |
<channel>community</channel>
|
| 8 |
<extends/>
|
| 9 |
+
<summary>Officially supported Magento extension for the Klaviyo marketing platform.</summary>
|
| 10 |
<description>With this extension, you can:
|
| 11 |
+

|
| 12 |
+
- Import newsletter subscribers into a Klaviyo list.
|
| 13 |
+
- Track checkouts and send abandoned cart emails.
|
| 14 |
+
- Track website visitors and the products they view. This data is automatically fed into Klaviyo's analytics engine to build preference profiles for each visitor.
|
| 15 |
+
- Configure different settings per website or store if you manage multiple brands in one installation.
|
| 16 |
+

|
| 17 |
+
Klaviyo also syncs order, customer and product catalog data using Magento's built-in API. This extension supports Magento versions 1.5 and later.
|
| 18 |
+

|
| 19 |
+
For Magento 2 installations, download our extension at http://docs.klaviyo.com/.</description>
|
| 20 |
+
<notes>Adding support for configuring Klaviyo settings at the store and website level.</notes>
|
| 21 |
<authors><author><name>Klaviyo Team</name><user>klaviyo</user><email>support@klaviyo.com</email></author></authors>
|
| 22 |
+
<date>2017-04-07</date>
|
| 23 |
+
<time>13:57:38</time>
|
| 24 |
+
<contents><target name="magecommunity"><dir name="Klaviyo"><dir name="Reclaim"><dir name="Block"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Fieldset"><file name="Info.php" hash="3e9e5c9393b4049bee342922faea77a3"/></dir><dir name="Form"><file name="Button.php" hash="9f864eccb15821b49aa8f401545ef7b9"/></dir></dir></dir></dir><dir name="Checkout"><file name="Monitor.php" hash="79992befa61467274ebbd280745d55ab"/></dir><dir name="Tracking"><file name="Default.php" hash="1ebce8bc5e151135e6b505b161a8a756"/><file name="Product.php" hash="905acae51c2e6a425b781a64dc3a3e8e"/></dir></dir><dir name="Helper"><file name="Data.php" hash="92b191f8ebc983f6a7996f2cf98e2fe3"/></dir><dir name="Model"><file name="Api.php" hash="f16169d8397df52ccf25b2f0aeaf2038"/><dir name="Api2"><dir name="Coupon"><dir name="Rest"><dir name="Admin"><file name="V1.php" hash="a449572e24a3458fec93d1682d7d9120"/></dir></dir></dir><file name="Coupon.php" hash="8d975ee135a6991dcb405011d3816f94"/><dir name="Rule"><dir name="Rest"><dir name="Admin"><file name="V1.php" hash="d736d601eaddf3971740374150c386f3"/></dir></dir></dir><file name="Rule.php" hash="fd04f68b9490499336ef8e0de28a74e3"/></dir><file name="Checkout.php" hash="482b26c7e4e882d15634e660415a4d65"/><file name="KlaviyoApi.php" hash="ead08d780556c0378120020e941709eb"/><dir name="Mysql4"><dir name="Checkout"><file name="Collection.php" hash="6c85e812ce3f3e4a341df4c7549a7e7f"/></dir><file name="Checkout.php" hash="8e3399cf5bf95f8aaf1e369ed95a0456"/></dir><file name="Observer.php" hash="d932a070b99f3bea5610a9e86d9b8aa8"/><file name="PublicApiKey.php" hash="dd78ea7d5ea0a25b0c8db2e7c7887a40"/><dir name="System"><dir name="Config"><dir name="Source"><file name="List.php" hash="44bb20b48631d4b193f46037fd9a5d91"/></dir></dir></dir><file name="Tracker.php" hash="0516ed9923d608a8d3b84636e1ba6bb2"/></dir><dir name="controllers"><file name="IndexController.php" hash="de82da454eb6906aad37a07b1b608a82"/><file name="KlaviyoController.php" hash="2c65e5f4101c1bb8a737de2390d51525"/></dir><dir name="etc"><file name="adminhtml.xml" hash="befc296020e0249d72b2a1076a48b45d"/><file name="api2.xml" hash="8a5e921eb5eddd993c9eddfe034a6abe"/><file name="config.xml" hash="2b2b95ea51df232065fb0444ee74c24b"/><file name="system.xml" hash="dd5614ba35fafbbec0de8d6e6bb3fd6d"/></dir><dir name="sql"><dir name="klaviyo_reclaim_setup"><file name="mysql4-install-1.0.5.php" hash="d6479ff37d54bd011270370c7c74e975"/></dir></dir></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="base"><dir name="default"><dir name="layout"><file name="klaviyoreclaim.xml" hash="51b3a3d783921a4e455c141b4a18e7ec"/></dir><dir name="template"><dir name="klaviyoreclaim"><dir name="system"><dir name="config"><dir name="fieldset"><file name="info.phtml" hash="9b972d85ac2e4e5c6d0fada6708b3a4b"/></dir></dir></dir></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="klaviyoreclaim.xml" hash="1523ed1271dae7967a7f125fd5a6897c"/></dir><dir name="template"><dir name="klaviyoreclaim"><dir name="checkout"><file name="monitor.phtml" hash="4ed26697dcc4a979345e7fc8835e033c"/></dir><dir name="tracking"><file name="default.phtml" hash="0985dab82f9290d3693298e51fd36745"/><file name="product.phtml" hash="987124910256fa6383e4ccc064c4a088"/></dir></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Klaviyo_Reclaim.xml" hash="97fa1624141df9065e240fd12dd49f2c"/></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="base"><dir name="default"><dir name="klaviyoreclaim"><file name="klaviyoreclaim.css" hash="c12f3281cac2d53b181dfcb7da53f278"/></dir></dir></dir></dir></target></contents>
|
| 25 |
<compatible/>
|
| 26 |
<dependencies><required><php><min>5.1.0</min><max>6.0.0</max></php></required></dependencies>
|
| 27 |
</package>
|
