Iterable_Plugin - Version 1.1.0

Version Notes

Added feature to choose which events get pushed to Iterable.

Download this release

Release Info

Developer Ilya Brin
Extension Iterable_Plugin
Version 1.1.0
Comparing to
See all releases


Version 1.1.0

app/code/community/Iterable/TrackOrderPlaced/Helper/Data.php ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Iterable_TrackOrderPlaced_Helper_Data extends Mage_Core_Helper_Abstract {
4
+
5
+ const XML_PATH_ITERABLE_API_KEY = 'api_options/api_key_options/api_key';
6
+ const XML_PATH_ENABLED_EVENTS = 'advanced_options/tracking_options/enabled_events';
7
+
8
+ private function getDecodedMagentoApiToken() {
9
+ $magentoApiKey = Mage::getStoreConfig(self::XML_PATH_ITERABLE_API_KEY);
10
+ return json_decode(base64_decode($magentoApiKey));
11
+ }
12
+
13
+ private function getIterableApiToken() {
14
+ $apiKeyJson = $this->getDecodedMagentoApiToken();
15
+ if ($apiKeyJson == NULL) {
16
+ return NULL;
17
+ }
18
+ return $apiKeyJson->t;
19
+ }
20
+
21
+ public function getNewsletterEmailListId() {
22
+ $apiKeyJson = $this->getDecodedMagentoApiToken();
23
+ if ($apiKeyJson == NULL) {
24
+ return NULL;
25
+ }
26
+ return $apiKeyJson->n;
27
+ }
28
+
29
+ public function getAccountEmailListId() {
30
+ $apiKeyJson = $this->getDecodedMagentoApiToken();
31
+ if ($apiKeyJson == NULL) {
32
+ return NULL;
33
+ }
34
+ return $apiKeyJson->u;
35
+ }
36
+
37
+ private function callIterableApi($event, $endpoint, $params) {
38
+ $eventsToTrack = Mage::getStoreConfig(self::XML_PATH_ENABLED_EVENTS);
39
+ $eventsToTrack = explode(",", $eventsToTrack);
40
+ if (!in_array($event, $eventsToTrack)) {
41
+ // TODO - maybe run this before gathering data about the cart
42
+ return;
43
+ }
44
+ $apiKey = $this->getIterableApiToken();
45
+ if ($apiKey == NULL) {
46
+ return;
47
+ }
48
+ $url = "https://api.iterable.com/{$endpoint}?api_key={$apiKey}";
49
+ try {
50
+ $client = new Zend_Http_Client($url);
51
+ } catch(Exception $e) {
52
+ Mage::log("Warning: unable to create http client with url {$url} ({$e->getMessage()})");
53
+ return;
54
+ }
55
+ $client->setMethod(Zend_Http_Client::POST);
56
+ // $client->setHeaders('Content-Type', 'application/json');
57
+ $json = json_encode($params);
58
+ $client->setRawData($json, 'application/json');
59
+ try {
60
+ $response = $client->request();
61
+ $status = $response->getStatus();
62
+ if ($status != 200) {
63
+ Mage::log("Iterable Tracker: Unable to track event at {$endpoint} with params {$json}; got status {$status} with body {$response->getBody()}");
64
+ }
65
+ } catch(Exception $e) {
66
+ Mage::log("Warning: unable to send event at {$endpoint} with params {$json} to Iterable ({$e->getMessage()})");
67
+ }
68
+ }
69
+
70
+ public function updateUser($email, $dataFields) {
71
+ $endpoint = '/api/users/update';
72
+ $params = array(
73
+ 'email' => $email
74
+ );
75
+ if (!empty($dataFields)) {
76
+ $params['dataFields'] = $dataFields;
77
+ }
78
+ $this->callIterableApi(Iterable_TrackOrderPlaced_Model_TrackingEventTypes::EVENT_TYPE_USER, $endpoint, $params);
79
+ }
80
+
81
+ public function subscribeEmailToList($email, $listId, $dataFields=array(), $resubscribe=False) {
82
+ $endpoint = '/api/lists/subscribe';
83
+ $params = array(
84
+ 'listId' => $listId,
85
+ 'subscribers' => array(
86
+ array(
87
+ 'email' => $email
88
+ )
89
+ ),
90
+ 'resubscribe' => $resubscribe
91
+ );
92
+ if (!empty($dataFields)) {
93
+ $params['subscribers'][0]['dataFields'] = $dataFields;
94
+ }
95
+ $this->callIterableApi(Iterable_TrackOrderPlaced_Model_TrackingEventTypes::EVENT_TYPE_NEWSLETTER_SUBSCRIBE, $endpoint, $params);
96
+ }
97
+
98
+ public function unsubscribeEmailFromList($email, $listId) {
99
+ $endpoint = '/api/lists/unsubscribe';
100
+ $params = array(
101
+ 'listId' => $listId,
102
+ 'subscribers' => array(
103
+ array(
104
+ 'email' => $email
105
+ )
106
+ )
107
+ // 'campaignId' => iterableCid cookie?
108
+ );
109
+ $this->callIterableApi(Iterable_TrackOrderPlaced_Model_TrackingEventTypes::EVENT_TYPE_NEWSLETTER_UNSUBSCRIBE, $endpoint, $params);
110
+ }
111
+
112
+ /*
113
+ public function track($event, $email, $dataFields=array()) {
114
+ $endpoint = '/api/events/track';
115
+ $params = array(
116
+ 'email' => $email,
117
+ 'eventName' => $event
118
+ );
119
+ if (!empty($dataFields)) {
120
+ $params['dataFields'] = $dataFields;
121
+ }
122
+ $this->callIterableApi($event, $endpoint, $params);
123
+ }
124
+ */
125
+
126
+ public function updateCart($email, $items, $dataFields=array()) {
127
+ $endpoint = '/api/commerce/updateCart';
128
+ $params = array(
129
+ 'user' => array(
130
+ 'email' => $email
131
+ ),
132
+ 'items' => $items
133
+ );
134
+ if (!empty($dataFields)) {
135
+ $params['user']['dataFields'] = $dataFields;
136
+ }
137
+ $this->callIterableApi(Iterable_TrackOrderPlaced_Model_TrackingEventTypes::EVENT_TYPE_CART_UPDATED, $endpoint, $params);
138
+ }
139
+
140
+ public function trackPurchase($email, $items, $campaignId=NULL, $dataFields=array()) {
141
+ $endpoint = '/api/commerce/trackPurchase';
142
+ $params = array(
143
+ 'user' => array(
144
+ 'email' => $email
145
+ ),
146
+ 'items' => $items
147
+ );
148
+ if (!empty($dataFields)) {
149
+ $params['user']['dataFields'] = $dataFields;
150
+ }
151
+ if ($campaignId != NULL) {
152
+ $params['campaignId'] = $campaignId;
153
+ }
154
+ $this->callIterableApi(Iterable_TrackOrderPlaced_Model_TrackingEventTypes::EVENT_TYPE_ORDER, $endpoint, $params);
155
+ }
156
+
157
+ }
app/code/community/Iterable/TrackOrderPlaced/Model/Observer.php ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Our class name should follow the directory structure of
4
+ * our Observer.php model, starting from the namespace,
5
+ * replacing directory separators with underscores.
6
+ * i.e. app/code/local/Iterable/
7
+ * TrackOrderPlaced/Model/Observer.php
8
+ *
9
+ * @author Iterable
10
+ */
11
+ class Iterable_TrackOrderPlaced_Model_Observer
12
+ {
13
+
14
+ // HELPER FUNCTIONS
15
+ // TODO - move to Helpers
16
+
17
+ private function getCategories($product) {
18
+ // http://stackoverflow.com/questions/4252547/display-all-categories-that-a-product-belongs-to-in-magento
19
+ $currentCatIds = $product->getCategoryIds();
20
+ if (empty($currentCatIds)) {
21
+ return array();
22
+ }
23
+ $childCategories = Mage::getResourceModel('catalog/category_collection')
24
+ ->addAttributeToSelect('name')
25
+ ->addAttributeToSelect('url')
26
+ ->addAttributeToFilter('entity_id', $currentCatIds)
27
+ ->addIsActiveFilter();
28
+ $categories = array();
29
+ foreach($childCategories as $category) {
30
+ $curkey = &$categories;
31
+ $parents = $category->getParentCategories();
32
+ usort($parents,function($categoryA, $categoryB) { return $categoryA->getLevel() < $categoryB->getLevel() ? -1: 1; } );
33
+ /*
34
+ // if instead we want it in dictionary/map form, we can use this (also uasort instead of usort)
35
+ $lastName = end($parents)->getName();
36
+ foreach ($parents as $parentCat) {
37
+ $name = $parentCat->getName();
38
+ $isLast = $name == $lastName;
39
+ if (!array_key_exists($name, $curkey)) {
40
+ if ($isLast) {
41
+ array_push($curkey, $name);
42
+ } else {
43
+ $curkey[$name] = array();
44
+ }
45
+ }
46
+ if (!$isLast) { // assigning to that index creates it so don't assign
47
+ $curkey = &$curkey[$name];
48
+ }
49
+ }
50
+ */
51
+ $names = array_map(function($c) { return $c->getName(); }, $parents);
52
+ array_push($categories, $names);
53
+ }
54
+ return $categories;
55
+ }
56
+
57
+ private function toIterableItem($product)
58
+ {
59
+ $item = $product;
60
+ $cls = get_class($product);
61
+ $isOrder = $cls == "Mage_Sales_Model_Order_Item";
62
+ $isQuote = $cls == "Mage_Sales_Model_Quote_Item";
63
+ if ($isOrder or $isQuote) {
64
+ $product = Mage::getModel('catalog/product')->load($item->getProductId());
65
+ }
66
+ $typeId = $product->getTypeId();
67
+
68
+ if ($isOrder) {
69
+ $price = $item->getPrice();
70
+ $quantity = $item->getQtyOrdered();
71
+ } elseif ($isQuote) {
72
+ $price = $item->getCalculationPrice();
73
+ $quantity = $item->getQty();
74
+ } else {
75
+ $price = ($typeId == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) ?
76
+ // or maybe $pricemodel->getTotalBundleItemsPrice($product)
77
+ Mage::getModel('bundle/product_price')->getFinalPrice($qty=1.0, $product) :
78
+ $product->getPrice();
79
+ $quantity = intval($product->getCartQty());
80
+ }
81
+
82
+ $imageUrl = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_MEDIA).'catalog/product/'.$product->getImage();
83
+ $categoryNames = array();
84
+ foreach ($this->getCategories($product) as $categoryBreadcrumbs) {
85
+ $categoryNames[] = implode('/', $categoryBreadcrumbs);
86
+ }
87
+ return array(
88
+ 'id' => $product->getEntityId(),
89
+ 'sku' => $item->getSku(),
90
+ 'name' => $item->getName(),
91
+ 'categories' => $categoryNames,
92
+ //'itemDescription' => $product->getDescription(), // maybe use short description instead?
93
+ 'imageUrl' => $imageUrl,
94
+ 'url' => $product->getProductUrl(),
95
+ 'quantity' => $quantity,
96
+ 'price' => $price == NULL ? 0.0: floatval($price) // TODO - make sure price isn't NULL
97
+ //'dataFields' => array(
98
+ // 'typeId' => $typeId
99
+ //)
100
+ );
101
+ }
102
+
103
+ public function getItemsFromQuote($quote=NULL, $includeConfigurableSubproducts=TRUE)
104
+ {
105
+ if ($quote == NULL) {
106
+ $quote = Mage::getSingleton('checkout/session')->getQuote();
107
+ }
108
+ $quoteItems = array();
109
+ foreach ($quote->getAllItems() as $item) {
110
+ $iterableItem = $this->toIterableItem($item);
111
+ $parent = $item->getParentItem();
112
+ if (!$parent) {
113
+ $quoteItems[$item->getId()] = $iterableItem;
114
+ } /* else {
115
+ $parentItem = &$quoteItems[$parent->getId()];
116
+ if ($includeConfigurableSubproducts or ($parentItem['dataFields']['typeId'] != Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE)) {
117
+ if (!array_key_exists('subproducts', $parentItem)) {
118
+ $parentItem['subproducts'] = array();
119
+ }
120
+ array_push($parentItem['subproducts'], $iterableItem);
121
+ }
122
+ } */
123
+ }
124
+ return $quoteItems;
125
+ }
126
+
127
+
128
+ // EVENT HOOKS
129
+
130
+ private function sendCartUpdated($items=NULL)
131
+ {
132
+ if (!Mage::helper('customer')->isLoggedIn()) {
133
+ return;
134
+ }
135
+ if ($items == NULL) {
136
+ $items = array_values($this->getItemsFromQuote(NULL, FALSE));
137
+ }
138
+ $customer = Mage::getSingleton('customer/session')->getCustomer();
139
+ $helper = Mage::helper('trackorderplaced');
140
+ $helper->updateCart($customer->getEmail(), $items);
141
+ }
142
+
143
+ /**
144
+ * Called when something is added to the cart
145
+ */
146
+ public function checkoutCartProductAddAfter(Varien_Event_Observer $observer)
147
+ {
148
+ // it seems that the price is empty sometimes on a configurable, and this seems to fix that...
149
+ $quote = $observer->getEvent()->getQuoteItem()->getQuote();
150
+ $quote->collectTotals();
151
+ $quote->save();
152
+
153
+ $this->sendCartUpdated();
154
+ }
155
+
156
+ /**
157
+ * Called when a product is updated (quantity changed on item page, bundle reconfigured, etc)
158
+ */
159
+ public function checkoutCartProductUpdateAfter(Varien_Event_Observer $observer)
160
+ {
161
+ $quote = $observer->getEvent()->getQuoteItem()->getQuote();
162
+ $quote->collectTotals();
163
+ $quote->save();
164
+
165
+ $this->sendCartUpdated();
166
+ }
167
+
168
+ /**
169
+ * Called when quantities changed, etc on shopping cart page ("Update Shopping Cart" clicked)
170
+ */
171
+ public function checkoutCartUpdateItemsAfter(Varien_Event_Observer $observer)
172
+ {
173
+ $this->sendCartUpdated();
174
+ }
175
+
176
+ /**
177
+ * Called when something is removed from the cart (for example via the trash can symbol on cart page)
178
+ * Unforunately it also gets called on updateItems with quantity = 0, or when you reconfigure a configurable product with different options (so we'll get a few extra events)
179
+ */
180
+ public function salesQuoteRemoveItem(Varien_Event_Observer $observer)
181
+ {
182
+ $this->sendCartUpdated();
183
+ }
184
+
185
+ /**
186
+ * Gets fired before the quote is saved. Seems to happen on changes to cart, in addition to whenever we view it
187
+ * There doesn't seem to be any event called when the user clicks "Clear Shopping Cart", so hook into this and check what they clicked
188
+ */
189
+ public function salesQuoteSaveBefore(Varien_Event_Observer $observer)
190
+ {
191
+ $updateAction = (string)Mage::app()->getRequest()->getParam('update_cart_action');
192
+
193
+ if ($updateAction == 'empty_cart') {
194
+ $this->sendCartUpdated();
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Gets called when an order is placed
200
+ */
201
+ public function orderPlaced(Varien_Event_Observer $observer)
202
+ {
203
+ $order = $observer->getEvent()->getOrder();
204
+ $orderItems = $this->getItemsFromQuote($order, FALSE);
205
+ $items = array_values($orderItems);
206
+ $email = $order->getCustomerEmail();
207
+ /*
208
+ $dataFields = array(
209
+ 'firstName' => $order->getCustomerFirstname(),
210
+ 'lastName' => $order->getCustomerLastname(),
211
+ 'subtotal' => $order->getSubtotal(),
212
+ 'grandTotal' => $order->getGrandTotal(),
213
+ 'taxAmount' => $order->getTaxAmount(),
214
+ 'shippingAmount' => $order->getShippingAmount(),
215
+ 'items' => $items
216
+ );
217
+ */
218
+ $cookieModel = Mage::getModel('core/cookie');
219
+ // $iterableUid = $cookieModel->get('iterable_uid');
220
+ $campaignId = $cookieModel->get('iterable_cid');
221
+ $campaignId = empty($campaignId) ? NULL: intval($campaignId);
222
+ // $dataFields["campaignId"] = $iterableCid;
223
+ $helper = Mage::helper('trackorderplaced');
224
+ $helper->trackPurchase($email, $items, $campaignId);
225
+
226
+ // don't need to clear cart, server does it automatically
227
+ }
228
+
229
+ /**
230
+ * Gets called when a customer saves their data
231
+ * Also seems to get called at several other times (after an order, etc)
232
+ */
233
+ public function customerSaveAfter(Varien_Event_Observer $observer)
234
+ {
235
+ $customer = $observer->getCustomer();
236
+
237
+ $email = $customer->getEmail();
238
+
239
+ $dataFields = $customer->getData();
240
+
241
+ // set shipping address
242
+ $defaultShipping = $customer->getDefaultShippingAddress();
243
+ if ($defaultShipping) { $dataFields['defaultShipping'] = $defaultShipping->getData(); }
244
+ // set billing address
245
+ $defaultBilling = $customer->getDefaultBillingAddress();
246
+ if ($defaultBilling) { $dataFields['defaultBilling'] = $defaultBilling->getData(); }
247
+ // unset password/conf
248
+ $fieldsToUnset = array('password', 'password_hash', 'confirmation');
249
+ foreach ($fieldsToUnset as $fieldToUnset) {
250
+ if (array_key_exists($fieldToUnset, $dataFields)) {
251
+ unset($dataFields[$fieldToUnset]);
252
+ }
253
+ }
254
+ $helper = Mage::helper('trackorderplaced');
255
+ $helper->updateUser($email, $dataFields);
256
+ // if they're just creating their account, add them to a new users list
257
+ if (!$customer->getOrigData()) {
258
+ $listId = $helper->getAccountEmailListId();
259
+ if ($listId != NULL) {
260
+ $helper->subscribeEmailToList($email, $listId, $dataFields);
261
+ }
262
+ }
263
+ }
264
+
265
+ /*
266
+ public function addToCart(Varien_Event_Observer $observer)
267
+ {
268
+ Mage::log("add to cart!");
269
+ if (!Mage::helper('customer')->isLoggedIn()) {
270
+ return;
271
+ }
272
+ $customer = Mage::helper('customer')->getCustomer();
273
+ $email = $customer->getEmail();
274
+ $product = $observer->getProduct();
275
+ $cartItem = $this->toIterableItem($product);
276
+ $subproductsKey = 'subproducts';
277
+ $typeId = $product->getTypeId();
278
+ if ($typeId == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
279
+ // nothing extra to do for simple products
280
+ } elseif ($typeId == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
281
+ // configurable item seems to already have all important stuff set
282
+ } elseif ($typeId == Mage_Catalog_Model_Product_Type::TYPE_VIRTUAL) {
283
+ // hmm... probably nothing extra needed here
284
+ } elseif ($typeId == Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
285
+ $cartItem[$subproductsKey] = array();
286
+ $associatedProducts = $product->getTypeInstance(true)->getAssociatedProducts($product);
287
+ foreach ($associatedProducts as $associatedProduct) {
288
+ if (!$associatedProduct->getCartQty()) {
289
+ continue;
290
+ }
291
+ array_push($cartItem[$subproductsKey], $this->toIterableItem($associatedProduct));
292
+ }
293
+ } elseif ($typeId == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) {
294
+ $cartItem[$subproductsKey] = array();
295
+ // also have thing in Mage_Bundle_Block_Catalog_Product_View_Type_Bundle
296
+ $bundlePriceHelper = Mage::getModel("bundle/product_price");
297
+ $optionsCollection = $bundlePriceHelper->getOptions($product);
298
+ foreach ($optionsCollection as $option) {
299
+ if (!$option->getSelections()) {
300
+ continue; // no selections
301
+ }
302
+ foreach ($option->getSelections() as $selection) {
303
+ // Mage::log("{$option->getTitle()} - {$selection->getName()} ({$selection->getCartQty()} @ {$selection->getPrice()})");
304
+ array_push($cartItem[$subproductsKey], $this->toIterableItem($selection));
305
+ }
306
+ }
307
+ } else {
308
+ // hmm...
309
+ }
310
+
311
+ $dataFields = array(
312
+ 'item' => $cartItem
313
+ );
314
+ $helper = Mage::helper('trackorderplaced');
315
+ $helper->track(Iterable_TrackOrderPlaced_Model_TrackingEventTypes::EVENT_TYPE_ADD_TO_CART, $email, $dataFields);
316
+ }
317
+ */
318
+
319
+ /**
320
+ * Called whenever a newsletter subscriber is saved
321
+ */
322
+ public function newsletterSubscriberSaveAfter(Varien_Event_Observer $observer)
323
+ {
324
+ if (!$subscriber = $observer->getEvent()->getSubscriber()) {
325
+ return;
326
+ }
327
+ if (!$subscriber->getIsStatusChanged()) {
328
+ return; // don't send if nothing changed
329
+ }
330
+ $helper = Mage::helper('trackorderplaced');
331
+ $listId = $helper->getNewsletterEmailListId();
332
+ if ($listId == NULL) {
333
+ return;
334
+ }
335
+ $email = $subscriber->getSubscriberEmail();
336
+ $dataFields = $subscriber->getData();
337
+ switch ($subscriber->getStatus()) {
338
+ case Mage_Newsletter_Model_Subscriber::STATUS_SUBSCRIBED:
339
+ $helper->subscribeEmailToList($email, $listId, $dataFields, True);
340
+ break;
341
+ case Mage_Newsletter_Model_Subscriber::STATUS_UNSUBSCRIBED:
342
+ $helper->unsubscribeEmailFromList($email, $listId);
343
+ break;
344
+ default:
345
+ break;
346
+ }
347
+ }
348
+
349
+ }
app/code/community/Iterable/TrackOrderPlaced/Model/TrackingEventTypes.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Iterable_TrackOrderPlaced_Model_TrackingEventTypes
4
+ {
5
+
6
+ const EVENT_TYPE_ORDER = 'order';
7
+ const EVENT_TYPE_USER = 'user';
8
+ const EVENT_TYPE_CART_UPDATED = 'cartUpdated';
9
+ const EVENT_TYPE_NEWSLETTER_SUBSCRIBE = 'newsletterSubscribe';
10
+ const EVENT_TYPE_NEWSLETTER_UNSUBSCRIBE = 'newsletterUnsubscribe';
11
+
12
+ /** @const */
13
+ private static $eventTypes = array(
14
+ self::EVENT_TYPE_ORDER => 'Orders',
15
+ self::EVENT_TYPE_USER => 'User',
16
+ self::EVENT_TYPE_CART_UPDATED => 'Cart Updated',
17
+ self::EVENT_TYPE_NEWSLETTER_SUBSCRIBE => 'Newsletter Subscribe',
18
+ self::EVENT_TYPE_NEWSLETTER_UNSUBSCRIBE => 'Newsletter Unsubscribe'
19
+ );
20
+
21
+ public function toOptionArray()
22
+ {
23
+ $events = array();
24
+ foreach (self::$eventTypes as $key => $name) {
25
+ $events[] = array('value' => $key, 'label'=>Mage::helper('trackorderplaced')->__($name));
26
+ }
27
+ return $events;
28
+ }
29
+
30
+ }
app/code/community/Iterable/TrackOrderPlaced/etc/config.xml ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ /**
4
+ * Module configuration
5
+ *
6
+ * @author Iterable
7
+ */
8
+ -->
9
+ <!-- The root node for Magento module configuration -->
10
+ <config>
11
+
12
+ <!--
13
+ The module's node contains basic
14
+ information about each Magento module
15
+ -->
16
+ <modules>
17
+ <!--
18
+ This must exactly match the namespace and module's folder
19
+ names, with directory separators replaced by underscores
20
+ -->
21
+ <Iterable_TrackOrderPlaced>
22
+ <!-- The version of our module, starting at 0.0.1 -->
23
+ <version>0.0.1</version>
24
+ </Iterable_TrackOrderPlaced>
25
+ </modules>
26
+
27
+ <!-- Configure our module's behavior in the global scope -->
28
+ <global>
29
+
30
+ <helpers>
31
+ <trackorderplaced>
32
+ <class>Iterable_TrackOrderPlaced_Helper</class>
33
+ </trackorderplaced>
34
+ </helpers>
35
+
36
+ <models>
37
+ <iterable_trackorderplaced>
38
+ <class>Iterable_TrackOrderPlaced_Model</class>
39
+ </iterable_trackorderplaced>
40
+ </models>
41
+
42
+ <!-- Defining an event observer -->
43
+ <events>
44
+ <!-- The code of the event we want to observe -->
45
+ <sales_order_place_after>
46
+ <!-- Defining an observer for this event -->
47
+ <observers>
48
+ <!--
49
+ Unique identifier within the
50
+ catalog_product_save_after node.
51
+ By convention, we write the module's
52
+ name in lowercase.
53
+ -->
54
+ <iterable_trackorderplaced>
55
+ <!-- The model to be instantiated -->
56
+ <class>iterable_trackorderplaced/observer</class>
57
+ <!-- The method of the class to be called -->
58
+ <method>orderPlaced</method>
59
+ <!-- The type of class to instantiate -->
60
+ <type>singleton</type>
61
+ </iterable_trackorderplaced>
62
+ </observers>
63
+ </sales_order_place_after>
64
+ <customer_save_after>
65
+ <observers>
66
+ <iterable_trackaccountcreated>
67
+ <class>iterable_trackorderplaced/observer</class>
68
+ <method>customerSaveAfter</method>
69
+ <type>singleton</type>
70
+ </iterable_trackaccountcreated>
71
+ </observers>
72
+ </customer_save_after>
73
+ <!--
74
+ <customer_address_save_after>
75
+ <observers>
76
+ <iterable_trackaccountcreated>
77
+ <class>iterable_trackorderplaced/observer</class>
78
+ <method>customerSaveAfter</method>
79
+ <type>singleton</type>
80
+ </iterable_trackaccountcreated>
81
+ </observers>
82
+ </customer_address_save_after>
83
+ -->
84
+ <!-- tracks additions to the cart, but doesn't track changed quantities -->
85
+ <!--
86
+ <checkout_cart_add_product_complete>
87
+ <observers>
88
+ <iterable_trackaddtocart>
89
+ <class>iterable_trackorderplaced/observer</class>
90
+ <method>addToCart</method>
91
+ <type>singleton</type>
92
+ </iterable_trackaddtocart>
93
+ </observers>
94
+ </checkout_cart_add_product_complete>
95
+ checkout_cart_update_item_complete
96
+ -->
97
+ <checkout_cart_product_add_after>
98
+ <observers>
99
+ <iterable_trackaddtocart>
100
+ <class>iterable_trackorderplaced/observer</class>
101
+ <method>checkoutCartProductAddAfter</method>
102
+ <type>singleton</type>
103
+ </iterable_trackaddtocart>
104
+ </observers>
105
+ </checkout_cart_product_add_after>
106
+ <checkout_cart_product_update_after>
107
+ <observers>
108
+ <iterable_trackupdatecart>
109
+ <class>iterable_trackorderplaced/observer</class>
110
+ <method>checkoutCartProductUpdateAfter</method>
111
+ <type>singleton</type>
112
+ </iterable_trackupdatecart>
113
+ </observers>
114
+ </checkout_cart_product_update_after>
115
+ <checkout_cart_update_items_after>
116
+ <observers>
117
+ <iterable_trackupdatecart>
118
+ <class>iterable_trackorderplaced/observer</class>
119
+ <method>checkoutCartUpdateItemsAfter</method>
120
+ <type>singleton</type>
121
+ </iterable_trackupdatecart>
122
+ </observers>
123
+ </checkout_cart_update_items_after>
124
+ <!-- should use cart events instead of quote events, quote events don't get fired when quantity changes... however we still need the remove
125
+ <sales_quote_add_item>
126
+ <observers>
127
+ <iterable_trackupdatecart>
128
+ <class>iterable_trackorderplaced/observer</class>
129
+ <method>salesQuoteAddItem</method>
130
+ <type>singleton</type>
131
+ </iterable_trackupdatecart>
132
+ </observers>
133
+ </sales_quote_add_item>
134
+ -->
135
+ <sales_quote_save_before>
136
+ <observers>
137
+ <iterable_trackclearcart>
138
+ <class>iterable_trackorderplaced/observer</class>
139
+ <method>salesQuoteSaveBefore</method>
140
+ <type>singleton</type>
141
+ </iterable_trackclearcart>
142
+ </observers>
143
+ </sales_quote_save_before>
144
+ <sales_quote_remove_item>
145
+ <observers>
146
+ <iterable_trackupdatecart>
147
+ <class>iterable_trackorderplaced/observer</class>
148
+ <method>salesQuoteRemoveItem</method>
149
+ <type>singleton</type>
150
+ </iterable_trackupdatecart>
151
+ </observers>
152
+ </sales_quote_remove_item>
153
+ <newsletter_subscriber_save_after>
154
+ <observers>
155
+ <iterable_tracknewslettersubscribe>
156
+ <class>iterable_trackorderplaced/observer</class>
157
+ <method>newsletterSubscriberSaveAfter</method>
158
+ <type>singleton</type>
159
+ </iterable_tracknewslettersubscribe>
160
+ </observers>
161
+ </newsletter_subscriber_save_after>
162
+ </events>
163
+ </global>
164
+
165
+ <default>
166
+ <api_options>
167
+ <api_key_options>
168
+ <api_key>MAGENTO_API_KEY</api_key>
169
+ </api_key_options>
170
+ </api_options>
171
+ <advanced_options>
172
+ <tracking_options>
173
+ <enabled_events>order,user,cartUpdated,newsletterSubscribe,newsletterUnsubscribe</enabled_events>
174
+ </tracking_options>
175
+ </advanced_options>
176
+ </default>
177
+
178
+ <adminhtml>
179
+ <layout>
180
+ <updates>
181
+ <iterable_trackorderplaced>
182
+ <file>iterable/common.xml</file>
183
+ </iterable_trackorderplaced>
184
+ </updates>
185
+ </layout>
186
+ <acl>
187
+ <resources>
188
+ <admin>
189
+ <children>
190
+ <system>
191
+ <children>
192
+ <config>
193
+ <children>
194
+ <api_options>
195
+ <title>Iterable API Module</title>
196
+ </api_options>
197
+ <advanced_options>
198
+ <title>Iterable Advanced Module</title>
199
+ </advanced_options>
200
+ </children>
201
+ </config>
202
+ </children>
203
+ </system>
204
+ </children>
205
+ </admin>
206
+ </resources>
207
+ </acl>
208
+ </adminhtml>
209
+
210
+ </config>
app/code/community/Iterable/TrackOrderPlaced/etc/system.xml ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <config>
2
+ <tabs> <!-- heading in the left-side navigation -->
3
+ <iterableconfig translate="label" module="trackorderplaced">
4
+ <label><![CDATA[<div>Iterable</div>]]></label>
5
+ <class>iterable-section</class>
6
+ <sort_order>101</sort_order> <!-- General is 100, from app/code/core/Mage/Core/etc/system.xml -->
7
+ </iterableconfig>
8
+ </tabs>
9
+ <sections> <!-- each section defined here is a subcategory of a heading from tabs. EACH SECTION NEEDS AN ACL DEFINED IN CONFIG.XML -->
10
+ <api_options translate="label" module="trackorderplaced">
11
+ <label>API</label>
12
+ <tab>iterableconfig</tab>
13
+ <frontend_type>text</frontend_type>
14
+ <sort_order>1000</sort_order>
15
+ <show_in_default>1</show_in_default>
16
+ <show_in_website>1</show_in_website>
17
+ <show_in_store>1</show_in_store>
18
+ <groups>
19
+ <api_key_options translate="label">
20
+ <label>API Key</label>
21
+ <frontend_type>text</frontend_type>
22
+ <expanded>1</expanded>
23
+ <sort_order>1</sort_order>
24
+ <show_in_default>1</show_in_default>
25
+ <show_in_website>1</show_in_website>
26
+ <show_in_store>1</show_in_store>
27
+ <fields>
28
+ <api_key>
29
+ <label>API Key</label>
30
+ <comment>Your unique API key. Can be found in your Iterable account <![CDATA[<a href="https://iterable.com/api/settings">here</a>]]>.</comment>
31
+ <frontend_type>text</frontend_type>
32
+ <sort_order>1</sort_order>
33
+ <show_in_default>1</show_in_default>
34
+ <show_in_website>1</show_in_website>
35
+ <show_in_store>1</show_in_store>
36
+ </api_key>
37
+ </fields>
38
+ </api_key_options>
39
+ </groups>
40
+ </api_options>
41
+ <advanced_options>
42
+ <label>Advanced</label>
43
+ <tab>iterableconfig</tab>
44
+ <frontend_type>text</frontend_type>
45
+ <sort_order>1001</sort_order>
46
+ <show_in_default>1</show_in_default>
47
+ <show_in_website>1</show_in_website>
48
+ <show_in_store>1</show_in_store>
49
+ <groups>
50
+ <tracking_options translate="label">
51
+ <label>Event Tracking</label>
52
+ <frontend_type>text</frontend_type>
53
+ <sort_order>2</sort_order>
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
+ <fields>
58
+ <enabled_events>
59
+ <label>Events</label>
60
+ <comment>The types of events to push to Iterable</comment>
61
+ <frontend_type>multiselect</frontend_type>
62
+ <source_model>iterable_trackorderplaced/trackingeventtypes</source_model>
63
+ <sort_order>1</sort_order>
64
+ <show_in_default>1</show_in_default>
65
+ <show_in_website>1</show_in_website>
66
+ <show_in_store>1</show_in_store>
67
+ </enabled_events>
68
+ </fields>
69
+ </tracking_options>
70
+ </groups>
71
+ </advanced_options>
72
+ </sections>
73
+ </config>
app/design/adminhtml/default/default/layout/iterable/common.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <layout>
3
+ <adminhtml_system_config_edit>
4
+ <reference name="head">
5
+ <action method="addCss"><stylesheet>iterable/iterable.css</stylesheet></action>
6
+ </reference>
7
+ </adminhtml_system_config_edit>
8
+ </layout>
app/etc/modules/Iterable_TrackOrderPlaced.xml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ /**
4
+ * Module initial config
5
+ *
6
+ * @author Iterable
7
+ */
8
+ -->
9
+ <config>
10
+ <modules>
11
+ <Iterable_TrackOrderPlaced>
12
+
13
+ <!-- Whether our module is active: true or false -->
14
+ <active>true</active>
15
+
16
+ <!-- Which code pool to use: core, community or local -->
17
+ <codePool>community</codePool>
18
+
19
+ <!-- Which modules our code depends on -->
20
+ <depends>
21
+ <!-- <Iterable_Tracker/> -->
22
+ </depends>
23
+
24
+ </Iterable_TrackOrderPlaced>
25
+ </modules>
26
+ </config>
package.xml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Iterable_Plugin</name>
4
+ <version>1.1.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://opensource.org/licenses/OSL-3.0">Open Software License (OSL) </license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>Automatically tie together your commerce platform and email marketing with this plugin from Iterable!</summary>
10
+ <description>After installing this plugin, you will be able to immediately view and take action in the Iterable platform whenever your Magento users make important decisions, such as signing up or making a purchase! Easily integrate and set up abandoned cart emails, handle newsletter subscriptions, or reach out to customers whenever they make a purchase. </description>
11
+ <notes>Added feature to choose which events get pushed to Iterable.</notes>
12
+ <authors><author><name>Ilya Brin</name><user>Iterable</user><email>ilya@iterable.com</email></author></authors>
13
+ <date>2014-04-30</date>
14
+ <time>03:23:26</time>
15
+ <contents><target name="magecommunity"><dir name="Iterable"><dir name="TrackOrderPlaced"><dir name="Helper"><file name="Data.php" hash="661ec5234fee78ffeae3d77011a2dde1"/></dir><dir name="Model"><file name="Observer.php" hash="d110ed71f94f2bff983e88ca1551ae2d"/><file name="TrackingEventTypes.php" hash="f0592c7a7506a0e606275dd422a57bb8"/></dir><dir name="etc"><file name="config.xml" hash="d9e0eae78379f3c6238d70023b110276"/><file name="system.xml" hash="e4ad1b58022f8969e64dff80971c4cfb"/></dir></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><dir name="iterable"><file name="common.xml" hash="d90fbb22fb34fa29deb6b0e131f9e767"/></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Iterable_TrackOrderPlaced.xml" hash="0d4506dadf95eecb54e215ed03b2c6a7"/></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="base"><dir name="default"><dir name="iterable"><dir name="images"><file name="section_logo.png" hash="fe5090ba955a890c1efe2c09cb260342"/></dir><file name="iterable.css" hash="4d928c5a4ed76d62e246a57ee868ec23"/><file name="iterable.css~" hash="2c3ee0498f159bc59bee37cd3c508f27"/></dir></dir></dir></dir></target></contents>
16
+ <compatible/>
17
+ <dependencies><required><php><min>5.4.1</min><max>6.0.0</max></php></required></dependencies>
18
+ </package>
skin/adminhtml/base/default/iterable/images/section_logo.png ADDED
Binary file
skin/adminhtml/base/default/iterable/iterable.css ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ .iterable-section > dl > dt.label > div {
2
+ height: 22px;
3
+ text-indent: -9999px;
4
+ background: url(images/section_logo.png) no-repeat -1px -1px;
5
+ }
skin/adminhtml/base/default/iterable/iterable.css~ ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ .bronto-section > dl > dt.label > div {
2
+ height: 22px;
3
+ text-indent: -9999px;
4
+ background: url(images/section_logo.png) no-repeat -1px -1px;
5
+ }