Netmatter_Bridge - Version 1.1.0

Version Notes

The Volo extension to Magento, powered by Netmatter, allows all high performance Magento stores – from single instance up to multiple, multi-lingual stores – to carry out 2-way bulk data transfers with the Volo platform in real time, with no loss of Magento performance.

Download this release

Release Info

Developer Netmatter
Extension Netmatter_Bridge
Version 1.1.0
Comparing to
See all releases


Version 1.1.0

app/code/community/Netmatter/Bridge/Block/Adminhtml/Config.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Netmatter_Bridge_Block_Adminhtml_Config extends Mage_Core_Block_Template
4
+ {
5
+ /**
6
+ * Retrieves the integration's configuration
7
+ */
8
+ public function getConfig()
9
+ {
10
+ require ('./app/code/local/Netmatter/Bridge/plugin/bridge.php');
11
+
12
+ return $netmatter_bridge_config;
13
+ }
14
+ }
app/code/community/Netmatter/Bridge/Helper/Data.php ADDED
@@ -0,0 +1,2662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * @class Netmatter_Bridge_Helper_Data
5
+ */
6
+ class Netmatter_Bridge_Helper_Data extends Mage_Core_Helper_Abstract
7
+ {
8
+ private $db = array(); ///< Instances to the db
9
+
10
+ /**
11
+ * Retrieves the table prefix
12
+ *
13
+ * @return string
14
+ */
15
+ public function getTablePrefix()
16
+ {
17
+ return Mage::getConfig()->getTablePrefix();
18
+ }
19
+
20
+ /**
21
+ * Retrieves the integration version
22
+ *
23
+ * @return string the integration version
24
+ */
25
+ public function getIntegrationVersion()
26
+ {
27
+ return '1.6.5';
28
+ }
29
+
30
+ public function isEnabled()
31
+ {
32
+ return Mage::getStoreConfig('netmatter/settings/enabled',Mage::app()->getStore()) === '1';
33
+ }
34
+
35
+ public function getConfigClientName()
36
+ {
37
+ return Mage::getStoreConfig('netmatter/settings/client_name',Mage::app()->getStore());
38
+ }
39
+
40
+ public function getConfigSource()
41
+ {
42
+ return Mage::getStoreConfig('netmatter/settings/source',Mage::app()->getStore());
43
+ }
44
+
45
+ public function getConfigAuthenticationToken()
46
+ {
47
+ return Mage::getStoreConfig('netmatter/settings/authentication_token',Mage::app()->getStore());
48
+ }
49
+
50
+ public function initBridge()
51
+ {
52
+ require (Mage::getModuleDir('', 'Netmatter_Bridge') . '/plugin/bridge.php');
53
+
54
+ //Set version details
55
+ $bridge->setIntegrationVersion($this->getIntegrationVersion());
56
+ $bridge->setHostVersion($this->getHostVersion());
57
+
58
+ $bridge->setCredentials(
59
+ $this->getConfigClientName(),
60
+ $this->getConfigSource(),
61
+ $this->getConfigAuthenticationToken()
62
+ );
63
+
64
+ return $bridge;
65
+ }
66
+
67
+ /**
68
+ * Checks if current installation is single website or not
69
+ *
70
+ * @param array $websites the array with the websites
71
+ * @return boolean
72
+ */
73
+ public function isSingleWebsite($websites)
74
+ {
75
+ if (isset($websites[0]))
76
+ {
77
+ unset($websites[0]);
78
+ }
79
+
80
+ return count($websites) === 1;
81
+ }
82
+
83
+ /**
84
+ * Retrieves a list with the ids of the websites
85
+ *
86
+ * @return array array($website_id => $first_store_id)
87
+ */
88
+ public function getWebsites()
89
+ {
90
+ $websites = array();
91
+
92
+ $result = $this->db()->query("SELECT MIN(store_id) AS store_id, website_id FROM " . $this->getTablePrefix() . "core_store GROUP BY website_id;");
93
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
94
+ $websites[$row['website_id']] = $row['store_id'];
95
+
96
+ return $websites;
97
+ }
98
+
99
+ /**
100
+ * Retrieves a the website id for the given store_id
101
+ *
102
+ * @param int $store_id the id of the store
103
+ * @return int the website_id
104
+ */
105
+ public function getWebsiteIdByStoreId($store_id)
106
+ {
107
+ $result = $this->db()->query("SELECT website_id FROM " . $this->getTablePrefix() . "core_store WHERE store_id = :store_id;", array('store_id' => $store_id));
108
+ return ($row = $result->fetch(PDO::FETCH_ASSOC)) !== false;
109
+ }
110
+
111
+ /**
112
+ * Retrieves a list with the ids of the stores
113
+ *
114
+ * @return array containing the stores ids
115
+ */
116
+ public function getStores()
117
+ {
118
+ $stores = array();
119
+
120
+ $result = $this->db()->query("SELECT store_id, code FROM " . $this->getTablePrefix() . "core_store;");
121
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
122
+ $stores[$row['code']] = $row['store_id'];
123
+
124
+ return $stores;
125
+ }
126
+
127
+ /*
128
+ * Retrieves the "default" tax class for the products
129
+ *
130
+ * @return array
131
+ */
132
+ public function getProductDefaultTaxClass()
133
+ {
134
+ return Mage::getModel('tax/class')
135
+ ->getCollection()
136
+ ->addFieldToFilter('class_type', 'PRODUCT')
137
+ ->getFirstItem()->getData();
138
+ }
139
+
140
+ public function getProductTaxClassByName($name)
141
+ {
142
+ $tax_class = Mage::getModel('tax/class')
143
+ ->getCollection()
144
+ ->addFieldToFilter('class_type', 'PRODUCT')
145
+ ->addFieldToFilter('class_name', $name)
146
+ ->getFirstItem();
147
+
148
+ if ($tax_class === null)
149
+ {
150
+ return null;
151
+ }
152
+
153
+ return $tax_class->getData();
154
+ }
155
+
156
+ public function getAttributeSetAttributes($attribute_set_id)
157
+ {
158
+ $ret = array();
159
+ $result = $this->db()->query("SELECT attribute_id FROM " . $this->getTablePrefix() . "eav_entity_attribute WHERE attribute_set_id = :attribute_set_id", array('attribute_set_id' => $attribute_set_id));
160
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
161
+ $ret[$row['attribute_id']] = 1;
162
+
163
+ return $ret;
164
+ }
165
+
166
+ public function syncProductAttributeSet($mg_product, $attribute_set_name)
167
+ {
168
+ $attribute_set_id = $this->getAttributeSetIdByName($attribute_set_name, true); //Create if id doesnt exist
169
+ $mg_product->setAttributeSetId($attribute_set_id);
170
+ }
171
+
172
+ public function syncProductAttributes($mg_product, $attributes, $attribute_types)
173
+ {
174
+ //Read available product attributes
175
+ $available_attributes = $this->getProductAttributes();
176
+
177
+ //Read all attributes for that attribute_set_id and add any needed
178
+ $attribute_set_id = $mg_product->getAttributeSetId();
179
+ $set_attributes = $this->getAttributeSetAttributes($attribute_set_id);
180
+
181
+ //Create non existing attributes
182
+ foreach ($attributes AS $attribute_key => $attribute_value)
183
+ {
184
+ list($attribute_name, $attribute_code, $store_id) = $this->parseAttributeName($attribute_key);
185
+ $string_attribute_value = (string)$attribute_value;
186
+
187
+ //Check if product has already this value configured and its correct
188
+ if (!isset($available_attributes[$attribute_code]))
189
+ {
190
+ $new_attribute_data = array(
191
+ 'frontend_label' => $attribute_name
192
+ );
193
+
194
+ //Check if we have specific attribute format
195
+ if (isset($attribute_types[$attribute_key]))
196
+ {
197
+ if ($attribute_types[$attribute_key] === 'option')
198
+ {
199
+ $new_attribute_data['backend_type'] = 'int';
200
+ $new_attribute_data['frontend_input'] = 'select';
201
+ $new_attribute_data['is_global'] = '1';
202
+ $new_attribute_data['is_configurable'] = '1';
203
+ $new_attribute_data['apply_to'] = array('simple');
204
+ }
205
+ elseif (in_array($attribute_types[$attribute_key], array('identity', 'physical')))
206
+ {
207
+ $new_attribute_data['backend_type'] = 'varchar';
208
+ $new_attribute_data['frontend_input'] = 'text';
209
+ $new_attribute_data['is_global'] = '1';
210
+ }
211
+ }
212
+ else
213
+ {
214
+ $new_attribute_data['backend_type'] = 'varchar';
215
+ $new_attribute_data['frontend_input'] = 'text';
216
+ }
217
+
218
+ $new_attribute_id = $this->createProductAttribute($attribute_code, $new_attribute_data);
219
+
220
+ $available_attributes[$attribute_code] = array(
221
+ 'id' => $new_attribute_id,
222
+ 'frontend_input' => $new_attribute_data['frontend_input'],
223
+ 'backend_type' => $new_attribute_data['backend_type']
224
+ );
225
+ }
226
+
227
+ $attribute_backend_type = $available_attributes[$attribute_code]['backend_type'];
228
+ $attribute_frontend_input = $available_attributes[$attribute_code]['frontend_input'];
229
+
230
+ //If required add new attribute to set
231
+ if (!isset($set_attributes[$attribute_code]))
232
+ {
233
+ $setup = new Mage_Eav_Model_Entity_Setup('core_setup');
234
+ $attribute_group_id = $setup->getAttributeGroupId(Mage_Catalog_Model_Product::ENTITY, $attribute_set_id, 'General');
235
+ $attribute_id = $setup->getAttributeId('catalog_product', $attribute_code);
236
+ $setup->addAttributeToSet('catalog_product', $attribute_set_id, $attribute_group_id, $attribute_id, 0);
237
+ }
238
+
239
+ //Check attribute type and set the correct value
240
+ if ($attribute_backend_type === 'int' && $attribute_frontend_input === 'select')
241
+ {
242
+ if (isset($string_attribute_value{0}))
243
+ {
244
+ $attribute_option = $this->getCreateAttributeOption($available_attributes[$attribute_code]['id'], $string_attribute_value);
245
+ $this->setProductAttribute($mg_product, $attribute_code, $attribute_option['option_id'], $store_id);
246
+ }
247
+ else //Unset empty values
248
+ {
249
+ $this->setProductAttribute($mg_product, $attribute_code, null, $store_id);
250
+ }
251
+ }
252
+ elseif ($attribute_backend_type === 'int' && $attribute_frontend_input === 'boolean')
253
+ {
254
+ if (!isset($string_attribute_value{0}) || (intval($attribute_value) < 1 && !in_array(strtolower($attribute_value), array('t', 'y', 'true', 'yes', 'on'))))
255
+ {
256
+ $this->setProductAttribute($mg_product, $attribute_code, 0, $store_id);
257
+ }
258
+ else
259
+ {
260
+ $this->setProductAttribute($mg_product, $attribute_code, 1, $store_id);
261
+ }
262
+ }
263
+ elseif (in_array($attribute_backend_type, array('varchar', 'text')))
264
+ {
265
+ $this->setProductAttribute($mg_product, $attribute_code, $attribute_value, $store_id);
266
+ }
267
+ elseif ($attribute_backend_type === 'int')
268
+ {
269
+ $this->setProductAttribute($mg_product, $attribute_code, intval($attribute_value), $store_id);
270
+ }
271
+ elseif (in_array($attribute_backend_type, array('float', 'decimal')))
272
+ {
273
+ $this->setProductAttribute($mg_product, $attribute_code, number_format($attribute_value, 4), $store_id);
274
+ }
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Returns the destination label, code and store-view_id
280
+ *
281
+ * @param string $attribute_key
282
+ * @return array($attr_name, $attr_code, $scope)
283
+ */
284
+ private function parseAttributeName($attribute_key)
285
+ {
286
+ // identify per-site stuff
287
+ if (preg_match('/^(.*[^-])-(\d+)$/', $attribute_key, $matches))
288
+ {
289
+ // TODO: only use live store?
290
+ $attr_label = str_replace('--', '-', $matches[1]);
291
+ $store_id = $matches[2];
292
+ }
293
+ else
294
+ {
295
+ $attr_label = str_replace('--', '-', $attribute_key);
296
+ $store_id = null;
297
+ }
298
+
299
+ // standardise underscores/camel-casing to spaces
300
+ $attr_label = preg_replace(array('/_/', '/([A-Z])/'), array(' ', ' \\1'), trim($attr_label));
301
+ // remove extra whitespace
302
+ $attr_label = ucwords(trim(preg_replace('/\s+/', ' ', $attr_label)));
303
+
304
+ $attr_code = preg_replace('/\s+/', '_', strtolower($attr_label));
305
+
306
+ return array($attr_label, $attr_code, $store_id);
307
+ }
308
+
309
+ /**
310
+ * Sets default attributes to $mg_product, or directly sets per-store ones
311
+ *
312
+ * @param Mage_Catalog_Model_Product $mg_product
313
+ * @param string $attribute_code
314
+ * @param mixed $attribute_value
315
+ * @param int|null $store_id
316
+ */
317
+ private function setProductAttribute($mg_product, $attribute_code, $attribute_value, $store_id)
318
+ {
319
+ if (is_null($store_id))
320
+ {
321
+ $mg_product->setData($attribute_code, $attribute_value);
322
+ }
323
+ else
324
+ {
325
+ $p = Mage::getModel('catalog/product');
326
+ $p->setStoreId($store_id);
327
+ $p->setId($mg_product->getId());
328
+ $p->setData($attribute_code, $attribute_value);
329
+ $p->getResource()->saveAttribute($p, $attribute_code);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Retrieves the current website root category id
335
+ *
336
+ * @param string $store the store to use
337
+ * @return int
338
+ */
339
+ public function getRootCategoryId($store = 'default')
340
+ {
341
+ return Mage::app()->getStore($store)->getRootCategoryId();
342
+ }
343
+
344
+ public function getTopRootCategoryId()
345
+ {
346
+ $category_select = Mage::getModel('catalog/category')
347
+ ->getCollection()
348
+ ->addAttributeToFilter('parent_id', 0)
349
+ ->addAttributeToFilter('attribute_set_id', 0)
350
+ ->addAttributeToFilter('position', 0)
351
+ ->addAttributeToFilter('level', 0)
352
+ ->getSelect();
353
+
354
+ $result = $this->db()->query($category_select);
355
+ if ($row = $result->fetch(PDO::FETCH_ASSOC))
356
+ return $row['entity_id'];
357
+
358
+ throw new Exception('Could not get top root category id');
359
+ }
360
+
361
+ public function getFileContents($url)
362
+ {
363
+ //Init curl
364
+ $curl = curl_init();
365
+ curl_setopt($curl, CURLOPT_URL, str_replace(' ', '%20', $url));
366
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
367
+ curl_setopt($curl, CURLOPT_HEADER, false);
368
+
369
+ //Read contents
370
+ $contents = curl_exec($curl);
371
+
372
+ //Close curl
373
+ curl_close($curl);
374
+
375
+ return $contents;
376
+ }
377
+
378
+ public function getTempDirectory()
379
+ {
380
+ $tmp_dir = Mage::getBaseDir('tmp');
381
+
382
+ while (true)
383
+ {
384
+ $dirname = uniqid((string)time(), true);
385
+ if (!file_exists($tmp_dir . DS . $dirname))
386
+ {
387
+ break;
388
+ }
389
+ }
390
+
391
+ return $tmp_dir . DS . $dirname;
392
+ }
393
+
394
+ public function syncProductImages($mg_product, $images)
395
+ {
396
+ //The supported images mime types
397
+ $mime_types = array(
398
+ 'image/jpeg' => 'jpg',
399
+ 'image/gif' => 'gif',
400
+ 'image/png' => 'png'
401
+ );
402
+
403
+ $mg_product->getResource()->getAttribute('media_gallery')->getBackend()->afterLoad($mg_product);
404
+ $media_gallery_images = $mg_product->getMediaGalleryImages();
405
+
406
+ //Calculate existing images md5 hash
407
+ $existing_images = array();
408
+ foreach ($media_gallery_images AS $img)
409
+ {
410
+ $existing_images[md5_file($img['path'])] = (array)$img->getData();
411
+ }
412
+
413
+ $media_changes = array(
414
+ 'create' => array(),
415
+ 'update' => array()
416
+ );
417
+
418
+ $i = -1;
419
+
420
+ $main_image_position = null;
421
+
422
+ //Loop though images and check
423
+ foreach ($images AS $image)
424
+ {
425
+ $i++;
426
+
427
+ //Check if label has been set
428
+ if (!isset($image['label']{0}))
429
+ {
430
+ $image['label'] = '';
431
+ }
432
+
433
+ if (isset($image['contents']) && !isset($image['md5'])) //We can calculcate the md5
434
+ {
435
+ $image['md5'] = md5($image['contents']);
436
+ }
437
+
438
+ if ($main_image_position === null || (isset($image['is_main_image']) && $image['is_main_image'] === true))
439
+ {
440
+ $main_image_position = (string)$i;
441
+ }
442
+
443
+ //Check if md5 hash has been defined. If check all current images for matching
444
+ if (isset($image['md5']))
445
+ {
446
+ if (isset($existing_images[$image['md5']]))
447
+ {
448
+ $media_changes['update'][$existing_images[$image['md5']]['id']] = array(
449
+ 'label' => $image['label'],
450
+ 'position' => $i
451
+ );
452
+ continue;
453
+ }
454
+ }
455
+
456
+ //We can load images either using a url or reading directly the contents from the message
457
+ if (isset($image['contents'])) //From contents
458
+ {
459
+ $image_contents = @base64_decode((string)$image['contents'], true);
460
+ }
461
+ else
462
+ if (isset($image['url'])) //From url
463
+ {
464
+ $image_contents = $this->getFileContents($image['url']);
465
+ }
466
+ else //Not a valid source to read image contents
467
+ {
468
+ continue; //Contents or url not set, skip to next image
469
+ }
470
+
471
+ $image['md5'] = md5($image_contents);
472
+
473
+ //Check if we already have this image under different name
474
+ if (isset($existing_images[$image['md5']]))
475
+ {
476
+ $media_changes['update'][$existing_images[$image['md5']]['id']] = array(
477
+ 'label' => $image['label'],
478
+ 'position' => $i
479
+ );
480
+ continue;
481
+ }
482
+
483
+ //Make image filename
484
+ if (isset($image['filename']))
485
+ {
486
+ $image_filename = $image['filename'];
487
+ }
488
+ else
489
+ if (isset($image['url'])) //From url
490
+ {
491
+ $image_filename = basename($image['url']);
492
+ }
493
+ else //The md5 hash
494
+ {
495
+ $image_filename = $image['md5'];
496
+ }
497
+
498
+ //Write contents to a file, just to determine the mime type
499
+ $tmpfilename = tempnam(Mage::getBaseDir('tmp'), 'prefix');
500
+ file_put_contents($tmpfilename, $image_contents);
501
+
502
+ //Read contents minetype in order to create the filename extension
503
+ $image_mime_type = mime_content_type($tmpfilename);
504
+
505
+ //Remove file
506
+ unlink($tmpfilename);
507
+
508
+ //Check if mime type is valid
509
+ if (!isset($mime_types[$image_mime_type]))
510
+ {
511
+ throw new Exception('Invalid mime type on image: ' . $image['name']);
512
+ }
513
+
514
+ //From mime types, extract extension
515
+ $image_extension = $mime_types[$image_mime_type];
516
+
517
+ //Remove extension from image
518
+ $image_filename = preg_replace('/\.[^\.]+$/', '', $image_filename) . '.' . $image_extension;
519
+
520
+ $media_changes['create'][] = array(
521
+ 'filename' => $image_filename,
522
+ 'contents' => $image_contents,
523
+ 'label' => $image['label'],
524
+ 'position' => $i
525
+ );
526
+ }
527
+
528
+ //Update existing images
529
+ $media_gallery = $mg_product->getData('media_gallery');
530
+
531
+ foreach ($media_gallery['images'] AS $key => $media_gallery_image)
532
+ {
533
+ $image_id = $media_gallery_image['value_id'];
534
+ if (isset($media_changes['update'][$image_id]))
535
+ {
536
+ $media_gallery['images'][$key]['label'] = $media_changes['update'][$image_id]['label'];
537
+ $media_gallery['images'][$key]['position'] = $media_changes['update'][$image_id]['position'];
538
+ $mg_product->setData('media_gallery', $media_gallery);
539
+ }
540
+
541
+ if ($media_gallery_image['position'] === $main_image_position)
542
+ {
543
+ $mg_product->setImage($media_gallery_image['file']);
544
+ $mg_product->setSmallImage($media_gallery_image['file']);
545
+ $mg_product->setThumbnail($media_gallery_image['file']);
546
+ }
547
+ }
548
+
549
+ //Create new images
550
+ foreach ($media_changes['create'] AS $image_to_create)
551
+ {
552
+ //We must create a new tmp file with the correct extension, so adding to image gallery will work
553
+ $tmpdirectory = $this->getTempDirectory();
554
+ mkdir($tmpdirectory);
555
+ $tmpfilename = $tmpdirectory . DS . $image_to_create['filename'];
556
+ @file_put_contents($tmpfilename, $image_to_create['contents']);
557
+
558
+ //Add the image to the media gallery of the product
559
+ $mg_product->addImageToMediaGallery($tmpfilename, array(), true, false);
560
+
561
+ //Remove temp directory
562
+ rmdir($tmpdirectory, true);
563
+
564
+ //Add extra attrs
565
+ $media_gallery = $mg_product->getData('media_gallery');
566
+ $media_gallery_last_index = count($media_gallery['images']) - 1;
567
+
568
+ $media_gallery['images'][$media_gallery_last_index]['label'] = $image_to_create['label'];
569
+ $media_gallery['images'][$media_gallery_last_index]['position'] = $image_to_create['position'];
570
+ $mg_product->setData('media_gallery', $media_gallery);
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Shortcut function to add a category
576
+ *
577
+ * @param array $data the new category's data
578
+ * @return category
579
+ */
580
+ public function createCategory(array $data)
581
+ {
582
+ if (!isset($data['parent_id']))
583
+ {
584
+ $data['parent_id'] = $this->getTopRootCategoryId();
585
+ }
586
+
587
+ $category = Mage::getModel('catalog/category');
588
+ $category->setName($data['name']);
589
+ $category->setIsActive(1);
590
+ $category->setDisplayMode('PRODUCTS');
591
+ $category->setAttributeSetId(Mage::getModel('eav/entity')->setType('catalog_category')->getTypeId());
592
+ $category->setIsAnchor(isset($data['anchor']) ? $data['anchor'] : 1);
593
+ $category->setParentId($data['parent_id']);
594
+ $parentCategory = Mage::getModel('catalog/category')->load($data['parent_id']);
595
+
596
+ $category->setPath($parentCategory->getPath());
597
+
598
+ foreach (array('meta_title', 'description', 'meta_keywords', 'meta_description') AS $attr)
599
+ {
600
+ if (isset($data[$attr]))
601
+ {
602
+ $category->setData($attr, $data[$attr]);
603
+ }
604
+ }
605
+
606
+ if (isset($data['url_key']))
607
+ {
608
+ $category->setUrlKey($data['url_key']);
609
+ }
610
+
611
+ if (isset($data['redirect']))
612
+ {
613
+ $category->setRedirect($data['redirect']);
614
+ }
615
+
616
+ $category->save();
617
+
618
+ return $category;
619
+ }
620
+
621
+ public function getStoresRootCategoryIds()
622
+ {
623
+ $ret = array();
624
+
625
+ $result = $this->db()->query("SELECT root_category_id, website_id FROM " . $this->getTablePrefix() . "core_store_group WHERE website_id <> 0;");
626
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
627
+ {
628
+ $ret[$row['website_id']] = $row['root_category_id'];
629
+ }
630
+
631
+ return $ret;
632
+ }
633
+
634
+ /**
635
+ * Syncs the categoreis of the given product
636
+ */
637
+ public function syncProductCategories($mg_product, $category_data)
638
+ {
639
+ //Read current product category ids and their path
640
+ $product_categories_select = $mg_product->getCategoryCollection()
641
+ ->getSelect()
642
+ ->reset(Zend_Db_Select::COLUMNS)
643
+ ->columns(array('e.entity_id', 'e.path'));
644
+
645
+ $current_product_categories = array();
646
+ $result = $this->db()->query($product_categories_select);
647
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
648
+ {
649
+ $current_product_categories[$row['entity_id']] = $row['path'];
650
+ }
651
+
652
+ //For all stores get root category ids
653
+ $stores_root_category_ids = $this->getStoresRootCategoryIds();
654
+
655
+ $splitter_character = ' > ';
656
+
657
+ //Loop through category fullnames and extract all category names
658
+ $category_names = array();
659
+
660
+ foreach ($category_data AS $store_id => $store_categories)
661
+ {
662
+ foreach ($store_categories AS $store_category => $store_category_data)
663
+ {
664
+ $fullname_parts = preg_split('/' . $splitter_character . '/', $store_category);
665
+ foreach ($fullname_parts AS $fullname_part)
666
+ {
667
+ $category_names[$fullname_part] = 1;
668
+ }
669
+ }
670
+ }
671
+
672
+ //Read categories from db for the names
673
+ $category_select = Mage::getModel('catalog/category')
674
+ ->setStoreId(0)
675
+ ->getCollection()
676
+ ->addAttributeToFilter('name', array('in' => array_keys($category_names)))
677
+ ->getSelect()
678
+ ->reset(Zend_Db_Select::COLUMNS)
679
+ ->columns(array('e.entity_id', 'e.parent_id', 'at_name.value'));
680
+
681
+ $db_categories = array();
682
+
683
+ $result = $this->db()->query($category_select);
684
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
685
+ {
686
+ $db_categories[strtolower($row['value'])][$row['parent_id']] = $row['entity_id'];
687
+ }
688
+
689
+ $new_product_categories = array();
690
+ $root_categories_ids = array();
691
+
692
+ $top_category_id = $this->getTopRootCategoryId();
693
+
694
+ foreach ($stores_root_category_ids AS $store_id => $store_root_category_id)
695
+ {
696
+ if (isset($category_data[$store_id]))
697
+ {
698
+ $store_categories = $category_data[$store_id];
699
+ }
700
+ else
701
+ if (isset($category_data["0"]))
702
+ {
703
+ $store_categories = $category_data["0"];
704
+ }
705
+ else
706
+ {
707
+ continue; //Skip to next store
708
+ }
709
+
710
+ $new_product_categories[] = $store_root_category_id; //Auto append store category id
711
+
712
+ foreach ($store_categories AS $store_category_fullname => $store_category_data)
713
+ {
714
+ $fullname_parts = preg_split('/' . $splitter_character . '/', $store_category_fullname);
715
+ $row_category_id = $store_root_category_id;
716
+ $i = 0;
717
+ foreach ($fullname_parts AS $category_name)
718
+ {
719
+ $category_name_lower = strtolower($category_name);
720
+
721
+ $parent_id = $row_category_id;
722
+ if (isset($db_categories[$category_name_lower][$parent_id])) //Category already exist
723
+ {
724
+ $row_category_id = $db_categories[$category_name_lower][$parent_id];
725
+ }
726
+ else //Create new category
727
+ {
728
+ $new_category = $this->createCategory(array(
729
+ 'name' => $category_name,
730
+ 'parent_id' => $parent_id
731
+ ));
732
+ $row_category_id = $new_category->getId();
733
+ $db_categories[$category_name_lower][$parent_id] = $row_category_id;
734
+ }
735
+
736
+ if ($i === 0)
737
+ {
738
+ $root_categories_ids[$row_category_id] = 1;
739
+ }
740
+
741
+ $i++;
742
+ }
743
+
744
+ //Add cateogry to product
745
+ $new_product_categories[] = $row_category_id;
746
+ }
747
+ }
748
+
749
+ //We need to remove all children categories that belong to root categories that were processed
750
+ foreach ($stores_root_category_ids AS $store_id => $store_root_category_id)
751
+ {
752
+ foreach ($current_product_categories AS $current_category_id => $current_category_path)
753
+ {
754
+ if (preg_match('/^' . $top_category_id . '\/' . $store_root_category_id . '(\/|$)/', $current_category_path) > 0)
755
+ {
756
+ unset($current_product_categories[$current_category_id]);
757
+ }
758
+ }
759
+ }
760
+
761
+ //Create new product_categories array
762
+ $new_product_categories = array_merge(array_keys($current_product_categories), $new_product_categories);
763
+
764
+ //Check if old_categories and new_categories are different
765
+ $old_product_categories = $mg_product->getCategoryIds();
766
+ sort($old_product_categories);
767
+ sort($new_product_categories);
768
+
769
+ if ($old_product_categories !== $new_product_categories)
770
+ {
771
+ $mg_product->setCategoryIds($new_product_categories);
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Sets the status of the given order
777
+ *
778
+ * @param $order the magento order object
779
+ * @param $status the order's new status
780
+ */
781
+ public function setOrderStatus($order, $status)
782
+ {
783
+ //Check current order status. Do not continue if it is the same
784
+ if ($order->getStatus() === $status)
785
+ {
786
+ return;
787
+ }
788
+
789
+ //Find state based on status
790
+ $status_details = Mage::getModel('sales/order_status')
791
+ ->getCollection()
792
+ ->joinStates()
793
+ ->addFieldToFilter('main_table.status', $status)
794
+ ->getFirstItem();
795
+
796
+ //Update order's status. Magento does have a separate function for completed or closed
797
+ switch ($status_details['state'])
798
+ {
799
+ //Order is cancelled
800
+ case 'cancelled':
801
+ if (!$order->canCancel())
802
+ {
803
+ throw new Exception('Order cannot be cancelled');
804
+ }
805
+
806
+ $order->cancel();
807
+
808
+ break;
809
+
810
+ //Order is complete
811
+ case 'complete':
812
+
813
+ //Disable order observer. Wrong order status is sent to bridge when payment is not captured yet
814
+ Mage::register('netmatter_bridge_disable_order_observer', 1);
815
+
816
+ //Check if order can be invoiced
817
+ if ($order->canInvoice())
818
+ {
819
+ //Create invoice
820
+ $invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice();
821
+
822
+ $invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE);
823
+ $invoice->register();
824
+
825
+ //Save
826
+ $transaction = Mage::getModel('core/resource_transaction')
827
+ ->addObject($invoice)
828
+ ->addObject($invoice->getOrder());
829
+ $transaction->save();
830
+ }
831
+
832
+ //Set again state/status manually
833
+ $order->setData('state', 'complete');
834
+ $order->setStatus($status);
835
+ $history = $order->addStatusHistoryComment('', false);
836
+ $history->setIsCustomerNotified(true);
837
+
838
+ //Re-enable
839
+ Mage::register('netmatter_disable_order_observer', 0);
840
+
841
+ //Trigger order to be resend
842
+ Mage::helper('bridge/data')->sendOrder($order);
843
+
844
+ break;
845
+
846
+ //Order is closed. Not supported [Closed orders are orders that have had a credit memo assigned to it and the customer has been refunded for their order]
847
+ case 'closed':
848
+
849
+ break;
850
+
851
+ //Any other status
852
+ default:
853
+
854
+ $order->setState($status_details['state'], $status);
855
+
856
+ break;
857
+ }
858
+
859
+ //Save order
860
+ $order->save();
861
+ }
862
+
863
+ /**
864
+ * Calculates a hash from the given shipment details
865
+ *
866
+ * @param array $shipment the shipment to use
867
+ * @return string
868
+ */
869
+ public function calculateShipmentHash(array $shipment)
870
+ {
871
+ $x = array(
872
+ 'rows' => $shipment['rows'],
873
+ 'shippedOn' => $shipment['shippedOn']
874
+ );
875
+
876
+ return sha1(serialize($x));
877
+ }
878
+
879
+ /**
880
+ * Retrieves a shipment by the order and hash
881
+ *
882
+ * @param Order $order the order to use
883
+ * @param string $hash the hash code to search
884
+ * @return Shipment|null
885
+ */
886
+ public function getShipmentByOrderHash($order, $hash)
887
+ {
888
+ $shipments = $order->getShipmentsCollection();
889
+ foreach ($shipments AS $shipment)
890
+ {
891
+ $shash = $this->getShipmentHash($shipment);
892
+ if ($shash === $hash)
893
+ {
894
+ return $shipment;
895
+ }
896
+ }
897
+
898
+ return null;
899
+ }
900
+
901
+ /**
902
+ * Retrieves the shipment's hash for the given shipment
903
+ *
904
+ * @param Shipment $shipment the shipment to use
905
+ * @return string|null
906
+ */
907
+ public function getShipmentHash($shipment)
908
+ {
909
+ $prefix = 'Netmatter Hash: ';
910
+
911
+ //Hash is stored within shipment's comments
912
+ foreach ($shipment->getCommentsCollection() AS $comment)
913
+ {
914
+ $comment = $comment->getData('comment');
915
+ preg_match('/^Netmatter Hash: ([a-zA-Z0-9]+)$/', $comment, $matches);
916
+ if (count($matches) === 0)
917
+ {
918
+ return null;
919
+ }
920
+
921
+ return $matches[1];
922
+ }
923
+
924
+ return null;
925
+ }
926
+
927
+ /**
928
+ * Syncs the shipments for the given order
929
+ *
930
+ * @param Order $order the order to sync the shipments
931
+ * @param array $shipments the shipments data
932
+ */
933
+ public function syncShipments($order, array $shipments)
934
+ {
935
+ //Check if we can create shipments for the given order
936
+ if (!$order->canShip())
937
+ {
938
+ return; //We cannot modify shipments
939
+ }
940
+
941
+ //Read shipments for the given order
942
+ $mg_shipment_collection = Mage::getResourceModel('sales/order_shipment_collection');
943
+ $mg_shipment_collection->addAttributeToFilter('order_id', $order->getId());
944
+
945
+ //Retrieve order products
946
+ $magento_order_items = array();
947
+ foreach ($order->getAllItems() AS $item)
948
+ {
949
+ // we need to ignore non-shipping rows so they don't overwrite the correct item_id
950
+ if ($item->getQtyToShip() > 0)
951
+ {
952
+ $magento_order_items[$item['sku']] = $item['item_id'];
953
+ }
954
+ }
955
+
956
+ //Check if all request products exist
957
+ foreach ($shipments AS $key => $shipment)
958
+ {
959
+ foreach ($shipment['rows'] AS $row)
960
+ {
961
+ if (!isset($magento_order_items[$row['sku']]))
962
+ {
963
+ //At least one product not found. Cannot continue
964
+ unset($shipments[$key]);
965
+ break;
966
+ }
967
+ }
968
+ }
969
+
970
+ //Sync shipments
971
+ foreach ($shipments AS $shipment)
972
+ {
973
+ //Check if shipment already exists (Hash checking)
974
+ $shipment_hash = $this->calculateShipmentHash($shipment);
975
+ if ($this->getShipmentByOrderHash($order, $shipment_hash) !== null)
976
+ {
977
+ continue;
978
+ }
979
+
980
+ //Make qty array
981
+ $qty = array();
982
+ foreach ($shipment['rows'] AS $row)
983
+ {
984
+ $qty[$magento_order_items[$row['sku']]] = $row['quantity'];
985
+ }
986
+
987
+ if (empty($qty)) //Could not found any products to ship
988
+ {
989
+ continue;
990
+ }
991
+
992
+ //Create new shipment
993
+ $new_shipment = Mage::getModel('sales/service_order', $order)->prepareShipment($qty);
994
+ if ($new_shipment)
995
+ {
996
+ $new_shipment->addComment('Netmatter Hash: ' . $shipment_hash);
997
+
998
+ $new_shipment->register();
999
+ $new_shipment->getOrder()->setIsInProcess(true);
1000
+ $transaction = Mage::getModel('core/resource_transaction')
1001
+ ->addObject($new_shipment)
1002
+ ->addObject($new_shipment->getOrder());
1003
+ $transaction->save();
1004
+ }
1005
+ }
1006
+ }
1007
+
1008
+ /**
1009
+ * Sets the tracking codes for the given order
1010
+ *
1011
+ * @param $order the magento order object
1012
+ * @param array $tracking_codes an array containing the tracking codes
1013
+ * @param array $shipment_hashes an array containing the shipment hases
1014
+ */
1015
+ public function setOrderTrackingCodes($order, array $tracking_codes, array $shipment_hashes = null)
1016
+ {
1017
+ //Read shipments for the given order
1018
+ $shipment_collection = Mage::getResourceModel('sales/order_shipment_collection');
1019
+ $shipment_collection->addAttributeToFilter('order_id', $order->getId());
1020
+
1021
+ //Append the tracking codes to all shipments, if they exist
1022
+ foreach($shipment_collection AS $mg_shipment)
1023
+ {
1024
+ //Extract tracking codes from shipment
1025
+ $mg_tracking_codes = array();
1026
+ foreach ($mg_shipment->getAllTracks() AS $tracknum)
1027
+ {
1028
+ $mg_tracking_codes[] = $tracknum->getNumber();
1029
+ }
1030
+
1031
+ $mg_shipment_hash = $this->getShipmentHash($mg_shipment);
1032
+
1033
+ foreach ($tracking_codes AS $shipping_method => $tracking_code)
1034
+ {
1035
+ //Check if tracking code has been already set
1036
+ if (in_array($tracking_code, $mg_tracking_codes))
1037
+ {
1038
+ continue;
1039
+ }
1040
+
1041
+ //Check hash
1042
+ if (isset($shipment_hashes[$tracking_code]) && isset($mg_shipment_hash{0}) && $shipment_hashes[$tracking_code] !== $mg_shipment_hash)
1043
+ {
1044
+ continue;
1045
+ }
1046
+
1047
+ //Add tracking line
1048
+ $track = Mage::getModel('sales/order_shipment_track')
1049
+ ->setData('title', $shipping_method)
1050
+ ->setData('number', $tracking_code)
1051
+ ->setData('carrier_code', 'custom')
1052
+ ->setData('order_id', $order->getId());
1053
+
1054
+ $mg_shipment->addTrack($track);
1055
+ $mg_shipment->save();
1056
+
1057
+ $mg_shipment->sendEmail(true);
1058
+ }
1059
+ }
1060
+ }
1061
+
1062
+ /**
1063
+ * Retrieves a list with the ids of the available groups
1064
+ *
1065
+ * @return array containing the groups
1066
+ */
1067
+ public function getGroups()
1068
+ {
1069
+ $groups = array();
1070
+ $result = $this->db()->query("SELECT customer_group_id, customer_group_code FROM " . $this->getTablePrefix() . "customer_group;");
1071
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1072
+ $groups[$row['customer_group_code']] = $row['customer_group_id'];
1073
+
1074
+ return $groups;
1075
+ }
1076
+
1077
+ /**
1078
+ * Retrieves the attributes that the product's can have
1079
+ *
1080
+ * @return an array containing the product attributes
1081
+ */
1082
+ public function getProductAttributes()
1083
+ {
1084
+ $attributes = array();
1085
+ $result = $this->db()->query("SELECT attribute_id, attribute_code, frontend_input, backend_type FROM " . $this->getTablePrefix() . "eav_attribute WHERE entity_type_id = 4;");
1086
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1087
+ {
1088
+ $attributes[$row['attribute_code']] = array(
1089
+ 'id' => $row['attribute_id'],
1090
+ 'frontend_input' => $row['frontend_input'],
1091
+ 'backend_type' => $row['backend_type']
1092
+ );
1093
+ }
1094
+
1095
+ return $attributes;
1096
+ }
1097
+
1098
+ public function getAttributeSets($entity_type_id = null)
1099
+ {
1100
+ $ret = array();
1101
+ $attribute_set_collection = Mage::getModel("eav/entity_attribute_set")->getCollection();
1102
+ foreach ($attribute_set_collection AS $attribute_set)
1103
+ {
1104
+ if ($entity_type_id !== null && (string)$attribute_set->getEntityTypeId() !== (string)$entity_type_id)
1105
+ {
1106
+ continue;
1107
+ }
1108
+ $ret[$attribute_set->getAttributeSetName()] = $attribute_set->getAttributeSetId();
1109
+ }
1110
+
1111
+ return $ret;
1112
+ }
1113
+
1114
+ /**
1115
+ * Retrieves the id of the product entity type
1116
+ *
1117
+ * @return int
1118
+ */
1119
+ public function getCatalogProductEntityTypeId()
1120
+ {
1121
+ return Mage::getModel('catalog/product')->getResource()->getEntityType()->getId();
1122
+ }
1123
+
1124
+ /**
1125
+ * Retrieves the default attribute set id of the products
1126
+ *
1127
+ * @return int
1128
+ */
1129
+ public function getProductDefaultAttributeSetId()
1130
+ {
1131
+ return Mage::getModel('catalog/product')->getDefaultAttributeSetId();
1132
+ }
1133
+
1134
+ /**
1135
+ * Creates an attribute set from the given name
1136
+ *
1137
+ * @param string $attribute_set_name the name of the attribute set
1138
+ * @return int the attribute_set id
1139
+ */
1140
+ public function createAttributeSet($attribute_set_name)
1141
+ {
1142
+ return (string)Mage::getModel('catalog/product_attribute_set_api')
1143
+ ->create($attribute_set_name, $this->getProductDefaultAttributeSetId());
1144
+ }
1145
+
1146
+ public function getAttributeSetIdByName($attribute_set_name, $create = null)
1147
+ {
1148
+ $attribute_sets = $this->getAttributeSets();
1149
+ if (isset($attribute_sets[$attribute_set_name]))
1150
+ {
1151
+ return $attribute_sets[$attribute_set_name];
1152
+ }
1153
+
1154
+ //Attribute set does not exists, and no create flag isset
1155
+ if ($create !== true)
1156
+ {
1157
+ return null;
1158
+ }
1159
+
1160
+ //Create attribute set
1161
+ return $this->createAttributeSet($attribute_set_name);
1162
+ }
1163
+
1164
+ public function createProductAttribute($attribute_code, $attribute_data)
1165
+ {
1166
+ $default_attribute_data = array(
1167
+ 'attribute_code' => $attribute_code,
1168
+ 'is_global' => '0',
1169
+ 'frontend_input' => 'text',
1170
+ 'backend_type' => 'varchar',
1171
+ 'default_value_yesno' => '0',
1172
+ 'default_value_text' => '',
1173
+ 'default_value_textarea' => '',
1174
+ 'default_value_date' => '',
1175
+ 'is_unique' => '0',
1176
+ 'is_required' => '0',
1177
+ 'apply_to' => array(),
1178
+ 'is_configurable' => '0',
1179
+ 'is_searchable' => '0',
1180
+ 'is_visible_in_advanced_search' => '0',
1181
+ 'is_comparable' => '0',
1182
+ 'is_wysiwyg_enabled' => '0',
1183
+ 'is_used_for_price_rules' => '0',
1184
+ 'is_visible_on_front' => '0',
1185
+ 'is_html_allowed_on_front' => '0',
1186
+ 'used_for_sort_by' => '0',
1187
+ 'used_in_product_listing' => '0',
1188
+ 'frontend_label' => 'New Attribute'
1189
+ );
1190
+
1191
+ $attribute_data = array_merge($default_attribute_data, $attribute_data);
1192
+
1193
+ $model = Mage::getModel('catalog/resource_eav_attribute');
1194
+ $model->addData($attribute_data);
1195
+ $model->setEntityTypeId(Mage::getModel('eav/entity')->setType('catalog_product')->getTypeId());
1196
+ $model->setIsUserDefined(1);
1197
+
1198
+ return $model->save()->getId();
1199
+ }
1200
+
1201
+ /**
1202
+ * Retrieve an array containing the product ids that have the given sku
1203
+ *
1204
+ * @param $product_sku the sku to use
1205
+ * @return an array contanining the ids
1206
+ */
1207
+ public function getProductIdsBySku($product_sku)
1208
+ {
1209
+ $ids = array();
1210
+ $result = $this->db()->query("SELECT entity_id FROM " . $this->getTablePrefix() . "catalog_product_entity WHERE sku = :sku;", array('sku' => $product_sku));
1211
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1212
+ $ids[] = $row['entity_id'];
1213
+
1214
+ return $ids;
1215
+ }
1216
+
1217
+ /**
1218
+ * Retrieve an array containing the available options for the given attribute
1219
+ *
1220
+ * @param $attribute_id the id of the attribute
1221
+ * @param $store_id the id of the store
1222
+ * @return an array contanining the attribute's options
1223
+ */
1224
+ public function getAttributeOptions($attribute_id, $store_id = null)
1225
+ {
1226
+ $stores = array(0);
1227
+ if ($store_id !== null)
1228
+ $stores[] = $store_id;
1229
+
1230
+ $options = array();
1231
+ $result = $this->db()->query("SELECT option_id, value FROM " . $this->getTablePrefix() . "eav_attribute_option_value WHERE option_id IN (SELECT option_id FROM " . $this->getTablePrefix() . "eav_attribute_option WHERE attribute_id = :attribute_id) AND store_id IN (" . implode(',', $stores) . ");", array('attribute_id' => $attribute_id));
1232
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1233
+ $options[$row['value']] = $row['option_id'];
1234
+
1235
+ return $options;
1236
+ }
1237
+
1238
+ /**
1239
+ * Retrieve or creates an attribute option value
1240
+ *
1241
+ * @param $attribute_id the id of the attribute
1242
+ * @param $store_id the id of the store
1243
+ * @return an array contanining the attribute's options
1244
+ */
1245
+ public function getCreateAttributeOption($attribute_id, $value, $store_id = null)
1246
+ {
1247
+ $stores = array(0);
1248
+ if ($store_id !== null)
1249
+ $stores[] = $store_id;
1250
+
1251
+ $options = array();
1252
+ $result = $this->db()->query("SELECT option_id, value FROM " . $this->getTablePrefix() . "eav_attribute_option_value WHERE option_id IN (SELECT option_id FROM " . $this->getTablePrefix() . "eav_attribute_option WHERE attribute_id = :attribute_id) AND store_id IN (" . implode(',', $stores) . ") AND value = :value;", array('attribute_id' => $attribute_id, 'value' => $value));
1253
+ if ($row = $result->fetch(PDO::FETCH_ASSOC))
1254
+ return $row;
1255
+
1256
+ //Create the option
1257
+ $this->addAttributeOption($attribute_id, $value);
1258
+
1259
+ //Recurse call to get the newly created value
1260
+ return $this->getCreateAttributeOption($attribute_id, $value, $store_id);
1261
+ }
1262
+
1263
+ /**
1264
+ * Adds an options to an attribute drop down
1265
+ *
1266
+ * @param $attribute_id the id of the attribute
1267
+ * @param $attribute_value the option's value
1268
+ */
1269
+ public function addAttributeOption($attribute_id, $option_value)
1270
+ {
1271
+ $option['attribute_id'] = $attribute_id;
1272
+ $option['value']['0_' . $option_value][0] = $option_value;
1273
+
1274
+ $setup = new Mage_Eav_Model_Entity_Setup('core_setup');
1275
+ $setup->addAttributeOption($option);
1276
+ }
1277
+
1278
+ /**
1279
+ * Checks if the given value is an integer or not
1280
+ *
1281
+ * @param $value the value to check
1282
+ * @return true if value is an integer, false if not
1283
+ */
1284
+ public function isInteger($value)
1285
+ {
1286
+ return (!($type === 'boolean' || filter_var($value, FILTER_VALIDATE_INT) === false));
1287
+ }
1288
+
1289
+ /**
1290
+ * Retrieve product(s) stock by sku
1291
+ *
1292
+ * @param $sku the sku or array or sku
1293
+ * @return an array containing sku, product_id and stock
1294
+ */
1295
+ public function getProductStockBySku($sku)
1296
+ {
1297
+ //Make sure given parameter is array
1298
+ $sku = (array)$sku;
1299
+
1300
+ //Make in query
1301
+ $in_query = implode(',', array_fill(0, count($sku), '?'));
1302
+
1303
+ //Make statement
1304
+ $stmt = $this->db()->prepare("SELECT sku, entity_id, qty FROM " . $this->getTablePrefix() . "catalog_product_entity LEFT JOIN " . $this->getTablePrefix() . "cataloginventory_stock_item ON product_id = entity_id WHERE sku IN (" . $in_query . ");");
1305
+
1306
+ //Bind values
1307
+ foreach ($sku as $key => $value)
1308
+ {
1309
+ $stmt->bindValue($key + 1, $value, PDO::PARAM_STR);
1310
+ }
1311
+
1312
+ //Execute query
1313
+ $stmt->execute();
1314
+
1315
+ //Fetch results
1316
+ $stock = array();
1317
+ while ($row = $stmt->fetch(PDO::FETCH_ASSOC))
1318
+ {
1319
+ $stock[$row['sku']] = array('product_id' => $row['entity_id'], 'qty' => $row['qty']);
1320
+ }
1321
+
1322
+ return $stock;
1323
+ }
1324
+
1325
+ /**
1326
+ * Sets the stock level for the given product
1327
+ *
1328
+ * @param int $product_id
1329
+ * @param int $qty The new stock level
1330
+ */
1331
+ public function setProductStockById($product_id, $qty)
1332
+ {
1333
+ $multiwarehouse = is_array($qty);
1334
+
1335
+ //Multiple stock items
1336
+ $total_qty = 0;
1337
+ if (!$multiwarehouse)
1338
+ {
1339
+ $total_qty = $qty;
1340
+ }
1341
+
1342
+ //Check for multi warehouse
1343
+ if ($multiwarehouse && Mage::getModel('advancedinventory/stock')->getMultiStockEnabledByProductId($product_id))
1344
+ {
1345
+ foreach ($qty AS $store_id => $value)
1346
+ {
1347
+ $advanced_inventory_stocks = Mage::getModel('advancedinventory/stock')->getStocksByProductIdAndStoreId($product_id, $store_id);
1348
+
1349
+ foreach ($advanced_inventory_stocks AS $advanced_inventory_stock)
1350
+ {
1351
+ $advanced_stock_item = Mage::getModel('advancedinventory/stock')->getStockByProductIdAndPlaceId($product_id, $advanced_inventory_stock->getPlaceId());
1352
+ if ($advanced_stock_item->getId() === null) //Skip places not found
1353
+ {
1354
+ continue;
1355
+ }
1356
+
1357
+ $total_qty += $value;
1358
+
1359
+ $advanced_stock_item->setData('quantity_in_stock', $value);
1360
+
1361
+ $advanced_stock_item->save();
1362
+ }
1363
+ }
1364
+ }
1365
+
1366
+ //Retrieve stock item
1367
+ $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($product_id);
1368
+ if (!$stockItem->getId())
1369
+ {
1370
+ $stockItem->setData('product_id', $product_id);
1371
+ $stockItem->setData('stock_id', $this->getDefaultStockId());
1372
+
1373
+ //Save and reload
1374
+ $stockItem->save();
1375
+ $stockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($product_id);
1376
+ }
1377
+
1378
+ $product_backorders = ($stockItem->getUseConfigBackorders() == 1 && Mage::getStoreConfig('cataloginventory/item_options/backorders') == 1) ||
1379
+ ($stockItem->getUseConfigBackorders() == 0 && $stockItem->getBackorders() > 0);
1380
+
1381
+ $new_is_in_stock_flag = ($total_qty > 0 || $product_backorders === true) ? 1 : 0;
1382
+
1383
+ //Check if stock needs to be updated
1384
+ if (netmatter_float_equals((float)$stockItem->getQty(), (float)$total_qty) &&
1385
+ $stockItem->getManageStock() == 1 &&
1386
+ $stockItem->getIsInStock() == $new_is_in_stock_flag)
1387
+ {
1388
+ return;
1389
+ }
1390
+
1391
+ $stockItem->setData('qty', $total_qty);
1392
+ $stockItem->setData('manage_stock', 1);
1393
+ $stockItem->setData('use_config_manage_stock', 0);
1394
+ $stockItem->setData('is_in_stock', $new_is_in_stock_flag);
1395
+ $stockItem->save();
1396
+ }
1397
+
1398
+ /**
1399
+ * Sets the stock level for the given product
1400
+ *
1401
+ * @param $product the magento product
1402
+ * @param $qty the stock level
1403
+ * @return the product
1404
+ */
1405
+ public function setProductStock($product, $qty)
1406
+ {
1407
+ $product->setStockData(array(
1408
+ 'use_config_manage_stock' => 0,
1409
+ 'manage_stock' => 1,
1410
+ 'is_in_stock' => $qty > 0 ? 1 : 0,
1411
+ 'qty' => $qty
1412
+ )
1413
+ );
1414
+
1415
+ return $product;
1416
+ }
1417
+
1418
+ /**
1419
+ * Retrieves the default stock id
1420
+ *
1421
+ * @return integer the default stock id
1422
+ */
1423
+ public function getDefaultStockId()
1424
+ {
1425
+ //Find stock with name "Default"
1426
+ $result = $this->db()->query("SELECT stock_id FROM " . $this->getTablePrefix() . "cataloginventory_stock WHERE stock_name = 'Default';");
1427
+ $row = $result->fetch(PDO::FETCH_ASSOC);
1428
+ if ($row !== false)
1429
+ {
1430
+ return $row['stock_id'];
1431
+ }
1432
+
1433
+ //Get the minimum stock id
1434
+ $result = $this->db()->query("SELECT MIN(stock_id) AS min_stock_id FROM " . $this->getTablePrefix() . "cataloginventory_stock;");
1435
+ $row = $result->fetch(PDO::FETCH_ASSOC);
1436
+
1437
+ return $row !== false ? $row['min_stock_id'] : 1;
1438
+ }
1439
+
1440
+ /**
1441
+ * Retrieves the product's parent product id
1442
+ *
1443
+ * @param $product_id the id of the product
1444
+ * @return the product's parent id if exists, or null if product does not have a parent product
1445
+ */
1446
+ public function getProductParentId($product_id)
1447
+ {
1448
+ $result = $this->db()->query("SELECT parent_id FROM " . $this->getTablePrefix() . "catalog_product_relation WHERE child_id = :child_id;", array('child_id' => $product_id));
1449
+ $row = $result->fetch(PDO::FETCH_ASSOC);
1450
+ return $row !== false ? $row['parent_id'] : null;
1451
+ }
1452
+
1453
+ /**
1454
+ * Checks if a product with the given id exists or not
1455
+ *
1456
+ * @param $product_id the id of the product
1457
+ * @return true or false
1458
+ */
1459
+ public function productExists($product_id)
1460
+ {
1461
+ $result = $this->db()->query("SELECT 1 FROM " . $this->getTablePrefix() . "catalog_product_entity WHERE entity_id = :entity_id;", array('entity_id' => $product_id));
1462
+ return ($row = $result->fetch(PDO::FETCH_ASSOC)) !== false;
1463
+ }
1464
+
1465
+ /**
1466
+ * Disables a product based on its id
1467
+ *
1468
+ * @param $product_id the id of the product
1469
+ * @param $website_id the id of the website
1470
+ */
1471
+ public function disableProduct($product_id, $website_id = 0)
1472
+ {
1473
+ Mage::getModel('catalog/product_status')->updateProductStatus($product_id, $website_id, Mage_Catalog_Model_Product_Status::STATUS_DISABLED);
1474
+ }
1475
+
1476
+ /**
1477
+ * Retrieves super links that a product has
1478
+ *
1479
+ * @param $product_id the id of the product
1480
+ * @return an array containing the product's super links
1481
+ */
1482
+ public function getProductSuperLinks($product_id)
1483
+ {
1484
+ $super_links = array();
1485
+ $result = $this->db()->query("SELECT link_id, parent_id FROM " . $this->getTablePrefix() . "catalog_product_super_link WHERE product_id = :product_id;", array('product_id' => $product_id));
1486
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1487
+ $super_links[$row['link_id']] = $row['parent_id'];
1488
+
1489
+ return $super_links;
1490
+ }
1491
+
1492
+ /**
1493
+ * Retrieves the prices array of the given product id by simply quering the database so we can update prices only if price have changed
1494
+ *
1495
+ * @param $product_id the id of the product
1496
+ * @return an array containing the product's prices
1497
+ */
1498
+ public function getSimpleProductPrices($product_id)
1499
+ {
1500
+ //Get attributes ids
1501
+ $attributes = $this->getProductAttributes();
1502
+ $filter_attributes = array(
1503
+ $attributes['cost']['id'] => 'cost',
1504
+ $attributes['msrp']['id'] => 'msrp',
1505
+ $attributes['price']['id'] => 'price',
1506
+ $attributes['special_price']['id'] => 'special_price',
1507
+ $attributes['special_to_date']['id'] => 'special_to_date',
1508
+ $attributes['special_from_date']['id'] => 'special_from_date'
1509
+ );
1510
+
1511
+ //Make query
1512
+ $query = "SELECT 'price' AS type, 1 AS qty, attribute_id AS id, store_id AS website_id, value FROM " . $this->getTablePrefix() . "catalog_product_entity_decimal WHERE entity_id = :product_id and attribute_id in (" . implode(', ', array_filter(array_keys($filter_attributes))) . ")
1513
+ UNION
1514
+ SELECT 'tier' AS type, FLOOR(qty) AS qty, customer_group_id AS id, website_id, value FROM " . $this->getTablePrefix() . "catalog_product_entity_tier_price WHERE entity_id = :product_id";
1515
+
1516
+ if ($this->supportsGroupPrices())
1517
+ {
1518
+ $query .= " UNION SELECT 'group' AS type, 1 AS qty, customer_group_id AS id, website_id, value FROM " . $this->getTablePrefix() . "catalog_product_entity_group_price WHERE entity_id = :product_id";
1519
+ }
1520
+
1521
+ //Read prices
1522
+ $prices = array();
1523
+ $result = $this->db()->query($query, array('product_id' => $product_id));
1524
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1525
+ {
1526
+ if (!isset($prices[$row['website_id']]['group']))
1527
+ {
1528
+ $prices[$row['website_id']]['group'] = array();
1529
+ }
1530
+
1531
+ if (!isset($prices[$row['website_id']]['tier']))
1532
+ {
1533
+ $prices[$row['website_id']]['tier'] = array();
1534
+ }
1535
+
1536
+ switch ($row['type'])
1537
+ {
1538
+ case 'group':
1539
+ $prices[$row['website_id']]['group'][$row['id']]["1"] = $row['value'];
1540
+ break;
1541
+
1542
+ case 'tier':
1543
+ if ($row['id'] > 0) //Specific group
1544
+ {
1545
+ $prices[$row['website_id']]['group'][$row['id']][$row['qty']] = $row['value'];
1546
+ }
1547
+ else //Retail
1548
+ {
1549
+ $prices[$row['website_id']]['tier'][$row['qty']] = $row['value'];
1550
+ }
1551
+
1552
+ break;
1553
+
1554
+ default:
1555
+ $prices[$row['website_id']][$filter_attributes[$row['id']]] = $row['value'];
1556
+ }
1557
+ }
1558
+
1559
+ return $prices;
1560
+ }
1561
+
1562
+ /**
1563
+ * Retrieves a Magento product by its id for the given store_id
1564
+ *
1565
+ * @param $product_id the id of the product
1566
+ * @param $store_id the id of the store. Leave to null for default
1567
+ * @return the Magento product
1568
+ */
1569
+ public function getProduct($product_id, $store_id = null)
1570
+ {
1571
+ $model = Mage::getModel('catalog/product');
1572
+ if ($store_id !== null)
1573
+ {
1574
+ $model->setStoreId($store_id);
1575
+ }
1576
+
1577
+ $product = $model->getCollection()->addAttributeToSelect('*')->addAttributeToFilter('entity_id', $product_id)->getFirstItem();
1578
+ if ($product->getId() !== $product_id) //Check for some custom magento
1579
+ {
1580
+ $product = $model->load($product_id);
1581
+ }
1582
+
1583
+ return $product;
1584
+ }
1585
+
1586
+ /**
1587
+ * Returns the sites that pricing is enabled for
1588
+ *
1589
+ * @param array $linked_websites array($first_store_id => $website_id)
1590
+ * @param array $websites array($website_id => $first_store_id)
1591
+ * @return array($foo => $bar)
1592
+ */
1593
+ public function getPricingWebsites($linked_websites, $websites)
1594
+ {
1595
+ $pricing_websites = $linked_websites;
1596
+
1597
+ //Check for single website. If it we have to set the 0 website instead of 1
1598
+ if ($this->isSingleWebsite($websites))
1599
+ {
1600
+ $linked_websites_keys = array_keys($linked_websites);
1601
+ $pricing_websites = array($linked_websites_keys[0] => 0);
1602
+ }
1603
+ else
1604
+ {
1605
+ //Always set the zero website
1606
+ $pricing_websites[0] = 0;
1607
+ }
1608
+
1609
+ return array_unique($pricing_websites);
1610
+ }
1611
+
1612
+ /**
1613
+ * Retrieves the super pricing for the given product
1614
+ *
1615
+ * @param $product_id the id of the product
1616
+ * @return array the super pricing
1617
+ */
1618
+ public function getProductSuperAttributePricing($product_id)
1619
+ {
1620
+ $pricing = array();
1621
+ $result = $this->db()->query("SELECT * FROM " . $this->getTablePrefix() . "catalog_product_super_attribute_pricing p JOIN " . $this->getTablePrefix() . "catalog_product_super_attribute a ON p.product_super_attribute_id = a.product_super_attribute_id WHERE product_id = :product_id;", array('product_id' => $product_id));
1622
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1623
+ {
1624
+ $pricing[$row['value_id']] = $row;
1625
+ }
1626
+
1627
+ return $pricing;
1628
+ }
1629
+
1630
+ /**
1631
+ * Quickly reads the price for the given product for the given store_id
1632
+ *
1633
+ * Reads store : 0 if no custom price for the given store exists
1634
+ *
1635
+ * @param $product_id the id of the product
1636
+ * @param $store_id the id of the store
1637
+ * @return the product's price
1638
+ */
1639
+ public function getProductPrice($product_id, $store_id)
1640
+ {
1641
+ $attributes = $this->getProductAttributes();
1642
+
1643
+ $values = array(
1644
+ 'store_id' => $store_id,
1645
+ 'entity_id' => $product_id,
1646
+ 'attribute_id' => $attributes['price']['id']
1647
+ );
1648
+
1649
+ $result = $this->db()->query("SELECT value FROM " . $this->getTablePrefix() . "catalog_product_entity_decimal WHERE store_id IN (0, :store_id) AND value IS NOT NULL AND value <> '' AND entity_id = :entity_id AND attribute_id = :attribute_id ORDER BY store_id DESC LIMIT 1;", $values);
1650
+ $row = $result->fetch(PDO::FETCH_ASSOC);
1651
+ return $row !== false ? $row['value'] : 0.00;
1652
+ }
1653
+
1654
+ /**
1655
+ * Retrieves an array containing the int attributes and values for the given entity
1656
+ *
1657
+ * @param $entity_id the id of the entity
1658
+ * @retun array
1659
+ */
1660
+ public function getIntAttributesForEntity($entity_id)
1661
+ {
1662
+ $attributes = array();
1663
+ $result = $this->db()->query("SELECT attribute_id, store_id, (SELECT website_id FROM " . $this->getTablePrefix() . "core_store WHERE store_id = p.store_id) AS website, value FROM " . $this->getTablePrefix() . "catalog_product_entity_int AS p WHERE entity_id = :entity_id;", array('entity_id' => $entity_id));
1664
+ while ($row = $result->fetch(PDO::FETCH_ASSOC))
1665
+ {
1666
+ $attributes[$row['attribute_id']] = $row;
1667
+ }
1668
+
1669
+ return $attributes;
1670
+ }
1671
+
1672
+ /**
1673
+ * Sets the price for the given product_id on the given product super link id for the given website
1674
+ *
1675
+ * @param $product_super_link_id the super link id
1676
+ * @param $product_id the product's id
1677
+ * @param $new_price the new product's price
1678
+ * @param $website_id the id of the website
1679
+ * @param $store_id the id of the store
1680
+ */
1681
+ public function setSuperLinkPrice($product_super_link_id, $product_id, $new_price, $website_id, $store_id)
1682
+ {
1683
+ //Read data
1684
+ $pricing_list = $this->getProductSuperAttributePricing($product_super_link_id);
1685
+ $child_attributes = $this->getIntAttributesForEntity($product_id);
1686
+
1687
+ $matched_price = null;
1688
+
1689
+ //Find which attribute_id and value_index is the correct
1690
+ foreach ($pricing_list as $pricing)
1691
+ {
1692
+ foreach ($child_attributes as $child_attribute)
1693
+ {
1694
+ if ($pricing['attribute_id'] == $child_attribute['attribute_id'] && $pricing['value_index'] == $child_attribute['value'])
1695
+ {
1696
+ if ($matched_price=== null || $website_id == $pricing['website_id'])
1697
+ {
1698
+ $matched_price = $pricing;
1699
+ break;
1700
+ }
1701
+ }
1702
+ }
1703
+ }
1704
+
1705
+ if ($matched_price === null) //We cannot automatically set. Customer must makes the variation manually first
1706
+ {
1707
+ return;
1708
+ }
1709
+
1710
+ //Calculate new price based on conf product's price
1711
+ $conf_price = $this->getProductPrice($product_super_link_id, $store_id);
1712
+ $new_price -= $conf_price;
1713
+
1714
+ //Check if price needs to be updated
1715
+ if ($matched_price['website_id'] == $website_id && $matched_price['is_percent'] == 0 && $matched_price['pricing_value'] == $new_price)
1716
+ {
1717
+ return; //No need to update
1718
+ }
1719
+
1720
+ //Update pricing
1721
+ if (isset($matched_price['value_id']{0}) && $matched_price['website_id'] == $website_id)
1722
+ {
1723
+ $this->db()->query("UPDATE " . $this->getTablePrefix() . "catalog_product_super_attribute_pricing SET pricing_value = :pricing_value WHERE value_id = :value_id;", array('pricing_value' => $new_price, 'value_id' => $matched_price['value_id']));
1724
+ }
1725
+ else //Insert pricing
1726
+ {
1727
+ $insert_values = array(
1728
+ ':product_super_attribute_id' => $matched_price['product_super_attribute_id'],
1729
+ ':value_index' => $matched_price['value_index'],
1730
+ ':pricing_value' => $new_price,
1731
+ ':website_id' => $website_id,
1732
+ );
1733
+
1734
+ $this->db()->query("INSERT INTO " . $this->getTablePrefix() . "catalog_product_super_attribute_pricing(product_super_attribute_id, value_index, is_percent, pricing_value, website_id) VALUES (:product_super_attribute_id, :value_index, 0, :pricing_value, :website_id);", $insert_values);
1735
+ }
1736
+ }
1737
+
1738
+ /**
1739
+ * Checks if the current version supports group prices or not
1740
+ *
1741
+ * @return boolean
1742
+ */
1743
+ public function supportsGroupPrices()
1744
+ {
1745
+ return $this->db()->isTableExists($this->getTablePrefix() . 'catalog_product_entity_group_price');
1746
+ }
1747
+
1748
+ /**
1749
+ * Sets the prices for the given product
1750
+ *
1751
+ * @param int $product_id the id of the product
1752
+ * @param array $prices an array containing the prices
1753
+ * @param array $websites array($store_id => $website_id)
1754
+ */
1755
+ public function setSimpleProductPrices($product_id, $prices, array $websites)
1756
+ {
1757
+ //Retrieve product's current prices
1758
+ $current_prices = $this->getSimpleProductPrices($product_id);
1759
+
1760
+ foreach ($websites AS $store_id => $website_id)
1761
+ {
1762
+ $attributes_data = array(
1763
+ 'msrp' => isset($prices['rrp-' . $website_id]['1']) ? $prices['rrp-' . $website_id]['1'] : '',
1764
+ 'cost' => isset($prices['cost-' . $website_id]['1']) ? $prices['cost-' . $website_id]['1'] : '',
1765
+ );
1766
+
1767
+ //Retail price
1768
+ if (isset($prices['retail-' . $website_id]['1']))
1769
+ {
1770
+ $attributes_data['price'] = $prices['retail-' . $website_id]['1'];
1771
+ }
1772
+
1773
+ //Check for sale price
1774
+ if (!array_key_exists('sale-' . $website_id, $prices))
1775
+ {
1776
+ // it's not mapped - don't change anything
1777
+ }
1778
+ elseif (isset($prices['sale-' . $website_id]['1']))
1779
+ {
1780
+ $attributes_data['special_price'] = $prices['sale-' . $website_id]['1'];
1781
+ }
1782
+ elseif (isset($prices['sale-' . $website_id]['price']))
1783
+ {
1784
+ $sale_obj = $prices['sale-' . $website_id];
1785
+ $attributes_data['special_price'] = $sale_obj['price'];
1786
+ $attributes_data['special_from_date'] = isset($sale_obj['start']) ? $sale_obj['start'] : null;
1787
+ $attributes_data['special_to_date'] = isset($sale_obj['end']) ? $sale_obj['end'] : null;
1788
+ }
1789
+ else //Remove sale price
1790
+ {
1791
+ $attributes_data['special_price'] = '';
1792
+ $attributes_data['special_to_date'] = '';
1793
+ $attributes_data['special_from_date'] = '';
1794
+ }
1795
+
1796
+ foreach ($attributes_data as $key => $value)
1797
+ {
1798
+ if ((isset($current_prices[$website_id][$key]) && $current_prices[$website_id][$key] == $value) || (!isset($current_prices[$website_id][$key]) && $value === ''))
1799
+ {
1800
+ unset($attributes_data[$key]);
1801
+ }
1802
+ }
1803
+
1804
+ //Check if update is required
1805
+ if (!empty($attributes_data))
1806
+ {
1807
+ Mage::getSingleton('catalog/product_action')->updateAttributes(array($product_id), $attributes_data, $store_id);
1808
+ }
1809
+
1810
+ //Check for product super links
1811
+ $product_super_links = $this->getProductSuperLinks($product_id);
1812
+ if (count($product_super_links) === 0)
1813
+ {
1814
+ continue;
1815
+ }
1816
+
1817
+ //In case that retail price or sale is updated and the product belongs to configurable products, we must properly set the values
1818
+ $new_price = null;
1819
+ if (isset($prices['sale-' . $website_id]['1'])) //Use sale over price
1820
+ {
1821
+ $new_price = $prices['sale-' . $website_id]['1'];
1822
+ }
1823
+ elseif (isset($prices['retail-' . $website_id]['1']))
1824
+ {
1825
+ $new_price = $prices['retail-' . $website_id]['1'];
1826
+ }
1827
+
1828
+ if ($new_price === null)
1829
+ {
1830
+ continue;
1831
+ }
1832
+
1833
+ //Get product's super links
1834
+ foreach ($product_super_links as $product_super_link)
1835
+ {
1836
+ $this->setSuperLinkPrice($product_super_link, $product_id, $new_price, $website_id, $store_id);
1837
+ }
1838
+ }
1839
+
1840
+ //Check for group and tier prices
1841
+ $group_prices = array();
1842
+ $tier_prices = array();
1843
+
1844
+ $groups = $this->getGroups();
1845
+
1846
+ foreach ($websites AS $store_id => $website_id)
1847
+ {
1848
+ //Check for retail price
1849
+ if (isset($prices['retail-' . $website_id]['1']))
1850
+ {
1851
+ foreach ($prices['retail-' . $website_id] AS $tier_qty => $price)
1852
+ {
1853
+ if ($tier_qty == 1)
1854
+ {
1855
+ continue;
1856
+ }
1857
+
1858
+ $tier_prices[] = array(
1859
+ 'website_id' => $website_id,
1860
+ 'cust_group' => Mage_Customer_Model_Group::CUST_GROUP_ALL,
1861
+ 'price' => $price,
1862
+ 'price_qty' => $tier_qty
1863
+ );
1864
+ }
1865
+ }
1866
+
1867
+ //Check for groups prices
1868
+ foreach ($groups AS $group_id)
1869
+ {
1870
+ if (isset($prices['group_' . $group_id . '-' . $website_id]['1']))
1871
+ {
1872
+ foreach ($prices['group_' . $group_id . '-' . $website_id] AS $tier_qty => $price)
1873
+ {
1874
+ if ($tier_qty == 1) //Price for 1 qty is stored under group prices
1875
+ {
1876
+ $group_prices[] = array(
1877
+ 'website_id' => $website_id,
1878
+ 'cust_group' => $group_id,
1879
+ 'price' => $price,
1880
+ );
1881
+ }
1882
+ else //Prices for more than 1 qty are stored as tier price
1883
+ {
1884
+ $tier_prices[] = array(
1885
+ 'website_id' => $website_id,
1886
+ 'cust_group' => $group_id,
1887
+ 'price' => $price,
1888
+ 'price_qty' => $tier_qty
1889
+ );
1890
+ }
1891
+ }
1892
+ }
1893
+ }
1894
+ }
1895
+
1896
+
1897
+ //Create compare arrays
1898
+ $mg_group_price = array();
1899
+ $mg_tier_price = array();
1900
+ foreach ($current_prices as $website_id => $pricelists)
1901
+ {
1902
+ foreach ($pricelists['group'] as $group_id => $values)
1903
+ {
1904
+ foreach ($values as $qty => $price)
1905
+ {
1906
+ if ($qty == 1)
1907
+ {
1908
+ $mg_group_price[] = array(
1909
+ 'website_id' => $website_id,
1910
+ 'cust_group' => $group_id,
1911
+ 'price' => $price
1912
+ );
1913
+ }
1914
+ else
1915
+ {
1916
+ $mg_tier_price[] = array(
1917
+ 'website_id' => $website_id,
1918
+ 'cust_group' => $group_id,
1919
+ 'price' => $price,
1920
+ 'price_qty' => $qty
1921
+ );
1922
+ }
1923
+ }
1924
+ }
1925
+
1926
+ foreach ($pricelists['tier'] as $qty => $price)
1927
+ {
1928
+ $mg_tier_price[] = array(
1929
+ 'website_id' => $website_id,
1930
+ 'cust_group' => Mage_Customer_Model_Group::CUST_GROUP_ALL,
1931
+ 'price' => $price,
1932
+ 'price_qty' => $qty
1933
+ );
1934
+ }
1935
+ }
1936
+
1937
+ array_multisort($mg_tier_price);
1938
+ array_multisort($tier_prices);
1939
+ array_multisort($mg_group_price);
1940
+ array_multisort($group_prices);
1941
+
1942
+ $tier_prices_different = $mg_tier_price != $tier_prices;
1943
+ $group_prices_different = $mg_group_price != $group_prices;
1944
+
1945
+ // Only update if necessary
1946
+ if ($tier_prices_different || $group_prices_different)
1947
+ {
1948
+ $supportsGroupPrices = $this->supportsGroupPrices();
1949
+
1950
+ $mg_product = Mage::getModel('catalog/product')->setStoreId(0)->load($product_id);
1951
+
1952
+ $mg_product->unsTierPrice();
1953
+ if ($supportsGroupPrices === true)
1954
+ {
1955
+ $mg_product->unsGroupPrice();
1956
+ }
1957
+
1958
+ //We need to double save to prevent integrity errors. Magento lol. Also in later version we need to fully reload product
1959
+ $mg_product->save();
1960
+ $mg_product = Mage::getModel('catalog/product')->setStoreId(0)->load($product_id);
1961
+
1962
+ $mg_product->setTierPrice($tier_prices);
1963
+ if ($supportsGroupPrices === true)
1964
+ {
1965
+ $mg_product->setGroupPrice($group_prices);
1966
+ }
1967
+
1968
+ $mg_product->save();
1969
+ }
1970
+ }
1971
+
1972
+ /**
1973
+ * Retrieve the instance to the db
1974
+ *
1975
+ * @return the db instance
1976
+ */
1977
+ public function db($write = false)
1978
+ {
1979
+ $key = $write === true ? 'write' : 'read';
1980
+
1981
+ if (!isset($this->db[$key]))
1982
+ {
1983
+ $this->db[$key] = Mage::getSingleton('core/resource')->getConnection('core_' . $key);
1984
+ }
1985
+
1986
+ return $this->db[$key];
1987
+ }
1988
+
1989
+ /**
1990
+ * Retrieves the host's version
1991
+ *
1992
+ * @retun string
1993
+ */
1994
+ public function getHostVersion()
1995
+ {
1996
+ return 'Magento ' . Mage::getVersion();
1997
+ }
1998
+
1999
+ /**
2000
+ * Sends an order to the bridge
2001
+ *
2002
+ * @param Mage_Sales_Model_Order $magento_order
2003
+ */
2004
+ public function sendOrder($magento_order)
2005
+ {
2006
+ /* @var $bridge Netmatter_Bridge_Bridge */
2007
+ $bridge = $this->initBridge();
2008
+
2009
+ //Create a new Bridge DTO Object
2010
+ $order_dto = $bridge->createOrder();
2011
+
2012
+ //Calculate date_placed
2013
+ $magento_order_created_at = $magento_order->getCreatedAt();
2014
+ $year = substr($magento_order_created_at, 0, 4);
2015
+ $month = substr($magento_order_created_at, 5, 2);
2016
+ $day = substr($magento_order_created_at, 8, 2);
2017
+ $hour = substr($magento_order_created_at, 11, 2);
2018
+ $minute = substr($magento_order_created_at, 14, 2);
2019
+ $second = substr($magento_order_created_at, 17, 2);
2020
+
2021
+ $date_placed = mktime($hour, $minute, $second, $month, $day, $year);
2022
+
2023
+ //Set main values
2024
+ $order_dto->setId($magento_order->getId())
2025
+ ->setPublicId($magento_order->getIncrementId())
2026
+ ->setChannelId($magento_order->getStoreId())
2027
+ ->setOrderStatus($magento_order->getStatus())
2028
+ ->setTotal((float)$magento_order->getGrandTotal())
2029
+ ->setDatePlaced($date_placed);
2030
+
2031
+ //Add billing information
2032
+ $magento_billing_data = $magento_order->getBillingAddress()->getData(); //Array
2033
+ $billing_street = preg_split('/\n/', $magento_billing_data['street']);
2034
+ $order_dto->addBilling()
2035
+ ->setFirstname(isset($magento_billing_data['firstname']{0}) ? $magento_billing_data['firstname'] : $magento_order->getCustomerFirstname())
2036
+ ->setLastname(isset($magento_billing_data['lastname']{0}) ? $magento_billing_data['lastname'] : $magento_order->getCustomerLastname())
2037
+ ->setCompany($magento_billing_data['company'])
2038
+ ->setStreet($billing_street[0])
2039
+ ->setSuburb(implode("\n", array_slice($billing_street, 1, count($billing_street))))
2040
+ ->setCity($magento_billing_data['city'])
2041
+ ->setCounty($magento_billing_data['region'])
2042
+ ->setPostcode($magento_billing_data['postcode'])
2043
+ ->setCountryIsoCode($magento_billing_data['country_id'])
2044
+ ->setTelephone($magento_billing_data['telephone'])
2045
+ ->setEmailAddress(isset($magento_billing_data['email']{0}) ? $magento_billing_data['email'] : $magento_order->getCustomerEmail());
2046
+
2047
+ //Add delivery information
2048
+ $magento_shipping_address = $magento_order->getShippingAddress();
2049
+
2050
+ //Check if shipping details exist, if not use billing data again
2051
+ $magento_shipping_data = $magento_shipping_address !== false ? $magento_shipping_address->getData() : $magento_billing_data;
2052
+ $shipping_street = preg_split('/\n/', $magento_shipping_data['street']);
2053
+ $order_dto->addDelivery()
2054
+ ->setFirstname($magento_shipping_data['firstname'])
2055
+ ->setLastname($magento_shipping_data['lastname'])
2056
+ ->setCompany($magento_shipping_data['company'])
2057
+ ->setStreet($shipping_street[0])
2058
+ ->setSuburb(implode("\n", array_slice($shipping_street, 1, count($shipping_street))))
2059
+ ->setCity($magento_shipping_data['city'])
2060
+ ->setCounty($magento_shipping_data['region'])
2061
+ ->setPostcode($magento_shipping_data['postcode'])
2062
+ ->setCountryIsoCode($magento_shipping_data['country_id'])
2063
+ ->setTelephone($magento_shipping_data['telephone'])
2064
+ ->setEmailAddress(isset($magento_shipping_data['email']{0}) ? $magento_shipping_data['email'] : $magento_order->getCustomerEmail());
2065
+
2066
+ //Set payment details
2067
+ $magento_order_payment = $magento_order->getPayment();
2068
+ $magento_order_payment_data = $magento_order_payment->getData();
2069
+ $payment = $order_dto->addPayment();
2070
+ $payment->setMethod($magento_order_payment_data['method'])
2071
+ ->setBaseCurrency($magento_order->getBaseCurrencyCode())
2072
+ ->setCurrency($magento_order->getOrderCurrencyCode());
2073
+
2074
+ //Set payment details data
2075
+ switch ($magento_order_payment_data['method'])
2076
+ {
2077
+ case 'paypal_direct':
2078
+ case 'paypal_express':
2079
+ case 'paypal_standard':
2080
+ $payment->setMethod('paypal');
2081
+
2082
+ $paypal_details = $payment->addPaypalDetails();
2083
+
2084
+ $paypal_details->setPayerId($magento_order_payment_data['additional_information']['paypal_payer_id'])
2085
+ ->setPayerEmailAddress($magento_order_payment_data['additional_information']['paypal_payer_email']);
2086
+
2087
+ if (Mage_Paypal_Model_Info::isPaymentSuccessful($magento_order_payment))
2088
+ {
2089
+ if ($payment_last_trans_id = $magento_order_payment->getLastTransId())
2090
+ {
2091
+ $paypal_details->setTxId($payment_last_trans_id);
2092
+ }
2093
+
2094
+ $paypal_details->setStatus('OK')->setStatusLabel('Payment Received');
2095
+
2096
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid());
2097
+ $payment->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2098
+ $order_dto->setIsPaid(true);
2099
+ }
2100
+
2101
+ break;
2102
+
2103
+ //Barclays epdq
2104
+ case 'ops_cc':
2105
+ $payment->setMethod('epdq');
2106
+
2107
+ //Check for payment id
2108
+ if (isset($magento_order_payment_data['additional_information']['paymentId']))
2109
+ {
2110
+ $epdq_details = $payment->addEpdqDetails();
2111
+
2112
+ $epdq_details->setTxId($magento_order_payment_data['additional_information']['paymentId']);
2113
+
2114
+ if ($order_is_paid === true) //Success
2115
+ {
2116
+ $epdq_details->setStatus('OK')->setStatusLabel('Payment Received');
2117
+ $epdq_details->setCcBrand($magento_order_payment_data['additional_information']['CC_BRAND'])
2118
+ ->setAavCheck($magento_order_payment_data['additional_information']['additionalScoringData']['AAVCHECK'])
2119
+ ->setCvcCheck($magento_order_payment_data['additional_information']['additionalScoringData']['CVCCHECK']);
2120
+
2121
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid());
2122
+ $payment->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2123
+ $order_dto->setIsPaid(true);
2124
+ }
2125
+ else
2126
+ {
2127
+ $epdq_details->setStatus('FAILED')->setStatusLabel('Payment failed. Status: ' . $magento_order_payment_data['additional_information']['status']);
2128
+ }
2129
+ }
2130
+
2131
+ break;
2132
+
2133
+ //Sagepay
2134
+ case 'sagepaydirectpro':
2135
+ case 'sagepayserver':
2136
+ case 'sagepayserver_moto':
2137
+ case 'sagepaypaypal':
2138
+ case 'sagepayform':
2139
+
2140
+ $payment->setMethod('sagepay');
2141
+
2142
+ $sagepay_data = Mage::getModel('sagepaysuite2/sagepaysuite_transaction')->getCollection()->addFieldToFilter('order_id', $magento_order->getId())->getFirstItem()->getData();
2143
+
2144
+ if (isset($sagepay_data['id']) && isset($sagepay_data['vps_tx_id']))
2145
+ {
2146
+ $sagepay_details = $payment->addSagepayDetails();
2147
+ $sagepay_details->setTxId($sagepay_data['vps_tx_id'])->setStatus('OK')->setStatusLabel('Payment success');
2148
+ $sagepay_details->setCv2Result($sagepay_data['cv2result'])
2149
+ ->setAddressResult($sagepay_data['address_result'])
2150
+ ->setPostcodeResult($sagepay_data['postcode_result'])
2151
+ ->setAvsCv2Check($sagepay_data['avscv2'])
2152
+ ->setAuthCode($sagepay_data['tx_auth_no'])
2153
+ ->setThreeDSecureStatus($sagepay_data['threed_secure_status']);
2154
+
2155
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid());
2156
+ $payment->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2157
+ $order_dto->setIsPaid(true);
2158
+ }
2159
+ else
2160
+ {
2161
+ //There is a weird issue with Sagepay plugin. Maybe it updates the status before it actually stores the data in the datbase
2162
+ //So an order is paid but we cant get sagepay details
2163
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid());
2164
+ $payment->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2165
+ $order_is_paid = netmatter_float_equals($magento_order->getGrandTotal(), $magento_order_payment->getAmountPaid());
2166
+ $order_dto->setIsPaid($order_is_paid);
2167
+ }
2168
+
2169
+ break;
2170
+
2171
+ //Securetradingxpay
2172
+ case 'securetradingxpay':
2173
+
2174
+ $cc_approval = strlen($magento_order_payment_data['cc_approval']) >= 10 && substr($magento_order_payment_data['cc_approval'], 0, 10) === 'AUTH CODE:';
2175
+
2176
+ if (isset($magento_order_payment_data['cc_trans_id']{0}) && $cc_approval === true)
2177
+ {
2178
+ $default_details = $payment->addDefaultDetails();
2179
+ $default_details->setValue('card_type', $magento_order_payment_data['cc_type']);
2180
+ $default_details->setValue('cc_trans_id', $magento_order_payment_data['cc_trans_id']);
2181
+ $default_details->setStatus('OK')->setStatusLabel('Payment Received');
2182
+
2183
+ //All the amount is paid
2184
+ $payment->setAmount($magento_order_payment_data['amount_authorized']);
2185
+ $payment->setBaseAmount($magento_order_payment_data['base_amount_authorized']);
2186
+
2187
+ //Mark order as paid
2188
+ $order_dto->setIsPaid(true);
2189
+ }
2190
+
2191
+ break;
2192
+
2193
+ //Charity Clear
2194
+ case 'CharityClearHosted_standard':
2195
+ $payment->setMethod('charity_clear');
2196
+
2197
+ $charity_clear_data = Mage::getModel('CharityClearHosted/CharityClearHosted_Trans')->getCollection()->addFieldToFilter('orderid', $magento_order->getIncrementId())->getFirstItem()->getData();
2198
+
2199
+ $success = strlen($charity_clear_data['message']) >= 9 && substr($charity_clear_data['message'], 0, 9) === 'AUTHCODE:';
2200
+
2201
+ if (isset($magento_order_payment_data['last_trans_id']{0}) && $success === true && (string)$charity_clear_data['responsecode'] === '0')
2202
+ {
2203
+ $default_details = $payment->addDefaultDetails();
2204
+ $default_details->setStatus('OK')->setStatusLabel('Payment Received');
2205
+
2206
+ //All the amount is paid
2207
+ $payment->setAmount($magento_order_payment_data['amount_paid']);
2208
+ $payment->setBaseAmount($magento_order_payment_data['base_amount_paid']);
2209
+
2210
+ //Mark order as paid
2211
+ $order_dto->setIsPaid(true);
2212
+ }
2213
+
2214
+ break;
2215
+
2216
+ //Amazon payments
2217
+ case 'amazonpayments_advanced':
2218
+ $payment->setMethod('amazon_payments');
2219
+ if (isset($magento_order_payment_data['additional_information']['amazon_order_reference_id']{0}))
2220
+ {
2221
+ $default_details = $payment->addDefaultDetails();
2222
+ $default_details->setStatus('OK')->setStatusLabel('Payment Received');
2223
+
2224
+ //All the amount is paid
2225
+ $payment->setAmount($magento_order_payment_data['amount_paid']);
2226
+ $payment->setBaseAmount($magento_order_payment_data['base_amount_paid']);
2227
+
2228
+ //Mark order as paid
2229
+ $order_dto->setIsPaid(true);
2230
+ }
2231
+
2232
+ break;
2233
+
2234
+ //M2E Plugin
2235
+ case 'm2epropayment':
2236
+
2237
+ //Set default
2238
+ $payment->setMethod('m2epropayment');
2239
+
2240
+ if (isset($magento_order_payment_data['additional_data']))
2241
+ {
2242
+ $additional_data = unserialize($magento_order_payment_data['additional_data']);
2243
+ $sum = 0;
2244
+
2245
+ if (isset($additional_data['payment_method']))
2246
+ {
2247
+ if ($additional_data['payment_method'] === 'PayPal')
2248
+ {
2249
+ $payment->setMethod('paypal');
2250
+ }
2251
+ else
2252
+ {
2253
+ if (isset($additional_data['payment_method']{0}))
2254
+ {
2255
+ $payment->setMethod($additional_data['payment_method']);
2256
+ }
2257
+ else
2258
+ {
2259
+ $payment->setMethod($additional_data['component_mode']);
2260
+ }
2261
+ }
2262
+ }
2263
+
2264
+ if (count($additional_data['transactions']) > 0)
2265
+ {
2266
+ foreach ($additional_data['transactions'] AS $transaction)
2267
+ {
2268
+ $sum += $transaction['sum'];
2269
+ }
2270
+ }
2271
+ else
2272
+ {
2273
+ $sum = $magento_order_payment_data['base_amount_paid'];
2274
+ }
2275
+
2276
+ $order_is_paid = netmatter_float_equals($magento_order->getGrandTotal(), $sum);
2277
+ if ($order_is_paid)
2278
+ {
2279
+ $default_details = $payment->addDefaultDetails();
2280
+ if (count($additional_data['transactions']) > 0)
2281
+ {
2282
+ $default_details->setTxId($additional_data['transactions'][count($additional_data['transactions']) - 1]['transaction_id']);
2283
+ }
2284
+ else
2285
+ {
2286
+ $default_details->setTxId($additional_data['channel_order_id']);
2287
+ }
2288
+ $default_details->setStatus('OK')->setStatusLabel('Payment Received');
2289
+
2290
+ //All the amount is paid
2291
+ $payment->setAmount($magento_order_payment_data['amount_paid']);
2292
+ $payment->setBaseAmount($magento_order_payment_data['base_amount_paid']);
2293
+
2294
+ //Mark order as paid
2295
+ $order_dto->setIsPaid(true);
2296
+ }
2297
+ }
2298
+
2299
+ break;
2300
+
2301
+ case 'worldpay_cc':
2302
+ $order_is_paid = netmatter_float_equals($magento_order->getGrandTotal(), $magento_order_payment->getAmountPaid());
2303
+ $order_dto->setIsPaid($order_is_paid);
2304
+
2305
+ if ($order_is_paid)
2306
+ {
2307
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid())
2308
+ ->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2309
+
2310
+ $payment->addDefaultDetails()
2311
+ ->setValue('card_type', $magento_order_payment_data['cc_type'])
2312
+ ->setValue('cc_trans_id', $magento_order_payment_data['cc_trans_id'])
2313
+ ->setStatus('OK')
2314
+ ->setStatusLabel('Payment Received');
2315
+ }
2316
+
2317
+ break;
2318
+
2319
+ case 'realex':
2320
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid())
2321
+ ->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2322
+
2323
+ $order_dto->setIsPaid(true);
2324
+
2325
+ break;
2326
+
2327
+ case 'free':
2328
+ $payment->setAmount(0)
2329
+ ->setBaseAmount(0);
2330
+
2331
+ $order_dto->setIsPaid(true);
2332
+
2333
+ break;
2334
+
2335
+ //Default
2336
+ default:
2337
+ $payment->setAmount((float)$magento_order_payment->getAmountPaid())
2338
+ ->setBaseAmount((float)$magento_order_payment->getBaseAmountPaid());
2339
+
2340
+ $order_dto->setIsPaid($magento_order->getBaseTotalDue() == 0);
2341
+ }
2342
+
2343
+ //Set customer details
2344
+ $customer_firstname = $magento_order->getCustomerFirstname();
2345
+ $customer_lastname = $magento_order->getCustomerLastname();
2346
+ $customer_street = preg_split('/\n/', $magento_billing_data['street']);
2347
+
2348
+ $order_dto->addCustomer()
2349
+ ->setId((int)$magento_order->getCustomerId())
2350
+ ->setFirstname(isset($customer_firstname{0}) ? $customer_firstname : $magento_billing_data['firstname'])
2351
+ ->setLastname(isset($customer_lastname{0}) ? $customer_lastname : $magento_billing_data['lastname'])
2352
+ ->setEmailAddress($magento_order->getCustomerEmail())
2353
+ ->setTelephone($magento_billing_data['telephone'])
2354
+ ->setCompany($magento_billing_data['company'])
2355
+ ->setStreet($customer_street[0])
2356
+ ->setSuburb(implode("\n", array_slice($customer_street, 1, count($customer_street))))
2357
+ ->setCity($magento_billing_data['city'])
2358
+ ->setCounty($magento_billing_data['region'])
2359
+ ->setPostcode($magento_billing_data['postcode'])
2360
+ ->setCountryIsoCode($magento_billing_data['country_id']);
2361
+
2362
+ //Retrieve magento order items
2363
+ $magento_order_items = $magento_order->getAllItems();
2364
+
2365
+ //Retrieve magento order tax rates, and tax codes for each item
2366
+ $magento_order_tax_rates = array();
2367
+ $magento_order_items_tax = array();
2368
+ $magento_order_tax_rates_lines = Mage::getModel('tax/sales_order_tax')->getCollection()->loadByOrder($magento_order)->toArray();
2369
+ foreach ($magento_order_tax_rates_lines['items'] as $magento_order_tax_rates_line)
2370
+ {
2371
+ $magento_order_tax_rates[$magento_order_tax_rates_line['tax_id']] = $magento_order_tax_rates_line;
2372
+
2373
+ //Get items tax info
2374
+ $magento_order_items_tax_lines = Mage::getModel('tax/sales_order_tax_item')->getCollection()->addFieldToFilter('tax_id', $magento_order_tax_rates_line['tax_id'])->toArray();
2375
+
2376
+ foreach ($magento_order_items_tax_lines['items'] as $magento_order_items_tax_line)
2377
+ {
2378
+ $magento_order_items_tax[$magento_order_items_tax_line['item_id']] = array(
2379
+ 'code' => $magento_order_tax_rates_line['code'],
2380
+ 'percent' => $magento_order_tax_rates_line['percent']
2381
+ );
2382
+ }
2383
+ }
2384
+
2385
+ //Try to get the correct tax rate for shipping method
2386
+ $shipping_amount = $magento_order->getShippingAmount();
2387
+ if ($shipping_amount > 0)
2388
+ {
2389
+ $shipping_tax_rate = (float)(($magento_order->getShippingInclTax() - $magento_order->getShippingAmount()) / $magento_order->getShippingAmount() * 100.0);
2390
+ }
2391
+ else
2392
+ {
2393
+ $shipping_tax_rate = 0.0;
2394
+ }
2395
+
2396
+ $shipping_tax_code = null;
2397
+ foreach ($magento_order_tax_rates as $magento_order_tax_rate)
2398
+ {
2399
+ if (netmatter_float_equals((float)$magento_order_tax_rate['percent'], (float)$shipping_tax_rate))
2400
+ {
2401
+ $shipping_tax_code = $magento_order_tax_rate['code'];
2402
+ break;
2403
+ }
2404
+ }
2405
+
2406
+ $magento_order_discounts = array();
2407
+
2408
+ //Add order lines
2409
+ foreach ($magento_order_items as $item)
2410
+ {
2411
+ //When a configurable product is bought, 2 item lines are added, so we only need the conf line and not the simple
2412
+ if ($item->getParentItemId() > 0)
2413
+ {
2414
+ continue;
2415
+ }
2416
+
2417
+ $item_dto = $order_dto->addLineItem();
2418
+
2419
+ //Find item's tax info
2420
+ if (isset($magento_order_items_tax[$item->getId()]))
2421
+ {
2422
+ $item_tax_code = $magento_order_items_tax[$item->getId()]['code'];
2423
+ }
2424
+ else
2425
+ {
2426
+ $item_tax_code = 'TAX_CODE_NOT_SET';
2427
+
2428
+ foreach ($magento_order_tax_rates_lines['items'] as $magento_order_tax_rates_line)
2429
+ {
2430
+ if (netmatter_float_equals($item['tax_percent'], $magento_order_tax_rates_line['percent']))
2431
+ {
2432
+ $item_tax_code = $magento_order_tax_rates_line['code'];
2433
+ break;
2434
+ }
2435
+ }
2436
+ }
2437
+
2438
+ $item_sku = '';
2439
+ $item_name = '';
2440
+ $item_options = array();
2441
+ $item_product_options = $item->getProductOptions();
2442
+
2443
+ //Based on type we have to extract name and sku
2444
+ switch ($item->getProductType())
2445
+ {
2446
+ //Item is configurable product
2447
+ case 'configurable':
2448
+ $item_sku = $item_product_options['simple_sku'];
2449
+ $item_name = $item_product_options['simple_name'];
2450
+
2451
+ break;
2452
+
2453
+ default:
2454
+ $item_sku = $item->getSku();
2455
+ $item_name = $item->getName();
2456
+ }
2457
+
2458
+ //Add product options if set
2459
+ if (isset($item_product_options['options']))
2460
+ {
2461
+ foreach ($item_product_options['options'] as $item_product_options_option)
2462
+ {
2463
+ $item_options[$item_product_options_option['label']] = $item_product_options_option['print_value'];
2464
+ }
2465
+ }
2466
+
2467
+ $item_dto->setProductId($item->getProductId())
2468
+ ->setName($item_name)
2469
+ ->setSku($item_sku)
2470
+ ->setQuantity($item->getQtyOrdered())
2471
+ ->setRowNet($item->getRowTotal())
2472
+ ->setRowGross($item->getRowTotalInclTax())
2473
+ ->setRowTax($item->getRowTotalInclTax() - $item->getRowTotal())
2474
+ ->setTaxCode($item_tax_code);
2475
+
2476
+ foreach ($item_options AS $key => $value)
2477
+ {
2478
+ $item_dto->addOption($key, $value);
2479
+ }
2480
+
2481
+ //Check if item has a discount
2482
+ $item_discount_amount = $item->getDiscountAmount();
2483
+
2484
+ if ($item_discount_amount > 0)
2485
+ {
2486
+ //Based on discount method, magento calculates values different. We have to find if the discount amount contains tax or not, in order to calculate the correct net and tax discount amounts
2487
+ $weee_helper = Mage::helper('weee');
2488
+
2489
+ //Check if item discount contains vat or not
2490
+ if (method_exists($weee_helper, 'getRowWeeeAmountAfterDiscount'))
2491
+ {
2492
+ $item_total_calculated = $item->getRowTotal() + $item->getTaxAmount() + $item->getHiddenTaxAmount() + Mage::helper('weee')->getRowWeeeAmountAfterDiscount($item) - $item->getDiscountAmount();
2493
+ }
2494
+ else
2495
+ {
2496
+ $item_total_calculated = $item->getRowTotal() + $item->getTaxAmount() + $item->getHiddenTaxAmount() + $item->getWeeeTaxAppliedRowAmount() - $item->getDiscountAmount();
2497
+ }
2498
+
2499
+ $item_discount_contains_tax = round($item->getRowTotalInclTax() - $item_discount_amount, 2) === round($item_total_calculated, 2);
2500
+
2501
+ if ($item_discount_contains_tax === true) //Discount contains tax
2502
+ {
2503
+ $net_discount = $item_discount_amount / (1.0 + (float)$item->getTaxPercent() / 100.0);
2504
+ $tax_discount = $item_discount_amount - $net_discount;
2505
+ }
2506
+ else
2507
+ {
2508
+ $net_discount = $item_discount_amount;
2509
+ $tax_discount = $item_discount_amount * ((float)$item->getTaxPercent() / 100.0);
2510
+ }
2511
+
2512
+ $magento_order_discounts[$item_tax_code][$item->getSku()] = array(
2513
+ 'net' => $net_discount,
2514
+ 'tax' => $tax_discount
2515
+ );
2516
+ }
2517
+
2518
+ //Check if item is bundle
2519
+ if ($item->getProductType() === 'bundle')
2520
+ {
2521
+ $bundle_product = Mage::getModel('catalog/product')->load($item->getProductId());
2522
+ if ($bundle_product->getSkuType() === '0')
2523
+ {
2524
+ //Get bundle product's products
2525
+ $bundleSelectionsCollection = $bundle_product->getTypeInstance(true)->getSelectionsCollection(
2526
+ $bundle_product->getTypeInstance(true)->getOptionsIds($bundle_product), $bundle_product
2527
+ );
2528
+
2529
+ $bundled_items = array();
2530
+ foreach($bundleSelectionsCollection as $option)
2531
+ {
2532
+ $bundled_items[$option->option_id][$option->selection_id] = array(
2533
+ 'product_id' => $option->product_id,
2534
+ 'sku' => $option->sku,
2535
+ 'name' => $option->name
2536
+ );
2537
+ }
2538
+
2539
+ $bundle_data = unserialize($item->getData('product_options'));
2540
+
2541
+ //Add bundle options as separate order lines
2542
+ foreach ($bundle_data['bundle_options'] AS $bundle_option)
2543
+ {
2544
+ foreach ($bundle_option['value'] AS $bundle_option_value_key => $bundle_option_value)
2545
+ {
2546
+ $option_value_id = $bundle_data['info_buyRequest']['bundle_option'][$bundle_option['option_id']];
2547
+ if (is_array($option_value_id))
2548
+ {
2549
+ $option_value_id = $option_value_id[$bundle_option_value_key];
2550
+ }
2551
+
2552
+ $bundled_item = $bundled_items[$bundle_option['option_id']][$option_value_id];
2553
+
2554
+ $bundle_item_dto = $order_dto->addLineItem();
2555
+ $bundle_item_dto->setProductId($bundled_item['product_id'])
2556
+ ->setName($bundled_item['name'])
2557
+ ->setSku($bundled_item['sku'])
2558
+ ->setQuantity($bundle_option_value['qty'] * $item->getQtyOrdered())
2559
+ ->setRowNet(0.00)
2560
+ ->setRowGross(0.00)
2561
+ ->setRowTax(0.00)
2562
+ ->setTaxCode($item_tax_code);
2563
+ }
2564
+ }
2565
+ }
2566
+ }
2567
+ }
2568
+
2569
+ //Check for shipping discount
2570
+ $shipping_discount_amount = $magento_order->getShippingDiscountAmount();
2571
+ if ($shipping_discount_amount > 0)
2572
+ {
2573
+ $shipping_discount_tax_code = isset($shipping_tax_code) ? $shipping_tax_code : 'order-discount';
2574
+
2575
+ $magento_order_discounts[$shipping_discount_tax_code]['shipping_cost'] = array(
2576
+ 'net' => $magento_order->getShippingDiscountAmount(),
2577
+ 'tax' => ($magento_order->getShippingInclTax() - $magento_order->getShippingAmount()) - $magento_order->getShippingTaxAmount()
2578
+ );
2579
+ }
2580
+
2581
+ //Combine discounts with same tax codes
2582
+ $order_discounts = array();
2583
+ foreach($magento_order_discounts AS $tax_code => $magento_order_discount)
2584
+ {
2585
+ $discount_label = trim($magento_order->getDiscountDescription());
2586
+ if ($discount_label === '')
2587
+ {
2588
+ $discount_label = 'Order discount';
2589
+ }
2590
+
2591
+ //Calculate net, tax and gross
2592
+ $net = 0.0;
2593
+ $tax = 0.0;
2594
+ foreach ($magento_order_discount as $d)
2595
+ {
2596
+ $net += $d['net'];
2597
+ $tax += $d['tax'];
2598
+ }
2599
+
2600
+ if (!isset($order_discounts[$tax_code]))
2601
+ {
2602
+ $order_discounts[$tax_code] = array(
2603
+ 'label' => $discount_label,
2604
+ 'net' => 0.00,
2605
+ 'tax' => 0.00,
2606
+ 'gross' => 0.00
2607
+ );
2608
+ }
2609
+
2610
+ $order_discounts[$tax_code]['net'] += $net;
2611
+ $order_discounts[$tax_code]['tax'] += $tax;
2612
+ $order_discounts[$tax_code]['gross'] += $net + $tax;
2613
+ if ($order_discounts[$tax_code]['label'] !== $discount_label)
2614
+ {
2615
+ $order_discounts[$tax_code]['label'] .= ', ' . $discount_label;
2616
+ }
2617
+ }
2618
+
2619
+ //Append discounts to DTO
2620
+ foreach ($order_discounts as $order_discount_tax_code => $order_discount)
2621
+ {
2622
+ $discount = $order_dto->addDiscount();
2623
+ $discount->setTaxCode($order_discount_tax_code);
2624
+ $discount->setLabel($order_discount['label']);
2625
+ $discount->setNet($order_discount['net'])->setTax($order_discount['tax'])->setGross($order_discount['gross']);
2626
+ }
2627
+
2628
+ //Add shipping
2629
+ $order_shipping_dto = $order_dto->addShipping();
2630
+ $order_shipping_dto->setMethod($magento_order->getShippingMethod())
2631
+ ->setMethodLabel($magento_order->getShippingDescription())
2632
+ ->setNet($magento_order->getShippingAmount())
2633
+ ->setTax($magento_order->getShippingInclTax() - $magento_order->getShippingAmount())
2634
+ ->setGross($magento_order->getShippingInclTax())
2635
+ ->setTaxCode($shipping_tax_code);
2636
+
2637
+ //Check for gift message
2638
+ $gift_message_id = $magento_order->getGiftMessageId();
2639
+ if ($gift_message_id > 0)
2640
+ {
2641
+ $gift_message = Mage::getModel('giftmessage/message')->load($gift_message_id);
2642
+ $order_shipping_dto->setGiftMessage($gift_message->getMessage(), $gift_message->getRecipient(), $gift_message->getSender());
2643
+ }
2644
+
2645
+ //Send the order to the Bridge
2646
+ return $bridge->sendOrder($order_dto) === true;
2647
+ }
2648
+
2649
+ /**
2650
+ * Checks if the given product id exists for the given website id
2651
+ *
2652
+ * @param $product_id the id of the product
2653
+ * @param $website_id the id of the store
2654
+ * @return boolean
2655
+ */
2656
+ public function productWebsiteExists($product_id, $website)
2657
+ {
2658
+ $result = $this->db()->query("SELECT 1 FROM " . $this->getTablePrefix() . "catalog_product_website WHERE product_id = :product_id AND website_id = :website_id;", array('product_id' => $product_id, 'website_id' => $website_id));
2659
+ $row = $result->fetch(PDO::FETCH_ASSOC);
2660
+ return $row !== false;
2661
+ }
2662
+ }
app/code/community/Netmatter/Bridge/Model/Order/Observer.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @class Netmatter_Bridge_Model_Order_Observer
4
+ *
5
+ * Contains observers that are executed to send the order to bridge
6
+ */
7
+ class Netmatter_Bridge_Model_Order_Observer
8
+ {
9
+ /**
10
+ * Sends the order to the bridge
11
+ *
12
+ * @param $magento_order the order to send
13
+ * @return self
14
+ */
15
+ private function sendOrder($magento_order)
16
+ {
17
+ //Check flag
18
+ if (Mage::registry('netmatter_bridge_disable_order_observer') === 1)
19
+ {
20
+ return $this;
21
+ }
22
+
23
+ Mage::helper('bridge/data')->sendOrder($magento_order);
24
+
25
+ return $this;
26
+ }
27
+
28
+ /**
29
+ * Handles new order placement event.
30
+ *
31
+ * Passes order information and customer information to the Bridge.
32
+ *
33
+ * This method is called internally when the Magento event, 'sales_order_place_after' is dispatched.
34
+ *
35
+ * @param Varien_Event_Observer A magento event object
36
+ * @return self
37
+ */
38
+ public function sales_order_place_after(Varien_Event_Observer $observer)
39
+ {
40
+ return $this->sendOrder($observer->getEvent()->getOrder());
41
+ }
42
+
43
+ /**
44
+ * Handles order save after event
45
+ *
46
+ * @param Varien_Event_Observer A magento event object
47
+ * @return self
48
+ */
49
+ public function sales_order_save_after(Varien_Event_Observer $observer)
50
+ {
51
+ $order = $observer->getEvent()->getOrder();
52
+
53
+ if ($order->dataHasChangedFor('status') !== true)
54
+ {
55
+ return $this;
56
+ }
57
+
58
+ return $this->sendOrder($observer->getEvent()->getOrder());
59
+ }
60
+
61
+ /**
62
+ * Handles order payment event
63
+ *
64
+ * Passes order information and customer information to the Bridge.
65
+ *
66
+ * This method is called internally when the Magento event, 'sales_order_payment_pay' is dispatched.
67
+ *
68
+ * @param Varien_Event_Observer A magento event object
69
+ * @return self
70
+ */
71
+ public function sales_order_payment_pay(Varien_Event_Observer $observer)
72
+ {
73
+ return $this->sendOrder($observer->getEvent()->getPayment()->getOrder());
74
+ }
75
+ }
app/code/community/Netmatter/Bridge/controllers/CallbackController.php ADDED
@@ -0,0 +1,649 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This file contains an callback controler that listens for
4
+ * requests from the Bridge and dispatches them to
5
+ *
6
+ * @category Netmatter
7
+ * @package Netmatter_Bridge
8
+ * @author Netmatter Team
9
+ * @copyright Copyright (c) 2008-2014 Netmatter Ltd. (http://www.netmatter.co.uk)
10
+ * @see Netmatter/etc/config.xml For events that are captured
11
+ */
12
+ class Netmatter_Bridge_CallbackController extends Mage_Core_Controller_Front_Action
13
+ {
14
+ /** @var Netmatter_Bridge_Bridge */
15
+ private $bridge;
16
+
17
+ /** @var Netmatter_Bridge_Helper_Data */
18
+ private $functions;
19
+
20
+ /**
21
+ * This method can be accessed by hitting
22
+ *
23
+ * Url: /netmatter/callback
24
+ *
25
+ * It expects POST data from the bridge, and can internally redirect
26
+ * to the correct action.
27
+ *
28
+ * @return void
29
+ */
30
+ public function indexAction()
31
+ {
32
+ //Include helper functions
33
+ $this->functions = Mage::helper('bridge/data');
34
+
35
+ //Check if integration is enabled
36
+ if (!$this->functions->isEnabled())
37
+ {
38
+ echo '{"errors": "Plugin not enabled"}';
39
+ return;
40
+ }
41
+
42
+ Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
43
+
44
+ //Include standard plugin
45
+ $bridge = $this->functions->initBridge();
46
+
47
+ //Define callback functions
48
+ $bridge->registerCallback('product_stock_modified', array($this, 'callbackBridgeProductStockModified'));
49
+ $bridge->registerCallback('order_status_modified', array($this, 'callbackBridgeOrderStatusModified'));
50
+ $bridge->registerCallback('product_modified', array($this, 'callbackBridgeProductModified'));
51
+ $bridge->registerCallback('configuration_get', array($this, 'callbackBridgeConfigurationGet'));
52
+
53
+ //Listen for requests
54
+ $bridge->listen();
55
+ }
56
+
57
+ /**
58
+ * Callback method that handles the Product Stock Modified event
59
+ *
60
+ * It updates the quantities of the given product
61
+ *
62
+ * @param array $product an array containing id and stock
63
+ * @return empty array on success, an array containing the errors on failure
64
+ */
65
+ public function callbackBridgeProductStockModified(array $product)
66
+ {
67
+ //Retrieve product ids based on given sku
68
+ $ids = $this->functions->getProductIdsBySku($product['sku']);
69
+
70
+ //Make checks
71
+ $count_ids = count($ids);
72
+ if ($count_ids > 1)
73
+ {
74
+ return array('numfound' => count($ids));
75
+ }
76
+
77
+ //Update product stock
78
+ $this->functions->setProductStockById($ids[0], $product['stock']);
79
+
80
+ //Success
81
+ return array();
82
+ }
83
+
84
+ /**
85
+ * Callback method that handles the Order status modified event
86
+ *
87
+ * It updates the status of the given order
88
+ *
89
+ * @param array $order an array containing id and statusId
90
+ * @return empty array on success, an array containing the errors on failure
91
+ */
92
+ public function callbackBridgeOrderStatusModified(array $order)
93
+ {
94
+ $mg_order = Mage::getModel("sales/order")->load($order['id']);
95
+
96
+ //Check if order exists
97
+ if ($mg_order->getId() === null)
98
+ {
99
+ return array('numfound' => 0);
100
+ }
101
+
102
+ $tracking_codes = array();
103
+
104
+ if (isset($order['shipments']))
105
+ {
106
+ foreach ($order['shipments'] AS $key => $shipment)
107
+ {
108
+ if (!isset($shipment['shippedOn'])) //Check for the shippedOn flag
109
+ {
110
+ unset($order['shipments'][$key]);
111
+ continue;
112
+ }
113
+
114
+ $tracking_codes[$shipment['shippingMethod']] = $shipment['reference'];
115
+ }
116
+
117
+ if (count($order['shipments']) > 0)
118
+ {
119
+ $this->functions->syncShipments($mg_order, $order['shipments']);
120
+ }
121
+ }
122
+
123
+ //Update order's status
124
+ $this->functions->setOrderStatus($mg_order, $order['statusId']);
125
+
126
+ //Set order's tracking codes
127
+ if (count($tracking_codes) > 0)
128
+ {
129
+ $shipment_hashes = array();
130
+ foreach ($order['shipments'] AS $shipment)
131
+ {
132
+ if (!isset($shipment['reference']))
133
+ {
134
+ continue;
135
+ }
136
+
137
+ $shipment_hashes[$shipment['reference']] = $this->functions->calculateShipmentHash($shipment);
138
+ }
139
+
140
+ $this->functions->setOrderTrackingCodes($mg_order, $tracking_codes, $shipment_hashes);
141
+ }
142
+
143
+ //Success
144
+ return array();
145
+ }
146
+
147
+ /**
148
+ * Callback method that handles the Product Modified event
149
+ *
150
+ * It updates the given product
151
+ *
152
+ * @param array $product an array containing product details
153
+ * @return empty array on success, an array containing the errors on failure
154
+ */
155
+ public function callbackBridgeProductModified(array $product)
156
+ {
157
+ //Retrieve product ids based on given sku
158
+ $ids = $this->functions->getProductIdsBySku($product['sku']);
159
+
160
+ //Make checks
161
+ $count_ids = count($ids);
162
+ if ($count_ids > 1)
163
+ {
164
+ return array('numfound' => count($ids));
165
+ }
166
+
167
+ //Retrieve available websites
168
+ $websites = $this->functions->getWebsites();
169
+ $website_ids = array_keys($websites);
170
+
171
+ //Linked key is present, so linked is true
172
+ if (!isset($product['linked']))
173
+ {
174
+ foreach ($websites as $website_id => $store_id)
175
+ {
176
+ $product['linked'][$website_id] = 'Yes';
177
+ }
178
+ }
179
+
180
+ $linked_websites = array();
181
+ foreach ($product['linked'] as $website_id => $value)
182
+ {
183
+ if (!in_array($website_id, $website_ids)) //Website not exists
184
+ {
185
+ continue;
186
+ }
187
+
188
+ if ($product['linked'][$website_id] === 'Yes')
189
+ {
190
+ $linked_websites[$websites[$website_id]] = $website_id;
191
+ }
192
+ }
193
+
194
+ //Check if linked is active for at least one website. If not we should not continue
195
+ if (count($linked_websites) === 0)
196
+ {
197
+ return null;
198
+ }
199
+
200
+ //Check if product exists
201
+ if ($count_ids === 1)
202
+ {
203
+ $product['id'] = $ids[0];
204
+ }
205
+ else //Product not exists
206
+ {
207
+ //Check the product
208
+ $product['id'] = $this->createProduct($product);
209
+ }
210
+
211
+ $mg_product = $this->functions->getProduct($product['id']);
212
+
213
+ $sync_product_categories = isset($product['categories']) && is_array($product['categories']);
214
+ $sync_product_images = isset($product['images']) && is_array($product['images']);
215
+
216
+ //Make sure that product is linked on all given websites
217
+ $product_website_ids = $mg_product->getWebsiteIds();
218
+ if ($sync_product_categories)
219
+ {
220
+ $mg_product->getCategoryIds(); //Load categories to product
221
+ }
222
+ if ($sync_product_images)
223
+ {
224
+ $mg_product->getResource()->getAttribute('media_gallery')->getBackend()->afterLoad($mg_product);
225
+ }
226
+ $mg_product_orig_data = $mg_product->getData();
227
+ foreach ($linked_websites AS $website_id)
228
+ {
229
+ if (!in_array($website_id, $product_website_ids))
230
+ {
231
+ $new_website_ids = array_values(array_unique(array_merge($product_website_ids, array_values($linked_websites))));
232
+ $mg_product->setWebsiteIds($new_website_ids);
233
+ break;
234
+ }
235
+ }
236
+
237
+ //Sync product attribute set
238
+ if (isset($product['productGroup']{0}))
239
+ {
240
+ $this->functions->syncProductAttributeSet($mg_product, $product['productGroup']);
241
+ }
242
+
243
+ //Sync product attributes
244
+ $attributes_type = array();
245
+
246
+ $product['attributes']['name'] = $product['name'];
247
+ $product['attributes']['description'] = $product['description']['text'];
248
+ $product['attributes']['short_description'] = $product['shortDescription']['text'];
249
+ $product['attributes']['weight'] = isset($product['weight']) ? $product['weight'] : 0.000;
250
+
251
+ if (isset($product['brandId']))
252
+ {
253
+ $product['attributes']['manufacturer'] = $product['brandId'];
254
+ }
255
+ if (isset($product['condition']))
256
+ {
257
+ $product['attributes']['condition'] = $product['condition'];
258
+ }
259
+
260
+ //Add identity
261
+ if (isset($product['identity']))
262
+ {
263
+ foreach ((array)$product['identity'] AS $product_identity_key => $product_identity_value)
264
+ {
265
+ if ($product_identity_key === 'sku')
266
+ {
267
+ continue;
268
+ }
269
+ $product['attributes'][$product_identity_key] = $product_identity_value;
270
+ $attributes_type[$product_identity_key] = 'identity';
271
+ }
272
+ }
273
+
274
+ //Add physical
275
+ if (isset($product['physical']))
276
+ {
277
+ foreach ((array)$product['physical'] AS $product_physical_key => $product_physical_value)
278
+ {
279
+ $product['attributes'][$product_physical_key] = $product_physical_value;
280
+ $attributes_type[$product_physical_key] = 'physical';
281
+ }
282
+ }
283
+
284
+ //Add all options as attributes dropdown
285
+ if (isset($product['options']))
286
+ {
287
+ foreach ($product['options'] AS $product_option_key => $product_option_value)
288
+ {
289
+ $attributes_type[$product_option_key] = 'option';
290
+ $product['attributes'][$product_option_key] = $product_option_value;
291
+ }
292
+ }
293
+
294
+ $this->functions->syncProductAttributes($mg_product, $product['attributes'], $attributes_type);
295
+
296
+ //Sync product tax code
297
+ if (isset($product['taxCode']))
298
+ {
299
+ if ($product['taxCode'] === 'None') //Magento special tax name
300
+ {
301
+ $mg_product->setTaxClassId(0);
302
+ }
303
+ else
304
+ {
305
+ //Only set if tax code exists
306
+ $product_tax_class = $this->functions->getProductTaxClassByName($product['taxCode']);
307
+ if ($product_tax_class !== null && count($product_tax_class) > 0)
308
+ {
309
+ $mg_product->setTaxClassId($product_tax_class['class_id']);
310
+ }
311
+ }
312
+ }
313
+
314
+ //Sync product categories
315
+ if ($sync_product_categories)
316
+ {
317
+ $this->functions->syncProductCategories($mg_product, $product['categories']);
318
+ }
319
+
320
+ //Sync product images
321
+ if ($sync_product_images)
322
+ {
323
+ $this->functions->syncProductImages($mg_product, $product['images']);
324
+ }
325
+
326
+ //Check if should trigger save on the product
327
+ if ($mg_product->hasDataChanges())
328
+ {
329
+ $product_updates = array_diff_assoc($mg_product->getData(), $mg_product_orig_data);
330
+ $product_updates = $this->array_recursive_diff($mg_product->getData(), $mg_product_orig_data);
331
+
332
+ if (count($product_updates) > 0) //We should trigger product save
333
+ {
334
+ $mg_product->save();
335
+ }
336
+ }
337
+
338
+ //Set product's stock
339
+ if (isset($product['stock']))
340
+ {
341
+ $this->functions->setProductStockById($product['id'], $product['stock']);
342
+ }
343
+
344
+ //Update product prices
345
+ if (isset($product['prices']))
346
+ {
347
+ $pricing_websites = $this->functions->getPricingWebsites($linked_websites, $websites);
348
+ //Format product prices
349
+ $product['prices'] = $this->formatProductPrices($product['prices'], $pricing_websites);
350
+
351
+ $this->functions->setSimpleProductPrices($product['id'], $product['prices'], $pricing_websites);
352
+ }
353
+
354
+ //Success
355
+ return $product['id'];
356
+ }
357
+
358
+ /**
359
+ * Creates a product
360
+ *
361
+ * @param array $product an array containing the new product's details
362
+ * @return the id of the product's cart id
363
+ */
364
+ public function createProduct(array $product)
365
+ {
366
+ //Retrieve available product attributes
367
+ $attributes = $this->functions->getProductAttributes();
368
+
369
+ $cart_product_id = null;
370
+ $mg_product = Mage::getModel('catalog/product');
371
+
372
+ if (isset($product['productGroup']{0}))
373
+ {
374
+ $attribute_set_id = $this->functions->getAttributeSetIdByName($product['productGroup'], true); //Create if id doesnt exist
375
+ }
376
+ else
377
+ {
378
+ $attribute_set_id = $this->functions->getProductDefaultAttributeSetId();
379
+ }
380
+
381
+ $product_default_tax_class = $this->functions->getProductDefaultTaxClass();
382
+
383
+ //Set product main details
384
+ $mg_product->setTypeId('simple')
385
+ ->setCreatedAt(strtotime('now'))
386
+ ->setSku($product['sku'])
387
+ ->setName($product['name'])
388
+ ->setPrice(0.00)
389
+ ->setStatus(Mage_Catalog_Model_Product_Status::STATUS_DISABLED)
390
+ ->setVisibility(Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH) //Catalog and search visibility
391
+ ->setWebsiteIds($product['website_ids'])
392
+ ->setTaxClassId($product_default_tax_class['class_id'])
393
+ ->setAttributeSetId($attribute_set_id);
394
+
395
+ //Save product
396
+ $mg_product->save();
397
+
398
+ //Retrieve new product's id
399
+ $cart_product_id = $mg_product->getId();
400
+
401
+ if ($cart_product_id === null) //Check if product is created or not
402
+ throw new Exception('Product could not be created');
403
+
404
+ return $cart_product_id;
405
+ }
406
+
407
+ /**
408
+ * Formats the given array of prices so it converts retail to retail-1, retail-2 etc
409
+ *
410
+ * It does not override shop specific prices when are present
411
+ *
412
+ * @param array $prices an array containing the prices
413
+ * @param array $shops an array containing the shops
414
+ * @return the formatted array
415
+ */
416
+ public function formatProductPrices(array $prices, array $shops)
417
+ {
418
+ foreach (array('cost', 'rrp', 'sale', 'retail') AS $pricekey)
419
+ {
420
+ if (isset($prices[$pricekey]))
421
+ {
422
+ foreach ($shops AS $shop => $set)
423
+ {
424
+ if (!isset($prices[$pricekey . '-' . $set]))
425
+ {
426
+ $prices[$pricekey . '-' . $set] = $prices[$pricekey];
427
+ }
428
+ }
429
+
430
+ unset($prices[$pricekey]);
431
+ }
432
+
433
+ foreach ($shops AS $shop => $set)
434
+ {
435
+ if ($shop === $set)
436
+ {
437
+ continue;
438
+ }
439
+
440
+ if (isset($prices[$pricekey . '-' . $shop]))
441
+ {
442
+ $prices[$pricekey . '-' . $set] = $prices[$pricekey . '-' . $shop];
443
+ unset($prices[$pricekey . '-' . $shop]);
444
+ }
445
+ }
446
+ }
447
+
448
+ if (isset($prices[$pricekey . '-0']))
449
+ {
450
+ foreach ($prices[$pricekey . '-0'] AS $qty => $value)
451
+ {
452
+ if ($qty > 1)
453
+ {
454
+ unset($prices[$pricekey . '-0'][$qty]);
455
+ }
456
+ }
457
+ }
458
+
459
+ //Check for groups prices
460
+ $groups = $this->functions->getGroups();
461
+ foreach ($groups AS $group_id)
462
+ {
463
+ if (isset($prices['group_' . $group_id]))
464
+ {
465
+ foreach ($shops AS $shop => $set)
466
+ {
467
+ if ($shop === 0) //Remove values from groups from zero 0 because magento bugs. If a default price is used, we copy the prices to all the enabled websites
468
+ {
469
+ continue;
470
+ }
471
+
472
+ if (!isset($prices['group_' . $group_id . '-' . $set]))
473
+ {
474
+ $prices['group_' . $group_id . '-' . $set] = $prices['group_' . $group_id];
475
+ }
476
+ }
477
+
478
+ unset($prices['group_' . $group_id]);
479
+ }
480
+
481
+ foreach ($shops AS $shop => $set)
482
+ {
483
+ if ($shop === $set)
484
+ {
485
+ continue;
486
+ }
487
+
488
+ if (isset($prices['group_' . $group_id . '-' . $shop]))
489
+ {
490
+ $prices['group_' . $group_id . '-' . $set] = $prices['group_' . $group_id . '-' . $shop];
491
+ unset($prices['group_' . $group_id . '-' . $shop]);
492
+ }
493
+ }
494
+ }
495
+
496
+ return $prices;
497
+ }
498
+
499
+ function array_recursive_diff($array1, $array2)
500
+ {
501
+ $ret = array();
502
+
503
+ foreach ($array1 as $key => $value)
504
+ {
505
+ if (array_key_exists($key, $array2))
506
+ {
507
+ if (is_array($value))
508
+ {
509
+ $a_recursive_diff = $this->array_recursive_diff($value, $array2[$key]);
510
+ if (count($a_recursive_diff))
511
+ {
512
+ $ret[$key] = $a_recursive_diff;
513
+ }
514
+ }
515
+ else
516
+ {
517
+ if ((string)$value !== (string)$array2[$key])
518
+ {
519
+ $ret[$key] = $value;
520
+ }
521
+ }
522
+ }
523
+ else
524
+ {
525
+ $ret[$key] = $value;
526
+ }
527
+ }
528
+ return $ret;
529
+ }
530
+
531
+ /**
532
+ * Callback method that handles the Configuration Get event
533
+ *
534
+ * @return array
535
+ */
536
+ public function callbackBridgeConfigurationGet()
537
+ {
538
+ $magento = array(
539
+ 'adminStub' => (string)Mage::getConfig()->getNode("admin/routers/adminhtml/args")->frontName,
540
+ 'adminUrl' => Mage::helper("adminhtml")->getUrl("adminhtml"),
541
+ );
542
+
543
+ //Order statuses
544
+ foreach (Mage::getModel('sales/order_status')->getCollection()->joinStates() as $order_status)
545
+ {
546
+ $magento['order_statuses'][] = array(
547
+ 'status' => $order_status->getStatus(),
548
+ 'state' => $order_status->getState(),
549
+ );
550
+ }
551
+
552
+ //Shipping methods
553
+ $shipping_methods = Mage::getSingleton('shipping/config')->getActiveCarriers();
554
+
555
+ foreach ($shipping_methods as $shipping_code => $shipping_model)
556
+ {
557
+ $shipping_title = Mage::getStoreConfig('carriers/' . $shipping_code . '/title');
558
+ $magento['shipping_methods'][] = array(
559
+ 'code' => $shipping_code,
560
+ 'title' => $shipping_title,
561
+ );
562
+ }
563
+
564
+ //Customer groups
565
+ foreach (Mage::getModel('customer/group')->getCollection() as $group)
566
+ {
567
+ $magento['customerGroups'][] = array(
568
+ 'id' => $group->getId(),
569
+ 'name' => $group->getCode(),
570
+ );
571
+ }
572
+
573
+ //Tax rates
574
+ foreach (Mage::getModel('tax/calculation_rate')->getCollection() as $rate)
575
+ {
576
+ $magento['tax'][] = array(
577
+ 'code' => $rate->getCode(),
578
+ 'country' => $rate->getCountryId(),
579
+ 'rate' => $rate->getRate(),
580
+ );
581
+ }
582
+
583
+ $magento['sites'] = $this->getSiteData();
584
+
585
+ $modules = (array)Mage::getConfig()->getNode('modules')->children();
586
+ $core_helper = Mage::helper('core');
587
+
588
+ foreach ($modules as $mod_name => $module)
589
+ {
590
+ $mod_info = array(
591
+ 'name' => $mod_name,
592
+ 'active' => $module->is('active'),
593
+ 'output' => $core_helper->isModuleOutputEnabled($mod_name),
594
+ 'version' => $module->version,
595
+ );
596
+ $magento['modules'][] = $mod_info;
597
+ }
598
+
599
+ $php = array(
600
+ 'version' => PHP_VERSION,
601
+ 'is64bit' => PHP_INT_SIZE === 8,
602
+ 'extensions' => get_loaded_extensions(),
603
+ 'os' => php_uname('a'),
604
+ 'host' => filter_input(INPUT_SERVER, 'HOST') ?: filter_input(INPUT_SERVER, 'HTTP_HOST'),
605
+ );
606
+
607
+ $info = array(
608
+ 'cart' => $magento,
609
+ 'php' => $php, // TODO: add to meta?
610
+ );
611
+
612
+ return $info;
613
+ }
614
+
615
+ private function getSiteData()
616
+ {
617
+ $sites = array();
618
+
619
+ foreach (Mage::getModel('core/website')->getCollection() as $website)
620
+ {
621
+ $sites[$website->getId()] = array(
622
+ 'id' => $website->getId(),
623
+ 'name' => $website->getName(),
624
+ 'defaultStoreId' => $website->getDefaultGroupId(),
625
+ );
626
+ }
627
+
628
+ foreach (Mage::getModel('core/store_group')->getCollection() as $store_group)
629
+ {
630
+ $sites[$store_group->getWebsiteId()]['stores'][$store_group->getGroupId()] = array(
631
+ 'id' => $store_group->getGroupId(),
632
+ 'name' => $store_group->getName(),
633
+ 'rootCategoryId' => $store_group->getRootCategoryId(),
634
+ 'defaultStoreViewId' => $store_group->getDefaultStoreId(),
635
+ );
636
+ }
637
+
638
+ foreach (Mage::getModel('core/store')->getCollection() as $store_view)
639
+ {
640
+ $sites[$store_view->getWebsiteId()]['stores'][$store_view->getGroupId()]['views'][$store_view->getStoreId()] = array(
641
+ 'id' => $store_view->getStoreId(),
642
+ 'name' => $store_view->getName(),
643
+ 'isActive' => (bool)$store_view->getIsActive(),
644
+ );
645
+ }
646
+
647
+ return array_values($sites);
648
+ }
649
+ }
app/code/community/Netmatter/Bridge/etc/adminhtml.xml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <acl>
4
+ <resources>
5
+ <admin>
6
+ <children>
7
+ <system>
8
+ <children>
9
+ <config>
10
+ <children>
11
+ <netmatter translate="label" module="bridge">
12
+ <title>Netmatter</title>
13
+ <sort_order>9999</sort_order>
14
+ </netmatter>
15
+ </children>
16
+ </config>
17
+ </children>
18
+ </system>
19
+ </children>
20
+ </admin>
21
+ </resources>
22
+ </acl>
23
+ </config>
app/code/community/Netmatter/Bridge/etc/config.xml ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <modules>
4
+ <Netmatter_Bridge>
5
+ <version>0.0.1</version>
6
+ </Netmatter_Bridge>
7
+ </modules>
8
+ <global>
9
+ <helpers>
10
+ <bridge>
11
+ <class>Netmatter_Bridge_Helper</class>
12
+ </bridge>
13
+ </helpers>
14
+ <models>
15
+ <netmatter_bridge>
16
+ <class>Netmatter_Bridge_Model</class>
17
+ </netmatter_bridge>
18
+ </models>
19
+ <events>
20
+ <sales_order_place_after>
21
+ <observers>
22
+ <netmatter_bridge_order_observer>
23
+ <type>singleton</type>
24
+ <class>netmatter_bridge/order_observer</class>
25
+ <method>sales_order_place_after</method>
26
+ </netmatter_bridge_order_observer>
27
+ </observers>
28
+ </sales_order_place_after>
29
+ <sales_order_save_after>
30
+ <observers>
31
+ <netmatter_bridge_order_observer>
32
+ <type>singleton</type>
33
+ <class>netmatter_bridge/order_observer</class>
34
+ <method>sales_order_save_after</method>
35
+ </netmatter_bridge_order_observer>
36
+ </observers>
37
+ </sales_order_save_after>
38
+ <sales_order_payment_pay>
39
+ <observers>
40
+ <netmatter_bridge_order_observer>
41
+ <type>singleton</type>
42
+ <class>netmatter_bridge/order_observer</class>
43
+ <method>sales_order_payment_pay</method>
44
+ </netmatter_bridge_order_observer>
45
+ </observers>
46
+ </sales_order_payment_pay>
47
+ </events>
48
+ <blocks>
49
+ <bridge>
50
+ <class>Netmatter_Bridge_Block</class>
51
+ </bridge>
52
+ </blocks>
53
+ </global>
54
+ <frontend>
55
+ <routers>
56
+ <bridge>
57
+ <use>standard</use>
58
+ <args>
59
+ <module>Netmatter_Bridge</module>
60
+ <frontName>netmatter</frontName>
61
+ </args>
62
+ </bridge>
63
+ </routers>
64
+ </frontend>
65
+ </config>
app/code/community/Netmatter/Bridge/etc/system.xml ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <config>
3
+ <tabs>
4
+ <netmatter translate="label" module="bridge">
5
+ <label>Netmatter</label>
6
+ <sort_order>99999</sort_order>
7
+ </netmatter>
8
+ </tabs>
9
+ <sections>
10
+ <netmatter translate="label" module="bridge">
11
+ <label>Bridge</label>
12
+ <tab>netmatter</tab>
13
+ <frontend_type>text</frontend_type>
14
+ <sort_order>99</sort_order>
15
+ <show_in_default>1</show_in_default>
16
+ <show_in_website>0</show_in_website>
17
+ <show_in_store>0</show_in_store>
18
+ <groups>
19
+ <settings translate="label">
20
+ <label>Settings</label>
21
+ <frontend_type>text</frontend_type>
22
+ <sort_order>1</sort_order>
23
+ <show_in_default>1</show_in_default>
24
+ <show_in_website>0</show_in_website>
25
+ <show_in_store>0</show_in_store>
26
+ <fields>
27
+ <enabled translate="label">
28
+ <label>Enabled</label>
29
+ <frontend_type>select</frontend_type>
30
+ <source_model>adminhtml/system_config_source_yesno</source_model>
31
+ <sort_order>10</sort_order>
32
+ <show_in_default>1</show_in_default>
33
+ <show_in_website>0</show_in_website>
34
+ <show_in_store>0</show_in_store>
35
+ </enabled>
36
+ <client_name translate="label">
37
+ <label>Client Name</label>
38
+ <frontend_type>text</frontend_type>
39
+ <sort_order>20</sort_order>
40
+ <show_in_default>1</show_in_default>
41
+ <show_in_website>0</show_in_website>
42
+ <show_in_store>0</show_in_store>
43
+ </client_name>
44
+ <source translate="label">
45
+ <label>Endpoint Key</label>
46
+ <frontend_type>text</frontend_type>
47
+ <sort_order>30</sort_order>
48
+ <show_in_default>1</show_in_default>
49
+ <show_in_website>0</show_in_website>
50
+ <show_in_store>0</show_in_store>
51
+ </source>
52
+ <authentication_token translate="label">
53
+ <label>Authentication Token</label>
54
+ <frontend_type>text</frontend_type>
55
+ <sort_order>40</sort_order>
56
+ <show_in_default>1</show_in_default>
57
+ <show_in_website>0</show_in_website>
58
+ <show_in_store>0</show_in_store>
59
+ </authentication_token>
60
+ </fields>
61
+ </settings>
62
+ </groups>
63
+ </netmatter>
64
+ </sections>
65
+ </config>
app/code/community/Netmatter/Bridge/plugin/bridge.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ if (!defined('NETMATTER_BRIDGE_PLUGIN_ROOT')) { define('NETMATTER_BRIDGE_PLUGIN_ROOT', realpath(dirname(__FILE__))); class Netmatter_Bridge_Registry { private $data = array(); private static $instance = null; private function __construct() {} private static function getInstance() { if (self::$instance !== null) { return self::$instance; } self::$instance = new Netmatter_Bridge_Registry(); return self::$instance; } private function _set($key, $value) { $this->data[$key] = $value; } public static function set($key, $value) { self::getInstance()->_set($key, $value); } private function _get($key) { return isset($this->data[$key]) ? $this->data[$key] : null; } public static function get($key) { return self::getInstance()->_get($key); } private function clearAll() { $this->data = array(); } public static function reset() { self::getInstance()->clearAll(); } } class Netmatter_Bridge_Bridge { private $callbacks = array(); private $connector; private $queue; private $logger; private $host_version; private $integration_version; function __construct(Netmatter_Bridge_Connector $connector, Netmatter_Bridge_Queue $queue, Netmatter_Bridge_Logger_LoggerInterface $logger) { $this->connector = $connector; $this->queue = $queue; $this->logger = $logger; $this->connector->setRetries(0); } public function setCredentials($client_name, $source, $auth) { $this->connector->setClientName($client_name); $this->connector->setSource($source); $this->connector->setAuth($auth); } public function sendOrder(Netmatter_Bridge_DTO_Order $order) { $event = netmatter_bridge_factory_event()->create('order_created'); $event->addOrder($order->toArray()); $meta['version'] = array( 'plugin' => $this->getPluginVersion(), 'integration' => $this->getIntegrationVersion(), 'host' => $this->getHostVersion() ); $event->setMeta($meta, true); return $this->queue->send($event, $this->connector); } public function createOrder() { return netmatter_bridge_factory_dto()->create('order'); } public function registerCallback($event_code, $callback) { $this->callbacks[$event_code] = $callback; return $this; } public function getCallbacks() { return $this->callbacks; } function listen($data = null, array $server = null) { if ($data === null) { $data = file_get_contents('php://input'); } if ($server === null) { $server = $_SERVER; } $this->logger->log('Receiving data: ' . $data); $ret = $this->connector->parseRequest($data, $server); if ($ret->status !== 1) { $this->logger->log('Sending data: ' . $ret->body); foreach ($ret->headers AS $header) header($header); echo $ret->body; return; } $method = 'processEvent' . $ret->code; $output = (array)$this->{$method}($ret->event); $output['meta']['version']['plugin'] = $this->getPluginVersion(); $output['meta']['version']['integration'] = $this->getIntegrationVersion(); $output['meta']['version']['host'] = $this->getHostVersion(); $output = json_encode($output); $this->logger->log('Sending data: ' . $output); header('Content-Type: application/json'); echo $output; } public function processEventProductStockModified(Netmatter_Bridge_Event_ProductStockModified $event) { $errors = array(); foreach ($event->getProducts() AS $product) { $product_key = isset($product['sku']) ? $product['sku'] : $product['id']; ob_start(); try { $this->logger->log('Updating stock for product: ' . $product_key . '. New stock: ' . $product['stock']); $callback_errors = call_user_func($this->callbacks['product_stock_modified'], $product); if ($callback_errors) { $errors[$product_key] = $callback_errors; $this->logger->log('Errors: ' . print_r($callback_errors, true)); } } catch (Exception $e) { $this->logger->log($e); $errors[$product_key] = array('general' => 'Exception: ' . $e->getMessage()); } $unexpected_output = ob_get_clean(); if ($unexpected_output !== '') { $errors[$product_key]['unexpected_output'] = $unexpected_output; } } return array( 'response' => true, 'errors' => $errors ); } public function processEventProductModified(Netmatter_Bridge_Event_ProductModified $event) { $cart_product_ids = array(); $errors = array(); foreach ($event->getProducts() AS $product) { $cart_product_id = null; $product_key = isset($product['sourceId']) ? $product['sourceId'] : $product['identity']['sku']; ob_start(); if (isset($product['prices'])) { foreach ($product['prices'] AS $key => $price) { if (!is_array($price)) { $product['prices'][$key] = array("1" => $price); } } } try { $ret = call_user_func($this->callbacks['product_modified'], $product); if (is_array($ret)) $errors[$product_key] = $ret; else $cart_product_id = $ret; $cart_product_ids[$product_key] = $cart_product_id; if ($cart_product_id !== null) { $this->logger->log('Setting product: ' . $product_key . '. Internal id: ' . $cart_product_id); } } catch (Exception $e) { $this->logger->log($e); $errors[$product_key] = array('general' => 'Exception: ' . $e->getMessage()); } $unexpected_output = ob_get_clean(); if ($unexpected_output !== '') { $errors[$product_key]['unexpected_output'] = $unexpected_output; } } return array( 'response' => $cart_product_ids, 'errors' => $errors ); } public function processEventOrderStatusModified(Netmatter_Bridge_Event_OrderStatusModified $event) { $errors = array(); foreach ($event->getOrders() AS $order) { $order_key = $order['id']; ob_start(); try { $this->logger->log('Updating status for order: ' . $order_key . '. New status: ' . $order['statusId']); $callback_errors = call_user_func($this->callbacks['order_status_modified'], $order); if ($callback_errors) { $errors[$order_key] = $callback_errors; } } catch (Exception $e) { $this->logger->log($e); $errors[$order_key] = array('general' => 'Exception: ' . $e->getMessage()); } $unexpected_output = ob_get_clean(); if ($unexpected_output !== '') { $errors[$order_key]['unexpected_output'] = $unexpected_output; } } return array( 'response' => true, 'errors' => $errors ); } public function processEventConfigurationGet(Netmatter_Bridge_Event_ConfigurationGet $event) { $response = array(); $errors = array(); try { $this->logger->log('Reading configuration'); $response = call_user_func($this->callbacks['configuration_get']); } catch (Exception $e) { $this->logger->log($e); $errors = array('general' => 'Exception: ' . $e->getMessage()); } $unexpected_output = ob_get_clean(); if ($unexpected_output !== '') { $errors['unexpected_output'] = $unexpected_output; } return array( 'response' => $response, 'errors' => $errors ); } public function setHostVersion($host_version) { $this->host_version = $host_version; } public function getHostVersion() { return $this->host_version; } public function setIntegrationVersion($integration_version) { $this->integration_version = $integration_version; } public function getIntegrationVersion() { return $this->integration_version; } public function getPluginVersion() { return NETMATTER_BRIDGE_VERSION; } } class Netmatter_Bridge_Queue { private $queueDir; private $lockHandle; public function __construct($queue_directory) { $this->queueDir = realpath($queue_directory); } public function send(Netmatter_Bridge_Event_AbstractEvent $event, Netmatter_Bridge_Connector $connector) { $queued = (bool)$this->enqueue($event); $this->processQueue($connector); return $queued; } public function enqueue(Netmatter_Bridge_Event_AbstractEvent $event) { $json = json_encode($event->toArray()); list($usec, $sec) = explode(' ', microtime()); $filename = sprintf('%s.%s.json', $sec, substr($usec, 2)); $bytes = file_put_contents($this->queueDir . DIRECTORY_SEPARATOR . $filename, $json); return $bytes === strlen($json); } public function processQueue(Netmatter_Bridge_Connector $connector) { if (!$this->getLock()) { return false; } $sent = 0; while ($filename = $this->getNextFilename()) { $contents = file_get_contents($filename); $event = netmatter_bridge_factory_event()->createFromString($contents); $ret = $connector->sendEvent($event); if (!$ret || !$this->isHttpStatusSuccessful($ret->code)) { break; } $sent++; unlink($filename); } $this->releaseLock(); return $sent; } private function isHttpStatusSuccessful($status_code) { return $status_code >= 200 && $status_code < 300; } private function getLock() { $this->lockHandle = fopen($this->queueDir . '/transmit.lock', 'w'); return flock($this->lockHandle, LOCK_EX|LOCK_NB); } private function getNextFilename() { $queue = glob($this->queueDir . '/*.json'); return isset($queue[0]) ? $queue[0] : false; } private function releaseLock() { if ($this->lockHandle === null) { return; } flock($this->lockHandle, LOCK_UN); } public function getQueueDir() { return $this->queueDir; } public function __destruct() { $this->releaseLock(); } } class Netmatter_Bridge_Connector { private $clientName; private $source; private $auth; private $sslCert; private $retries; private $lastRetries; private $connection; private $logger; public function __construct(Netmatter_Bridge_Connection_ConnectionInterface $connection, Netmatter_Bridge_Logger_LoggerInterface $logger) { $this->connection = $connection; $this->logger = $logger; $this->retries = 0; } public function sendEvent(Netmatter_Bridge_Event_AbstractEvent $event) { $data = $event->toArray(); $data['guid'] = guidv4(); $data['auth'] = $this->getAuth(); $data['source'] = $this->getSource(); $data['clientName'] = $this->getClientName(); $post = json_encode($data); $this->connection->init(); $this->connection->addHeader('Content-Type: application/json'); if ($this->sslCert !== null) { $this->connection->setSSLCert($this->sslCert)->setSSLVerifyPeer(1); } $this->logger->log('Sending data to bridge: ' . $post); $this->lastRetries = 0; while (true) { $response = $this->connection->execute('', $post); $this->logger->log('Receiving from bridge: ' . json_encode($response)); if ($response && $response->code === 202) break; if ($this->lastRetries >= $this->retries) { $this->logger->log('Failed: Maximum retries reached. ' . $this->lastRetries . '. Data: ' . $post); break; } $this->lastRetries++; $this->logger->log('Retrying #' . $this->lastRetries . ': ' . $post); } return $response; } function parseRequest($json, array $server) { $ret = new StdClass(); if (!isset($server['SERVER_PROTOCOL'])) $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; if (!isset($server['REQUEST_METHOD']) || $server['REQUEST_METHOD'] !== 'POST') { $ret->status = 0; $ret->headers = array( $server['SERVER_PROTOCOL'] . ' 405 Method Not Allowed', 'Allow: POST' ); $ret->body = 'Only post requests are allowed'; return $ret; } try { $event = netmatter_bridge_factory_event()->createFromString($json); } catch (Exception $e) { $this->logger->log($e); $ret->status = 0; $ret->headers = array( $server['SERVER_PROTOCOL'] . ' 400 Bad Request' ); $ret->body = json_encode(array('response' => false, 'errors' => array('general' => 'Malformed data given'))); return $ret; } if ($this->checkAuth($event->getAuth()) !== true) { $ret->status = 0; $ret->headers = array( $server['SERVER_PROTOCOL'] . ' 403 Forbidden' ); $ret->body = json_encode(array('response' => false, 'errors' => array('general' => 'Authentication token not valid'))); return $ret; } $ret->status = 1; $ret->event = $event; $ret->code = netmatter_bridge_camelcase($event->getResourceType(), '.') . netmatter_bridge_camelcase($event->getLifecycleEvent()); return $ret; } public function setCredentials($clientName, $source, $auth) { $this->clientName = $clientName; $this->source = $source; $this->auth = $auth; return $this; } function checkAuth($auth) { return $this->auth === $auth; } function getClientName() { return $this->clientName; } function setClientName($clientName) { $this->clientName = $clientName; return $this; } function getSource() { return $this->source; } function setSource($source) { $this->source = $source; return $this; } function getAuth() { return $this->auth; } function setAuth($auth) { $this->auth = $auth; return $this; } function getConnection() { return $this->connection; } function getLogger() { return $this->logger; } function setRetries($retries) { $this->retries = $retries; return $this; } function getRetries() { return $this->retries; } function setSSLCert($sslCert) { $this->sslCert = $sslCert; return $this; } function getSSLCert() { return $this->sslCert; } function getLastRetries() { return $this->lastRetries; } } abstract class Netmatter_Bridge_DTO_AbstractDTO { public function enforceUnicode($string) { if (function_exists('mb_detect_encoding') && mb_detect_encoding($string, array('UTF-8', 'UTF-7', 'ASCII'), true) === false) { $string = utf8_encode($string); } return $string; } abstract public function toArray(); } class Netmatter_Bridge_DTO_Customer extends Netmatter_Bridge_DTO_AbstractDTO { private $id; private $salutation; private $firstname; private $lastname; private $emailAddress; private $telephone; private $mobileTelephone; private $company; private $street; private $suburb; private $city; private $county; private $postcode; private $countryIsoCode; public function setId($id) { $this->id = $id; return $this; } public function getId() { return $this->id; } public function setSalutation($salutation) { $this->salutation = $this->enforceUnicode($salutation); return $this; } public function getSalutation() { return $this->salutation; } public function setFirstname($firstname) { $this->firstname = $this->enforceUnicode($firstname); return $this; } public function getFirstname() { return $this->firstname; } public function setLastname($lastname) { $this->lastname = $this->enforceUnicode($lastname); return $this; } public function geLastname() { return $this->firstname; } public function setEmailAddress($emailAddress) { $this->emailAddress = $this->enforceUnicode($emailAddress); return $this; } public function getEmailAddress() { return $this->emailAddress; } public function setTelephone($telephone) { $this->telephone = $this->enforceUnicode($telephone); return $this; } public function getTelephone() { return $this->telephone; } public function getMobileTelephone() { return $this->mobileTelephone; } public function setMobileTelephone($mobileTelephone) { $this->mobileTelephone = $this->enforceUnicode($mobileTelephone); return $this; } public function setCompany($company) { $this->company = $this->enforceUnicode($company); return $this; } public function getCompany() { return $this->company; } public function setStreet($street) { $this->street = $this->enforceUnicode($street); return $this; } public function getStreet() { return $this->street; } public function setSuburb($suburb) { $this->suburb = $this->enforceUnicode($suburb); return $this; } public function getSuburb() { return $this->suburb; } public function setCity($city) { $this->city = $this->enforceUnicode($city); return $this; } public function getCity() { return $this->city; } public function setCounty($county) { $this->county = $this->enforceUnicode($county); return $this; } public function getCounty() { return $this->county; } public function setPostcode($postcode) { $this->postcode = $this->enforceUnicode($postcode); return $this; } public function getPostcode() { return $this->postcode; } public function setCountryIsoCode($countryIsoCode) { $this->countryIsoCode = $this->enforceUnicode($countryIsoCode); return $this; } public function getCountryIsoCode() { return $this->countryIsoCode; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_LineItem extends Netmatter_Bridge_DTO_AbstractDTO { private $name; private $options = array(); private $productId; private $sku; private $quantity; private $rowNet; private $rowGross; private $rowTax; private $taxCode; public function setName($name) { $this->name = $this->enforceUnicode($name); return $this; } public function getName() { return $this->name; } public function addOption($key, $value) { $this->options[$key] = $this->enforceUnicode($value); } public function getOptions() { return $this->options; } public function setProductId($productId) { $this->productId = $this->enforceUnicode($productId); return $this; } public function getProductId() { return $this->productId; } public function setSku($sku) { $this->sku = $this->enforceUnicode($sku); return $this; } public function getSku() { return $this->sku; } public function setQuantity($quantity) { $this->quantity = $quantity; return $this; } public function getQuantity() { return $this->quantity; } public function setRowNet($rowNet) { $this->rowNet = $rowNet; return $this; } public function getRowNet() { return $this->rowNet; } public function setRowGross($rowGross) { $this->rowGross = $rowGross; return $this; } public function getRowGross() { return $this->rowGross; } public function setRowTax($rowTax) { $this->rowTax = $rowTax; return $this; } public function getRowTax() { return $this->rowTax; } public function setTaxCode($taxCode) { $this->taxCode = $this->enforceUnicode($taxCode); return $this; } public function getTaxCode() { return $this->taxCode; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_Address extends Netmatter_Bridge_DTO_AbstractDTO { private $firstname; private $lastname; private $company; private $street; private $suburb; private $city; private $county; private $postcode; private $countryIsoCode; private $telephone; private $mobileTelephone; private $emailAddress; public function setFirstname($firstname) { $this->firstname = $this->enforceUnicode($firstname); return $this; } public function getFirstname() { return $this->firstname; } public function setLastname($lastname) { $this->lastname = $this->enforceUnicode($lastname); return $this; } public function getLastname() { return $this->lastname; } public function setCompany($company) { $this->company = $this->enforceUnicode($company); return $this; } public function getCompany() { return $this->company; } public function setStreet($street) { $this->street = $this->enforceUnicode($street); return $this; } public function getStreet() { return $this->street; } public function setSuburb($suburb) { $this->suburb = $this->enforceUnicode($suburb); return $this; } public function getSuburb() { return $this->suburb; } public function setCity($city) { $this->city = $this->enforceUnicode($city); return $this; } public function getCity() { return $this->city; } public function setCounty($county) { $this->county = $this->enforceUnicode($county); return $this; } public function getCounty() { return $this->county; } public function setPostcode($postcode) { $this->postcode = $this->enforceUnicode($postcode); return $this; } public function getPostcode() { return $this->postcode; } public function setCountryIsoCode($countryIsoCode) { $this->countryIsoCode = $this->enforceUnicode($countryIsoCode); return $this; } public function getCountryIsoCode() { return $this->countryIsoCode; } public function setTelephone($telephone) { $this->telephone = $this->enforceUnicode($telephone); return $this; } public function getTelephone() { return $this->telephone; } public function setMobileTelephone($mobileTelephone) { $this->mobileTelephone = $this->enforceUnicode($mobileTelephone); return $this; } public function getMobileTelephone() { return $this->mobileTelephone; } public function setEmailAddress($emailAddress) { $this->emailAddress = $this->enforceUnicode($emailAddress); return $this; } public function getEmailAddress() { return $this->emailAddress; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_Shipping extends Netmatter_Bridge_DTO_AbstractDTO { private $method; private $methodLabel; private $net; private $gross; private $tax; private $taxCode; private $gift_message; public function setMethod($method) { $this->method = $this->enforceUnicode($method); return $this; } public function getMethod() { return $this->method; } public function setMethodLabel($methodLabel) { $this->methodLabel = $this->enforceUnicode($methodLabel); return $this; } public function getMethodLabel() { return $this->methodLabel; } public function setNet($net) { $this->net = $net; return $this; } public function getNet() { return $this->net; } public function setGross($gross) { $this->gross = $gross; return $this; } public function getGross() { return $this->gross; } public function setTax($tax) { $this->tax = $tax; return $this; } public function getTax() { return $this->tax; } public function setTaxCode($taxCode) { $this->taxCode = $this->enforceUnicode($taxCode); return $this; } public function getTaxCode() { return $this->taxCode; } public function setGiftMessage($message, $recipient = null, $sender = null) { $this->gift_message = array( 'message' => $message, 'recipient' => $recipient, 'sender' => $sender ); return $this; } public function getGiftMessage() { return $this->gift_message; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_Discount extends Netmatter_Bridge_DTO_AbstractDTO { private $label; private $taxCode; private $net; private $gross; private $tax; public function setLabel($label) { $this->label = $this->enforceUnicode($label); return $this; } public function getLabel() { return $this->label; } public function setTaxCode($taxCode) { $this->taxCode = $this->enforceUnicode($taxCode); return $this; } public function getTaxCode() { return $this->taxCode; } public function setNet($net) { $this->net = $net; return $this; } public function getNet() { return $this->net; } public function setGross($gross) { $this->gross = $gross; return $this; } public function getGross() { return $this->gross; } public function setTax($tax) { $this->tax = $tax; return $this; } public function getTax() { return $this->tax; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_Payment extends Netmatter_Bridge_DTO_AbstractDTO { private $method; private $currency; private $baseCurrency; private $amount; private $baseAmount; private $notes = array(); private $exceptions = array(); private $details; public function setMethod($method) { $this->method = $this->enforceUnicode($method); return $this; } public function getMethod() { return $this->method; } public function setCurrency($currency) { $this->currency = $this->enforceUnicode($currency); return $this; } public function getCurrency() { return $this->currency; } public function setBaseCurrency($baseCurrency) { $this->baseCurrency = $this->enforceUnicode($baseCurrency); return $this; } public function getBaseCurrency() { return $this->baseCurrency; } public function setAmount($amount) { $this->amount = $amount; return $this; } public function getAmount() { return $this->amount; } public function setBaseAmount($baseAmount) { $this->baseAmount = $baseAmount; return $this; } public function getBaseAmount() { return $this->baseAmount; } public function addNote($note) { $this->notes[] = $this->enforceUnicode($note); return $this; } public function getNotes() { return $this->notes; } public function addException($exception) { $this->exceptions[] = $this->enforceUnicode($exception); return $this; } public function getExceptions() { return $this->exceptions; } public function addSagepayDetails() { $this->details = new Netmatter_Bridge_DTO_PaymentDetails_Sagepay(); return $this->details; } public function addDefaultDetails() { $this->details = new Netmatter_Bridge_DTO_PaymentDetails_Default(); return $this->details; } public function addInfusionSoftDetails() { $this->details = new Netmatter_Bridge_DTO_PaymentDetails_InfusionSoft(); return $this->details; } public function addPaypalDetails() { $this->details = new Netmatter_Bridge_DTO_PaymentDetails_Paypal(); return $this->details; } public function addEpdqDetails() { $this->details = new Netmatter_Bridge_DTO_PaymentDetails_Epdq(); return $this->details; } public function addWorldPayDetails() { $this->details = new Netmatter_Bridge_DTO_PaymentDetails_WorldPay(); return $this->details; } public function toArray() { $data = get_object_vars($this); if ($this->details !== null) $data['details'] = $this->details->toArray(); return $data; } } abstract class Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails extends Netmatter_Bridge_DTO_AbstractDTO { protected $txId; protected $status; protected $statusLabel; public function setTxId($txId) { $this->txId = $this->enforceUnicode($txId); return $this; } public function getTxId() { return $this->txId; } public function setStatus($status) { $this->status = $this->enforceUnicode($status); return $this; } public function getStatus() { return $this->status; } public function setStatusLabel($statusLabel) { $this->statusLabel = $this->enforceUnicode($statusLabel); return $this; } public function getStatusLabel() { return $this->statusLabel; } } class Netmatter_Bridge_DTO_PaymentDetails_Default extends Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails { private $values = array(); public function setValue($key, $value) { $this->values[$key] = $this->enforceUnicode($value); return $this; } public function getValue($key) { if (!isset($this->values[$key])) { return null; } return $this->values[$key]; } public function toArray() { $ret = get_object_vars($this); unset($ret['values']); foreach ($this->values as $key => $value) { $ret[$key] = $value; } return $ret; } } class Netmatter_Bridge_DTO_PaymentDetails_Paypal extends Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails { private $payerId; private $payerFirstname; private $payerLastname; private $payerEmailAddress; private $type; public function setPayerId($payerId) { $this->payerId = $this->enforceUnicode($payerId); return $this; } public function getPayerId() { return $this->payerId; } public function setPayerFirstname($payerFirstname) { $this->payerFirstname = $this->enforceUnicode($payerFirstname); return $this; } public function getPayerFirstname() { return $this->payerFirstname; } public function setPayerLastname($payerLastname) { $this->payerLastname = $this->enforceUnicode($payerLastname); return $this; } public function getPayerLastname() { return $this->payerLastname; } public function setPayerEmailAddress($payerEmailAddress) { $this->payerEmailAddress = $this->enforceUnicode($payerEmailAddress); return $this; } public function getPayerEmailAddress() { return $this->payerEmailAddress; } public function setType($type) { $this->type = $this->enforceUnicode($type); return $this; } public function getType() { return $this->type; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_PaymentDetails_Epdq extends Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails { private $ccBrand; private $aavCheck; private $cvcCheck; public function setCcBrand($ccBrand) { $this->ccBrand = $this->enforceUnicode($ccBrand); return $this; } public function getCcBrand() { return $this->ccBrand; } public function setAavCheck($aavCheck) { $this->aavCheck = $this->enforceUnicode($aavCheck); return $this; } public function getAavCheck() { return $this->aavCheck; } public function setCvcCheck($cvcCheck) { $this->cvcCheck = $this->enforceUnicode($cvcCheck); return $this; } public function getCvcCheck() { return $this->cvcCheck; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_PaymentDetails_Sagepay extends Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails { private $authCode; private $avsCv2Check; private $cv2Result; private $addressResult; private $postcodeResult; private $threeDSecureStatus; public function setAuthCode($authCode) { $this->authCode = $this->enforceUnicode($authCode); return $this; } public function getAuthCode() { return $this->authCode; } public function setAvsCv2Check($avsCv2Check) { $this->avsCv2Check = $this->enforceUnicode($avsCv2Check); return $this; } public function getAvsCv2Check() { return $this->avsCv2Check; } public function setCv2Result($cv2Result) { $this->cv2Result = $this->enforceUnicode($cv2Result); return $this; } public function getCv2Result() { return $this->cv2Result; } public function setAddressResult($addressResult) { $this->addressResult = $this->enforceUnicode($addressResult); return $this; } public function getAddressResult() { return $this->addressResult; } public function setPostcodeResult($postcodeResult) { $this->postcodeResult = $this->enforceUnicode($postcodeResult); return $this; } public function getPostcodeResult() { return $this->postcodeResult; } public function setThreeDSecureStatus($threeDSecureStatus) { $this->threeDSecureStatus = $this->enforceUnicode($threeDSecureStatus); return $this; } public function getThreedSecureStatus() { return $this->threeDSecureStatus; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_PaymentDetails_WorldPay extends Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails { private $payerName; private $payerEmailAddress; private $message; private $transStatus; private $countryMatch; private $avs; private $rawAuthCode; private $authMode; public function setPayerName($payerName) { $this->payerName = $this->enforceUnicode($payerName); return $this; } public function getPayerName() { return $this->payerName; } public function setPayerEmailAddress($payerEmailAddress) { $this->payerEmailAddress = $this->enforceUnicode($payerEmailAddress); return $this; } public function getPayerEmailAddress() { return $this->payerEmailAddress; } public function setMessage($message) { $this->message = $this->enforceUnicode($message); return $this; } public function getMessage() { return $this->message; } public function setCountryMatch($countryMatch) { $this->countryMatch = $this->enforceUnicode($countryMatch); return $this; } public function getCountryMatch() { return $this->countryMatch; } public function setAvs($avs) { $this->avs = $this->enforceUnicode($avs); return $this; } public function getAvs() { return $this->avs; } public function setRawAuthCode($rawAuthCode) { $this->rawAuthCode = $this->enforceUnicode($rawAuthCode); return $this; } public function getRawAuthCode() { return $this->rawAuthCode; } public function setAuthmode($authMode) { $this->authMode = $this->enforceUnicode($authMode); return $this; } public function getAuthMode() { return $this->authMode; } public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_PaymentDetails_InfusionSoft extends Netmatter_Bridge_DTO_PaymentDetails_AbstractPaymentDetails { public function toArray() { return get_object_vars($this); } } class Netmatter_Bridge_DTO_Order extends Netmatter_Bridge_DTO_AbstractDTO { private $id; private $publicId; private $comment; private $channelId; private $lineItems = array(); private $customer; private $delivery; private $billing; private $shipping; private $discounts = array(); private $payment; private $leadSource; private $datePlaced; private $orderStatus; private $total; private $isPaid; public function setId($id) { $this->id = $id; return $this; } public function getId() { return $this->id; } public function setPublicId($publicId) { $this->publicId = $publicId; return $this; } public function getPublicId() { return $this->publicId; } function addLineItem() { $lineItem = new Netmatter_Bridge_DTO_LineItem(); $this->lineItems[] = $lineItem; return $lineItem; } public function getLineItems() { return $this->lineItems; } public function addCustomer() { $this->customer = new Netmatter_Bridge_DTO_Customer(); return $this->customer; } public function getCustomer() { return $this->customer; } public function addDelivery() { $this->delivery = new Netmatter_Bridge_DTO_Address(); return $this->delivery; } public function getDelivery() { return $this->delivery; } public function addBilling() { $this->billing = new Netmatter_Bridge_DTO_Address(); return $this->billing; } public function getBilling() { return $this->billing; } public function setComment($comment) { $this->comment = $this->enforceUnicode($comment); return $this; } public function getComment() { return $this->comment; } public function setChannelId($channelId) { $this->channelId = $this->enforceUnicode($channelId); return $this; } public function getChannelId() { return $this->channelId; } public function setOrderStatus($orderStatus) { $this->orderStatus = $this->enforceUnicode($orderStatus); return $this; } public function getOrderStatus() { return $this->orderStatus; } public function setLeadSource($leadSource) { $this->leadSource = $this->enforceUnicode($leadSource); return $this; } public function getLeadSource() { return $this->leadSource; } public function setDatePlaced($datePlaced) { $this->datePlaced = $datePlaced; return $this; } public function getDatePlaced() { return $this->datePlaced; } public function getTotal() { return $this->total; } public function setTotal($total) { $this->total = $total; return $this; } public function setIsPaid($isPaid) { $this->isPaid = $isPaid; return $this; } public function getIsPaid() { return $this->isPaid; } public function addShipping() { $this->shipping = new Netmatter_Bridge_DTO_Shipping(); return $this->shipping; } public function getShipping() { return $this->shipping; } public function addPayment() { $this->payment = new Netmatter_Bridge_DTO_Payment(); return $this->payment; } public function addDiscount() { $discount = new Netmatter_Bridge_DTO_Discount(); $this->discounts[] = $discount; return $discount; } public function getDiscounts() { return $this->discounts; } public function getPayment() { return $this->payment; } public function toArray() { $data = get_object_vars($this); if ($this->customer !== null) $data['customer'] = $this->customer->toArray(); if ($this->delivery !== null) $data['delivery'] = $this->delivery->toArray(); if ($this->billing !== null) $data['billing'] = $this->billing->toArray(); if ($this->shipping !== null) $data['shipping'] = $this->shipping->toArray(); if ($this->payment !== null) $data['payment'] = $this->payment->toArray(); $data['discounts'] = array(); foreach ($this->discounts AS $key => $discount) $data['discounts'][$key] = $discount->toArray(); $data['lineItems'] = array(); foreach ($this->lineItems AS $key => $lineItem) $data['lineItems'][$key] = $lineItem->toArray(); return $data; } } abstract class Netmatter_Bridge_Event_AbstractEvent { protected $auth; protected $idSet; protected $client_name; protected $source_name; protected $meta = array(); abstract public function parseObjects(array $objects); public function setAuth($auth) { $this->auth = $auth; } public function getAuth() { return $this->auth; } abstract public function getResourceType(); public function setIdSet($idSet) { $this->idSet = $idSet; } public function getIdSet() { return $this->idSet; } public function setMeta(array $meta, $append = false) { if ($append !== false) $this->meta = array_merge($meta, $this->meta); else $this->meta = $meta; } public function getMeta() { return $this->meta; } abstract public function getLifecycleEvent(); abstract public function getObjects(); public function toArray() { return array( 'auth' => $this->auth, 'resourceType' => $this->getResourceType(), 'idSet' => $this->idSet, 'lifecycleEvent' => $this->getLifecycleEvent(), 'objects' => $this->getObjects(), 'meta' => $this->getMeta() ); } public function toJson() { return json_encode($this->toArray()); } } class Netmatter_Bridge_Event_OrderCreated extends Netmatter_Bridge_Event_AbstractEvent { private $orders = array(); public function getResourceType() { return 'order'; } public function getLifecycleEvent() { return 'created'; } public function addOrder(array $data) { $errors = $this->checkOrder($data); if ($errors) return $errors; $this->orders[] = $data; $this->syncIdSet(); } public function checkOrder(array $data) { return array(); } private function syncIdSet() { $ids = array(); foreach ($this->orders AS $order) $ids[] = $order['id']; $this->setIdSet(implode(',', $ids)); } public function parseObjects(array $objects) { if (!isset($objects['orders'])) throw new Exception('Orders key not present'); $orders = array(); foreach ($objects['orders'] AS $key => $order) { $errors = $this->checkOrder($order); if ($errors) return $errors; $orders[] = $order; } $this->orders = $orders; } public function getOrders() { return $this->orders; } public function getObjects() { return array( 'orders' => $this->orders ); } } class Netmatter_Bridge_Event_OrderStatusModified extends Netmatter_Bridge_Event_AbstractEvent { private $orders = array(); public function getResourceType() { return 'order.status'; } public function getLifecycleEvent() { return 'modified'; } public function parseObjects(array $objects) { if (!isset($objects['orders'])) throw new Exception('Orders key not present'); $orders = array(); foreach ($objects['orders'] AS $key => $order) { if (!isset($order['id'])) throw new Exception('Order id not set'); if (!isset($order['statusId'])) throw new Exception('Order statusId not set'); $orders[] = $order; } $this->orders = $orders; } public function getOrders() { return $this->orders; } public function getObjects() { return array( 'order' => $this->orders ); } } class Netmatter_Bridge_Event_ProductCreated extends Netmatter_Bridge_Event_AbstractEvent { private $products = array(); public function getResourceType() { return 'product'; } public function getLifecycleEvent() { return 'created'; } public function parseObjects(array $objects) { if (!isset($objects['products'])) throw new Exception('Products key not present'); $products = array(); foreach ($objects['products'] AS $key => $product) { $products[] = $product; } $this->products = $products; } public function getProducts() { return $this->products; } public function getObjects() { return array( 'products' => $this->products ); } } class Netmatter_Bridge_Event_ProductModified extends Netmatter_Bridge_Event_AbstractEvent { private $products = array(); public function getResourceType() { return 'product'; } public function getLifecycleEvent() { return 'modified'; } public function parseObjects(array $objects) { if (!isset($objects['products'])) throw new Exception('Products key not present'); $products = array(); foreach ($objects['products'] AS $key => $product) { if (!isset($product['identity'])) { throw new Exception('Missing products informations'); } $products[] = $product; } $this->products = $products; } public function getProducts() { return $this->products; } public function getObjects() { return array( 'products' => $this->products ); } } class Netmatter_Bridge_Event_ProductStockModified extends Netmatter_Bridge_Event_AbstractEvent { private $products = array(); public function getResourceType() { return 'product.stock'; } public function getLifecycleEvent() { return 'modified'; } public function parseObjects(array $objects) { if (!isset($objects['products'])) throw new Exception('Products key not present'); $products = array(); foreach ($objects['products'] AS $key => $product) { if (!isset($product['id']) && !isset($product['sku'])) throw new Exception('Product id or sku not set'); if (!isset($product['stock'])) throw new Exception('Product stock not set or invalid'); $products[] = $product; } $this->products = $products; } public function getProducts() { return $this->products; } public function getObjects() { return array( 'products' => $this->products ); } } class Netmatter_Bridge_Event_ConfigurationGet extends Netmatter_Bridge_Event_AbstractEvent { public function getResourceType() { return 'configuration'; } public function getLifecycleEvent() { return 'get'; } public function parseObjects(array $objects) { } public function getObjects() { return array(); } } class Netmatter_Bridge_Factory_DTO { public function create($key) { $key = netmatter_bridge_camelcase($key); $class_name = 'Netmatter_Bridge_DTO_' . $key; return new $class_name(); } } class Netmatter_Bridge_Factory_Event { public function create($key) { $key = netmatter_bridge_camelcase($key); $class_name = 'Netmatter_Bridge_Event_' . $key; return new $class_name(); } public function createFromString($string) { $data = json_decode($string, true); if ($data === null) { throw new Exception('Invalid json string given'); } if (!isset($data['resourceType']{0})) { throw new Exception('ResourceType not present'); } if (!isset($data['lifecycleEvent']{0})) { throw new Exception('LivecycleEvent not present'); } $key = str_replace('.', '_', $data['resourceType']) . '_' . $data['lifecycleEvent']; $event = $this->create($key); if (isset($data['auth']{0})) { $event->setAuth($data['auth']); } if (isset($data['idSet']{0})) { $event->setIdSet($data['idSet']); } if (isset($data['objects'])) { $event->parseObjects($data['objects']); } if (isset($data['meta'])) { $event->setMeta($data['meta']); } return $event; } } interface Netmatter_Bridge_Logger_LoggerInterface { public function log($text); } class Netmatter_Bridge_Logger_Array implements Netmatter_Bridge_Logger_LoggerInterface { private $logs = array(); public function log($text) { $this->logs[] = $text; } public function getLogs() { return $this->logs; } public function getLast() { $count = count($this->logs); return $count > 0 ? $this->logs[$count - 1] : null; } } class Netmatter_Bridge_Logger_File implements Netmatter_Bridge_Logger_LoggerInterface { private $file; public function __construct($file) { $this->file = $file; } public function log($text) { $handler = fopen($this->file, 'a+'); fwrite($handler, gmdate('d.m.Y H:i:s T', time()) . ' | ' . $text . PHP_EOL . PHP_EOL); fclose($handler); } } class Netmatter_Bridge_Logger_RotatingFiles implements Netmatter_Bridge_Logger_LoggerInterface { private $dir; private $chanceOfCleanup = 1000; private $numberOfOldLogs = 6; private $uniqueId; public function __construct($dir) { $this->dir = $dir; $this->file = $this->createLogName(); $this->uniqueId = uniqid(getmypid()); } private function createLogName($time = null) { if (!$time) { $time = time(); } return date('Y-m-d', $time) . '.log'; } public function log($text) { $handler = fopen($this->dir . '/' . $this->file, 'a+'); fwrite($handler, gmdate('Y-m-d\TH:i:s\Z') . ' @' . $this->uniqueId . ' ' . $text . PHP_EOL); fclose($handler); } public function clearOldLogs() { $first_log = $this->createLogName(strtotime($this->numberOfOldLogs . ' days ago')); foreach (glob($this->dir . '/????-??-??.log') as $filename) { if (basename($filename) < $first_log) { unlink($filename); } } } public function __destruct() { if (mt_rand(0, $this->chanceOfCleanup) === 0) { $this->clearOldLogs(); } } } class Netmatter_Bridge_Logger_Null implements Netmatter_Bridge_Logger_LoggerInterface { public function log($text) { } } class Netmatter_Bridge_Logger_Screen implements Netmatter_Bridge_Logger_LoggerInterface { public function log($text) { echo $text . PHP_EOL; } } interface Netmatter_Bridge_Connection_ConnectionInterface { public function init(); public function close(); public function addHeader($header); public function getHeaders(); public function setTimeout($timeout); public function setSSLCert($ssl_cert); public function getSSLCert(); public function setSSLVerifyPeer($ssl_verify_peer); public function getSSLVerifyPeer(); public function execute($query_string = null, $post_string = null); } class Netmatter_Bridge_Connection_Curl implements Netmatter_Bridge_Connection_ConnectionInterface { private $url; private $handler; private $headers = array(); private $options = array(); function __construct($url) { $this->url = $url; } function init() { $this->close(); $this->handler = curl_init($this->url); $this->options = array(); $this->headers = array(); $this->setOption(CURLOPT_RETURNTRANSFER, 1); $this->setOption(CURLOPT_CONNECTTIMEOUT, 3); $this->setOption(CURLOPT_HEADER, 1); $this->setOption(CURLOPT_TIMEOUT, 30); $this->setOption(CURLOPT_SSL_VERIFYHOST, 2); $this->setOption(CURLOPT_SSL_VERIFYPEER, 0); } function close() { if ($this->handler !== null) { curl_close($this->handler); $this->handler = null; } } public function getUrl() { return $this->url; } public function addHeader($header) { if (!in_array($header, $this->headers)) $this->headers[] = $header; return $this; } public function getHeaders() { return $this->headers; } public function setTimeout($timeout) { $this->setOption(CURLOPT_TIMEOUT, $timeout); } public function setSSLCert($ssl_cert) { $this->setOption(CURLOPT_SSLCERT, $ssl_cert); return $this; } public function getSSLCert() { return $this->getOption(CURLOPT_SSLCERT); } public function setSSLVerifyPeer($ssl_verify_peer) { $this->setOption(CURLOPT_SSL_VERIFYPEER, $ssl_verify_peer); return $this; } public function getSSLVerifyPeer() { return $this->getOption(CURLOPT_SSL_VERIFYPEER); } public function setOption($key, $value) { $this->options[$key] = $value; return $this; } public function getOption($key) { return isset($this->options[$key]) ? $this->options[$key] : null; } public function getOptions() { return $this->options; } function curlExec() { return curl_exec($this->handler); } function curlGetInfo($opt) { return curl_getinfo($this->handler, $opt); } function execute($query_string = null, $post_string = null) { if ($this->handler === null) $this->init(); if ($query_string !== null) { $this->setOption(CURLOPT_URL, $this->url . $query_string); } if ($post_string !== null) { $this->setOption(CURLOPT_POST, 1); $this->setOption(CURLOPT_POSTFIELDS, $post_string); } if (count($this->headers) > 0) { $this->setOption(CURLOPT_HTTPHEADER, $this->headers); } foreach ($this->options AS $key => $value) curl_setopt($this->handler, $key, $value); $response = $this->curlExec($this->handler); if ($response === false) { return (object)array('code' => 0, 'headers' => array(), 'body' => 'Error: ' . curl_error($this->handler) . '. No: ' . curl_errno($this->handler)); } $code = $this->curlGetInfo(CURLINFO_HTTP_CODE); $header_size = $this->curlGetInfo(CURLINFO_HEADER_SIZE); $header = substr($response, 0, $header_size); $headers = netmatter_bridge_parse_http_headers($header); $body = substr($response, $header_size); return (object)array('code' => $code, 'headers' => $headers, 'body' => $body); } function getHandler() { return $this->handler; } function __destruct() { $this->close(); } } function netmatter_bridge_connector() { $connector = Netmatter_Bridge_Registry::get('service::connector'); if ($connector !== null) { return $connector; } $connection = new Netmatter_Bridge_Connection_Curl(Netmatter_Bridge_Registry::get('config::bridge_callback')); $connector = new Netmatter_Bridge_Connector($connection, netmatter_bridge_logger()); $connector->setAuth(Netmatter_Bridge_Registry::get('config::auth')); $connector->setClientName(Netmatter_Bridge_Registry::get('config::client_name')); $connector->setSource(Netmatter_Bridge_Registry::get('config::source')); Netmatter_Bridge_Registry::set('service::connector', $connector); return $connector; } function netmatter_bridge_bridge() { $bridge = Netmatter_Bridge_Registry::get('service::bridge'); if ($bridge !== null) { return $bridge; } $bridge = new Netmatter_Bridge_Bridge(netmatter_bridge_connector(), netmatter_bridge_queue(), netmatter_bridge_logger()); Netmatter_Bridge_Registry::set('service::bridge', $bridge); return $bridge; } function netmatter_bridge_logger() { $logger = null; $log_type = Netmatter_Bridge_Registry::get('config::log_type'); switch ($log_type) { case 'file': $log_file = Netmatter_Bridge_Registry::get('config::log_file'); if (!isset($log_file{0})) throw new Exception('Netmatter bridge log_file not set or not writable'); $logger = new Netmatter_Bridge_Logger_File($log_file); break; case 'files': $log_dir = Netmatter_Bridge_Registry::get('config::log_dir'); if (!isset($log_dir)) { throw new Exception('Netmatter bridge log_dir not set'); } $logger = new Netmatter_Bridge_Logger_RotatingFiles($log_dir); break; case 'array': $logger = new Netmatter_Bridge_Logger_Array(); break; case 'screen': $logger = new Netmatter_Bridge_Logger_Screen(); break; default: $logger = new Netmatter_Bridge_Logger_Null(); } return $logger; } function netmatter_bridge_queue() { $queue = Netmatter_Bridge_Registry::get('service::queue'); if ($queue !== null) { return $queue; } $queue_directory = Netmatter_Bridge_Registry::get('config::queue_dir'); $queue = new Netmatter_Bridge_Queue($queue_directory); Netmatter_Bridge_Registry::set('service::queue', $queue); return $queue; } function netmatter_bridge_factory_event() { return new Netmatter_Bridge_Factory_Event(); } function netmatter_bridge_factory_dto() { return new Netmatter_Bridge_Factory_DTO(); } function netmatter_bridge_camelcase($string, $separator = null) { if ($separator === null) { $separator = '_'; } return str_replace(' ', '', ucwords(str_replace($separator, ' ', $string))); } function netmatter_bridge_parse_http_headers($header) { $headers = array(); $key = ''; foreach(explode("\n", $header) AS $i => $h) { $h = explode(':', $h, 2); if (isset($h[1])) { if (!isset($headers[$h[0]])) { $headers[$h[0]] = trim($h[1]); } elseif (is_array($headers[$h[0]])) { $headers[$h[0]] = array_merge($headers[$h[0]], array(trim($h[1]))); } else { $headers[$h[0]] = array_merge(array($headers[$h[0]]), array(trim($h[1]))); } $key = $h[0]; } else { if (substr($h[0], 0, 1) == "\t") { $headers[$key] .= "\r\n\t" . trim($h[0]); } elseif (!$key) { $headers[0] = trim($h[0]); } } } return $headers; } function guidv4() { if (function_exists('openssl_random_pseudo_bytes')) { $data = openssl_random_pseudo_bytes(16); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); $data[8] = chr(ord($data[8]) & 0x3f | 0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } else { return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); } } function netmatter_float_equals($float1, $float2) { if (function_exists('bccomp')) { return bccomp($float1, $float2, 10) === 0; } $epsilon = 0.0000000001; return abs($float1 - $float2) < $epsilon; } if (is_readable(NETMATTER_BRIDGE_PLUGIN_ROOT . '/config.php')) { require(NETMATTER_BRIDGE_PLUGIN_ROOT . '/config.php'); } if (isset($netmatter_bridge_config) && is_array($netmatter_bridge_config)) { foreach ($netmatter_bridge_config AS $key => $value) { Netmatter_Bridge_Registry::set('config::' . $key, $value); } } $bridge = netmatter_bridge_bridge(); define('NETMATTER_BRIDGE_VERSION', '1.2.2'); } $bridge = netmatter_bridge_bridge();
app/code/community/Netmatter/Bridge/plugin/config.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ //The url that the connector will try to communicate to
3
+ $netmatter_bridge_config['bridge_callback'] = 'https://test/callback.php';
4
+
5
+ //Queue settings
6
+ $netmatter_bridge_config['queue_dir'] = Mage::getBaseDir('var') . '/bridge/queue'; // Outgoing message queue directory
7
+
8
+ //The log settings
9
+ $netmatter_bridge_config['log_type'] = 'files'; //screen|array|file|files|null
10
+ $netmatter_bridge_config['log_file'] = ''; //When logger is file, the filename to write logs to
11
+ $netmatter_bridge_config['log_dir'] = Mage::getBaseDir('var') . '/bridge/log'; //When logger is RotatingFiles, the dir to use
app/etc/modules/Netmatter_Bridge.xml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ /**
4
+ * Netmatter
5
+ *
6
+ * @category Netmatter
7
+ * @package Netmatter_Bridge
8
+ * @author Netmatter Team
9
+ * @copyright Copyright (c) 2008-2014 Netmatter Ltd. (http://www.netmatter.co.uk)
10
+ */
11
+ -->
12
+ <config>
13
+ <modules>
14
+ <Netmatter_Bridge>
15
+ <active>true</active>
16
+ <codePool>community</codePool>
17
+ </Netmatter_Bridge>
18
+ </modules>
19
+ </config>
package.xml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <package>
3
+ <name>Netmatter_Bridge</name>
4
+ <version>1.1.0</version>
5
+ <stability>stable</stability>
6
+ <license uri="http://www.opensource.org/licenses/osl-3.0.php">OSL v3.0</license>
7
+ <channel>community</channel>
8
+ <extends/>
9
+ <summary>The Volo extension to Magento, powered by Netmatter, allows all high performance Magento stores &#x2013; from single instance up to multiple, multi-lingual stores &#x2013; to carry out 2-way bulk data transfers with the Volo platform in real time, with no loss of Magento performance.</summary>
10
+ <description>Volo Commerce&#x2019;s platform and services orchestrate the complex ecommerce universe for large online merchants and brands to help them quickly and sustainably &#xD;
11
+ grow their multichannel sales.&#xD;
12
+ &#xD;
13
+ At the heart of this is a comprehensive platform which manages multichannel ecommerce in all the key areas: purchasing, supply, inventory, stock levels, orders, fulfilment, shipping, refunds and returns, and customer service.&#xD;
14
+ &#xD;
15
+ The platform automates what were previously manual processes, allowing sellers to grow rapidly and realise major productivity gains which impact both top line revenues and bottom line profits. &#xD;
16
+ &#xD;
17
+ In the area of multichannel management, the Volo platform enables sellers to add new channels quickly, centrally manage all their channels and post optimised listings simultaneously across the different marketplaces where they have a presence. All Volo customers on average double their revenues within 24 months.&#xD;
18
+ &#xD;
19
+ The Volo extension to Magento, powered by Netmatter, allows all high performance Magento stores &#x2013; from single instance up to multiple, multi-lingual stores &#x2013; to carry out 2-way bulk data transfers with the Volo platform in real time, with no loss of Magento performance.&#xD;
20
+ &#xD;
21
+ This official extension is part of the Netmatter iPaaS network, which makes it possible for multichannel sellers to connect their store to a network of integrated end-points through one common language and configuration.&#xD;
22
+ </description>
23
+ <notes>The Volo extension to Magento, powered by Netmatter, allows all high performance Magento stores &#x2013; from single instance up to multiple, multi-lingual stores &#x2013; to carry out 2-way bulk data transfers with the Volo platform in real time, with no loss of Magento performance.</notes>
24
+ <authors><author><name>Netmatter</name><user>netmatter</user><email>support@netmatter.co.uk</email></author></authors>
25
+ <date>2016-05-25</date>
26
+ <time>15:57:36</time>
27
+ <contents><target name="magecommunity"><dir name="Netmatter"><dir name="Bridge"><dir name="Block"><dir name="Adminhtml"><file name="Config.php" hash="c591e8ab43b0ec3573eca83b87daba54"/></dir></dir><dir name="Helper"><file name="Data.php" hash="58598d341eb1190cbd46771510d60c34"/></dir><dir name="Model"><dir name="Order"><file name="Observer.php" hash="2e721431bab206c7c85d9a300f3abfc2"/></dir></dir><dir name="controllers"><file name="CallbackController.php" hash="ac557ce846b284a6c0ba75cfbad85b8b"/></dir><dir name="etc"><file name="adminhtml.xml" hash="182a9db254fd689ee893e2ee5f99de07"/><file name="config.xml" hash="c22bd996078f42df236720d9949148b7"/><file name="system.xml" hash="6d0d3b24fa437f789ee41109773c7440"/></dir><dir name="plugin"><file name="bridge.php" hash="f9140bb29b8c63765fbfdd59018d10f2"/><file name="config.php" hash="9767abf32066dbec049a6eac028ae02c"/></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Netmatter_Bridge.xml" hash="2183bab433800ec9839bb4866e96fc78"/></dir></target><target name="mage"><dir name="var"><dir name="bridge"><dir name="log"><file name="warning.txt" hash="40dcbdea0a509204682ce3e57d3b9487"/></dir><dir name="queue"><file name="warning.txt" hash="40dcbdea0a509204682ce3e57d3b9487"/></dir></dir></dir></target></contents>
28
+ <compatible/>
29
+ <dependencies><required><php><min>5.2.2</min><max>7.0.0</max></php></required></dependencies>
30
+ </package>
var/bridge/log/warning.txt ADDED
@@ -0,0 +1 @@
 
1
+ The logger will assume that any file in this directory is safe to delete.
var/bridge/queue/warning.txt ADDED
@@ -0,0 +1 @@
 
1
+ The logger will assume that any file in this directory is safe to delete.