Antidot_Antidot - Version 1.2.1

Version Notes

Fix facet with ampersand configuration for search engine > facets
Replace "-" with "&afs:feed" to separate feeds in url (compatibility with AFS 7.7)
Fix empty facet id sent when performing an empty query to afs to get facets list
Add description node for variants
Add cdata for variant name
Prevent to export empty categories node
Add cdata for variant name
Add variant details for grouped products

Download this release

Release Info

Developer Antidot
Extension Antidot_Antidot
Version 1.2.1
Comparing to
See all releases


Code changes from version 1.1.4 to 1.2.1

Files changed (43) hide show
  1. app/code/community/MDN/Antidot/Block/Catalogsearch/Banner.php +67 -0
  2. app/code/community/MDN/Antidot/Block/System/Config/Button/Forward.php +39 -0
  3. app/code/community/MDN/Antidot/Helper/Data.php +16 -1
  4. app/code/community/MDN/Antidot/Model/Export/Abstract.php +29 -43
  5. app/code/community/MDN/Antidot/Model/Export/Article.php +3 -3
  6. app/code/community/MDN/Antidot/Model/Export/Category.php +14 -12
  7. app/code/community/MDN/Antidot/Model/Export/Context.php +357 -0
  8. app/code/community/MDN/Antidot/Model/Export/Model/Product.php +412 -0
  9. app/code/community/MDN/Antidot/Model/Export/Model/Product/Link.php +40 -0
  10. app/code/community/MDN/Antidot/Model/Export/Model/Product/Type/Configurable.php +92 -0
  11. app/code/community/MDN/Antidot/Model/Export/Model/Product/Type/Grouped.php +69 -0
  12. app/code/community/MDN/Antidot/Model/Export/Product.php +128 -248
  13. app/code/community/MDN/Antidot/Model/Observer.php +31 -36
  14. app/code/community/MDN/Antidot/Model/Resource/Advanced.php +2 -1
  15. app/code/community/MDN/Antidot/Model/Resource/Catalog/Product/Collection.php +40 -3
  16. app/code/community/MDN/Antidot/Model/Resource/Engine/Abstract.php +3 -2
  17. app/code/community/MDN/Antidot/Model/Resource/Engine/Antidot.php +9 -0
  18. app/code/community/MDN/Antidot/Model/Resource/Export/Product.php +138 -0
  19. app/code/community/MDN/Antidot/Model/Resource/Export/Product/Collection.php +59 -0
  20. app/code/community/MDN/Antidot/Model/Resource/Export/Product/Link/Product/Collection.php +102 -0
  21. app/code/community/MDN/Antidot/Model/Resource/Export/Product/Type/Configurable/Product/Collection.php +64 -0
  22. app/code/community/MDN/Antidot/Model/Search/Search.php +2 -10
  23. app/code/community/MDN/Antidot/Model/System/Config/{Engine.php → Source/Acpengine.php} +14 -8
  24. app/code/community/MDN/Antidot/Model/System/Config/Source/Engine.php +64 -0
  25. app/code/community/MDN/Antidot/Model/Transport/Ftp.php +2 -1
  26. app/code/community/MDN/Antidot/Test/Model/Export/Abstract.php +26 -0
  27. app/code/community/MDN/Antidot/Test/Model/Export/Article.php +4 -2
  28. app/code/community/MDN/Antidot/Test/Model/Export/Category.php +8 -10
  29. app/code/community/MDN/Antidot/Test/Model/Export/Context.php +83 -0
  30. app/code/community/MDN/Antidot/Test/Model/Export/Context/fixtures/testContext.yaml +129 -0
  31. app/code/community/MDN/Antidot/Test/Model/Export/Product.php +121 -119
  32. app/code/community/MDN/Antidot/Test/Model/Export/Product/fixtures/testGetProductCategories.yaml +0 -101
  33. app/code/community/MDN/Antidot/Test/Model/Export/Product/fixtures/testWritePricesFixedtax.yaml +3 -1
  34. app/code/community/MDN/Antidot/Test/Model/Export/Product/fixtures/testWriteXml.yaml +3 -0
  35. app/code/community/MDN/Antidot/Test/Model/Observer.php +19 -30
  36. app/code/community/MDN/Antidot/Test/Model/Resource/Engine/Antidot.php +27 -0
  37. app/code/community/MDN/Antidot/Test/Model/System/Config/Source/Engine.php +43 -0
  38. app/code/community/MDN/Antidot/etc/config.xml +17 -2
  39. app/code/community/MDN/Antidot/etc/system.xml +20 -3
  40. app/design/frontend/base/default/layout/antidot.xml +2 -1
  41. app/design/frontend/base/default/template/antidot/catalogsearch/result.phtml +5 -1
  42. app/design/frontend/base/default/template/antidot/catalogsearch/result/banner.phtml +6 -0
  43. package.xml +4 -4
app/code/community/MDN/Antidot/Block/Catalogsearch/Banner.php ADDED
@@ -0,0 +1,67 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalogsearch_Banner extends Mage_Core_Block_Template
17
+ {
18
+ protected $_banners = null;
19
+
20
+ /**
21
+ * Return banners
22
+ * @return type
23
+ */
24
+ public function getBanners()
25
+ {
26
+ if ($this->_banners == null)
27
+ {
28
+ $this->loadBanners();
29
+ }
30
+ return $this->_banners;
31
+ }
32
+
33
+ /**
34
+ * Return banners
35
+ * @return boolean
36
+ */
37
+ public function hasBanner()
38
+ {
39
+ return count($this->getBanners())>0;
40
+ }
41
+
42
+ /**
43
+ * Load banners based on antidot results
44
+ */
45
+ protected function loadBanners()
46
+ {
47
+
48
+ $this->_banners = $this->getLayer()->getProductCollection()->getBanners();
49
+
50
+ }
51
+
52
+ /**
53
+ * Returns current catalog layer.
54
+ *
55
+ * @return MDN_Antidot_Model_Catalogsearch_Layer|Mage_Catalog_Model_Layer
56
+ */
57
+ public function getLayer()
58
+ {
59
+ $helper = Mage::helper('Antidot');
60
+ if ($helper->isActiveEngine()) {
61
+ return Mage::getSingleton('Antidot/catalogsearch_layer');
62
+ }
63
+
64
+ return parent::getLayer();
65
+ }
66
+
67
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/Forward.php ADDED
@@ -0,0 +1,39 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_Forward
17
+ extends Mage_Adminhtml_Block_System_Config_Form_Field
18
+ {
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
24
+ {
25
+ $this->setElement($element);
26
+ $url = Mage::helper("adminhtml")->getUrl('adminhtml/system_config/edit/', array( "section" => "antidot"));
27
+
28
+ $html = $this->getLayout()->createBlock('adminhtml/widget_button')
29
+ ->setType('button')
30
+ ->setClass('scalable')
31
+ ->setLabel(Mage::helper('Antidot')->__('Configure AFS@Store'))
32
+ ->setOnClick("setLocation('$url')")
33
+ ->toHtml();
34
+
35
+ return $html.'<input type="hidden" id="catalog_search_antidot_config_forward" />';
36
+ //add input hidden with id , use by javascript show/hide function
37
+ }
38
+
39
+ }
app/code/community/MDN/Antidot/Helper/Data.php CHANGED
@@ -179,7 +179,7 @@ class MDN_Antidot_Helper_Data extends Mage_Core_Helper_Abstract
179
*/
180
public function sendMail($subject, $message)
181
{
182
- if(!$email = Mage::getStoreConfig('antidot/general/email')) {
183
return;
184
}
185
@@ -285,4 +285,19 @@ class MDN_Antidot_Helper_Data extends Mage_Core_Helper_Abstract
285
return (int) $val;
286
}
287
288
}
179
*/
180
public function sendMail($subject, $message)
181
{
182
+ if(!$email = Mage::getStoreConfig('antidot/general/email', Mage_Core_Model_App::ADMIN_STORE_ID)) {
183
return;
184
}
185
285
return (int) $val;
286
}
287
288
+ const EDITION_COMMUNITY = 'Community';
289
+ const EDITION_ENTERPRISE = 'Enterprise';
290
+
291
+ public function getMagentoEdition() {
292
+ if (method_exists('Mage','getEdition')) {
293
+ $mageEdition = Mage::getEdition();
294
+ } else {
295
+ if (Mage::helper('core')->isModuleEnabled('Enterprise_Enterprise')) {
296
+ $mageEdition = 'Enterprise';
297
+ } else {
298
+ $mageEdition = 'Community';
299
+ }
300
+ }
301
+ return $mageEdition;
302
+ }
303
}
app/code/community/MDN/Antidot/Model/Export/Abstract.php CHANGED
@@ -22,15 +22,6 @@ class MDN_Antidot_Model_Export_Abstract extends Mage_Core_Model_Abstract
22
*/
23
protected $xml;
24
25
- /**
26
- * List website loaded
27
- *
28
- * @var array
29
- */
30
- protected $website = array();
31
-
32
- protected $storeLang = array();
33
-
34
/**
35
* The fields to load
36
*
@@ -52,21 +43,28 @@ class MDN_Antidot_Model_Export_Abstract extends Mage_Core_Model_Abstract
52
{
53
if($this->xml === null) {
54
$this->xml = Mage::helper('Antidot/xmlWriter');
55
- $this->xml->init();
56
}
57
}
58
59
/**
60
- * Extract the uri from an url
61
- *
62
* @param string $url
63
* @return string
64
*/
65
- protected function getUri($url)
66
{
67
- $urls = parse_url($url);
68
//replace all antidotExport*.php script by index.php in uri (in case of cron export) :
69
- return preg_replace('#\/(.*)\.php#', '/index.php', $urls['path']);
70
}
71
72
/**
@@ -112,52 +110,40 @@ class MDN_Antidot_Model_Export_Abstract extends Mage_Core_Model_Abstract
112
113
return $entity->$method();
114
}
115
-
116
- /**
117
- * Get website by store
118
- *
119
- * @param Store $store
120
- * @return WebSite
121
- */
122
- protected function getWebSiteByStore($store)
123
- {
124
- if(!isset($this->website[$store->getId()])) {
125
- $this->website[$store->getId()] = Mage::getModel('core/website')->load($store->getWebSiteId());
126
- }
127
-
128
- return $this->website[$store->getId()];
129
- }
130
-
131
- protected function getStoreLang($storeId)
132
- {
133
- if(!isset($this->storeLang[$storeId])) {
134
- list($this->storeLang[$storeId]) = explode('_', Mage::getStoreConfig('general/locale/code', $storeId));
135
- }
136
-
137
- return $this->storeLang[$storeId];
138
- }
139
140
/**
141
* Write the xml header
142
- *
143
*/
144
protected function writeHeader($context)
145
{
146
$this->xml->push('header');
147
- $this->xml->element('owner', $context['owner']);
148
$this->xml->element('feed', $this->getFeed($context));
149
$this->xml->element('generated_at', date('c', Mage::getModel('core/date')->timestamp(time())));
150
$this->xml->pop();
151
}
152
153
/**
154
* Get the value to insert in the feed tag
155
* @param $type (product, category, article)
156
- * @param $context
157
* @return string
158
*/
159
public function getFeed($context) {
160
- return strtolower($this::TYPE) . ' ' . $context['run'] . ' v' . Mage::getConfig()->getNode()->modules->MDN_Antidot->version;
161
}
162
163
/**
22
*/
23
protected $xml;
24
25
/**
26
* The fields to load
27
*
43
{
44
if($this->xml === null) {
45
$this->xml = Mage::helper('Antidot/xmlWriter');
46
+ $debug = Mage::getStoreConfig('antidot/export/debug_xml_enabled');
47
+ $this->xml->init($debug);
48
}
49
}
50
51
/**
52
+ * Extract the exact uri from an url
53
+ * (When executed from cron script magento can generate url whith the script name instead of index.php)
54
+ * (MCNX-253)
55
+ *
56
* @param string $url
57
+ * $param boolean $onlyPath
58
* @return string
59
*/
60
+ protected function getExactUrl($url, $onlyPath = true)
61
{
62
+ if ($onlyPath) {
63
+ $urls = parse_url($url);
64
+ $url = $urls['path'];
65
+ }
66
//replace all antidotExport*.php script by index.php in uri (in case of cron export) :
67
+ return preg_replace('#\/antidotExport(.*)\.php#', '/index.php', $url);
68
}
69
70
/**
110
111
return $entity->$method();
112
}
113
114
/**
115
* Write the xml header
116
+ * @param MDN_Antidot_Model_Export_Context $context
117
*/
118
protected function writeHeader($context)
119
{
120
$this->xml->push('header');
121
+ $this->xml->element('owner', $this->getOwner());
122
$this->xml->element('feed', $this->getFeed($context));
123
$this->xml->element('generated_at', date('c', Mage::getModel('core/date')->timestamp(time())));
124
$this->xml->pop();
125
}
126
127
+ /**
128
+ * Get the value to insert in the owner tag
129
+ * @return string
130
+ */
131
+ public function getOwner() {
132
+ $owner = 'AFS@Store for Magento v'.Mage::getConfig()->getNode()->modules->MDN_Antidot->version;
133
+ if (Mage::getStoreConfig('antidot/general/owner', Mage_Core_Model_App::ADMIN_STORE_ID)) {
134
+ $owner = Mage::getStoreConfig('antidot/general/owner', Mage_Core_Model_App::ADMIN_STORE_ID);
135
+ }
136
+ return $owner;
137
+ }
138
+
139
/**
140
* Get the value to insert in the feed tag
141
* @param $type (product, category, article)
142
+ * @param MDN_Antidot_Model_Export_Context $context
143
* @return string
144
*/
145
public function getFeed($context) {
146
+ return strtolower($this::TYPE) . ' ' . $context->getRunType() . ' v' . Mage::getConfig()->getNode()->modules->MDN_Antidot->version;
147
}
148
149
/**
app/code/community/MDN/Antidot/Model/Export/Article.php CHANGED
@@ -43,12 +43,12 @@ class MDN_Antidot_Model_Export_Article extends MDN_Antidot_Model_Export_Product
43
$this->writeHeader($context);
44
$this->writePart($this->xml->flush());
45
46
- foreach($context['store_id'] as $storeId) {
47
- $store = Mage::getModel('core/store')->load($storeId);
48
$page = 1;
49
while($articles = $this->getProducts($store, $page, self::ARTICLE_LIMIT)) {
50
foreach($articles as $article) {
51
- $this->xml->push('article', array('id' => $article->getId(), 'xml:lang' => $context['lang']));
52
53
$this->xml->push('websites');
54
$this->xml->element('website', $store->getWebsite()->getName(), array('id' => $store->getWebsite()->getId()));
43
$this->writeHeader($context);
44
$this->writePart($this->xml->flush());
45
46
+ foreach($context->getWebsiteAndStores() as $ws) {
47
+ $store = $ws['store'];
48
$page = 1;
49
while($articles = $this->getProducts($store, $page, self::ARTICLE_LIMIT)) {
50
foreach($articles as $article) {
51
+ $this->xml->push('article', array('id' => $article->getId(), 'xml:lang' => $context->getLang()));
52
53
$this->xml->push('websites');
54
$this->xml->element('website', $store->getWebsite()->getName(), array('id' => $store->getWebsite()->getId()));
app/code/community/MDN/Antidot/Model/Export/Category.php CHANGED
@@ -23,16 +23,17 @@ class MDN_Antidot_Model_Export_Category extends MDN_Antidot_Model_Export_Abstrac
23
/**
24
* Get xml
25
*
26
- * @param type $context
27
*/
28
public function writeXml($context, $filename)
29
{
30
$nbTotalCategories = 0;
31
- $context['categories'] = array();
32
- foreach($context['stores'] as $store) {
33
- $categories = $this->getCategories($store);
34
- $nbTotalCategories += $categories->getSize();
35
- $context['categories'][$store->getId()] = $categories;
36
}
37
38
if ($nbTotalCategories == 0) {
@@ -47,13 +48,14 @@ class MDN_Antidot_Model_Export_Category extends MDN_Antidot_Model_Export_Abstrac
47
$this->writeHeader($context);
48
49
$nbItems = 0;
50
- foreach($context['stores'] as $store) {
51
- foreach($context['categories'][$store->getId()] as $cat) {
52
53
if (!$this->getField($cat, 'name'))
54
continue;
55
56
- $this->xml->push('category', array('id' => $cat->getId(), 'xml:lang' => $context['lang']));
57
58
$this->xml->element('name', $this->xml->encloseCData($this->getField($cat, 'name')));
59
@@ -64,7 +66,7 @@ class MDN_Antidot_Model_Export_Category extends MDN_Antidot_Model_Export_Abstrac
64
} else {
65
$cat->getUrlInstance()->setStore($store->getId());
66
}
67
- $this->xml->element('url', $this->getUri($cat->getUrl()));
68
69
if ($cat->getImageUrl()) {
70
$this->xml->element('image', $cat->getImageUrl());
@@ -86,10 +88,10 @@ class MDN_Antidot_Model_Export_Category extends MDN_Antidot_Model_Export_Abstrac
86
$this->xml->emptyelement('broader', array('idref' => $cat->getParentId()));
87
}
88
89
- $storeIds = array_intersect($context['store_id'], $cat->getStoreIds());
90
$this->xml->push('websites');
91
foreach($storeIds as $storeId) {
92
- $website = $this->getWebSiteByStore($context['stores'][$storeId]);
93
$this->xml->element('website', '', array('id' => $website->getId(), 'name' => $website->getName()));
94
}
95
$this->xml->pop();
23
/**
24
* Get xml
25
*
26
+ * @param MDN_Antidot_Model_Export_Context $context
27
*/
28
public function writeXml($context, $filename)
29
{
30
$nbTotalCategories = 0;
31
+ $categories = array();
32
+ foreach($context->getWebsiteAndStores() as $ws) {
33
+ $store = $ws['store'];
34
+ $categoriesColl = $this->getCategories($store);
35
+ $nbTotalCategories += $categoriesColl->getSize();
36
+ $categories[$store->getId()] = $categoriesColl;
37
}
38
39
if ($nbTotalCategories == 0) {
48
$this->writeHeader($context);
49
50
$nbItems = 0;
51
+ foreach($context->getWebsiteAndStores() as $ws) {
52
+ $store = $ws['store'];
53
+ foreach($categories[$store->getId()] as $cat) {
54
55
if (!$this->getField($cat, 'name'))
56
continue;
57
58
+ $this->xml->push('category', array('id' => $cat->getId(), 'xml:lang' => $context->getLang()));
59
60
$this->xml->element('name', $this->xml->encloseCData($this->getField($cat, 'name')));
61
66
} else {
67
$cat->getUrlInstance()->setStore($store->getId());
68
}
69
+ $this->xml->element('url', $this->getExactUrl($cat->getUrl()));
70
71
if ($cat->getImageUrl()) {
72
$this->xml->element('image', $cat->getImageUrl());
88
$this->xml->emptyelement('broader', array('idref' => $cat->getParentId()));
89
}
90
91
+ $storeIds = array_intersect($context->getStoreIds(), $cat->getStoreIds());
92
$this->xml->push('websites');
93
foreach($storeIds as $storeId) {
94
+ $website = $context->getWebSiteByStore($storeId);
95
$this->xml->element('website', '', array('id' => $website->getId(), 'name' => $website->getName()));
96
}
97
$this->xml->pop();
app/code/community/MDN/Antidot/Model/Export/Context.php ADDED
@@ -0,0 +1,357 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Context extends Mage_Core_Model_Abstract
17
+ {
18
+ /**
19
+ * @var string $runType
20
+ */
21
+ protected $runType;
22
+ /**
23
+ * @var string $lang
24
+ */
25
+ protected $lang;
26
+ /**
27
+ * Array of the websites, key=websiteId
28
+ *
29
+ * @var array
30
+ */
31
+ protected $websites = array();
32
+ /**
33
+ * MultiDimentional Array of the stores, key=websiteId, storeId
34
+ * @var array
35
+ */
36
+ protected $stores = array();
37
+ /**
38
+ * Array of store ids
39
+ * @var array
40
+ */
41
+ protected $storeIds = array();
42
+ /**
43
+ * Array of categoryTrees
44
+ *
45
+ * @var array
46
+ */
47
+ protected $categoryTrees = array();
48
+ /**
49
+ * Array of rootCategoryIds
50
+ *
51
+ * @var array
52
+ */
53
+ protected $rootCategoryIds = array();
54
+
55
+ /**
56
+ * Array of attributes to load in products
57
+ *
58
+ * @var array $attributesToLoad
59
+ */
60
+ protected $attributesToLoad = array();
61
+
62
+ /**
63
+ * @param $runType
64
+ * @param $lang
65
+ */
66
+ function __construct($args) {
67
+ $this->lang = $args[0];
68
+ $this->runType = $args[1];
69
+ }
70
+
71
+ function getLang() {
72
+ return $this->lang;
73
+ }
74
+
75
+ function getRunType() {
76
+ return $this->runType;
77
+ }
78
+
79
+ /**
80
+ * Add a store to the current Export context
81
+ *
82
+ * @param $store Mage_Core_Model_Store
83
+ */
84
+ function addStore($store) {
85
+ if ($store->getIsActive()) {
86
+ $website = $store->getWebsite();
87
+ if (!isset($this->websites[$website->getId()])) {
88
+ $this->websites[$website->getId()] = $website;
89
+ }
90
+ if (!isset($this->stores[$website->getId()][$store->getId()])) {
91
+ //construct lists of stores grouped by websiteid
92
+ $this->stores[$website->getId()][$store->getId()] = $store;
93
+ //construct list of all storeids
94
+ $this->storeIds[] = $store->getId();
95
+ //construct list of rootcategoryIds
96
+ if (!isset($this->rootCategoryIds[$store->getRootCategoryId()])) {
97
+ $this->rootCategoryIds[$store->getRootCategoryId()] = array();
98
+ }
99
+ $this->rootCategoryIds[$store->getRootCategoryId()][] = $store;
100
+ }
101
+ }
102
+
103
+ }
104
+
105
+ /**
106
+ * Load and init the category tree corresponding to the stores
107
+ * of the current export context
108
+ */
109
+ function initCategoryTree() {
110
+
111
+ Varien_Profiler::start("export_product_initCategoryTree");
112
+
113
+ foreach ($this->rootCategoryIds as $rootId => $stores) {
114
+ /**
115
+ * define the store tu used to load the category tree :
116
+ * use default store first
117
+ */
118
+ $defaultStoreId = null;
119
+ //first take the default store of the default website
120
+ foreach ($stores as $store) {
121
+ $website = $store->getWebsite();
122
+ if ($website->getIsDefault() && $website->getDefautStore() && $website->getDefautStore()->getId() == $store->getId()) {
123
+ $defaultStoreId = $store->getId();
124
+ }
125
+ }
126
+ //if not found take the first store of the default website
127
+ if ($defaultStoreId == null) {
128
+ foreach ($stores as $store) {
129
+ $website = $store->getWebsite();
130
+ if ($website->getIsDefault() && $defaultStoreId == null) {
131
+ $defaultStoreId = $store->getId();
132
+ }
133
+ }
134
+ }
135
+ //if not found take the first store of the first website
136
+ if ($defaultStoreId == null) {
137
+ $store = current($stores);
138
+ $defaultStoreId = $store->getId();
139
+ }
140
+
141
+ //LOAD TREE with $rootId and $defaultStoreId
142
+ $tree = Mage::getResourceModel('catalog/category_tree')
143
+ ->load($rootId);
144
+
145
+ $collection = Mage::getModel('catalog/category')->getCollection();
146
+ /** @var $collection Mage_Catalog_Model_Resource_Category_Collection */
147
+
148
+ //Set Store Id
149
+ $collection->setStoreId($defaultStoreId);
150
+
151
+ //add attributes to display
152
+ $collection->addAttributeToSelect(array('name', 'image'));
153
+
154
+ //exclude categories not actives and without name
155
+ $collection->addAttributeToFilter('name', array('neq' => '')); //Exclude empty name categories
156
+ $collection->addAttributeToFilter('is_active', 1);
157
+
158
+ //filter on the tree categories
159
+ $nodeIds = array();
160
+ foreach ($tree->getNodes() as $node) {
161
+ $nodeIds[] = $node->getId();
162
+ }
163
+ $collection->addIdFilter($nodeIds);
164
+
165
+ //join url-rewrite table
166
+ if (class_exists ('Mage_Catalog_Model_Factory', false)) {
167
+ Mage::getSingleton('catalog/factory')->getCategoryUrlRewriteHelper()
168
+ ->joinTableToEavCollection($collection, $defaultStoreId);
169
+ } else {
170
+ /**
171
+ * Join url rewrite table to eav collection
172
+ *
173
+ * @param Mage_Eav_Model_Entity_Collection_Abstract $collection
174
+ * @param int $storeId
175
+ * @return Mage_Catalog_Helper_Category_Url_Rewrite
176
+ */
177
+
178
+ $collection->joinTable(
179
+ 'core/url_rewrite',
180
+ 'category_id=entity_id',
181
+ array('request_path'),
182
+ "{{table}}.is_system=1 AND " .
183
+ "{{table}}.store_id='{$defaultStoreId}' AND " .
184
+ "{{table}}.id_path LIKE 'category/%'",
185
+ 'left'
186
+ );
187
+
188
+ }
189
+
190
+ //Add collection data to the tre nodes see Mage_Catalog_Model_Resource_Category_Tree#addCollectionData
191
+ foreach ($collection as $category) {
192
+ if ($node = $tree->getNodeById($category->getId())) {
193
+
194
+ /* Calculate the url to export */
195
+ if (method_exists($category, 'getUrlModel')) { //compatibility with older magento version where category#getUrlModel doesn't exist
196
+ $category->getUrlModel()->getUrlInstance()->setStore($defaultStoreId);
197
+ } else {
198
+ $category->getUrlInstance()->setStore($defaultStoreId);
199
+ }
200
+ $category->setData('url', $category->getUrl());
201
+
202
+ $node->addData($category->getData());
203
+ }
204
+ }
205
+
206
+ foreach ($tree->getNodes() as $node) {
207
+ //if the node is not in the collection (not active), remove it and all his descendant from the tree (except if it's the root node)
208
+ if ($collection->getItemById($node->getId()) == null && $node->getLevel() > 1) {
209
+ $this->removeBranch($tree, $node);
210
+ }
211
+ }
212
+
213
+ //Mage::log("LOAD TREE ".$rootId. " ". $defaultStoreId . ' '. spl_object_hash($tree), null, 'antidot.log');
214
+ //Mage::log($collection->getSelect()->__toString() , null, 'antidot.log');
215
+ //$i=0;
216
+ //foreach ($tree->getNodes() as $node) {
217
+ // $i++;
218
+ //}
219
+ //Mage::log('Tree nodes '.$i , null, 'antidot.log');
220
+
221
+ $this->categoryTrees[] = $tree;
222
+
223
+ }
224
+
225
+ Varien_Profiler::stop("export_product_initCategoryTree");
226
+
227
+ }
228
+
229
+ /**
230
+ * recursive call of removeNode on tree
231
+ */
232
+ private function removeBranch($tree, $node) {
233
+ foreach ($node->getChildren() as $child) {
234
+ $this->removeBranch($tree, $child);
235
+ }
236
+ $tree->removeNode($node);
237
+ }
238
+
239
+
240
+ /**
241
+ * Get the category trees
242
+ *
243
+ * @return array
244
+ */
245
+ function getCategoryTrees() {
246
+ return $this->categoryTrees;
247
+ }
248
+
249
+ /* non utilisé pour l'instant
250
+ function getWebsites() {
251
+ $this->websites;
252
+ }*/
253
+
254
+ /**
255
+ * Get the list of the pair website/store of the current context
256
+ *
257
+ * @return array
258
+ */
259
+ function getWebsiteAndStores() {
260
+ $list = array();
261
+ foreach($this->stores as $websiteId => $stores) {
262
+ $website = $this->websites[$websiteId];
263
+ foreach ($stores as $store) {
264
+ $list[] = array('website' => $website, 'store' => $store);
265
+ }
266
+ }
267
+ return $list;
268
+ }
269
+
270
+ /**
271
+ * Get the website corresponding to the storeId
272
+ *
273
+ * @return Mage_Core_Model_Website
274
+ */
275
+ function getWebSiteByStore($storeId) {
276
+ foreach($this->stores as $websiteId => $stores) {
277
+ foreach($stores as $store) {
278
+ if ($store->getId() == $storeId) {
279
+ return $this->websites[$websiteId];
280
+ }
281
+ }
282
+ }
283
+ return null;
284
+ }
285
+
286
+ /**
287
+ * Get the list of website ids of the current context
288
+ *
289
+ * @return array
290
+ */
291
+ function getWebsiteIds() {
292
+ return array_keys($this->websites);
293
+ }
294
+
295
+ /**
296
+ * Get the list of store ids of the current context
297
+ *
298
+ * @return array
299
+ */
300
+ function getStoreIds() {
301
+ return $this->storeIds;
302
+ }
303
+
304
+ /**
305
+ *
306
+ */
307
+ function addAttributeToLoad($fields) {
308
+
309
+ //Theses attributes are configured in BO System > config > AfsStore :
310
+ $this->attributesToLoad = array();
311
+ foreach ($fields as $afsCode => $attributeCode) {
312
+ if ($afsCode == 'in_stock_only') {
313
+ continue; //not an attribute
314
+ }
315
+
316
+ if (is_array($attributeCode)) { //description, identifier, properties
317
+ foreach ($attributeCode as $code) {
318
+ if ($afsCode == 'properties') {
319
+ $this->attributesToLoad[] = array('code' => $code['value'], 'on_store' => false);
320
+ } else {
321
+ $this->attributesToLoad[] = array('code' => $code, 'on_store' => false);
322
+ }
323
+ }
324
+ } else {
325
+
326
+ if ($attributeCode) {
327
+ //Theses attributes must be reloaded on each stores
328
+ if (in_array($afsCode, array('is_new', 'is_best_sale', 'is_featured'))) {
329
+ $this->attributesToLoad[] = array('code' => $attributeCode, 'on_store' => true);
330
+ } else {
331
+ $this->attributesToLoad[] = array('code' => $attributeCode, 'on_store' => false);
332
+ }
333
+ }
334
+ }
335
+ }
336
+
337
+ //Theses attributes must be reloaded on each stores
338
+ $this->attributesToLoad[] = array('code' => 'image', 'on_store' => true);
339
+ $this->attributesToLoad[] = array('code' => 'thumbnail', 'on_store' => true);
340
+
341
+ }
342
+
343
+ function getAttributesToLoad($forStore = false) {
344
+
345
+ $attributes = array();
346
+ foreach ($this->attributesToLoad as $attribute) {
347
+ if ($attribute['on_store'] || !$forStore) {
348
+ if ($attribute['code'] != 'sku') { //sku already loaded on catalog_product_entity
349
+ $attributes[] = $attribute['code'];
350
+ }
351
+ }
352
+ }
353
+ return $attributes;
354
+
355
+ }
356
+
357
+ }
app/code/community/MDN/Antidot/Model/Export/Model/Product.php ADDED
@@ -0,0 +1,412 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Model_Product extends Mage_Catalog_Model_Product
17
+ {
18
+
19
+ /**
20
+ * The export context
21
+ * @var MDN_Antidot_Model_Export_Context $context
22
+ */
23
+ protected $context;
24
+
25
+ /**
26
+ * The stores of the current export product
27
+ * @var array $storeIds
28
+ */
29
+ protected $stores;
30
+
31
+ /**
32
+ * The websites of the current export product
33
+ * @var array $websites
34
+ */
35
+ protected $websites;
36
+
37
+ /**
38
+ * The id of the store to use for loading data attributes
39
+ * @var integer $currentStoreId
40
+ */
41
+ protected $currentStoreId;
42
+
43
+ /**
44
+ * The category tree
45
+ * @var Varien_Data_tree $categoryTree
46
+ */
47
+ protected $categoryTree;
48
+
49
+ /**
50
+ * Constructor
51
+ *
52
+ *
53
+ * @param $args
54
+ */
55
+ public function __construct($args = array()) {
56
+
57
+ if (count($args)>0) {
58
+ $this->context = $args[0];
59
+ }
60
+ $this->stores= array();
61
+ $this->websites= array();
62
+ $this->currentStoreId=null;
63
+ $this->categoryTree=null;
64
+
65
+ //initialise model with MDN_Antidot_Model_Resource_Export_Product resource
66
+ $this->_init('Antidot/export_product');
67
+
68
+ }
69
+
70
+ public function setContext($context, $forVariant = false) {
71
+ $this->context = $context;
72
+ return $this->initWebsitesStores($forVariant);
73
+ }
74
+
75
+ /**
76
+ *
77
+ * Define the stores of the current export where the product
78
+ * is visible and searcheable
79
+ *
80
+ */
81
+ public function initWebsitesStores($forVariant = false)
82
+ {
83
+ foreach ($this->context->getWebsiteAndStores() as $wstore) {
84
+
85
+ $website = $wstore['website'];
86
+ $store = $wstore['store'];
87
+
88
+ Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID); //USE EAV table
89
+ $collection = Mage::getModel('catalog/product')->getCollection();
90
+ $collection->addWebsiteFilter($website->getId());
91
+ $collection->setStoreId($store->getId());
92
+ if (!$forVariant) {
93
+ $collection->addAttributeToFilter(
94
+ 'visibility',
95
+ array(
96
+ Mage_Catalog_Model_Product_Visibility::VISIBILITY_IN_SEARCH,
97
+ Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH,
98
+ )
99
+ );
100
+ }
101
+ $collection->addAttributeToFilter('status', 1);
102
+ $collection->addAttributeToFilter('entity_id', $this->getId());
103
+
104
+ //Mage::log($collection->getSelect()->__toString(), null, 'antidot.log');
105
+
106
+ if ($collection->getSize()>0) {
107
+ $this->stores[$store->getId()] = $store;
108
+ if (!isset($this->websites[$website->getId()])) {
109
+ $this->websites[$website->getId()] = $website;
110
+ }
111
+ //define the default store for this context :
112
+ //take the magento default store if it is this export context
113
+ if ($website->getIsDefault()) {
114
+ if ($website->getDefaultStore() &&
115
+ $website->getDefaultStore()->getId() == $store->getId()) {
116
+ $this->currentStoreId = $store->getId();
117
+ }
118
+ }
119
+ }
120
+
121
+ }
122
+
123
+ //if the magento default store is not in this context
124
+ //take the default store of the first website
125
+ if ($this->currentStoreId == null) {
126
+ $website = current($this->websites);
127
+ if ($website && $website->getDefaultStore()
128
+ && in_array($website->getDefaultStore()->getId(), array_keys($this->stores))) {
129
+ $this->currentStoreId = $website->getDefaultStore()->getId();
130
+ }
131
+ }
132
+
133
+ //if default store is still not defined
134
+ if ($this->currentStoreId == null) {
135
+ if ($store = current($this->stores)) {
136
+ $this->currentStoreId = $store->getId();
137
+ }
138
+ }
139
+
140
+ //if the product is not active on any store of the context, don't export it
141
+ if ($this->currentStoreId == null) {
142
+ return false;
143
+ }
144
+
145
+ return true;
146
+
147
+ }
148
+
149
+
150
+ /**
151
+ * The current export context
152
+ * @return MDN_Antidot_Model_Export_Context
153
+ */
154
+ public function getContext() {
155
+ return $this->context;
156
+ }
157
+
158
+ /**
159
+ * The stores of the current export
160
+ * @return array
161
+ */
162
+ public function getStores() {
163
+ return $this->stores;
164
+ }
165
+
166
+ /**
167
+ * get the websites of the current export
168
+ * @return array
169
+ */
170
+ function getWebsites() {
171
+ return $this->websites;
172
+ }
173
+
174
+ /**
175
+ * Get the category tree
176
+ */
177
+ function getCategoryTree() {
178
+
179
+ if (!$this->categoryTree) {
180
+
181
+ /**
182
+ * Create the Tree with the root node
183
+ */
184
+ $this->categoryTree = new Varien_Data_Tree();
185
+ $rootNode = new Varien_Data_Tree_Node(
186
+ array(
187
+ 'entity_id' => 1,
188
+ 'parent_id' => 0,
189
+ 'path' => 1,
190
+ 'position' => 0,
191
+ 'level' => 0,
192
+ 'path_id' => 1,
193
+ 'name' => 'Root',
194
+ 'is_active' => 1),
195
+ 'entity_id', $this->categoryTree, null);
196
+ $rootNode->setLevel(0);
197
+ $rootNode->setPathId(1);
198
+ $this->categoryTree->addNode($rootNode, null);
199
+
200
+ /**
201
+ * Get the category ids linked to the product
202
+ */
203
+ $categoryIds = $this->getCategoryIds();
204
+
205
+ /**
206
+ * Run through the context category trees and extract the nodes corresponding
207
+ * to the product categories and construct his category tree
208
+ */
209
+ $trees = $this->context->getCategoryTrees();
210
+ foreach ($trees as $tree) {
211
+ foreach ($categoryIds as $categoryId) {
212
+ /** @var Varien_Data_Tree_Node $node */
213
+ if ($node = $tree->getNodeById($categoryId)) {
214
+
215
+ //Add this category to the product category tree:
216
+ $path = array();
217
+ while ($node != null) {
218
+ $path[] = $node;
219
+ $node = $node->getParent();
220
+ }
221
+
222
+ $parentNode = $rootNode;
223
+ foreach (array_reverse($path) as $node) {
224
+ if ($node->getLevel() > $parentNode->getLevel()) {
225
+ $productTreeNode = $this->categoryTree->getNodeById($node->getId());
226
+ if ($productTreeNode == null) {
227
+ $parentNode = $this->categoryTree->appendChild($node->getData(), $parentNode);
228
+ } else {
229
+ $parentNode = $productTreeNode;
230
+ }
231
+ }
232
+ }
233
+
234
+ }
235
+ }
236
+ }
237
+
238
+ }
239
+
240
+ return $this->categoryTree;
241
+ }
242
+
243
+ /**
244
+ *
245
+ */
246
+ public function loadNeededAttributes($forStore = false) {
247
+
248
+ $attributeIds = array();
249
+ foreach ($this->context->getAttributesToLoad($forStore) as $attrCode) {
250
+ if ($attribute = $this->getResource()->getAttribute($attrCode)) {
251
+ $attributeIds[] = $attribute->getId();
252
+ }
253
+ }
254
+ $this->getResource()->loadModelAttributes($this, $attributeIds);
255
+
256
+ }
257
+
258
+ protected function _beforeLoad($id, $field = null)
259
+ {
260
+ //don't dispatch before load events
261
+ return $this;
262
+ }
263
+
264
+ /**
265
+ * Load object data
266
+ *
267
+ * @param integer $id
268
+ * @return Mage_Core_Model_Abstract
269
+ */
270
+ public function load($id, $field=null)
271
+ {
272
+ $this->_beforeLoad($id, $field);
273
+ $this->_getResource()->load($this, $id, $this->context->getAttributesToLoad(false));
274
+ $this->_afterLoad();
275
+ //don't set origin data (reduce memory consumtion)
276
+ //$this->setOrigData();
277
+ $this->_hasDataChanges = false;
278
+ return $this;
279
+ }
280
+
281
+ protected function _afterLoad()
282
+ {
283
+ //Don't dispatch after load events
284
+ return $this;
285
+ }
286
+
287
+ public function afterLoad()
288
+ {
289
+ // don't call after load
290
+ //$this->getResource()->afterLoad($this);
291
+ $this->_afterLoad();
292
+ return $this;
293
+ }
294
+
295
+ /**
296
+ * Retrieve link instance
297
+ *
298
+ * @return Mage_Catalog_Model_Product_Link
299
+ */
300
+ public function getLinkInstance()
301
+ {
302
+ if (!$this->_linkInstance) {
303
+ $this->_linkInstance = Mage::getSingleton('Antidot/export_model_product_link');
304
+ }
305
+ return $this->_linkInstance;
306
+ }
307
+
308
+ /**
309
+ * Retrieve type instance
310
+ *
311
+ * Type instance implement type depended logic
312
+ *
313
+ * @param bool $singleton
314
+ * @return Mage_Catalog_Model_Product_Type_Abstract
315
+ */
316
+ public function getTypeInstance($singleton = false)
317
+ {
318
+ //configure the type model in config.xml to let developper implement their own product types
319
+ if ($model = (string) Mage::getConfig()->getNode('default/antidot/export/product_type_'.$this->getTypeID())) {
320
+ return Mage::getSingleton($model);
321
+ }
322
+ return parent::getTypeInstance($singleton = false);
323
+ }
324
+
325
+ /**
326
+ * Return qty
327
+ *
328
+ * qty is loaded by product collection join with inventory table
329
+ *
330
+ * @return int
331
+ */
332
+ public function getQty() {
333
+ if (!$this->hasData('qty')) {
334
+ if ($this->hasData('stock_item')) {
335
+ $this->setData('stock_item', Mage::getModel('cataloginventory/stock_item')->loadByProduct($this));
336
+ }
337
+ $this->setData('qty', $this->getStockItem()->getQty());
338
+ }
339
+ return $this->getData('qty');
340
+ }
341
+
342
+ /**
343
+ * Return is_in_stock status
344
+ *
345
+ * is_in_stock is loaded by product collection join with inventory table
346
+ *
347
+ * @return int
348
+ */
349
+ public function getIsInStock() {
350
+ if (!$this->hasData('is_in_stock')) {
351
+ if ($this->hasData('stock_item')) {
352
+ $this->setData('stock_item', Mage::getModel('cataloginventory/stock_item')->loadByProduct($this));
353
+ }
354
+ $this->setData('is_in_stock', $this->getStockItem()->getIsInStock());
355
+ }
356
+ return $this->getData('is_in_stock');
357
+ }
358
+
359
+ /**
360
+ * Retrieve Product URL
361
+ *
362
+ * @param bool $useSid
363
+ * @return string
364
+ */
365
+ public function getProductUrl($useSid = null)
366
+ {
367
+ Mage::app()->setCurrentStore($this->getStoreId()); //to avoid ?___store param
368
+ if (method_exists($this, 'getUrlModel')) { //compatibility with older magento version where category#getUrlModel doesn't exist
369
+ $this->getUrlModel()->getUrlInstance()->setStore($this->getStoreId());
370
+ } else {
371
+ $this->getUrlInstance()->setStore($this->getStoreId());
372
+ }
373
+ $url = parent::getProductUrl(false);
374
+ $this->unsetData('url'); //unset data in order to force re-generation of product url on next store
375
+ return $url;
376
+ }
377
+
378
+ /**
379
+ * release memory
380
+ */
381
+ public function clearInstanceFull() {
382
+ Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
383
+ $this->clearInstance();
384
+ $this->stores= array();
385
+ $this->websites= array();
386
+ $this->currentStoreId=null;
387
+ $this->categoryTree=null;
388
+ }
389
+
390
+ /**
391
+ * Retrieve Store Id
392
+ *
393
+ * @return int
394
+ */
395
+ public function getStoreId()
396
+ {
397
+ return $this->currentStoreId;
398
+ }
399
+
400
+ /**
401
+ * set Store Id
402
+ *
403
+ * @param int
404
+ */
405
+ public function setStoreId($storeId)
406
+ {
407
+ parent::setStoreId($storeId);
408
+ $this->currentStoreId = $storeId;
409
+ return $this;
410
+ }
411
+
412
+ }
app/code/community/MDN/Antidot/Model/Export/Model/Product/Link.php ADDED
@@ -0,0 +1,40 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Model_Product_Link extends Mage_Catalog_Model_Product_Link
17
+ {
18
+
19
+ /**
20
+ * Retrieve linked product collection
21
+ *
22
+ * add join invenory table to load qty and is_in_stock
23
+ *
24
+ */
25
+ public function getProductCollection()
26
+ {
27
+ $onlyProductsWithStock = !(boolean)Mage::getStoreConfig('antidot/fields_product/in_stock_only');
28
+ $productsInStock = $onlyProductsWithStock ? ' AND is_in_stock = 1' : '';
29
+
30
+ $collection = Mage::getResourceModel('Antidot/export_product_link_product_collection')
31
+ ->joinTable('cataloginventory/stock_item',
32
+ 'product_id=entity_id', // warning : no spaces between = and entity_id , magento1.5 isn't robust enought
33
+ array('qty', 'is_in_stock'),
34
+ '{{table}}.stock_id = 1'.$productsInStock)
35
+ ->setLinkModel($this);
36
+
37
+ return $collection;
38
+ }
39
+
40
+ }
app/code/community/MDN/Antidot/Model/Export/Model/Product/Type/Configurable.php ADDED
@@ -0,0 +1,92 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Model_Product_Type_Configurable extends Mage_Catalog_Model_Product_Type_Configurable
17
+ {
18
+
19
+
20
+ /**
21
+ * Retrieve array of "subproducts"
22
+ *
23
+ * @param array
24
+ * @param Mage_Catalog_Model_Product $product
25
+ * @return array
26
+ */
27
+ public function getUsedProducts($requiredAttributeIds = null, $product = null)
28
+ {
29
+
30
+ Varien_Profiler::start('EXPORT_PRODUCT_CONFIGURABLE:'.__METHOD__);
31
+ if (!$this->getProduct($product)->hasData($this->_usedProducts)) {
32
+ if (is_null($requiredAttributeIds)
33
+ and is_null($this->getProduct($product)->getData($this->_configurableAttributes))) {
34
+ // If used products load before attributes, we will load attributes.
35
+ $this->getConfigurableAttributes($product);
36
+ // After attributes loading products loaded too.
37
+ Varien_Profiler::stop('EXPORT_PRODUCT_CONFIGURABLE:'.__METHOD__);
38
+ return $this->getProduct($product)->getData($this->_usedProducts);
39
+ }
40
+
41
+ $usedProducts = array();
42
+ $collection = $this->getUsedProductCollection($product)
43
+ //->addAttributeToSelect('*')
44
+ ->addFilterByRequiredOptions();
45
+
46
+ if (is_array($requiredAttributeIds)) {
47
+ foreach ($requiredAttributeIds as $attributeId) {
48
+ $attribute = $this->getAttributeById($attributeId, $product);
49
+ if (!is_null($attribute))
50
+ $collection->addAttributeToFilter($attribute->getAttributeCode(), array('notnull'=>1));
51
+ }
52
+ }
53
+
54
+ foreach ($collection as $item) {
55
+ $usedProducts[] = $item;
56
+ }
57
+
58
+ $this->getProduct($product)->setData($this->_usedProducts, $usedProducts);
59
+ }
60
+ Varien_Profiler::stop('EXPORT_PRODUCT_CONFIGURABLE:'.__METHOD__);
61
+ return $this->getProduct($product)->getData($this->_usedProducts);
62
+ }
63
+
64
+ /**
65
+ * Retrieve related products collection
66
+ *
67
+ * @param Mage_Catalog_Model_Product $product
68
+ * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Type_Configurable_Product_Collection
69
+ */
70
+ public function getUsedProductCollection($product = null)
71
+ {
72
+
73
+ /**
74
+ * list the products which are available in stock (according to configuration)
75
+ */
76
+ $onlyProductsWithStock = !(boolean)Mage::getStoreConfig('antidot/fields_product/in_stock_only');
77
+ $productsInStock = $onlyProductsWithStock ? ' AND is_in_stock = 1' : '';
78
+
79
+ $collection = Mage::getResourceModel('Antidot/export_product_type_configurable_product_collection')
80
+ ->joinTable('cataloginventory/stock_item',
81
+ 'product_id=entity_id', // warning : no spaces between = and entity_id , magento1.5 isn't robust enought
82
+ array('qty', 'is_in_stock'),
83
+ '{{table}}.stock_id = 1'.$productsInStock)
84
+ ->setProductFilter($this->getProduct($product));
85
+ if (!is_null($this->getStoreFilter($product))) {
86
+ $collection->addStoreFilter($this->getStoreFilter($product));
87
+ }
88
+
89
+ return $collection;
90
+ }
91
+
92
+ }
app/code/community/MDN/Antidot/Model/Export/Model/Product/Type/Grouped.php ADDED
@@ -0,0 +1,69 @@
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2015 Antidot (http://www.antidot.net)
13
+ * @author : Antidot devmagento@antidot.net
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Model_Product_Type_Grouped extends Mage_Catalog_Model_Product_Type_Grouped
17
+ {
18
+
19
+
20
+ /**
21
+ * Retrieve array of associated products
22
+ *
23
+ * @param Mage_Catalog_Model_Product $product
24
+ * @return array
25
+ */
26
+ public function getAssociatedProducts($product = null)
27
+ {
28
+ if (!$this->getProduct($product)->hasData($this->_keyAssociatedProducts)) {
29
+ $associatedProducts = array();
30
+
31
+ $this->setSaleableStatus($product);
32
+
33
+ $collection = $this->getAssociatedProductCollection($product)
34
+ /*->addAttributeToSelect('*') don't load every attribute in collection */
35
+ ->addFilterByRequiredOptions()
36
+ ->setPositionOrder()
37
+ ->addStoreFilter($this->getStoreFilter($product))
38
+ ->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
39
+
40
+ //FILTER only enabled product */
41
+ foreach ($collection as $item) {
42
+ $associatedProducts[] = $item;
43
+ }
44
+
45
+ $this->getProduct($product)->setData($this->_keyAssociatedProducts, $associatedProducts);
46
+ }
47
+ return $this->getProduct($product)->getData($this->_keyAssociatedProducts);
48
+ }
49
+
50
+
51
+ /**
52
+ * Retrieve collection of associated products
53
+ *
54
+ * @param Mage_Catalog_Model_Product $product
55
+ * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Link_Product_Collection
56
+ */
57
+ public function getAssociatedProductCollection($product = null)
58
+ {
59
+ $collection = $this->getProduct($product)->getLinkInstance()->useGroupedLinks()
60
+ ->getProductCollection()
61
+ //->setFlag('require_stock_items', true)
62
+ //->setFlag('product_children', true)
63
+ ->setIsStrongMode();
64
+ $collection->setProduct($this->getProduct($product));
65
+ return $collection;
66
+ }
67
+
68
+
69
+ }
app/code/community/MDN/Antidot/Model/Export/Product.php CHANGED
@@ -23,19 +23,18 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
23
const XSD = 'http://ref.antidot.net/store/latest/catalog.xsd';
24
25
/*
26
- * Maximum length for the facet values accepted by AFSStore
27
- * (actually 120)
28
*/
29
const FACET_MAX_LENGTH = 119;
30
31
protected $file;
32
33
protected $productGenerated = array();
34
35
- protected $categories = array();
36
-
37
- protected $enabledStores = null;
38
-
39
protected $onlyProductsWithStock;
40
41
protected $autoCompleteProducts;
@@ -58,7 +57,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
58
/**
59
* Write the xml file
60
*
61
- * @param array $context
62
* @param string $filename
63
* @param string $type Incremantal or full
64
* @return int nb items generated
@@ -66,7 +65,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
66
public function writeXml($context, $filename, $type)
67
{
68
69
- if (count($context['store_id']) == 0) {
70
return 0;
71
}
72
@@ -85,18 +84,19 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
85
$this->onlyProductsWithStock = !(boolean)Mage::getStoreConfig('antidot/fields_product/in_stock_only');
86
$this->autoCompleteProducts = Mage::getStoreConfig('antidot/suggest/enable') === 'Antidot/engine_antidot' ? 'on' : 'off';
87
88
$productsInStock = $this->onlyProductsWithStock ? ' AND is_in_stock = 1' : '';
89
- $collection = Mage::getModel('catalog/product')
90
->getCollection()
91
- ->setStoreId($context['store_id'][0]) //take the first store
92
- ->addWebsiteFilter($context['website_ids'])
93
->addAttributeToFilter('visibility', $this->productVisible)
94
->addAttributeToFilter('status', 1)
95
- ->joinField('qty',
96
- 'cataloginventory/stock_item',
97
- 'qty',
98
- 'product_id=entity_id', // warning : no spaces between = and entity_id , magento1.5 isn't robust enought
99
- '{{table}}.stock_id = 1'.$productsInStock)
100
;
101
102
if ($type === MDN_Antidot_Model_Observer::GENERATE_INC) {
@@ -113,6 +113,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
113
$collection->setPageSize($chunkSize);
114
115
$productsCount = $collection->getSize();
116
Mage::log('Products to export : '.$productsCount, null, 'antidot.log');
117
$chunkCount = $collection->getLastPageNumber();
118
@@ -127,14 +128,14 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
127
$this->initPropertyLabel();
128
$this->initProductsWithOperations();
129
$this->initFields('product');
130
- $this->initEnabledStores();
131
- $this->setFilename($filename);
132
133
$this->xml->push('catalog', array('xmlns' => "http://ref.antidot.net/store/afs#"));
134
$this->writeHeader($context);
135
$this->writePart($this->xml->flush());
136
137
- $this->lang = $context['lang'];
138
139
140
$lastExecutionTime = time();
@@ -148,20 +149,19 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
148
149
foreach($collection as $product) {
150
151
- $stores = $this->getProductStores($product, $context);
152
- if ($store = current($stores)) { //we take the "first" store of the current lang
153
- $product = Mage::getModel('catalog/product')->setStoreId($store->getId())->load(
154
- $product->getId()
155
- );
156
157
- $this->writeProduct($product, $stores);
158
159
- $product->clearInstance(); //memory flush
160
- $product = null;
161
- unset($product);
162
- $this->garbageCollection();
163
}
164
165
}
166
$this->writePart($this->xml->flush());
167
@@ -182,20 +182,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
182
183
Varien_Profiler::stop("export_product_writeXml");
184
185
- return $productsCount;
186
- }
187
-
188
- /**
189
- * List only enabled stores
190
- */
191
- protected function initEnabledStores()
192
- {
193
- $stores = Mage::getModel('core/store')->getCollection();
194
- foreach($stores as $store)
195
- {
196
- if ($store->getis_active())
197
- $this->enabledStores[] = $store->getId();
198
- }
199
}
200
201
/**
@@ -239,17 +226,18 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
239
/**
240
* Write the product
241
*
242
- * @param Product $product
243
- * @param Array $stores
244
*/
245
- protected function writeProduct($product, $stores)
246
{
247
Varien_Profiler::start("export_product_writeProduct");
248
249
//skip product if no websites
250
- if (count($stores) == 0)
251
- return;
252
253
/**
254
* MCNX-211 : check if grouped/configurables product has variant before begin export product
255
*/
@@ -257,8 +245,6 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
257
if ($product->getTypeID() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE
258
|| $product->getTypeID() == Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
259
260
- Mage::app()->setCurrentStore($product->getStoreId()); //Set store id in order to exclude not in stock products
261
-
262
switch ($product->getTypeID()) {
263
case Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE:
264
$variantProductsColl = $product->getTypeInstance(true)->getUsedProducts(null, $product);
@@ -270,8 +256,14 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
270
271
foreach ($variantProductsColl as $variantProduct) {
272
//Do not include product if status is not enabled
273
- if ($variantProduct->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_ENABLED && $variantProduct->isSalable() ) {
274
- $variantProducts[] = $variantProduct;
275
}
276
}
277
@@ -280,35 +272,24 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
280
return;
281
}
282
}
283
284
$this->xml->push('product', array('id' => $product->getId(), 'xml:lang' => $this->lang, 'autocomplete' => $this->autoCompleteProducts));
285
286
$this->xml->push('websites');
287
- $websites = array();
288
- $rootCategoriesIds = array(); //the root categories of the stores of this context, used for exporting only the categories within theses roots
289
- foreach($stores as $store) {
290
- $rootCategoriesIds[] = $store->getRootCategoryId();
291
- $website = $this->getWebSiteByStore($store);
292
- if (!array_key_exists($website->getId(), $websites))
293
- {
294
- $websites[$website->getId()] = $website->getName();
295
- $this->xml->element('website', $website->getName(), array('id' => $website->getId()));
296
- }
297
}
298
$this->xml->pop();
299
300
//$this->xml->writeElement('created_at', $product->getCreated_at()); AFM-83
301
//$this->xml->writeElement('last_updated_at', $product->getUpdated_at()); AFM-92
302
303
- $this->xml->element('name', $this->xml->encloseCData($this->utf8CharacterValidation($this->getField($product, 'name'))));
304
- if($shortName = $this->getField($product, 'short_name')) {
305
- $this->xml->element('short_name', $this->xml->encloseCData(mb_substr($shortName, 0, 45, 'UTF-8')), array('autocomplete' => 'off'));
306
- }
307
-
308
- if ($keywords = $this->getField($product, 'keywords')) {
309
- $this->xml->element('keywords', $this->xml->encloseCData($keywords));
310
- }
311
- $this->writeClassification($product, $rootCategoriesIds);
312
$this->writeBrand($product);
313
$this->writeMaterials($product);
314
$this->writeColors($product);
@@ -317,9 +298,10 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
317
$this->writeGenders($product);
318
$this->writeMisc($product);
319
320
- $this->writeVariants($product, $variantProducts, $stores);
321
322
$this->xml->pop();
323
324
Varien_Profiler::stop("export_product_writeProduct");
325
@@ -328,87 +310,83 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
328
/**
329
* Write the store's informations
330
*
331
- * @param Product $product
332
- * @param array $stores
333
*/
334
- protected function writeStore($product, $stores, $variantProduct)
335
{
336
Varien_Profiler::start("export_product_writeStore");
337
338
$this->xml->push('stores');
339
340
/* Qty is the same for all stores, better compute it outside the loop: */
341
- $qty = Mage::getModel('cataloginventory/stock_item')->loadByProduct($variantProduct)->getQty();
342
$qty = ($qty > 0 ? $qty : 0);
343
344
- foreach($stores as $store) {
345
Mage::app()->setCurrentStore($store->getId());
346
-
347
/*
348
* reload the $variantProduct if this is a real variant or if we are on a different store
349
*/
350
- $reloaded = false;
351
- if ($product->getId() != $variantProduct->getId() || $store->getId() != $product->getStoreId()) {
352
- $reloadedVariantProduct = Mage::getModel('catalog/product')->setStoreId($store->getId())->load(
353
- $variantProduct->getId()
354
- );
355
- $reloaded = true;
356
- } else {
357
- $reloadedVariantProduct = $variantProduct;
358
}
359
360
$this->xml->push('store', array('id' => $store->getId(), 'name' => $store->getName()));
361
- $storeContext['currency'] = $store->getCurrentCurrencyCode();
362
- $storeContext['country'] = $this->getStoreLang($store->getId());
363
364
- $operations = $this->getOperations($product, $store);
365
- $this->writePrices($reloadedVariantProduct, $product, $storeContext, $store);
366
- $this->writeMarketing($reloadedVariantProduct, $operations);
367
368
- $isAvailable = $reloadedVariantProduct->isSalable() || (in_array($reloadedVariantProduct->getTypeId(), $this->productMultiple) && $product->isInStock());
369
$this->xml->element('is_available', (int)$isAvailable);
370
371
$this->xml->element('stock', (int)$qty);
372
373
- $this->writeProductUrl($reloadedVariantProduct);
374
- $this->writeImageUrl($reloadedVariantProduct);
375
376
$this->xml->pop();
377
378
- if ($reloaded) {
379
- $reloadedVariantProduct->clearInstance(); //memory flush
380
- $reloadedVariantProduct = null;
381
- unset($reloadedVariantProduct);
382
- $this->garbageCollection();
383
- }
384
-
385
386
}
387
$this->xml->pop();
388
389
Varien_Profiler::stop("export_product_writeStore");
390
391
}
392
-
393
/**
394
- * Get product stores
395
- *
396
* @param Product $product
397
- * @param array $context
398
*/
399
- protected function getProductStores($product, $context)
400
{
401
- $stores = array();
402
-
403
- $storeIds = array_intersect($product->getStoreIds(), $context['store_id']);
404
- foreach($storeIds as $storeId) {
405
- if (in_array($storeId, $this->enabledStores))
406
- $stores[] = $context['stores'][$storeId];
407
}
408
409
- return $stores;
410
}
411
-
412
/**
413
* Write the product descriptions
414
*
@@ -476,7 +454,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
476
if(!empty($this->fields['identifier'])) {
477
foreach($this->fields['identifier'] as $identifier) {
478
if ($value = $this->getField($product, $identifier)) {
479
- $identifiers[$identifier] = mb_substr($value, 0, 40, 'UTF-8');
480
}
481
}
482
}
@@ -512,10 +490,10 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
512
if ($manufacturer = $this->getField($product, 'manufacturer')) {
513
if(!empty($manufacturer)) {
514
$field = empty($this->fields['manufacturer']) ? 'manufacturer' : $this->fields['manufacturer'];
515
- $brand = mb_substr($product->getAttributeText($field), 0, 40, 'UTF-8');
516
$brandUrl = Mage::helper('catalogsearch')->getResultUrl($brand, $product->getStoreId());
517
$brandUrl = parse_url($brandUrl, PHP_URL_PATH).'?'.parse_url($brandUrl, PHP_URL_QUERY);
518
- $brandUrl = str_replace('antidotExport.php', 'index.php', $brandUrl);
519
if(!empty($brand)) {
520
$this->xml->element('brand', $this->xml->encloseCData($brand), array('id' => $manufacturer, 'url' => $brandUrl));
521
}
@@ -529,13 +507,13 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
529
/**
530
* Write the product urls
531
*
532
- * @param Product $product
533
* @param string $urlImg
534
*/
535
protected function writeProductUrl($product)
536
{
537
Varien_Profiler::start("export_product_writeProductUrl");
538
- $this->xml->element('url', $this->xml->encloseCData($product->getProductUrl(false)));
539
Varien_Profiler::stop("export_product_writeProductUrl");
540
541
}
@@ -560,7 +538,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
560
$this->xml->element('url_thumbnail', $this->xml->encloseCData(Mage::getModel('catalog/product_media_config')->getMediaUrl($product->getThumbnail())));
561
}
562
} catch(Exception $e) {
563
- Mage::log("writeImageUrl Exception : " . $e->getMessage(), Zend_Log::ERR, 'antidot.log');
564
}
565
566
try {
@@ -568,7 +546,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
568
$this->xml->element('url_image', $this->xml->encloseCData(Mage::getModel('catalog/product_media_config')->getMediaUrl($product->getImage())));
569
}
570
} catch(Exception $e) {
571
- Mage::log("writeImageUrl Exception : " .$e->getMessage(), Zend_Log::ERR, 'antidot.log');
572
}
573
574
Varien_Profiler::stop("export_product_writeImageUrl");
@@ -578,34 +556,18 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
578
/**
579
* Write the product classification
580
*
581
- * @param Product $product
582
*/
583
- protected function writeClassification($product, $rootCategoriesIds)
584
{
585
Varien_Profiler::start("export_product_writeClassification");
586
- $categories = $this->getProductCategories($product, $rootCategoriesIds);
587
- $exportCategory = false;
588
- foreach($categories as $category) {
589
- $exportCategory = true;
590
- $path = array($category);
591
- while ($category = $this->getCategoryParent($category, $rootCategoriesIds)) {
592
- $path[] = $category;
593
- }
594
- //if one of the ancestor of the category is inactive, rise flag to not export the category
595
- foreach ($path as $cat) {
596
- if (!$cat->getName() || !$cat->getIsActive()) {
597
- $exportCategory = false;
598
- }
599
- }
600
- }
601
- if ($exportCategory) {
602
$this->xml->push('classification');
603
- foreach (array_reverse($path) as $cat) {
604
- $this->writeCategory($cat);
605
- }
606
- foreach ($path as $cat) {
607
- $this->xml->pop();
608
- }
609
$this->xml->pop();
610
}
611
Varien_Profiler::stop("export_product_writeClassification");
@@ -616,108 +578,24 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
616
* get parent category node
617
*
618
* @param $category
619
- * @param array $rootCategoriesIds
620
- */
621
- protected function getCategoryParent($category, $rootCategoriesIds)
622
- {
623
-
624
- //if the parent is the root node, don't export it
625
- if ($category->getParentId() == 1 || $category->getParentId() == 0 || in_array($category->getParentId(), $rootCategoriesIds)) {
626
- return false;
627
- }
628
-
629
- return $this->getCategoryById($category->getStoreId(), $category->getParentId());
630
-
631
- }
632
-
633
-
634
- /**
635
- * Write category node
636
- *
637
- * @param $category
638
*/
639
protected function writeCategory($category)
640
{
641
642
- $categoryUrl = $this->getUri($category->getUrl());
643
- $categoryUrl = str_replace('antidotExport.php', 'index.php', $categoryUrl);
644
- $attributes = array('id' => $category->getId(), 'label' => substr($category->getName(), 0, self::FACET_MAX_LENGTH), 'url' => $categoryUrl);
645
if ($category->getImage()) {
646
$attributes['img'] = $category->getImage();
647
}
648
$this->xml->push('category', $attributes);
649
650
- }
651
-
652
- /**
653
- * Get category by id
654
- *
655
- * @param int $categoryId
656
- * @return Category
657
- */
658
- protected function getCategoryById($storeId, $categoryId)
659
- {
660
- if(!isset($this->categories[$storeId][$categoryId])) {
661
- $category = Mage::getModel('catalog/category')->setStoreId($storeId)->load($categoryId);
662
- if (method_exists($category, 'getUrlModel')) { //compatibility with older magento version where category#getUrlModel doesn't exist
663
- $category->getUrlModel()->getUrlInstance()->setStore($storeId);
664
- } else {
665
- $category->getUrlInstance()->setStore($storeId);
666
- }
667
- $this->categories[$storeId][$categoryId] = $category;
668
- }
669
-
670
- return $this->categories[$storeId][$categoryId];
671
- }
672
-
673
- /**
674
- * Return the top level category
675
- *
676
- * @param Product $product
677
- * @return array
678
- */
679
- protected function getProductCategories($product, $rootCategoriesIds)
680
- {
681
- $categories = $product->getCategoryCollection();
682
- $categories->setStoreId($product->getStoreId());
683
- $categories->addAttributeToSelect('name');
684
- $categories->addAttributeToSelect('image');
685
- $categories->addAttributeToSelect('url_key');
686
- $categories->addAttributeToFilter('name', array('neq' => ''));
687
-
688
- //Add a filter on the path in order to export only the categories whose root category is used on the exported stores
689
- $rootCategoryCondition = array();
690
- foreach ($rootCategoriesIds as $rootCategoryId) {
691
- $rootCategoryCondition[] = array('like' => '1/'.$rootCategoryId.'/%');
692
}
693
- $categories->addAttributeToFilter('path', $rootCategoryCondition);
694
- $categories->addAttributeToFilter('is_active', 1); //MCNX-236
695
696
- //Mage::log($categories->getSelect()->__toString(), null, 'antidot.log');
697
-
698
- $productCategories = array();
699
- foreach($categories as $category) {
700
- //Force the store on the url in order to generate the store code in url if it is configured in system > config
701
- $category->setStoreId($product->getStoreId());
702
- if (method_exists($category, 'getUrlModel')) { //compatibility with older magento version where category#getUrlModel doesn't exist
703
- $category->getUrlModel()->getUrlInstance()->setStore($product->getStoreId());
704
- } else {
705
- $category->getUrlInstance()->setStore($product->getStoreId());
706
- }
707
- if (($category->getparent_id() == 0) || ($category->getparent_id() == 1))
708
- continue;
709
- $productCategories[$category->getId()] = $category;
710
- $this->categories[$product->getStoreId()][$category->getId()] = $category; //cache categories already loaded here to avoid full load in getCategoryById()
711
- $parentCategory[] = $category->getParentId();
712
- }
713
-
714
- foreach($productCategories as $category) {
715
- if(in_array($category->getParentId(), $parentCategory)) {
716
- unset($productCategories[$category->getParentId()]);
717
- }
718
- }
719
720
- return $productCategories;
721
}
722
723
/**
@@ -897,7 +775,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
897
*
898
* @param Product $product
899
*/
900
- protected function writePrices($product, $parentProduct, $context, $store)
901
{
902
Varien_Profiler::start("export_product_writePrices");
903
@@ -929,10 +807,12 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
929
$price = Mage::helper('tax')->getPrice($product, $prices['final_price'] + $weeeAmount, true);
930
}
931
932
- $price = Mage::helper('directory')->currencyConvert($price, Mage::app()->getStore()->getCurrentCurrencyCode(), $store->getCurrentCurrencyCode());
933
-
934
$this->xml->push('prices');
935
- $attributes = array('currency' => $context['currency'], 'type' => 'PRICE_FINAL', 'vat_included' => 'true', 'country' => strtoupper($context['country']));
936
if (isset($priceCut))
937
{
938
$off = $this->computePriceOff($priceCut, $price);
@@ -950,7 +830,7 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
950
$this->xml->element(
951
'price',
952
Mage::helper('Antidot')->round($priceCut),
953
- array('currency' => $context['currency'], 'type' => 'PRICE_CUT', 'vat_included' => 'true', 'country' => strtoupper($context['country']))
954
);
955
956
}
@@ -1135,19 +1015,19 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
1135
* @param array $variantProducts
1136
* @param array $stores
1137
*/
1138
- protected function writeVariants($product, $variantProducts, $stores)
1139
{
1140
Varien_Profiler::start("export_product_writeVariants");
1141
$this->xml->push('variants');
1142
1143
$this->xml->push('variant', array('id' => 'fake'));
1144
- $this->writeVariant($product, $product, $stores);
1145
$this->xml->pop();
1146
1147
foreach($variantProducts as $variantProduct) {
1148
1149
$this->xml->push('variant', array('id' => $variantProduct->getId()));
1150
- $this->writeVariant($variantProduct, $product, $stores);
1151
$this->xml->pop();
1152
}
1153
@@ -1160,23 +1040,23 @@ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
1160
* Write variant
1161
*
1162
* @param Product $variantProduct
1163
- * @param Product $product
1164
* @param array $stores
1165
*/
1166
- protected function writeVariant($variantProduct, $product, $stores)
1167
{
1168
Varien_Profiler::start("export_product_writeVariant");
1169
1170
$this->xml->element('name', $this->xml->encloseCData($this->utf8CharacterValidation($variantProduct->getName())));
1171
$this->writeDescriptions($variantProduct);
1172
- $this->writeStore($product, $stores, $variantProduct);
1173
$this->writeIdentifiers($variantProduct);
1174
- $this->writeProperties($variantProduct, ($variantProduct->getId()==$product->getId()));
1175
$this->writeMaterials($variantProduct);
1176
$this->writeColors($variantProduct);
1177
$this->writeModels($variantProduct);
1178
$this->writeSizes($variantProduct);
1179
- $this->writeGenders($product);
1180
$this->writeMisc($variantProduct);
1181
1182
Varien_Profiler::stop("export_product_writeVariant");
23
const XSD = 'http://ref.antidot.net/store/latest/catalog.xsd';
24
25
/*
26
+ * Maximum length for the values accepted by AFSStore
27
*/
28
const FACET_MAX_LENGTH = 119;
29
+ const NAME_MAX_LENGTH = 255;
30
+ const SHORT_NAME_MAX_LENGTH = 45;
31
+ const IDENTIFIER_MAX_LENGTH = 40;
32
+ const BRAND_MAX_LENGTH = 40;
33
34
protected $file;
35
36
protected $productGenerated = array();
37
38
protected $onlyProductsWithStock;
39
40
protected $autoCompleteProducts;
57
/**
58
* Write the xml file
59
*
60
+ * @param MDN_Antidot_Model_Export_Context $context
61
* @param string $filename
62
* @param string $type Incremantal or full
63
* @return int nb items generated
65
public function writeXml($context, $filename, $type)
66
{
67
68
+ if (count($context->getStoreIds()) == 0) {
69
return 0;
70
}
71
84
$this->onlyProductsWithStock = !(boolean)Mage::getStoreConfig('antidot/fields_product/in_stock_only');
85
$this->autoCompleteProducts = Mage::getStoreConfig('antidot/suggest/enable') === 'Antidot/engine_antidot' ? 'on' : 'off';
86
87
+ /**
88
+ * This first collect list the products of the exported websites which are available in stock (according to configuration)
89
+ */
90
$productsInStock = $this->onlyProductsWithStock ? ' AND is_in_stock = 1' : '';
91
+ $collection = Mage::getModel('Antidot/export_model_product')
92
->getCollection()
93
+ ->addWebsiteFilter($context->getWebsiteIds())
94
->addAttributeToFilter('visibility', $this->productVisible)
95
->addAttributeToFilter('status', 1)
96
+ ->joinTable('cataloginventory/stock_item',
97
+ 'product_id=entity_id', // warning : no spaces between = and entity_id , magento1.5 isn't robust enought
98
+ array('qty', 'is_in_stock'),
99
+ '{{table}}.stock_id = 1'.$productsInStock)
100
;
101
102
if ($type === MDN_Antidot_Model_Observer::GENERATE_INC) {
113
$collection->setPageSize($chunkSize);
114
115
$productsCount = $collection->getSize();
116
+ $productsExported = 0;
117
Mage::log('Products to export : '.$productsCount, null, 'antidot.log');
118
$chunkCount = $collection->getLastPageNumber();
119
128
$this->initPropertyLabel();
129
$this->initProductsWithOperations();
130
$this->initFields('product');
131
+ $context->addAttributeToLoad($this->fields);
132
+ $this->setFilename($filename);
133
134
$this->xml->push('catalog', array('xmlns' => "http://ref.antidot.net/store/afs#"));
135
$this->writeHeader($context);
136
$this->writePart($this->xml->flush());
137
138
+ $this->lang = $context->getLang();
139
140
141
$lastExecutionTime = time();
149
150
foreach($collection as $product) {
151
152
+ /** @var $product MDN_Antidot_Model_Export_Model_Product */
153
+ if ($product->setContext($context)) {
154
155
+ $product->loadNeededAttributes();
156
+ $productsExported += $this->writeProduct($product);
157
158
}
159
160
+ $product->clearInstanceFull(); //memory flush
161
+ $product = null;
162
+ unset($product);
163
+ $this->garbageCollection();
164
+
165
}
166
$this->writePart($this->xml->flush());
167
182
183
Varien_Profiler::stop("export_product_writeXml");
184
185
+ return $productsExported;
186
}
187
188
/**
226
/**
227
* Write the product
228
*
229
+ * @param MDN_Antidot_Model_Export_Model_Product $product
230
+ *
231
*/
232
+ protected function writeProduct($product)
233
{
234
Varien_Profiler::start("export_product_writeProduct");
235
236
//skip product if no websites
237
+ if (count($product->getStores()) == 0)
238
+ return 0;
239
240
+ Varien_Profiler::start("export_product_getVariantsProduct");
241
/**
242
* MCNX-211 : check if grouped/configurables product has variant before begin export product
243
*/
245
if ($product->getTypeID() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE
246
|| $product->getTypeID() == Mage_Catalog_Model_Product_Type::TYPE_GROUPED) {
247
248
switch ($product->getTypeID()) {
249
case Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE:
250
$variantProductsColl = $product->getTypeInstance(true)->getUsedProducts(null, $product);
256
257
foreach ($variantProductsColl as $variantProduct) {
258
//Do not include product if status is not enabled
259
+ if ($variantProduct->getStatus() == 1) {
260
+
261
+ /** @var $product MDN_Antidot_Model_Export_Model_Product */
262
+ if ($variantProduct->setContext($product->getContext(), true)) {
263
+ $variantProduct->loadNeededAttributes();
264
+ $variantProducts[] = $variantProduct;
265
+ }
266
+
267
}
268
}
269
272
return;
273
}
274
}
275
+ Varien_Profiler::stop("export_product_getVariantsProduct");
276
277
+ Varien_Profiler::start("export_writeProductWebsites");
278
$this->xml->push('product', array('id' => $product->getId(), 'xml:lang' => $this->lang, 'autocomplete' => $this->autoCompleteProducts));
279
280
$this->xml->push('websites');
281
+ foreach($product->getWebsites() as $website) {
282
+ $this->xml->element('website', $website->getName(), array('id' => $website->getId()));
283
}
284
$this->xml->pop();
285
286
//$this->xml->writeElement('created_at', $product->getCreated_at()); AFM-83
287
//$this->xml->writeElement('last_updated_at', $product->getUpdated_at()); AFM-92
288
289
+ $this->writeName($product);
290
+ $this->writeKeywords($product);
291
+ Varien_Profiler::stop("export_writeProductWebsites");
292
+ $this->writeClassification($product);
293
$this->writeBrand($product);
294
$this->writeMaterials($product);
295
$this->writeColors($product);
298
$this->writeGenders($product);
299
$this->writeMisc($product);
300
301
+ $this->writeVariants($product, $variantProducts);
302
303
$this->xml->pop();
304
+ return 1;
305
306
Varien_Profiler::stop("export_product_writeProduct");
307
310
/**
311
* Write the store's informations
312
*
313
+ * @param Product $parentProduct
314
+ * @param Product $variantProduct
315
*/
316
+ protected function writeStore($parentProduct, $variantProduct)
317
{
318
Varien_Profiler::start("export_product_writeStore");
319
320
$this->xml->push('stores');
321
322
/* Qty is the same for all stores, better compute it outside the loop: */
323
+ $qty = $variantProduct->getQty();
324
$qty = ($qty > 0 ? $qty : 0);
325
326
+ foreach($parentProduct->getStores() as $store) {
327
Mage::app()->setCurrentStore($store->getId());
328
/*
329
* reload the $variantProduct if this is a real variant or if we are on a different store
330
*/
331
+ if ($store->getId() != $parentProduct->getStoreId()) {
332
+ $parentProduct->setStoreId($store->getId());
333
+ $variantProduct->setStoreId($store->getId());
334
+ //$parentProduct->loadNeededAttributes(true);
335
+ $variantProduct->loadNeededAttributes(true);
336
}
337
338
$this->xml->push('store', array('id' => $store->getId(), 'name' => $store->getName()));
339
340
+ $operations = $this->getOperations($parentProduct, $store);
341
+ $this->writePrices($variantProduct, $parentProduct, $store);
342
+ $this->writeMarketing($variantProduct, $operations);
343
344
+ $isAvailable = ($variantProduct->isInStock()/* status*/ && $variantProduct->getIsInStock()/* stock status */)
345
+ || (in_array($variantProduct->getTypeId(), $this->productMultiple) && $parentProduct->isInStock());
346
$this->xml->element('is_available', (int)$isAvailable);
347
348
$this->xml->element('stock', (int)$qty);
349
350
+ $this->writeProductUrl($variantProduct);
351
+ $this->writeImageUrl($variantProduct);
352
353
$this->xml->pop();
354
355
356
}
357
+
358
$this->xml->pop();
359
360
Varien_Profiler::stop("export_product_writeStore");
361
362
}
363
+
364
/**
365
+ * Write the product name
366
+ *
367
* @param Product $product
368
*/
369
+ protected function writeName($product)
370
{
371
+ $name = $this->utf8CharacterValidation($this->getField($product, 'name'));
372
+ $this->xml->element('name', $this->xml->encloseCData(mb_substr($name, 0, self::NAME_MAX_LENGTH, 'UTF-8')));
373
+ if($shortName = $this->getField($product, 'short_name')) {
374
+ $this->xml->element('short_name', $this->xml->encloseCData(mb_substr($shortName, 0, self::SHORT_NAME_MAX_LENGTH, 'UTF-8')), array('autocomplete' => 'off'));
375
}
376
+ }
377
378
+ /**
379
+ * Write the product keywords
380
+ *
381
+ * @param Product $product
382
+ */
383
+ protected function writeKeywords($product)
384
+ {
385
+ if ($keywords = $this->getField($product, 'keywords')) {
386
+ $this->xml->element('keywords', $this->xml->encloseCData($keywords));
387
+ }
388
}
389
+
390
/**
391
* Write the product descriptions
392
*
454
if(!empty($this->fields['identifier'])) {
455
foreach($this->fields['identifier'] as $identifier) {
456
if ($value = $this->getField($product, $identifier)) {
457
+ $identifiers[$identifier] = mb_substr($value, 0, self::IDENTIFIER_MAX_LENGTH, 'UTF-8');
458
}
459
}
460
}
490
if ($manufacturer = $this->getField($product, 'manufacturer')) {
491
if(!empty($manufacturer)) {
492
$field = empty($this->fields['manufacturer']) ? 'manufacturer' : $this->fields['manufacturer'];
493
+ $brand = mb_substr($product->getAttributeText($field), 0, self::BRAND_MAX_LENGTH, 'UTF-8');
494
$brandUrl = Mage::helper('catalogsearch')->getResultUrl($brand, $product->getStoreId());
495
$brandUrl = parse_url($brandUrl, PHP_URL_PATH).'?'.parse_url($brandUrl, PHP_URL_QUERY);
496
+ $brandUrl = $this->getExactUrl($brandUrl);
497
if(!empty($brand)) {
498
$this->xml->element('brand', $this->xml->encloseCData($brand), array('id' => $manufacturer, 'url' => $brandUrl));
499
}
507
/**
508
* Write the product urls
509
*
510
+ * @param MDN_Antidot_Model_Export_Model_Product $product
511
* @param string $urlImg
512
*/
513
protected function writeProductUrl($product)
514
{
515
Varien_Profiler::start("export_product_writeProductUrl");
516
+ $this->xml->element('url', $this->xml->encloseCData($this->getExactUrl($product->getProductUrl(false), false)));
517
Varien_Profiler::stop("export_product_writeProductUrl");
518
519
}
538
$this->xml->element('url_thumbnail', $this->xml->encloseCData(Mage::getModel('catalog/product_media_config')->getMediaUrl($product->getThumbnail())));
539
}
540
} catch(Exception $e) {
541
+ //Mage::log("writeImageUrl Exception : " . $e->getMessage(), Zend_Log::ERR, 'antidot.log');
542
}
543
544
try {
546
$this->xml->element('url_image', $this->xml->encloseCData(Mage::getModel('catalog/product_media_config')->getMediaUrl($product->getImage())));
547
}
548
} catch(Exception $e) {
549
+ //Mage::log("writeImageUrl Exception : " .$e->getMessage(), Zend_Log::ERR, 'antidot.log');
550
}
551
552
Varien_Profiler::stop("export_product_writeImageUrl");
556
/**
557
* Write the product classification
558
*
559
+ * @param MDN_Antidot_Model_Export_Model_Product $product
560
*/
561
+ protected function writeClassification($product)
562
{
563
Varien_Profiler::start("export_product_writeClassification");
564
+ $tree = $product->getCategoryTree();
565
+ $rootNode = $tree->getNodeById(1);
566
+ if($rootNode->hasChildren()) {
567
$this->xml->push('classification');
568
+ foreach ($rootNode->getChildren() as $node) {
569
+ $this->writeCategory($node);
570
+ }
571
$this->xml->pop();
572
}
573
Varien_Profiler::stop("export_product_writeClassification");
578
* get parent category node
579
*
580
* @param $category
581
*/
582
protected function writeCategory($category)
583
{
584
585
+ //antidot_url is set in the Context initCategoryTree
586
+ $attributes = array('id' => $category->getId(), 'label' => substr($category->getName(), 0, self::FACET_MAX_LENGTH), 'url' => $this->getExactUrl($category->getUrl()));
587
if ($category->getImage()) {