Klaviyo_Reclaim - Version 1.6.2

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 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 and !($is_api_key_set or $is_cron_running or $has_reclaim_entries);
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 Mage::getStoreConfigFlag(self::XML_PATH_ENABLED, $store);
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 Mage::app()->getWebsite($store)->getConfig(self::XML_PATH_PUBLIC_API_KEY);
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 Mage::getStoreConfig(self::XML_PATH_PRIVATE_API_KEY, $store);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- const MAX_LINE_ITEMS_WITH_DETAILS = 5;
 
 
 
 
 
 
 
 
 
 
 
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
- if (!Mage::helper('klaviyo_reclaim')->isEnabled()) {
36
- return;
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
- $updated_after = Zend_Date::now();
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
- $tracker = self::getTracker($quote);
65
- $billing_address = $quote->getBillingAddress();
66
-
67
- if (!$billing_address || !$tracker) {
68
- continue;
69
  }
 
 
 
 
 
 
 
70
 
71
- // Skip quotes that don't have an email or remote_ip set. Checking the IP is our best guess
72
- // at not sending emails for quotes created on the Magento backend. There
73
- // doesn't seem a reliable way to tell if a quote is from the frontend or backend.
74
- if (!$quote->getCustomerEmail() || is_null($quote->getRemoteIp())) {
75
- continue;
76
- }
 
77
 
78
- $quote_id = $quote->getId();
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
- $item_descriptions = array();
101
- $item_details = array();
102
- $line_item_count = 0;
103
- $item_count = 0;
104
-
105
- $configurable_product_ids = array();
106
-
107
- foreach ($quote->getItemsCollection() as $quote_item) {
108
- $quote_item_quantity = $quote_item->getQty();
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
- if (!empty($configurable_product_ids)) {
148
- $model_configurable = Mage::getModel('catalog/product_type_configurable');
149
- $tmp = array();
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
- // Skip quotes that don't have items.
173
- if (empty($item_details)) {
174
- continue;
175
- }
176
 
177
- $checkout_id = $checkout->getId();
178
- $checkout_url = $quote->getStore()->getUrl('reclaim/index/view', array('_query' => array('id' => $checkout_id)));
179
-
180
- $properties = array(
181
- '$event_id' => $quote_id,
182
- '$value' => (float) $quote->getGrandTotal(),
183
- 'Items' => $item_descriptions,
184
- 'Items Count' => $item_count,
185
- '$extra' => array(
186
- 'checkout_url' => $checkout_url,
187
- 'checkout_id' => $checkout_id,
188
- 'line_items' => $item_details,
189
- )
190
- );
191
-
192
- $coupon_code = $quote->getCouponCode();
193
- if ($coupon_code) {
194
- $totals = $quote->getTotals();
195
- $properties['Discount Codes'] = array($coupon_code);
196
- if (array_key_exists('discount', $totals) && is_object($totals['discount'])) {
197
- $properties['Total Discounts'] = (float) $totals['discount']->getValue() * -1;
198
- }
199
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
- $timestamp = strtotime($quote->getUpdatedAt());
202
 
203
- $tracker->track('Checkout Started', $customer_properties, $properties, $timestamp);
204
 
205
- if (!$quote->getIsActive()) {
206
- $tracker->track('Checkout Completed', $customer_properties, $properties, $timestamp);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
 
 
208
 
209
- $quotes_tracked++;
 
 
 
 
 
 
 
 
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
- return Mage::helper('core')->jsonEncode(array('tracked' => $quotes_tracked));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- public function syncCustomer (Varien_Event_Observer $observer) {
307
- // Mage::log($observer);
 
 
 
 
 
 
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
- * Save cart email action
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
- $is_enabled = $helper->isEnabled();
106
- $is_api_key_set = $helper->getPublicApiKey() != NULL;
107
 
108
- $adapter = Mage::getSingleton('core/resource')->getConnection('sales_read');
109
- $hour_ago = Zend_Date::now();
110
- $hour_ago->sub(60, Zend_Date::MINUTE);
111
- $hour_ago = $adapter->convertDateTime($hour_ago);
112
 
113
- $is_cron_running = Mage::getModel('cron/schedule')->getCollection()
114
- ->addFieldToFilter('status', Mage_Cron_Model_Schedule::STATUS_SUCCESS)
115
- ->addFieldToFilter('finished_at', array('gteq' => $hour_ago))
116
- ->count() > 0;
117
 
118
- $has_reclaim_entries = Mage::getModel('klaviyo_reclaim/checkout')->getCollection()->count() > 0;
 
119
 
120
  $response = array(
121
- 'data' => array($is_enabled, $is_api_key_set, $is_cron_running, $has_reclaim_entries)
 
 
 
 
 
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
- <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>
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.3.5</version>
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/integrations" target="_blank">https://www.klaviyo.com/integrations</a>.]]></comment>
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
- </layout>
 
 
 
 
 
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
- $_product_image_url = $_product_image->getUrl();
9
- break;
10
  }
11
  }
12
  ?>
13
  <script text="text/javascript">
14
  var _learnq = _learnq || [];
15
-
16
- _learnq.push(['track', 'Viewed Product', {
17
- Name: "<?php echo $_product->getName(); ?>",
 
18
  SKU: "<?php echo $_product->getSku(); ?>",
19
- Url: "<?php echo $_product->getProductUrl(); ?>",
20
- <?php if ($_product_image_url) { ?>ImageUrl: "<?php echo $_product_image_url; ?>", <?php } ?>
 
 
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.3.5</version>
5
  <stability>stable</stability>
6
  <license>Open Software License (OSL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
- <summary>A Magento extension to connect to the Klaviyo email marketing platform.</summary>
10
  <description>With this extension, you can:&#xD;
11
- - Import newsletter subscribers to a Klaviyo list.&#xD;
12
- - Track checkouts and send abandoned cart emails.&#xD;
13
- - Track site visitors and what products they look at to personalize email campaigns.&#xD;
14
- Additionally, Klaviyo tracks sales data using Magento's built-in API.&#xD;
15
- This extension is supports all Magento versions 1.5 and later.</description>
16
- <notes>Add cron job monitoring to extension page.</notes>
 
 
 
 
17
  <authors><author><name>Klaviyo Team</name><user>klaviyo</user><email>support@klaviyo.com</email></author></authors>
18
- <date>2016-03-28</date>
19
- <time>14:23:12</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="a38325604653158f97cd4d98733a69ba"/></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="e0518247bbdccb8350bca77b64968d23"/></dir><dir name="Model"><file name="Api.php" hash="a121792d153c2e6545c825d366451429"/><file name="Checkout.php" hash="482b26c7e4e882d15634e660415a4d65"/><file name="KlaviyoApi.php" hash="8c6b650058fabc2864793ab164b360c1"/><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="0c59d1423db59a618de61f9816ec88df"/><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="4006484d2bbedf9480af3767ff7c11af"/></dir><dir name="etc"><file name="adminhtml.xml" hash="50fb6bd76e5945412049be9798282eaf"/><file name="config.xml" hash="d0fb11557248073057914548afefce92"/><file name="system.xml" hash="6c708e11f6f3242f44b07b08690df94d"/></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="3916ca3377e6965babeba8b3e7af3ba1"/></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="8830831b94ef55f6e22032ed784e0d77"/></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>
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:&#xD;
11
+ &#xD;
12
+ - Import newsletter subscribers into a Klaviyo list.&#xD;
13
+ - Track checkouts and send abandoned cart emails.&#xD;
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.&#xD;
15
+ - Configure different settings per website or store if you manage multiple brands in one installation.&#xD;
16
+ &#xD;
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.&#xD;
18
+ &#xD;
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>