Fruugo_Integration - Version 1.0.6

Version Notes

This update includes:

- Only add one price tag for each product

- Enabled currency conversion

Download this release

Release Info

Developer inoutput.io
Extension Fruugo_Integration
Version 1.0.6
Comparing to
See all releases


Code changes from version 1.0.5 to 1.0.6

Files changed (45) hide show
  1. app/code/community/Fruugo/Integration/.DS_Store +0 -0
  2. app/code/community/Fruugo/Integration/Block/.DS_Store +0 -0
  3. app/code/community/Fruugo/Integration/Block/Catalog/.DS_Store +0 -0
  4. app/code/community/Fruugo/Integration/Block/Catalog/Product/.DS_Store +0 -0
  5. app/code/community/Fruugo/Integration/Block/Sales/.DS_Store +0 -0
  6. app/code/community/Fruugo/Integration/Block/Sales/Order/.DS_Store +0 -0
  7. app/code/community/Fruugo/Integration/Block/Sales/Order/View/.DS_Store +0 -0
  8. app/code/community/Fruugo/Integration/Helper/.DS_Store +0 -0
  9. app/code/community/Fruugo/Integration/Helper/Logger.php +40 -2
  10. app/code/community/Fruugo/Integration/Helper/ProductsFeedGenerator.php +634 -137
  11. app/code/community/Fruugo/Integration/Helper/ProductsFeedGeneratorProfiler.php +340 -0
  12. app/code/community/Fruugo/Integration/Model/.DS_Store +0 -0
  13. app/code/community/Fruugo/Integration/Model/Adminhtml/.DS_Store +0 -0
  14. app/code/community/Fruugo/Integration/Model/Adminhtml/System/.DS_Store +0 -0
  15. app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/.DS_Store +0 -0
  16. app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Backend/.DS_Store +0 -0
  17. app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/ExportPageSize.php +52 -0
  18. app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/MaxErrors.php +52 -0
  19. app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/MaxResourcesLoad.php +46 -0
  20. app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/SleepTimeSec.php +52 -0
  21. app/code/community/Fruugo/Integration/Model/CronJobObserver.php +1 -5
  22. app/code/community/Fruugo/Integration/controllers/.DS_Store +0 -0
  23. app/code/community/Fruugo/Integration/controllers/.gitignore +3 -1
  24. app/code/community/Fruugo/Integration/controllers/ProductsController.php +26 -32
  25. app/code/community/Fruugo/Integration/data/.DS_Store +0 -0
  26. app/code/community/Fruugo/Integration/design/.DS_Store +0 -0
  27. app/code/community/Fruugo/Integration/design/adminhtml/.DS_Store +0 -0
  28. app/code/community/Fruugo/Integration/design/adminhtml/default/.DS_Store +0 -0
  29. app/code/community/Fruugo/Integration/design/adminhtml/default/default/.DS_Store +0 -0
  30. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/.DS_Store +0 -0
  31. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/.DS_Store +0 -0
  32. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/catalog/.DS_Store +0 -0
  33. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/catalog/product/.DS_Store +0 -0
  34. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/sales/.DS_Store +0 -0
  35. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/sales/order/.DS_Store +0 -0
  36. app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/sales/order/view/.DS_Store +0 -0
  37. app/code/community/Fruugo/Integration/etc/config.xml +6 -1
  38. app/code/community/Fruugo/Integration/etc/system.xml +40 -0
  39. app/design/adminhtml/default/default/template/integration/.DS_Store +0 -0
  40. app/design/adminhtml/default/default/template/integration/catalog/.DS_Store +0 -0
  41. app/design/adminhtml/default/default/template/integration/catalog/product/.DS_Store +0 -0
  42. app/design/adminhtml/default/default/template/integration/sales/.DS_Store +0 -0
  43. app/design/adminhtml/default/default/template/integration/sales/order/.DS_Store +0 -0
  44. app/design/adminhtml/default/default/template/integration/sales/order/view/.DS_Store +0 -0
  45. package.xml +6 -12
app/code/community/Fruugo/Integration/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Block/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Block/Catalog/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Block/Catalog/Product/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Block/Sales/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Block/Sales/Order/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Block/Sales/Order/View/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Helper/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Helper/Logger.php CHANGED
@@ -22,8 +22,46 @@
22
 
23
  class Fruugo_Integration_Helper_Logger extends Mage_Core_Helper_Abstract
24
  {
25
- public static function log($message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  {
27
- Mage::log("Integration - " . $message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
  }
22
 
23
  class Fruugo_Integration_Helper_Logger extends Mage_Core_Helper_Abstract
24
  {
25
+ const ALWAYS = 0;
26
+ const ERROR = 1;
27
+ const WARNING = 2;
28
+ const INFO = 3;
29
+ const DEBUG = 4;
30
+
31
+ public static $LOG_LEVEL = self::WARNING;
32
+ public static $CODE = array(
33
+ self::ALWAYS => 'NOTICE',
34
+ self::ERROR => 'ERROR',
35
+ self::WARNING=> 'WARNING',
36
+ self::INFO => 'INFO',
37
+ self::DEBUG => 'DEBUG',
38
+ );
39
+
40
+ public static function log($message, $level = self::INFO)
41
+ {
42
+ if ($level <= self::$LOG_LEVEL) {
43
+ Mage::log("Fruugo - " . self::$CODE[$level] . ': ' . $message);
44
+ }
45
+ }
46
+
47
+ public static function getFormattedReport($report)
48
  {
49
+ $repStr = "\n\n---------------------------[ Fruugo Product Export Results ]---------------------------\n";
50
+ $repStr .= "Status: " . $report['status'] . "\n";
51
+ $repStr .= "Total products exported: " . $report['total_exported'] . "\n";
52
+ $repStr .= "Total products processed: " . $report['total_processed'] . "\n";
53
+ $repStr .= "Start time: " . $report['start_time_utc'] . "\n";
54
+ $repStr .= "End time: " . $report['end_time_utc'] . "\n";
55
+ $repStr .= "Total processing time: " . $report['processing_time_sec'] . " seconds\n";
56
+ $repStr .= "Total paused time: " . $report['time_paused_sec'] . " seconds\n";
57
+ $repStr .= "Max memory used: " . $report['max_ram_usage_mb'] . " MB\n";
58
+ $repStr .= "Avg memory used: " . round($report['avg_ram_usage_mb'], 2) . " MB\n";
59
+ $repStr .= "Avg resource load: " . round($report['avg_load'], 2) . "\n";
60
+ $repStr .= "Error count: " . $report['error_count'] . "\n";
61
+ $repStr .= "Resource threshold triggers: " . $report['load_above_threshold_count'] . "\n";
62
+ $repStr .= "XML File size: " . round($report['xml_file_size_mb'], 2) . " MB\n";
63
+ $repStr .= "----------------------------------------------------------------------------------------\n\n";
64
+
65
+ return $repStr;
66
  }
67
  }
app/code/community/Fruugo/Integration/Helper/ProductsFeedGenerator.php CHANGED
@@ -24,64 +24,236 @@ require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/Logger.php'
24
 
25
  class Fruugo_Integration_ProductsFeedGenerator extends Mage_Core_Helper_Abstract
26
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  public function generateProdcutsFeed($cached = false)
28
  {
29
- $cachedFile = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/products.xml';
30
- if ($cached === true && file_exists($cachedFile)) {
31
- return $cachedFile;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  } else {
33
- Fruugo_Integration_Helper_Logger::log("Start exporting products data feed.");
 
 
 
 
 
 
 
 
 
 
 
 
34
  $productsXml;
35
 
36
- $devMode = Mage::getStoreConfig('integration_options/orders_options/dev_mode');
37
- if ($devMode == '1') {
38
- $productsXml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>'
39
- .'<Products xmlns="http://schemas.fruugo.com/fruugoflat"></Products>');
40
  } else {
41
- $productsXml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>'
42
- .'<Products></Products>');
 
 
 
43
  }
44
 
45
- $products = Mage::getModel('catalog/product')
46
- ->getCollection()
47
- ->addAttributeToSelect('*') // select all attributes
48
- ->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED) // only select enabled products
49
- ->addAttributeToFilter('type_id', Mage_Catalog_Model_Product_Type::TYPE_SIMPLE); // only select simple products
50
 
51
  // Get store langauge map or use null if none
52
  $storelangsSettings = Mage::getStoreConfig('integration_options/products_options/language_store');
53
  $storelangsMapping = empty($storelangsSettings) ? null : unserialize($storelangsSettings);
54
 
55
- foreach ($products as $product) {
56
- $disabledOnFruugo = false;
57
- $productCountries = Mage::getModel('integration/countries')->load($product->getId(), 'product_id');
58
- if (isset($productCountries) && $productCountries->getFruugoCountries() == 'Disabled') {
59
- $disabledOnFruugo = true;
 
 
 
 
 
 
 
 
 
 
 
60
  }
 
 
 
 
 
 
 
 
 
 
61
 
62
- $parentProduct = $this->_getParentProduct($product);
63
- $images = $this->_getProductImages($product, $parentProduct);
64
-
65
- if (!$disabledOnFruugo &&
66
- $product->getSku() !== null &&
67
- $product->getName() !== null &&
68
- $product->getDescription() !== null &&
69
- $product->getPrice() !== null &&
70
- count($images) > 0) {
71
- $productXml = $productsXml->addChild('Product');
72
- $productXml = $this->_fillProductXml($productXml, $product, $storelangsMapping);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
 
 
 
 
75
 
76
- Fruugo_Integration_Helper_Logger::log("Exporting products data feed finished.");
77
- return $productsXml;
 
 
78
  }
 
 
79
  }
80
 
81
- public function _fillProductXml($productXml, $product, $storelangsMapping)
82
  {
83
- // M: Mandatory R: Recommended O: Optional
84
  $parentProduct = $this->_getParentProduct($product);
 
 
 
 
 
 
 
85
 
86
  // ProductId *M
87
  if (isset($parentProduct)) {
@@ -115,33 +287,27 @@ class Fruugo_Integration_ProductsFeedGenerator extends Mage_Core_Helper_Abstract
115
 
116
  // Category *R
117
  $categoryEntity = $product->getCategoryCollection()
 
118
  ->addAttributeToSort('level', 'DESC')
119
  ->addAttributeToSort('position', 'DESC')
120
  ->addAttributeToFilter('is_active', '1')
121
  ->getFirstItem();
122
 
123
- $category = Mage::getModel('catalog/category')
124
- ->load($categoryEntity->getId());
125
-
126
- if ($category->getName() !== null) {
127
- $productXml->addChild('Category', htmlspecialchars($category->getName()));
128
  }
129
 
130
  // Imageurl1 *M
131
  // Imageurl2, Imageurl3, Imageurl4, Imageurl5 *R
132
- $images = $this->_getProductImages($product, $parentProduct);
133
  $imageIndex = 0;
134
 
135
- foreach ($images as $image) {
136
  if ($imageIndex >= 5) {
137
  break;
138
  }
139
 
140
- $imageUrl = $image->getUrl();
141
- if (strpos($imageUrl, 'placeholder') !== true) {
142
- $productXml->addChild('Imageurl'.($imageIndex + 1), $imageUrl);
143
- $imageIndex++;
144
- }
145
  }
146
 
147
  // StockStatus & StockQuantity *M
@@ -150,9 +316,14 @@ class Fruugo_Integration_ProductsFeedGenerator extends Mage_Core_Helper_Abstract
150
  if ($stockItem === null || !$stockItem->getIsInStock()) {
151
  $productXml->addChild('StockStatus', 'OUTOFSTOCK');
152
  } else {
153
- $productXml->addChild('StockStatus', 'INSTOCK');
154
  $stocklevel = (int)$stockItem->getQty();
155
- $productXml->addChild('StockQuantity', $stocklevel);
 
 
 
 
 
 
156
  }
157
 
158
  // RestockDate *O
@@ -165,178 +336,504 @@ class Fruugo_Integration_ProductsFeedGenerator extends Mage_Core_Helper_Abstract
165
  }
166
 
167
  // Description Node
168
- $stores = Mage::app()->getStores(false);
169
-
170
  $addedLanguages = array();
171
- foreach ($stores as $store) {
 
172
  // store 'fruugo' is for Fruugo orders
173
- if ($store->getCode() != 'fruugo') {
174
- $product = $product->setStoreId($store->getId())->load($product->getId());
175
-
176
- $localeCode = Mage::getStoreConfig('general/locale/code', $store->getId());
177
- $language = substr($localeCode, 0, strpos($localeCode, '_'));
178
-
179
- // Make sure no language is added more than once
180
- if (!in_array($language, $addedLanguages)) {
181
- // Add description when no store selected for language OR the store is selected for the langauge in storelangsMapping array
182
- if ($storelangsMapping == null || $storelangsMapping[$localeCode] == "" || $storelangsMapping[$localeCode] == $store->getCode()) {
183
- // Language *R
184
- $descriptionXml = $productXml->addChild('Description');
185
- $descriptionXml->addChild('Language', $language);
186
-
187
- $descriptionXml->addChild('Title', htmlspecialchars($product->getName()));
188
-
189
- // description
190
- $nestedDescriptionXml = $descriptionXml->addChild('Description');
191
- $descriptionType = Mage::getStoreConfig('integration_options/products_options/descrption_type');
192
-
193
- if ($descriptionType == 'long') {
194
- $this->_addCData($nestedDescriptionXml, $product->getDescription());
195
- } elseif ($descriptionType == 'short') {
196
- $this->_addCData($nestedDescriptionXml, $product->getShortDescription());
197
- } elseif ($descriptionType == 'merge_short_first') {
198
- $this->_addCData($nestedDescriptionXml, $product->getShortDescription() . PHP_EOL . $product->getDescription());
199
- } else {
200
- $this->_addCData($nestedDescriptionXml, $product->getDescription() . PHP_EOL . $product->getShortDescription());
201
- }
202
 
203
- // AttributeColor *R
204
- if ($product->getColor() !== null && $product->getAttributeText('color')) {
205
- $descriptionXml->addChild('AttributeColor', $product->getAttributeText('color'));
206
- }
207
 
208
- // AttributeSize *R
209
- if ($product->getShoeSize() !== null && $product->getAttributeText('shoe_size')) {
210
- $descriptionXml->addChild('AttributeSize', $product->getAttributeText('shoe_size'));
211
- } elseif ($product->getSize() !== null && $product->getAttributeText('size')) {
212
- $descriptionXml->addChild('AttributeSize', $product->getAttributeText('size'));
213
- }
214
 
215
- // optional attributes: Arrtibute1 - Attribute10 *O
216
- if ($product->getFit() !== null) {
217
- $descriptionXml->addChild('Attribute1', $product->getFit());
218
- }
219
- if ($product->getLength() !== null) {
220
- $descriptionXml->addChild('Attribute2', $product->getLength());
221
- }
222
- if ($product->getWidth() !== null) {
223
- $descriptionXml->addChild('Attribute3', $product->getWidth());
224
- }
 
 
 
225
 
226
- array_push($addedLanguages, $language);
227
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  }
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
 
232
  $addedCurrencies = array();
233
- foreach ($stores as $store) {
234
- if ($store->getCode() != 'fruugo') {
 
 
 
 
 
 
235
  $currencyCode = $store->getCurrentCurrencyCode();
 
 
 
236
  if (in_array($currencyCode, $addedCurrencies)) {
237
  continue;
238
  } else {
239
  array_push($addedCurrencies, $currencyCode);
240
  }
241
- $product->setStoreId($store->getId())->load($product->getId());
242
  // Price Node
243
  $priceXml = $productXml->addChild('Price');
244
  // Currency *R
245
  $priceXml->addChild('Currency', $currencyCode);
246
  // Country
247
- $existingProductCountries = Mage::getModel('integration/countries')->load($product->getId(), 'product_id');
248
- if (isset($existingProductCountries) && $existingProductCountries->getFruugoCountries() != null) {
249
- $existingList = $existingProductCountries->getFruugoCountries();
250
  $priceXml->addChild('Country', $existingList);
251
  }
252
 
253
  // Normal price.
254
- $taxHelper = Mage::helper('tax');
255
- $normalPriceExclTax = $taxHelper->getPrice($product, $product->getPrice(), false);
256
- $priceXml->addChild('NormalPriceWithoutVAT', number_format($normalPriceExclTax, 2, '.', ''));
257
-
258
- // VATRate.
259
- $taxCalculation = Mage::getModel('tax/calculation');
260
- $request = $taxCalculation->getRateRequest(null, null, null, $store);
261
- $taxClassId = $product->getTaxClassId();
262
- $percent = $taxCalculation->getRate($request->setProductClassId($taxClassId));
263
- $priceXml->addChild('VATRate', number_format($percent, 2, '.', ''));
264
 
265
  // Discount price
266
- $finalPriceExclTax = $taxHelper->getPrice($product, $product->getFinalPrice(), false);
267
- $rulePriceExclTax = Mage::getModel('catalogrule/rule')->calcProductPriceRule($product, $normalPriceExclTax);
268
  $discountedPriceExclTax = null;
 
 
 
269
 
270
  if ($rulePriceExclTax == null || $finalPriceExclTax < $rulePriceExclTax) {
271
  $discountedPriceExclTax = $finalPriceExclTax;
272
  } else {
273
  $discountedPriceExclTax = $rulePriceExclTax;
274
  }
275
- if ($normalPriceExclTax > $discountedPriceExclTax) {
276
- $priceXml->addChild('DiscountPriceWithoutVAT', number_format($discountedPriceExclTax, 2, '.', ''));
 
 
 
 
 
 
 
 
 
277
  // DiscountPriceStartDate *O
278
  if ($product->getSpecialFromDate()) {
279
  $fromTime = strtotime($product->getSpecialFromDate());
280
- $formatedFromTimeStr = date('Y-m-d',$fromTime);
281
  $priceXml->addChild('DiscountPriceStartDate', $formatedFromTimeStr);
282
  }
283
 
284
  // DiscountPriceEndDate *O
285
  if ($product->getSpecialToDate()) {
286
  $toTime = strtotime($product->getSpecialToDate());
287
- $fromatedToTimeStr = date('Y-m-d',$toTime);
288
  $priceXml->addChild('DiscountPriceEndDate', $fromatedToTimeStr);
289
  }
 
 
 
 
290
  }
 
 
 
291
  }
292
  }
293
 
294
- return $productXml;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  }
296
 
297
- private function _getProductImages($product, $parentProduct)
 
298
  {
299
- $productObj = $product->load($product->getId());
 
 
300
  $images = array();
301
- $skuImages = array_values($productObj->getMediaGalleryImages()->getItems());
302
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  if (!empty($skuImages)) {
304
  foreach ($skuImages as $skuImage) {
305
- array_push($images, $skuImage);
 
 
 
306
  }
307
  }
308
 
309
  if (isset($parentProduct)) {
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  $parentImages = array_values($parentProduct->getMediaGalleryImages()->getItems());
311
  if (!empty($parentImages)) {
312
  foreach ($parentImages as $parentImage) {
313
- array_push($images, $parentImage);
 
 
 
314
  }
315
  }
316
  }
317
 
318
- return $images;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  }
320
 
321
- private function _getParentProduct($product)
322
  {
323
  $parentProduct = null;
324
 
325
  $parentIds = Mage::getResourceSingleton('catalog/product_type_configurable')
326
- ->getParentIdsByChild($product->getId());
327
 
328
  if (isset($parentIds[0])) {
329
- $parentProduct = Mage::getModel('catalog/product')->load($parentIds[0]);
 
330
  }
331
 
332
- return $parentProduct;
333
  }
334
 
335
- private function _addCData($xml, $cdata_text)
336
  {
337
  $node = dom_import_simplexml($xml);
338
  $no = $node->ownerDocument;
339
  $node->appendChild($no->createCDATASection(htmlspecialchars($cdata_text)));
340
  return $xml;
341
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  }
24
 
25
  class Fruugo_Integration_ProductsFeedGenerator extends Mage_Core_Helper_Abstract
26
  {
27
+ protected $MONITOR_RESOURCES = true; // Should the script check resource usage while running
28
+ protected $MAX_RESOURCES = 0.5; // Maximum load average allowed in the last minute
29
+ protected $SLEEP_TIME_SEC = 20; // Time to sleep for if over load limit
30
+
31
+ protected $MAX_ERRORS = 30; // The number of errors after which the script will abort, set to -1 to disable
32
+ protected $PAGE_SIZE = 100; // The number of products to process per batch
33
+ // whether or not to track the last id processed to avoid a double process if items are deleted
34
+ // in between selecting batches, incurs a small performance cost.
35
+ protected $TRACK_LAST_ID = true;
36
+
37
+ protected $stores = null;
38
+ protected $taxHelper = null;
39
+ protected $taxCalculation = null;
40
+ protected $devMode = null;
41
+ protected $currentTimer = array();
42
+ protected $categoryRule = null;
43
+ protected $tmpProductsXmlPath = null;
44
+ protected $productsXmlPath = null;
45
+ protected $report = null;
46
+ protected $currencyConverter = null;
47
+ protected $storeBaseCurrencies = null;
48
+ protected $shouldConvertCurrency = true;
49
+
50
+ protected static $ALWAYS = Fruugo_Integration_Helper_Logger::ALWAYS;
51
+ protected static $ERROR = Fruugo_Integration_Helper_Logger::ERROR;
52
+ protected static $WARNING = Fruugo_Integration_Helper_Logger::WARNING;
53
+ protected static $INFO = Fruugo_Integration_Helper_Logger::INFO;
54
+ protected static $DEBUG = Fruugo_Integration_Helper_Logger::DEBUG;
55
+
56
+
57
  public function generateProdcutsFeed($cached = false)
58
  {
59
+ $devMode = Mage::getStoreConfig('integration_options/orders_options/dev_mode');
60
+ $this->devMode = ($devMode == '1') ? true : false;
61
+
62
+ $this->productsXmlPath = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/products.xml';
63
+ if ($cached === true && file_exists($this->productsXmlPath)) {
64
+ return $this->productsXmlPath;
65
+ }
66
+
67
+ // BEGIN LOCK CHECK
68
+ // This makes sure that another copy of this script is not executing already
69
+ $lockFile = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/products.lock';
70
+ if (!file_exists($lockFile)) {
71
+ touch($lockFile);
72
+ }
73
+
74
+ $f = fopen($lockFile, 'w');
75
+ if ($f === false) {
76
+ $this->_writeLog('Did not start Fruugo products export because the script is already running.', self::$WARNING);
77
+ die('Cannot create lock file');
78
+ }
79
+
80
+ if (!flock($f, LOCK_EX | LOCK_NB)) {
81
+ $this->_writeLog('Did not start Fruugo products export because the script is already running.', self::$WARNING);
82
+ die('Cannot create lock file');
83
  } else {
84
+ $this->_writeLog('Beginning export of products feed...', self::$ALWAYS);
85
+ }
86
+ // END LOCK CHECK
87
+
88
+ if ($this->devMode) {
89
+ Fruugo_Integration_Helper_Logger::$LOG_LEVEL = self::$DEBUG;
90
+ $this->MONITOR_RESOURCES = false;
91
+ }
92
+
93
+ try {
94
+ $time = time();
95
+ $this->_setupGlobalData();
96
+ $this->report['start_time_utc'] = date("Y-m-d H:i:s", time());
97
  $productsXml;
98
 
99
+ if ($this->devMode) {
100
+ $productsXml = '<?xml version="1.0" encoding="UTF-8"?><Products xmlns="http://schemas.fruugo.com/fruugoflat">';
 
 
101
  } else {
102
+ $productsXml = '<?xml version="1.0" encoding="UTF-8"?><Products>';
103
+ }
104
+
105
+ if (file_exists($this->tmpProductsXmlPath)) {
106
+ unlink($this->tmpProductsXmlPath);
107
  }
108
 
109
+ $this->_writeProductsXml($productsXml);
 
 
 
 
110
 
111
  // Get store langauge map or use null if none
112
  $storelangsSettings = Mage::getStoreConfig('integration_options/products_options/language_store');
113
  $storelangsMapping = empty($storelangsSettings) ? null : unserialize($storelangsSettings);
114
 
115
+ $numOfProds = 0;
116
+ $currentPage = 0;
117
+ $numResults = $this->PAGE_SIZE;
118
+ $numIterations = 0;
119
+ $totalMemory = 0;
120
+ $xmlBuffer = '';
121
+ $errorsCount = 0;
122
+ $totalProcessed = 0;
123
+ $finalException = null; // This is a hack because old versions of PHP(<5.5) don't have finally
124
+ $lastProcessedId = 0;
125
+
126
+ do {
127
+ $products = Mage::getModel('catalog/product')->getCollection();
128
+ $products->addAttributeToSelect('*'); // select all attributes
129
+ if ($this->TRACK_LAST_ID) {
130
+ $products->addAttributeToFilter('entity_id', array('gt' => $lastProcessedId)); // make sure we don't double process items due to deletions during the process
131
  }
132
+ $products->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED); // only select enabled products
133
+ $products->addAttributeToFilter('type_id', Mage_Catalog_Model_Product_Type::TYPE_SIMPLE);
134
+ $products->setOrder('entity_id', 'ASC');
135
+ if ($this->TRACK_LAST_ID) {
136
+ $products->getSelect()->limit($this->PAGE_SIZE);
137
+ } else {
138
+ $products->getSelect()->limit($this->PAGE_SIZE, $currentPage);
139
+ }
140
+
141
+ $this->_writeLog("Processing page " . $currentPage. ', max memory used ' . ((memory_get_peak_usage(true) /1024)/1024) . 'MB.', self::$DEBUG);
142
 
143
+ $numResults = 0;
144
+ foreach ($products as $product) {
145
+ $numResults++;
146
+ $totalProcessed++;
147
+ $lastProcessedId = $product->getId();
148
+
149
+ $disabledOnFruugo = false;
150
+ $productCountries = Mage::getModel('integration/countries')->load($product->getId(), 'product_id');
151
+
152
+ if (isset($productCountries) && $productCountries->getFruugoCountries() == 'Disabled') {
153
+ $disabledOnFruugo = true;
154
+ continue;
155
+ }
156
+
157
+ unset($productCountries);
158
+ if ($this->_shouldInclude($product)) {
159
+ try {
160
+ $productXml = $this->_fillProductXml($product, $storelangsMapping);
161
+
162
+ if ($productXml) {
163
+ $xmlBuffer .= $productXml;
164
+ }
165
+
166
+ $numOfProds++;
167
+ } catch (Exception $ex) {
168
+ $errorsCount += 1;
169
+ Mage::logException($ex);
170
+
171
+ if (count($this->report['errors']) < 10) {
172
+ $errorLog = array(
173
+ 'product_id' => $product->getId(),
174
+ 'stack_trace' => $ex->getTraceAsString(),
175
+ 'message' => $ex->getMessage(),
176
+ );
177
+ array_push($this->report['errors'], $errorLog);
178
+ }
179
+
180
+ if ($this->MAX_ERRORS != -1 && $errorsCount > $this->MAX_ERRORS) {
181
+ $this->_writeLog('Product export aborting due to hitting maximum error threshold of ' . $this->MAX_ERRORS, self::$ERROR);
182
+ throw new Exception('Fruugo product export aborting due to hitting maximum error threshold of ' . $this->MAX_ERRORS, 500, $ex);
183
+ }
184
+ }
185
+
186
+ }
187
  }
188
+
189
+ $this->_writeProductsXml($xmlBuffer);
190
+ $numIterations += 1;
191
+ $xmlBuffer = '';
192
+ $totalMemory += ((memory_get_usage(false) /1024)/1024);
193
+
194
+ $currentPage = $currentPage + $this->PAGE_SIZE;
195
+
196
+ //clear collection and free memory
197
+ $products->resetData();
198
+ $products->clear();
199
+ unset($products);
200
+
201
+ $this->checkServerLoad();
202
+
203
+ } while ($numResults >= $this->PAGE_SIZE);
204
+
205
+ // write file end and rename
206
+ $this->_writeProductsXml('</Products>');
207
+ rename($this->tmpProductsXmlPath, $this->productsXmlPath);
208
+
209
+ $this->report['xml_file_size_mb'] = ((filesize($this->productsXmlPath) /1024)/1024);
210
+ } catch (Exception $ex) {
211
+ $this->report['status'] = 'failed';
212
+ $numOfProds = 0;
213
+ $finalException = $ex;
214
+ }
215
+
216
+ // This should probably go in a finally block but older
217
+ // version of PHP don't support it,
218
+ $this->report['total_exported'] = $numOfProds;
219
+ $this->report['processing_time_sec'] = (time() - $time);
220
+ $this->report['end_time_utc'] = date("Y-m-d H:i:s", time());
221
+ $this->report['avg_ram_usage_mb'] = $totalMemory / $numIterations;
222
+ $this->report['max_ram_usage_mb'] = ((memory_get_peak_usage(true) /1024)/1024);
223
+ $this->report['total_processed'] = $totalProcessed;
224
+ $this->report['error_count'] = $errorsCount;
225
+
226
+
227
+ if (!$this->report['status']) {
228
+ if ($errorsCount == 0) {
229
+ $this->report['status'] = 'success';
230
+ } else {
231
+ $this->report['status'] = 'success_with_errors';
232
  }
233
+ }
234
+
235
+ $this->_writeReport($this->report);
236
+ $this->_writeLog(Fruugo_Integration_Helper_Logger::getFormattedReport($this->report), self::$INFO);
237
 
238
+ if ($finalException !== null) {
239
+ $this->_writeLog('The Fruugo product export has stopped due to an error: ' . $ex->getMessage(), self::$ERROR);
240
+ Mage::logException($ex);
241
+ throw $ex;
242
  }
243
+
244
+ return $this->productsXmlPath;
245
  }
246
 
247
+ protected function _fillProductXml($product, $storelangsMapping)
248
  {
 
249
  $parentProduct = $this->_getParentProduct($product);
250
+ $images = $this->_getProductImages($product, $parentProduct);
251
+
252
+ if (count($images) == 0) {
253
+ return false;
254
+ }
255
+ // M: Mandatory R: Recommended O: Optional
256
+ $productXml = new SimpleXMLElement('<Product></Product>');
257
 
258
  // ProductId *M
259
  if (isset($parentProduct)) {
287
 
288
  // Category *R
289
  $categoryEntity = $product->getCategoryCollection()
290
+ ->addAttributeToSelect('name')
291
  ->addAttributeToSort('level', 'DESC')
292
  ->addAttributeToSort('position', 'DESC')
293
  ->addAttributeToFilter('is_active', '1')
294
  ->getFirstItem();
295
 
296
+ if ($categoryEntity->getName() !== null) {
297
+ $productXml->addChild('Category', htmlspecialchars($categoryEntity->getName()));
 
 
 
298
  }
299
 
300
  // Imageurl1 *M
301
  // Imageurl2, Imageurl3, Imageurl4, Imageurl5 *R
 
302
  $imageIndex = 0;
303
 
304
+ foreach ($images as $imageUrl) {
305
  if ($imageIndex >= 5) {
306
  break;
307
  }
308
 
309
+ $productXml->addChild('Imageurl'.($imageIndex + 1), $imageUrl);
310
+ $imageIndex++;
 
 
 
311
  }
312
 
313
  // StockStatus & StockQuantity *M
316
  if ($stockItem === null || !$stockItem->getIsInStock()) {
317
  $productXml->addChild('StockStatus', 'OUTOFSTOCK');
318
  } else {
 
319
  $stocklevel = (int)$stockItem->getQty();
320
+ if ($stocklevel <= 0) {
321
+ $productXml->addChild('StockStatus', 'OUTOFSTOCK');
322
+ $productXml->addChild('StockQuantity', 0);
323
+ } else {
324
+ $productXml->addChild('StockStatus', 'INSTOCK');
325
+ $productXml->addChild('StockQuantity', $stocklevel);
326
+ }
327
  }
328
 
329
  // RestockDate *O
336
  }
337
 
338
  // Description Node
 
 
339
  $addedLanguages = array();
340
+ $descriptionNodeCount = 0;
341
+ foreach ($this->stores as $store) {
342
  // store 'fruugo' is for Fruugo orders
343
+ if ($store->getCode() == 'fruugo') {
344
+ continue;
345
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
+ $localeCode = Mage::getStoreConfig('general/locale/code', $store->getId());
348
+ $language = substr($localeCode, 0, strpos($localeCode, '_'));
 
 
349
 
350
+ // Make sure no language is added more than once
351
+ if (in_array($language, $addedLanguages)) {
352
+ continue;
353
+ }
 
 
354
 
355
+ // Make sure product has description based on store config
356
+ $descriptionType = Mage::getStoreConfig('integration_options/products_options/descrption_type', $store);
357
+ $descriptionsArray = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), array(
358
+ 'description',
359
+ 'short_description',
360
+ ), $store->getId());
361
+
362
+ // This is a workaround for a bug in getAttributeRawValue which returns the wrong values for product attributes that have been
363
+ // modified in another site so to save db hits we try and get them and if they differ from the main product we are processing we
364
+ // then load them individually
365
+ if (isset($descriptionsArray['description']) && ($product->getDescription() != $descriptionsArray['description'])) {
366
+ $descriptionsArray['description'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'description', $store->getId());
367
+ }
368
 
369
+ if (isset($descriptionsArray['short_description']) && ($product->getShortDescription() != $descriptionsArray['short_description'])) {
370
+ $descriptionsArray['short_description'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'short_description', $store->getId());
371
+ }
372
+
373
+ // check product description not null for each store
374
+ if ($descriptionType == 'long' && $descriptionsArray['description'] === null) {
375
+ continue;
376
+ } elseif ($descriptionType == 'short' && $descriptionsArray['short_description'] === null) {
377
+ continue;
378
+ }
379
+
380
+ // Add description when no store selected for language OR the store is selected for the langauge in storelangsMapping array
381
+ if ($storelangsMapping == null || $storelangsMapping[$localeCode] == "" || $storelangsMapping[$localeCode] == $store->getCode()) {
382
+ // Language *R
383
+ $descriptionXml = $productXml->addChild('Description');
384
+ $descriptionXml->addChild('Language', $language);
385
+
386
+ $attributes = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), array(
387
+ 'shoe_size',
388
+ 'size',
389
+ 'color',
390
+ 'fit',
391
+ 'length',
392
+ 'width',
393
+ ), $store->getId());
394
+
395
+ // This is a workaround for a bug in getAttributeRawValue which returns the wrong values for product attributes that have been
396
+ // modified in another site so to save db hits we try and get them and if they differ from the main product we are processing we
397
+ // then load them individually
398
+ if (isset($attributes['fit']) && ($product->getFit() != $attributes['fit'])) {
399
+ $attributes['fit'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'fit', $store->getId());
400
  }
401
+
402
+ if (isset($attributes['color']) && ($product->getColor() != $attributes['color'])) {
403
+ $attributes['color'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'color', $store->getId());
404
+ }
405
+
406
+ if (isset($attributes['size']) && ($product->getSize() != $attributes['size'])) {
407
+ $attributes['size'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'size', $store->getId());
408
+ }
409
+
410
+ if (isset($attributes['shoe_size']) && ($product->getShoe_size() != $attributes['shoe_size'])) {
411
+ $attributes['shoe_size'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'shoe_size', $store->getId());
412
+ }
413
+
414
+ if (isset($attributes['length']) && ($product->getLength() != $attributes['length'])) {
415
+ $attributes['length'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'length', $store->getId());
416
+ }
417
+
418
+ if (isset($attributes['width']) && ($product->getWidth() != $attributes['width'])) {
419
+ $attributes['width'] = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'width', $store->getId());
420
+ }
421
+
422
+ // title
423
+ $name = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'name', $store->getId());
424
+ $descriptionXml->addChild('Title', htmlspecialchars($name));
425
+
426
+ // description
427
+ $nestedDescriptionXml = $descriptionXml->addChild('Description');
428
+
429
+ if ($descriptionType == 'long') {
430
+ $this->_addCData($nestedDescriptionXml, $descriptionsArray['description']);
431
+ } elseif ($descriptionType == 'short') {
432
+ $this->_addCData($nestedDescriptionXml, $descriptionsArray['short_description']);
433
+ } elseif ($descriptionType == 'merge_short_first') {
434
+ $this->_addCData($nestedDescriptionXml, $descriptionsArray['short_description'] . PHP_EOL . $descriptionsArray['description']);
435
+ } else {
436
+ $this->_addCData($nestedDescriptionXml, $descriptionsArray['description'] . PHP_EOL . $descriptionsArray['short_description']);
437
+ }
438
+
439
+ // AttributeColor *R
440
+ if (!empty($attributes['color'])) {
441
+ $descriptionXml->addChild('AttributeColor', $this->_getAttributesText($language, 'color', $attributes['color'], $store->getId()));
442
+ }
443
+
444
+ // AttributeSize *R
445
+ if (!empty($attributes['shoe_size'])) {
446
+ $descriptionXml->addChild('AttributeSize', $this->_getAttributesText($language, 'shoe_size', $attributes['shoe_size'], $store->getId()));
447
+ } elseif (!empty($attributes['size'])) {
448
+ $descriptionXml->addChild('AttributeSize', $this->_getAttributesText($language, 'size', $attributes['size'], $store->getId()));
449
+ }
450
+
451
+ // optional attributes: Arrtibute1 - Attribute10 *O
452
+ if (!empty($attributes['fit'])) {
453
+ $descriptionXml->addChild('Attribute1', $this->_getAttributesText($language, 'fit', $attributes['fit'], $store->getId()));
454
+ }
455
+ if (!empty($attributes['length'])) {
456
+ $descriptionXml->addChild('Attribute2', $this->_getAttributesText($language, 'length', $attributes['length'], $store->getId()));
457
+ }
458
+ if (!empty($attributes['width'])) {
459
+ $descriptionXml->addChild('Attribute3', $this->_getAttributesText($language, 'width', $attributes['width'], $store->getId()));
460
+ }
461
+
462
+ array_push($addedLanguages, $language);
463
+ $descriptionNodeCount++;
464
  }
465
  }
466
 
467
+ // Ignore if no descriptions added
468
+ if ($descriptionNodeCount == 0) {
469
+ return false;
470
+ }
471
+
472
+ $existingProductCountries = Mage::getModel('integration/countries')->load($product->getId(), 'product_id');
473
+
474
+ if (isset($existingProductCountries)) {
475
+ $existingList = $existingProductCountries->getFruugoCountries();
476
+ }
477
+
478
  $addedCurrencies = array();
479
+ foreach ($this->stores as $store) {
480
+ if ($store->getCode() == 'fruugo') {
481
+ continue;
482
+ }
483
+
484
+ // Add price for store in language mapping.
485
+ $localeCode = Mage::getStoreConfig('general/locale/code', $store->getId());
486
+ if ($storelangsMapping == null || $storelangsMapping[$localeCode] == $store->getCode()) {
487
  $currencyCode = $store->getCurrentCurrencyCode();
488
+ $baseCurrencyCode = $this->storeBaseCurrencies[$store->getId()];
489
+
490
+ // Skip if currency has been added.
491
  if (in_array($currencyCode, $addedCurrencies)) {
492
  continue;
493
  } else {
494
  array_push($addedCurrencies, $currencyCode);
495
  }
496
+
497
  // Price Node
498
  $priceXml = $productXml->addChild('Price');
499
  // Currency *R
500
  $priceXml->addChild('Currency', $currencyCode);
501
  // Country
502
+ if (isset($existingList)) {
 
 
503
  $priceXml->addChild('Country', $existingList);
504
  }
505
 
506
  // Normal price.
507
+ $normalPriceExclTax = $this->taxHelper->getPrice($product, $product->getPrice(), false, null, null, null, $store);
508
+ $normalPriceExclTaxConv = $this->_convertCurrency($normalPriceExclTax, $baseCurrencyCode, $currencyCode);
509
+ $priceXml->addChild('NormalPriceWithoutVAT', number_format($normalPriceExclTaxConv, 2, '.', ''));
 
 
 
 
 
 
 
510
 
511
  // Discount price
 
 
512
  $discountedPriceExclTax = null;
513
+ $finalPrice = $product->getFinalPrice();
514
+ $finalPriceExclTax = $this->taxHelper->getPrice($product, $finalPrice, false, null, null, null, $store);
515
+ $rulePriceExclTax = $this->categoryRule->clearInstance()->calcProductPriceRule($product, $normalPriceExclTax);
516
 
517
  if ($rulePriceExclTax == null || $finalPriceExclTax < $rulePriceExclTax) {
518
  $discountedPriceExclTax = $finalPriceExclTax;
519
  } else {
520
  $discountedPriceExclTax = $rulePriceExclTax;
521
  }
522
+
523
+ // VATRate.
524
+ $request = $this->taxCalculation->clearInstance()->getRateRequest(null, null, null, $store);
525
+ $taxClassId = $product->getTaxClassId();
526
+ $percent = $this->taxCalculation->clearInstance()->getRate($request->setProductClassId($taxClassId));
527
+
528
+ if ($discountedPriceExclTax && $normalPriceExclTax > $discountedPriceExclTax) {
529
+ $discountedPriceExclTaxConv = $this->_convertCurrency($discountedPriceExclTax, $baseCurrencyCode, $currencyCode);
530
+ $priceXml->addChild('DiscountPriceWithoutVAT', number_format($discountedPriceExclTaxConv, 2, '.', ''));
531
+
532
+ $priceXml->addChild('VATRate', number_format($percent, 2, '.', ''));
533
  // DiscountPriceStartDate *O
534
  if ($product->getSpecialFromDate()) {
535
  $fromTime = strtotime($product->getSpecialFromDate());
536
+ $formatedFromTimeStr = date('Y-m-d', $fromTime);
537
  $priceXml->addChild('DiscountPriceStartDate', $formatedFromTimeStr);
538
  }
539
 
540
  // DiscountPriceEndDate *O
541
  if ($product->getSpecialToDate()) {
542
  $toTime = strtotime($product->getSpecialToDate());
543
+ $fromatedToTimeStr = date('Y-m-d', $toTime);
544
  $priceXml->addChild('DiscountPriceEndDate', $fromatedToTimeStr);
545
  }
546
+ } else {
547
+ // This looks strange because the VATRate is also added above but the sequence
548
+ // defined in the XSD requires it appears after DiscountPriceWithoutVAT if it is present
549
+ $priceXml->addChild('VATRate', number_format($percent, 2, '.', ''));
550
  }
551
+
552
+ // Only need one price tag for each product now.
553
+ break;
554
  }
555
  }
556
 
557
+ $productStr = $productXml->asXML();
558
+
559
+ if ($productStr) {
560
+ $productStr = str_replace('<?xml version="1.0"?>', '', trim($productStr));
561
+ }
562
+
563
+ foreach ($productXml->children as $child) {
564
+ unset($child);
565
+ }
566
+ unset($productXml);
567
+
568
+ return $productStr;
569
+ }
570
+
571
+ protected function _convertCurrency($price, $baseCurrencyCode, $currencyCode)
572
+ {
573
+ if ($this->shouldConvertCurrency && $baseCurrencyCode != $currencyCode) {
574
+ return $this->currencyConverter->currencyConvert(
575
+ $price,
576
+ $baseCurrencyCode,
577
+ $currencyCode
578
+ );
579
+ }
580
+
581
+ return $price;
582
+ }
583
+
584
+ // This caches the options label translation for selectable product attributes.
585
+ // They are cached by attribute name, language and then value.
586
+ protected function _getAttributesText($language, $attributeName, $optionId, $storeId)
587
+ {
588
+ // These are the asset keys that are cached, you should only cache attributes
589
+ // that have selectable items
590
+ if (!isset($this->attributeMap)) {
591
+ $this->attributeMap = array(
592
+ 'color' => array(),
593
+ 'shoe_size' => array(),
594
+ 'size' => array(),
595
+ 'fit' => array(),
596
+ );
597
+ }
598
+
599
+ if (!isset($this->attributeMap[$attributeName])) {
600
+ return $optionId;
601
+ }
602
+
603
+ if (!isset($this->attributeMap[$attributeName][$language])) {
604
+ $this->attributeMap[$attributeName][$language] = array();
605
+ }
606
+
607
+ if (!isset($this->attributeMap[$attributeName][$language][$optionId])) {
608
+ $this->attributeMap[$attributeName][$language][$optionId] = null;
609
+ } else {
610
+ return $this->attributeMap[$attributeName][$language][$optionId];
611
+ }
612
+
613
+ $attributeId = Mage::getResourceModel('eav/entity_attribute')->getIdByCode('catalog_product', $attributeName);
614
+ $collection = Mage::getResourceModel('eav/entity_attribute_option_collection')
615
+ ->setPositionOrder('asc')
616
+ ->setAttributeFilter($attributeId)
617
+ ->setStoreFilter($storeId)
618
+ ->load()
619
+ ->toOptionArray();
620
+
621
+ $found = false;
622
+ foreach ($collection as $option) {
623
+ if ($option['value'] == $optionId) {
624
+ $found = true;
625
+ }
626
+
627
+ $this->attributeMap[$attributeName][$language][$option['value']] = $option['label'];
628
+ }
629
+
630
+ if (!$found) {
631
+ return $optionId;
632
+ }
633
+
634
+ return $this->attributeMap[$attributeName][$language][$optionId];
635
  }
636
 
637
+ protected $tempProductObj = false;
638
+ protected function _getProductImages($product, $parentProduct)
639
  {
640
+ if (!isset($this->tempProductObj) || $this->tempProductObj === false) {
641
+ $this->tempProductObj = Mage::getModel('catalog/product');
642
+ }
643
  $images = array();
 
644
 
645
+ // This is a workaround to avoid the memory leaks that seem to occur when fully
646
+ // loading an image gallery on a product model.
647
+ $productObj = $this->tempProductObj->clearInstance()->setId($product->getId());
648
+ $attributes = $productObj->getTypeInstance(true)->getSetAttributes($productObj);
649
+ $media_gallery = $attributes['media_gallery'];
650
+ $backend = $media_gallery->getBackend();
651
+ $backend->afterLoad($productObj);
652
+
653
+ // Add base image
654
+ $baseImage = Mage::getResourceModel('catalog/product')->getAttributeRawValue($product->getId(), 'image', 0);
655
+ $productMediaConfig = Mage::getSingleton('catalog/product_media_config');
656
+ if (!empty($baseImage) && $baseImage !== 'no_selection') {
657
+ $baseImageUrl = $productMediaConfig->getMediaUrl($baseImage);
658
+ array_push($images, $baseImageUrl);
659
+ }
660
+
661
+ // Add gallery
662
+ $galleryImages = $productObj->getMediaGalleryImages();
663
+ $skuImages = array_values($galleryImages->getItems());
664
  if (!empty($skuImages)) {
665
  foreach ($skuImages as $skuImage) {
666
+ $imageUrl = $skuImage->getUrl();
667
+ if (strpos($imageUrl, 'placeholder/image') !== true) {
668
+ array_push($images, $imageUrl);
669
+ }
670
  }
671
  }
672
 
673
  if (isset($parentProduct)) {
674
+ // Add parent base image
675
+ $attributes = $parentProduct->getTypeInstance(true)->getSetAttributes($parentProduct);
676
+ $media_gallery = $attributes['media_gallery'];
677
+ $backend = $media_gallery->getBackend();
678
+ $backend->afterLoad($parentProduct);
679
+
680
+ $parentBaseImage = Mage::getResourceModel('catalog/product')->getAttributeRawValue($parentProduct->getId(), 'image', 0);
681
+ if (!empty($parentBaseImage) && $parentBaseImage !== 'no_selection') {
682
+ $baseImageUrl = $productMediaConfig->getMediaUrl($parentBaseImage);
683
+ array_push($images, $baseImageUrl);
684
+ }
685
+
686
+ // Add parent gallery
687
  $parentImages = array_values($parentProduct->getMediaGalleryImages()->getItems());
688
  if (!empty($parentImages)) {
689
  foreach ($parentImages as $parentImage) {
690
+ $imageUrl = $parentImage->getUrl();
691
+ if (strpos($imageUrl, 'placeholder/image') !== true) {
692
+ array_push($images, $imageUrl);
693
+ }
694
  }
695
  }
696
  }
697
 
698
+ $productObj->clearInstance();
699
+ unset($productObj);
700
+
701
+ // If base image is not 'disabled' there will be duplicates
702
+ $unique = array_keys(array_flip($images)); // array_unique is supposed to be slower
703
+ return $unique;
704
+ }
705
+
706
+ protected function _shouldInclude($product)
707
+ {
708
+ if ($product->getSku() === null || $product->getName() === null || $product->getPrice() === null) {
709
+ return false;
710
+ }
711
+
712
+ if ($product->getDescription() === null && $product->getShortDescription() === null) {
713
+ return false;
714
+ }
715
+
716
+ return true;
717
  }
718
 
719
+ protected function _getParentProduct($product)
720
  {
721
  $parentProduct = null;
722
 
723
  $parentIds = Mage::getResourceSingleton('catalog/product_type_configurable')
724
+ ->getParentIdsByChild($product->getId());
725
 
726
  if (isset($parentIds[0])) {
727
+ $parentProduct = Mage::getSingleton('catalog/product')->clearInstance()->setId($parentIds[0]);
728
+ return $parentProduct;
729
  }
730
 
731
+ return null;
732
  }
733
 
734
+ protected function _addCData($xml, $cdata_text)
735
  {
736
  $node = dom_import_simplexml($xml);
737
  $no = $node->ownerDocument;
738
  $node->appendChild($no->createCDATASection(htmlspecialchars($cdata_text)));
739
  return $xml;
740
  }
741
+
742
+ protected function _writeProductsXml($xmlStr)
743
+ {
744
+ file_put_contents($this->tmpProductsXmlPath, $xmlStr, FILE_APPEND | LOCK_EX);
745
+ }
746
+
747
+ protected function _writeReport($report)
748
+ {
749
+ file_put_contents($this->reportPath, json_encode($report));
750
+ }
751
+
752
+ protected function _writeLog($message, $level = Fruugo_Integration_Helper_Logger::DEBUG)
753
+ {
754
+ Fruugo_Integration_Helper_Logger::log($message, $level);
755
+ }
756
+
757
+ protected function _setupGlobalData()
758
+ {
759
+ // Cache the stores list to avoid frequent lookups
760
+ $this->stores = array();
761
+ $this->storeBaseCurrencies = array();
762
+ foreach (Mage::app()->getStores(true) as $store) {
763
+ array_push($this->stores, $store);
764
+ $this->storeBaseCurrencies[$store->getId()] = $store->getBaseCurrencyCode();
765
+ }
766
+
767
+ $this->PAGE_SIZE = Mage::getStoreConfig('integration_options/products_options/export_page_size');
768
+ $this->MAX_RESOURCES = Mage::getStoreConfig('integration_options/products_options/max_resources_load');
769
+ $this->SLEEP_TIME_SEC = Mage::getStoreConfig('integration_options/products_options/sleep_time_sec');
770
+ $this->MAX_ERRORS = Mage::getStoreConfig('integration_options/products_options/max_errors');
771
+
772
+ $this->taxHelper = Mage::helper('tax');
773
+ $this->currencyConverter = Mage::helper('directory');
774
+ $this->taxCalculation = Mage::getSingleton('tax/calculation');
775
+ $this->categoryRule = Mage::getSingleton('catalogrule/rule');
776
+ $this->tmpProductsXmlPath = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/tmp_products.xml';
777
+ $this->reportPath = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/report.json';
778
+ $this->report = array(
779
+ 'processing_time_sec' => 0,
780
+ 'start_time_utc' => 0,
781
+ 'end_time_utc' => 0,
782
+ 'total_exported' => 0,
783
+ 'total_processed' => 0,
784
+ 'max_ram_usage_mb' => 0,
785
+ 'avg_ram_usage_mb' => 0,
786
+ 'error_count' => 0,
787
+ 'errors' => array(),
788
+ 'status' => '',
789
+ 'avg_load' => 0,
790
+ 'load_above_threshold_count' => 0,
791
+ 'time_paused_sec' => 0,
792
+ 'xml_file_size_mb' => 0,
793
+ );
794
+ }
795
+
796
+ // This monitors system resources and pauses execution if the utilisation is
797
+ // above the configured threshold.
798
+ // Recommended for systems with large numbers of products
799
+ // Note: this feature is not available on windows servers.
800
+ protected $loadCheckCount = 0;
801
+ protected $loadCheckTotal = 0.0;
802
+ protected function checkServerLoad()
803
+ {
804
+ if (stristr(PHP_OS, 'win')) {
805
+ return;
806
+ }
807
+ $systemLoad = sys_getloadavg();
808
+ $this->loadCheckTotal += $systemLoad[0];
809
+ $this->loadCheckCount++;
810
+
811
+ $this->report['avg_load'] = $this->loadCheckTotal / $this->loadCheckCount;
812
+
813
+ if ($this->devMode) {
814
+ $this->_writeLog('Server load is ' . $systemLoad[0], self::$DEBUG);
815
+ }
816
+
817
+ if (!$this->MONITOR_RESOURCES) {
818
+ return;
819
+ }
820
+
821
+ $systemLoad = sys_getloadavg();
822
+ if ($this->devMode && $systemLoad[0] > $this->MAX_RESOURCES) {
823
+ $this->_writeLog(
824
+ 'High server load detected. Usage of ' . $systemLoad[0] .
825
+ ' is greater than configured maximum of ' . $this->MAX_RESOURCES .
826
+ '. The product export job will now pause for ' . $this->SLEEP_TIME_SEC . ' seconds.',
827
+ self::$DEBUG
828
+ );
829
+ $this->report['load_above_threshold_count'] += 1;
830
+
831
+ sleep($this->SLEEP_TIME_SEC);
832
+
833
+ $this->report['time_paused_sec'] += $this->SLEEP_TIME_SEC;
834
+ $this->_writeLog('Fruugo export resumed after waiting ' . $this->SLEEP_TIME_SEC . ' seconds.', self::$DEBUG);
835
+ }
836
+ }
837
+
838
+
839
  }
app/code/community/Fruugo/Integration/Helper/ProductsFeedGeneratorProfiler.php ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * NOTICE OF LICENSE
4
+ *
5
+ * Magento extension which extracts a product feed from Magento, imports the feed into Fruugo and uses the Fruugo Order API to export all Fruugo orders into Magento.
6
+ *
7
+ * Copyright (C) 2015 Fruugo.com Ltd
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ * See the GNU General Public License for more details.bitbu
18
+ *
19
+ * You should have received a copy of the GNU General Public License along with this program.
20
+ * If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+
23
+ require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/Logger.php';
24
+
25
+ /*
26
+ * This class is for profiling and testing the performance of the feed generation. Make sure you run
27
+ * this after you make changes to check for memory leaks and performance issues.
28
+ * It can be run by hitting this URL: http://127.0.0.1:8080/index.php/fruugo-integration/products/profiler
29
+ * NOTE: you must be in dev mode for this to work.
30
+ */
31
+ class Fruugo_Integration_ProductsFeedGeneratorProfiler extends Fruugo_Integration_ProductsFeedGenerator
32
+ {
33
+ protected $currentTimer = array();
34
+
35
+ protected $openTimers = array();
36
+
37
+ protected $executionTree = array();
38
+
39
+ public function generateProdcutsFeed($cached = false) {
40
+ $devMode = Mage::getStoreConfig('integration_options/orders_options/dev_mode');
41
+ if($devMode != '1') {
42
+ throw new Exception('You must be in dev mode to use this feature');
43
+ }
44
+ Fruugo_Integration_Helper_Logger::$LOG_LEVEL = self::$DEBUG;
45
+
46
+ $this->_writeLog('--------------------------------------------', self::$DEBUG);
47
+ //$this->_testReport();
48
+ //return false;
49
+ $this->_writeLog('Profiling the product feed generator...', self::$DEBUG);
50
+ $this->_startTimer('generateProdcutsFeed');
51
+ $val = parent::generateProdcutsFeed($cached);
52
+ $this->_stopTimer('generateProdcutsFeed');
53
+ $this->_writeTimerLog();
54
+ return $val;
55
+ }
56
+
57
+ protected function _fillProductXml($product, $storelangsMapping) {
58
+ $this->_startTimer('_fillProductXml');
59
+ $val = parent::_fillProductXml($product, $storelangsMapping);
60
+ $this->_stopTimer('_fillProductXml');
61
+ return $val;
62
+ }
63
+
64
+ protected function _getAttributesText($language, $attributeName, $optionId, $storeId) {
65
+ $this->_startTimer('_getAttributesText');
66
+ $val = parent::_getAttributesText($language, $attributeName, $optionId, $storeId);
67
+ $this->_stopTimer('_getAttributesText');
68
+ return $val;
69
+ }
70
+
71
+ protected function _convertCurrency($price, $baseCurrencyCode, $currencyCode) {
72
+ $this->_startTimer('_convertCurrency');
73
+ $val = parent::_convertCurrency($price, $baseCurrencyCode, $currencyCode);
74
+ $this->_stopTimer('_convertCurrency');
75
+ return $val;
76
+ }
77
+
78
+ protected function _getProductImages($product, $parentProduct) {
79
+ $this->_startTimer('_getProductImages');
80
+ $val = parent::_getProductImages($product, $parentProduct);
81
+ $this->_stopTimer('_getProductImages');
82
+ return $val;
83
+ }
84
+
85
+ protected function _getParentProduct($product)
86
+ {
87
+ $this->_startTimer('_getParentProduct');
88
+ $val = parent::_getParentProduct($product);
89
+ $this->_stopTimer('_getParentProduct');
90
+ return $val;
91
+ }
92
+
93
+ protected function _startTimer($id, $message=null)
94
+ {
95
+ if (!array_key_exists($id, $this->currentTimer)) {
96
+ $this->currentTimer[$id] = array(
97
+ 'start_time' => 0,
98
+ 'stop_time' => 0,
99
+ 'total' => 0,
100
+ 'length' => 0,
101
+ 'count' => 0,
102
+ 'callers' => array(),
103
+ 'start_memory' => 0,
104
+ 'total_memory_leaked' => 0,
105
+ );
106
+ }
107
+
108
+ $this->currentTimer[$id]['start_memory'] = memory_get_usage(false);
109
+
110
+ if(count($this->openTimers) > 0) {
111
+ $caller = $this->openTimers[0];
112
+ if(!array_key_exists($caller, $this->currentTimer[$id]['callers'])) {
113
+ $this->currentTimer[$id]['callers'][$caller] = 1;
114
+ }
115
+ $this->currentTimer[$id]['callers'][$caller]++;
116
+
117
+ $this->_addToTree($id, $caller);
118
+ }
119
+ else {
120
+ $this->_addToTree($id, null);
121
+ }
122
+
123
+ array_unshift($this->openTimers, $id);
124
+
125
+ $this->currentTimer[$id]['start_time'] = microtime(true);
126
+ if($message) {
127
+ $this->_writeLog($message, self::$DEBUG);
128
+ }
129
+ }
130
+
131
+ protected function _stopTimer($id, $message=null, $threshhold = 1000, $threshholdMessage = '')
132
+ {
133
+ if (!array_key_exists($id, $this->currentTimer)) {
134
+ return;
135
+ }
136
+
137
+ $start_memory = $this->currentTimer[$id]['start_memory'];
138
+ $this->currentTimer[$id]['total_memory_leaked'] += memory_get_usage(false) - $start_memory;
139
+
140
+ $this->currentTimer[$id]['stop_time'] = microtime(true);
141
+ $stopTimer = ($this->currentTimer[$id]['stop_time'] - $this->currentTimer[$id]['start_time']);
142
+ $this->currentTimer[$id]['length'] = $stopTimer;
143
+ $this->currentTimer[$id]['total'] += $this->currentTimer[$id]['length'];
144
+ $this->currentTimer[$id]['count'] += 1;
145
+
146
+ if (round($stopTimer) >= $threshhold) {
147
+ $this->_writeLog('=================================================', self::$DEBUG);
148
+ $this->_writeLog($id . ' exceeded threshold of ' . $threshhold . ' with the time of ' . $stopTimer . ' threshholdMessage ' . $threshholdMessage, self::$DEBUG);
149
+ }
150
+
151
+ if($message) {
152
+ $this->_writeLog($message . ' in ' . $stopTimer . ' seconds.' . ' Running total: ' . $this->currentTimer[$id]['total'], self::$DEBUG);
153
+ }
154
+
155
+ array_shift($this->openTimers);
156
+ }
157
+
158
+ protected function _writeTimerLog()
159
+ {
160
+ $this->_writeLog('Profiling Statistics', self::$DEBUG);
161
+ foreach ($this->currentTimer as $key => $value) {
162
+ $this->_writeLog("\n", self::$DEBUG);
163
+ $this->_writeLog('--------------------------------------------------', self::$DEBUG);
164
+ $this->_writeLog('Stats for ' . $key);
165
+ $this->_writeLog('Total executing time:' . round($value['total'], 7), self::$DEBUG);
166
+ $this->_writeLog('Average execution time: ' . round($value['total']/$value['count'], 7), self::$DEBUG);
167
+ $this->_writeLog('Times executed: ' . $value['count'], self::$DEBUG);
168
+ $this->_writeLog('Memory leaked: ' . (($value['total_memory_leaked'] / 1024) / 1024) . ' MB', self::$DEBUG);
169
+ $this->_writeLog('Called by: ', self::$DEBUG);
170
+ foreach ($value['callers'] as $caller => $callerCount) {
171
+ $this->_writeLog("\t" . $caller . ' x ' . $callerCount, self::$DEBUG);
172
+ }
173
+ }
174
+
175
+ $this->_writeLog("\n", self::$DEBUG);
176
+ $this->_writeLog('--------------------------------------------', self::$DEBUG);
177
+ $this->_generateTree();
178
+ }
179
+
180
+ protected function _generateTree() {
181
+ foreach($this->executionTree[0] as $parentkey => $val) {
182
+ $this->_writeLog($parentkey, self::$DEBUG);
183
+ $this->_writeTree(1, $parentkey, $parentkey, 0);
184
+ }
185
+ }
186
+
187
+ // Note: the tree view is not a finished feature, and there may be display issues
188
+ protected function _writeTree($x, $parent, $caller, $indentLen) {
189
+
190
+ if($x >= count($this->executionTree)) {
191
+ return;
192
+ }
193
+ if(!array_key_exists($caller, $this->executionTree[$x])) {
194
+ return;
195
+ }
196
+ foreach($this->executionTree[$x][$caller]['children'] as $key => $val) {
197
+ $len = strlen($caller) + $indentLen;
198
+ $indent = str_pad("", $len, " ");
199
+ $line = str_pad("", strlen($caller)/3, "-");
200
+ $arrow = '|' . $line . '(x ' . $val['call_count'] . ')' . $line . '> ';
201
+ $this->_writeLog( $indent . $arrow . $key, self::$DEBUG);
202
+ $this->_writeTree($x + 1, $caller, $key, strlen($caller . $arrow));
203
+ }
204
+ }
205
+
206
+ protected function _addToTree($id, $caller=null) {
207
+ $depth = count($this->openTimers);
208
+ if(!isset($this->executionTree[$depth])) {
209
+ $this->executionTree[$depth] = array();
210
+ }
211
+ $data = array(
212
+ 'name' => $id,
213
+ 'depth' => $depth,
214
+ 'children' => array(),
215
+ 'parents' => array()
216
+ );
217
+
218
+ $childData = array(
219
+ 'name' => $id,
220
+ 'depth' => $depth,
221
+ 'children' => array(),
222
+ 'parents' => array(),
223
+ 'call_count' => 0,
224
+ );
225
+
226
+ if($caller === null) {
227
+ $this->executionTree[$depth] = array();
228
+ $this->executionTree[$depth][$id] = $data;
229
+ return;
230
+ }
231
+ else{
232
+ if(!array_key_exists($caller, $this->executionTree[$depth])) {
233
+ $this->executionTree[$depth][$caller] = $data;
234
+ }
235
+ if(!array_key_exists($id, $this->executionTree[$depth][$caller]['children'])) {
236
+ $this->executionTree[$depth][$caller]['children'][$id] = $childData;
237
+ }
238
+ $this->executionTree[$depth][$caller]['children'][$id]['call_count'] += 1;
239
+ }
240
+ }
241
+
242
+ // Some fixtures data for testing the report generation
243
+ protected function _testReport() {
244
+
245
+ $this->executionTree = Array (
246
+ 0 => Array
247
+ (
248
+ 'generateProdcutsFeed' => Array
249
+ (
250
+ 'name' => 'generateProdcutsFeed',
251
+ 'depth' => 0,
252
+ 'children' => Array(),
253
+ 'parents' => Array(),
254
+ )
255
+ ),
256
+
257
+ 1 => Array
258
+ (
259
+ 'generateProdcutsFeed' => Array
260
+ (
261
+ 'name' => '_fillProductXml',
262
+ 'depth' => 1,
263
+ 'children' => Array
264
+ (
265
+ '_fillProductXml' => Array
266
+ (
267
+ 'name' => '_fillProductXml',
268
+ 'depth' => 1,
269
+ 'children' => Array(),
270
+ 'parents' => Array(),
271
+
272
+ 'call_count' => 477,
273
+ )
274
+
275
+ ),
276
+
277
+ 'parents' => Array(),
278
+ )
279
+
280
+ ),
281
+
282
+ 2 => Array
283
+ (
284
+ '_fillProductXml' => Array
285
+ (
286
+ 'name' => '_getParentProduct',
287
+ 'depth' => 2,
288
+ 'children' => Array
289
+ (
290
+ '_getParentProduct' => Array
291
+ (
292
+ 'name' => '_getParentProduct',
293
+ 'depth' => 2,
294
+ 'children' => Array(),
295
+ 'parents' => Array(),
296
+ 'call_count' => 477,
297
+ ),
298
+
299
+ '_getProductImages' => Array
300
+ (
301
+ 'name' => '_getProductImages',
302
+ 'depth' => 2,
303
+ 'children' => Array(),
304
+ 'parents' => Array(),
305
+ 'call_count' => 477,
306
+ ),
307
+
308
+ '_getAttributesText' => Array
309
+ (
310
+ 'name' => '_getAttributesText',
311
+ 'depth' => 2,
312
+ 'children' => Array(),
313
+ 'parents' => Array(),
314
+ 'call_count' => 3762,
315
+ ),
316
+
317
+ '_convertCurrency' => Array
318
+ (
319
+ 'name' => '_convertCurrency',
320
+ 'depth' => 2,
321
+ 'children' => Array(),
322
+ 'parents' => Array(),
323
+ 'call_count' => 1455,
324
+ ),
325
+
326
+ ),
327
+
328
+ 'parents' => Array
329
+ (
330
+ ),
331
+
332
+ )
333
+
334
+ )
335
+
336
+ );
337
+
338
+ $this->_generateTree();
339
+ }
340
+ }
app/code/community/Fruugo/Integration/Model/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Model/Adminhtml/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Model/Adminhtml/System/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Backend/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/ExportPageSize.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * NOTICE OF LICENSE
4
+ *
5
+ * Magento extension which extracts a product feed from Magento, imports the feed into Fruugo and uses the Fruugo Order API to export all Fruugo orders into Magento.
6
+ *
7
+ * Copyright (C) 2015 Fruugo.com Ltd
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ * See the GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License along with this program.
20
+ * If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+
23
+ /**
24
+ * Used in creating options for Hour config value selection
25
+ *
26
+ */
27
+ class Fruugo_Integration_Model_Adminhtml_System_Config_Source_ExportPageSize
28
+ {
29
+ /**
30
+ * Options getter
31
+ *
32
+ * @return array
33
+ */
34
+ public function toOptionArray()
35
+ {
36
+ $exportPageSize = array();
37
+
38
+ array_push($exportPageSize, array('value' => 20, 'label' => 20));
39
+ array_push($exportPageSize, array('value' => 30, 'label' => 30));
40
+ array_push($exportPageSize, array('value' => 50, 'label' => 50));
41
+ array_push($exportPageSize, array('value' => 70, 'label' => 70));
42
+ array_push($exportPageSize, array('value' => 100, 'label' => 100));
43
+ array_push($exportPageSize, array('value' => 150, 'label' => 150));
44
+ array_push($exportPageSize, array('value' => 200, 'label' => 200));
45
+ array_push($exportPageSize, array('value' => 300, 'label' => 300));
46
+ array_push($exportPageSize, array('value' => 500, 'label' => 500));
47
+ array_push($exportPageSize, array('value' => 1000, 'label' => 1000));
48
+ array_push($exportPageSize, array('value' => 3000, 'label' => 3000));
49
+
50
+ return $exportPageSize;
51
+ }
52
+ }
app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/MaxErrors.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * NOTICE OF LICENSE
4
+ *
5
+ * Magento extension which extracts a product feed from Magento, imports the feed into Fruugo and uses the Fruugo Order API to export all Fruugo orders into Magento.
6
+ *
7
+ * Copyright (C) 2015 Fruugo.com Ltd
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ * See the GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License along with this program.
20
+ * If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+
23
+ /**
24
+ * Used in creating options for Hour config value selection
25
+ *
26
+ */
27
+ class Fruugo_Integration_Model_Adminhtml_System_Config_Source_MaxErrors
28
+ {
29
+ /**
30
+ * Options getter
31
+ *
32
+ * @return array
33
+ */
34
+ public function toOptionArray()
35
+ {
36
+ $maxErrors = array();
37
+
38
+ array_push($maxErrors, array('value' => -1, 'label' => -1));
39
+ array_push($maxErrors, array('value' => 5, 'label' => 5));
40
+ array_push($maxErrors, array('value' => 10, 'label' => 10));
41
+ array_push($maxErrors, array('value' => 15, 'label' => 15));
42
+ array_push($maxErrors, array('value' => 20, 'label' => 20));
43
+ array_push($maxErrors, array('value' => 25, 'label' => 25));
44
+ array_push($maxErrors, array('value' => 30, 'label' => 30));
45
+ array_push($maxErrors, array('value' => 40, 'label' => 40));
46
+ array_push($maxErrors, array('value' => 50, 'label' => 50));
47
+ array_push($maxErrors, array('value' => 70, 'label' => 70));
48
+ array_push($maxErrors, array('value' => 100, 'label' => 100));
49
+
50
+ return $maxErrors;
51
+ }
52
+ }
app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/MaxResourcesLoad.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * NOTICE OF LICENSE
4
+ *
5
+ * Magento extension which extracts a product feed from Magento, imports the feed into Fruugo and uses the Fruugo Order API to export all Fruugo orders into Magento.
6
+ *
7
+ * Copyright (C) 2015 Fruugo.com Ltd
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ * See the GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License along with this program.
20
+ * If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+
23
+ /**
24
+ * Used in creating options for Hour config value selection
25
+ *
26
+ */
27
+ class Fruugo_Integration_Model_Adminhtml_System_Config_Source_MaxResourcesLoad
28
+ {
29
+ /**
30
+ * Options getter
31
+ *
32
+ * @return array
33
+ */
34
+ public function toOptionArray()
35
+ {
36
+ $maxResources = array();
37
+
38
+ array_push($maxResources, array('value' => 0.25, 'label' => '0.25'));
39
+ array_push($maxResources, array('value' => 0.5, 'label' => '0.5'));
40
+ array_push($maxResources, array('value' => 1.0, 'label' => '1.0'));
41
+ array_push($maxResources, array('value' => 1.5, 'label' => '1.5'));
42
+ array_push($maxResources, array('value' => 2.0, 'label' => '2.0'));
43
+
44
+ return $maxResources;
45
+ }
46
+ }
app/code/community/Fruugo/Integration/Model/Adminhtml/System/Config/Source/SleepTimeSec.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * NOTICE OF LICENSE
4
+ *
5
+ * Magento extension which extracts a product feed from Magento, imports the feed into Fruugo and uses the Fruugo Order API to export all Fruugo orders into Magento.
6
+ *
7
+ * Copyright (C) 2015 Fruugo.com Ltd
8
+ *
9
+ * This program is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
+ * See the GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License along with this program.
20
+ * If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+
23
+ /**
24
+ * Used in creating options for Hour config value selection
25
+ *
26
+ */
27
+ class Fruugo_Integration_Model_Adminhtml_System_Config_Source_SleepTimeSec
28
+ {
29
+ /**
30
+ * Options getter
31
+ *
32
+ * @return array
33
+ */
34
+ public function toOptionArray()
35
+ {
36
+ $sleepTimeSecs = array();
37
+
38
+ array_push($sleepTimeSecs, array('value' => 5, 'label' => 5));
39
+ array_push($sleepTimeSecs, array('value' => 10, 'label' => 10));
40
+ array_push($sleepTimeSecs, array('value' => 15, 'label' => 15));
41
+ array_push($sleepTimeSecs, array('value' => 20, 'label' => 20));
42
+ array_push($sleepTimeSecs, array('value' => 25, 'label' => 25));
43
+ array_push($sleepTimeSecs, array('value' => 30, 'label' => 30));
44
+ array_push($sleepTimeSecs, array('value' => 40, 'label' => 40));
45
+ array_push($sleepTimeSecs, array('value' => 50, 'label' => 50));
46
+ array_push($sleepTimeSecs, array('value' => 60, 'label' => 60));
47
+ array_push($sleepTimeSecs, array('value' => 90, 'label' => 90));
48
+ array_push($sleepTimeSecs, array('value' => 120, 'label' => 120));
49
+
50
+ return $sleepTimeSecs;
51
+ }
52
+ }
app/code/community/Fruugo/Integration/Model/CronJobObserver.php CHANGED
@@ -30,14 +30,10 @@ class Fruugo_Integration_Model_CronJobObserver
30
  {
31
  // Export products xml
32
  $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGenerator();
33
- $xml = $productsFeedGenerator->generateProdcutsFeed(false)->asXML();
34
 
35
  try {
36
  Fruugo_Integration_Helper_Logger::log("Writing exported products to file.");
37
- $outputDir = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/products.xml';
38
- $myfile = fopen($outputDir, "w");
39
- fwrite($myfile, $xml);
40
- fclose($myfile);
41
  Fruugo_Integration_Helper_Logger::log("Writing products data feed finished.");
42
  } catch (Exception $e) {
43
  Mage::logException($e);
30
  {
31
  // Export products xml
32
  $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGenerator();
 
33
 
34
  try {
35
  Fruugo_Integration_Helper_Logger::log("Writing exported products to file.");
36
+ $productsFeedGenerator->generateProdcutsFeed(false);
 
 
 
37
  Fruugo_Integration_Helper_Logger::log("Writing products data feed finished.");
38
  } catch (Exception $e) {
39
  Mage::logException($e);
app/code/community/Fruugo/Integration/controllers/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/controllers/.gitignore CHANGED
@@ -1 +1,3 @@
1
- *.xml
 
 
1
+ *.xml
2
+ report.json
3
+ products.lock
app/code/community/Fruugo/Integration/controllers/ProductsController.php CHANGED
@@ -21,6 +21,7 @@
21
  */
22
 
23
  require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/ProductsFeedGenerator.php';
 
24
  require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/FruugoCountriesSeeder.php';
25
 
26
  class Fruugo_Integration_ProductsController extends Mage_Core_Controller_Front_Action
@@ -28,50 +29,43 @@ class Fruugo_Integration_ProductsController extends Mage_Core_Controller_Front_A
28
  public function indexAction()
29
  {
30
  $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGenerator();
31
- $productsXml = $productsFeedGenerator->generateProdcutsFeed(false);
32
- $this->writeProductsFile($productsXml->asXML());
33
- $this->streamXmlFile($productsXml->asXML());
34
  }
35
 
36
- public function dataFeedAction()
37
  {
38
- $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGenerator();
39
- $cachedFile = $productsFeedGenerator->generateProdcutsFeed(true);
40
- if (file_exists($cachedFile)) {
41
- $productsXmlStr = file_get_contents($cachedFile);
42
- $this->streamXmlFile($productsXmlStr);
43
- } else {
44
- $this->indexAction();
45
- }
46
  }
47
 
48
- private function writeProductsFile($productsXmlStr)
49
  {
50
- $outputDir = Mage::getModuleDir('', 'Fruugo_Integration') . '/controllers/products.xml';
51
- $productsFeedFile = fopen($outputDir, "w");
52
- fwrite($productsFeedFile, $productsXmlStr);
53
- fclose($productsFeedFile);
54
  }
55
 
56
- private function streamXmlFile($productsXmlStr)
57
  {
58
- if (function_exists('mb_strlen')) {
59
- $filesize = mb_strlen($productsXmlStr, '8bit');
60
- } else {
61
- $filesize = strlen($productsXmlStr);
62
  }
63
 
64
- header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
65
- header('Content-Description: File Transfer');
66
- header('Content-type: text/xml');
67
- header('Expires: 0');
68
- header('Pragma: public');
69
- header('Content-Disposition: attachment; filename=products.xml');
70
- header('Content-Length: ' . $filesize);
71
 
72
- $file = fopen('php://output', 'w');
73
- fwrite($file, $productsXmlStr);
74
- fclose($file);
75
  exit;
76
  }
77
 
21
  */
22
 
23
  require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/ProductsFeedGenerator.php';
24
+ require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/ProductsFeedGeneratorProfiler.php';
25
  require_once Mage::getModuleDir('', 'Fruugo_Integration') . '/Helper/FruugoCountriesSeeder.php';
26
 
27
  class Fruugo_Integration_ProductsController extends Mage_Core_Controller_Front_Action
29
  public function indexAction()
30
  {
31
  $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGenerator();
32
+ $cachedFile = $productsFeedGenerator->generateProdcutsFeed(false);
33
+ $this->streamXmlFile($cachedFile);
 
34
  }
35
 
36
+ public function profilerAction()
37
  {
38
+ $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGeneratorProfiler();
39
+ $cachedFile = $productsFeedGenerator->generateProdcutsFeed(false);
40
+ $this->streamXmlFile($cachedFile);
 
 
 
 
 
41
  }
42
 
43
+ public function dataFeedAction()
44
  {
45
+ $productsFeedGenerator = new Fruugo_Integration_ProductsFeedGenerator();
46
+ $cachedFile = $productsFeedGenerator->generateProdcutsFeed(true);
47
+ $this->streamXmlFile($cachedFile);
 
48
  }
49
 
50
+ private function streamXmlFile($cachedFile)
51
  {
52
+ if (!is_file($cachedFile) || !is_readable($cachedFile)) {
53
+ // return 404
54
+ $this->norouteAction();
55
+ return;
56
  }
57
 
58
+ $this->getResponse()
59
+ ->setHttpResponseCode(200)
60
+ ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true)
61
+ ->setHeader('Pragma', 'public', true)
62
+ ->setHeader('Content-type', 'application/force-download')
63
+ ->setHeader('Content-Length', filesize($cachedFile))
64
+ ->setHeader('Content-Disposition', 'attachment' . '; filename=' . basename($cachedFile));
65
 
66
+ $this->getResponse()->clearBody();
67
+ $this->getResponse()->sendHeaders();
68
+ readfile($cachedFile);
69
  exit;
70
  }
71
 
app/code/community/Fruugo/Integration/data/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/catalog/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/catalog/product/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/sales/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/sales/order/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/design/adminhtml/default/default/template/integration/sales/order/view/.DS_Store DELETED
Binary file
app/code/community/Fruugo/Integration/etc/config.xml CHANGED
@@ -24,7 +24,7 @@
24
  <config>
25
  <modules>
26
  <Fruugo_Integration>
27
- <version>1.0.5</version> <!-- Version number of your module -->
28
  </Fruugo_Integration>
29
  </modules>
30
  <global>
@@ -145,6 +145,11 @@
145
  <products_options>
146
  <export_frequency>12</export_frequency>
147
  <descrption_type>long</descrption_type>
 
 
 
 
 
148
  </products_options>
149
  <orders_options>
150
  <fetch_frequency>4</fetch_frequency>
24
  <config>
25
  <modules>
26
  <Fruugo_Integration>
27
+ <version>1.0.6</version> <!-- Version number of your module -->
28
  </Fruugo_Integration>
29
  </modules>
30
  <global>
145
  <products_options>
146
  <export_frequency>12</export_frequency>
147
  <descrption_type>long</descrption_type>
148
+ <export_page_size>100</export_page_size>
149
+ <max_resources_load>0.5</max_resources_load>
150
+ <sleep_time_sec>20</sleep_time_sec>
151
+ <max_errors>30</max_errors>
152
+ <track_last_id>1</track_last_id>
153
  </products_options>
154
  <orders_options>
155
  <fetch_frequency>4</fetch_frequency>
app/code/community/Fruugo/Integration/etc/system.xml CHANGED
@@ -119,6 +119,46 @@
119
  <show_in_store>1</show_in_store>
120
  <comment>Choose a default store for a language to export product description.</comment>
121
  </language_store>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </fields>
123
  </products_options>
124
  <orders_options translate="label" module="integration">
119
  <show_in_store>1</show_in_store>
120
  <comment>Choose a default store for a language to export product description.</comment>
121
  </language_store>
122
+ <export_page_size translate="label">
123
+ <label>Export Page Size</label>
124
+ <frontend_type>select</frontend_type>
125
+ <source_model>Fruugo_Integration_Model_Adminhtml_System_Config_Source_ExportPageSize</source_model>
126
+ <sort_order>60</sort_order>
127
+ <show_in_default>1</show_in_default>
128
+ <show_in_website>0</show_in_website>
129
+ <show_in_store>0</show_in_store>
130
+ <comment>The number of products to process and write to xml per batch.</comment>
131
+ </export_page_size>
132
+ <max_resources_load translate="label">
133
+ <label>Max Resources</label>
134
+ <frontend_type>select</frontend_type>
135
+ <source_model>Fruugo_Integration_Model_Adminhtml_System_Config_Source_MaxResourcesLoad</source_model>
136
+ <sort_order>70</sort_order>
137
+ <show_in_default>1</show_in_default>
138
+ <show_in_website>0</show_in_website>
139
+ <show_in_store>0</show_in_store>
140
+ <comment>Maximum average system load (the number of processes in the system run queue, based on /proc/loadavg) allowed over the last minute, not available on Windows Servers because php sys_getloadavg() function is not implemented on Windows platforms.</comment>
141
+ </max_resources_load>
142
+ <sleep_time_sec translate="label">
143
+ <label>Sleep time seconds</label>
144
+ <frontend_type>select</frontend_type>
145
+ <source_model>Fruugo_Integration_Model_Adminhtml_System_Config_Source_SleepTimeSec</source_model>
146
+ <sort_order>80</sort_order>
147
+ <show_in_default>1</show_in_default>
148
+ <show_in_website>0</show_in_website>
149
+ <show_in_store>0</show_in_store>
150
+ <comment>Time to sleep for if over load limit during products export</comment>
151
+ </sleep_time_sec>
152
+ <max_errors translate="label">
153
+ <label>Max Errors Allowed</label>
154
+ <frontend_type>select</frontend_type>
155
+ <source_model>Fruugo_Integration_Model_Adminhtml_System_Config_Source_MaxErrors</source_model>
156
+ <sort_order>90</sort_order>
157
+ <show_in_default>1</show_in_default>
158
+ <show_in_website>0</show_in_website>
159
+ <show_in_store>0</show_in_store>
160
+ <comment>The number of errors after which the exporting products process will abort, set to -1 to disable</comment>
161
+ </max_errors>
162
  </fields>
163
  </products_options>
164
  <orders_options translate="label" module="integration">
app/design/adminhtml/default/default/template/integration/.DS_Store DELETED
Binary file
app/design/adminhtml/default/default/template/integration/catalog/.DS_Store DELETED
Binary file
app/design/adminhtml/default/default/template/integration/catalog/product/.DS_Store DELETED
Binary file
app/design/adminhtml/default/default/template/integration/sales/.DS_Store DELETED
Binary file
app/design/adminhtml/default/default/template/integration/sales/order/.DS_Store DELETED
Binary file
app/design/adminhtml/default/default/template/integration/sales/order/view/.DS_Store DELETED
Binary file
package.xml CHANGED
@@ -1,7 +1,7 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Fruugo_Integration</name>
4
- <version>1.0.5</version>
5
  <stability>stable</stability>
6
  <license uri="http://www.gnu.org/licenses/">GNU</license>
7
  <channel>community</channel>
@@ -16,19 +16,13 @@ This plugin mainly performs two tasks:&#xD;
16
  - Read from Fruugo Rrders API periodically on a specified frequency, and notifies Magento order events to Fruugo Orders API.</description>
17
  <notes>This update includes:&#xD;
18
  &#xD;
19
- - Up to 5 images for a product in the products feed&#xD;
20
  &#xD;
21
- - Added a configuration section for multiple stores with the same language to choose which store to use to export product description for that language in order to avoid duplicate product description of the same language&#xD;
22
- &#xD;
23
- - Added options to choose product description and short description merge order&#xD;
24
- &#xD;
25
- - Add product discount price when it is set&#xD;
26
- &#xD;
27
- - Custom Fruugo shipping method for Fruugo orders</notes>
28
  <authors><author><name>inoutput.io</name><user>inoutput</user><email>support@inoutput.io</email></author><author><name>fruugo.com</name><user>Fruugo</user><email>support@fruugo.com</email></author></authors>
29
- <date>2015-11-11</date>
30
- <time>10:01:57</time>
31
- <contents><target name="magecommunity"><dir name="Fruugo"><dir name="Integration"><dir name="Block"><dir name="Catalog"><dir name="Product"><file name="Tab.php" hash="412d5a38c07f78fd56e3509809038008"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><file name=".DS_Store" hash="c0c814fd19a01064c6737a7a33f53c0a"/></dir><file name="Languagestoremapping.php" hash="7852846c2428b4fc15434a1e78a37de7"/><file name="Refreshcountriesbutton.php" hash="5a7e3af708dd470725981ec187b2c07d"/><dir name="Sales"><dir name="Order"><dir name="View"><file name="Tabs.php" hash="d98faeeede3c06d5f28f8f9731438314"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><file name=".DS_Store" hash="55b80dc008f359a78148ef0fc1722049"/></dir><file name=".DS_Store" hash="bbae86b9af10fdcbf50c8644074ed58c"/></dir><file name=".DS_Store" hash="26d674cc95490ff01df42facb067195e"/></dir><dir name="Helper"><file name="ConfigLoader.php" hash="f930d687b44fe0a3a9e70383e0500a7c"/><file name="Data.php" hash="866fe8e1ea50749218d6efcab8454f28"/><file name="Defines.php" hash="a97616fe105332db06292c68eabecdf1"/><file name="FruugoCountriesSeeder.php" hash="68949469a1b1ce6a605fccd132e9e3c0"/><file name="Logger.php" hash="ac482415c8c1bc6643c81779f3d580c7"/><file name="OrdersFeedProcessor.php" hash="c0969f7217b6e4eb1c875e00afc21227"/><file name="ProductsFeedGenerator.php" hash="7214dd26f546cbe722ed90060e81f3e2"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><dir name="Model"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Backend"><file name="OrderCron.php" hash="8187697b999171b8940be7e5306a3bc7"/><file name="ProductCron.php" hash="a8bdd62ad14cdb85d9ef0ae2b85cecad"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><dir name="Source"><file name="Hour.php" hash="fab1337425532af32cf8642b8b244930"/><file name="ProductDescriptionType.php" hash="4a2810baeabe1a74b1b06b92cc2e4481"/></dir><file name=".DS_Store" hash="d20a4947c565c165a828059468e40615"/></dir><file name=".DS_Store" hash="9edeb8e2463be37d84dbbb69544fcf33"/></dir><file name=".DS_Store" hash="2429e61ffca453f96749fe4e336e1aa6"/></dir><file name="Countries.php" hash="a23378525c616fd87c5589933aec48c6"/><file name="CronJobObserver.php" hash="e8ddc80c37a3ef2b4ce025879f04e7c0"/><file name="Observer.php" hash="1e16ebea05d59b472cc9f279965cd17e"/><file name="Payment.php" hash="4e7d2d72e7662ea71c178d85c9b9bd6c"/><dir name="Resource"><dir name="Countries"><file name="Collection.php" hash="618a95f1535d4c63724a2f7eaebf6c2b"/></dir><file name="Countries.php" hash="dc3da7aec3c3472a4ccd73e2b2f255f0"/><dir name="Shipment"><file name="Collection.php" hash="66d038f890e36403f9ffb2e283e88daf"/></dir><file name="Shipment.php" hash="d8c13444ba5089cf606241f2c7c9a87d"/></dir><file name="Shipment.php" hash="23b1c0b4bc4e29cb137c9b3819723a63"/><file name=".DS_Store" hash="79784d7aed8dd8e37e943705186d586f"/></dir><dir name="controllers"><file name="OrdersController.php" hash="a2d9d2f4c916c376f146cfccecb4110d"/><file name="PackinglistController.php" hash="d76ac40e81f51635a073fb20492883f8"/><file name="ProductsController.php" hash="7b120e3ef0152ddc40821ef082786db4"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/><file name=".gitignore" hash="acd4f929fc2eebc0a25a1b7a06b4f56f"/></dir><dir name="data"><dir name="fruugo_attributes_setup"><file name="data-install-0.1.1.php" hash="73a6210fdb6c5a2edfa0222a6a239e9a"/></dir><file name=".DS_Store" hash="cd195b6eaf08907f8b960d088eb658ec"/></dir><dir name="design"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="integration.xml" hash="3f173b27f9f483c00b33b713c0bad737"/></dir><dir name="template"><dir name="integration"><dir name="catalog"><dir name="product"><file name="fruugo-allowed-countries.phtml" hash="066098b4583e6bd3882c01645bd9ed1d"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><file name=".DS_Store" hash="40954a645387438ecf1cfd83dd604862"/></dir><dir name="sales"><dir name="order"><dir name="view"><dir name="tab"><file name="packinglist.phtml" hash="9ec64369d5c23043976b08573f3435dd"/></dir><file name=".DS_Store" hash="d26113bef03e4a93d3b16a9aacd87cfc"/></dir><file name=".DS_Store" hash="96bd4b45d72afef63db9e4ca6973ea7a"/></dir><file name=".DS_Store" hash="6ab1f047e942f2d939c7cccba9d7b5cf"/></dir><file name=".DS_Store" hash="5e3b5dca196bbd2c9702ade45af32abd"/></dir><file name=".DS_Store" hash="8140bb817e4002386b236a695bbe7526"/></dir><file name=".DS_Store" hash="0553606a64967b5a5e9465482611d59e"/></dir><file name=".DS_Store" hash="3e9bb37c55f91d8adc5fc6c5179e5981"/></dir><file name=".DS_Store" hash="3d0d0b6a6dd6a3990a86b24a0e6799f4"/></dir><file name=".DS_Store" hash="466e7eaa43e0d7602b4cf93b7e8e7c85"/></dir><dir name="etc"><file name="config.xml" hash="1deb80a937fd57e530c5ffb0a77ad727"/><file name="system.xml" hash="26af0cc51ddccb6b91aadf9ff68c32ee"/></dir><dir name="sql"><dir name="fruugo_attributes_setup"><file name="install-0.1.1.php" hash="326629d7305e3c3b20aaf8d97de1f17b"/></dir></dir><file name=".DS_Store" hash="ffc18567b95ae1fb2526dd57d74e8006"/><file name=".gitignore" hash="f0cb20b35e2469e9e9617f658ed452a5"/></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="integration.xml" hash="3f173b27f9f483c00b33b713c0bad737"/></dir><dir name="template"><dir name="integration"><dir name="catalog"><dir name="product"><file name="fruugo-allowed-countries.phtml" hash="066098b4583e6bd3882c01645bd9ed1d"/><file name=".DS_Store" hash="194577a7e20bdcc7afbb718f502c134c"/></dir><file name=".DS_Store" hash="40954a645387438ecf1cfd83dd604862"/></dir><dir name="sales"><dir name="order"><dir name="view"><dir name="tab"><file name="packinglist.phtml" hash="9ec64369d5c23043976b08573f3435dd"/></dir><file name=".DS_Store" hash="d26113bef03e4a93d3b16a9aacd87cfc"/></dir><file name=".DS_Store" hash="96bd4b45d72afef63db9e4ca6973ea7a"/></dir><file name=".DS_Store" hash="6ab1f047e942f2d939c7cccba9d7b5cf"/></dir><file name=".DS_Store" hash="5e3b5dca196bbd2c9702ade45af32abd"/></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Fruugo_Integration.xml" hash="b01dcb5088d487517bd87af216d6d7c8"/></dir></target></contents>
32
  <compatible/>
33
  <dependencies><required><php><min>5.4.0</min><max>6.0.0</max></php><package><name>Mage_Core_Modules</name><channel>community</channel><min>1.8</min><max/></package></required></dependencies>
34
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Fruugo_Integration</name>
4
+ <version>1.0.6</version>
5
  <stability>stable</stability>
6
  <license uri="http://www.gnu.org/licenses/">GNU</license>
7
  <channel>community</channel>
16
  - Read from Fruugo Rrders API periodically on a specified frequency, and notifies Magento order events to Fruugo Orders API.</description>
17
  <notes>This update includes:&#xD;
18
  &#xD;
19
+ - Only add one price tag for each product&#xD;
20
  &#xD;
21
+ - Enabled currency conversion</notes>
 
 
 
 
 
 
22
  <authors><author><name>inoutput.io</name><user>inoutput</user><email>support@inoutput.io</email></author><author><name>fruugo.com</name><user>Fruugo</user><email>support@fruugo.com</email></author></authors>
23
+ <date>2015-12-21</date>
24
+ <time>10:13:13</time>
25
+ <contents><target name="magecommunity"><dir name="Fruugo"><dir name="Integration"><dir name="Block"><dir name="Catalog"><dir name="Product"><file name="Tab.php" hash="412d5a38c07f78fd56e3509809038008"/></dir></dir><file name="Languagestoremapping.php" hash="7852846c2428b4fc15434a1e78a37de7"/><file name="Refreshcountriesbutton.php" hash="5a7e3af708dd470725981ec187b2c07d"/><dir name="Sales"><dir name="Order"><dir name="View"><file name="Tabs.php" hash="d98faeeede3c06d5f28f8f9731438314"/></dir></dir></dir></dir><dir name="Helper"><file name="ConfigLoader.php" hash="f930d687b44fe0a3a9e70383e0500a7c"/><file name="Data.php" hash="866fe8e1ea50749218d6efcab8454f28"/><file name="Defines.php" hash="a97616fe105332db06292c68eabecdf1"/><file name="FruugoCountriesSeeder.php" hash="68949469a1b1ce6a605fccd132e9e3c0"/><file name="Logger.php" hash="a83fdd017e59261e059a8d18d223dd59"/><file name="OrdersFeedProcessor.php" hash="c0969f7217b6e4eb1c875e00afc21227"/><file name="ProductsFeedGenerator.php" hash="a1238387664193d9afb988a4c2114242"/><file name="ProductsFeedGeneratorProfiler.php" hash="33cbf6db6cecda919011d23432d9bdb9"/></dir><dir name="Model"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Backend"><file name="OrderCron.php" hash="8187697b999171b8940be7e5306a3bc7"/><file name="ProductCron.php" hash="a8bdd62ad14cdb85d9ef0ae2b85cecad"/></dir><dir name="Source"><file name="ExportPageSize.php" hash="a322cb1b507d33444078ba6494b461fd"/><file name="Hour.php" hash="fab1337425532af32cf8642b8b244930"/><file name="MaxErrors.php" hash="03213889323c47349b5aaa7d7b6752f5"/><file name="MaxResourcesLoad.php" hash="8b941802b5a45f2744abe83b117dceba"/><file name="ProductDescriptionType.php" hash="4a2810baeabe1a74b1b06b92cc2e4481"/><file name="SleepTimeSec.php" hash="9c9790275cf3ec3347746bee53cb50ae"/></dir></dir></dir></dir><file name="Countries.php" hash="a23378525c616fd87c5589933aec48c6"/><file name="CronJobObserver.php" hash="303c9619a9abb2489365b08c94507b31"/><file name="Observer.php" hash="1e16ebea05d59b472cc9f279965cd17e"/><file name="Payment.php" hash="4e7d2d72e7662ea71c178d85c9b9bd6c"/><dir name="Resource"><dir name="Countries"><file name="Collection.php" hash="618a95f1535d4c63724a2f7eaebf6c2b"/></dir><file name="Countries.php" hash="dc3da7aec3c3472a4ccd73e2b2f255f0"/><dir name="Shipment"><file name="Collection.php" hash="66d038f890e36403f9ffb2e283e88daf"/></dir><file name="Shipment.php" hash="d8c13444ba5089cf606241f2c7c9a87d"/></dir><file name="Shipment.php" hash="23b1c0b4bc4e29cb137c9b3819723a63"/></dir><dir name="controllers"><file name="OrdersController.php" hash="a2d9d2f4c916c376f146cfccecb4110d"/><file name="PackinglistController.php" hash="d76ac40e81f51635a073fb20492883f8"/><file name="ProductsController.php" hash="111475a85b64e4c14753f333568842ec"/><file name=".gitignore" hash="a1d994a3c45d2cb16e44c5a311159532"/></dir><dir name="data"><dir name="fruugo_attributes_setup"><file name="data-install-0.1.1.php" hash="73a6210fdb6c5a2edfa0222a6a239e9a"/></dir></dir><dir name="design"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="integration.xml" hash="3f173b27f9f483c00b33b713c0bad737"/></dir><dir name="template"><dir name="integration"><dir name="catalog"><dir name="product"><file name="fruugo-allowed-countries.phtml" hash="066098b4583e6bd3882c01645bd9ed1d"/></dir></dir><dir name="sales"><dir name="order"><dir name="view"><dir name="tab"><file name="packinglist.phtml" hash="9ec64369d5c23043976b08573f3435dd"/></dir></dir></dir></dir></dir></dir></dir></dir></dir></dir><dir name="etc"><file name="config.xml" hash="57014c6e82e14791ec5ddf82b15e8a83"/><file name="system.xml" hash="fbc772bf4764682c912c97be3959ce12"/></dir><dir name="sql"><dir name="fruugo_attributes_setup"><file name="install-0.1.1.php" hash="326629d7305e3c3b20aaf8d97de1f17b"/></dir></dir><file name=".gitignore" hash="f0cb20b35e2469e9e9617f658ed452a5"/></dir></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="integration.xml" hash="3f173b27f9f483c00b33b713c0bad737"/></dir><dir name="template"><dir name="integration"><dir name="catalog"><dir name="product"><file name="fruugo-allowed-countries.phtml" hash="066098b4583e6bd3882c01645bd9ed1d"/></dir></dir><dir name="sales"><dir name="order"><dir name="view"><dir name="tab"><file name="packinglist.phtml" hash="9ec64369d5c23043976b08573f3435dd"/></dir></dir></dir></dir></dir></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Fruugo_Integration.xml" hash="b01dcb5088d487517bd87af216d6d7c8"/></dir></target></contents>
26
  <compatible/>
27
  <dependencies><required><php><min>5.4.0</min><max>6.0.0</max></php><package><name>Mage_Core_Modules</name><channel>community</channel><min>1.8</min><max/></package></required></dependencies>
28
  </package>