Nosto_Tagging - Version 2.6.5

Version Notes

* Add "external_order_ref" to order tagging and API requests in order to better
track orders
* Add "order_statuses" to the order export to be able to better check up on order funnels

Download this release

Release Info

Developer Nosto
Extension Nosto_Tagging
Version 2.6.5
Comparing to
See all releases


Code changes from version 2.6.4 to 2.6.5

app/code/community/Nosto/Tagging/Model/Export/Collection/Order.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Open Software License (OSL 3.0)
8
+ * that is bundled with this package in the file LICENSE.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category Nosto
22
+ * @package Nosto_Tagging
23
+ * @author Nosto Solutions Ltd <magento@nosto.com>
24
+ * @copyright Copyright (c) 2013-2015 Nosto Solutions Ltd (http://www.nosto.com)
25
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
26
+ */
27
+
28
+ /**
29
+ * Order collection for historical data exports.
30
+ */
31
+ class Nosto_Tagging_Model_Export_Collection_Order extends NostoOrderCollection implements NostoExportCollectionInterface
32
+ {
33
+ /**
34
+ * @inheritdoc
35
+ */
36
+ public function getJson()
37
+ {
38
+ $array = array();
39
+ /** @var Nosto_Tagging_Model_Meta_Order $item */
40
+ foreach ($this->getArrayCopy() as $item) {
41
+ $data = array(
42
+ 'order_number' => $item->getOrderNumber(),
43
+ 'external_order_ref' => $item->getExternalOrderRef(),
44
+ 'order_status_code' => $item->getOrderStatus()->getCode(),
45
+ 'order_status_label' => $item->getOrderStatus()->getLabel(),
46
+ 'order_statuses' => array(),
47
+ 'created_at' => Nosto::helper('date')->format($item->getCreatedDate()),
48
+ 'buyer' => array(
49
+ 'first_name' => $item->getBuyerInfo()->getFirstName(),
50
+ 'last_name' => $item->getBuyerInfo()->getLastName(),
51
+ 'email' => $item->getBuyerInfo()->getEmail(),
52
+ ),
53
+ 'payment_provider' => $item->getPaymentProvider(),
54
+ 'purchased_items' => array(),
55
+ );
56
+ foreach ($item->getPurchasedItems() as $orderItem) {
57
+ $data['purchased_items'][] = array(
58
+ 'product_id' => $orderItem->getProductId(),
59
+ 'quantity' => (int)$orderItem->getQuantity(),
60
+ 'name' => $orderItem->getName(),
61
+ 'unit_price' => Nosto::helper('price')->format($orderItem->getUnitPrice()),
62
+ 'price_currency_code' => strtoupper($orderItem->getCurrencyCode()),
63
+ );
64
+ }
65
+ foreach ($item->getOrderStatuses() as $status) {
66
+ if (!isset($data['order_statuses'][$status->getCode()])) {
67
+ $data['order_statuses'][$status->getCode()] = array();
68
+ }
69
+ $data['order_statuses'][$status->getCode()][] =
70
+ date('Y-m-d\TH:i:s\Z', strtotime($status->getCreatedAt()));
71
+ }
72
+ $array[] = $data;
73
+ }
74
+ return json_encode($array);
75
+ }
76
+ }
app/code/community/Nosto/Tagging/Model/Meta/Order.php CHANGED
@@ -47,6 +47,11 @@ class Nosto_Tagging_Model_Meta_Order extends Mage_Core_Model_Abstract implements
47
  */
48
  protected $_orderNumber;
49
 
 
 
 
 
 
50
  /**
51
  * @var string the date when the order was placed.
52
  */
@@ -74,6 +79,11 @@ class Nosto_Tagging_Model_Meta_Order extends Mage_Core_Model_Abstract implements
74
  */
75
  protected $_orderStatus;
76
 
 
 
 
 
 
77
  /**
78
  * @inheritdoc
79
  */
@@ -90,52 +100,212 @@ class Nosto_Tagging_Model_Meta_Order extends Mage_Core_Model_Abstract implements
90
  public function loadData(Mage_Sales_Model_Order $order)
91
  {
92
  $this->_orderNumber = $order->getId();
 
93
  $this->_createdDate = $order->getCreatedAt();
94
  $this->_paymentProvider = $order->getPayment()->getMethod();
95
 
96
- /** @var Nosto_Tagging_Model_Meta_Order_Status $orderStatus */
97
- $orderStatus = Mage::getModel('nosto_tagging/meta_order_status');
98
- $orderStatus->loadData($order);
99
- $this->_orderStatus = $orderStatus;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- /** @var Nosto_Tagging_Model_Meta_Order_Buyer $orderBuyer */
102
- $orderBuyer = Mage::getModel('nosto_tagging/meta_order_buyer');
103
- $orderBuyer->loadData($order);
104
- $this->_buyer = $orderBuyer;
 
 
 
 
105
 
106
- /** @var $item Mage_Sales_Model_Order_Item */
107
  foreach ($order->getAllVisibleItems() as $item) {
108
- /** @var Nosto_Tagging_Model_Meta_Order_Item $orderItem */
109
- $orderItem = Mage::getModel('nosto_tagging/meta_order_item');
110
- $orderItem->loadData($item);
111
- $this->_items[] = $orderItem;
112
  }
113
 
114
  if ($this->includeSpecialItems) {
115
  if (($discount = $order->getDiscountAmount()) > 0) {
116
  /** @var Nosto_Tagging_Model_Meta_Order_Item $orderItem */
117
- $orderItem = Mage::getModel('nosto_tagging/meta_order_item');
118
- $orderItem->loadSpecialItemData(
119
- 'Discount',
120
- $discount,
121
- $order->getOrderCurrencyCode()
 
 
 
 
122
  );
123
- $this->_items[] = $orderItem;
124
  }
125
 
126
  if (($shippingInclTax = $order->getShippingInclTax()) > 0) {
127
  /** @var Nosto_Tagging_Model_Meta_Order_Item $orderItem */
128
- $orderItem = Mage::getModel('nosto_tagging/meta_order_item');
129
- $orderItem->loadSpecialItemData(
130
- 'Shipping and handling',
131
- $shippingInclTax,
132
- $order->getOrderCurrencyCode()
 
 
 
 
133
  );
134
- $this->_items[] = $orderItem;
135
  }
136
  }
137
  }
138
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  /**
140
  * The unique order number identifying the order.
141
  *
@@ -146,6 +316,16 @@ class Nosto_Tagging_Model_Meta_Order extends Mage_Core_Model_Abstract implements
146
  return $this->_orderNumber;
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
149
  /**
150
  * The date when the order was placed.
151
  *
@@ -196,4 +376,14 @@ class Nosto_Tagging_Model_Meta_Order extends Mage_Core_Model_Abstract implements
196
  {
197
  return $this->_orderStatus;
198
  }
 
 
 
 
 
 
 
 
 
 
199
  }
47
  */
48
  protected $_orderNumber;
49
 
50
+ /**
51
+ * @var string the Magento order "real order ID" property.
52
+ */
53
+ protected $_externalOrderRef;
54
+
55
  /**
56
  * @var string the date when the order was placed.
57
  */
79
  */
80
  protected $_orderStatus;
81
 
82
+ /**
83
+ * @var Nosto_Tagging_Model_Meta_Order_Status[] list of order status history.
84
+ */
85
+ protected $_orderStatuses = array();
86
+
87
  /**
88
  * @inheritdoc
89
  */
100
  public function loadData(Mage_Sales_Model_Order $order)
101
  {
102
  $this->_orderNumber = $order->getId();
103
+ $this->_externalOrderRef = $order->getRealOrderId();
104
  $this->_createdDate = $order->getCreatedAt();
105
  $this->_paymentProvider = $order->getPayment()->getMethod();
106
 
107
+ $this->_orderStatus = Mage::getModel(
108
+ 'nosto_tagging/meta_order_status',
109
+ array(
110
+ 'code' => $order->getStatus(),
111
+ 'label' => $order->getStatusLabel()
112
+ )
113
+ );
114
+
115
+ foreach ($order->getAllStatusHistory() as $item) {
116
+ /** @var Mage_Sales_Model_Order_Status_History $item */
117
+ $this->_orderStatuses[] = Mage::getModel(
118
+ 'nosto_tagging/meta_order_status',
119
+ array(
120
+ 'code' => $item->getStatus(),
121
+ 'label' => $item->getStatusLabel(),
122
+ 'createdAt' => $item->getCreatedAt()
123
+ )
124
+ );
125
+ }
126
 
127
+ $this->_buyer = Mage::getModel(
128
+ 'nosto_tagging/meta_order_buyer',
129
+ array(
130
+ 'firstName' => $order->getCustomerFirstname(),
131
+ 'lastName' => $order->getCustomerLastname(),
132
+ 'email' => $order->getCustomerEmail()
133
+ )
134
+ );
135
 
 
136
  foreach ($order->getAllVisibleItems() as $item) {
137
+ /** @var $item Mage_Sales_Model_Order_Item */
138
+ $this->_items[] = $this->buildItem($item, $order);
 
 
139
  }
140
 
141
  if ($this->includeSpecialItems) {
142
  if (($discount = $order->getDiscountAmount()) > 0) {
143
  /** @var Nosto_Tagging_Model_Meta_Order_Item $orderItem */
144
+ $this->_items[] = Mage::getModel(
145
+ 'nosto_tagging/meta_order_item',
146
+ array(
147
+ 'productId' => -1,
148
+ 'quantity' => 1,
149
+ 'name' => 'Discount',
150
+ 'unitPrice' => $discount,
151
+ 'currencyCode' => $order->getOrderCurrencyCode()
152
+ )
153
  );
 
154
  }
155
 
156
  if (($shippingInclTax = $order->getShippingInclTax()) > 0) {
157
  /** @var Nosto_Tagging_Model_Meta_Order_Item $orderItem */
158
+ $this->_items[] = Mage::getModel(
159
+ 'nosto_tagging/meta_order_item',
160
+ array(
161
+ 'productId' => -1,
162
+ 'quantity' => 1,
163
+ 'name' => 'Shipping and handling',
164
+ 'unitPrice' => $shippingInclTax,
165
+ 'currencyCode' => $order->getOrderCurrencyCode()
166
+ )
167
  );
 
168
  }
169
  }
170
  }
171
 
172
+ /**
173
+ * Builds a order items object form the Magento sales item.
174
+ *
175
+ * @param Mage_Sales_Model_Order_Item $item the sales item model.
176
+ * @param Mage_Sales_Model_Order $order the order model.
177
+ *
178
+ * @return Nosto_Tagging_Model_Meta_Order_Item the built item.
179
+ */
180
+ protected function buildItem(Mage_Sales_Model_Order_Item $item, Mage_Sales_Model_Order $order)
181
+ {
182
+ return Mage::getModel(
183
+ 'nosto_tagging/meta_order_item',
184
+ array(
185
+ 'productId' => (int)$this->buildItemProductId($item),
186
+ 'quantity' => (int)$item->getQtyOrdered(),
187
+ 'name' => $this->buildItemName($item),
188
+ 'unitPrice' => $item->getPriceInclTax(),
189
+ 'currencyCode' => $order->getOrderCurrencyCode()
190
+ )
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Returns the product id for a quote item.
196
+ * Always try to find the "parent" product ID if the product is a child of
197
+ * another product type. We do this because it is the parent product that
198
+ * we tag on the product page, and the child does not always have it's own
199
+ * product page. This is important because it is the tagged info on the
200
+ * product page that is used to generate recommendations and email content.
201
+ *
202
+ * @param Mage_Sales_Model_Order_Item $item the sales item model.
203
+ *
204
+ * @return int
205
+ */
206
+ protected function buildItemProductId(Mage_Sales_Model_Order_Item $item)
207
+ {
208
+ $parent = $item->getProductOptionByCode('super_product_config');
209
+ if (isset($parent['product_id'])) {
210
+ return $parent['product_id'];
211
+ } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
212
+ /** @var Mage_Catalog_Model_Product_Type_Configurable $model */
213
+ $model = Mage::getModel('catalog/product_type_configurable');
214
+ $parentIds = $model->getParentIdsByChild($item->getProductId());
215
+ $attributes = $item->getBuyRequest()->getData('super_attribute');
216
+ // If the product has a configurable parent, we assume we should tag
217
+ // the parent. If there are many parent IDs, we are safer to tag the
218
+ // products own ID.
219
+ if (count($parentIds) === 1 && !empty($attributes)) {
220
+ return $parentIds[0];
221
+ }
222
+ }
223
+ return $item->getProductId();
224
+ }
225
+
226
+ /**
227
+ * Returns the name for a sales item.
228
+ * Configurable products will have their chosen options added to their name.
229
+ * Bundle products will have their chosen child product names added.
230
+ * Grouped products will have their parents name prepended.
231
+ * All others will have their own name only.
232
+ *
233
+ * @param Mage_Sales_Model_Order_Item $item the sales item model.
234
+ *
235
+ * @return string
236
+ */
237
+ protected function buildItemName(Mage_Sales_Model_Order_Item $item)
238
+ {
239
+ $name = $item->getName();
240
+ $optNames = array();
241
+
242
+ if ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
243
+ /** @var Mage_Catalog_Model_Product_Type_Configurable $model */
244
+ $model = Mage::getModel('catalog/product_type_configurable');
245
+ $parentIds = $model->getParentIdsByChild($item->getProductId());
246
+ // If the product has a configurable parent, we assume we should tag
247
+ // the parent. If there are many parent IDs, we are safer to tag the
248
+ // products own name alone.
249
+ if (count($parentIds) === 1) {
250
+ $attributes = $item->getBuyRequest()->getData('super_attribute');
251
+ if (is_array($attributes)) {
252
+ foreach ($attributes as $id => $value) {
253
+ /** @var Mage_Catalog_Model_Resource_Eav_Attribute $attribute */
254
+ $attribute = Mage::getModel('catalog/resource_eav_attribute')
255
+ ->load($id);
256
+ $label = $attribute->getSource()->getOptionText($value);
257
+ if (!empty($label)) {
258
+ $optNames[] = $label;
259
+ }
260
+ }
261
+ }
262
+ }
263
+ } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
264
+ $opts = $item->getProductOptionByCode('attributes_info');
265
+ if (is_array($opts)) {
266
+ foreach ($opts as $opt) {
267
+ if (isset($opt['value']) && is_string($opt['value'])) {
268
+ $optNames[] = $opt['value'];
269
+ }
270
+ }
271
+ }
272
+ } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) {
273
+ $opts = $item->getProductOptionByCode('bundle_options');
274
+ if (is_array($opts)) {
275
+ foreach ($opts as $opt) {
276
+ if (isset($opt['value']) && is_array($opt['value'])) {
277
+ foreach ($opt['value'] as $val) {
278
+ $qty = '';
279
+ if (isset($val['qty']) && is_int($val['qty'])) {
280
+ $qty .= $val['qty'] . ' x ';
281
+ }
282
+ if (isset($val['title']) && is_string($val['title'])) {
283
+ $optNames[] = $qty . $val['title'];
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+ } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
290
+ $config = $item->getProductOptionByCode('super_product_config');
291
+ if (isset($config['product_id'])) {
292
+ /** @var Mage_Catalog_Model_Product $parent */
293
+ $parent = Mage::getModel('catalog/product')
294
+ ->load($config['product_id']);
295
+ $parentName = $parent->getName();
296
+ if (!empty($parentName)) {
297
+ $name = $parentName.' - '.$name;
298
+ }
299
+ }
300
+ }
301
+
302
+ if (!empty($optNames)) {
303
+ $name .= ' (' . implode(', ', $optNames) . ')';
304
+ }
305
+
306
+ return $name;
307
+ }
308
+
309
  /**
310
  * The unique order number identifying the order.
311
  *
316
  return $this->_orderNumber;
317
  }
318
 
319
+ /**
320
+ * Returns the Magento order "real order ID" property.
321
+ *
322
+ * @return string the order ref.
323
+ */
324
+ public function getExternalOrderRef()
325
+ {
326
+ return $this->_externalOrderRef;
327
+ }
328
+
329
  /**
330
  * The date when the order was placed.
331
  *
376
  {
377
  return $this->_orderStatus;
378
  }
379
+
380
+ /**
381
+ * Returns a list of order status history items.
382
+ *
383
+ * @return Nosto_Tagging_Model_Meta_Order_Status[] the list.
384
+ */
385
+ public function getOrderStatuses()
386
+ {
387
+ return $this->_orderStatuses;
388
+ }
389
  }
app/code/community/Nosto/Tagging/Model/Meta/Order/Buyer.php CHANGED
@@ -52,23 +52,37 @@ class Nosto_Tagging_Model_Meta_Order_Buyer extends Mage_Core_Model_Abstract impl
52
  protected $_email;
53
 
54
  /**
55
- * @inheritdoc
 
 
 
 
 
 
56
  */
57
- protected function _construct()
58
  {
59
- $this->_init('nosto_tagging/meta_order_buyer');
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
61
 
62
  /**
63
- * Loads the buyer info from a Magento order model.
64
- *
65
- * @param Mage_Sales_Model_Order $order the order model.
66
  */
67
- public function loadData(Mage_Sales_Model_Order $order)
68
  {
69
- $this->_firstName = $order->getCustomerFirstname();
70
- $this->_lastName = $order->getCustomerLastname();
71
- $this->_email = $order->getCustomerEmail();
72
  }
73
 
74
  /**
52
  protected $_email;
53
 
54
  /**
55
+ * Constructor.
56
+ *
57
+ * Sets up this Value Object.
58
+ *
59
+ * @param array $args the object data.
60
+ *
61
+ * @throws InvalidArgumentException
62
  */
63
+ public function __construct(array $args)
64
  {
65
+ if (!isset($args['firstName']) || !is_string($args['firstName']) || empty($args['firstName'])) {
66
+ throw new InvalidArgumentException(sprintf('%s.firstName must be a non-empty string value.', __CLASS__));
67
+ }
68
+ if (!isset($args['lastName']) || !is_string($args['lastName']) || empty($args['lastName'])) {
69
+ throw new InvalidArgumentException(sprintf('%s.lastName must be a non-empty string value.', __CLASS__));
70
+ }
71
+ if (!isset($args['email']) || !is_string($args['email']) || empty($args['email'])) {
72
+ throw new InvalidArgumentException(sprintf('%s.email must be a non-empty string value.', __CLASS__));
73
+ }
74
+
75
+ $this->_firstName = $args['firstName'];
76
+ $this->_lastName = $args['lastName'];
77
+ $this->_email = $args['email'];
78
  }
79
 
80
  /**
81
+ * @inheritdoc
 
 
82
  */
83
+ protected function _construct()
84
  {
85
+ $this->_init('nosto_tagging/meta_order_buyer');
 
 
86
  }
87
 
88
  /**
app/code/community/Nosto/Tagging/Model/Meta/Order/Item.php CHANGED
@@ -63,159 +63,45 @@ class Nosto_Tagging_Model_Meta_Order_Item extends Mage_Core_Model_Abstract imple
63
  protected $_currencyCode;
64
 
65
  /**
66
- * @inheritdoc
67
- */
68
- protected function _construct()
69
- {
70
- $this->_init('nosto_tagging/meta_order_item');
71
- }
72
-
73
- /**
74
- * Loads the item info from the Magento order item model.
75
  *
76
- * @param Mage_Sales_Model_Order_Item $item the item model.
77
- */
78
- public function loadData(Mage_Sales_Model_Order_Item $item)
79
- {
80
- $order = $item->getOrder();
81
- $this->_productId = (int)$this->fetchProductId($item);
82
- $this->_quantity = (int)$item->getQtyOrdered();
83
- $this->_name = $this->fetchProductName($item);
84
- $this->_unitPrice = $item->getPriceInclTax();
85
- $this->_currencyCode = strtoupper($order->getOrderCurrencyCode());
86
- }
87
-
88
- /**
89
- * Loads the "special item" info from provided data.
90
- * A "special item" is an item that is included in an order but does not
91
- * represent an item being bough, e.g. shipping fees, discounts etc.
92
- *
93
- * @param string $name the name of the item.
94
- * @param float|int|string $unitPrice the unit price of the item.
95
- * @param string $currencyCode the currency code for the item unit price.
96
- */
97
- public function loadSpecialItemData($name, $unitPrice, $currencyCode)
98
- {
99
- $this->_productId = -1;
100
- $this->_quantity = 1;
101
- $this->_name = (string)$name;
102
- $this->_unitPrice = $unitPrice;
103
- $this->_currencyCode = strtoupper($currencyCode);
104
- }
105
-
106
- /**
107
- * Returns the product id for a quote item.
108
- * Always try to find the "parent" product ID if the product is a child of
109
- * another product type. We do this because it is the parent product that
110
- * we tag on the product page, and the child does not always have it's own
111
- * product page. This is important because it is the tagged info on the
112
- * product page that is used to generate recommendations and email content.
113
  *
114
- * @param Mage_Sales_Model_Order_Item $item the sales item model.
115
  *
116
- * @return int
117
  */
118
- protected function fetchProductId(Mage_Sales_Model_Order_Item $item)
119
  {
120
- $parent = $item->getProductOptionByCode('super_product_config');
121
- if (isset($parent['product_id'])) {
122
- return $parent['product_id'];
123
- } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
124
- /** @var Mage_Catalog_Model_Product_Type_Configurable $model */
125
- $model = Mage::getModel('catalog/product_type_configurable');
126
- $parentIds = $model->getParentIdsByChild($item->getProductId());
127
- $attributes = $item->getBuyRequest()->getData('super_attribute');
128
- // If the product has a configurable parent, we assume we should tag
129
- // the parent. If there are many parent IDs, we are safer to tag the
130
- // products own ID.
131
- if (count($parentIds) === 1 && !empty($attributes)) {
132
- return $parentIds[0];
133
- }
134
  }
135
- return $item->getProductId();
 
 
 
 
 
 
 
 
136
  }
137
 
138
  /**
139
- * Returns the name for a sales item.
140
- * Configurable products will have their chosen options added to their name.
141
- * Bundle products will have their chosen child product names added.
142
- * Grouped products will have their parents name prepended.
143
- * All others will have their own name only.
144
- *
145
- * @param Mage_Sales_Model_Order_Item $item the sales item model.
146
- *
147
- * @return string
148
  */
149
- protected function fetchProductName(Mage_Sales_Model_Order_Item $item)
150
  {
151
- $name = $item->getName();
152
- $optNames = array();
153
-
154
- if ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_SIMPLE) {
155
- /** @var Mage_Catalog_Model_Product_Type_Configurable $model */
156
- $model = Mage::getModel('catalog/product_type_configurable');
157
- $parentIds = $model->getParentIdsByChild($item->getProductId());
158
- // If the product has a configurable parent, we assume we should tag
159
- // the parent. If there are many parent IDs, we are safer to tag the
160
- // products own name alone.
161
- if (count($parentIds) === 1) {
162
- $attributes = $item->getBuyRequest()->getData('super_attribute');
163
- if (is_array($attributes)) {
164
- foreach ($attributes as $id => $value) {
165
- /** @var Mage_Catalog_Model_Resource_Eav_Attribute $attribute */
166
- $attribute = Mage::getModel('catalog/resource_eav_attribute')
167
- ->load($id);
168
- $label = $attribute->getSource()->getOptionText($value);
169
- if (!empty($label)) {
170
- $optNames[] = $label;
171
- }
172
- }
173
- }
174
- }
175
- } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
176
- $opts = $item->getProductOptionByCode('attributes_info');
177
- if (is_array($opts)) {
178
- foreach ($opts as $opt) {
179
- if (isset($opt['value']) && is_string($opt['value'])) {
180
- $optNames[] = $opt['value'];
181
- }
182
- }
183
- }
184
- } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) {
185
- $opts = $item->getProductOptionByCode('bundle_options');
186
- if (is_array($opts)) {
187
- foreach ($opts as $opt) {
188
- if (isset($opt['value']) && is_array($opt['value'])) {
189
- foreach ($opt['value'] as $val) {
190
- $qty = '';
191
- if (isset($val['qty']) && is_int($val['qty'])) {
192
- $qty .= $val['qty'] . ' x ';
193
- }
194
- if (isset($val['title']) && is_string($val['title'])) {
195
- $optNames[] = $qty . $val['title'];
196
- }
197
- }
198
- }
199
- }
200
- }
201
- } elseif ($item->getProductType() === Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
202
- $config = $item->getProductOptionByCode('super_product_config');
203
- if (isset($config['product_id'])) {
204
- /** @var Mage_Catalog_Model_Product $parent */
205
- $parent = Mage::getModel('catalog/product')
206
- ->load($config['product_id']);
207
- $parentName = $parent->getName();
208
- if (!empty($parentName)) {
209
- $name = $parentName.' - '.$name;
210
- }
211
- }
212
- }
213
-
214
- if (!empty($optNames)) {
215
- $name .= ' (' . implode(', ', $optNames) . ')';
216
- }
217
-
218
- return $name;
219
  }
220
 
221
  /**
63
  protected $_currencyCode;
64
 
65
  /**
66
+ * Constructor.
 
 
 
 
 
 
 
 
67
  *
68
+ * Sets up this Value Object.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  *
70
+ * @param array $args the object data.
71
  *
72
+ * @throws InvalidArgumentException
73
  */
74
+ public function __construct(array $args)
75
  {
76
+ if (!isset($args['productId']) || !is_int($args['productId'])) {
77
+ throw new InvalidArgumentException(sprintf('%s.productId must be a integer value.', __CLASS__));
78
+ }
79
+ if (!isset($args['quantity']) || !is_int($args['quantity']) || !($args['quantity'] > 0)) {
80
+ throw new InvalidArgumentException(sprintf('%s.quantity must be a integer value above zero.', __CLASS__));
81
+ }
82
+ if (!isset($args['name']) || !is_string($args['name']) || empty($args['name'])) {
83
+ throw new InvalidArgumentException(sprintf('%s.name must be a non-empty string value.', __CLASS__));
84
+ }
85
+ if (!isset($args['unitPrice']) || !is_numeric($args['unitPrice'])) {
86
+ throw new InvalidArgumentException(sprintf('%s.unitPrice must be a numeric value.', __CLASS__));
 
 
 
87
  }
88
+ if (!isset($args['currencyCode']) || !is_string($args['currencyCode']) || empty($args['currencyCode'])) {
89
+ throw new InvalidArgumentException(sprintf('%s.currencyCode must be a non-empty string value.', __CLASS__));
90
+ }
91
+
92
+ $this->_productId = $args['productId'];
93
+ $this->_quantity = $args['quantity'];
94
+ $this->_name = $args['name'];
95
+ $this->_unitPrice = $args['unitPrice'];
96
+ $this->_currencyCode = $args['currencyCode'];
97
  }
98
 
99
  /**
100
+ * @inheritdoc
 
 
 
 
 
 
 
 
101
  */
102
+ protected function _construct()
103
  {
104
+ $this->_init('nosto_tagging/meta_order_item');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  }
106
 
107
  /**
app/code/community/Nosto/Tagging/Model/Meta/Order/Status.php CHANGED
@@ -47,22 +47,46 @@ class Nosto_Tagging_Model_Meta_Order_Status extends Mage_Core_Model_Abstract imp
47
  protected $_label;
48
 
49
  /**
50
- * @inheritdoc
51
  */
52
- protected function _construct()
 
 
 
 
 
 
 
 
 
 
 
53
  {
54
- $this->_init('nosto_tagging/meta_order_status');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
 
57
  /**
58
- * Loads the status info from a Magento order model.
59
- *
60
- * @param Mage_Sales_Model_Order $order the order model.
61
  */
62
- public function loadData(Mage_Sales_Model_Order $order)
63
  {
64
- $this->_code = $order->getStatus();
65
- $this->_label = $order->getStatusLabel();
66
  }
67
 
68
  /**
@@ -84,4 +108,14 @@ class Nosto_Tagging_Model_Meta_Order_Status extends Mage_Core_Model_Abstract imp
84
  {
85
  return $this->_label;
86
  }
 
 
 
 
 
 
 
 
 
 
87
  }
47
  protected $_label;
48
 
49
  /**
50
+ * @var string the order status created at date.
51
  */
52
+ protected $_createdAt;
53
+
54
+ /**
55
+ * Constructor.
56
+ *
57
+ * Sets up this Value Object.
58
+ *
59
+ * @param array $args the object data.
60
+ *
61
+ * @throws InvalidArgumentException
62
+ */
63
+ public function __construct(array $args)
64
  {
65
+ if (!isset($args['code']) || !is_string($args['code']) || empty($args['code'])) {
66
+ throw new InvalidArgumentException(sprintf('%s.code must be a non-empty string value.', __CLASS__));
67
+ }
68
+ if (!isset($args['label']) || !is_string($args['label']) || empty($args['label'])) {
69
+ throw new InvalidArgumentException(sprintf('%s.label must be a non-empty string value.', __CLASS__));
70
+ }
71
+ if (isset($args['createdAt'])) {
72
+ if (!is_string($args['createdAt']) || strtotime($args['createdAt']) === false) {
73
+ throw new InvalidArgumentException(sprintf('%s.createdAt must be a valid date.', __CLASS__));
74
+ }
75
+ }
76
+
77
+ $this->_code = $args['code'];
78
+ $this->_label = $args['label'];
79
+ if (isset($args['createdAt'])) {
80
+ $this->_createdAt = $args['createdAt'];
81
+ }
82
  }
83
 
84
  /**
85
+ * @inheritdoc
 
 
86
  */
87
+ protected function _construct()
88
  {
89
+ $this->_init('nosto_tagging/meta_order_status');
 
90
  }
91
 
92
  /**
108
  {
109
  return $this->_label;
110
  }
111
+
112
+ /**
113
+ * Returns the status created date.
114
+ *
115
+ * @return string the created date or null if not set.
116
+ */
117
+ public function getCreatedAt()
118
+ {
119
+ return $this->_createdAt;
120
+ }
121
  }
app/code/community/Nosto/Tagging/Model/Observer.php CHANGED
@@ -188,7 +188,9 @@ class Nosto_Tagging_Model_Observer
188
  $customerId = Mage::helper('nosto_tagging/customer')
189
  ->getNostoId($mageOrder);
190
  if ($account !== null && $account->isConnectedToNosto()) {
191
- NostoOrderConfirmation::send($order, $account, $customerId);
 
 
192
  }
193
  } catch (NostoException $e) {
194
  Mage::log("\n" . $e->__toString(), Zend_Log::ERR, 'nostotagging.log');
188
  $customerId = Mage::helper('nosto_tagging/customer')
189
  ->getNostoId($mageOrder);
190
  if ($account !== null && $account->isConnectedToNosto()) {
191
+ /** @var Nosto_Tagging_Model_Service_Order $service */
192
+ $service = Mage::getModel('nosto_tagging/service_order');
193
+ $service->confirm($order, $account, $customerId);
194
  }
195
  } catch (NostoException $e) {
196
  Mage::log("\n" . $e->__toString(), Zend_Log::ERR, 'nostotagging.log');
app/code/community/Nosto/Tagging/Model/Service/Order.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Open Software License (OSL 3.0)
8
+ * that is bundled with this package in the file LICENSE.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category Nosto
22
+ * @package Nosto_Tagging
23
+ * @author Nosto Solutions Ltd <magento@nosto.com>
24
+ * @copyright Copyright (c) 2013-2015 Nosto Solutions Ltd (http://www.nosto.com)
25
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
26
+ */
27
+
28
+ /**
29
+ * Handles sending the order confirmations to Nosto via the API.
30
+ *
31
+ * Order confirmations can be sent two different ways:
32
+ * - matched orders; where we know the Nosto customer ID of the user who placed the order
33
+ * - un-matched orders: where we do not know the Nosto customer ID of the user who placed the order
34
+ *
35
+ * The second option is a fallback and should be avoided as much as possible.
36
+ */
37
+ class Nosto_Tagging_Model_Service_Order
38
+ {
39
+ /**
40
+ * Sends an order confirmation to Nosto.
41
+ *
42
+ * @param Nosto_Tagging_Model_Meta_Order $order the order to confirm.
43
+ * @param NostoAccount $account the Nosto account object.
44
+ * @param null $customerId the Nosto customer ID of the user who placed the order.
45
+ * @throws NostoException on failure.
46
+ * @return true on success.
47
+ */
48
+ public function confirm(Nosto_Tagging_Model_Meta_Order $order, NostoAccount $account, $customerId = null)
49
+ {
50
+ $request = $this->initApiRequest($account, $customerId);
51
+ $response = $request->post($this->getOrderAsJson($order));
52
+ if ($response->getCode() !== 200) {
53
+ Nosto::throwHttpException('Failed to send order confirmation to Nosto.', $request, $response);
54
+ }
55
+ return true;
56
+ }
57
+
58
+ /**
59
+ * Builds the API request and returns it.
60
+ *
61
+ * @param NostoAccount $account the Nosto account object.
62
+ * @param string|null $customerId the Nosto customer ID of the user who placed the order.
63
+ * @return NostoApiRequest the request object.
64
+ */
65
+ protected function initApiRequest(NostoAccount $account, $customerId)
66
+ {
67
+ $request = new NostoApiRequest();
68
+ $request->setContentType('application/json');
69
+ if (!empty($customerId)) {
70
+ $request->setPath(NostoApiRequest::PATH_ORDER_TAGGING);
71
+ $request->setReplaceParams(array('{m}' => $account->getName(), '{cid}' => $customerId));
72
+ } else {
73
+ $request->setPath(NostoApiRequest::PATH_UNMATCHED_ORDER_TAGGING);
74
+ $request->setReplaceParams(array('{m}' => $account->getName()));
75
+ }
76
+ return $request;
77
+ }
78
+
79
+ /**
80
+ * Turns an order object into a JSON structure.
81
+ *
82
+ * @param Nosto_Tagging_Model_Meta_Order $order the order object.
83
+ * @return string the JSON structure.
84
+ */
85
+ protected function getOrderAsJson(Nosto_Tagging_Model_Meta_Order $order)
86
+ {
87
+ $data = array(
88
+ 'order_number' => $order->getOrderNumber(),
89
+ 'external_order_ref' => $order->getExternalOrderRef(),
90
+ 'order_status_code' => $order->getOrderStatus()->getCode(),
91
+ 'order_status_label' => $order->getOrderStatus()->getLabel(),
92
+ 'buyer' => array(
93
+ 'first_name' => $order->getBuyerInfo()->getFirstName(),
94
+ 'last_name' => $order->getBuyerInfo()->getLastName(),
95
+ 'email' => $order->getBuyerInfo()->getEmail(),
96
+ ),
97
+ 'created_at' => Nosto::helper('date')->format($order->getCreatedDate()),
98
+ 'payment_provider' => $order->getPaymentProvider(),
99
+ 'purchased_items' => array(),
100
+ );
101
+ foreach ($order->getPurchasedItems() as $item) {
102
+ $data['purchased_items'][] = array(
103
+ 'product_id' => $item->getProductId(),
104
+ 'quantity' => (int)$item->getQuantity(),
105
+ 'name' => $item->getName(),
106
+ 'unit_price' => Nosto::helper('price')->format($item->getUnitPrice()),
107
+ 'price_currency_code' => strtoupper($item->getCurrencyCode()),
108
+ );
109
+ }
110
+ return json_encode($data);
111
+ }
112
+ }
app/code/community/Nosto/Tagging/controllers/ExportController.php CHANGED
@@ -37,7 +37,7 @@ require_once Mage::getBaseDir('lib') . '/nosto/php-sdk/src/config.inc.php';
37
  * @package Nosto_Tagging
38
  * @author Nosto Solutions Ltd <magento@nosto.com>
39
  */
40
- class Nosto_tagging_ExportController extends Mage_Core_Controller_Front_Action
41
  {
42
  /**
43
  * Exports completed orders from the current store.
@@ -58,7 +58,8 @@ class Nosto_tagging_ExportController extends Mage_Core_Controller_Front_Action
58
  if ($currentPage > $orders->getLastPageNumber()) {
59
  $orders = array();
60
  }
61
- $collection = new NostoExportOrderCollection();
 
62
  foreach ($orders as $order) {
63
  /** @var Nosto_Tagging_Model_Meta_Order $meta */
64
  $meta = Mage::getModel('nosto_tagging/meta_order');
37
  * @package Nosto_Tagging
38
  * @author Nosto Solutions Ltd <magento@nosto.com>
39
  */
40
+ class Nosto_Tagging_ExportController extends Mage_Core_Controller_Front_Action
41
  {
42
  /**
43
  * Exports completed orders from the current store.
58
  if ($currentPage > $orders->getLastPageNumber()) {
59
  $orders = array();
60
  }
61
+ /** @var Nosto_Tagging_Model_Export_Collection_Order $collection */
62
+ $collection = Mage::getModel('nosto_tagging/export_collection_order');
63
  foreach ($orders as $order) {
64
  /** @var Nosto_Tagging_Model_Meta_Order $meta */
65
  $meta = Mage::getModel('nosto_tagging/meta_order');
app/code/community/Nosto/Tagging/etc/config.xml CHANGED
@@ -29,7 +29,7 @@
29
  <config>
30
  <modules>
31
  <Nosto_Tagging>
32
- <version>2.6.4</version>
33
  </Nosto_Tagging>
34
  </modules>
35
  <global>
29
  <config>
30
  <modules>
31
  <Nosto_Tagging>
32
+ <version>2.6.5</version>
33
  </Nosto_Tagging>
34
  </modules>
35
  <global>
app/design/frontend/base/default/template/nostotagging/order.phtml CHANGED
@@ -41,6 +41,7 @@ $priceHelper = Mage::helper('nosto_tagging/price');
41
  <!-- Nosto Order Tagging -->
42
  <div class="nosto_purchase_order" style="display:none">
43
  <span class="order_number"><?php echo $order->getOrderNumber(); ?></span>
 
44
  <span class="order_status_code"><?php echo $helper->escapeHtml($order->getOrderStatus()->getCode()); ?></span>
45
  <span class="order_status_label"><?php echo $helper->escapeHtml($order->getOrderStatus()->getLabel()); ?></span>
46
  <span class="payment_provider"><?php echo $helper->escapeHtml($order->getPaymentProvider()); ?></span>
41
  <!-- Nosto Order Tagging -->
42
  <div class="nosto_purchase_order" style="display:none">
43
  <span class="order_number"><?php echo $order->getOrderNumber(); ?></span>
44
+ <span class="external_order_ref"><?php echo $helper->escapeHtml($order->getExternalOrderRef()); ?></span>
45
  <span class="order_status_code"><?php echo $helper->escapeHtml($order->getOrderStatus()->getCode()); ?></span>
46
  <span class="order_status_label"><?php echo $helper->escapeHtml($order->getOrderStatus()->getLabel()); ?></span>
47
  <span class="payment_provider"><?php echo $helper->escapeHtml($order->getPaymentProvider()); ?></span>
lib/nosto/php-sdk/LICENSE.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2015, Nosto Solutions Ltd
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its contributors
15
+ may be used to endorse or promote products derived from this software without
16
+ specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
lib/nosto/php-sdk/README.md ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ php-sdk
2
+ =======
3
+
4
+ Provides tools for building modules that integrate Nosto into your e-commerce platform.
5
+
6
+ ## Requirements
7
+
8
+ * PHP 5.2+
9
+
10
+ ## What's included?
11
+
12
+ ### Classes
13
+
14
+ * **NostoApiRequest** class for making API requests to the Nosto APIs
15
+ * **NostoApiToken** class that represents an API token which can be used whn making authenticated requests to the Nosto APIs
16
+ * **NostoCollection** collection base class
17
+ * **NostoProductCollection** collection class for nosto product objects
18
+ * **NostoOrderCollection** collection class for nosto order objects
19
+ * **NostoException** main exception class for all exceptions thrown by the SDK
20
+ * **NostoHttpException** HTTP request exceptions upon request failure
21
+ * **NostoExporter** class for exporting encrypted historical data from the shop
22
+ * **NostoExportOrderCollection** class for exporting historical order data
23
+ * **NostoExportProductCollection** class for exporting historical product data
24
+ * **NostoHelper** base class for all nosto helpers
25
+ * **NostoHelperDate** helper class for date related operations
26
+ * **NostoHelperIframe** helper class for iframe related operations
27
+ * **NostoHelperPrice** helper class for price related operations
28
+ * **NostoHttpRequest** class for making HTTP request, supports both curl and socket connections
29
+ * **NostoHttpRequestAdapter** base class for creating http request adapters
30
+ * **NostoHttpRequestAdapterCurl** http request adapter for making http requests using curl
31
+ * **NostoHttpRequestAdapterSocket** http request adapter for making http requests using sockets
32
+ * **NostoHttpResponse** class that represents a response for an http request made through the NostoHttpRequest class
33
+ * **NostoOAuthClient** class for authorizing the module to act on the Nosto account owners behalf using OAuth2 Authorization Code method
34
+ * **NostoOAuthToken** class that represents a token granted using the OAuth client
35
+ * **NostoOperationProduct** class for performing create/update/delete operations on product object
36
+ * **Nosto** main sdk class for common functionality
37
+ * **NostoAccount** class that represents a Nosto account which can be used to create new accounts and connect to existing accounts using OAuth2
38
+ * **NostoCipher** class for AES encrypting product/order information that can be exported for Nosto to improve recommendations from the get-go
39
+ * **NostoDotEnv** class for handling environment variables used while developing and testing
40
+ * **NostoMessage** util class for holding info about messages that can be forwarded to the account administration iframe to show to the user
41
+ * **NostoObject** base class for Nosto objects that need to share functionality
42
+ * **NostoOrderConfirmation** class for sending order confirmations through the API
43
+ * **NostoProductReCrawl** class for sending product re-crawl requests to Nosto over the API
44
+ * **NostoValidator** class for performing data validations on objects implementing NostoValidatableInterface
45
+
46
+ ### Interfaces
47
+
48
+ * **NostoAccountInterface** interface defining methods needed to manage Nosto accounts
49
+ * **NostoAccountMetaDataBillingDetailsInterface** interface defining getters for billing information needed during Nosto account creation over the API
50
+ * **NostoAccountMetaDataIframeInterface** interface defining getters for information needed by the Nosto account configuration iframe
51
+ * **NostoAccountMetaDataInterface** interface defining getters for information needed during Nosto account creation over the API
52
+ * **NostoAccountMetaDataOwnerInterface** interface defining getters for account owner information needed during Nosto account creation over the API
53
+ * **NostoOrderBuyerInterface** interface defining getters for buyer information needed during order confirmation requests
54
+ * **NostoOrderInterface** interface defining getters for information needed during order confirmation requests
55
+ * **NostoOrderPurchasedItemInterface** interface defining getters for purchased item information needed during order confirmation requests
56
+ * **NostoOrderStatusInterface** interface defining getters for order status information needed during order confirmation requests
57
+ * **NostoExportCollectionInterface** interface defining getters for exportable data collections for the historical data
58
+ * **NostoOauthMetaDataInterface** interface defining getters for information needed during OAuth2 requests
59
+ * **NostoProductInterface** interface defining getters for product information needed during product re-crawl requests to Nosto over the API
60
+ * **NostoValidatableInterface** interface defining getters for validatable objects that can be used in conjunction with the NostoValidator class
61
+
62
+ ### Libs
63
+
64
+ * **NostoCryptAES** class for aes encryption that uses mcrypt if available and an internal implementation otherwise
65
+ * **NostoCryptBase** base class for creating encryption classes
66
+ * **NostoCryptRijndael** class for rijndael encryption that uses mcrypt if available and an internal implementation otherwise
67
+ * **NostoCryptRandom** class for generating random strings
68
+
69
+ ## Getting started
70
+
71
+ ### Creating a new Nosto account
72
+
73
+ A Nosto account is needed for every shop and every language within each shop.
74
+
75
+ ```php
76
+ .....
77
+ try {
78
+ /** @var NostoAccountMetaDataInterface $meta */
79
+ /** @var NostoAccount $account */
80
+ $account = NostoAccount::create($meta);
81
+ // save newly created account according to the platforms requirements
82
+ .....
83
+ } catch (NostoException $e) {
84
+ // handle failure
85
+ .....
86
+ }
87
+ .....
88
+ ```
89
+
90
+ ### Connecting with an existing Nosto account
91
+
92
+ This should be done in the shops back end when the admin user wants to connect an existing Nosto account to the shop.
93
+
94
+ First redirect to the Nosto OAuth2 server.
95
+
96
+ ```php
97
+ .....
98
+ /** @var NostoOAuthClientMetaDataInterface $meta */
99
+ $client = new NostoOAuthClient($meta);
100
+ header('Location: ' . $client->getAuthorizationUrl());
101
+ ```
102
+
103
+ Then have a public endpoint ready to handle the return request.
104
+
105
+ ```php
106
+ if (isset($_GET['code'])) {
107
+ try {
108
+ /** @var NostoOAuthClientMetaDataInterface $meta */
109
+ $account = NostoAccount::syncFromNosto($meta, $_GET['code']);
110
+ // save the synced account according to the platforms requirements
111
+ } catch (NostoException $e) {
112
+ // handle failures
113
+ }
114
+ // redirect to the admin page where the user can see the account configuration iframe
115
+ .....
116
+ }
117
+ } elseif (isset($_GET['error'])) {
118
+ // handle errors; 3 parameter will be sent, 'error', 'error_reason' and 'error_description'
119
+ // redirect to the admin page where the user can see an error message
120
+ .....
121
+ } else {
122
+ // 404
123
+ .....
124
+ }
125
+ ```
126
+
127
+ ### Deleting a Nosto account
128
+
129
+ This should be used when you delete a Nosto account for a shop. It will notify Nosto that this account is no longer used.
130
+
131
+ ```php
132
+ try {
133
+ /** @var NostoAccount $account */
134
+ $account->delete();
135
+ } catch (NostoException $e) {
136
+ // handle failure
137
+ }
138
+ ```
139
+
140
+ ### Get authenticated iframe URL for Nosto account configuration
141
+
142
+ The Nosto account can be created and managed through an iframe that should be accessible to the admin user in the shops
143
+ backend.
144
+ This iframe will load only content from nosto.com.
145
+
146
+ ```php
147
+ .....
148
+ /**
149
+ * @var NostoAccount|null $account account with at least the 'SSO' token loaded or null if no account exists yet
150
+ * @var NostoAccountMetaDataIframeInterface $meta
151
+ * @var array $params (optional) extra params to add to the iframe url
152
+ */
153
+ try
154
+ {
155
+ $url = Nosto::helper('iframe')->getUrl($meta, $account, $params);
156
+ }
157
+ catch (NostoException $e)
158
+ {
159
+ // handle failure
160
+ }
161
+ // show the iframe to the user with given url
162
+ .....
163
+ ```
164
+
165
+ The iframe can communicate with your module through window.postMessage
166
+ (https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage). In order to set this up you can include the JS
167
+ file `src/js/NostoIframe.min.js` on the page where you show the iframe and just init the API.
168
+
169
+ ```js
170
+ ...
171
+ Nosto.iframe({
172
+ iframeId: "nosto_iframe",
173
+ urls: {
174
+ createAccount: "url_to_the_create_account_endpoint_for_current_shop",
175
+ connectAccount: "url_to_the_connect_account_endpoint_for_current_shop",
176
+ deleteAccount: "url_to_the_delete_account_endpoint_for_current_shop"
177
+ },
178
+ xhrParams: {} // additional xhr params to include in the requests
179
+ });
180
+ ```
181
+
182
+ The iframe API makes POST requests to the specified endpoints with content-type `application/x-www-form-urlencoded`.
183
+ The response for these requests should always be JSON and include a `redirect_url` key. This url will be used to
184
+ redirect the iframe after the action has been performed. In case of the connect account, the url will be used to
185
+ redirect your browser to the Nosto OAuth server.
186
+ The redirect url also needs to include error/success message keys, if you want to show messages to the user after the
187
+ actions, e.g. when a new account has been created a success message can be shown with instructions. These messages are
188
+ hard-coded in Nosto.
189
+ You do NOT need to use this JS API, but instead set up your own postMessage handler in your application.
190
+
191
+ ### Sending order confirmations using the Nosto API
192
+
193
+ Sending order confirmations to Nosto is a vital part of the functionality. Order confirmations should be sent when an
194
+ order has been completed in the shop. It is NOT recommended to do this when the "thank you" page is shown to the user,
195
+ as payment gateways work differently and you cannot rely on the user always being redirected back to the shop after a
196
+ payment has been made. Therefore, it is recommended to send the order conformation when the order is marked as payed
197
+ in the shop.
198
+
199
+ Order confirmations can be sent two different ways:
200
+
201
+ * matched orders; where we know the Nosto customer ID of the user who placed the order
202
+ * un-matched orders: where we do not know the Nosto customer ID of the user who placed the order
203
+
204
+ The Nosto customer ID is set in a cookie "2c.cId" by Nosto and it is up to the platform to keep a link between users
205
+ and the Nosto customer ID. It is recommended to tie the Nosto customer ID to the order or shopping cart instead of an
206
+ user ID, as the platform may support guest checkouts.
207
+
208
+ ```php
209
+ .....
210
+ try {
211
+ /**
212
+ * @var NostoOrderInterface $order
213
+ * @var NostoAccountInterface $account
214
+ * @var string $customerId
215
+ */
216
+ NostoOrderConfirmation::send($order, $account, $customerId);
217
+ } catch (NostoException $e) {
218
+ // handle error
219
+ }
220
+ .....
221
+ ```
222
+
223
+ ### Sending product re-crawl requests using the Nosto API
224
+
225
+ Note: this feature has been deprecated in favor of the create/update/delete method below.
226
+
227
+ When a product changes in the store, stock is reduced, price is updated etc. it is recommended to send an API request
228
+ to Nosto that initiates a product "re-crawl" event. This is done to update the recommendations including that product
229
+ so that the newest information can be shown to the users on the site.
230
+
231
+ Note: the $product model needs to include only `productId` and `url` properties, all others can be omitted.
232
+
233
+ ```php
234
+ .....
235
+ try {
236
+ /**
237
+ * @var NostoProductInterface $product
238
+ * @var NostoAccountInterface $account
239
+ */
240
+ NostoProductReCrawl::send($product, $account);
241
+ } catch (NostoException $e) {
242
+ // handle error
243
+ }
244
+ .....
245
+ ```
246
+
247
+ Batch re-crawling is also possible by creating a collection of product models:
248
+
249
+ ```php
250
+ .....
251
+ try {
252
+ /**
253
+ * @var NostoExportProductCollection $collection
254
+ * @var NostoProductInterface $product
255
+ * @var NostoAccountInterface $account
256
+ */
257
+ $collection[] = $product;
258
+ NostoProductReCrawl::sendBatch($collection, $account);
259
+ } catch (NostoException $e) {
260
+ // handle error
261
+ }
262
+ .....
263
+ ```
264
+
265
+ ### Sending product create/update/delete requests using the Nosto API
266
+
267
+ When a product changes in the store, stock is reduced, price is updated etc. it is recommended to send an API request
268
+ to Nosto to handle the updated product info. This is also true when adding new products as well as deleting existing ones.
269
+ This is done to update the recommendations including that product so that the newest information can be shown to the users
270
+ on the site.
271
+
272
+ Creating new products:
273
+
274
+ ```php
275
+ .....
276
+ try {
277
+ /**
278
+ * @var NostoProductInterface $product
279
+ * @var NostoAccountInterface $account
280
+ */
281
+ $op = new NostoOperationProduct($account);
282
+ $op->addProduct($product);
283
+ $op->create();
284
+ } catch (NostoException $e) {
285
+ // handle error
286
+ }
287
+ .....
288
+ ```
289
+
290
+ Note: you can call `addProduct` multiple times to add more products to the request. This way you can batch create products.
291
+
292
+ Updating existing products:
293
+
294
+ ```php
295
+ .....
296
+ try {
297
+ /**
298
+ * @var NostoProductInterface $product
299
+ * @var NostoAccountInterface $account
300
+ */
301
+ $op = new NostoOperationProduct($account);
302
+ $op->addProduct($product);
303
+ $op->update();
304
+ } catch (NostoException $e) {
305
+ // handle error
306
+ }
307
+ .....
308
+ ```
309
+
310
+ Note: you can call `addProduct` multiple times to add more products to the request. This way you can batch update products.
311
+
312
+ Deleting existing products:
313
+
314
+ ```php
315
+ .....
316
+ try {
317
+ /**
318
+ * @var NostoProductInterface $product
319
+ * @var NostoAccountInterface $account
320
+ */
321
+ $op = new NostoOperationProduct($account);
322
+ $op->addProduct($product);
323
+ $op->delete();
324
+ } catch (NostoException $e) {
325
+ // handle error
326
+ }
327
+ .....
328
+ ```
329
+
330
+ Note: you can call `addProduct` multiple times to add more products to the request. This way you can batch delete products.
331
+
332
+ ### Exporting encrypted product/order information that Nosto can request
333
+
334
+ When new Nosto accounts are created for a shop, Nosto will try to fetch historical data about products and orders.
335
+ This information is used to bootstrap recommendations and decreases the time needed to get accurate recommendations
336
+ showing in the shop.
337
+
338
+ For this to work, Nosto requires 2 public endpoints that can be called once a new account has been created through
339
+ the API. These endpoints should serve the history data encrypted with AES. The SDK comes bundled with the ability to
340
+ encrypt the data with a pure PHP solution (http://phpseclib.sourceforge.net/), It is recommended, but not required, to
341
+ have mcrypt installed on the server.
342
+
343
+ Additionally, the endpoints need to support the ability to limit the amount of products/orders to export and an offset
344
+ for fetching batches of data. These must be implemented as GET parameters "limit" and "offset" which will be sent as
345
+ integer values and expected to be applied to the data set being exported.
346
+
347
+ ```php
348
+ .....
349
+ /**
350
+ * @var NostoProductInterface[] $products
351
+ * @var NostoAccountInterface $account
352
+ */
353
+ $collection = new NostoExportProductCollection();
354
+ foreach ($products as $product) {
355
+ $collection[] = $product;
356
+ }
357
+ // The exported will encrypt the collection and output the result.
358
+ $cipher_text = NostoExporter::export($account, $collection);
359
+ echo $cipher_text;
360
+ // It is important to stop the script execution after the export, in order to avoid any additional data being outputted.
361
+ die();
362
+ ```
363
+
364
+ ```php
365
+ .....
366
+ /**
367
+ * @var NostoOrderInterface[] $orders
368
+ * @var NostoAccountInterface $account
369
+ */
370
+ $collection = new NostoExportOrderCollection();
371
+ foreach ($orders as $order) {
372
+ $collection[] = $order;
373
+ }
374
+ // The exported will encrypt the collection and output the result.
375
+ $cipher_text = NostoExporter::export($account, $collection);
376
+ echo $cipher_text;
377
+ // It is important to stop the script execution after the export, in order to avoid any additional data being outputted.
378
+ die();
379
+ ```
380
+
381
+ ## Testing
382
+
383
+ The SDK is unit tested with Codeception (http://codeception.com/).
384
+ API and OAuth2 requests are tested using api-mock server (https://www.npmjs.com/package/api-mock) running on Node.
385
+
386
+ ### Install Codeception & api-mock
387
+
388
+ First cd into the root directory.
389
+
390
+ Then install Codeception via composer:
391
+
392
+ ```bash
393
+ php composer.phar install
394
+ ```
395
+
396
+ And then install Node (http://nodejs.org/) and the npm package manager (https://www.npmjs.com/). After that you can install the api-mock server via npm:
397
+
398
+ ```bash
399
+ npm install -g api-mock
400
+ ```
401
+
402
+ ### Running tests
403
+
404
+ First cd into the root directory.
405
+
406
+ Then start the api-mock server with the API blueprint:
407
+
408
+ ```bash
409
+ api-mock tests/api-blueprint.md
410
+ ```
411
+
412
+ Then in another window run the tests:
413
+
414
+ ```bash
415
+ vendor/bin/codecept run
416
+ ```
package.xml CHANGED
@@ -1,18 +1,20 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Nosto_Tagging</name>
4
- <version>2.6.4</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Personalization for Magento</summary>
10
  <description>Increase your conversion rate and average order value by delivering your customers personalized product recommendations throughout their shopping journey.</description>
11
- <notes>* Fix redirect to admin store after OAuth is finished</notes>
 
 
12
  <authors><author><name>Nosto</name><user>nosto</user><email>magento@nosto.com</email></author></authors>
13
- <date>2015-09-07</date>
14
- <time>05:45:47</time>
15
- <contents><target name="magecommunity"><dir name="Nosto"><dir name="Tagging"><dir name="Block"><file name="Addtocart.php" hash="61d645e5cc9e54a5170ae03797f0426a"/><dir name="Adminhtml"><file name="Iframe.php" hash="d60e2221e6689b2ab2da9263897f6a6b"/><file name="Wizard.php" hash="f98931a1a6e327c0d05e43b73063f672"/></dir><file name="Cart.php" hash="c1c707dad76c98d807d5160b90c06ac6"/><file name="Category.php" hash="177a5d8d9f1ba9e246d149c228c3aec8"/><file name="Customer.php" hash="d35777ca399244e4fb441456320f8ad7"/><file name="Element.php" hash="e5b12bb118b92eb7075ff7a6e16e0eba"/><file name="Embed.php" hash="e8b628b6bc49fb865697552300d08526"/><file name="Meta.php" hash="1f761e731214b7572ef139d9e0577ea1"/><file name="Order.php" hash="32115070d380e654f093da11dd6e4d85"/><file name="Product.php" hash="ac7b85e6d03d94d03c59416b622b0039"/></dir><dir name="Helper"><file name="Account.php" hash="40eb62dd1718741b2064d4a440f46b2f"/><file name="Customer.php" hash="92da4f0ae6c2aef635b837635cd74238"/><file name="Data.php" hash="c50b9b5a694fdd0401748741de97f6ab"/><file name="Date.php" hash="1a108a4ec8169b73411b6c11b86a1d80"/><file name="Oauth.php" hash="9ecbea9e8411501cabe780e0511a1d05"/><file name="Price.php" hash="b7fe656fad1182f5c2166e95a4b29204"/><file name="Url.php" hash="289294904d246c56e4de96798927a7f6"/></dir><dir name="Model"><file name="Base.php" hash="c0b3c4a6f68a0b707bf85ceff50e3c4b"/><dir name="Container"><file name="Cart.php" hash="20d2936693361999575f1e6ac56f2e40"/><file name="Customer.php" hash="b03bca89845b6c939521add8df1f7e9a"/></dir><file name="Customer.php" hash="6d992a43a75f29836ed1450e130df560"/><dir name="Meta"><dir name="Account"><file name="Billing.php" hash="353e807f832a37591bf7359857044ca1"/><file name="Iframe.php" hash="731e2bf424e1b9d4e00d403f55a87b54"/><file name="Owner.php" hash="6fd3228a2758d06f78729c0250f7fd30"/></dir><file name="Account.php" hash="8df7024533e1cf657b407c49fb19a3c5"/><file name="Oauth.php" hash="0863271d8a3db0de93525f0e64308f1c"/><dir name="Order"><file name="Buyer.php" hash="3b8e57e195e214ed3324967684614745"/><file name="Item.php" hash="90dbec8a0201ccd8fbca3e10f2c362ad"/><file name="Status.php" hash="37520c32630aa4bf14d8213f39e80a4b"/></dir><file name="Order.php" hash="7844be08fe7f3f5310c18f352a952a5e"/><file name="Product.php" hash="40cf899477bac2f9916b030af8885e5e"/></dir><file name="Observer.php" hash="58bf4eb430d8a74fbb1ac5c68750c30c"/><file name="Product.php" hash="3630a2a688f013b296d1865e6f32e1d2"/><dir name="Resource"><dir name="Customer"><file name="Collection.php" hash="0c99b46d5108a6fe31cad1190d8ef47c"/></dir><file name="Customer.php" hash="f92e4e5334b5e3493e8f04664b20f105"/><dir name="Product"><file name="Collection.php" hash="7566a7f4483a00c923bac2fbea173abd"/></dir><file name="Setup.php" hash="506b08bcb10cbe74e95812c5f646429f"/></dir><dir name="System"><dir name="Config"><dir name="Source"><file name="Image.php" hash="d1fcc96aab01342627b96edf6fad1dd1"/></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="NostoController.php" hash="fd43fd322ea1f8d6b7db6d716250f71e"/></dir><file name="ExportController.php" hash="e20f49db51535241ed1857c2a86e077e"/><file name="OauthController.php" hash="19927d6e9648d0f381b1c2f1b9684339"/></dir><dir name="etc"><file name="adminhtml.xml" hash="7ea210738a77630f299a14b442f3b8d0"/><file name="cache.xml" hash="e874fbbf2eb89b325deb1a62afbcfb4d"/><file name="config.xml" hash="a9f989480972018def6d777d9f8a85ca"/><file name="system.xml" hash="7deba6544a09d7af8c07e6b5edc0975a"/></dir><dir name="sql"><dir name="tagging_setup"><file name="mysql4-install-1.2.0.php" hash="3cbc52834c44870859562eec7969b9fd"/><file name="mysql4-upgrade-1.1.7-1.2.0.php" hash="0d8eb7990461a9d4b74b479120067aeb"/></dir></dir></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="nostotagging"><file name="iframe.phtml" hash="c81931adf855a4893e0cf2dc60c390cb"/><file name="wizard.phtml" hash="3f92e94e5ee3d3cd9906a6b0942a3a1b"/></dir></dir><dir name="layout"><file name="nostotagging.xml" hash="29eb82c1a28e9c4d398239c765a3f5fd"/></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="template"><dir name="nostotagging"><file name="addtocart.phtml" hash="e8776f59ec4c7c7b6eea2fae9d56e2a4"/><file name="cart.phtml" hash="3c4e80cdcebf63c56dc14a6594e167b8"/><dir name="category"><file name="view.phtml" hash="fc09e2770ad6281ec96c8e92accc01f1"/></dir><file name="category.phtml" hash="81f36742cb4e3adbf9f692cc8f0ea8a8"/><file name="customer.phtml" hash="e9bc92bb7f7df989cc706c7eaee3614a"/><file name="element.phtml" hash="e1d38981789e632a3607ccf2e171f0cb"/><file name="embed.phtml" hash="9193ddd1ae373b68c53f8364f511b0a5"/><file name="meta.phtml" hash="c1b862a4993503fae420fb0eba194b00"/><file name="order.phtml" hash="e15b11ff1cb20aafaab0dc134ddd1a9b"/><file name="product.phtml" hash="9df3ff9f6e363e478d7fd68bd09827b1"/></dir></dir><dir name="layout"><file name="nostotagging.xml" hash="8adc3eecf5f90feffabee29d6308912b"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Nosto_Tagging.xml" hash="1705cb7e6db28388662ef52882d61701"/></dir></target><target name="magelib"><dir name="nosto"><dir name="php-sdk"><dir name="src"><dir name="classes"><file name="Nosto.php" hash="c595c5f646f6e513b6604848f8d10118"/><file name="NostoAccount.php" hash="c422a7f104c5ebc6eac9d83986280579"/><file name="NostoCipher.php" hash="a3d7d148311aa0319387c0918e5e26dc"/><file name="NostoDotEnv.php" hash="90b23c1b02095c9368372f1d5346c3f9"/><file name="NostoMessage.php" hash="5933eadd2ad08427010b0bf16657266b"/><file name="NostoObject.php" hash="187808f0c55d02294d41e9395f4c5d58"/><file name="NostoOrderConfirmation.php" hash="2a3fd5efb196bc0dcb7ad57fb61a81d0"/><file name="NostoProductReCrawl.php" hash="f370c539409dd74fc98b7aa1ea0f6387"/><file name="NostoValidator.php" hash="2096ddc9979ec77f8f0b1ec58b459329"/><dir name="api"><file name="NostoApiRequest.php" hash="37db50c0d35ff9a848afe6a4bb871b6f"/><file name="NostoApiToken.php" hash="2febdbec0fd3609162845aa678b36b38"/></dir><dir name="collection"><file name="NostoCollection.php" hash="35cea0282628354413f629062b58663a"/><file name="NostoOrderCollection.php" hash="db6f517948c1258e1521ceb1ce49d477"/><file name="NostoProductCollection.php" hash="3ae29dbbcb7effa234fa83461d3e6901"/></dir><dir name="exception"><file name="NostoException.php" hash="c4610fb70278d01bd85bc83b8e74df30"/><file name="NostoHttpException.php" hash="dabf4298746db898c8fe709ea9e25818"/></dir><dir name="export"><file name="NostoExportOrderCollection.php" hash="91bdd99a2be75be062fe5c7cde1803e4"/><file name="NostoExportProductCollection.php" hash="33986c8767922da98aa90f03759dbc0f"/><file name="NostoExporter.php" hash="72ad0af4bce8e57ada5a29e3634df94f"/></dir><dir name="helper"><file name="NostoHelper.php" hash="f4ca6c78e047fec93c32b7eb822b0692"/><file name="NostoHelperDate.php" hash="081a2d8bfec710f2baa52192a99a2658"/><file name="NostoHelperIframe.php" hash="303668f25103c245396b43811a15c28d"/><file name="NostoHelperPrice.php" hash="ee9f217dc1b8e0fc679fb56ca3fd3ca1"/></dir><dir name="http"><file name="NostoHttpRequest.php" hash="2c5b96a56d33b33e334d4d3772c2b14d"/><file name="NostoHttpRequestAdapter.php" hash="d8bf8e5db44ad982b655372529f4d88c"/><file name="NostoHttpRequestAdapterCurl.php" hash="945a4357578f91291d09f1e2850da580"/><file name="NostoHttpRequestAdapterSocket.php" hash="88f57e6d98de2ac652a235e2786f0be7"/><file name="NostoHttpResponse.php" hash="9982d3ed15bfcc8f0147b2b4cc400223"/></dir><dir name="oauth"><file name="NostoOAuthClient.php" hash="2672d9de80d23c91776d97b9803d01b4"/><file name="NostoOAuthToken.php" hash="f81c1c1e5c4fc2010162d8f69de2cb33"/></dir><dir name="operation"><file name="NostoOperationProduct.php" hash="37e24121df779ead75e028b15a3fe226"/></dir></dir><file name="config.inc.php" hash="b46a56f8d61ff2cf69cedb4281719e00"/><dir name="interfaces"><file name="NostoExportCollectionInterface.php" hash="63c833c17fe43ce48b45e0f552313a8b"/><file name="NostoOAuthClientMetaDataInterface.php" hash="75a82417bfc27cd82e41ea9810d31428"/><file name="NostoProductInterface.php" hash="64a9e110a97ebd8be690664ce5b8e371"/><file name="NostoValidatableInterface.php" hash="57ad8b28225344127843b97049f5868d"/><dir name="account"><file name="NostoAccountInterface.php" hash="063da0d25a5420f4e727938467c078f3"/><file name="NostoAccountMetaDataBillingDetailsInterface.php" hash="ef31f0c3b41591fd76f85a25021f0fe4"/><file name="NostoAccountMetaDataIframeInterface.php" hash="c739e6a3ef39a308df6852361f2fd1d7"/><file name="NostoAccountMetaDataInterface.php" hash="5adf0a5ed876348a05028a3ff111bf79"/><file name="NostoAccountMetaDataOwnerInterface.php" hash="4c037b8cb1b529c16c91f25cce2b0ed7"/></dir><dir name="order"><file name="NostoOrderBuyerInterface.php" hash="890884e45bcca61f96f0f98cda40b6c1"/><file name="NostoOrderInterface.php" hash="d341b4faf020bfca01336c5d60ca5e48"/><file name="NostoOrderPurchasedItemInterface.php" hash="e33b94290465eea80d614db35144b75a"/><file name="NostoOrderStatusInterface.php" hash="8c89cf4296629b635f52b53a9b800b1f"/></dir></dir><dir name="js"><file name="NostoIframe.min.js" hash="5fd9f5b418dd796c469aaa0c58b34aaa"/><dir name="src"><file name="NostoIframe.js" hash="ca03585215f846258d2dbb58724e48fa"/></dir></dir><dir name="libs"><dir name="phpseclib"><dir name="crypt"><file name="NostoCryptAES.php" hash="7f16ec6fa7eefa011a27acf4506a3b57"/><file name="NostoCryptBase.php" hash="c78432d428ad8a70be832f91261d4c42"/><file name="NostoCryptRandom.php" hash="eb21a56b9cf6e6ef0b220620dd4d0ebe"/><file name="NostoCryptRijndael.php" hash="9c728d06c0249f8bc24a7c61606ccc89"/></dir></dir></dir><file name=".env.example" hash="42aa9514d8b3f65e9df96b0df601f63c"/></dir></dir></dir></target><target name="mage"><dir name="js"><dir name="nosto"><file name="NostoAdmin.js" hash="0119fb333233f1f0fa70f4167fb014ef"/><file name="NostoIframe.min.js" hash="5fd9f5b418dd796c469aaa0c58b34aaa"/><file name="iframeresizer.min.js" hash="163b065c9dd702dc364913a951b81fee"/></dir></dir></target></contents>
16
  <compatible/>
17
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php><package><name>Mage_Core_Modules</name><channel>community</channel><min>1.6.0.0</min><max>1.9.2.1</max></package></required></dependencies>
18
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Nosto_Tagging</name>
4
+ <version>2.6.5</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL)</license>
7
  <channel>community</channel>
8
  <extends/>
9
  <summary>Personalization for Magento</summary>
10
  <description>Increase your conversion rate and average order value by delivering your customers personalized product recommendations throughout their shopping journey.</description>
11
+ <notes>* Add "external_order_ref" to order tagging and API requests in order to better&#xD;
12
+ track orders&#xD;
13
+ * Add "order_statuses" to the order export to be able to better check up on order funnels</notes>
14
  <authors><author><name>Nosto</name><user>nosto</user><email>magento@nosto.com</email></author></authors>
15
+ <date>2015-10-07</date>
16
+ <time>09:12:46</time>
17
+ <contents><target name="magecommunity"><dir name="Nosto"><dir name="Tagging"><dir name="Block"><file name="Addtocart.php" hash="61d645e5cc9e54a5170ae03797f0426a"/><dir name="Adminhtml"><file name="Iframe.php" hash="d60e2221e6689b2ab2da9263897f6a6b"/><file name="Wizard.php" hash="f98931a1a6e327c0d05e43b73063f672"/></dir><file name="Cart.php" hash="c1c707dad76c98d807d5160b90c06ac6"/><file name="Category.php" hash="177a5d8d9f1ba9e246d149c228c3aec8"/><file name="Customer.php" hash="d35777ca399244e4fb441456320f8ad7"/><file name="Element.php" hash="e5b12bb118b92eb7075ff7a6e16e0eba"/><file name="Embed.php" hash="e8b628b6bc49fb865697552300d08526"/><file name="Meta.php" hash="1f761e731214b7572ef139d9e0577ea1"/><file name="Order.php" hash="32115070d380e654f093da11dd6e4d85"/><file name="Product.php" hash="ac7b85e6d03d94d03c59416b622b0039"/></dir><dir name="Helper"><file name="Account.php" hash="40eb62dd1718741b2064d4a440f46b2f"/><file name="Customer.php" hash="92da4f0ae6c2aef635b837635cd74238"/><file name="Data.php" hash="c50b9b5a694fdd0401748741de97f6ab"/><file name="Date.php" hash="1a108a4ec8169b73411b6c11b86a1d80"/><file name="Oauth.php" hash="9ecbea9e8411501cabe780e0511a1d05"/><file name="Price.php" hash="b7fe656fad1182f5c2166e95a4b29204"/><file name="Url.php" hash="289294904d246c56e4de96798927a7f6"/></dir><dir name="Model"><file name="Base.php" hash="c0b3c4a6f68a0b707bf85ceff50e3c4b"/><dir name="Container"><file name="Cart.php" hash="20d2936693361999575f1e6ac56f2e40"/><file name="Customer.php" hash="b03bca89845b6c939521add8df1f7e9a"/></dir><file name="Customer.php" hash="6d992a43a75f29836ed1450e130df560"/><dir name="Export"><dir name="Collection"><file name="Order.php" hash="d30a5091514397ecf10d1bcac09dade9"/></dir></dir><dir name="Meta"><dir name="Account"><file name="Billing.php" hash="353e807f832a37591bf7359857044ca1"/><file name="Iframe.php" hash="731e2bf424e1b9d4e00d403f55a87b54"/><file name="Owner.php" hash="6fd3228a2758d06f78729c0250f7fd30"/></dir><file name="Account.php" hash="8df7024533e1cf657b407c49fb19a3c5"/><file name="Oauth.php" hash="0863271d8a3db0de93525f0e64308f1c"/><dir name="Order"><file name="Buyer.php" hash="fa231d30ae0d275ba820f05bfcd2f12e"/><file name="Item.php" hash="598aa5a2c713a59e9125976b48764488"/><file name="Status.php" hash="6d55ecde2ea856227b27b0f95c89a976"/></dir><file name="Order.php" hash="e8638ff582ac759a7e6d4c8d311e7d04"/><file name="Product.php" hash="40cf899477bac2f9916b030af8885e5e"/></dir><file name="Observer.php" hash="102b3e6d330e151a4595c54dc88a18a9"/><file name="Product.php" hash="3630a2a688f013b296d1865e6f32e1d2"/><dir name="Resource"><dir name="Customer"><file name="Collection.php" hash="0c99b46d5108a6fe31cad1190d8ef47c"/></dir><file name="Customer.php" hash="f92e4e5334b5e3493e8f04664b20f105"/><dir name="Product"><file name="Collection.php" hash="7566a7f4483a00c923bac2fbea173abd"/></dir><file name="Setup.php" hash="506b08bcb10cbe74e95812c5f646429f"/></dir><dir name="Service"><file name="Order.php" hash="6c6f74cc3d50101bbda13a21a19b5f3c"/></dir><dir name="System"><dir name="Config"><dir name="Source"><file name="Image.php" hash="d1fcc96aab01342627b96edf6fad1dd1"/></dir></dir></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="NostoController.php" hash="fd43fd322ea1f8d6b7db6d716250f71e"/></dir><file name="ExportController.php" hash="f22510a39256c0baca4fbcb9301c8ef5"/><file name="OauthController.php" hash="19927d6e9648d0f381b1c2f1b9684339"/></dir><dir name="etc"><file name="adminhtml.xml" hash="7ea210738a77630f299a14b442f3b8d0"/><file name="cache.xml" hash="e874fbbf2eb89b325deb1a62afbcfb4d"/><file name="config.xml" hash="56e427762b58625eb31ed2a9a07d2719"/><file name="system.xml" hash="7deba6544a09d7af8c07e6b5edc0975a"/></dir><dir name="sql"><dir name="tagging_setup"><file name="mysql4-install-1.2.0.php" hash="3cbc52834c44870859562eec7969b9fd"/><file name="mysql4-upgrade-1.1.7-1.2.0.php" hash="0d8eb7990461a9d4b74b479120067aeb"/></dir></dir></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="nostotagging"><file name="iframe.phtml" hash="c81931adf855a4893e0cf2dc60c390cb"/><file name="wizard.phtml" hash="3f92e94e5ee3d3cd9906a6b0942a3a1b"/></dir></dir><dir name="layout"><file name="nostotagging.xml" hash="29eb82c1a28e9c4d398239c765a3f5fd"/></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="template"><dir name="nostotagging"><file name="addtocart.phtml" hash="e8776f59ec4c7c7b6eea2fae9d56e2a4"/><file name="cart.phtml" hash="3c4e80cdcebf63c56dc14a6594e167b8"/><dir name="category"><file name="view.phtml" hash="fc09e2770ad6281ec96c8e92accc01f1"/></dir><file name="category.phtml" hash="81f36742cb4e3adbf9f692cc8f0ea8a8"/><file name="customer.phtml" hash="e9bc92bb7f7df989cc706c7eaee3614a"/><file name="element.phtml" hash="e1d38981789e632a3607ccf2e171f0cb"/><file name="embed.phtml" hash="9193ddd1ae373b68c53f8364f511b0a5"/><file name="meta.phtml" hash="c1b862a4993503fae420fb0eba194b00"/><file name="order.phtml" hash="c176f10c5cc5a1c53fa6ed973a637f6c"/><file name="product.phtml" hash="9df3ff9f6e363e478d7fd68bd09827b1"/></dir></dir><dir name="layout"><file name="nostotagging.xml" hash="8adc3eecf5f90feffabee29d6308912b"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Nosto_Tagging.xml" hash="1705cb7e6db28388662ef52882d61701"/></dir></target><target name="magelib"><dir name="nosto"><dir name="php-sdk"><file name="LICENSE.txt" hash="309b355442cb85601216abd7a41909d2"/><file name="README.md" hash="0c4860a3dfe7bca30f3eb56c146b4166"/><dir name="src"><dir name="classes"><file name="Nosto.php" hash="c595c5f646f6e513b6604848f8d10118"/><file name="NostoAccount.php" hash="c422a7f104c5ebc6eac9d83986280579"/><file name="NostoCipher.php" hash="a3d7d148311aa0319387c0918e5e26dc"/><file name="NostoDotEnv.php" hash="90b23c1b02095c9368372f1d5346c3f9"/><file name="NostoMessage.php" hash="5933eadd2ad08427010b0bf16657266b"/><file name="NostoObject.php" hash="187808f0c55d02294d41e9395f4c5d58"/><file name="NostoOrderConfirmation.php" hash="2a3fd5efb196bc0dcb7ad57fb61a81d0"/><file name="NostoProductReCrawl.php" hash="f370c539409dd74fc98b7aa1ea0f6387"/><file name="NostoValidator.php" hash="2096ddc9979ec77f8f0b1ec58b459329"/><dir name="api"><file name="NostoApiRequest.php" hash="37db50c0d35ff9a848afe6a4bb871b6f"/><file name="NostoApiToken.php" hash="2febdbec0fd3609162845aa678b36b38"/></dir><dir name="collection"><file name="NostoCollection.php" hash="35cea0282628354413f629062b58663a"/><file name="NostoOrderCollection.php" hash="db6f517948c1258e1521ceb1ce49d477"/><file name="NostoProductCollection.php" hash="3ae29dbbcb7effa234fa83461d3e6901"/></dir><dir name="exception"><file name="NostoException.php" hash="c4610fb70278d01bd85bc83b8e74df30"/><file name="NostoHttpException.php" hash="dabf4298746db898c8fe709ea9e25818"/></dir><dir name="export"><file name="NostoExportOrderCollection.php" hash="91bdd99a2be75be062fe5c7cde1803e4"/><file name="NostoExportProductCollection.php" hash="33986c8767922da98aa90f03759dbc0f"/><file name="NostoExporter.php" hash="72ad0af4bce8e57ada5a29e3634df94f"/></dir><dir name="helper"><file name="NostoHelper.php" hash="f4ca6c78e047fec93c32b7eb822b0692"/><file name="NostoHelperDate.php" hash="081a2d8bfec710f2baa52192a99a2658"/><file name="NostoHelperIframe.php" hash="303668f25103c245396b43811a15c28d"/><file name="NostoHelperPrice.php" hash="ee9f217dc1b8e0fc679fb56ca3fd3ca1"/></dir><dir name="http"><file name="NostoHttpRequest.php" hash="2c5b96a56d33b33e334d4d3772c2b14d"/><file name="NostoHttpRequestAdapter.php" hash="d8bf8e5db44ad982b655372529f4d88c"/><file name="NostoHttpRequestAdapterCurl.php" hash="945a4357578f91291d09f1e2850da580"/><file name="NostoHttpRequestAdapterSocket.php" hash="88f57e6d98de2ac652a235e2786f0be7"/><file name="NostoHttpResponse.php" hash="9982d3ed15bfcc8f0147b2b4cc400223"/></dir><dir name="oauth"><file name="NostoOAuthClient.php" hash="2672d9de80d23c91776d97b9803d01b4"/><file name="NostoOAuthToken.php" hash="f81c1c1e5c4fc2010162d8f69de2cb33"/></dir><dir name="operation"><file name="NostoOperationProduct.php" hash="37e24121df779ead75e028b15a3fe226"/></dir></dir><file name="config.inc.php" hash="b46a56f8d61ff2cf69cedb4281719e00"/><dir name="interfaces"><file name="NostoExportCollectionInterface.php" hash="63c833c17fe43ce48b45e0f552313a8b"/><file name="NostoOAuthClientMetaDataInterface.php" hash="75a82417bfc27cd82e41ea9810d31428"/><file name="NostoProductInterface.php" hash="64a9e110a97ebd8be690664ce5b8e371"/><file name="NostoValidatableInterface.php" hash="57ad8b28225344127843b97049f5868d"/><dir name="account"><file name="NostoAccountInterface.php" hash="063da0d25a5420f4e727938467c078f3"/><file name="NostoAccountMetaDataBillingDetailsInterface.php" hash="ef31f0c3b41591fd76f85a25021f0fe4"/><file name="NostoAccountMetaDataIframeInterface.php" hash="c739e6a3ef39a308df6852361f2fd1d7"/><file name="NostoAccountMetaDataInterface.php" hash="5adf0a5ed876348a05028a3ff111bf79"/><file name="NostoAccountMetaDataOwnerInterface.php" hash="4c037b8cb1b529c16c91f25cce2b0ed7"/></dir><dir name="order"><file name="NostoOrderBuyerInterface.php" hash="890884e45bcca61f96f0f98cda40b6c1"/><file name="NostoOrderInterface.php" hash="d341b4faf020bfca01336c5d60ca5e48"/><file name="NostoOrderPurchasedItemInterface.php" hash="e33b94290465eea80d614db35144b75a"/><file name="NostoOrderStatusInterface.php" hash="8c89cf4296629b635f52b53a9b800b1f"/></dir></dir><dir name="js"><file name="NostoIframe.min.js" hash="5fd9f5b418dd796c469aaa0c58b34aaa"/><dir name="src"><file name="NostoIframe.js" hash="ca03585215f846258d2dbb58724e48fa"/></dir></dir><dir name="libs"><dir name="phpseclib"><dir name="crypt"><file name="NostoCryptAES.php" hash="7f16ec6fa7eefa011a27acf4506a3b57"/><file name="NostoCryptBase.php" hash="c78432d428ad8a70be832f91261d4c42"/><file name="NostoCryptRandom.php" hash="eb21a56b9cf6e6ef0b220620dd4d0ebe"/><file name="NostoCryptRijndael.php" hash="9c728d06c0249f8bc24a7c61606ccc89"/></dir></dir></dir><file name=".env.example" hash="42aa9514d8b3f65e9df96b0df601f63c"/></dir></dir></dir></target><target name="mage"><dir name="js"><dir name="nosto"><file name="NostoAdmin.js" hash="0119fb333233f1f0fa70f4167fb014ef"/><file name="NostoIframe.min.js" hash="5fd9f5b418dd796c469aaa0c58b34aaa"/><file name="iframeresizer.min.js" hash="163b065c9dd702dc364913a951b81fee"/></dir></dir></target></contents>
18
  <compatible/>
19
  <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php><package><name>Mage_Core_Modules</name><channel>community</channel><min>1.6.0.0</min><max>1.9.2.1</max></package></required></dependencies>
20
  </package>