seg - Version 1.0.0.0

Version Notes

First stable release.

Download this release

Release Info

Developer Seg
Extension seg
Version 1.0.0.0
Comparing to
See all releases


Version 1.0.0.0

Files changed (37) hide show
  1. app/code/community/Koan/.DS_Store +0 -0
  2. app/code/community/Koan/Seg/Block/Adminhtml/Exporter.php +95 -0
  3. app/code/community/Koan/Seg/Block/Adminhtml/Exporter/Grid.php +88 -0
  4. app/code/community/Koan/Seg/Block/Adminhtml/Exporter/Grid/Renderer/Status.php +25 -0
  5. app/code/community/Koan/Seg/Block/Header.php +21 -0
  6. app/code/community/Koan/Seg/Block/Track.php +284 -0
  7. app/code/community/Koan/Seg/Helper/Data.php +179 -0
  8. app/code/community/Koan/Seg/Model/Batch/Status.php +145 -0
  9. app/code/community/Koan/Seg/Model/Exception/Handler.php +26 -0
  10. app/code/community/Koan/Seg/Model/Logger.php +13 -0
  11. app/code/community/Koan/Seg/Model/Observer.php +83 -0
  12. app/code/community/Koan/Seg/Model/Resource/Batch/Status.php +9 -0
  13. app/code/community/Koan/Seg/Model/Resource/Batch/Status/Collection.php +10 -0
  14. app/code/community/Koan/Seg/Model/Seg/Basket.php +87 -0
  15. app/code/community/Koan/Seg/Model/Seg/Client.php +239 -0
  16. app/code/community/Koan/Seg/Model/Seg/Customer.php +71 -0
  17. app/code/community/Koan/Seg/Model/Seg/Exporter.php +384 -0
  18. app/code/community/Koan/Seg/Model/Seg/Order.php +78 -0
  19. app/code/community/Koan/Seg/Model/Seg/Order/Line.php +125 -0
  20. app/code/community/Koan/Seg/Model/Seg/Product.php +69 -0
  21. app/code/community/Koan/Seg/Model/Seg/Quote/Line.php +139 -0
  22. app/code/community/Koan/Seg/Model/Seg/Range.php +45 -0
  23. app/code/community/Koan/Seg/controllers/Adminhtml/SegController.php +123 -0
  24. app/code/community/Koan/Seg/controllers/IndexController.php +11 -0
  25. app/code/community/Koan/Seg/etc/adminhtml.xml +47 -0
  26. app/code/community/Koan/Seg/etc/config.xml +133 -0
  27. app/code/community/Koan/Seg/etc/system.xml +104 -0
  28. app/code/community/Koan/Seg/lib/Rollbar.php +986 -0
  29. app/code/community/Koan/Seg/sql/koan_seg_setup/install-1.0.0.0.php +14 -0
  30. app/code/community/Koan/Seg/sql/koan_seg_setup/upgrade-1.0.0.0-1.0.0.1.php +35 -0
  31. app/code/community/Koan/Seg/sql/koan_seg_setup/upgrade-1.0.0.1-1.0.0.2.php +16 -0
  32. app/design/adminhtml/default/default/layout/seg.xml +8 -0
  33. app/design/frontend/base/default/layout/seg.xml +12 -0
  34. app/design/frontend/base/default/template/seg/config.phtml +9 -0
  35. app/design/frontend/base/default/template/seg/tracking.phtml +23 -0
  36. app/etc/modules/Koan_Seg.xml +8 -0
  37. package.xml +41 -0
app/code/community/Koan/.DS_Store ADDED
Binary file
app/code/community/Koan/Seg/Block/Adminhtml/Exporter.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Block_Adminhtml_Exporter extends Mage_Adminhtml_Block_Widget_Grid_Container
4
+ {
5
+
6
+ private function _getOrderBatchUrl()
7
+ {
8
+ $orderBatchUrl = Mage::getModel('adminhtml/url')->getUrl(
9
+ 'adminhtml/seg/createBatchOrder',
10
+ array('order_date_from' => 'NO_FILTER')
11
+ );
12
+
13
+ return $orderBatchUrl;
14
+ }
15
+
16
+ public function __construct()
17
+ {
18
+ $this->_controller = 'adminhtml_exporter';
19
+ $this->_blockGroup = 'koan_seg';
20
+ $this->_headerText = Mage::helper('koan_seg')->__('Seg Exporter');
21
+ $this->_addButtonLabel = Mage::helper('koan_seg')->__('Export');
22
+ parent::__construct();
23
+ $this->_removeButton('add');
24
+
25
+ $customerUrl = Mage::getModel('adminhtml/url')->getUrl(
26
+ 'adminhtml/seg/createBatchCustomer',
27
+ null
28
+ );
29
+
30
+
31
+ $this->addButton(
32
+ 'start_order_batch',
33
+ array(
34
+ 'label' => Mage::helper('koan_seg')->__('Start New Orders Export Batch'),
35
+ 'onclick' => 'getOrderBatchUrl()',
36
+ 'class' => 'add',
37
+ 'before_html' => $this->_getDateSelectorHtml()
38
+ )
39
+ );
40
+
41
+ $this->addButton(
42
+ 'start_customer_batch',
43
+ array(
44
+ 'label' => Mage::helper('koan_seg')->__('Start New Customers Export Batch'),
45
+ 'onclick' => 'setLocation(\'' . $customerUrl . '\')',
46
+ 'class' => 'add',
47
+ 'style' => 'margin-right:20px;'
48
+ )
49
+ );
50
+ }
51
+
52
+ private function _getDateSelectorHtml()
53
+ {
54
+ $element = new Varien_Data_Form_Element_Date(
55
+ array(
56
+ 'name' => 'order_date_filter',
57
+ 'label' => Mage::helper('koan_seg')->__('Date'),
58
+ 'tabindex' => 1,
59
+ 'image' => $this->getSkinUrl('images/grid-cal.gif'),
60
+ 'format' => Varien_Date::DATE_INTERNAL_FORMAT,
61
+ 'value' => null
62
+ )
63
+ );
64
+
65
+ $element->setForm(new Varien_Data_Form());
66
+ $element->setId('order_date_filter');
67
+
68
+ return '<label for="order_date_filter">Filter orders from date: </label>' . $element->getElementHtml();
69
+ }
70
+
71
+ private function _getDateFilterJs()
72
+ {
73
+ $js = '<script>';
74
+ $js .= 'function getOrderBatchUrl(){';
75
+ $js .= 'var url=\'' . $this->_getOrderBatchUrl() . '\';';
76
+ $js .= 'var dtFilter = $("order_date_filter").getValue();';
77
+ $js .= 'if(dtFilter){url = url.sub("NO_FILTER", JSON.stringify({"date":dtFilter}));}';
78
+ //$js .= 'if(dtFilter){url = url.sub("NO_FILTER", dtFilter);}';
79
+ $js .= 'setLocation(url);';
80
+ $js .= '}';
81
+ $js .= '</script>';
82
+
83
+ return $js;
84
+ }
85
+
86
+ protected function _toHtml()
87
+ {
88
+ $html = parent::_toHtml();
89
+ $html .= $this->_getDateFilterJs();
90
+
91
+ return $html;
92
+ }
93
+
94
+
95
+ }
app/code/community/Koan/Seg/Block/Adminhtml/Exporter/Grid.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Block_Adminhtml_Exporter_Grid extends Mage_Adminhtml_Block_Widget_Grid
4
+ {
5
+
6
+ public function __construct()
7
+ {
8
+ parent::__construct();
9
+
10
+ $this->setId('segExporterGrid');
11
+ $this->setDefaultSort('id');
12
+ $this->setDefaultDir('ASC');
13
+ $this->setSaveParametersInSession(true);
14
+ }
15
+
16
+ protected function _prepareCollection()
17
+ {
18
+ $collection = Mage::getResourceModel('koan_seg/batch_status_collection');
19
+ $this->setCollection($collection);
20
+ return parent::_prepareCollection();
21
+ }
22
+
23
+ protected function _prepareColumns()
24
+ {
25
+ $this->addColumn('id', array(
26
+ 'header' => Mage::helper('koan_seg')->__('Id'),
27
+ 'align' => 'right',
28
+ 'width' => '40px',
29
+ 'index' => 'id',
30
+ ));
31
+
32
+ $this->addColumn('entity_type', array(
33
+ 'header' => Mage::helper('koan_seg')->__('Entity Type'),
34
+ 'align' => 'left',
35
+ 'index' => 'entity_type',
36
+ ));
37
+
38
+ $this->addColumn('start_time', array(
39
+ 'header' => Mage::helper('koan_seg')->__('Start Time'),
40
+ 'align' => 'left',
41
+ 'index' => 'start_time',
42
+ ));
43
+
44
+ $this->addColumn('end_time', array(
45
+ 'header' => Mage::helper('koan_seg')->__('End Time'),
46
+ 'align' => 'left',
47
+ 'index' => 'end_time',
48
+ ));
49
+ $this->addColumn('total_row_count', array(
50
+ 'header' => Mage::helper('koan_seg')->__('Total Items Count'),
51
+ 'align' => 'left',
52
+ 'index' => 'total_row_count',
53
+ ));
54
+ $this->addColumn('num_rows_processed', array(
55
+ 'header' => Mage::helper('koan_seg')->__('Items Processed'),
56
+ 'align' => 'left',
57
+ 'index' => 'num_rows_processed',
58
+ ));
59
+
60
+ $this->addColumn('current_status', array(
61
+ 'header' => Mage::helper('koan_seg')->__('Current Status'),
62
+ 'align' => 'left',
63
+ 'index' => 'current_status',
64
+ 'renderer' => 'koan_seg/adminhtml_exporter_grid_renderer_status',
65
+ ));
66
+ $this->addColumn('comment', array(
67
+ 'header' => Mage::helper('koan_seg')->__('Comment'),
68
+ 'align' => 'left',
69
+ 'index' => 'comment',
70
+ ));
71
+
72
+ return parent::_prepareColumns();
73
+ }
74
+
75
+ protected function _prepareMassaction()
76
+ {
77
+ $this->setMassactionIdField('id');
78
+ $this->getMassactionBlock()->setFormFieldName('batch_id');
79
+
80
+ $this->getMassactionBlock()->addItem('delete', array(
81
+ 'label' => Mage::helper('koan_seg')->__('Delete'),
82
+ 'url' => $this->getUrl('*/*/massDelete'),
83
+ 'confirm' => Mage::helper('koan_seg')->__('Are you sure?')
84
+ ));
85
+
86
+ return $this;
87
+ }
88
+ }
app/code/community/Koan/Seg/Block/Adminhtml/Exporter/Grid/Renderer/Status.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Block_Adminhtml_Exporter_Grid_Renderer_Status extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
4
+ {
5
+
6
+ private function getStatusText($status)
7
+ {
8
+ $statuses = array(
9
+ Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_NOT_STARTED => 'Export not started yet',
10
+ Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_STARTING => 'Export starting in progress',
11
+ Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_PROCESSING_ROWS => 'Processing rows',
12
+ Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_COMPLETE => 'Export completed',
13
+ Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_ERROR => 'Error'
14
+ );
15
+
16
+ return isset($statuses[$status]) ? $statuses[$status] : $status;
17
+ }
18
+
19
+ public function render(Varien_Object $row)
20
+ {
21
+ $value = $row->getData($this->getColumn()->getIndex());
22
+ return $this->getStatusText($value);
23
+
24
+ }
25
+ }
app/code/community/Koan/Seg/Block/Header.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+ class Koan_Seg_Block_Header extends Mage_Core_Block_Template
11
+ {
12
+ protected function _beforeToHtml()
13
+ {
14
+ $websiteId = $this->helper('koan_seg')->getWebsiteId();
15
+ if (empty($websiteId)) {
16
+ $this->_template = null;
17
+ }
18
+ return $this;
19
+ }
20
+
21
+ }
app/code/community/Koan/Seg/Block/Track.php ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+ class Koan_Seg_Block_Track extends Mage_Core_Block_Template
11
+ {
12
+ const PAGE_TYPE_PRODUCT_VIEW = 1;
13
+ const PAGE_TYPE_CATEGORY = 2;
14
+ const PAGE_TYPE_DEFAULT = 3;
15
+ const PAGE_TYPE_CHECKOUT_SUCCESS = 4;
16
+
17
+ public function getCustomerData()
18
+ {
19
+ if (!$customer = $this->getCustomer()) {
20
+ return null;
21
+ }
22
+
23
+ if (!$email = $customer->getEmail()) {
24
+ return null;
25
+ }
26
+
27
+ $result = array('Email' => $email);
28
+
29
+ $id = $customer->getId();
30
+ if (!empty($id)) {
31
+ $result['Id'] = $id;
32
+ }
33
+
34
+ $title = $customer->getPrefix();
35
+ if (!empty($title)) {
36
+ $result['Title'] = $title;
37
+ }
38
+
39
+ $firstname = $customer->getFirstname();
40
+ if (!empty($firstname)) {
41
+ $result['FirstName'] = $firstname;
42
+ }
43
+
44
+ $lastname = $customer->getLastname();
45
+ if (!empty($lastname)) {
46
+ $result['LastName'] = $lastname;
47
+ }
48
+
49
+ $address = $customer->getPrimaryBillingAddress();
50
+ if (!$address) {
51
+ $address = $customer->getPrimaryShippingAddress();
52
+ }
53
+
54
+ $country = null;
55
+ if ($address AND $address->getId()) {
56
+ $country = $address->getCountry();
57
+ }
58
+
59
+ if (!empty($country)) {
60
+ $result['CountryCode'] = $country;
61
+ }
62
+
63
+ $gender = $this->_getAttributeText($customer, 'gender');
64
+ if (!empty($gender)) {
65
+ $result['Gender'] = $gender;
66
+ }
67
+
68
+ if ($result AND is_array($result) and count($result)) {
69
+ return json_encode($result);
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ public function getCustomerDob()
76
+ {
77
+ if (!$customer = $this->getCustomer()) {
78
+ return null;
79
+ }
80
+
81
+ $dob = $customer->getDob();
82
+ if (!empty($dob)) {
83
+ return date('Y-m-d', strtotime($dob));
84
+ }
85
+
86
+ return null;
87
+ }
88
+
89
+ public function getPageData()
90
+ {
91
+ $result = array('type' => self::PAGE_TYPE_DEFAULT);
92
+
93
+ $request = $moduleName = Mage::app()->getRequest();
94
+ if (!$request) {
95
+ return $result;
96
+ }
97
+
98
+ $moduleName = $request->getModuleName();
99
+ $controllerName = $request->getControllerName();
100
+ $actionName = $request->getActionName();
101
+
102
+ $product = Mage::registry('current_product');
103
+ $category = Mage::registry('current_category');
104
+
105
+ if ($moduleName == 'catalog' AND $controllerName == 'product' AND ($product AND $product->getId())) {
106
+
107
+ $result['type'] = self::PAGE_TYPE_PRODUCT_VIEW;
108
+ $result['data'] = $product;
109
+ return $result;
110
+ }
111
+
112
+ if ($moduleName == 'catalog' AND $controllerName == 'category' AND ($category && $category->getId())) {
113
+ $result['type'] = self::PAGE_TYPE_CATEGORY;
114
+ $result['data'] = $category;
115
+ return $result;
116
+ }
117
+
118
+ if ($moduleName == 'checkout' AND $controllerName == 'onepage' AND $actionName == 'success') {
119
+ $result['type'] = self::PAGE_TYPE_CHECKOUT_SUCCESS;
120
+ return $result;
121
+ }
122
+
123
+ return $result;
124
+ }
125
+
126
+ protected function _beforeToHtml()
127
+ {
128
+ $websiteId = $this->helper('koan_seg')->getWebsiteId();
129
+ if (empty($websiteId)) {
130
+ $this->_template = null;
131
+ return $this;
132
+ }
133
+
134
+ if ($pageData = $this->getPageData()) {
135
+ if (isset($pageData['type']) AND $pageData['type'] == self::PAGE_TYPE_CHECKOUT_SUCCESS) {
136
+ $this->_prepareLastOrder();
137
+ }
138
+ }
139
+
140
+ return parent::_beforeToHtml();
141
+ }
142
+
143
+ protected function _prepareLastOrder()
144
+ {
145
+ $orderId = Mage::getSingleton('checkout/session')->getLastOrderId();
146
+ if ($orderId) {
147
+ $order = Mage::getModel('sales/order')->load($orderId);
148
+ if ($order->getId()) {
149
+ $this->addData(array(
150
+ 'order_id' => $order->getId(),
151
+ ));
152
+ }
153
+ }
154
+ }
155
+
156
+ public function getTrackingEventCode()
157
+ {
158
+ try {
159
+ //Check if we have addToCart event in session
160
+ //and return add to cart event
161
+ $quoteItemId = Mage::getSingleton('customer/session')->getItemAddedToCart();
162
+
163
+ if ($quoteItemId) {
164
+ Mage::getSingleton('customer/session')->unsItemAddedToCart();
165
+ return $this->_getAddToCartEventCode($quoteItemId);
166
+ }
167
+
168
+ $pageData = $this->getPageData();
169
+ $pageType = isset($pageData['type']) ? $pageData['type'] : self::PAGE_TYPE_DEFAULT;
170
+
171
+ switch ($pageType) {
172
+ case self::PAGE_TYPE_PRODUCT_VIEW:
173
+
174
+ $product = isset($pageData['data']) ? $pageData['data'] : null;
175
+
176
+ if (!$product) {
177
+ Mage::throwException($this->_getHelper()->__('Product doesn\'t exists!'));
178
+ }
179
+
180
+ return $this->_getProductViewEventCode($product);
181
+ break;
182
+
183
+ case self::PAGE_TYPE_CATEGORY:
184
+
185
+ $category = isset($pageData['data']) ? $pageData['data'] : null;
186
+
187
+ if (!$category) {
188
+ Mage::throwException($this->_getHelper()->__('Category doesn\'t exists!'));
189
+ }
190
+
191
+ return $this->_getRangeViewEventCode($category);
192
+ break;
193
+
194
+ case self::PAGE_TYPE_CHECKOUT_SUCCESS:
195
+
196
+ $orderId = $this->getOrderId();
197
+ $order = Mage::getModel('sales/order')->load($orderId);
198
+
199
+ if (!$order OR !$order->getId()) {
200
+ Mage::throwException($this->_getHelper()->__('Order doesn\'t exists!'));
201
+ }
202
+
203
+ return $this->_getOrderSuccessEventCode($order);
204
+ break;
205
+
206
+ default:
207
+ return array('event' => 'PageView', 'data' => null);
208
+ break;
209
+ }
210
+
211
+ } Catch (Exception $e) {
212
+ Mage::getSingleton('koan_seg/exception_handler')->handle('Magento: error in frontend tracking - getTrackingEventCode', $e);
213
+ }
214
+
215
+ return null;
216
+ }
217
+
218
+ private function _getAddToCartEventCode($itemId)
219
+ {
220
+ $cartData = Mage::getModel('koan_seg/seg_basket')->prepare($itemId);
221
+ $result = array(
222
+ 'event' => 'AddedToBasket',
223
+ 'data' => json_encode($cartData)
224
+ );
225
+
226
+ return $result;
227
+ }
228
+
229
+ private function _getRangeViewEventCode($category)
230
+ {
231
+ $categoryData = Mage::getModel('koan_seg/seg_range')->prepare($category);
232
+
233
+ $result = array(
234
+ 'event' => 'RangeView',
235
+ 'data' => json_encode($categoryData)
236
+ );
237
+
238
+ return $result;
239
+ }
240
+
241
+ private function _getProductViewEventCode($product)
242
+ {
243
+ $productData = Mage::getModel('koan_seg/seg_product')->prepare($product);
244
+
245
+ $result = array(
246
+ 'event' => 'ProductView',
247
+ 'data' => json_encode($productData)
248
+ );
249
+
250
+ return $result;
251
+ }
252
+
253
+ private function _getOrderSuccessEventCode($order)
254
+ {
255
+ $orderData = Mage::getModel('koan_seg/seg_order')->prepare($order);
256
+
257
+ $result = array(
258
+ 'event' => 'OrderPlaced',
259
+ 'data' => json_encode($orderData)
260
+ );
261
+
262
+ return $result;
263
+ }
264
+
265
+
266
+ private function getCustomer()
267
+ {
268
+ return Mage::getSingleton('customer/session')->getCustomer();
269
+ }
270
+
271
+ private function _getAttributeText($customer, $attribute)
272
+ {
273
+ return $customer->getResource()
274
+ ->getAttribute($attribute)
275
+ ->getFrontend()
276
+ ->getValue($customer);
277
+ }
278
+
279
+ private function _getHelper()
280
+ {
281
+ return Mage::helper('koan_seg');
282
+ }
283
+
284
+ }
app/code/community/Koan/Seg/Helper/Data.php ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Helper_Data extends Mage_Core_Helper_Abstract
4
+ {
5
+ const SEG_WEBSITE_ID_PATH = 'koan_seg/general/seg_website_id';
6
+
7
+ const SEG_ORDER_HISTORY_ENDPOINT_URL_PATH = 'koan_seg/endpoint/order_history';
8
+ const SEG_UPDATE_CUSTOMERS_ENDPOINT_URL_PATH = 'koan_seg/endpoint/update_customers';
9
+ const SEG_ORDER_PLACED_ENDPOINT_URL_PATH = 'koan_seg/endpoint/order_placed';
10
+
11
+ const SEG_ORDERS_EXPORT_BATCH_SIZE_PATH = 'koan_seg/general/orders_export_batch_size';
12
+ const SEG_CUSTOMERS_EXPORT_BATCH_SIZE_PATH = 'koan_seg/general/customers_export_batch_size';
13
+
14
+ const SEG_PRODUCT_BRAND_ATTRIBUTE_CODE_PATH = 'koan_seg/general/brand_attr_code';
15
+ const SEG_ROLLBAR_LOG_REQUEST_INFO = 'koan_seg/general/rollbar_report_params';
16
+
17
+ const SEG_EXPORT_CRON_ENABLED = 'koan_seg/general/export_cron_enable';
18
+
19
+ const SEG_EXPORTER_PHP_MEMORY_LIMIT = 'koan_seg/advanced/php_memory_limit';
20
+
21
+ public function getExporterPhpMemoryLimit()
22
+ {
23
+ return Mage::getStoreConfig(self::SEG_EXPORTER_PHP_MEMORY_LIMIT, null);
24
+ }
25
+
26
+ public function isExportCronEnabled()
27
+ {
28
+ return Mage::getStoreConfigFlag(self::SEG_EXPORT_CRON_ENABLED, null);
29
+ }
30
+
31
+ public function getWebsiteId($storeId = null)
32
+ {
33
+ return Mage::getStoreConfig(self::SEG_WEBSITE_ID_PATH, $storeId);
34
+ }
35
+
36
+ public function getOrderHistoryUrl()
37
+ {
38
+ return Mage::getStoreConfig(self::SEG_ORDER_HISTORY_ENDPOINT_URL_PATH);
39
+ }
40
+
41
+ public function getUpdateCustomersUrl()
42
+ {
43
+ return Mage::getStoreConfig(self::SEG_UPDATE_CUSTOMERS_ENDPOINT_URL_PATH);
44
+ }
45
+
46
+ public function getOrderPlacedUrl()
47
+ {
48
+ return Mage::getStoreConfig(self::SEG_ORDER_PLACED_ENDPOINT_URL_PATH);
49
+ }
50
+
51
+ public function getOrdersExportBatchSize()
52
+ {
53
+ return Mage::getStoreConfig(self::SEG_ORDERS_EXPORT_BATCH_SIZE_PATH);
54
+ }
55
+
56
+ public function getCustomersExportBatchSize()
57
+ {
58
+ return Mage::getStoreConfig(self::SEG_CUSTOMERS_EXPORT_BATCH_SIZE_PATH);
59
+ }
60
+
61
+ public function getBrandAttributeCode($storeId = null)
62
+ {
63
+ return Mage::getStoreConfig(self::SEG_PRODUCT_BRAND_ATTRIBUTE_CODE_PATH, $storeId);
64
+ }
65
+
66
+ public function logRequestInfo($storeId = null)
67
+ {
68
+ return Mage::getStoreConfigFlag(self::SEG_ROLLBAR_LOG_REQUEST_INFO, $storeId);
69
+ }
70
+
71
+ public function includeRollBar()
72
+ {
73
+ if (strpos(get_include_path(), Mage::getModuleDir(null, 'Koan_Seg')) == false) {
74
+ set_include_path(get_include_path() . PATH_SEPARATOR . Mage::getModuleDir(null, 'Koan_Seg') . DS . 'lib');
75
+ }
76
+
77
+ require_once('Rollbar.php');
78
+ }
79
+
80
+ public function initRollbar()
81
+ {
82
+ $this->includeRollBar();
83
+
84
+ if (!Rollbar::$instance) {
85
+ Rollbar::init(array(
86
+
87
+ 'access_token' => '39591143a2524b08b29a18a653897f95',
88
+ 'environment' => Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB),
89
+ 'root' => Mage::getBaseDir(),
90
+ 'batched' => 0,
91
+
92
+ ));
93
+ }
94
+ }
95
+
96
+ public function getProductBrands($product, $storeId)
97
+ {
98
+ $brandAttributeCode = $this->getBrandAttributeCode($storeId);
99
+ $brandAttributeType = null;
100
+
101
+ if ($brandAttributeCode) {
102
+ $brandAttributeType = $this->_getBrandAttributeType($brandAttributeCode);
103
+ }
104
+
105
+ //Try to determine product brand
106
+ if (is_null($brandAttributeType)) {
107
+ $brandAttributeType = 'text';
108
+ }
109
+
110
+ $getBrand = 'get' . $this->_camelize($brandAttributeCode);
111
+
112
+ if ($brandAttributeType == 'select') {
113
+
114
+ $resultBrand = null;
115
+ $brandValue = $product->$getBrand();
116
+
117
+ if (!empty($brandValue)) {
118
+ $product->setStoreId($storeId)
119
+ ->setData(
120
+ $brandAttributeCode,
121
+ $brandValue
122
+ );
123
+ $resultBrand = $product->getAttributeText($brandAttributeCode);
124
+ }
125
+
126
+ } else if ($brandAttributeType == 'multiselect') {
127
+
128
+ $productBrandIds = null;
129
+ $resultBrand = array();
130
+
131
+ $brandValueList = $product->$getBrand();
132
+ if ($brandValueList) {
133
+ $productBrandValsArray = explode(',', $brandValueList);
134
+ if ($productBrandValsArray AND is_array($productBrandValsArray) AND count($productBrandValsArray)) {
135
+ foreach ($productBrandValsArray as $brandValue) {
136
+ if (!empty($brandValue)) {
137
+ $product->setStoreId($storeId)
138
+ ->setData(
139
+ $brandAttributeCode,
140
+ $brandValue
141
+ );
142
+ $resultBrand[] = $product->getAttributeText($brandAttributeCode);
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ } else {
149
+ $resultBrand = $product->$getBrand();
150
+ }
151
+
152
+ $brand = $resultBrand ? $resultBrand : null;
153
+
154
+ if (!$brand) {
155
+ return null;
156
+ }
157
+
158
+ if (!is_array($brand)) {
159
+ $brand = array($brand);
160
+ }
161
+
162
+ return $brand;
163
+ }
164
+
165
+ private function _getBrandAttributeType($brandAttributeCode)
166
+ {
167
+ $attribute = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_product', $brandAttributeCode);
168
+ if ($attribute) {
169
+ return $attribute->getFrontendInput();
170
+ }
171
+
172
+ return 'text';
173
+ }
174
+
175
+ protected function _camelize($name)
176
+ {
177
+ return uc_words($name, '');
178
+ }
179
+ }
app/code/community/Koan/Seg/Model/Batch/Status.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Batch_Status extends Mage_Core_Model_Abstract
4
+ {
5
+ public function _construct()
6
+ {
7
+ parent::_construct();
8
+ $this->_init('koan_seg/batch_status');
9
+ }
10
+
11
+ public function createNew($entityType = null, $filter = null)
12
+ {
13
+ $data = array(
14
+ 'entity_type' => $entityType,
15
+ 'start_time' => null,
16
+ 'end_time' => null,
17
+ 'total_row_count' => 0,
18
+ 'num_rows_processed' => 0,
19
+ 'current_status' => 0,
20
+ 'comment' => 'Waiting for start ...',
21
+ 'num_retried' => 0
22
+ );
23
+
24
+ if ($filter) {
25
+ $data['filter'] = $filter;
26
+ }
27
+
28
+ $this->setData(
29
+ $data
30
+ );
31
+
32
+ try {
33
+ $this->save();
34
+ } Catch (Exception $e) {
35
+ throw $e;
36
+ }
37
+
38
+
39
+ return $this;
40
+ }
41
+
42
+ public function setStartingStatus($rowCount, $currentStatus)
43
+ {
44
+ $this->addData(
45
+ array(
46
+ 'start_time' => Varien_Date::now(),
47
+ 'total_row_count' => $rowCount,
48
+ 'current_status' => $currentStatus,
49
+ 'comment' => 'Starting new export ...',
50
+ )
51
+ );
52
+
53
+ try {
54
+ $this->save();
55
+ } Catch (Exception $e) {
56
+ throw $e;
57
+ }
58
+
59
+ return $this;
60
+ }
61
+
62
+ public function setProcessingStatus($currentStatus)
63
+ {
64
+ $this->addData(
65
+ array(
66
+ 'current_status' => $currentStatus,
67
+ 'comment' => 'Processing rows ...',
68
+ )
69
+ );
70
+
71
+ try {
72
+ $this->save();
73
+ } Catch (Exception $e) {
74
+ throw $e;
75
+ }
76
+
77
+ return $this;
78
+ }
79
+
80
+ public function setBatchError($message)
81
+ {
82
+ $numRetried = is_null($this->getNumRetried()) ? 0 : $this->getNumRetried();
83
+ $numRetried++;
84
+
85
+ $data = array(
86
+ 'num_retried' => $numRetried,
87
+ 'comment' => $message,
88
+ );
89
+
90
+ if ($numRetried >= 3) {
91
+ $data['current_status'] = Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_ERROR;
92
+ }
93
+
94
+ $this->addData(
95
+ $data
96
+ );
97
+
98
+ try {
99
+ $this->save();
100
+ } Catch (Exception $e) {
101
+ throw $e;
102
+ }
103
+ }
104
+
105
+ public function setCompleteStatus($currentStatus)
106
+ {
107
+ $this->addData(
108
+ array(
109
+ 'current_status' => $currentStatus,
110
+ 'comment' => 'Processing complete.',
111
+ 'end_time' => Varien_Date::now(),
112
+ )
113
+ );
114
+
115
+ try {
116
+ $this->save();
117
+ } Catch (Exception $e) {
118
+ throw $e;
119
+ }
120
+
121
+ return $this;
122
+ }
123
+
124
+ public function updateNumRowsProcessed($rowCount = null, $numRowsProcessed)
125
+ {
126
+ $newNumProcessed = (int)$numRowsProcessed + (int)$this->getNumRowsProcessed();
127
+
128
+ $data = array(
129
+ 'num_rows_processed' => $newNumProcessed,
130
+ );
131
+
132
+ if ($rowCount) {
133
+ $data['total_row_count'] = $rowCount;
134
+ }
135
+
136
+ $this->addData($data);
137
+
138
+ try {
139
+ $this->save();
140
+ } Catch (Exception $e) {
141
+ throw $e;
142
+ }
143
+ }
144
+
145
+ }
app/code/community/Koan/Seg/Model/Exception/Handler.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ Mage::helper('koan_seg')->includeRollBar();
4
+
5
+ class Koan_Seg_Model_Exception_Handler
6
+ {
7
+ public function handle($action, Exception $e)
8
+ {
9
+ $data = array(
10
+ 'code' => $e->getCode(),
11
+ 'message' => $e->getMessage(),
12
+ );
13
+
14
+ Mage::getSingleton('koan_seg/logger')->log($action, 'error', $data);
15
+ }
16
+
17
+ public function handleHttpResponseError($action, $code, $message)
18
+ {
19
+ $data = array(
20
+ 'code' => $code,
21
+ 'message' => $message,
22
+ );
23
+
24
+ Mage::getSingleton('koan_seg/logger')->log($action, 'error', $data);
25
+ }
26
+ }
app/code/community/Koan/Seg/Model/Logger.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ Mage::helper('koan_seg')->includeRollBar();
4
+
5
+ class Koan_Seg_Model_Logger
6
+ {
7
+ public function log($action, $level = 'error', $data)
8
+ {
9
+ $data['site_id'] = Mage::helper('koan_seg')->getWebsiteId();
10
+ Rollbar::report_message($action, $level, $data);
11
+ }
12
+
13
+ }
app/code/community/Koan/Seg/Model/Observer.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Observer
4
+ {
5
+ public function cronprocess()
6
+ {
7
+ if (!Mage::helper('koan_seg')->isExportCronEnabled()) {
8
+ return $this;
9
+ }
10
+
11
+ $exporter = Mage::getSingleton('koan_seg/seg_exporter');
12
+
13
+ if ($exporter->hasCustomersToProcess()) {
14
+ Mage::getSingleton('koan_seg/seg_exporter')->exportCustomers();
15
+ return $this;
16
+ }
17
+
18
+ if ($exporter->hasOrdersToProcess()) {
19
+ Mage::getSingleton('koan_seg/seg_exporter')->exportHistoryOrders();
20
+ }
21
+
22
+ return $this;
23
+ }
24
+
25
+ public function checkoutCartProductAddAfter($observer)
26
+ {
27
+ $quoteItem = $observer->getEvent()->getQuoteItem();
28
+ $quoteItem->setAddedToCartFlag(true);
29
+
30
+ return $observer;
31
+ }
32
+
33
+ public function cartItemAfterSave($observer)
34
+ {
35
+ $quoteItem = $observer->getEvent()->getItem();
36
+
37
+ if ($children = $quoteItem->getChildren()) {
38
+ foreach ($children as $childItem) {
39
+ if ($childItem->getAddedToCartFlag() == true) {
40
+ $quoteItem = $childItem;
41
+ break;
42
+ }
43
+
44
+ }
45
+ }
46
+
47
+ if ($quoteItem->getAddedToCartFlag()) {
48
+ if ($quoteItem->getParentItemId()) {
49
+ Mage::getSingleton('customer/session')->setItemAddedToCart($quoteItem->getParentItemId());
50
+ $quoteItem->unsAddedToCartFlag();
51
+ } else if ($quoteItem->hasId()) {
52
+ Mage::getSingleton('customer/session')->setItemAddedToCart($quoteItem->getId());
53
+ $quoteItem->unsAddedToCartFlag();
54
+ }
55
+
56
+ return $observer;
57
+ }
58
+
59
+ return $observer;
60
+ }
61
+
62
+ public function orderPlaceAfter($observer)
63
+ {
64
+ $order = $observer->getEvent()->getOrder();
65
+ if (!$order) {
66
+ return $observer;
67
+ }
68
+
69
+ if (!$order->getId()) {
70
+ return $observer;
71
+ }
72
+
73
+ try {
74
+ $orderData = Mage::getModel('koan_seg/seg_order')->prepare($order);
75
+ Mage::getModel('koan_seg/seg_client')->exportNewOrder($orderData);
76
+ } Catch (Exception $e) {
77
+ Mage::getSingleton('koan_seg/exception_handler')->handle('Magento: error in orderPlaceAfter observer - exportNewOrder', $e);
78
+ }
79
+
80
+ return $observer;
81
+ }
82
+
83
+ }
app/code/community/Koan/Seg/Model/Resource/Batch/Status.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Resource_Batch_Status extends Mage_Core_Model_Resource_Db_Abstract
4
+ {
5
+ public function _construct()
6
+ {
7
+ $this->_init('koan_seg/batch_status', 'id');
8
+ }
9
+ }
app/code/community/Koan/Seg/Model/Resource/Batch/Status/Collection.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Resource_Batch_Status_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
4
+ {
5
+ public function _construct()
6
+ {
7
+ parent::_construct();
8
+ $this->_init('koan_seg/batch_status');
9
+ }
10
+ }
app/code/community/Koan/Seg/Model/Seg/Basket.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Seg_Basket extends Varien_Object
4
+ {
5
+ public function prepare($itemId)
6
+ {
7
+ $item = Mage::getModel('sales/quote_item')->load($itemId);
8
+ if (!$item OR !$item->getId()) {
9
+ Mage::throwException($this->_getHelper()->__('Quote Item with this id does not exists!'));
10
+ }
11
+
12
+ $quoteId = $item->getQuoteId();
13
+ if (!$quoteId) {
14
+ Mage::throwException($this->_getHelper()->__('Quote id does not exists!'));
15
+ }
16
+
17
+ $quote = Mage::getModel('sales/quote')->load($quoteId);
18
+ if (!$quote) {
19
+ Mage::throwException('Quote is not valid!');
20
+ }
21
+
22
+ if (!$quoteId = $quote->getId()) {
23
+ Mage::throwException('Missing Quote Id');
24
+ }
25
+
26
+ $item->setQuote($quote);
27
+ $revenue = is_null($quote->getBaseGrandTotal()) ? 0 : $quote->getBaseGrandTotal();
28
+
29
+ $resultAttributes = array(
30
+ 'Discount',
31
+ 'Id',
32
+ 'OrderLines',
33
+ //'_p',
34
+ 'Revenue'
35
+ );
36
+
37
+ $shippingAddress = $quote->getShippingAddress();
38
+
39
+ $shippingMethod = $shippingAddress->getShippingDescription();
40
+ if (!empty($shippingMethod)) {
41
+ $this->setData('DeliveryMethod', $shippingMethod);
42
+ $resultAttributes[] = 'DeliveryMethod';
43
+ }
44
+
45
+ $deliveryRevenue = $shippingAddress->getBaseShippingInclTax();
46
+ if (!is_null($deliveryRevenue)) {
47
+ $this->setData('DeliveryRevenue', number_format($deliveryRevenue, 2, '.', ''));
48
+ $resultAttributes[] = 'DeliveryRevenue';
49
+ }
50
+
51
+ $discountAmount = $shippingAddress->getBaseDiscountAmount();
52
+ if (!is_null($discountAmount)) {
53
+ $this->setData('Discount', number_format($discountAmount, 2, '.', ''));
54
+ $resultAttributes[] = 'Discount';
55
+ }
56
+
57
+ $this->setData('Id', $quoteId);
58
+ $this->setData('OrderLines', $this->_prepareQuoteLines($quote, $itemId));
59
+
60
+ $this->setData('Revenue', number_format($revenue, 2, '.', ''));
61
+
62
+ return $this->toArray($resultAttributes);
63
+ }
64
+
65
+ private function _prepareQuoteLines($quote, $addedItemId)
66
+ {
67
+ $quoteItems = $quote->getAllVisibleItems();
68
+ $result = array();
69
+ if ($quoteItems AND count($quoteItems)) {
70
+ foreach ($quoteItems as $item) {
71
+ if ($item->getId() == $addedItemId) {
72
+ $result[] = Mage::getModel('koan_seg/seg_quote_line')->prepare($item, true);
73
+ } else {
74
+ $result[] = Mage::getModel('koan_seg/seg_quote_line')->prepare($item);
75
+ }
76
+
77
+ }
78
+ }
79
+
80
+ return $result;
81
+ }
82
+
83
+ private function _getHelper()
84
+ {
85
+ return Mage::helper('koan_seg');
86
+ }
87
+ }
app/code/community/Koan/Seg/Model/Seg/Client.php ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ Mage::helper('koan_seg')->includeRollBar();
4
+
5
+ class Koan_Seg_Model_Seg_Client extends Varien_Http_Client
6
+ {
7
+ const REQUEST_TIMEOUT = 30;
8
+
9
+ public function __construct()
10
+ {
11
+ //$this->_getHelper()->initRollbar();
12
+
13
+ $this->config['useragent'] = 'Koan_Seg_Model_Seg_Client';
14
+ parent::__construct();
15
+ }
16
+
17
+ public function exportCustomers($customers)
18
+ {
19
+ if (!$customers) {
20
+ Mage::throwException($this->__('Customers data does not exists!'));
21
+ }
22
+
23
+ if (!is_array($customers) OR !count($customers)) {
24
+ Mage::throwException($this->__('Invalid customers data!'));
25
+ }
26
+
27
+ $params = json_encode($customers);
28
+
29
+ $url = $this->_getExportCustomerUrl();
30
+
31
+ $this->_init();
32
+ $this->setUri($url);
33
+
34
+ $this->setRawData($params, 'application/json');
35
+
36
+ if ($this->_getHelper()->logRequestInfo()) {
37
+ Mage::getSingleton('koan_seg/logger')->log('Magento: posting customers to Seg', 'info', array('customers' => $params, 'endpoint' => $url));
38
+ }
39
+
40
+ try {
41
+ $response = $this->request();
42
+ } Catch (Exception $e) {
43
+ throw $e;
44
+ }
45
+
46
+ try {
47
+ $this->__handleResponse($response, 'exportCustomers');
48
+ } Catch (Exception $e) {
49
+ Mage::throwException('[exportCustomers]::__handleResponse: ' . $e->getMessage());
50
+ }
51
+
52
+ return $this;
53
+ }
54
+
55
+ public function exportHistoryOrders($orders)
56
+ {
57
+ if (!$orders) {
58
+ Mage::throwException($this->__('Order data does not exists!'));
59
+ }
60
+
61
+ if (!is_array($orders) OR !count($orders)) {
62
+ Mage::throwException($this->__('Invalid order data!'));
63
+ }
64
+
65
+ $params = json_encode($orders);
66
+
67
+ $url = $this->_getExportOrderHistoryUrl();
68
+
69
+ $this->_init();
70
+ $this->setUri($url);
71
+
72
+ $this->setRawData($params, 'application/json');
73
+
74
+ if ($this->_getHelper()->logRequestInfo()) {
75
+
76
+ $batchSize = 20;
77
+ $total = count($orders);
78
+
79
+ $numBatches = floor(floatval($total) / floatval($batchSize)) + (floatval($total) % floatval($batchSize));
80
+ $batchCounter = 0;
81
+
82
+ $batch = array();
83
+
84
+ $counter = 0;
85
+ foreach ($orders as $order) {
86
+ $counter++;
87
+
88
+ if ($counter >= $total) {
89
+ $batchCounter++;
90
+ $logData = json_encode($batch);
91
+ Mage::getSingleton('koan_seg/logger')->log(
92
+ 'Magento: posting history orders to Seg', 'info', array('orders' => $logData, 'endpoint' => $url, 'batch' => $batchCounter, 'batches' => $numBatches));
93
+
94
+ break;
95
+ }
96
+
97
+ if (count($batch) < $batchSize) {
98
+ $batch[] = $order;
99
+ } else {
100
+ $batchCounter++;
101
+ $logData = json_encode($batch);
102
+
103
+ Mage::getSingleton('koan_seg/logger')->log(
104
+ 'Magento: posting history orders to Seg', 'info', array('orders' => $logData, 'endpoint' => $url, 'batch' => $batchCounter, 'batches' => $numBatches));
105
+
106
+ $batch = array();
107
+ }
108
+
109
+ }
110
+
111
+ }
112
+
113
+ try {
114
+ $response = $this->request();
115
+ } Catch (Exception $e) {
116
+ throw $e;
117
+ }
118
+
119
+ try {
120
+ $this->__handleResponse($response, 'posting history orders to Seg');
121
+ } Catch (Exception $e) {
122
+ Mage::throwException('[exportHistoryOrders]::__handleResponse: ' . $e->getMessage());
123
+ }
124
+
125
+ return $this;
126
+ }
127
+
128
+ public function exportNewOrder($orderData)
129
+ {
130
+ if (!$orderData) {
131
+ Mage::throwException($this->__('Order data does not exists!'));
132
+ }
133
+
134
+ if (!is_array($orderData)) {
135
+ Mage::throwException($this->__('Invalid order data!'));
136
+ }
137
+
138
+ $params = json_encode($orderData);
139
+
140
+ $url = $this->_getOrderPlacedUrl();
141
+
142
+ $this->_init();
143
+ $this->setUri($url);
144
+
145
+ $this->setRawData($params, 'application/json');
146
+
147
+ if ($this->_getHelper()->logRequestInfo()) {
148
+ Mage::getSingleton('koan_seg/logger')->log('Magento: posting new order to Seg', 'info', array('order' => $params, 'endpoint' => $url));
149
+ }
150
+
151
+ try {
152
+ $response = $this->request();
153
+ } Catch (Exception $e) {
154
+ throw $e;
155
+ }
156
+
157
+ try {
158
+ $this->__handleResponse($response, 'posting new order to Seg');
159
+ } Catch (Exception $e) {
160
+ Mage::throwException('[exportHistoryOrders]::__handleResponse: ' . $e->getMessage());
161
+ }
162
+
163
+ return $this;
164
+ }
165
+
166
+ private function __handleResponse($response, $msg = '')
167
+ {
168
+ /** @var Zend_Http_Response $response */
169
+ if ($response->isSuccessful()) {
170
+ if ($this->_getHelper()->logRequestInfo()) {
171
+ Mage::getSingleton('koan_seg/logger')->log(sprintf('Magento: %s - response from Seg', $msg), 'info',
172
+ array('response' => 'ResponseOK: ' . $response->getBody()));
173
+ }
174
+ return true;
175
+ }
176
+
177
+ if ($response->isError()) {
178
+ $code = $response->getStatus();
179
+ $message = $response->getMessage();
180
+ Mage::getSingleton('koan_seg/exception_handler')->handleHttpResponseError(sprintf('Magento: error in \'%s\' HTTP request: %s', $msg, $message), $code, $message);
181
+ }
182
+
183
+ return false;
184
+ }
185
+
186
+ private function _init()
187
+ {
188
+ $this->setConfig($this->_getHttpConfig());
189
+ $this->setMethod(Zend_Http_Client::POST);
190
+
191
+ if ($headers = $this->_getHttpHeaders()) {
192
+ $this->setHeaders($headers);
193
+ }
194
+ return $this;
195
+ }
196
+
197
+ private function _getExportCustomerUrl()
198
+ {
199
+ return sprintf($this->_getHelper()->getUpdateCustomersUrl(), $this->_getHelper()->getWebsiteId());
200
+ }
201
+
202
+ private function _getExportOrderHistoryUrl()
203
+ {
204
+ return sprintf($this->_getHelper()->getOrderHistoryUrl(), $this->_getHelper()->getWebsiteId());
205
+ }
206
+
207
+ private function _getOrderPlacedUrl()
208
+ {
209
+ return sprintf($this->_getHelper()->getOrderPlacedUrl(), $this->_getHelper()->getWebsiteId());
210
+ }
211
+
212
+ private function _getHttpHeaders()
213
+ {
214
+ return array('accept' => 'application/json');
215
+ }
216
+
217
+ private function _getHttpConfig()
218
+ {
219
+ return array(
220
+ 'timeout' => $this->_getTimeout(),
221
+ //'proxy' => '127.0.0.1:8888' //For development purposes only
222
+ );
223
+ }
224
+
225
+ private function _getTimeout()
226
+ {
227
+ return self::REQUEST_TIMEOUT;
228
+ }
229
+
230
+ private function _getHelper()
231
+ {
232
+ return Mage::helper('koan_seg');
233
+ }
234
+
235
+ private function __($text)
236
+ {
237
+ return $this->_getHelper()->__($text);
238
+ }
239
+ }
app/code/community/Koan/Seg/Model/Seg/Customer.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+ class Koan_Seg_Model_Seg_Customer extends Varien_Object
11
+ {
12
+ public function prepare($customer)
13
+ {
14
+ if (!$customer) {
15
+ Mage::throwException('Customer is not valid!');
16
+ }
17
+
18
+ if (!$customerId = $customer->getId()) {
19
+ Mage::throwException('Missing Customer Id');
20
+ }
21
+
22
+ $attributes = array(
23
+ 'Email',
24
+ );
25
+
26
+ $email = $customer->getEmail();
27
+ if (empty($email)) {
28
+ Mage::throwException($this->__('Customer email is missing!'));
29
+ }
30
+
31
+ $this->setData('Email', $email);
32
+
33
+ $title = $customer->getPrefix();
34
+ if (!empty($title)) {
35
+ $this->setData('Title', $title);
36
+ $attributes[] = 'Title';
37
+ }
38
+
39
+ $firstName = $customer->getFirstname();
40
+ // if (empty($firstName)) {
41
+ // $firstName = $email;
42
+ // }
43
+
44
+ if (!empty($firstName)) {
45
+ $this->setData('FirstName', $firstName);
46
+ $attributes[] = 'FirstName';
47
+ }
48
+
49
+ $lastName = $customer->getLastname();
50
+ if (!empty($lastName)) {
51
+ $this->setData('LastName', $lastName);
52
+ $attributes[] = 'LastName';
53
+ }
54
+
55
+ $address = $customer->getPrimaryBillingAddress();
56
+ if (!$address) {
57
+ $address = $customer->getPrimaryShippingAddress();
58
+ }
59
+
60
+ $country = null;
61
+ if ($address AND $address->getId()) {
62
+ $country = $address->getCountry();
63
+ }
64
+ if (!empty($country)) {
65
+ $this->setData('CountryCode', $country);
66
+ $attributes[] = 'CountryCode';
67
+ }
68
+
69
+ return $this->toArray($attributes);
70
+ }
71
+ }
app/code/community/Koan/Seg/Model/Seg/Exporter.php ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Seg_Exporter
4
+ {
5
+ const BATCH_STATUS_NOT_STARTED = 0;
6
+ const BATCH_STATUS_STARTING = 1;
7
+ const BATCH_STATUS_PROCESSING_ROWS = 2;
8
+
9
+ const BATCH_STATUS_COMPLETE = 5;
10
+ const BATCH_STATUS_ERROR = 6;
11
+
12
+ const BATCH_ENTITY_TYPE_HISTORY_ORDERS = 'history_orders';
13
+ const BATCH_ENTITY_TYPE_CUSTOMERS = 'customers';
14
+
15
+ public static $_exportHistoryOrdersGenerateCron = false;
16
+ public static $_exportHistoryOrdersCron = false;
17
+
18
+ public static $_exportCustomersGenerateCron = false;
19
+ public static $_exportCustomersCron = false;
20
+
21
+ private $_historyOrdersCollection = null;
22
+
23
+ private $_customersCollection = null;
24
+
25
+ /**************************************** CUSTOMERS ******************************************/
26
+
27
+ //Prepare new customers batch to export
28
+ public function generateCustomersExportBatch()
29
+ {
30
+ if (self::$_exportCustomersGenerateCron == true) {
31
+ return $this;
32
+ }
33
+
34
+ $this->_getHelper()->initRollbar();
35
+
36
+ try {
37
+ $this->_exportCustomers(true);
38
+ } Catch (Exception $e) {
39
+ Mage::getSingleton('koan_seg/exception_handler')->handle('Magento: error in generateCustomersExportBatch', $e);
40
+ }
41
+
42
+ self::$_exportCustomersGenerateCron = true;
43
+ return $this;
44
+ }
45
+
46
+ //Export prepared batches of customers (if any)
47
+ public function exportCustomers()
48
+ {
49
+ if (self::$_exportCustomersCron == true) {
50
+ return $this;
51
+ }
52
+
53
+ $this->_getHelper()->initRollbar();
54
+
55
+ try {
56
+
57
+ if ($limit = $this->_getHelper()->getExporterPhpMemoryLimit()) {
58
+ if (!empty($limit)) {
59
+ ini_set('memory_limit', $limit);
60
+ }
61
+ }
62
+
63
+ $this->_exportCustomers();
64
+ } Catch (Exception $e) {
65
+ Mage::getSingleton('koan_seg/exception_handler')->handle('Magento: error in batch exporter - exportCustomers', $e);
66
+ }
67
+
68
+ self::$_exportCustomersCron = true;
69
+ return $this;
70
+ }
71
+
72
+ private function _exportCustomers($createNew = false)
73
+ {
74
+ if ($createNew == true) {
75
+ Mage::getModel('koan_seg/batch_status')->createNew(self::BATCH_ENTITY_TYPE_CUSTOMERS);
76
+ return $this;
77
+ }
78
+
79
+ $batchRows = $this->_getBatchRows(self::BATCH_ENTITY_TYPE_CUSTOMERS);
80
+ //We can use count here as there should be only few rows
81
+ if (!$numRows = count($batchRows)) {
82
+ return $this;
83
+ }
84
+
85
+ foreach ($batchRows as $batch) {
86
+
87
+ try {
88
+ $currentStatus = $batch->getCurrentStatus();
89
+
90
+ switch ($currentStatus) {
91
+ case self::BATCH_STATUS_NOT_STARTED:
92
+ $totalItemsCount = $this->_getCustomersCollectionSize();
93
+ $batch->setStartingStatus($totalItemsCount, self::BATCH_STATUS_STARTING);
94
+ case self::BATCH_STATUS_STARTING:
95
+ $batch->setProcessingStatus(self::BATCH_STATUS_PROCESSING_ROWS);
96
+ case self::BATCH_STATUS_PROCESSING_ROWS:
97
+ $this->_processExportCustomers($batch);
98
+ $batch->setCompleteStatus(self::BATCH_STATUS_COMPLETE);
99
+ }
100
+
101
+ } Catch (Exception $e) {
102
+ $batch->setBatchError($e->getMessage());
103
+ throw $e;
104
+ }
105
+
106
+ }
107
+
108
+ return $this;
109
+ }
110
+
111
+ public function _getCustomersCollectionSize()
112
+ {
113
+ $collection = $this->_getCustomersCollection();
114
+ return $collection->getSize();
115
+ }
116
+
117
+ public function _getCustomersCollection()
118
+ {
119
+ if (!$this->_customersCollection) {
120
+ $collection = Mage::getResourceModel('customer/customer_collection')
121
+ ->addAttributeToSelect('*')
122
+ ->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left')
123
+ ->joinAttribute('shipping_country_id', 'customer_address/country_id', 'default_shipping', null, 'left');
124
+ //->load();
125
+
126
+ $this->_customersCollection = $collection;
127
+ }
128
+
129
+ return $this->_customersCollection;
130
+ }
131
+
132
+ private function _processExportCustomers($batch)
133
+ {
134
+ $numProcTotal = is_null($batch->getNumRowsProcessed()) ? 0 : intval($batch->getNumRowsProcessed());
135
+ $totalRows = is_null($batch->getTotalRowCount()) ? 0 : intval($batch->getTotalRowCount());
136
+
137
+ if ($numProcTotal < $totalRows) {
138
+
139
+ $break = false;
140
+
141
+ do {
142
+
143
+ $collection = clone $this->_getCustomersCollection();
144
+ $collection->setOrder('entity_id', 'ASC');
145
+ $collection->getSelect()->limit($this->_getExportCustomersPageSize(), $numProcTotal);
146
+ $collection->load();
147
+
148
+ $numProcessed = 0;
149
+
150
+ $customers = array();
151
+ foreach ($collection as $customer) {
152
+
153
+ $customers[] = Mage::getModel('koan_seg/seg_customer')->prepare($customer);
154
+ $numProcessed++;
155
+ }
156
+
157
+ Mage::getModel('koan_seg/seg_client')->exportCustomers($customers);
158
+
159
+ unset($customers);
160
+ $customers = array();
161
+
162
+ $numProcTotal += $numProcessed;
163
+ $batch->updateNumRowsProcessed(null, $numProcessed);
164
+
165
+ if ($numProcTotal >= $totalRows) {
166
+ //Update total rows
167
+ $batch->updateNumRowsProcessed($numProcTotal, 0);
168
+ $break = true;
169
+ }
170
+
171
+ } while (!$break);
172
+
173
+ }
174
+
175
+ }
176
+
177
+ private function _getExportCustomersPageSize()
178
+ {
179
+ return Mage::helper('koan_seg')->getCustomersExportBatchSize();
180
+ }
181
+
182
+ /**************************************** END CUSTOMERS ******************************************/
183
+
184
+ /**************************************** ORDERS ******************************************/
185
+
186
+ //Prepare new history orders batch to export
187
+ public function generateHistoryOrdersExportBatch($orderDateFilter = null)
188
+ {
189
+ if (self::$_exportHistoryOrdersGenerateCron == true) {
190
+ return $this;
191
+ }
192
+
193
+ $this->_getHelper()->initRollbar();
194
+
195
+ try {
196
+ $this->_exportHistoryOrders(true, $orderDateFilter);
197
+ } Catch (Exception $e) {
198
+ Mage::getSingleton('koan_seg/exception_handler')->handle('Magento: error in generateHistoryOrdersExportBatch', $e);
199
+ }
200
+
201
+ self::$_exportHistoryOrdersGenerateCron = true;
202
+ return $this;
203
+ }
204
+
205
+ //Export prepared batches of history orders (if any)
206
+ public function exportHistoryOrders()
207
+ {
208
+ if (self::$_exportHistoryOrdersCron == true) {
209
+ return $this;
210
+ }
211
+
212
+ $this->_getHelper()->initRollbar();
213
+
214
+ try {
215
+
216
+ if ($limit = $this->_getHelper()->getExporterPhpMemoryLimit()) {
217
+ if (!empty($limit)) {
218
+ ini_set('memory_limit', $limit);
219
+ }
220
+ }
221
+
222
+ $this->_exportHistoryOrders();
223
+ } Catch (Exception $e) {
224
+ Mage::getSingleton('koan_seg/exception_handler')->handle('Magento: error in batch exporter - exportHistoryOrders', $e);
225
+ }
226
+
227
+ self::$_exportHistoryOrdersCron = true;
228
+ return $this;
229
+ }
230
+
231
+ private function _exportHistoryOrders($createNew = false, $orderDateFilter = null)
232
+ {
233
+ if ($createNew == true) {
234
+ Mage::getModel('koan_seg/batch_status')->createNew(self::BATCH_ENTITY_TYPE_HISTORY_ORDERS, $orderDateFilter);
235
+ return $this;
236
+ }
237
+
238
+ $batchRows = $this->_getBatchRows(self::BATCH_ENTITY_TYPE_HISTORY_ORDERS);
239
+ //We can use count here as there should be only few rows
240
+ if (!$numRows = count($batchRows)) {
241
+ return $this;
242
+ }
243
+
244
+ foreach ($batchRows as $batch) {
245
+ try {
246
+ $currentStatus = $batch->getCurrentStatus();
247
+
248
+ switch ($currentStatus) {
249
+ case self::BATCH_STATUS_NOT_STARTED:
250
+ $totalItemsCount = $this->_getHistoryOrdersCollectionSize($batch);
251
+ $batch->setStartingStatus($totalItemsCount, self::BATCH_STATUS_STARTING);
252
+ case self::BATCH_STATUS_STARTING:
253
+ $batch->setProcessingStatus(self::BATCH_STATUS_PROCESSING_ROWS);
254
+ case self::BATCH_STATUS_PROCESSING_ROWS:
255
+ $this->_processExportHistoryOrders($batch);
256
+ $batch->setCompleteStatus(self::BATCH_STATUS_COMPLETE);
257
+ }
258
+
259
+ } Catch (Exception $e) {
260
+ $batch->setBatchError($e->getMessage());
261
+ throw $e;
262
+ }
263
+
264
+ }
265
+
266
+ return $this;
267
+ }
268
+
269
+ private function _processExportHistoryOrders($batch)
270
+ {
271
+ $numProcTotal = is_null($batch->getNumRowsProcessed()) ? 0 : intval($batch->getNumRowsProcessed());
272
+ $totalRows = is_null($batch->getTotalRowCount()) ? 0 : intval($batch->getTotalRowCount());
273
+
274
+ if ($numProcTotal < $totalRows) {
275
+
276
+ $break = false;
277
+
278
+ do {
279
+
280
+ $collection = clone $this->_getHistoryOrdersCollection($batch);
281
+ $collection->setOrder('entity_id', 'ASC');
282
+ $collection->getSelect()->limit($this->_getExportHistoryPageSize(), $numProcTotal);
283
+ $collection->load();
284
+
285
+ $numProcessed = 0;
286
+
287
+ $orders = array();
288
+ foreach ($collection as $order) {
289
+
290
+ //TODO: Handle collection export
291
+ $orders[] = Mage::getModel('koan_seg/seg_order')->prepare($order);
292
+ $numProcessed++;
293
+ }
294
+
295
+ Mage::getModel('koan_seg/seg_client')->exportHistoryOrders($orders);
296
+
297
+ unset($orders);
298
+ $orders = array();
299
+
300
+ $numProcTotal += $numProcessed;
301
+ $batch->updateNumRowsProcessed(null, $numProcessed);
302
+
303
+ if ($numProcTotal >= $totalRows) {
304
+ $batch->updateNumRowsProcessed($numProcTotal, 0);
305
+ $break = true;
306
+ }
307
+
308
+ } while (!$break);
309
+
310
+ }
311
+
312
+ }
313
+
314
+ private function _getExportHistoryPageSize()
315
+ {
316
+ return Mage::helper('koan_seg')->getOrdersExportBatchSize();
317
+ }
318
+
319
+ public function hasCustomersToProcess()
320
+ {
321
+ $rows = $this->_getBatchRows(self::BATCH_ENTITY_TYPE_CUSTOMERS);
322
+ if ($rows AND count($rows)) {
323
+ return true;
324
+ }
325
+
326
+ return false;
327
+ }
328
+
329
+ public function hasOrdersToProcess()
330
+ {
331
+ $rows = $this->_getBatchRows(self::BATCH_ENTITY_TYPE_HISTORY_ORDERS);
332
+ if ($rows AND count($rows)) {
333
+ return true;
334
+ }
335
+
336
+ return false;
337
+ }
338
+
339
+ private function _getBatchRows($entityType)
340
+ {
341
+ $allowedStages = array(
342
+ self::BATCH_STATUS_NOT_STARTED,
343
+ self::BATCH_STATUS_STARTING,
344
+ self::BATCH_STATUS_PROCESSING_ROWS
345
+ );
346
+
347
+ $collection = Mage::getResourceModel('koan_seg/batch_status_collection');
348
+ $collection->addFieldToFilter('current_status', array('in' => $allowedStages));
349
+ $collection->addFieldToFilter('entity_type', $entityType);
350
+ $collection->setOrder('current_status', 'ASC');
351
+
352
+ return $collection;
353
+ }
354
+
355
+ public function _getHistoryOrdersCollectionSize($batch = null)
356
+ {
357
+ return $this->_getHistoryOrdersCollection($batch)->getSize();
358
+ }
359
+
360
+ public function _getHistoryOrdersCollection($batch = null)
361
+ {
362
+ if (!$this->_historyOrdersCollection) {
363
+ $collection = Mage::getResourceModel('sales/order_collection');
364
+ $collection->addFieldToFilter('state', 'complete');
365
+
366
+ if ($batch AND $filter = $batch->getFilter()) {
367
+ $collection->addFieldToFilter('created_at', array('from' => $filter));
368
+ }
369
+
370
+ $this->_historyOrdersCollection = $collection;
371
+ }
372
+
373
+ return $this->_historyOrdersCollection;
374
+ }
375
+
376
+ /**************************************** END ORDERS ******************************************/
377
+
378
+
379
+ public function _getHelper()
380
+ {
381
+ return Mage::helper('koan_seg');
382
+ }
383
+
384
+ }
app/code/community/Koan/Seg/Model/Seg/Order.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Seg_Order extends Varien_Object
4
+ {
5
+ public function prepare($order, $includeMailAndDate = true)
6
+ {
7
+ if (!$order) {
8
+ Mage::throwException($this->_getHelper()->__('Order is not valid!'));
9
+ }
10
+
11
+ if (!$orderId = $order->getId()) {
12
+ Mage::throwException($this->_getHelper()->__('Missing Order Id'));
13
+ }
14
+
15
+ $baseGrandTotal = is_null($order->getBaseGrandTotal()) ? 0 : $order->getBaseGrandTotal();
16
+ $baseTotalRefunded = is_null($order->getBaseTotalRefunded()) ? 0 : $order->getBaseTotalRefunded();
17
+
18
+ $revenue = $baseGrandTotal - $baseTotalRefunded;
19
+
20
+ $shippingMethod = $order->getShippingDescription();
21
+ if (empty($shippingMethod)) {
22
+ $shippingMethod = 'VIRTUAL ORDER';
23
+ }
24
+
25
+ $customerEmail = is_null($order->getCustomerEmail()) ? 'unknown@mail.com' : $order->getCustomerEmail();
26
+ $createdAt = is_null($order->getCreatedAt()) ? '0000-00-00 0:00:00' : $order->getCreatedAt();
27
+
28
+ $params = array(
29
+ 'DeliveryMethod',
30
+ 'DeliveryRevenue',
31
+ 'Discount',
32
+ 'Id',
33
+ 'OrderLines',
34
+ //'_p',
35
+ 'Revenue'
36
+ );
37
+
38
+ if ($includeMailAndDate) {
39
+ $this->setData('email', $customerEmail);
40
+ $this->setData('date', $createdAt);
41
+
42
+ $params[] = 'email';
43
+ $params[] = 'date';
44
+ }
45
+
46
+ $this->setData('DeliveryMethod', $shippingMethod);
47
+ $this->setData('DeliveryRevenue', number_format($order->getBaseShippingAmount(), 2, '.', ''));
48
+ $this->setData('Discount', number_format($order->getBaseDiscountAmount() * (-1), 2, '.', ''));
49
+ $this->setData('Id', $orderId);
50
+ $this->setData('OrderLines', $this->_prepareOrderLines($order));
51
+ //$this->setData('_p', $order->getBaseGrandTotal());
52
+ $this->setData('Revenue', number_format($revenue, 2, '.', ''));
53
+
54
+ return $this->toArray($params);
55
+
56
+ //return prepared array structure for sending via HTTP request
57
+ }
58
+
59
+ private function _prepareOrderLines($order)
60
+ {
61
+ $orderItems = $order->getAllVisibleItems();
62
+
63
+ $result = array();
64
+ if ($orderItems AND count($orderItems)) {
65
+ foreach ($orderItems as $item) {
66
+ $result[] = Mage::getModel('koan_seg/seg_order_line')->prepare($item);
67
+ }
68
+ }
69
+
70
+ return $result;
71
+ }
72
+
73
+ private function _getHelper()
74
+ {
75
+ return Mage::helper('koan_seg');
76
+ }
77
+
78
+ }
app/code/community/Koan/Seg/Model/Seg/Order/Line.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Model_Seg_Order_Line extends Varien_Object
4
+ {
5
+
6
+ public function prepare($item)
7
+ {
8
+ $qty = $item->getQtyOrdered();
9
+
10
+ //Product is configurable parent
11
+ if (count($item->getChildrenItems())) {
12
+ $children = $item->getChildrenItems();
13
+ $product = $children[0]->getProduct();
14
+ //Product is simple
15
+ } else {
16
+ $product = $item->getProduct();
17
+ }
18
+
19
+ $product->load($product->getId());
20
+
21
+ if ($parent = $item->getParentItem()) {
22
+ $subtotal = $parent->getBaseRowTotalInclTax();
23
+ $discount = is_null($parent->getBaseDiscountAmount()) ? 0 : $parent->getBaseDiscountAmount();
24
+ $subtotal -= $discount;
25
+ } else {
26
+ $subtotal = $item->getBaseRowTotalInclTax();
27
+ $discount = is_null($item->getBaseDiscountAmount()) ? 0 : $item->getBaseDiscountAmount();
28
+ $subtotal -= $discount;
29
+ }
30
+
31
+ if ($parent = $item->getParentItem()) {
32
+ $originalPrice = $parent->getBasePriceInclTax();
33
+ } else {
34
+ $originalPrice = $item->getBasePriceInclTax();
35
+ }
36
+
37
+ if ($discount > 0) {
38
+ $price = $subtotal / $item->getQtyOrdered();
39
+ } else {
40
+ $price = $originalPrice;
41
+ }
42
+
43
+ $this->setData('Quantity', number_format($qty, 2, '.', ''));
44
+ $this->setData('Id', $item->getProductId());
45
+ $this->setData('ImageUrl', $product->getImageUrl());
46
+
47
+ //TODO: Check this parameter for configurable products and Bundle products etc
48
+ $this->setData('Name', $item->getName());
49
+
50
+ $this->setData('OriginalPrice', number_format($originalPrice, 2, '.', ''));
51
+ $this->setData('Price', number_format($price, 2, '.', ''));
52
+
53
+ $optionsResult = array();
54
+
55
+ if ($options = $item->getProductOptions()) {
56
+ if (isset($options['options'])) {
57
+ $optionsResult = array_merge($optionsResult, $options['options']);
58
+ }
59
+ if (isset($options['additional_options'])) {
60
+ $optionsResult = array_merge($optionsResult, $options['additional_options']);
61
+ }
62
+ if (!empty($options['attributes_info'])) {
63
+ $optionsResult = array_merge($options['attributes_info'], $optionsResult);
64
+ }
65
+
66
+ $variantName = '';
67
+ $i = 0;
68
+
69
+ foreach ($optionsResult as $option) {
70
+ if ($i > 0) {
71
+ $variantName .= ', ';
72
+ }
73
+
74
+ $variantName .= sprintf('%s: %s', $option['label'], $option['value']);
75
+ $i++;
76
+ }
77
+
78
+ $this->setData('VariantName', $variantName);
79
+ }
80
+
81
+ $outputAttributes = array(
82
+ 'Quantity',
83
+ 'Id',
84
+ 'ImageUrl',
85
+ 'Name',
86
+ 'OriginalPrice',
87
+ 'Price',
88
+ );
89
+
90
+ $brands = $this->_getHelper()->getProductBrands($product, $item->getStoreId());
91
+ if (!empty($brands)) {
92
+ $this->setData('Brands', $brands);
93
+ $outputAttributes[] = 'Brands';
94
+ }
95
+
96
+ if (!empty($variantName)) {
97
+ $outputAttributes[] = 'VariantName';
98
+ }
99
+
100
+ $catIds = $product->getCategoryIds();
101
+ $categoriesResult = array();
102
+ if ($catIds AND is_array($catIds) AND count($catIds)) {
103
+ foreach ($catIds as $catId) {
104
+ $category = Mage::getModel('catalog/category')
105
+ ->setStoreId($item->getStoreId())
106
+ ->load($catId);
107
+
108
+ if ($category AND $category->getId()) {
109
+ $categoriesResult[] = $category->getName();
110
+ }
111
+ }
112
+ }
113
+ if (count($categoriesResult)) {
114
+ $this->setData('Categories', $categoriesResult);
115
+ $outputAttributes[] = 'Categories';
116
+ }
117
+
118
+ return $this->toArray($outputAttributes);
119
+ }
120
+
121
+ private function _getHelper()
122
+ {
123
+ return Mage::helper('koan_seg');
124
+ }
125
+ }
app/code/community/Koan/Seg/Model/Seg/Product.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+ class Koan_Seg_Model_Seg_Product extends Varien_Object
11
+ {
12
+ public function prepare($product)
13
+ {
14
+ if (!$product) {
15
+ Mage::throwException('Product is not valid!');
16
+ }
17
+
18
+ if (!$productId = $product->getId()) {
19
+ Mage::throwException('Missing Product Id');
20
+ }
21
+
22
+ $storeId = Mage::app()->getStore()->getId();
23
+
24
+ $outputAttributes = array(
25
+ 'Id', 'ImageUrl', 'Name', 'OriginalPrice', 'Price'
26
+ );
27
+
28
+ $productData = array(
29
+ 'Id' => $productId,
30
+ 'ImageUrl' => $product->getImageUrl(),
31
+ 'Name' => $product->getName(),
32
+ 'OriginalPrice' => Mage::helper('tax')->getPrice($product, $product->getPrice()),
33
+ 'Price' => Mage::helper('tax')->getPrice($product, $product->getFinalPrice()),
34
+ );
35
+
36
+ $this->setData($productData);
37
+
38
+ $catIds = $product->getCategoryIds();
39
+ $categoriesResult = array();
40
+ if ($catIds AND is_array($catIds) AND count($catIds)) {
41
+ foreach ($catIds as $catId) {
42
+ $category = Mage::getModel('catalog/category')
43
+ ->setStoreId($storeId)
44
+ ->load($catId);
45
+
46
+ if ($category AND $category->getId()) {
47
+ $categoriesResult[] = $category->getName();
48
+ }
49
+ }
50
+ }
51
+ if (count($categoriesResult)) {
52
+ $this->setData('Categories', $categoriesResult);
53
+ $outputAttributes[] = 'Categories';
54
+ }
55
+
56
+ $brands = $this->_getHelper()->getProductBrands($product, $storeId);
57
+ if (!empty($brands)) {
58
+ $this->setData('Brands', $brands);
59
+ $outputAttributes[] = 'Brands';
60
+ }
61
+
62
+ return $this->toArray($outputAttributes);
63
+ }
64
+
65
+ private function _getHelper()
66
+ {
67
+ return Mage::helper('koan_seg');
68
+ }
69
+ }
app/code/community/Koan/Seg/Model/Seg/Quote/Line.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+ class Koan_Seg_Model_Seg_Quote_Line extends Varien_Object
11
+ {
12
+
13
+ public function prepare($item, $isAdded = false)
14
+ {
15
+ $qty = $item->getQty();
16
+ $parentProduct = null;
17
+
18
+ //Product is configurable parent
19
+ if (count($item->getChildren())) {
20
+ $children = $item->getChildren();
21
+ $product = $children[0]->getProduct();
22
+ $parentProduct = $item->getProduct();
23
+ //Product is simple
24
+ } else {
25
+ $product = $item->getProduct();
26
+ }
27
+
28
+ $product->load($product->getId());
29
+
30
+ if ($parent = $item->getParentItem()) {
31
+ $subtotal = $parent->getBaseRowTotalInclTax();
32
+ $discount = is_null($parent->getBaseDiscountAmount()) ? 0 : $parent->getBaseDiscountAmount();
33
+ $subtotal -= $discount;
34
+ } else {
35
+ $subtotal = $item->getBaseRowTotalInclTax();
36
+ $discount = is_null($item->getBaseDiscountAmount()) ? 0 : $item->getBaseDiscountAmount();
37
+ $subtotal -= $discount;
38
+ }
39
+
40
+ if ($parent = $item->getParentItem()) {
41
+ $originalPrice = $parent->getBasePriceInclTax();
42
+ } else {
43
+ $originalPrice = $item->getBasePriceInclTax();
44
+ }
45
+
46
+ if ($discount > 0) {
47
+ $price = $subtotal / $item->getQty();
48
+ } else {
49
+ $price = $originalPrice;
50
+ }
51
+
52
+ $this->setData('Quantity', number_format($qty, 2, '.', ''));
53
+ $this->setData('Id', $item->getProductId());
54
+ $this->setData('ImageUrl', $product->getImageUrl());
55
+
56
+ //TODO: Check this parameter for configurable products and Bundle products etc
57
+ $this->setData('Name', $item->getName());
58
+
59
+ $this->setData('OriginalPrice', number_format($originalPrice, 2, '.', ''));
60
+ $this->setData('Price', number_format($price, 2, '.', ''));
61
+
62
+ $optionsResult = array();
63
+
64
+ $helper = Mage::helper('catalog/product_configuration');
65
+
66
+ if ($parentProduct) {
67
+ $typeId = $parentProduct->getTypeId();
68
+ if ($typeId AND $typeId == Mage_Catalog_Model_Product_Type_Configurable::TYPE_CODE) {
69
+ $optionsResult = $helper->getConfigurableOptions($item);
70
+
71
+ if ($optionsResult) {
72
+ $variantName = '';
73
+ $i = 0;
74
+ foreach ($optionsResult as $option) {
75
+ if ($i > 0) {
76
+ $variantName .= ', ';
77
+ }
78
+
79
+ $variantName .= sprintf('%s: %s', $option['label'], $option['value']);
80
+ $i++;
81
+ }
82
+
83
+ $this->setData('VariantName', $variantName);
84
+ }
85
+
86
+ }
87
+
88
+ }
89
+
90
+ $outputAttributes = array(
91
+ 'Quantity',
92
+ 'Id',
93
+ 'ImageUrl',
94
+ 'Name',
95
+ 'OriginalPrice',
96
+ 'Price',
97
+ );
98
+
99
+ $brands = $this->_getHelper()->getProductBrands($product, $item->getStoreId());
100
+ if (!empty($brands)) {
101
+ $this->setData('Brands', $brands);
102
+ $outputAttributes[] = 'Brands';
103
+ }
104
+
105
+ if (!empty($variantName)) {
106
+ $outputAttributes[] = 'VariantName';
107
+ }
108
+
109
+ $catIds = $product->getCategoryIds();
110
+ $categoriesResult = array();
111
+ if ($catIds AND is_array($catIds) AND count($catIds)) {
112
+ foreach ($catIds as $catId) {
113
+ $category = Mage::getModel('catalog/category')
114
+ ->setStoreId($item->getStoreId())
115
+ ->load($catId);
116
+
117
+ if ($category AND $category->getId()) {
118
+ $categoriesResult[] = $category->getName();
119
+ }
120
+ }
121
+ }
122
+ if (count($categoriesResult)) {
123
+ $this->setData('Categories', $categoriesResult);
124
+ $outputAttributes[] = 'Categories';
125
+ }
126
+
127
+ if ($isAdded == true) {
128
+ $this->setData('Added', true);
129
+ array_unshift($outputAttributes, 'Added');
130
+ }
131
+
132
+ return $this->toArray($outputAttributes);
133
+ }
134
+
135
+ private function _getHelper()
136
+ {
137
+ return Mage::helper('koan_seg');
138
+ }
139
+ }
app/code/community/Koan/Seg/Model/Seg/Range.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+ class Koan_Seg_Model_Seg_Range extends Varien_Object
11
+ {
12
+ public function prepare($category)
13
+ {
14
+ if (!$category) {
15
+ Mage::throwException('Category is not valid!');
16
+ }
17
+
18
+ if (!$categoryId = $category->getId()) {
19
+ Mage::throwException('Missing Category Id');
20
+ }
21
+
22
+ $categoryData = array('Categories');
23
+
24
+ $lastCatName = $category->getName();
25
+ $lastCategoryAdjust = 1;
26
+
27
+ if ($path = $category->getPath()) {
28
+ $path = explode('/', $path);
29
+
30
+ for ($i = 2; $i < count($path) - $lastCategoryAdjust; $i++) {
31
+
32
+ $cat = Mage::getModel('catalog/category')->load($path[$i]);
33
+ if ($cat && $cat->getIsActive()) {
34
+
35
+ $categoryData['Categories'][] = $cat->getName();
36
+ }
37
+ }
38
+
39
+ $categoryData['Categories'][] = $lastCatName;
40
+ }
41
+
42
+ $this->setData($categoryData);
43
+ return $this->toArray(array('Categories'));
44
+ }
45
+ }
app/code/community/Koan/Seg/controllers/Adminhtml/SegController.php ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_Adminhtml_SegController extends Mage_Adminhtml_Controller_Action
4
+ {
5
+ private function _checkIfCronActivated()
6
+ {
7
+
8
+ $isCronActive = Mage::helper('koan_seg')->isExportCronEnabled();
9
+ if (!$isCronActive) {
10
+
11
+ Mage::getSingleton('adminhtml/session')->addWarning($this->__('Exporter CRON operation is set to INACTIVE in config and export will not work.
12
+ Please navigate to System -> Configuration -> Koan -> Seg Options and set "Enable export CRON value to YES"'));
13
+ }
14
+
15
+ return $this;
16
+
17
+ }
18
+
19
+ public function exporterAction()
20
+ {
21
+ $this->_checkIfCronActivated();
22
+
23
+ $this->loadLayout();
24
+ $this->_setActiveMenu('seg');
25
+ $this->renderLayout();
26
+ }
27
+
28
+ public function createBatchCustomerAction()
29
+ {
30
+ $exporter = Mage::getSingleton('koan_seg/seg_exporter');
31
+
32
+ $batchCollection = Mage::getResourceModel('koan_seg/batch_status_collection');
33
+ $batchCollection->addFieldToFilter('entity_type', 'customers');
34
+ $batchCollection->addFieldToFilter('current_status',
35
+ array('nin' => array(Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_COMPLETE, Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_ERROR)
36
+ ));
37
+
38
+ try {
39
+
40
+ $isRunning = $batchCollection->getSize();
41
+ if ($isRunning) {
42
+ Mage::throwException('Can not start new batch while one is still running. Please try again later!');
43
+ return;
44
+ }
45
+
46
+ $exporter->generateCustomersExportBatch();
47
+ $msg = Mage::helper('koan_seg')->__('New batch has been scheduled successfully. Export will start with new CRON job in several minutes.');
48
+
49
+ Mage::getSingleton('adminhtml/session')->addSuccess($msg);
50
+ } catch (Exception $e) {
51
+ Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
52
+ }
53
+
54
+ $this->_redirect('adminhtml/seg/exporter');
55
+ return;
56
+ }
57
+
58
+ public function createBatchOrderAction()
59
+ {
60
+ $orderDateFilter = null;
61
+
62
+ $fromDateFiter = $this->getRequest()->getParam('order_date_from');
63
+ if ($fromDateFiter AND $fromDateFiter != 'NO_FILTER') {
64
+ $filter = @json_decode($fromDateFiter, true);
65
+ if ($filter AND is_array($filter) AND isset($filter['date'])) {
66
+ $orderDateFilter = $filter['date'];
67
+ }
68
+ }
69
+
70
+ $exporter = Mage::getSingleton('koan_seg/seg_exporter');
71
+
72
+ $batchCollection = Mage::getResourceModel('koan_seg/batch_status_collection');
73
+ $batchCollection->addFieldToFilter('entity_type', 'history_orders');
74
+ $batchCollection->addFieldToFilter('current_status',
75
+ array('nin' => array(Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_COMPLETE, Koan_Seg_Model_Seg_Exporter::BATCH_STATUS_ERROR)
76
+ ));
77
+
78
+ try {
79
+
80
+ $isRunning = $batchCollection->getSize();
81
+ if ($isRunning) {
82
+ Mage::throwException('Can not start new batch while one is still running. Please try again later!');
83
+ return;
84
+ }
85
+
86
+ $exporter->generateHistoryOrdersExportBatch($orderDateFilter);
87
+ $msg = Mage::helper('koan_seg')->__('New batch has been scheduled successfully. Export will start with new CRON job in several minutes.');
88
+
89
+ Mage::getSingleton('adminhtml/session')->addSuccess($msg);
90
+ } catch (Exception $e) {
91
+ Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
92
+ }
93
+
94
+ $this->_redirect('adminhtml/seg/exporter');
95
+ return;
96
+ }
97
+
98
+ public function massDeleteAction()
99
+ {
100
+ $batchIds = $this->getRequest()->getParam('batch_id', null);
101
+ if (is_null($batchIds) OR !is_array($batchIds) OR count($batchIds) < 1) {
102
+ Mage::getSingleton('adminhtml/session')->addError(Mage::helper('adminhtml')->__('Please select batch(es)'));
103
+ } else {
104
+ try {
105
+ foreach ($batchIds as $batchId) {
106
+ $batch = Mage::getModel('koan_seg/batch_status')->load($batchId);
107
+ if ($batch AND $batch->getId()) {
108
+ $batch->delete();
109
+ }
110
+ }
111
+ Mage::getSingleton('adminhtml/session')->addSuccess(
112
+ Mage::helper('adminhtml')->__(
113
+ 'Total of %d rows(s) were successfully deleted', count($batchIds)
114
+ )
115
+ );
116
+ } catch (Exception $e) {
117
+ Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
118
+ }
119
+ }
120
+ $this->_redirect('adminhtml/seg/exporter');
121
+ }
122
+
123
+ }
app/code/community/Koan/Seg/controllers/IndexController.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Koan_Seg_IndexController extends Mage_Core_Controller_Front_Action
4
+ {
5
+
6
+ public function indexAction()
7
+ {
8
+ exit;
9
+ }
10
+
11
+ }
app/code/community/Koan/Seg/etc/adminhtml.xml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <acl>
4
+ <resources>
5
+ <all>
6
+ <title>Allow Everything</title>
7
+ </all>
8
+ <admin>
9
+ <children>
10
+ <system>
11
+ <children>
12
+ <config>
13
+ <children>
14
+ <koan_seg translate="title">
15
+ <title>Seg</title>
16
+ <sort_order>100</sort_order>
17
+ </koan_seg>
18
+ </children>
19
+ </config>
20
+ </children>
21
+ </system>
22
+ <seg translate="title" module="koan_seg">
23
+ <children>
24
+ <exporter>
25
+ <title>Exporter</title>
26
+ </exporter>
27
+ </children>
28
+ <sort_order>1</sort_order>
29
+ </seg>
30
+ </children>
31
+ </admin>
32
+ </resources>
33
+ </acl>
34
+ <menu>
35
+ <seg module="koan_seg">
36
+ <title>Seg</title>
37
+ <sort_order>100</sort_order>
38
+ <children>
39
+ <exporter module="koan_seg">
40
+ <title>Exporter</title>
41
+ <sort_order>1</sort_order>
42
+ <action>adminhtml/seg/exporter</action>
43
+ </exporter>
44
+ </children>
45
+ </seg>
46
+ </menu>
47
+ </config>
app/code/community/Koan/Seg/etc/config.xml ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <Koan_Seg>
5
+ <version>1.0.0.2</version>
6
+ </Koan_Seg>
7
+ </modules>
8
+ <global>
9
+ <helpers>
10
+ <koan_seg>
11
+ <class>Koan_Seg_Helper</class>
12
+ </koan_seg>
13
+ </helpers>
14
+ <models>
15
+ <koan_seg>
16
+ <class>Koan_Seg_Model</class>
17
+ <resourceModel>koan_seg_resource</resourceModel>
18
+ </koan_seg>
19
+ <koan_seg_resource>
20
+ <class>Koan_Seg_Model_Resource</class>
21
+ <entities>
22
+ <batch_status>
23
+ <table>seg_batch_status</table>
24
+ </batch_status>
25
+ </entities>
26
+ </koan_seg_resource>
27
+ </models>
28
+ <blocks>
29
+ <koan_seg>
30
+ <class>Koan_Seg_Block</class>
31
+ </koan_seg>
32
+ </blocks>
33
+ <resources>
34
+ <koan_seg_setup>
35
+ <setup>
36
+ <module>Koan_Seg</module>
37
+ </setup>
38
+ </koan_seg_setup>
39
+ </resources>
40
+ </global>
41
+ <frontend>
42
+ <routers>
43
+ <koan_seg>
44
+ <use>standard</use>
45
+ <args>
46
+ <module>Koan_Seg</module>
47
+ <frontName>seg</frontName>
48
+ </args>
49
+ </koan_seg>
50
+ </routers>
51
+ <events>
52
+ <checkout_cart_product_add_after>
53
+ <observers>
54
+ <koan_seg>
55
+ <class>koan_seg/observer</class>
56
+ <method>checkoutCartProductAddAfter</method>
57
+ </koan_seg>
58
+ </observers>
59
+ </checkout_cart_product_add_after>
60
+ <sales_quote_item_save_after>
61
+ <observers>
62
+ <koan_seg>
63
+ <class>koan_seg/observer</class>
64
+ <method>cartItemAfterSave</method>
65
+ </koan_seg>
66
+ </observers>
67
+ </sales_quote_item_save_after>
68
+ <sales_order_place_after>
69
+ <observers>
70
+ <koan_seg>
71
+ <class>koan_seg/observer</class>
72
+ <method>orderPlaceAfter</method>
73
+ </koan_seg>
74
+ </observers>
75
+ </sales_order_place_after>
76
+ </events>
77
+ <layout>
78
+ <updates>
79
+ <koan_seg>
80
+ <file>seg.xml</file>
81
+ </koan_seg>
82
+ </updates>
83
+ </layout>
84
+ </frontend>
85
+ <admin>
86
+ <routers>
87
+ <adminhtml>
88
+ <args>
89
+ <modules>
90
+ <Koan_Seg before="Mage_Adminhtml">Koan_Seg_Adminhtml</Koan_Seg>
91
+ </modules>
92
+ </args>
93
+ </adminhtml>
94
+ </routers>
95
+ </admin>
96
+ <adminhtml>
97
+ <layout>
98
+ <updates>
99
+ <integration>
100
+ <file>seg.xml</file>
101
+ </integration>
102
+ </updates>
103
+ </layout>
104
+ </adminhtml>
105
+ <crontab>
106
+ <jobs>
107
+ <seg_export>
108
+ <schedule>
109
+ <!--<cron_expr>*/5 * * * *</cron_expr>-->
110
+ <cron_expr>* * * * *</cron_expr>
111
+ </schedule>
112
+ <run>
113
+ <model>koan_seg/observer::cronprocess</model>
114
+ </run>
115
+ </seg_export>
116
+ </jobs>
117
+ </crontab>
118
+ <default>
119
+ <koan_seg>
120
+ <endpoint>
121
+ <order_history>https://tracker.segapp.com/magento/%s/order-history</order_history>
122
+ <update_customers>https://tracker.segapp.com/magento/%s/update-customers</update_customers>
123
+ <order_placed>https://tracker.segapp.com/magento/%s/order-placed</order_placed>
124
+ </endpoint>
125
+ <general>
126
+ <orders_export_batch_size>200</orders_export_batch_size>
127
+ <customers_export_batch_size>500</customers_export_batch_size>
128
+ <rollbar_report_params>0</rollbar_report_params>
129
+ <export_cron_enable>1</export_cron_enable>
130
+ </general>
131
+ </koan_seg>
132
+ </default>
133
+ </config>
app/code/community/Koan/Seg/etc/system.xml ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <tabs>
4
+ <koan_seg translate="label" module="koan_seg">
5
+ <label>Seg</label>
6
+ <sort_order>100</sort_order>
7
+ </koan_seg>
8
+ </tabs>
9
+ <sections>
10
+ <koan_seg translate="label" module="koan_seg">
11
+ <label>Settings</label>
12
+ <tab>koan_seg</tab>
13
+ <sort_order>1000</sort_order>
14
+ <show_in_default>1</show_in_default>
15
+ <show_in_website>0</show_in_website>
16
+ <show_in_store>0</show_in_store>
17
+ <groups>
18
+ <general translate="label" module="koan_seg">
19
+ <label>General settings</label>
20
+ <frontend_type>text</frontend_type>
21
+ <sort_order>100</sort_order>
22
+ <show_in_default>1</show_in_default>
23
+ <show_in_website>1</show_in_website>
24
+ <show_in_store>0</show_in_store>
25
+ <fields>
26
+ <seg_website_id translate="label">
27
+ <label>Seg Website Id</label>
28
+ <frontend_type>text</frontend_type>
29
+ <sort_order>100</sort_order>
30
+ <show_in_default>1</show_in_default>
31
+ <show_in_website>0</show_in_website>
32
+ <show_in_store>0</show_in_store>
33
+ <comment><![CDATA[<a href="https://www.segapp.com/signup" target="_blank">Not yet registered with Seg? Click here to create account</a>]]></comment>
34
+ </seg_website_id>
35
+ <orders_export_batch_size translate="label">
36
+ <label>Orders export batch size</label>
37
+ <frontend_type>text</frontend_type>
38
+ <sort_order>110</sort_order>
39
+ <show_in_default>1</show_in_default>
40
+ <show_in_website>0</show_in_website>
41
+ <show_in_store>0</show_in_store>
42
+ </orders_export_batch_size>
43
+ <customers_export_batch_size translate="label">
44
+ <label>Customers export batch size</label>
45
+ <frontend_type>text</frontend_type>
46
+ <sort_order>120</sort_order>
47
+ <show_in_default>1</show_in_default>
48
+ <show_in_website>0</show_in_website>
49
+ <show_in_store>0</show_in_store>
50
+ </customers_export_batch_size>
51
+ <brand_attr_code translate="label">
52
+ <label>"Brand" attribute code</label>
53
+ <frontend_type>text</frontend_type>
54
+ <sort_order>130</sort_order>
55
+ <show_in_default>1</show_in_default>
56
+ <show_in_website>0</show_in_website>
57
+ <show_in_store>0</show_in_store>
58
+ <comment>Code of attribute which represents product brand</comment>
59
+ </brand_attr_code>
60
+ <rollbar_report_params translate="label">
61
+ <label>Enable diagnostics logging</label>
62
+ <frontend_type>select</frontend_type>
63
+ <source_model>adminhtml/system_config_source_yesno</source_model>
64
+ <sort_order>140</sort_order>
65
+ <show_in_default>1</show_in_default>
66
+ <show_in_website>0</show_in_website>
67
+ <show_in_store>0</show_in_store>
68
+ <comment>Only enable if instructed to do so by the Seg team.</comment>
69
+ </rollbar_report_params>
70
+ <export_cron_enable>
71
+ <label>Enable export CRON</label>
72
+ <frontend_type>select</frontend_type>
73
+ <source_model>adminhtml/system_config_source_yesno</source_model>
74
+ <sort_order>150</sort_order>
75
+ <show_in_default>1</show_in_default>
76
+ <show_in_website>0</show_in_website>
77
+ <show_in_store>0</show_in_store>
78
+ <comment>This CRON is needed for exporter. Disable this option after successful export of Customers and History orders.</comment>
79
+ </export_cron_enable>
80
+ </fields>
81
+ </general>
82
+ <advanced>
83
+ <label>Advanced settings</label>
84
+ <frontend_type>text</frontend_type>
85
+ <sort_order>150</sort_order>
86
+ <show_in_default>1</show_in_default>
87
+ <show_in_website>0</show_in_website>
88
+ <show_in_store>0</show_in_store>
89
+ <fields>
90
+ <php_memory_limit>
91
+ <label>Exporter PHP Memory limit override</label>
92
+ <frontend_type>text</frontend_type>
93
+ <sort_order>160</sort_order>
94
+ <show_in_default>1</show_in_default>
95
+ <show_in_website>0</show_in_website>
96
+ <show_in_store>0</show_in_store>
97
+ <comment>This is advanced value. Please do not change unless don't know what are you doing! Leave empty or 0 to turn Off</comment>
98
+ </php_memory_limit>
99
+ </fields>
100
+ </advanced>
101
+ </groups>
102
+ </koan_seg>
103
+ </sections>
104
+ </config>
app/code/community/Koan/Seg/lib/Rollbar.php ADDED
@@ -0,0 +1,986 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Singleton-style wrapper around RollbarNotifier
4
+ *
5
+ * Unless you need multiple RollbarNotifier instances in the same project, use this.
6
+ */
7
+ class Rollbar {
8
+ /** @var RollbarNotifier */
9
+ public static $instance = null;
10
+
11
+ public static function init($config = array(), $set_exception_handler = true, $set_error_handler = true, $report_fatal_errors = true) {
12
+ // Heroku support
13
+ // Use env vars for configuration, if set
14
+ if (isset($_ENV['ROLLBAR_ACCESS_TOKEN']) && !isset($config['access_token'])) {
15
+ $config['access_token'] = $_ENV['ROLLBAR_ACCESS_TOKEN'];
16
+ }
17
+ if (isset($_ENV['ROLLBAR_ENDPOINT']) && !isset($config['endpoint'])) {
18
+ $config['endpoint'] = $_ENV['ROLLBAR_ENDPOINT'];
19
+ }
20
+ if (isset($_ENV['HEROKU_APP_DIR']) && !isset($config['root'])) {
21
+ $config['root'] = $_ENV['HEROKU_APP_DIR'];
22
+ }
23
+
24
+ self::$instance = new RollbarNotifier($config);
25
+
26
+ if ($set_exception_handler) {
27
+ set_exception_handler('Rollbar::report_exception');
28
+ }
29
+ if ($set_error_handler) {
30
+ set_error_handler('Rollbar::report_php_error');
31
+ }
32
+ if ($report_fatal_errors) {
33
+ register_shutdown_function('Rollbar::report_fatal_error');
34
+ }
35
+
36
+ if (self::$instance->batched) {
37
+ register_shutdown_function('Rollbar::flush');
38
+ }
39
+ }
40
+
41
+ public static function report_exception($exc, $extra_data = null, $payload_data = null) {
42
+ if (self::$instance == null) {
43
+ return;
44
+ }
45
+ return self::$instance->report_exception($exc, $extra_data, $payload_data);
46
+ }
47
+
48
+ public static function report_message($message, $level = 'error', $extra_data = null, $payload_data = null) {
49
+ if (self::$instance == null) {
50
+ return;
51
+ }
52
+ return self::$instance->report_message($message, $level, $extra_data, $payload_data);
53
+ }
54
+
55
+ public static function report_fatal_error() {
56
+ // Catch any fatal errors that are causing the shutdown
57
+ $last_error = error_get_last();
58
+ if (!is_null($last_error)) {
59
+ switch ($last_error['type']) {
60
+ case E_PARSE:
61
+ case E_ERROR:
62
+ self::$instance->report_php_error($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
63
+ break;
64
+ }
65
+ }
66
+ }
67
+
68
+ // This function must return false so that the default php error handler runs
69
+ public static function report_php_error($errno, $errstr, $errfile, $errline) {
70
+ if (self::$instance != null) {
71
+ self::$instance->report_php_error($errno, $errstr, $errfile, $errline);
72
+ }
73
+ return false;
74
+ }
75
+
76
+ public static function flush() {
77
+ self::$instance->flush();
78
+ }
79
+ }
80
+
81
+ // Send errors that have these levels
82
+ if (!defined('ROLLBAR_INCLUDED_ERRNO_BITMASK')) {
83
+ define('ROLLBAR_INCLUDED_ERRNO_BITMASK', E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR);
84
+ }
85
+
86
+ class RollbarNotifier {
87
+ const VERSION = "0.14.0";
88
+
89
+ // required
90
+ public $access_token = '';
91
+
92
+ // optional / defaults
93
+ public $base_api_url = 'https://api.rollbar.com/api/1/';
94
+ public $batch_size = 50;
95
+ public $batched = true;
96
+ public $branch = null;
97
+ public $capture_error_backtraces = true;
98
+ public $code_version = null;
99
+ public $environment = 'production';
100
+ public $error_sample_rates = array();
101
+ // available handlers: blocking, agent
102
+ public $handler = 'blocking';
103
+ public $agent_log_location = '/var/tmp';
104
+ public $host = null;
105
+ /** @var iRollbarLogger */
106
+ public $logger = null;
107
+ public $included_errno = ROLLBAR_INCLUDED_ERRNO_BITMASK;
108
+ public $person = null;
109
+ public $person_fn = null;
110
+ public $root = '';
111
+ public $scrub_fields = array('passwd', 'pass', 'password', 'secret', 'confirm_password',
112
+ 'password_confirmation', 'auth_token', 'csrf_token');
113
+ public $shift_function = true;
114
+ public $timeout = 3;
115
+ public $report_suppressed = false;
116
+ public $use_error_reporting = false;
117
+ public $proxy = null;
118
+
119
+ private $config_keys = array('access_token', 'base_api_url', 'batch_size', 'batched', 'branch',
120
+ 'capture_error_backtraces', 'code_version', 'environment', 'error_sample_rates', 'handler',
121
+ 'agent_log_location', 'host', 'logger', 'included_errno', 'person', 'person_fn', 'root',
122
+ 'scrub_fields', 'shift_function', 'timeout', 'report_suppressed', 'use_error_reporting', 'proxy');
123
+
124
+ // cached values for request/server/person data
125
+ private $_request_data = null;
126
+ private $_server_data = null;
127
+ private $_person_data = null;
128
+
129
+ // payload queue, used when $batched is true
130
+ private $_queue = array();
131
+
132
+ // file handle for agent log
133
+ private $_agent_log = null;
134
+
135
+ private $_iconv_available = null;
136
+
137
+ private $_mt_randmax;
138
+
139
+ private $_curl_ipresolve_supported;
140
+
141
+ public function __construct($config) {
142
+ foreach ($this->config_keys as $key) {
143
+ if (isset($config[$key])) {
144
+ $this->$key = $config[$key];
145
+ }
146
+ }
147
+
148
+ if (!$this->access_token && $this->handler != 'agent') {
149
+ $this->log_error('Missing access token');
150
+ }
151
+
152
+ // fill in missing values in error_sample_rates
153
+ $levels = array(E_WARNING, E_NOTICE, E_USER_ERROR, E_USER_WARNING,
154
+ E_USER_NOTICE, E_STRICT, E_RECOVERABLE_ERROR);
155
+
156
+ // PHP 5.3.0
157
+ if (defined('E_DEPRECATED')) {
158
+ $levels = array_merge($levels, array(E_DEPRECATED, E_USER_DEPRECATED));
159
+ }
160
+
161
+ // PHP 5.3.0
162
+ $this->_curl_ipresolve_supported = defined('CURLOPT_IPRESOLVE');
163
+
164
+ $curr = 1;
165
+ for ($i = 0, $num = count($levels); $i < $num; $i++) {
166
+ $level = $levels[$i];
167
+ if (isset($this->error_sample_rates[$level])) {
168
+ $curr = $this->error_sample_rates[$level];
169
+ } else {
170
+ $this->error_sample_rates[$level] = $curr;
171
+ }
172
+ }
173
+
174
+ // cache this value
175
+ $this->_mt_randmax = mt_getrandmax();
176
+ }
177
+
178
+ public function report_exception($exc, $extra_data = null, $payload_data = null) {
179
+ try {
180
+ if (!$exc instanceof Exception) {
181
+ throw new Exception('Report exception requires an instance of Exception.');
182
+ }
183
+
184
+ return $this->_report_exception($exc, $extra_data, $payload_data);
185
+ } catch (Exception $e) {
186
+ try {
187
+ $this->log_error("Exception while reporting exception");
188
+ } catch (Exception $e) {
189
+ // swallow
190
+ }
191
+ }
192
+ }
193
+
194
+ public function report_message($message, $level = 'error', $extra_data = null, $payload_data = null) {
195
+ try {
196
+ return $this->_report_message($message, $level, $extra_data, $payload_data);
197
+ } catch (Exception $e) {
198
+ try {
199
+ $this->log_error("Exception while reporting message");
200
+ } catch (Exception $e) {
201
+ // swallow
202
+ }
203
+ }
204
+ }
205
+
206
+ public function report_php_error($errno, $errstr, $errfile, $errline) {
207
+ try {
208
+ return $this->_report_php_error($errno, $errstr, $errfile, $errline);
209
+ } catch (Exception $e) {
210
+ try {
211
+ $this->log_error("Exception while reporting php error");
212
+ } catch (Exception $e) {
213
+ // swallow
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Flushes the queue.
220
+ * Called internally when the queue exceeds $batch_size, and by Rollbar::flush
221
+ * on shutdown.
222
+ */
223
+ public function flush() {
224
+ $queue_size = $this->queueSize();
225
+ if ($queue_size > 0) {
226
+ $this->log_info('Flushing queue of size ' . $queue_size);
227
+ $this->send_batch($this->_queue);
228
+ $this->_queue = array();
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Returns the current queue size.
234
+ */
235
+ public function queueSize() {
236
+ return count($this->_queue);
237
+ }
238
+
239
+ /**
240
+ * @param Exception $exc
241
+ */
242
+ protected function _report_exception(Exception $exc, $extra_data = null, $payload_data = null) {
243
+ if (!$this->check_config()) {
244
+ return;
245
+ }
246
+
247
+ if (error_reporting() === 0 && !$this->report_suppressed) {
248
+ // ignore
249
+ return;
250
+ }
251
+
252
+ $data = $this->build_base_data();
253
+
254
+ $trace_chain = $this->build_exception_trace_chain($exc, $extra_data);
255
+
256
+ if (count($trace_chain) > 1) {
257
+ $data['body']['trace_chain'] = $trace_chain;
258
+ } else {
259
+ $data['body']['trace'] = $trace_chain[0];
260
+ }
261
+
262
+ // request, server, person data
263
+ $data['request'] = $this->build_request_data();
264
+ $data['server'] = $this->build_server_data();
265
+ $data['person'] = $this->build_person_data();
266
+
267
+ // merge $payload_data into $data
268
+ // (overriding anything already present)
269
+ if ($payload_data !== null && is_array($payload_data)) {
270
+ foreach ($payload_data as $key => $val) {
271
+ $data[$key] = $val;
272
+ }
273
+ }
274
+
275
+ $data = $this->_sanitize_keys($data);
276
+ array_walk_recursive($data, array($this, '_sanitize_utf8'));
277
+
278
+ $payload = $this->build_payload($data);
279
+ $this->send_payload($payload);
280
+
281
+ return $data['uuid'];
282
+ }
283
+
284
+ protected function _sanitize_utf8(&$value) {
285
+ if (!isset($this->_iconv_available)) {
286
+ $this->_iconv_available = function_exists('iconv');
287
+ }
288
+ if (is_string($value) && $this->_iconv_available) {
289
+ $value = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
290
+ }
291
+ }
292
+
293
+ protected function _sanitize_keys(array $data) {
294
+ $response = array();
295
+ foreach ($data as $key => $value) {
296
+ $this->_sanitize_utf8($key);
297
+ if (is_array($value)) {
298
+ $response[$key] = $this->_sanitize_keys($value);
299
+ } else {
300
+ $response[$key] = $value;
301
+ }
302
+ }
303
+
304
+ return $response;
305
+ }
306
+
307
+ protected function _report_php_error($errno, $errstr, $errfile, $errline) {
308
+ if (!$this->check_config()) {
309
+ return;
310
+ }
311
+
312
+ if (error_reporting() === 0 && !$this->report_suppressed) {
313
+ // ignore
314
+ return;
315
+ }
316
+
317
+ if ($this->use_error_reporting && (error_reporting() & $errno) === 0) {
318
+ // ignore
319
+ return;
320
+ }
321
+
322
+ if ($this->included_errno != -1 && ($errno & $this->included_errno) != $errno) {
323
+ // ignore
324
+ return;
325
+ }
326
+
327
+ if (isset($this->error_sample_rates[$errno])) {
328
+ // get a float in the range [0, 1)
329
+ // mt_rand() is inclusive, so add 1 to mt_randmax
330
+ $float_rand = mt_rand() / ($this->_mt_randmax + 1);
331
+ if ($float_rand > $this->error_sample_rates[$errno]) {
332
+ // skip
333
+ return;
334
+ }
335
+ }
336
+
337
+ $data = $this->build_base_data();
338
+
339
+ // set error level and error constant name
340
+ $level = 'info';
341
+ $constant = '#' . $errno;
342
+ switch ($errno) {
343
+ case 1:
344
+ $level = 'error';
345
+ $constant = 'E_ERROR';
346
+ break;
347
+ case 2:
348
+ $level = 'warning';
349
+ $constant = 'E_WARNING';
350
+ break;
351
+ case 8:
352
+ $level = 'info';
353
+ $constant = 'E_NOTICE';
354
+ break;
355
+ case 256:
356
+ $level = 'error';
357
+ $constant = 'E_USER_ERROR';
358
+ break;
359
+ case 512:
360
+ $level = 'warning';
361
+ $constant = 'E_USER_WARNING';
362
+ break;
363
+ case 1024:
364
+ $level = 'info';
365
+ $constant = 'E_USER_NOTICE';
366
+ break;
367
+ case 2048:
368
+ $level = 'info';
369
+ $constant = 'E_STRICT';
370
+ break;
371
+ case 4096:
372
+ $level = 'error';
373
+ $constant = 'E_RECOVERABLE_ERROR';
374
+ break;
375
+ case 8192:
376
+ $level = 'info';
377
+ $constant = 'E_DEPRECATED';
378
+ break;
379
+ case 16384:
380
+ $level = 'info';
381
+ $constant = 'E_USER_DEPRECATED';
382
+ break;
383
+ }
384
+ $data['level'] = $level;
385
+
386
+ // use the whole $errstr. may want to split this by colon for better de-duping.
387
+ $error_class = $constant . ': ' . $errstr;
388
+
389
+ // build something that looks like an exception
390
+ $data['body'] = array(
391
+ 'trace' => array(
392
+ 'frames' => $this->build_error_frames($errfile, $errline),
393
+ 'exception' => array(
394
+ 'class' => $error_class
395
+ )
396
+ )
397
+ );
398
+
399
+ // request, server, person data
400
+ $data['request'] = $this->build_request_data();
401
+ $data['server'] = $this->build_server_data();
402
+ $data['person'] = $this->build_person_data();
403
+
404
+ array_walk_recursive($data, array($this, '_sanitize_utf8'));
405
+
406
+ $payload = $this->build_payload($data);
407
+ $this->send_payload($payload);
408
+
409
+ return $data['uuid'];
410
+ }
411
+
412
+ protected function _report_message($message, $level, $extra_data, $payload_data) {
413
+ if (!$this->check_config()) {
414
+ return;
415
+ }
416
+
417
+ $data = $this->build_base_data();
418
+ $data['level'] = strtolower($level);
419
+
420
+ $message_obj = array('body' => $message);
421
+ if ($extra_data !== null && is_array($extra_data)) {
422
+ // merge keys from $extra_data to $message_obj
423
+ foreach ($extra_data as $key => $val) {
424
+ if ($key == 'body') {
425
+ // rename to 'body_' to avoid clobbering
426
+ $key = 'body_';
427
+ }
428
+ $message_obj[$key] = $val;
429
+ }
430
+ }
431
+ $data['body']['message'] = $message_obj;
432
+
433
+ $data['request'] = $this->build_request_data();
434
+ $data['server'] = $this->build_server_data();
435
+ $data['person'] = $this->build_person_data();
436
+
437
+ // merge $payload_data into $data
438
+ // (overriding anything already present)
439
+ if ($payload_data !== null && is_array($payload_data)) {
440
+ foreach ($payload_data as $key => $val) {
441
+ $data[$key] = $val;
442
+ }
443
+ }
444
+
445
+ array_walk_recursive($data, array($this, '_sanitize_utf8'));
446
+
447
+ $payload = $this->build_payload($data);
448
+ $this->send_payload($payload);
449
+
450
+ return $data['uuid'];
451
+ }
452
+
453
+ protected function check_config() {
454
+ return $this->handler == 'agent' || ($this->access_token && strlen($this->access_token) == 32);
455
+ }
456
+
457
+ protected function build_request_data() {
458
+ if ($this->_request_data === null) {
459
+ $request = array(
460
+ 'url' => $this->scrub_url($this->current_url()),
461
+ 'user_ip' => $this->user_ip(),
462
+ 'headers' => $this->headers(),
463
+ 'method' => isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null,
464
+ );
465
+
466
+ if ($_GET) {
467
+ $request['GET'] = $this->scrub_request_params($_GET);
468
+ }
469
+ if ($_POST) {
470
+ $request['POST'] = $this->scrub_request_params($_POST);
471
+ }
472
+ if (isset($_SESSION) && $_SESSION) {
473
+ $request['session'] = $this->scrub_request_params($_SESSION);
474
+ }
475
+ $this->_request_data = $request;
476
+ }
477
+
478
+ return $this->_request_data;
479
+ }
480
+
481
+ protected function scrub_url($url) {
482
+ $url_query = parse_url($url, PHP_URL_QUERY);
483
+ if (!$url_query) return $url;
484
+ parse_str($url_query, $parsed_output);
485
+ // using x since * requires URL-encoding
486
+ $scrubbed_params = $this->scrub_request_params($parsed_output, 'x');
487
+ $scrubbed_url = str_replace($url_query, http_build_query($scrubbed_params), $url);
488
+ return $scrubbed_url;
489
+ }
490
+
491
+ protected function scrub_request_params($params, $replacement = '*') {
492
+ $scrubbed = array();
493
+ $potential_regex_filters = array_filter($this->scrub_fields, function($field) {
494
+ return strpos($field, '/') === 0;
495
+ });
496
+ foreach ($params as $k => $v) {
497
+ if ($this->_key_should_be_scrubbed($k, $potential_regex_filters)) {
498
+ $scrubbed[$k] = $this->_scrub($v, $replacement);
499
+ } elseif (is_array($v)) {
500
+ // recursively handle array params
501
+ $scrubbed[$k] = $this->scrub_request_params($v, $replacement);
502
+ } else {
503
+ $scrubbed[$k] = $v;
504
+ }
505
+ }
506
+
507
+ return $scrubbed;
508
+ }
509
+
510
+ protected function _key_should_be_scrubbed($key, $potential_regex_filters) {
511
+ if (in_array($key, $this->scrub_fields)) return true;
512
+ foreach ($potential_regex_filters as $potential_regex) {
513
+ if (@preg_match($potential_regex, $key)) return true;
514
+ }
515
+ return false;
516
+ }
517
+
518
+ protected function _scrub($value, $replacement = '*') {
519
+ $count = is_array($value) ? count($value) : strlen($value);
520
+ return str_repeat($replacement, $count);
521
+ }
522
+
523
+ protected function headers() {
524
+ $headers = array();
525
+ foreach ($this->scrub_request_params($_SERVER) as $key => $val) {
526
+ if (substr($key, 0, 5) == 'HTTP_') {
527
+ // convert HTTP_CONTENT_TYPE to Content-Type, HTTP_HOST to Host, etc.
528
+ $name = strtolower(substr($key, 5));
529
+ if (strpos($name, '_') != -1) {
530
+ $name = preg_replace('/ /', '-', ucwords(preg_replace('/_/', ' ', $name)));
531
+ } else {
532
+ $name = ucfirst($name);
533
+ }
534
+ $headers[$name] = $val;
535
+ }
536
+ }
537
+
538
+ if (count($headers) > 0) {
539
+ return $headers;
540
+ } else {
541
+ // serializes to emtpy json object
542
+ return new stdClass;
543
+ }
544
+ }
545
+
546
+ protected function current_url() {
547
+ if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
548
+ $proto = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']);
549
+ } else if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
550
+ $proto = 'https';
551
+ } else {
552
+ $proto = 'http';
553
+ }
554
+
555
+ if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
556
+ $host = $_SERVER['HTTP_X_FORWARDED_HOST'];
557
+ } else if (!empty($_SERVER['HTTP_HOST'])) {
558
+ $parts = explode(':', $_SERVER['HTTP_HOST']);
559
+ $host = $parts[0];
560
+ } else if (!empty($_SERVER['SERVER_NAME'])) {
561
+ $host = $_SERVER['SERVER_NAME'];
562
+ } else {
563
+ $host = 'unknown';
564
+ }
565
+
566
+ if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
567
+ $port = $_SERVER['HTTP_X_FORWARDED_PORT'];
568
+ } else if (!empty($_SERVER['SERVER_PORT'])) {
569
+ $port = $_SERVER['SERVER_PORT'];
570
+ } else {
571
+ $port = 80;
572
+ }
573
+
574
+ $path = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
575
+
576
+ $url = $proto . '://' . $host;
577
+
578
+ if (($proto == 'https' && $port != 443) || ($proto == 'http' && $port != 80)) {
579
+ $url .= ':' . $port;
580
+ }
581
+
582
+ $url .= $path;
583
+
584
+ return $url;
585
+ }
586
+
587
+ protected function user_ip() {
588
+ $forwardfor = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : null;
589
+ if ($forwardfor) {
590
+ // return everything until the first comma
591
+ $parts = explode(',', $forwardfor);
592
+ return $parts[0];
593
+ }
594
+ $realip = isset($_SERVER['HTTP_X_REAL_IP']) ? $_SERVER['HTTP_X_REAL_IP'] : null;
595
+ if ($realip) {
596
+ return $realip;
597
+ }
598
+ return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
599
+ }
600
+
601
+ /**
602
+ * @param Exception $exc
603
+ * @param mixed $extra_data
604
+ * @return array
605
+ */
606
+ protected function build_exception_trace(Exception $exc, $extra_data = null)
607
+ {
608
+ $message = $exc->getMessage();
609
+
610
+ $trace = array(
611
+ 'frames' => $this->build_exception_frames($exc),
612
+ 'exception' => array(
613
+ 'class' => get_class($exc),
614
+ 'message' => !empty($message) ? $message : 'unknown',
615
+ ),
616
+ );
617
+
618
+ if ($extra_data !== null) {
619
+ $trace['extra'] = $extra_data;
620
+ }
621
+
622
+ return $trace;
623
+ }
624
+
625
+ /**
626
+ * @param Exception $exc
627
+ * @param array $extra_data
628
+ * @return array
629
+ */
630
+ protected function build_exception_trace_chain(Exception $exc, $extra_data = null)
631
+ {
632
+ $chain = array();
633
+ $chain[] = $this->build_exception_trace($exc, $extra_data);
634
+
635
+ $previous = $exc->getPrevious();
636
+
637
+ while ($previous instanceof Exception) {
638
+ $chain[] = $this->build_exception_trace($previous);
639
+ $previous = $previous->getPrevious();
640
+ }
641
+
642
+ return $chain;
643
+ }
644
+
645
+ /**
646
+ * @param Exception $exc
647
+ * @return array
648
+ */
649
+ protected function build_exception_frames(Exception $exc) {
650
+ $frames = array();
651
+
652
+ foreach ($exc->getTrace() as $frame) {
653
+ $frames[] = array(
654
+ 'filename' => isset($frame['file']) ? $frame['file'] : '<internal>',
655
+ 'lineno' => isset($frame['line']) ? $frame['line'] : 0,
656
+ 'method' => $frame['function']
657
+ // TODO include args? need to sanitize first.
658
+ );
659
+ }
660
+
661
+ // rollbar expects most recent call to be last, not first
662
+ $frames = array_reverse($frames);
663
+
664
+ // add top-level file and line to end of the reversed array
665
+ $frames[] = array(
666
+ 'filename' => $exc->getFile(),
667
+ 'lineno' => $exc->getLine()
668
+ );
669
+
670
+ $this->shift_method($frames);
671
+
672
+ return $frames;
673
+ }
674
+
675
+ protected function shift_method(&$frames) {
676
+ if ($this->shift_function) {
677
+ // shift 'method' values down one frame, so they reflect where the call
678
+ // occurs (like Rollbar expects), instead of what is being called.
679
+ for ($i = count($frames) - 1; $i > 0; $i--) {
680
+ $frames[$i]['method'] = $frames[$i - 1]['method'];
681
+ }
682
+ $frames[0]['method'] = '<main>';
683
+ }
684
+ }
685
+
686
+ protected function build_error_frames($errfile, $errline) {
687
+ if ($this->capture_error_backtraces) {
688
+ $frames = array();
689
+ $backtrace = debug_backtrace();
690
+ foreach ($backtrace as $frame) {
691
+ // skip frames in this file
692
+ if (isset($frame['file']) && $frame['file'] == __FILE__) {
693
+ continue;
694
+ }
695
+ // skip the confusing set_error_handler frame
696
+ if ($frame['function'] == 'report_php_error' && count($frames) == 0) {
697
+ continue;
698
+ }
699
+
700
+ $frames[] = array(
701
+ // Sometimes, file and line are not set. See:
702
+ // http://stackoverflow.com/questions/4581969/why-is-debug-backtrace-not-including-line-number-sometimes
703
+ 'filename' => isset($frame['file']) ? $frame['file'] : "<internal>",
704
+ 'lineno' => isset($frame['line']) ? $frame['line'] : 0,
705
+ 'method' => $frame['function']
706
+ );
707
+ }
708
+
709
+ // rollbar expects most recent call last, not first
710
+ $frames = array_reverse($frames);
711
+
712
+ // add top-level file and line to end of the reversed array
713
+ $frames[] = array(
714
+ 'filename' => $errfile,
715
+ 'lineno' => $errline
716
+ );
717
+
718
+ $this->shift_method($frames);
719
+
720
+ return $frames;
721
+ } else {
722
+ return array(
723
+ array(
724
+ 'filename' => $errfile,
725
+ 'lineno' => $errline
726
+ )
727
+ );
728
+ }
729
+ }
730
+
731
+ protected function build_server_data() {
732
+ if ($this->_server_data === null) {
733
+ $server_data = array();
734
+
735
+ if ($this->host === null) {
736
+ // PHP 5.3.0
737
+ if (function_exists('gethostname')) {
738
+ $this->host = gethostname();
739
+ } else {
740
+ $this->host = php_uname('n');
741
+ }
742
+ }
743
+ $server_data['host'] = $this->host;
744
+
745
+ if ($this->branch) {
746
+ $server_data['branch'] = $this->branch;
747
+ }
748
+ if ($this->root) {
749
+ $server_data['root'] = $this->root;
750
+ }
751
+ $this->_server_data = $server_data;
752
+ }
753
+ return $this->_server_data;
754
+ }
755
+
756
+ protected function build_person_data() {
757
+ // return cached value if non-null
758
+ // it *is* possible for it to really be null (i.e. user is not logged in)
759
+ // but we'll keep trying anyway until we get a logged-in user value.
760
+ if ($this->_person_data == null) {
761
+ // first priority: try to use $this->person
762
+ if ($this->person && is_array($this->person)) {
763
+ if (isset($this->person['id'])) {
764
+ $this->_person_data = $this->person;
765
+ return $this->_person_data;
766
+ }
767
+ }
768
+
769
+ // second priority: try to use $this->person_fn
770
+ if ($this->person_fn && is_callable($this->person_fn)) {
771
+ $data = @call_user_func($this->person_fn);
772
+ if (isset($data['id'])) {
773
+ $this->_person_data = $data;
774
+ return $this->_person_data;
775
+ }
776
+ }
777
+ } else {
778
+ return $this->_person_data;
779
+ }
780
+
781
+ return null;
782
+ }
783
+
784
+ protected function build_base_data($level = 'error') {
785
+ $data = array(
786
+ 'timestamp' => time(),
787
+ 'environment' => $this->environment,
788
+ 'level' => $level,
789
+ 'language' => 'php',
790
+ 'framework' => 'php',
791
+ 'notifier' => array(
792
+ 'name' => 'rollbar-php',
793
+ 'version' => self::VERSION
794
+ ),
795
+ 'uuid' => $this->uuid4()
796
+ );
797
+
798
+ if ($this->code_version) {
799
+ $data['code_version'] = $this->code_version;
800
+ }
801
+
802
+ return $data;
803
+ }
804
+
805
+ protected function build_payload($data) {
806
+ $payload = array(
807
+ 'data' => $data
808
+ );
809
+
810
+ if ($this->access_token) {
811
+ $payload['access_token'] = $this->access_token;
812
+ }
813
+
814
+ return $payload;
815
+ }
816
+
817
+ protected function send_payload($payload) {
818
+ if ($this->batched) {
819
+ if ($this->queueSize() >= $this->batch_size) {
820
+ // flush queue before adding payload to queue
821
+ $this->flush();
822
+ }
823
+ $this->_queue[] = $payload;
824
+ } else {
825
+ $this->_send_payload($payload);
826
+ }
827
+ }
828
+
829
+ /**
830
+ * Sends a single payload to the /item endpoint.
831
+ * $payload - php array
832
+ */
833
+ protected function _send_payload($payload) {
834
+ if ($this->handler == 'agent') {
835
+ $this->_send_payload_agent($payload);
836
+ } else {
837
+ $this->_send_payload_blocking($payload);
838
+ }
839
+ }
840
+
841
+ protected function _send_payload_blocking($payload) {
842
+ $this->log_info("Sending payload");
843
+ $access_token = $payload['access_token'];
844
+ $post_data = json_encode($payload);
845
+ $this->make_api_call('item', $access_token, $post_data);
846
+ }
847
+
848
+ protected function _send_payload_agent($payload) {
849
+ // Only open this the first time
850
+ if (empty($this->_agent_log)) {
851
+ $this->load_agent_file();
852
+ }
853
+ $this->log_info("Writing payload to file");
854
+ fwrite($this->_agent_log, json_encode($payload) . "\n");
855
+ }
856
+
857
+ /**
858
+ * Sends a batch of payloads to the /batch endpoint.
859
+ * A batch is just an array of standalone payloads.
860
+ * $batch - php array of payloads
861
+ */
862
+ protected function send_batch($batch) {
863
+ if ($this->handler == 'agent') {
864
+ $this->send_batch_agent($batch);
865
+ } else {
866
+ $this->send_batch_blocking($batch);
867
+ }
868
+ }
869
+
870
+ protected function send_batch_agent($batch) {
871
+ $this->log_info("Writing batch to file");
872
+
873
+ // Only open this the first time
874
+ if (empty($this->_agent_log)) {
875
+ $this->load_agent_file();
876
+ }
877
+
878
+ foreach ($batch as $item) {
879
+ fwrite($this->_agent_log, json_encode($item) . "\n");
880
+ }
881
+ }
882
+
883
+ protected function send_batch_blocking($batch) {
884
+ $this->log_info("Sending batch");
885
+ $access_token = $batch[0]['access_token'];
886
+ $post_data = json_encode($batch);
887
+ $this->make_api_call('item_batch', $access_token, $post_data);
888
+ }
889
+
890
+ protected function make_api_call($action, $access_token, $post_data) {
891
+ $url = $this->base_api_url . $action . '/';
892
+
893
+ $ch = curl_init();
894
+ curl_setopt($ch, CURLOPT_URL, $url);
895
+ curl_setopt($ch, CURLOPT_POST, true);
896
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
897
+ curl_setopt($ch, CURLOPT_VERBOSE, false);
898
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
899
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
900
+ curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
901
+ curl_setopt($ch, CURLOPT_HTTPHEADER, array('X-Rollbar-Access-Token: ' . $access_token));
902
+
903
+ if ($this->proxy) {
904
+ $proxy = is_array($this->proxy) ? $this->proxy : array('address' => $this->proxy);
905
+
906
+ if (isset($proxy['address'])) {
907
+ curl_setopt($ch, CURLOPT_PROXY, $proxy['address']);
908
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
909
+ }
910
+
911
+ if (isset($proxy['username']) && isset($proxy['password'])) {
912
+ curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['username'] . ':' . $proxy['password']);
913
+ }
914
+ }
915
+
916
+ if ($this->_curl_ipresolve_supported) {
917
+ curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
918
+ }
919
+
920
+ $result = curl_exec($ch);
921
+ $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
922
+ curl_close($ch);
923
+
924
+ if ($status_code != 200) {
925
+ $this->log_warning('Got unexpected status code from Rollbar API ' . $action .
926
+ ': ' .$status_code);
927
+ $this->log_warning('Output: ' .$result);
928
+ } else {
929
+ $this->log_info('Success');
930
+ }
931
+ }
932
+
933
+ /* Logging */
934
+
935
+ protected function log_info($msg) {
936
+ $this->log_message("INFO", $msg);
937
+ }
938
+
939
+ protected function log_warning($msg) {
940
+ $this->log_message("WARNING", $msg);
941
+ }
942
+
943
+ protected function log_error($msg) {
944
+ $this->log_message("ERROR", $msg);
945
+ }
946
+
947
+ protected function log_message($level, $msg) {
948
+ if ($this->logger !== null) {
949
+ $this->logger->log($level, $msg);
950
+ }
951
+ }
952
+
953
+ // from http://www.php.net/manual/en/function.uniqid.php#94959
954
+ protected function uuid4() {
955
+ mt_srand();
956
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
957
+ // 32 bits for "time_low"
958
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff),
959
+
960
+ // 16 bits for "time_mid"
961
+ mt_rand(0, 0xffff),
962
+
963
+ // 16 bits for "time_hi_and_version",
964
+ // four most significant bits holds version number 4
965
+ mt_rand(0, 0x0fff) | 0x4000,
966
+
967
+ // 16 bits, 8 bits for "clk_seq_hi_res",
968
+ // 8 bits for "clk_seq_low",
969
+ // two most significant bits holds zero and one for variant DCE1.1
970
+ mt_rand(0, 0x3fff) | 0x8000,
971
+
972
+ // 48 bits for "node"
973
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
974
+ );
975
+ }
976
+
977
+ protected function load_agent_file() {
978
+ $this->_agent_log = fopen($this->agent_log_location . '/rollbar-relay.' . getmypid() . '.' . microtime(true) . '.rollbar', 'a');
979
+ }
980
+ }
981
+
982
+ interface iRollbarLogger {
983
+ public function log($level, $msg);
984
+ }
985
+
986
+ class Ratchetio extends Rollbar {}
app/code/community/Koan/Seg/sql/koan_seg_setup/install-1.0.0.0.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @category Koan
5
+ * @package Seg
6
+ * @author Seg <hello@getseg.com, http://getseg.com>
7
+ * @copyright Seg <http://getseg.com>
8
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
9
+ */
10
+
11
+ $installer = $this;
12
+
13
+ $installer->startSetup();
14
+ $installer->endSetup();
app/code/community/Koan/Seg/sql/koan_seg_setup/upgrade-1.0.0.0-1.0.0.1.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this;
4
+ $installer->startSetup();
5
+
6
+ $table = $installer->getConnection()
7
+ ->newTable($installer->getTable('koan_seg/batch_status'))
8
+ ->addColumn('id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
9
+ 'identity' => true,
10
+ 'unsigned' => true,
11
+ 'nullable' => false,
12
+ 'primary' => true
13
+ ))
14
+ ->addColumn('entity_type', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array(
15
+ 'nullable' => false
16
+ ))
17
+ ->addColumn('start_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(), 'Start Time')
18
+ ->addColumn('end_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array(), 'End Time')
19
+ ->addColumn('total_row_count', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
20
+ 'nullable' => false
21
+ ))
22
+ ->addColumn('num_rows_processed', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
23
+ 'nullable' => false
24
+ ))
25
+ ->addColumn('current_status', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
26
+ 'nullable' => false,
27
+ 'default' => '0'
28
+ ))
29
+ ->addColumn('comment', Varien_Db_Ddl_Table::TYPE_TEXT, null, array(
30
+ 'nullable' => false
31
+ ))
32
+ ->addColumn('num_retried', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
33
+ 'nullable' => false
34
+ ));
35
+ $installer->getConnection()->createTable($table);
app/code/community/Koan/Seg/sql/koan_seg_setup/upgrade-1.0.0.1-1.0.0.2.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this;
4
+ $installer->startSetup();
5
+
6
+ $installer->getConnection()->addColumn(
7
+ $installer->getTable('koan_seg/batch_status'),
8
+ 'filter',
9
+ array(
10
+ 'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
11
+ 'length' => 255,
12
+ 'nullable' => true,
13
+ 'default' => null,
14
+ 'comment' => 'Date filter for orders'
15
+ )
16
+ );
app/design/adminhtml/default/default/layout/seg.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <layout>
3
+ <adminhtml_seg_exporter>
4
+ <reference name="content">
5
+ <block type="koan_seg/adminhtml_exporter" name="exporter"/>
6
+ </reference>
7
+ </adminhtml_seg_exporter>
8
+ </layout>
app/design/frontend/base/default/layout/seg.xml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+
3
+ <layout>
4
+ <default>
5
+ <reference name="head">
6
+ <block type="koan_seg/header" name="seg.tracking" template="seg/config.phtml"/>
7
+ </reference>
8
+ <reference name="before_body_end">
9
+ <block type="koan_seg/track" name="seg.track" template="seg/tracking.phtml"/>
10
+ </reference>
11
+ </default>
12
+ </layout>
app/design/frontend/base/default/template/seg/config.phtml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <script type="text/javascript">
2
+ ;(function () { var a, b, c, e = window, f = document, g = arguments, h = "script", i = ["config", "track", "identify", "callback"], j = function () { var a, b = this; for (b._s = [], a = 0; a < i.length; a++) !function (a) { b[a] = function () { return b._s.push([a].concat(Array.prototype.slice.call(arguments, 0))), b } }(i[a]) }; for (e._seg = e._seg || {}, a = 0; g.length > a; a++) e._seg[g[a]] = e[g[a]] = e[g[a]] || new j; b = f.createElement(h), b.async = 1, b.src = "https://segapp.blob.core.windows.net/release/seg-analytics.min.js", c = f.getElementsByTagName(h)[0], c.parentNode.insertBefore(b, c) }("seg"));
3
+
4
+ <?php $currentStore = Mage::app()->getStore()->getId() ?>
5
+ // Unique website identifying code
6
+ seg.config({
7
+ site: "<?php echo Mage::helper('koan_seg')->getWebsiteId($currentStore) ?>"
8
+ });
9
+ </script>
app/design/frontend/base/default/template/seg/tracking.phtml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ <?php if($customerData = $this->getCustomerData()): ?>
3
+ seg.identify(<?php echo $customerData ?>);
4
+ <?php endif; ?>
5
+ <?php if($dob = $this->getCustomerDob()): ?>
6
+ seg.identify("DateOfBirth", new Date("<?php echo $dob ?>"));
7
+ <?php endif; ?>
8
+
9
+ <?php $trackingEventCode = $this->getTrackingEventCode() ?>
10
+ <?php if ($trackingEventCode AND is_array($trackingEventCode)): ?>
11
+ <?php if (array_key_exists('event', $trackingEventCode) AND array_key_exists('data', $trackingEventCode)): ?>
12
+ <?php if (is_null($trackingEventCode['data'])): ?>
13
+ seg.track();
14
+ <?php else: ?>
15
+ seg.track("<?php echo $trackingEventCode['event'] ?>", <?php echo $trackingEventCode['data'] ?>);
16
+ <?php endif; ?>
17
+ <?php else: ?>
18
+ seg.track();
19
+ <?php endif; ?>
20
+ <?php else: ?>
21
+ seg.track();
22
+ <?php endif; ?>
23
+ </script>
app/etc/modules/Koan_Seg.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <config>
2
+ <modules>
3
+ <Koan_Seg>
4
+ <active>true</active>
5
+ <codePool>community</codePool>
6
+ </Koan_Seg>
7
+ </modules>
8
+ </config>
package.xml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>seg</name>
4
+ <version>1.0.0.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL 3.0)</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>Easily and automatically make more revenue from MailChimp by understanding customer preferences and behaviour.</summary>
10
+ <description>Seg makes Magento and MailChimp really work together. If you run an Magento store and use MailChimp, you really need to use Seg; it will transform the effectiveness of your email marketing campaigns in MailChimp. &#xD;
11
+ &#xD;
12
+ = What does Seg do? = &#xD;
13
+ Seg for Magento + MailChimp captures all your visitors behaviour from their email, web browsing and orders and turns that into rich profiles of their preferences for your categories &amp; products, and how likely they are to respond to emails, offers and more. &#xD;
14
+ &#xD;
15
+ You can then create your campaign in MailChimp as normal, but you use Seg to target the right customers for your content based on a wide range of factors including: &#xD;
16
+ - How much money you want to make from your campaign (we predict that too!) &#xD;
17
+ - How relevant the content is to your customers (customers who have "shown an interest" vs. "love" category/brand X) &#xD;
18
+ - Whether they've bought recently &#xD;
19
+ - Whether they've visited recently &#xD;
20
+ - And so much more. &#xD;
21
+ &#xD;
22
+ You can also: &#xD;
23
+ - See how much revenue and profit your MailChimp campaigns have made in detail &#xD;
24
+ - See your customers in real time and what they are doing on your website &#xD;
25
+ - See a detailed profile for every customer that has ever visited or bought from your store and what their preferences are &#xD;
26
+ - See exactly how you can improve your email marketing (through MailChimp), inlcuding how much money you could make &#xD;
27
+ - See your campaign performance over time via our email analytics system &#xD;
28
+ &#xD;
29
+ Seg automatically does this for you: &#xD;
30
+ - Tracks all customers behaviour and orders &#xD;
31
+ - Protects you from over-emailing your customers (you set the limit) &#xD;
32
+ - Predicts the revenue you will make from a campaign (knowing individual customer's behaviours and preferences) so you can tailor the audience to make the most money &#xD;
33
+ - Rates each campaign (as 1-5 stars) so you can easily find the best campaigns and improve them / resend them / use for inspiration in the future</description>
34
+ <notes>First stable release.</notes>
35
+ <authors><author><name>Seg</name><user>KoanLeeroy</user><email>Lee@koan.is</email></author></authors>
36
+ <date>2015-07-31</date>
37
+ <time>13:30:44</time>
38
+ <contents><target name="magecommunity"><dir name="Koan"><dir name="Seg"><dir name="Block"><dir name="Adminhtml"><dir name="Exporter"><dir name="Grid"><dir name="Renderer"><file name="Status.php" hash="bab35c7c04bfb7b2ca628a08f9d0b9cf"/></dir></dir><file name="Grid.php" hash="5c4d9013851c80b14d9240e800cc5f54"/></dir><file name="Exporter.php" hash="c8ad547a58f8bd3b56d1d18627389e32"/></dir><file name="Header.php" hash="bd07e0f326730dd7ccb796eb2f33a1c7"/><file name="Track.php" hash="b04e1d5090b6c6f9e2d34a46ac31c9ae"/></dir><dir name="Helper"><file name="Data.php" hash="147ac05f825ce5dcf11f104ea629d0c4"/></dir><dir name="Model"><dir name="Batch"><file name="Status.php" hash="1eebea45e69dbf320a871ef535c49772"/></dir><dir name="Exception"><file name="Handler.php" hash="60cef7aef2b276fbd42d17f5406a4d64"/></dir><file name="Logger.php" hash="d2f267ea04603190d30dbcf185b6651f"/><file name="Observer.php" hash="faad57341c555ba99061605c0562a907"/><dir name="Resource"><dir name="Batch"><dir name="Status"><file name="Collection.php" hash="814df925981a77007df1d7710d407ec5"/></dir><file name="Status.php" hash="f96826a954f09bc9fc67e97f14dec207"/></dir></dir><dir name="Seg"><file name="Basket.php" hash="00c1c6b1ddeb5300b5d79d7793a4e7c9"/><file name="Client.php" hash="8c0563b5e1103a9b7991a13c5f8051d3"/><file name="Customer.php" hash="fc8c59e8cd5daaa7fd6a839c3be8ab11"/><file name="Exporter.php" hash="8201cddf650385f0371a41b5654a5176"/><dir name="Order"><file name="Line.php" hash="c2acaea156dcaedf8dbe50f3c98987e2"/></dir><file name="Order.php" hash="d3af49ea2c8a5c09829ff3f712c0058d"/><file name="Product.php" hash="dba1f75f98972013113392df55273922"/><dir name="Quote"><file name="Line.php" hash="352335afd649295052173e4c4d177e3c"/></dir><file name="Range.php" hash="81b1eed35211554c2a76df4160ff992a"/></dir></dir><dir name="controllers"><dir name="Adminhtml"><file name="SegController.php" hash="5c3fa5c56c91dff79a46fe862baf94fe"/></dir><file name="IndexController.php" hash="5d4a841c4f1bb1d3de2dfe1a325abe0f"/></dir><dir name="etc"><file name="adminhtml.xml" hash="0f44123ff295e29dc1f53967a1e5eff4"/><file name="config.xml" hash="7cca137d8acbc54de7ae46e86922aee1"/><file name="system.xml" hash="2a732cb77e3bba5d8d1d1caf8a146faf"/></dir><dir name="lib"><file name="Rollbar.php" hash="b93689fd5505f34a686cb5b979d0fdca"/></dir><dir name="sql"><dir name="koan_seg_setup"><file name="install-1.0.0.0.php" hash="983531e5fb85bdff121a5d0d74b34025"/><file name="upgrade-1.0.0.0-1.0.0.1.php" hash="9c91352527e09881251fd92766ac7a6f"/><file name="upgrade-1.0.0.1-1.0.0.2.php" hash="3a664531053ec11f56688527105cf8cd"/></dir></dir></dir><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="seg.xml" hash="4980a411970cde4aacae7d2a3c1815bb"/></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="seg.xml" hash="017d053269b5e2741eb58370696b1669"/></dir><dir name="template"><dir name="seg"><file name="config.phtml" hash="079d1614bcc44acc0766d9f43f5131a3"/><file name="tracking.phtml" hash="8085abd78c95b8eace352f64a07738ab"/></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Koan_Seg.xml" hash="7bea24d633fd1e53870ecc7fe19b4e41"/></dir></target></contents>
39
+ <compatible/>
40
+ <dependencies><required><php><min>5.1.0</min><max>6.0.0</max></php></required></dependencies>
41
+ </package>