SearchSpring_Manager - Version 2.0.0

Version Notes

Support for multi-store installations
Fixed categories from other stores being exported in feed
Added support for registering store/installation specific URLs with the SearchSpring account feed settings.

Download this release

Release Info

Developer SearchSpring Development Team
Extension SearchSpring_Manager
Version 2.0.0
Comparing to
See all releases


Code changes from version 1.10.2 to 2.0.0

Files changed (46) hide show
  1. app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Hint.php +5 -18
  2. app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Setup.php +144 -6
  3. app/code/community/SearchSpring/Manager/Entity/Credentials.php +20 -0
  4. app/code/community/SearchSpring/Manager/Entity/IndexingRequestBody.php +16 -11
  5. app/code/community/SearchSpring/Manager/Entity/ProductDeletionToken.php +68 -0
  6. app/code/community/SearchSpring/Manager/Entity/RequestCredentials.php +73 -0
  7. app/code/community/SearchSpring/Manager/Entity/SearchRequestBody.php +2 -6
  8. app/code/community/SearchSpring/Manager/Factory/ApiFactory.php +59 -33
  9. app/code/community/SearchSpring/Manager/Factory/GeneratorFactory.php +2 -1
  10. app/code/community/SearchSpring/Manager/Factory/LiveIndexerFactory.php +37 -0
  11. app/code/community/SearchSpring/Manager/Factory/SearchRequestBodyFactory.php +8 -1
  12. app/code/community/SearchSpring/Manager/Helper/Data.php +175 -166
  13. app/code/community/SearchSpring/Manager/Helper/Http.php +7 -8
  14. app/code/community/SearchSpring/Manager/Helper/Product.php +4 -0
  15. app/code/community/SearchSpring/Manager/Helper/Webservice.php +78 -27
  16. app/code/community/SearchSpring/Manager/Model/Api2/Auth/Adapter.php +2 -2
  17. app/code/community/SearchSpring/Manager/Model/Api2/Indexing/Rest/Admin/V1.php +2 -16
  18. app/code/community/SearchSpring/Manager/Model/Config.php +285 -0
  19. app/code/community/SearchSpring/Manager/Model/Layer.php +1 -1
  20. app/code/community/SearchSpring/Manager/Model/Observer.php +26 -0
  21. app/code/community/SearchSpring/Manager/Model/Observer/CategorySaveObserver.php +84 -106
  22. app/code/community/SearchSpring/Manager/Model/Observer/ConfigObserver.php +32 -30
  23. app/code/community/SearchSpring/Manager/Model/Observer/LiveIndexer.php +0 -64
  24. app/code/community/SearchSpring/Manager/Model/Observer/ProductSaveObserver.php +67 -121
  25. app/code/community/SearchSpring/Manager/Model/Source.php +1 -1
  26. app/code/community/SearchSpring/Manager/Operation/Product.php +2 -2
  27. app/code/community/SearchSpring/Manager/Operation/Product/SetCategories.php +40 -2
  28. app/code/community/SearchSpring/Manager/Operation/Product/SetCoreFields.php +0 -2
  29. app/code/community/SearchSpring/Manager/Operation/Product/SetReport.php +1 -1
  30. app/code/community/SearchSpring/Manager/Operation/ProductOperation.php +2 -2
  31. app/code/community/SearchSpring/Manager/Provider/ProductCollection/FeedProvider.php +6 -1
  32. app/code/community/SearchSpring/Manager/Provider/ProductCollection/ProductProvider.php +6 -0
  33. app/code/community/SearchSpring/Manager/Service/LiveIndexer.php +551 -0
  34. app/code/community/SearchSpring/Manager/Service/SearchSpring/ApiAdapter.php +1 -1
  35. app/code/community/SearchSpring/Manager/Service/SearchSpring/IndexingApiAdapter.php +43 -10
  36. app/code/community/SearchSpring/Manager/Transformer/ProductCollectionToRecordCollectionTransformer.php +4 -4
  37. app/code/community/SearchSpring/Manager/Validator/ProductValidator.php +17 -2
  38. app/code/community/SearchSpring/Manager/Writer/Product/Params/FileWriterParams.php +7 -1
  39. app/code/community/SearchSpring/Manager/controllers/GenerateController.php +11 -13
  40. app/code/community/SearchSpring/Manager/etc/config.xml +5 -4
  41. app/code/community/SearchSpring/Manager/etc/system.xml +20 -22
  42. app/code/community/SearchSpring/Manager/sql/searchspring_manager/mysql4-data-upgrade-1.10.2-2.0.0.php +85 -0
  43. app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/setup.phtml +7 -7
  44. app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/setup_scope.phtml +59 -0
  45. js/searchspring/setup.js +13 -5
  46. package.xml +3 -1
app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Hint.php CHANGED
@@ -13,32 +13,19 @@
13
  * @author James Bathgate <james@b7interactive.com>
14
  */
15
 
16
- class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Hint extends Mage_Adminhtml_Block_Abstract implements Varien_Data_Form_Element_Renderer_Interface
17
  {
18
- protected $_template = 'searchspring/manager/system/config/fieldset/hint.phtml';
19
 
20
  public function __construct() {
21
  parent::__construct();
22
- return $this;
23
- }
24
-
25
- /**
26
- * Render fieldset html
27
- *
28
- * @param Varien_Data_Form_Element_Abstract $element
29
- * @return string
30
- */
31
- public function render(Varien_Data_Form_Element_Abstract $element)
32
- {
33
- return $this->toHtml();
34
- }
35
 
 
36
 
37
- public function isSetup() {
38
- $hlp = Mage::helper('searchspring_manager');
39
- return $hlp->registerMagentoAPIAuthenticationWithSearchSpring(true);
40
  }
41
 
 
42
  public function getVersion() {
43
  return (string) Mage::helper('searchspring_manager')->getVersion();
44
  }
13
  * @author James Bathgate <james@b7interactive.com>
14
  */
15
 
16
+ class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Hint extends SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Setup
17
  {
18
+ const TEMPLATE_HINT = 'searchspring/manager/system/config/fieldset/hint.phtml';
19
 
20
  public function __construct() {
21
  parent::__construct();
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ $this->setTemplate(self::TEMPLATE_HINT);
24
 
25
+ return $this;
 
 
26
  }
27
 
28
+
29
  public function getVersion() {
30
  return (string) Mage::helper('searchspring_manager')->getVersion();
31
  }
app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Setup.php CHANGED
@@ -11,19 +11,42 @@
11
  * Display connection setup helper
12
  *
13
  * @author James Bathgate <james@b7interactive.com>
 
14
  */
15
 
16
  class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Setup extends Mage_Adminhtml_Block_Abstract implements Varien_Data_Form_Element_Renderer_Interface
17
  {
18
- protected $_template = 'searchspring/manager/system/config/fieldset/setup.phtml';
 
19
 
20
- public function __construct() {
21
- parent::__construct();
22
 
23
- return $this;
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
 
 
 
 
 
 
26
 
 
 
 
 
 
 
27
 
28
  /**
29
  * Render fieldset html
@@ -45,9 +68,124 @@ class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Setup extends
45
  return $this;
46
  }
47
 
48
- public function isSetup() {
 
 
 
 
 
 
 
 
 
 
49
  $hlp = Mage::helper('searchspring_manager');
50
- return $hlp->registerMagentoAPIAuthenticationWithSearchSpring(true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  }
11
  * Display connection setup helper
12
  *
13
  * @author James Bathgate <james@b7interactive.com>
14
+ * @author Jake Shelby <jake@b7interactive.com>
15
  */
16
 
17
  class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Setup extends Mage_Adminhtml_Block_Abstract implements Varien_Data_Form_Element_Renderer_Interface
18
  {
19
+ const TEMPLATE_SETUP = 'searchspring/manager/system/config/fieldset/setup.phtml';
20
+ const TEMPLATE_CHOOSE_STORE = 'searchspring/manager/system/config/fieldset/setup_scope.phtml';
21
 
22
+ protected $_configModel;
 
23
 
24
+ protected function _construct()
25
+ {
26
+ parent::_construct();
27
+
28
+ $this->_initConfigModel();
29
+
30
+ if ($this->isInStoreScope()) {
31
+ $this->setTemplate(self::TEMPLATE_SETUP);
32
+ } else {
33
+ $this->setTemplate(self::TEMPLATE_CHOOSE_STORE);
34
+ }
35
  }
36
 
37
+ protected function _initConfigModel()
38
+ {
39
+ // In newer versions, this data is available in the adminhtml/config_data singleton ... however older versions make use pull the params directly from the request
40
+ $section = $this->getRequest()->getParam('section');
41
+ $website = $this->getRequest()->getParam('website');
42
+ $store = $this->getRequest()->getParam('store');
43
 
44
+ $this->_configModel = Mage::getModel('adminhtml/config_data')
45
+ ->setSection($section)
46
+ ->setWebsite($website)
47
+ ->setStore($store)
48
+ ;
49
+ }
50
 
51
  /**
52
  * Render fieldset html
68
  return $this;
69
  }
70
 
71
+ public function isAnyStoreSetup() {
72
+ $hlp = Mage::helper('searchspring_manager');
73
+ foreach($this->getAllStores() as $store) {
74
+ if ($hlp->isStoreSetup($store)) {
75
+ return true;
76
+ }
77
+ }
78
+ return false;
79
+ }
80
+
81
+ public function isSetup($store = null) {
82
  $hlp = Mage::helper('searchspring_manager');
83
+
84
+ // If no store was passed
85
+ if (!$store) {
86
+ if (!$this->isInStoreScope()) {
87
+ // If we're not in the store scope
88
+ return $this->isAnyStoreSetup();
89
+ }
90
+ $store = $this->_configModel->getStore();
91
+ }
92
+
93
+ return $hlp->isStoreSetup($store) && $hlp->verifySetupWithSearchSpring($store);
94
+ }
95
+
96
+ public function isInDefaultScope() {
97
+ return (
98
+ $this->_configModel->getWebsite() == null &&
99
+ $this->_configModel->getStore() == null
100
+ );
101
+ }
102
+
103
+ public function isInWebsiteScope() {
104
+ return (
105
+ $this->_configModel->getWebsite() != null &&
106
+ $this->_configModel->getStore() == null
107
+ );
108
+ }
109
+
110
+ public function isInStoreScope() {
111
+ return ($this->_configModel->getStore() != null);
112
  }
113
 
114
+ public function getWebsites() {
115
+ $websites = array();
116
+ $storeModel = Mage::getSingleton('adminhtml/system_store');
117
+ foreach($storeModel->getWebsiteCollection() as $website) {
118
+ $websites[$website->getId()] = $website;
119
+ }
120
+ return $websites;
121
+ }
122
+
123
+ public function getGroups($website = null) {
124
+ // If no website was passed, and we're in the context
125
+ // of a website, only return stores under that website
126
+ if (!$website && $this->isInWebsiteScope()) {
127
+ $code = $this->_configModel->getWebsite();
128
+ $websiteId = Mage::app()->getWebsite($code)->getId();
129
+ } else if (!$website) {
130
+ $websiteId = null;
131
+ } else {
132
+ $websiteId = $website->getId();
133
+ }
134
+
135
+ $groups = array();
136
+ $storeModel = Mage::getSingleton('adminhtml/system_store');
137
+ foreach($storeModel->getGroupCollection() as $group) {
138
+ if (!$websiteId || $group->getWebsiteId() == $websiteId) {
139
+ $groups[$group->getId()] = $group;
140
+ }
141
+ }
142
+ return $groups;
143
+ }
144
+
145
+ public function getStores($group) {
146
+ $stores = array();
147
+ $storeModel = Mage::getSingleton('adminhtml/system_store');
148
+ foreach($storeModel->getStoreCollection() as $store) {
149
+ if ($store->getGroupId() == $group->getId()) {
150
+ $stores[$store->getId()] = $store;
151
+ }
152
+ }
153
+
154
+ return $stores;
155
+ }
156
+
157
+ public function getAllStores() {
158
+ $stores = array();
159
+ $storeModel = Mage::getSingleton('adminhtml/system_store');
160
+ foreach($storeModel->getStoreCollection() as $store) {
161
+ $stores[$store->getId()] = $store;
162
+ }
163
+ return $stores;
164
+ }
165
+
166
+ public function getWebsiteLabel($website) {
167
+ return $website->getName();
168
+ }
169
+
170
+ public function getGroupLabel($group) {
171
+ return $group->getName();
172
+ }
173
+
174
+ public function getStoreLabel($store) {
175
+ return $store->getName();
176
+ }
177
+
178
+ public function getStoreUrl($store) {
179
+ // There is no central function for
180
+ // doing this, so we just build it
181
+ // ourselves with these three params
182
+ $url = Mage::getModel('adminhtml/url');
183
+ $website = $store->getWebsite();
184
+ $section = $this->_configModel->getSection();
185
+ return $url->getUrl('*/*/*', array(
186
+ 'section' => $section,
187
+ 'website' => $website->getCode(),
188
+ 'store' => $store->getCode())
189
+ );
190
+ }
191
  }
app/code/community/SearchSpring/Manager/Entity/Credentials.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File Credentials.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Interface SearchSpring_Manager_Entity_Credentials
10
+ *
11
+ * @author Jake Shelby <jake@b7interactive.com>
12
+ */
13
+ interface SearchSpring_Manager_Entity_Credentials
14
+ {
15
+
16
+ public function getUsername();
17
+
18
+ public function getPassword();
19
+
20
+ }
app/code/community/SearchSpring/Manager/Entity/IndexingRequestBody.php CHANGED
@@ -8,9 +8,12 @@
8
  /**
9
  * Class SearchSpring_Manager_Entity_IndexingRequestBody
10
  *
11
- * The class models a SearchSpring API request body which needs a feed id and an array of records.
 
 
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
 
14
  */
15
  class SearchSpring_Manager_Entity_IndexingRequestBody extends SearchSpring_Manager_Entity_RequestBody
16
  {
@@ -64,28 +67,30 @@ class SearchSpring_Manager_Entity_IndexingRequestBody extends SearchSpring_Manag
64
  $this->type = $type;
65
  $this->ids = $ids;
66
  $this->shouldDelete = $shouldDelete;
67
-
68
- $this->feedId = Mage::helper('searchspring_manager')->getApiFeedId();
69
- if (null === $this->feedId) {
70
- throw new UnexpectedValueException('SearchSpring: Feed ID must be set');
71
- }
72
  }
73
 
74
  /**
75
- * Returns a value that's allowed to be given to json_encode
76
  *
77
  * @return array
78
  */
79
- public function jsonSerialize()
80
  {
81
- $url = Mage::helper('searchspring_manager')->getMageAPIUrlProduct();
 
82
 
 
 
 
 
 
 
 
83
  $body = array(
84
  'type' => $this->type,
85
  'ids' => $this->ids,
86
  'delete' => $this->shouldDelete,
87
- 'feedId' => $this->feedId,
88
- 'generateUrl' => $url
89
  );
90
 
91
  return $body;
8
  /**
9
  * Class SearchSpring_Manager_Entity_IndexingRequestBody
10
  *
11
+ * The class models a SearchSpring API request body which needs
12
+ * an array of records. Each instance is feed agnostic, and
13
+ * therefore requires a feed id to serialize.
14
  *
15
  * @author Nate Brunette <nate@b7interactive.com>
16
+ * @author Jake Shelby <jake@b7interactive.com>
17
  */
18
  class SearchSpring_Manager_Entity_IndexingRequestBody extends SearchSpring_Manager_Entity_RequestBody
19
  {
67
  $this->type = $type;
68
  $this->ids = $ids;
69
  $this->shouldDelete = $shouldDelete;
 
 
 
 
 
70
  }
71
 
72
  /**
73
+ * Get the IDs for the request
74
  *
75
  * @return array
76
  */
77
+ public function getIds()
78
  {
79
+ return $this->ids;
80
+ }
81
 
82
+ /**
83
+ * Returns a value that's allowed to be given to json_encode
84
+ *
85
+ * @return array
86
+ */
87
+ public function jsonSerialize($feedId)
88
+ {
89
  $body = array(
90
  'type' => $this->type,
91
  'ids' => $this->ids,
92
  'delete' => $this->shouldDelete,
93
+ 'feedId' => $feedId,
 
94
  );
95
 
96
  return $body;
app/code/community/SearchSpring/Manager/Entity/ProductDeletionToken.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File ProductDeletionToken.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Entity_ProductDeletionToken
10
+ *
11
+ * Token that represents the prepped data needed for the deletion of a product
12
+ *
13
+ * @author Jake Shelby <jake@b7interactive.com>
14
+ */
15
+ class SearchSpring_Manager_Entity_ProductDeletionToken
16
+ {
17
+
18
+ /**
19
+ * Product ID to be deleted
20
+ *
21
+ * @var int $product
22
+ */
23
+ protected $productId;
24
+
25
+ /**
26
+ * Stores affected
27
+ *
28
+ * @var array $stores
29
+ */
30
+ protected $stores;
31
+
32
+ /**
33
+ * Product IDs related, affected by deletion
34
+ *
35
+ * @var int $product
36
+ */
37
+ protected $relatedProductIds;
38
+
39
+ /**
40
+ * Constructor
41
+ *
42
+ * @param int $productId
43
+ * @param array $stores
44
+ * @param array $relatedProductIds optional
45
+ */
46
+ public function __construct($productId, array $stores, array $relatedProductIds = array())
47
+ {
48
+ $this->productId = $productId;
49
+ $this->stores = $stores;
50
+ $this->relatedProductIds = $relatedProductIds;
51
+ }
52
+
53
+ public function getProductId()
54
+ {
55
+ return $this->productId;
56
+ }
57
+
58
+ public function getStores()
59
+ {
60
+ return $this->stores;
61
+ }
62
+
63
+ public function getRelatedProductIds()
64
+ {
65
+ return $this->relatedProductIds;
66
+ }
67
+
68
+ }
app/code/community/SearchSpring/Manager/Entity/RequestCredentials.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File RequestCredentials.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Entity_RequestCredentials
10
+ *
11
+ * The class models a SearchSpring API request's credentials parameters
12
+ *
13
+ * @author Jake Shelby <jake@b7interactive.com>
14
+ */
15
+ class SearchSpring_Manager_Entity_RequestCredentials implements SearchSpring_Manager_Entity_Credentials
16
+ {
17
+
18
+ /**
19
+ * The SearchSpring website account Site ID, also used as a sort of username
20
+ *
21
+ * @var string $
22
+ */
23
+ protected $siteId;
24
+
25
+ /**
26
+ * The SearchSpring website account Secret Key, also used a password of sorts
27
+ *
28
+ * @var string $type
29
+ */
30
+ protected $secretKey;
31
+
32
+ /**
33
+ * Constructor
34
+ *
35
+ * @param string $siteId
36
+ * @param string $secretKey
37
+ */
38
+ public function __construct($siteId, $secretKey)
39
+ {
40
+ $this->siteId = $siteId;
41
+ $this->secretKey = $secretKey;
42
+ }
43
+
44
+ public function isPopulated() {
45
+ if (empty($this->siteId) ||
46
+ empty($this->secretKey))
47
+ {
48
+ return false;
49
+ }
50
+ return true;
51
+ }
52
+
53
+ public function getSecretKey() {
54
+ return $this->secretKey;
55
+ }
56
+
57
+ public function getSecret() {
58
+ return $this->getSecretKey();
59
+ }
60
+
61
+ public function getSiteId() {
62
+ return $this->siteId;
63
+ }
64
+
65
+ public function getUsername() {
66
+ return $this->getSiteId();
67
+ }
68
+
69
+ public function getPassword() {
70
+ return $this->getSecretKey();
71
+ }
72
+
73
+ }
app/code/community/SearchSpring/Manager/Entity/SearchRequestBody.php CHANGED
@@ -31,13 +31,9 @@ class SearchSpring_Manager_Entity_SearchRequestBody extends SearchSpring_Manager
31
  /**
32
  * Constructor
33
  */
34
- public function __construct()
35
  {
36
- $this->_siteId = Mage::helper('searchspring_manager')->getApiSiteId();
37
-
38
- if (null === $this->_siteId) {
39
- throw new UnexpectedValueException('SearchSpring: Site ID must be set');
40
- }
41
  }
42
 
43
  public function addFilter($field, $value, $append = false) {
31
  /**
32
  * Constructor
33
  */
34
+ public function __construct($siteId)
35
  {
36
+ $this->_siteId = $siteId;
 
 
 
 
37
  }
38
 
39
  public function addFilter($field, $value, $append = false) {
app/code/community/SearchSpring/Manager/Factory/ApiFactory.php CHANGED
@@ -8,40 +8,40 @@
8
  /**
9
  * Class SearchSpring_Manager_Factory_ApiFactory
10
  *
11
- * Create a SearchSpring api request object
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
 
14
  */
15
  class SearchSpring_Manager_Factory_ApiFactory
16
  {
 
17
  /**
18
- * Make the object
19
  *
20
- * Pulls in config values from the Magento configuration. Uses the Varien curl adapter.
 
21
  *
22
- * @param $type string The type of API to build (index or search)
23
  *
24
  * @return SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter
25
  *
26
- * @throws UnexpectedValueException
27
  */
28
- public function make($type)
29
  {
 
30
 
31
- $baseUrl = Mage::helper('searchspring_manager')->getApiBaseUrl();
32
- $siteId = Mage::helper('searchspring_manager')->getApiSiteId();
33
- $secretKey = Mage::helper('searchspring_manager')->getApiSecretKey();
34
-
35
- if (empty($baseUrl)) {
36
- throw new UnexpectedValueException('SearchSpring: Base URL must be set');
37
- }
38
 
39
- if (empty($siteId)) {
40
- throw new UnexpectedValueException('SearchSpring: Site ID must be set');
 
41
  }
42
-
43
- if (empty($secretKey)) {
44
- throw new UnexpectedValueException('SearchSpring: Secret key must be set');
45
  }
46
 
47
  $apiErrorHandler = new SearchSpring_Manager_Handler_ApiErrorHandler();
@@ -54,22 +54,48 @@ class SearchSpring_Manager_Factory_ApiFactory
54
  'keepalive' => true
55
  ));
56
 
57
- if($type == 'index') {
58
- $client->setAuth($siteId, $secretKey);
59
-
60
- $api = new SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter(
61
- $apiErrorHandler,
62
- $client,
63
- $baseUrl
64
- );
65
- } else {
66
- $api = new SearchSpring_Manager_Service_SearchSpring_SearchApiAdapter(
67
- $apiErrorHandler,
68
- $client,
69
- $baseUrl
70
- );
71
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  return $api;
74
  }
 
 
 
 
 
 
75
  }
8
  /**
9
  * Class SearchSpring_Manager_Factory_ApiFactory
10
  *
11
+ * Create a SearchSpring api request objects
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
14
+ * @author Jake Shelby <jake@b7interactive.com>
15
  */
16
  class SearchSpring_Manager_Factory_ApiFactory
17
  {
18
+
19
  /**
20
+ * Make indexing adapter for a specific store
21
  *
22
+ * Pulls in config values from the Magento configuration, based
23
+ * on specified store. Uses the Zend Http Client adapter.
24
  *
25
+ * @param $store mixed Mage store id|code|object
26
  *
27
  * @return SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter
28
  *
29
+ * @throws Exception
30
  */
31
+ public function makeIndexingAdapter($store)
32
  {
33
+ $hlp = Mage::helper('searchspring_manager');
34
 
35
+ $feedId = $hlp->getApiFeedId($store);
36
+ $creds = $hlp->getApiCredentials($store);
 
 
 
 
 
37
 
38
+ if (!$feedId) {
39
+ $code = Mage::app()->getStore($store)->getCode();
40
+ throw new Exception("Cannot create API adapter for store: $code; feed id is not configured.");
41
  }
42
+ if (!$creds->isPopulated()) {
43
+ $code = Mage::app()->getStore($store)->getCode();
44
+ throw new Exception("Cannot create API adapter for store: $code; incomplete API credentials.");
45
  }
46
 
47
  $apiErrorHandler = new SearchSpring_Manager_Handler_ApiErrorHandler();
54
  'keepalive' => true
55
  ));
56
 
57
+ $client->setAuth($creds->getUsername(), $creds->getPassword());
58
+
59
+ $api = new SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter(
60
+ $apiErrorHandler,
61
+ $client,
62
+ $this->getApiBaseUrl(),
63
+ $feedId
64
+ );
65
+
66
+ return $api;
67
+ }
68
+
69
+ /**
70
+ * Make search adapter
71
+ *
72
+ * Creates an instance of a search adapter. Uses the Zend Http Client adapter
73
+ *
74
+ * @return SearchSpring_Manager_Service_SearchSpring_SearchApiAdapter
75
+ */
76
+ public function makeSearchAdapter()
77
+ {
78
+ $apiErrorHandler = new SearchSpring_Manager_Handler_ApiErrorHandler();
79
+
80
+ $client = new Zend_Http_Client();
81
+ $client->setConfig(array(
82
+ 'maxredirects' => 0,
83
+ 'timeout' => 15,
84
+ 'keepalive' => true
85
+ ));
86
+
87
+ $api = new SearchSpring_Manager_Service_SearchSpring_SearchApiAdapter(
88
+ $apiErrorHandler,
89
+ $client,
90
+ $this->getApiBaseUrl()
91
+ );
92
 
93
  return $api;
94
  }
95
+
96
+ public function getApiBaseUrl()
97
+ {
98
+ return Mage::helper('searchspring_manager')->getApiBaseUrl();
99
+ }
100
+
101
  }
app/code/community/SearchSpring/Manager/Factory/GeneratorFactory.php CHANGED
@@ -83,7 +83,8 @@ class SearchSpring_Manager_Factory_GeneratorFactory
83
  throw new InvalidArgumentException('Ids must be an array');
84
  }
85
 
86
- $validator = new SearchSpring_Manager_Validator_ProductValidator();
 
87
 
88
  // get product ids for each category
89
  $productIds = array();
83
  throw new InvalidArgumentException('Ids must be an array');
84
  }
85
 
86
+ $hlp = Mage::helper('searchspring_manager');
87
+ $validator = new SearchSpring_Manager_Validator_ProductValidator($hlp->getConfig());
88
 
89
  // get product ids for each category
90
  $productIds = array();
app/code/community/SearchSpring/Manager/Factory/LiveIndexerFactory.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * LiveIndexerFactory.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Factory_LiveIndexerFactory
10
+ *
11
+ * Create a SearchSpring Live Indexer Service
12
+ *
13
+ * @author Jake Shelby <jake@b7interactive.com>
14
+ */
15
+ class SearchSpring_Manager_Factory_LiveIndexerFactory
16
+ {
17
+
18
+ /**
19
+ * Make Live Indexer Service
20
+ *
21
+ * @return SearchSpring_Manager_Service_LiveIndexer
22
+ */
23
+ public function make()
24
+ {
25
+ $hlp = Mage::helper('searchspring_manager');
26
+
27
+ $service = new SearchSpring_Manager_Service_LiveIndexer(
28
+ Mage::app(),
29
+ $hlp->getConfig(),
30
+ new SearchSpring_Manager_Factory_ApiFactory,
31
+ new SearchSpring_Manager_Factory_IndexingRequestBodyFactory
32
+ );
33
+
34
+ return $service;
35
+ }
36
+
37
+ }
app/code/community/SearchSpring/Manager/Factory/SearchRequestBodyFactory.php CHANGED
@@ -14,9 +14,16 @@
14
  */
15
  class SearchSpring_Manager_Factory_SearchRequestBodyFactory
16
  {
 
17
  public function make()
18
  {
19
- $requestBody = new SearchSpring_Manager_Entity_SearchRequestBody();
 
 
 
 
 
 
20
  return $requestBody;
21
  }
22
  }
14
  */
15
  class SearchSpring_Manager_Factory_SearchRequestBodyFactory
16
  {
17
+
18
  public function make()
19
  {
20
+ // Build using the site Id of the current magento store
21
+ $siteId = Mage::helper('searchspring_manager')->getApiSiteId( Mage::app()->getStore() );
22
+ if (null === $siteId) {
23
+ throw new UnexpectedValueException('SearchSpring: Site ID must be set to create a search request, none found for store: ' . Mage::app()->getStore()->getCode());
24
+ }
25
+
26
+ $requestBody = new SearchSpring_Manager_Entity_SearchRequestBody($siteId);
27
  return $requestBody;
28
  }
29
  }
app/code/community/SearchSpring/Manager/Helper/Data.php CHANGED
@@ -8,246 +8,255 @@
8
  /**
9
  * Class SearchSpring_Manager_Helper_Data
10
  *
11
- * You should need to put anything in this class, but Magento needs to to function.
12
- *
13
  * @author Nate Brunette <nate@b7interactive.com>
 
14
  */
15
  class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
16
  {
17
 
18
- const XML_PATH_GLOBAL_LIVE_INDEXING_ENABLE_FL = 'ssmanager/ssmanager_general/live_indexing';
19
- const XML_PATH_INDEX_ZERO_PRICE = 'ssmanager/ssmanager_general/index_zero_price';
20
- const XML_PATH_INDEX_OUT_OF_STOCK = 'ssmanager/ssmanager_general/index_out_of_stock';
21
-
22
- const XML_PATH_FEED_SETTING_PATH = 'ssmanager/ssmanager_feed/feed_path';
 
23
 
24
- const XML_PATH_SALES_RANK_TIMESPAN = 'ssmanager/ssmanager_sales_rank/timespan';
 
 
 
25
 
26
- const XML_PATH_GLOBAL_CATEGORY_ENABLE_FL = 'ssmanager/ssmanager_catalog/enable_categories';
 
 
27
 
28
- const XML_PATH_API_BASE_URL = 'ssmanager/ssmanager_api/base_url';
29
- const ENV_VAR_API_BASE_URL = 'SEARCHSPRING_API_HOST';
 
30
 
31
- const XML_PATH_API_FEED_ID = 'ssmanager/ssmanager_api/feed_id';
32
- const XML_PATH_API_SITE_ID = 'ssmanager/ssmanager_api/site_id';
33
- const XML_PATH_API_SECRET_KEY = 'ssmanager/ssmanager_api/secret_key';
34
 
35
- const XML_PATH_API_AUTHENTICATION_METHOD = 'ssmanager/ssmanager_api/authentication_method';
36
- const AUTH_METHOD_SIMPLE = 'simple';
37
- const AUTH_METHOD_OAUTH = 'oauth';
38
 
39
- const XML_PATH_GENERATE_CACHE_IMAGES = 'ssmanager/ssmanager_images/generate_cache_images';
40
- const XML_PATH_IMAGE_WIDTH = 'ssmanager/ssmanager_images/image_width';
41
- const XML_PATH_IMAGE_HEIGHT = 'ssmanager/ssmanager_images/image_height';
42
 
43
- const XML_PATH_GENERATE_SWATCH_IMAGES = 'ssmanager/ssmanager_images/generate_swatch_images';
44
- const XML_PATH_SWATCH_WIDTH = 'ssmanager/ssmanager_images/swatch_width';
45
- const XML_PATH_SWATCH_HEIGHT = 'ssmanager/ssmanager_images/swatch_height';
 
 
 
46
 
47
- const XML_PATH_UUID = 'ssmanager/ssmanager_track/uuid';
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- const MANAGER_API_PATH_PRODUCT_SIMPLE = 'searchspring/generate/index';
50
- const MANAGER_API_PATH_GENERATE_SIMPLE = 'searchspring/generate/feed';
51
- const MANAGER_API_PATH_PRODUCT_OAUTH = 'api/rest/searchspring/index';
52
- const MANAGER_API_PATH_GENERATE_OAUTH = 'api/rest/searchspring/feed';
 
 
 
53
 
54
- public function getVersion() {
55
- return Mage::getConfig()->getNode('modules/SearchSpring_Manager/version');
56
  }
57
 
58
- public function isLiveIndexingEnabled()
59
- {
60
- return Mage::getStoreConfigFlag(self::XML_PATH_GLOBAL_LIVE_INDEXING_ENABLE_FL);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
 
63
- public function isCategorySearchEnabled()
64
- {
65
- return Mage::getStoreConfigFlag(self::XML_PATH_GLOBAL_CATEGORY_ENABLE_FL);
66
  }
67
 
68
- public function isZeroPriceIndexingEnabled()
 
 
 
 
 
 
69
  {
70
- return Mage::getStoreConfigFlag(self::XML_PATH_INDEX_ZERO_PRICE);
 
 
 
 
71
  }
72
 
73
- public function isOutOfStockIndexingEnabled()
 
 
 
 
 
 
 
 
 
74
  {
75
- return Mage::getStoreConfigFlag(self::XML_PATH_INDEX_OUT_OF_STOCK);
 
 
 
 
 
 
 
 
76
  }
77
 
78
- public function getSalesRankTimespan()
79
- {
80
- return Mage::getStoreConfig(self::XML_PATH_SALES_RANK_TIMESPAN);
 
 
 
81
  }
82
 
83
- public function getFeedPath()
84
- {
85
- return Mage::getStoreConfig(self::XML_PATH_FEED_SETTING_PATH);
86
  }
87
 
88
- public function getApiFeedId()
89
- {
90
- return Mage::getStoreConfig(self::XML_PATH_API_FEED_ID);
91
  }
92
 
93
- public function getApiSiteId()
94
- {
95
- return Mage::getStoreConfig(self::XML_PATH_API_SITE_ID);
96
  }
97
 
98
- public function getApiBaseUrl()
99
- {
100
- if($env = getenv(self::ENV_VAR_API_BASE_URL)) {
101
- return $env;
102
- } else {
103
- return Mage::getStoreConfig(self::XML_PATH_API_BASE_URL);
104
- }
105
  }
106
 
107
- public function getApiSecretKey()
108
- {
109
- return Mage::getStoreConfig(self::XML_PATH_API_SECRET_KEY);
110
  }
111
 
112
- public function getAuthenticationMethod() {
113
- return Mage::getStoreConfig(self::XML_PATH_API_AUTHENTICATION_METHOD);
114
  }
115
 
116
- public function isCacheImagesEnabled()
117
- {
118
- return Mage::getStoreConfig(self::XML_PATH_GENERATE_CACHE_IMAGES);
119
  }
120
 
121
- public function getImageHeight()
122
- {
123
- return Mage::getStoreConfig(self::XML_PATH_IMAGE_HEIGHT);
124
  }
125
 
126
- public function getImageWidth()
127
- {
128
- return Mage::getStoreConfig(self::XML_PATH_IMAGE_WIDTH);
129
  }
130
 
131
- public function isSwatchImagesEnabled()
132
- {
133
- return Mage::getStoreConfig(self::XML_PATH_GENERATE_SWATCH_IMAGES);
134
  }
135
 
136
- public function getSwatchHeight()
137
- {
138
- return Mage::getStoreConfig(self::XML_PATH_SWATCH_HEIGHT);
139
  }
140
 
141
- public function getSwatchWidth()
142
- {
143
- return Mage::getStoreConfig(self::XML_PATH_SWATCH_WIDTH);
144
  }
145
 
146
- public function getUUID() {
147
- return Mage::getStoreConfig(self::XML_PATH_UUID);
148
  }
149
 
150
- public function getMageAPIPathGenerate() {
151
- switch ($this->getAuthenticationMethod()) {
152
- case self::AUTH_METHOD_SIMPLE:
153
- return self::MANAGER_API_PATH_GENERATE_SIMPLE;
154
- case self::AUTH_METHOD_OAUTH:
155
- return self::MANAGER_API_PATH_GENERATE_OAUTH;
156
- }
157
- return false;
158
  }
159
 
160
- public function getMageAPIPathProduct() {
161
- switch ($this->getAuthenticationMethod()) {
162
- case self::AUTH_METHOD_SIMPLE:
163
- return self::MANAGER_API_PATH_PRODUCT_SIMPLE;
164
- case self::AUTH_METHOD_OAUTH:
165
- return self::MANAGER_API_PATH_PRODUCT_OAUTH;
166
- }
167
- return false;
168
  }
169
 
170
- public function getMageAPIUrlGenerate() {
171
- return Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, true) . $this->getMageAPIPathGenerate();
172
  }
173
 
174
- public function getMageAPIUrlProduct() {
175
- return Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, true) . $this->getMageAPIPathProduct();
176
  }
177
 
178
- public function writeStoreConfig($path, $value, $scope = 'default', $scopeId = 0) {
179
- Mage::getConfig()->saveConfig($path, $value, $scope, $scopeId)->reinit();
 
 
180
  }
181
 
182
- public function registerMagentoAPIAuthenticationWithSearchSpring($verify = false) {
183
 
184
- $whlp = Mage::helper('searchspring_manager/webservice');
 
 
185
 
186
- // TODO -- should we cache responses from verify??
 
 
187
 
188
- try {
189
-
190
- switch ($this->getAuthenticationMethod()) {
191
- case self::AUTH_METHOD_SIMPLE:
192
- if ($verify)
193
- $response = $whlp->verifyMageAPIAuthSimple();
194
- else
195
- $response = $whlp->registerMageAPIAuthSimple();
196
- break;
197
- case self::AUTH_METHOD_OAUTH:
198
- if ($verify)
199
- $response = $whlp->verifyMageAPIAuthOAuth();
200
- else
201
- $response = $whlp->registerMageAPIAuthOAuth();
202
- break;
203
- default:
204
- // TODO - Should we be returning false, this means the setting hasn't been initialized?
205
- return false;
206
- }
207
-
208
- } catch (Exception $e) {
209
- Mage::log(__METHOD__.": Problem while attempting to access the SearchSpring service: " . $e->getMessage());
210
- return false;
211
- }
212
 
213
- return $response;
 
214
  }
215
 
216
- /**
217
- * Intended for layout action parameter helper calls
218
- *
219
- * If the current layer category is enabled, return
220
- * new template; if not return the blocks existing template
221
- */
222
- public function getBlockTemplateIfCategoryEnabled($block, $newTemplate)
223
- {
224
- $layer = Mage::getSingleton('searchspring_manager/layer');
225
- if ($layer->isSearchSpringEnabled()) {
226
- return $newTemplate;
227
- }
228
- return Mage::app()->getLayout()->getBlock($block)->getTemplate();
229
  }
230
 
231
- /**
232
- * Check if module exists and is enabled in global config.
233
- *
234
- * NOTE: This function most likely exists in the parent, but
235
- * may not depending on the version of magento installed.
236
- *
237
- * @param string $moduleName the full module name, example Mage_Core
238
- * @return boolean
239
- */
240
- public function isModuleEnabled($moduleName = null)
241
- {
242
- if (!Mage::getConfig()->getNode('modules/' . $moduleName)) {
243
- return false;
244
- }
245
 
246
- $isActive = Mage::getConfig()->getNode('modules/' . $moduleName . '/active');
247
- if (!$isActive || !in_array((string)$isActive, array('true', '1'))) {
248
- return false;
249
- }
250
- return true;
 
251
  }
252
 
253
  }
8
  /**
9
  * Class SearchSpring_Manager_Helper_Data
10
  *
 
 
11
  * @author Nate Brunette <nate@b7interactive.com>
12
+ * @author Jake Shelby <jake@b7interactive.com>
13
  */
14
  class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
15
  {
16
 
17
+ /**
18
+ * SearchSpring Config Model
19
+ *
20
+ * @var SearchSpring_Manager_Model_Config
21
+ */
22
+ protected $config;
23
 
24
+ public function __construct() {
25
+ // Create an instance of Search Spring config provider
26
+ $this->config = new SearchSpring_Manager_Model_Config( Mage::app() );
27
+ }
28
 
29
+ public function getConfig() {
30
+ return $this->config;
31
+ }
32
 
33
+ /**
34
+ * Functions to do ... stuff (lack of better word)
35
+ */
36
 
37
+ public function registerMagentoAPIWithSearchSpring($store) {
 
 
38
 
39
+ // TODO - Restrict one store being registered with the same feed id as another store
 
 
40
 
41
+ // First make sure we have the data we need
42
+ $feedId = $this->getApiFeedId($store);
43
+ $creds = $this->getApiCredentials($store);
44
 
45
+ if (!$feedId) {
46
+ throw new Exception("Cannot register with SearchSpring, missing feedId.");
47
+ }
48
+ if (!$creds->isPopulated()) {
49
+ throw new Exception("Cannot register with SearchSpring, incomplete API credentials.");
50
+ }
51
 
52
+ // Register Auth Method
53
+ $whlp = Mage::helper('searchspring_manager/webservice');
54
+ switch ($this->getAuthenticationMethod($store)) {
55
+ case SearchSpring_Manager_Model_Config::AUTH_METHOD_SIMPLE:
56
+ $whlp->registerMageAPIAuthSimple($feedId, $creds);
57
+ break;
58
+ case SearchSpring_Manager_Model_Config::AUTH_METHOD_OAUTH:
59
+ $whlp->registerMageAPIAuthOAuth($feedId, $creds);
60
+ break;
61
+ default:
62
+ throw new Exception("Cannot register with SearchSpring, missing or unknown authentication method chosen.");
63
+ }
64
 
65
+ // Register store specific API URLs that this module provides
66
+ $whlp->registerMageAPIUrls(
67
+ $feedId, $creds,
68
+ $this->getMageUrlGeneratedFeed($store), // Feed
69
+ $this->getMageAPIUrlGenerate($store), // Batch
70
+ $this->getMageAPIUrlProduct($store) // Live
71
+ );
72
 
73
+ // Each register call above will throw an exception in the
74
+ // event of an unsuccessful result
75
  }
76
 
77
+ public function verifySetupWithSearchSpring($store) {
78
+
79
+ // First make sure we have the data we need
80
+ $feedId = $this->getApiFeedId($store);
81
+ $creds = $this->getApiCredentials($store);
82
+
83
+ if (!$feedId) {
84
+ throw new Exception("Cannot verify settings with SearchSpring, missing feedId.");
85
+ }
86
+ if (!$creds->isPopulated()) {
87
+ throw new Exception("Cannot verify settings with SearchSpring, incomplete API credentials.");
88
+ }
89
+
90
+ // TODO -- should we cache responses from verify??
91
+
92
+ $whlp = Mage::helper('searchspring_manager/webservice');
93
+ switch ($this->getAuthenticationMethod($store)) {
94
+ case SearchSpring_Manager_Model_Config::AUTH_METHOD_SIMPLE:
95
+ return $whlp->verifyMageAPIAuthSimple($feedId, $creds);
96
+ case SearchSpring_Manager_Model_Config::AUTH_METHOD_OAUTH:
97
+ return $whlp->verifyMageAPIAuthOAuth($feedId, $creds);
98
+ default:
99
+ throw new Exception("Cannot register with SearchSpring, missing or unknown authentication method chosen.");
100
+ }
101
+
102
+ // Each verify call above will throw an exception in the
103
+ // event of problem connecting to SearchSpring webservice
104
  }
105
 
106
+ public function writeStoreConfig($path, $value, $scope = 'default', $scopeId = 0) {
107
+ Mage::getConfig()->saveConfig($path, $value, $scope, $scopeId)->reinit();
 
108
  }
109
 
110
+ /**
111
+ * Intended for layout action parameter helper calls
112
+ *
113
+ * If the current layer category is enabled, return
114
+ * new template; if not return the blocks existing template
115
+ */
116
+ public function getBlockTemplateIfCategoryEnabled($block, $newTemplate)
117
  {
118
+ $layer = Mage::getSingleton('searchspring_manager/layer');
119
+ if ($layer->isSearchSpringEnabled()) {
120
+ return $newTemplate;
121
+ }
122
+ return Mage::app()->getLayout()->getBlock($block)->getTemplate();
123
  }
124
 
125
+ /**
126
+ * Check if module exists and is enabled in global config.
127
+ *
128
+ * NOTE: This function most likely exists in the parent, but
129
+ * may not depending on the version of magento installed.
130
+ *
131
+ * @param string $moduleName the full module name, example Mage_Core
132
+ * @return boolean
133
+ */
134
+ public function isModuleEnabled($moduleName = null)
135
  {
136
+ if (!Mage::getConfig()->getNode('modules/' . $moduleName)) {
137
+ return false;
138
+ }
139
+
140
+ $isActive = Mage::getConfig()->getNode('modules/' . $moduleName . '/active');
141
+ if (!$isActive || !in_array((string)$isActive, array('true', '1'))) {
142
+ return false;
143
+ }
144
+ return true;
145
  }
146
 
147
+ /**
148
+ * Forwarded Config Getters
149
+ */
150
+
151
+ public function getVersion() {
152
+ return $this->config->getVersion();
153
  }
154
 
155
+ public function getApiBaseUrl() {
156
+ return $this->config->getApiBaseUrl();
 
157
  }
158
 
159
+ public function getUUID() {
160
+ return $this->config->getUUID();
 
161
  }
162
 
163
+ public function getApiSiteId($store) {
164
+ return $this->config->getApiSiteId($store);
 
165
  }
166
 
167
+ public function getApiSecretKey($store) {
168
+ return $this->config->getApiSecretKey($store);
 
 
 
 
 
169
  }
170
 
171
+ public function getApiFeedId($store) {
172
+ return $this->config->getApiFeedId($store);
 
173
  }
174
 
175
+ public function getAuthenticationMethod($store) {
176
+ return $this->config->getAuthenticationMethod($store);
177
  }
178
 
179
+ public function isLiveIndexingEnabled($store = null) {
180
+ return $this->config->isLiveIndexingEnabled($store);
 
181
  }
182
 
183
+ public function isZeroPriceIndexingEnabled($store = null) {
184
+ return $this->config->isZeroPriceIndexingEnabled($store);
 
185
  }
186
 
187
+ public function isOutOfStockIndexingEnabled($store = null) {
188
+ return $this->config->isOutOfStockIndexingEnabled($store);
 
189
  }
190
 
191
+ public function getSalesRankTimespan($store = null) {
192
+ return $this->config->getSalesRankTimespan($store);
 
193
  }
194
 
195
+ public function getFeedPath($store = null) {
196
+ return $this->config->getFeedPath($store);
 
197
  }
198
 
199
+ public function isCacheImagesEnabled($store = null) {
200
+ return $this->config->isCacheImagesEnabled($store);
 
201
  }
202
 
203
+ public function getImageHeight($store = null) {
204
+ return $this->config->getImageHeight($store);
205
  }
206
 
207
+ public function getImageWidth($store = null) {
208
+ return $this->config->getImageWidth($store);
 
 
 
 
 
 
209
  }
210
 
211
+ public function isSwatchImagesEnabled($store = null) {
212
+ return $this->config->isSwatchImagesEnabled($store);
 
 
 
 
 
 
213
  }
214
 
215
+ public function getSwatchHeight($store = null) {
216
+ return $this->config->getSwatchHeight($store);
217
  }
218
 
219
+ public function getSwatchWidth($store = null) {
220
+ return $this->config->getSwatchWidth($store);
221
  }
222
 
223
+
224
+ // This config is not really used yet
225
+ public function isCategorySearchEnabled() {
226
+ return $this->config->isCategorySearchEnabled();
227
  }
228
 
 
229
 
230
+ public function isStoreSetup($store) {
231
+ return $this->config->isStoreSetup($store);
232
+ }
233
 
234
+ public function getApiCredentials($store) {
235
+ return $this->config->getApiCredentials($store);
236
+ }
237
 
238
+ public function getMageApiCredentials($store) {
239
+ return $this->config->getMageApiCredentials($store);
240
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
+ public function getMageAPIPathGenerate($store) {
243
+ return $this->config->getMageAPIPathGenerate($store);
244
  }
245
 
246
+ public function getMageAPIPathProduct($store) {
247
+ return $this->config->getMageAPIPathProduct($store);
 
 
 
 
 
 
 
 
 
 
 
248
  }
249
 
250
+ public function getMageAPIUrlGenerate($store) {
251
+ return $this->config->getMageAPIUrlGenerate($store);
252
+ }
 
 
 
 
 
 
 
 
 
 
 
253
 
254
+ public function getMageAPIUrlProduct($store) {
255
+ return $this->config->getMageAPIUrlProduct($store);
256
+ }
257
+
258
+ public function getMageUrlGeneratedFeed($store) {
259
+ return $this->config->getMageUrlGeneratedFeed($store);
260
  }
261
 
262
  }
app/code/community/SearchSpring/Manager/Helper/Http.php CHANGED
@@ -39,20 +39,19 @@ class SearchSpring_Manager_Helper_Http extends Mage_Core_Helper_Http
39
  *
40
  * @return bool
41
  */
42
- public function isSimpleAuthValid() {
43
 
 
44
  if (!$this->isSimpleAuthProvided()) {
45
  return false;
46
  }
47
 
 
48
  list($username, $password) = $this->getBasicAuthCredentials();
49
-
50
- // Get Valid Credentials from configuration
51
- $helper = Mage::helper('searchspring_manager');
52
- $feedId = $helper->getApiFeedId();
53
- $secretKey = $helper->getApiSecretKey();
54
-
55
- return ($feedId == $username && $secretKey == $password);
56
  }
57
 
58
  /**
39
  *
40
  * @return bool
41
  */
42
+ public function isSimpleAuthValid(SearchSpring_Manager_Entity_Credentials $creds) {
43
 
44
+ // Make sure auth was provided for this request at all
45
  if (!$this->isSimpleAuthProvided()) {
46
  return false;
47
  }
48
 
49
+ // Validate against credentials
50
  list($username, $password) = $this->getBasicAuthCredentials();
51
+ return (
52
+ $username == $creds->getUsername() &&
53
+ $password == $creds->getPassword()
54
+ );
 
 
 
55
  }
56
 
57
  /**
app/code/community/SearchSpring/Manager/Helper/Product.php CHANGED
@@ -24,6 +24,10 @@ class SearchSpring_Manager_Helper_Product extends Mage_Core_Helper_Abstract
24
  $attribute = $product->getResource()->getAttribute($attributeCode);
25
  }
26
 
 
 
 
 
27
  if ($attribute->getFrontendInput() === 'boolean' ||
28
  $attribute->getFrontendInput() === 'select'
29
  ) {
24
  $attribute = $product->getResource()->getAttribute($attributeCode);
25
  }
26
 
27
+ if (!is_object($attribute)) {
28
+ throw new Exception("Unknown attribute: $attributeCode");
29
+ }
30
+
31
  if ($attribute->getFrontendInput() === 'boolean' ||
32
  $attribute->getFrontendInput() === 'select'
33
  ) {
app/code/community/SearchSpring/Manager/Helper/Webservice.php CHANGED
@@ -16,47 +16,62 @@ class SearchSpring_Manager_Helper_Webservice extends Mage_Core_Helper_Abstract
16
  const PATH_FEED_AUTH_METHOD_SET = "/api/manage/feeds/auth-method/%s/set.json";
17
  const PATH_FEED_AUTH_METHOD_VERIFY = "/api/manage/feeds/auth-method/%s/verify.json";
18
 
 
 
19
  // SearchSpring Specific Authentication Codes
20
  const AUTH_METHOD_SIMPLE = 'simple';
21
  const AUTH_METHOD_OAUTH = 'o-auth';
22
 
23
- public function verifyMageAPIAuthSimple() {
 
 
 
 
 
 
24
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_VERIFY, self::AUTH_METHOD_SIMPLE);
25
- $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForSimple());
26
  return $this->isResponseStatusSuccess($response);
27
  }
28
 
29
- public function registerMageAPIAuthSimple() {
30
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_SET, self::AUTH_METHOD_SIMPLE);
31
- $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForSimple());
32
- return $this->isResponseSuccess($response);
33
  }
34
 
35
- public function verifyMageAPIAuthOAuth() {
36
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_VERIFY, self::AUTH_METHOD_OAUTH);
37
- $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForOAuth());
38
  return $this->isResponseStatusSuccess($response);
39
  }
40
 
41
- public function registerMageAPIAuthOAuth() {
42
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_SET, self::AUTH_METHOD_OAUTH);
43
- $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForOAuth());
44
- return $this->isResponseSuccess($response);
45
  }
46
 
47
- public function callSearchSpringWebservice($path, $params = array()) {
48
-
49
- $hlp = Mage::helper('searchspring_manager');
 
 
 
 
 
 
 
 
 
 
50
 
51
- $apiHost = $hlp->getApiBaseUrl();
52
- $siteId = $hlp->getApiSiteId();
53
- $secret = $hlp->getApiSecretKey();
54
- $feedId = $hlp->getApiFeedId();
55
 
56
- if (empty($apiHost) || empty($siteId) || empty($secret) || empty($feedId)) {
57
- // Can't do much without these first
58
- return false;
59
- }
60
 
61
  $url = $apiHost . $path;
62
 
@@ -64,9 +79,12 @@ class SearchSpring_Manager_Helper_Webservice extends Mage_Core_Helper_Abstract
64
  'maxredirects' => 0,
65
  'timeout'=>30
66
  ));
67
- $client->setAuth($siteId, $secret);
68
 
69
- $params = array_merge(array('feedId' => $feedId), $params);
 
 
 
 
70
 
71
  $client->setParameterGet($params);
72
 
@@ -90,12 +108,28 @@ class SearchSpring_Manager_Helper_Webservice extends Mage_Core_Helper_Abstract
90
  return true;
91
  }
92
 
93
- protected function getAuthMethodParamsForSimple() {
94
- // Nothing needed
95
- return array();
 
 
 
 
 
 
 
 
 
96
  }
97
 
98
- protected function getAuthMethodParamsForOAuth() {
 
 
 
 
 
 
 
99
 
100
  $oahlp = Mage::helper('searchspring_manager/oauth');
101
  if (!($consumer = $oahlp->getConsumer())) {
@@ -106,10 +140,27 @@ class SearchSpring_Manager_Helper_Webservice extends Mage_Core_Helper_Abstract
106
  $cSecret = $consumer->getSecret();
107
 
108
  return array(
 
109
  'consumerKey' => $cKey,
110
  'consumerSecret' => $cSecret,
111
  'type' => 'magento_indexing',
112
  );
113
  }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
16
  const PATH_FEED_AUTH_METHOD_SET = "/api/manage/feeds/auth-method/%s/set.json";
17
  const PATH_FEED_AUTH_METHOD_VERIFY = "/api/manage/feeds/auth-method/%s/verify.json";
18
 
19
+ const PATH_FEED_SETTINGS_URL_SET = "/api/manage/feeds/settings/indexing-urls";
20
+
21
  // SearchSpring Specific Authentication Codes
22
  const AUTH_METHOD_SIMPLE = 'simple';
23
  const AUTH_METHOD_OAUTH = 'o-auth';
24
 
25
+ protected $_apiBaseUrl;
26
+
27
+ /**
28
+ * Convenience Functions for calling the webservice
29
+ */
30
+
31
+ public function verifyMageAPIAuthSimple($feedId, $creds) {
32
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_VERIFY, self::AUTH_METHOD_SIMPLE);
33
+ $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForSimple($feedId), $creds);
34
  return $this->isResponseStatusSuccess($response);
35
  }
36
 
37
+ public function registerMageAPIAuthSimple($feedId, $creds) {
38
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_SET, self::AUTH_METHOD_SIMPLE);
39
+ $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForSimple($feedId), $creds);
40
+ $this->ensureResponseSuccess($response, __FUNCTION__);
41
  }
42
 
43
+ public function verifyMageAPIAuthOAuth($feedId, $creds) {
44
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_VERIFY, self::AUTH_METHOD_OAUTH);
45
+ $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForOAuth($feedId), $creds);
46
  return $this->isResponseStatusSuccess($response);
47
  }
48
 
49
+ public function registerMageAPIAuthOAuth($feedId, $creds) {
50
  $path = sprintf(self::PATH_FEED_AUTH_METHOD_SET, self::AUTH_METHOD_OAUTH);
51
+ $response = $this->callSearchSpringWebservice($path, $this->getAuthMethodParamsForOAuth($feedId), $creds);
52
+ $this->ensureResponseSuccess($response, __FUNCTION__);
53
  }
54
 
55
+ public function registerMageAPIUrls($feedId, $creds, $feedUrl, $batchUrl, $liveUrl) {
56
+ $params = array(
57
+ 'feedId' => $feedId,
58
+ 'feedUrl' => $feedUrl,
59
+ 'batchUrl' => $batchUrl,
60
+ 'liveUrl' => $liveUrl,
61
+ );
62
+ $response = $this->callSearchSpringWebservice(
63
+ self::PATH_FEED_SETTINGS_URL_SET,
64
+ $params, $creds
65
+ );
66
+ $this->ensureResponseSuccess($response, __FUNCTION__);
67
+ }
68
 
69
+ /**
70
+ * Easy generic way to make a request to the SearchSpring API
71
+ */
72
+ public function callSearchSpringWebservice($path, $params = array(), SearchSpring_Manager_Entity_Credentials $creds = null) {
73
 
74
+ $apiHost = $this->getApiBaseUrl();
 
 
 
75
 
76
  $url = $apiHost . $path;
77
 
79
  'maxredirects' => 0,
80
  'timeout'=>30
81
  ));
 
82
 
83
+ // If credentials were provided
84
+ if ($creds) {
85
+ // ... add them as basic auth
86
+ $client->setAuth( $creds->getUsername(), $creds->getPassword() );
87
+ }
88
 
89
  $client->setParameterGet($params);
90
 
108
  return true;
109
  }
110
 
111
+ protected function ensureResponseSuccess($response, $context = null) {
112
+ if (!$this->isResponseSuccess($response)) {
113
+ $message = '';
114
+ if ($context) {
115
+ $message .= $context . ': ';
116
+ }
117
+ $message .= 'SearchSpring webservice response returned non-success response';
118
+ if (is_object($response)) {
119
+ $message .= ': ' . $response->getBody();
120
+ }
121
+ throw new Exception($message);
122
+ }
123
  }
124
 
125
+ protected function getAuthMethodParamsForSimple($feedId) {
126
+ // Nothing else needed
127
+ return array(
128
+ 'feedId' => $feedId,
129
+ );
130
+ }
131
+
132
+ protected function getAuthMethodParamsForOAuth($feedId) {
133
 
134
  $oahlp = Mage::helper('searchspring_manager/oauth');
135
  if (!($consumer = $oahlp->getConsumer())) {
140
  $cSecret = $consumer->getSecret();
141
 
142
  return array(
143
+ 'feedId' => $feedId,
144
  'consumerKey' => $cKey,
145
  'consumerSecret' => $cSecret,
146
  'type' => 'magento_indexing',
147
  );
148
  }
149
 
150
+ /**
151
+ * To allow caller to specify a different API base URL
152
+ */
153
+
154
+ public function getApiBaseUrl() {
155
+ if (empty($this->_apiBaseUrl)) {
156
+ return Mage::helper('searchspring_manager')->getApiBaseUrl();
157
+ }
158
+ return $this->_apiBaseUrl;
159
+ }
160
+
161
+ public function setApiBaseUrl($url) {
162
+ $this->_apiBaseUrl = $url;
163
+ return $this;
164
+ }
165
+
166
  }
app/code/community/SearchSpring/Manager/Model/Api2/Auth/Adapter.php CHANGED
@@ -19,11 +19,11 @@ class SearchSpring_Manager_Model_Api2_Auth_Adapter extends Mage_Api2_Model_Auth_
19
  $oauthServer = Mage::getModel('searchspring_manager/oauth_server', $request);
20
  $hlp = Mage::helper('searchspring_manager');
21
  $oahlp = Mage::helper('searchspring_manager/oauth');
 
22
 
23
  try {
24
-
25
  // Make sure this Authentication Method is enabled
26
- if ($hlp->getAuthenticationMethod() != 'oauth') {
27
  throw new Mage_Api2_Exception('Not authorized', Mage_Api2_Model_Server::HTTP_UNAUTHORIZED);
28
  }
29
 
19
  $oauthServer = Mage::getModel('searchspring_manager/oauth_server', $request);
20
  $hlp = Mage::helper('searchspring_manager');
21
  $oahlp = Mage::helper('searchspring_manager/oauth');
22
+ $storeCode = $request->getParam('store');
23
 
24
  try {
 
25
  // Make sure this Authentication Method is enabled
26
+ if ($hlp->getAuthenticationMethod($storeCode) != 'oauth') {
27
  throw new Mage_Api2_Exception('Not authorized', Mage_Api2_Model_Server::HTTP_UNAUTHORIZED);
28
  }
29
 
app/code/community/SearchSpring/Manager/Model/Api2/Indexing/Rest/Admin/V1.php CHANGED
@@ -11,12 +11,6 @@ class SearchSpring_Manager_Model_Api2_Indexing_Rest_Admin_V1 extends Mage_Api2_M
11
  */
12
  const COUNT_DEFAULT = 100;
13
 
14
- /**
15
- * Default store if no parameter is set
16
- */
17
- const STORE_DEFAULT = 'default';
18
-
19
-
20
  // Live Indexing
21
  public function _create()
22
  {
@@ -48,9 +42,6 @@ class SearchSpring_Manager_Model_Api2_Indexing_Rest_Admin_V1 extends Mage_Api2_M
48
  */
49
  public function fetchById()
50
  {
51
- // TODO can we get rid of the SearchSpring_Manager_Request_JSON object?
52
- // $request = new SearchSpring_Manager_Request_JSON($this->getRequest());
53
-
54
  $request = $this->getRequest()->getBodyParams();
55
 
56
  if(!is_array($request)) {
@@ -95,15 +86,10 @@ class SearchSpring_Manager_Model_Api2_Indexing_Rest_Admin_V1 extends Mage_Api2_M
95
  return;
96
  }
97
 
98
- if(empty($request['store'])) {
99
- $request['store'] = self::STORE_DEFAULT;
100
- }
101
- $store = $request['store'];
102
-
103
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
104
  (int)$request['size'],
105
  (int)$request['start'],
106
- $store
107
  );
108
 
109
  $params = array('ids' => $ids);
@@ -148,7 +134,7 @@ class SearchSpring_Manager_Model_Api2_Indexing_Rest_Admin_V1 extends Mage_Api2_M
148
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
149
  (int)$this->getRequest()->getParam('count', self::COUNT_DEFAULT),
150
  (int)$this->getRequest()->getParam('start', self::OFFSET_DEFAULT),
151
- $this->getRequest()->getParam('store', self::STORE_DEFAULT)
152
  );
153
 
154
  $params = array('filename' => $uniqueFilename);
11
  */
12
  const COUNT_DEFAULT = 100;
13
 
 
 
 
 
 
 
14
  // Live Indexing
15
  public function _create()
16
  {
42
  */
43
  public function fetchById()
44
  {
 
 
 
45
  $request = $this->getRequest()->getBodyParams();
46
 
47
  if(!is_array($request)) {
86
  return;
87
  }
88
 
 
 
 
 
 
89
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
90
  (int)$request['size'],
91
  (int)$request['start'],
92
+ $this->_getStore()->getCode()
93
  );
94
 
95
  $params = array('ids' => $ids);
134
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
135
  (int)$this->getRequest()->getParam('count', self::COUNT_DEFAULT),
136
  (int)$this->getRequest()->getParam('start', self::OFFSET_DEFAULT),
137
+ $this->_getStore()->getCode()
138
  );
139
 
140
  $params = array('filename' => $uniqueFilename);
app/code/community/SearchSpring/Manager/Model/Config.php ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File Config.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Model_Config
10
+ *
11
+ * You should need to put anything in this class, but Magento needs to to function.
12
+ *
13
+ * @author Jake Shelby <jake@b7interactive.com>
14
+ */
15
+ class SearchSpring_Manager_Model_Config
16
+ {
17
+
18
+ const ENV_VAR_API_BASE_URL = 'SEARCHSPRING_API_HOST';
19
+
20
+ const PATH_API_FEED_ID = 'ssmanager/ssmanager_api/feed_id';
21
+ const PATH_API_SITE_ID = 'ssmanager/ssmanager_api/site_id';
22
+ const PATH_API_SECRET_KEY = 'ssmanager/ssmanager_api/secret_key';
23
+
24
+ const PATH_API_AUTHENTICATION_METHOD = 'ssmanager/ssmanager_api/authentication_method';
25
+ const AUTH_METHOD_SIMPLE = 'simple';
26
+ const AUTH_METHOD_OAUTH = 'oauth';
27
+
28
+ const PATH_GLOBAL_LIVE_INDEXING_ENABLE_FL = 'ssmanager/ssmanager_general/live_indexing';
29
+ const PATH_INDEX_ZERO_PRICE = 'ssmanager/ssmanager_general/index_zero_price';
30
+ const PATH_INDEX_OUT_OF_STOCK = 'ssmanager/ssmanager_general/index_out_of_stock';
31
+
32
+ const PATH_FEED_SETTING_PATH = 'ssmanager/ssmanager_feed/feed_path';
33
+
34
+ const PATH_SALES_RANK_TIMESPAN = 'ssmanager/ssmanager_sales_rank/timespan';
35
+
36
+ const PATH_GLOBAL_CATEGORY_ENABLE_FL = 'ssmanager/ssmanager_catalog/enable_categories';
37
+
38
+ const PATH_GENERATE_CACHE_IMAGES = 'ssmanager/ssmanager_images/generate_cache_images';
39
+ const PATH_IMAGE_WIDTH = 'ssmanager/ssmanager_images/image_width';
40
+ const PATH_IMAGE_HEIGHT = 'ssmanager/ssmanager_images/image_height';
41
+
42
+ const PATH_GENERATE_SWATCH_IMAGES = 'ssmanager/ssmanager_images/generate_swatch_images';
43
+ const PATH_SWATCH_WIDTH = 'ssmanager/ssmanager_images/swatch_width';
44
+ const PATH_SWATCH_HEIGHT = 'ssmanager/ssmanager_images/swatch_height';
45
+
46
+ const PATH_UUID = 'ssmanager/ssmanager_track/uuid';
47
+
48
+ // TODO -- should these things be a part of the configuration class??
49
+ const MANAGER_API_PATH_PRODUCT_SIMPLE = 'searchspring/generate/index';
50
+ const MANAGER_API_PATH_GENERATE_SIMPLE = 'searchspring/generate/feed';
51
+ const MANAGER_API_PATH_PRODUCT_OAUTH = 'api/rest/searchspring/index';
52
+ const MANAGER_API_PATH_GENERATE_OAUTH = 'api/rest/searchspring/feed';
53
+
54
+ /**
55
+ * Magento App Container
56
+ *
57
+ * @var Mage_Core_Model_App $app
58
+ */
59
+ protected $app;
60
+
61
+ /**
62
+ * New SearchSpring Config Model
63
+ *
64
+ * @var Mage_Core_Model_App $app
65
+ */
66
+ public function __construct(Mage_Core_Model_App $app)
67
+ {
68
+ $this->app = $app;
69
+ }
70
+
71
+ /**
72
+ * Retrieve config value for store by path
73
+ *
74
+ * @param string $path
75
+ * @param mixed $store
76
+ * @return mixed
77
+ */
78
+ public function getStoreConfig($path, $store = null)
79
+ {
80
+ return $this->app->getStore($store)->getConfig($path);
81
+ }
82
+
83
+ /**
84
+ * Retrieve config flag for store by path
85
+ *
86
+ * @param string $path
87
+ * @param mixed $store
88
+ * @return bool
89
+ */
90
+ public function getStoreConfigFlag($path, $store = null)
91
+ {
92
+ $flag = strtolower($this->getStoreConfig($path, $store));
93
+ if (!empty($flag) && 'false' !== $flag) {
94
+ return true;
95
+ } else {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Protected Configuration
102
+ */
103
+ public function getVersion() {
104
+ return $this->app->getConfig()->getNode('modules/SearchSpring_Manager/version');
105
+ }
106
+
107
+ public function getApiBaseUrl()
108
+ {
109
+ if($env = getenv(self::ENV_VAR_API_BASE_URL)) {
110
+ return $env;
111
+ } else {
112
+ return $this->app->getConfig()->getNode('global/searchspring/api_host');
113
+ }
114
+ }
115
+
116
+ public function getUUID() {
117
+ return $this->getStoreConfig(self::PATH_UUID);
118
+ }
119
+
120
+ /**
121
+ * Store Level Only Configuration
122
+ */
123
+
124
+ public function getApiSiteId($store)
125
+ {
126
+ return $this->getStoreConfig(self::PATH_API_SITE_ID, $store);
127
+ }
128
+
129
+ public function getApiSecretKey($store)
130
+ {
131
+ return $this->getStoreConfig(self::PATH_API_SECRET_KEY, $store);
132
+ }
133
+
134
+ public function getApiFeedId($store)
135
+ {
136
+ return $this->getStoreConfig(self::PATH_API_FEED_ID, $store);
137
+ }
138
+
139
+ public function getAuthenticationMethod($store)
140
+ {
141
+ return $this->getStoreConfig(self::PATH_API_AUTHENTICATION_METHOD, $store);
142
+ }
143
+
144
+ /**
145
+ * General Configuration Getters
146
+ */
147
+
148
+ public function isLiveIndexingEnabled($store = null)
149
+ {
150
+ return $this->getStoreConfigFlag(self::PATH_GLOBAL_LIVE_INDEXING_ENABLE_FL, $store);
151
+ }
152
+
153
+ public function isZeroPriceIndexingEnabled($store = null)
154
+ {
155
+ return $this->getStoreConfigFlag(self::PATH_INDEX_ZERO_PRICE, $store);
156
+ }
157
+
158
+ public function isOutOfStockIndexingEnabled($store = null)
159
+ {
160
+ return $this->getStoreConfigFlag(self::PATH_INDEX_OUT_OF_STOCK, $store);
161
+ }
162
+
163
+ public function getSalesRankTimespan($store = null)
164
+ {
165
+ return $this->getStoreConfig(self::PATH_SALES_RANK_TIMESPAN, $store);
166
+ }
167
+
168
+ public function getFeedPath($store = null)
169
+ {
170
+ return $this->getStoreConfig(self::PATH_FEED_SETTING_PATH, $store);
171
+ }
172
+
173
+ public function isCacheImagesEnabled($store = null)
174
+ {
175
+ return $this->getStoreConfig(self::PATH_GENERATE_CACHE_IMAGES, $store);
176
+ }
177
+
178
+ public function getImageHeight($store = null)
179
+ {
180
+ return $this->getStoreConfig(self::PATH_IMAGE_HEIGHT, $store);
181
+ }
182
+
183
+ public function getImageWidth($store = null)
184
+ {
185
+ return $this->getStoreConfig(self::PATH_IMAGE_WIDTH, $store);
186
+ }
187
+
188
+ public function isSwatchImagesEnabled($store = null)
189
+ {
190
+ return $this->getStoreConfig(self::PATH_GENERATE_SWATCH_IMAGES, $store);
191
+ }
192
+
193
+ public function getSwatchHeight($store = null)
194
+ {
195
+ return $this->getStoreConfig(self::PATH_SWATCH_HEIGHT, $store);
196
+ }
197
+
198
+ public function getSwatchWidth($store = null)
199
+ {
200
+ return $this->getStoreConfig(self::PATH_SWATCH_WIDTH, $store);
201
+ }
202
+
203
+
204
+ // This config is not really used yet
205
+ public function isCategorySearchEnabled()
206
+ {
207
+ return $this->getStoreConfigFlag(self::PATH_GLOBAL_CATEGORY_ENABLE_FL);
208
+ }
209
+
210
+ /**
211
+ * Dynamic / Combined Configuration Getters
212
+ */
213
+
214
+ public function isStoreSetup($store) {
215
+ // Make sure these things are configured for the store
216
+ return (
217
+ $this->getApiSiteId($store) &&
218
+ $this->getApiSecretKey($store) &&
219
+ $this->getApiFeedId($store) &&
220
+ $this->getAuthenticationMethod($store)
221
+ );
222
+ }
223
+
224
+ // Alias for isStoreSetup
225
+ public function isStoreConfigured($store) {
226
+ return $this->isStoreSetup($store);
227
+ }
228
+
229
+ public function getApiCredentials($store) {
230
+ return new SearchSpring_Manager_Entity_RequestCredentials(
231
+ $this->getApiSiteId($store),
232
+ $this->getApiSecretKey($store)
233
+ );
234
+ }
235
+
236
+ public function getMageApiCredentials($store) {
237
+ return new SearchSpring_Manager_Entity_RequestCredentials(
238
+ $this->getApiFeedId($store),
239
+ $this->getApiSecretKey($store)
240
+ );
241
+ }
242
+
243
+ public function getMageAPIPathGenerate($store) {
244
+ switch ($this->getAuthenticationMethod($store)) {
245
+ case self::AUTH_METHOD_SIMPLE:
246
+ return self::MANAGER_API_PATH_GENERATE_SIMPLE;
247
+ case self::AUTH_METHOD_OAUTH:
248
+ $params = '?store=' . $this->app->getStore($store)->getCode();
249
+ return self::MANAGER_API_PATH_GENERATE_OAUTH . $params;
250
+ }
251
+ return false;
252
+ }
253
+
254
+ public function getMageAPIPathProduct($store) {
255
+ switch ($this->getAuthenticationMethod($store)) {
256
+ case self::AUTH_METHOD_SIMPLE:
257
+ return self::MANAGER_API_PATH_PRODUCT_SIMPLE;
258
+ case self::AUTH_METHOD_OAUTH:
259
+ $params = '?store=' . $this->app->getStore($store)->getCode();
260
+ return self::MANAGER_API_PATH_PRODUCT_OAUTH . $params;
261
+ }
262
+ return false;
263
+ }
264
+
265
+ public function getMageAPIUrlGenerate($store) {
266
+ return $this->app->getStore($store)
267
+ ->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, true) .
268
+ $this->getMageAPIPathGenerate($store);
269
+ }
270
+
271
+ public function getMageAPIUrlProduct($store) {
272
+ return $this->app->getStore($store)
273
+ ->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, true) .
274
+ $this->getMageAPIPathProduct($store);
275
+ }
276
+
277
+ public function getMageUrlGeneratedFeed($store) {
278
+ $filename = SearchSpring_Manager_Writer_Product_Params_FileWriterParams::getBaseFilename($store);
279
+ $feedFilePath = rtrim($this->getFeedPath($store), '/') . '/' . $filename;
280
+
281
+ return $this->app->getStore($store)
282
+ ->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, false) . $feedFilePath;
283
+ }
284
+
285
+ }
app/code/community/SearchSpring/Manager/Model/Layer.php CHANGED
@@ -28,7 +28,7 @@ class SearchSpring_Manager_Model_Layer extends Mage_Catalog_Model_Layer
28
  $collection = $this->_productCollections[$this->getCurrentCategory()->getId()];
29
  } else {
30
  $apiFactory = new SearchSpring_Manager_Factory_ApiFactory();
31
- $api = $apiFactory->make('search');
32
 
33
  $searchRequestBodyFactory = new SearchSpring_Manager_Factory_SearchRequestBodyFactory();
34
  $searchRequestBody = $searchRequestBodyFactory->make();
28
  $collection = $this->_productCollections[$this->getCurrentCategory()->getId()];
29
  } else {
30
  $apiFactory = new SearchSpring_Manager_Factory_ApiFactory();
31
+ $api = $apiFactory->makeSearchAdapter();
32
 
33
  $searchRequestBodyFactory = new SearchSpring_Manager_Factory_SearchRequestBodyFactory();
34
  $searchRequestBody = $searchRequestBodyFactory->make();
app/code/community/SearchSpring/Manager/Model/Observer.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Observer.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Model_Observer
10
+ *
11
+ * @author Jake Shelby <jake@b7interactive.com>
12
+ */
13
+ abstract class SearchSpring_Manager_Model_Observer
14
+ {
15
+
16
+ protected function notifyAdminUser($message)
17
+ {
18
+ // Only if we're in the admin panel
19
+ if (Mage::app()->getStore()->isAdmin()) {
20
+ $session = Mage::getSingleton('adminhtml/session');
21
+ $session->addWarning($message);
22
+ }
23
+ return $this;
24
+ }
25
+
26
+ }
app/code/community/SearchSpring/Manager/Model/Observer/CategorySaveObserver.php CHANGED
@@ -8,32 +8,38 @@
8
  /**
9
  * Class SearchSpring_Manager_Model_Observer_CategorySaveObserver
10
  *
11
- * On a category change, trigger one of these methods
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
 
14
  */
15
- class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpring_Manager_Model_Observer_LiveIndexer
16
  {
 
17
  /**
18
- * Performs operations on a Varien_Object
19
  *
20
- * @var SearchSpring_Manager_VarienObject_Data $varienObjectData
21
  */
22
- private $varienObjectData;
23
 
24
  /**
25
- * Create a request body
26
  *
27
- * @var SearchSpring_Manager_Factory_IndexingRequestBodyFactory $requestBodyFactory
28
  */
29
- private $requestBodyFactory;
30
 
31
  /**
32
  * Constructor
 
 
33
  */
34
  public function __construct()
35
  {
36
- $this->requestBodyFactory = new SearchSpring_Manager_Factory_IndexingRequestBodyFactory();
 
 
37
  $this->varienObjectData = new SearchSpring_Manager_VarienObject_Data();
38
  }
39
 
@@ -44,150 +50,122 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
44
  *
45
  * @todo implement status change
46
  *
47
- * @param Varien_Event_Observer $productEvent
48
  *
49
- * @return bool
 
50
  */
51
  public function afterSaveUpdateProductCategory(Varien_Event_Observer $productEvent)
52
  {
53
- if (!$this->isEnabled()) {
54
- return true;
55
- }
56
 
57
- /** @var Mage_Catalog_Model_Category $category */
58
- $category = $productEvent->getData('category');
59
 
60
- // if this is a new category return because we already sent the ids
61
- if (true === $this->varienObjectData->isNew($category)) {
62
- return true;
63
- }
64
 
65
- $updates = $this->varienObjectData->findUpdatedData($category);
66
 
67
- // if category name is not changed, this will not affect the product categories
68
- if (!isset($updates['name']) && !isset($updates['is_active'])) {
69
- return true;
70
- }
71
 
72
- $requestBody = $this->requestBodyFactory->make(
73
- SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY,
74
- $category->getAllChildren(true)
75
- );
76
- $this->apiPushProductIds($requestBody);
77
 
78
- return true;
 
 
79
  }
80
 
81
  /**
82
- * After category products have changed
83
  *
84
- * This is triggered when products are checked or unchecked for a category. Only affects products of category.
 
85
  *
86
- * @param Varien_Event_Observer $productEvent
87
  *
88
- * @return bool
 
89
  */
90
- public function afterProductChangeUpdateProductCategory(Varien_Event_Observer $productEvent)
91
  {
92
- if (!$this->isEnabled()) {
93
- return true;
94
- }
95
 
96
- $validator = new SearchSpring_Manager_Validator_ProductValidator();
 
97
 
98
- $productIds = $productEvent->getData('product_ids');
99
- foreach($productIds as $k => $productId) {
100
- $product = Mage::getModel('catalog/product')->load($productId);
101
- if(!$validator->isValid($product)) {
102
- unset($productIds[$k]);
103
  }
104
- }
105
-
106
- $requestBody = $this->requestBodyFactory->make(
107
- SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT,
108
- $productIds
109
- );
110
 
111
- $this->apiPushProductIds($requestBody);
112
 
113
- return true;
 
 
114
  }
115
 
116
  /**
117
- * After a category is moved
118
  *
119
- * Updates products and sub-products. Only update if path is changed. This should be double checked, but path
120
- * should not be changed if the category is just reordered.
121
  *
122
  * @param Varien_Event_Observer $productEvent
123
- *
124
- * @return bool
125
  */
126
- public function afterMoveUpdateProductCategory(Varien_Event_Observer $productEvent)
127
  {
128
- if (!$this->isEnabled()) {
129
- return true;
130
- }
131
-
132
- $category = $productEvent->getData('category');
133
- $updates = $this->varienObjectData->findUpdatedData($category);
134
 
135
- if (!isset($updates['path'])) {
136
- return true;
137
- }
138
 
139
- $requestBody = $this->requestBodyFactory->make(
140
- SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY,
141
- $category->getAllChildren(true)
142
- );
143
- $this->apiPushProductIds($requestBody);
144
 
145
- return true;
 
 
146
  }
147
 
148
  /**
149
- * After a category is deleted
150
  *
151
- * Remove the category from products and sub-products.
 
 
152
  *
153
  * @param Varien_Event_Observer $productEvent
154
- * @return bool
155
  */
156
- public function afterDeleteUpdateProductCategory(Varien_Event_Observer $productEvent)
157
  {
158
- if (!$this->isEnabled()) {
159
- return true;
160
- }
161
 
162
- /** @var Mage_Catalog_Model_Category $category */
163
- $category = $productEvent->getData('category');
164
- $requestBody = $this->requestBodyFactory->make(
165
- SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT,
166
- $this->getCategoryProductIds($category)
167
- );
168
- $this->apiPushProductIds($requestBody);
169
 
170
- return true;
 
 
 
 
171
  }
172
 
173
- /**
174
- * Get product ids for category and subcategories
175
- *
176
- * @param Mage_Catalog_Model_Category $category
177
- *
178
- * @return array
179
- */
180
- private function getCategoryProductIds(Mage_Catalog_Model_Category $category)
181
  {
182
- /** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
183
- $collection = mage::getModel('catalog/product')->getCollection()
184
- ->addAttributeToSelect('*')
185
- ->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id=entity_id', null, 'left')
186
- ->addAttributeToFilter('category_id', array('in' => $category->getAllChildren(true)));
187
- $collection->getSelect()->group('e.entity_id');
188
- $productIds = $collection->load()->getAllIds();
189
-
190
- return $productIds;
191
  }
192
 
193
  }
8
  /**
9
  * Class SearchSpring_Manager_Model_Observer_CategorySaveObserver
10
  *
11
+ * Listens for category change events and forwards to the Live Indexing Service
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
14
+ * @author Jake Shelby <jake@b7interactive.com>
15
  */
16
+ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpring_Manager_Model_Observer
17
  {
18
+
19
  /**
20
+ * Live Indexer Service
21
  *
22
+ * @var SearchSpring_Manager_Service_LiveIndexer $liveIndexer
23
  */
24
+ private $liveIndexer;
25
 
26
  /**
27
+ * Performs operations on a Varien_Object
28
  *
29
+ * @var SearchSpring_Manager_VarienObject_Data $varienObjectData
30
  */
31
+ private $varienObjectData;
32
 
33
  /**
34
  * Constructor
35
+ *
36
+ * We need to do some setup in here because there's no way to inject dependencies
37
  */
38
  public function __construct()
39
  {
40
+ $factory = new SearchSpring_Manager_Factory_LiveIndexerFactory;
41
+ $this->liveIndexer = $factory->make();
42
+
43
  $this->varienObjectData = new SearchSpring_Manager_VarienObject_Data();
44
  }
45
 
50
  *
51
  * @todo implement status change
52
  *
53
+ * @event catalog_category_save_commit_after
54
  *
55
+ * @param Varien_Event_Observer $productEvent
56
+ * @return void
57
  */
58
  public function afterSaveUpdateProductCategory(Varien_Event_Observer $productEvent)
59
  {
60
+ try {
 
 
61
 
62
+ $category = $productEvent->getCategory();
 
63
 
64
+ // if this is a new category return because we already sent the ids
65
+ if (true === $this->varienObjectData->isNew($category)) {
66
+ return true;
67
+ }
68
 
69
+ $updates = $this->varienObjectData->findUpdatedData($category);
70
 
71
+ // If category name is not changed, this will not affect the product categories
72
+ if (!isset($updates['name']) && !isset($updates['is_active'])) {
73
+ return true;
74
+ }
75
 
76
+ $this->liveIndexer->categorySaved($category);
 
 
 
 
77
 
78
+ } catch (Exception $e) {
79
+ $this->handleException($e);
80
+ }
81
  }
82
 
83
  /**
84
+ * After a category is moved
85
  *
86
+ * Updates products and sub-products. Only update if path is changed. This should be double checked, but path
87
+ * should not be changed if the category is just reordered.
88
  *
89
+ * @event category_move
90
  *
91
+ * @param Varien_Event_Observer $productEvent
92
+ * @return void
93
  */
94
+ public function afterMoveUpdateProductCategory(Varien_Event_Observer $productEvent)
95
  {
96
+ try {
 
 
97
 
98
+ $category = $productEvent->getCategory();
99
+ $updates = $this->varienObjectData->findUpdatedData($category);
100
 
101
+ if (!isset($updates['path'])) {
102
+ return;
 
 
 
103
  }
 
 
 
 
 
 
104
 
105
+ $this->liveIndexer->categorySaved($category);
106
 
107
+ } catch (Exception $e) {
108
+ $this->handleException($e);
109
+ }
110
  }
111
 
112
  /**
113
+ * Right before a category is deleted. Remove the category from products and sub-products.
114
  *
115
+ * @event catalog_controller_category_delete
 
116
  *
117
  * @param Varien_Event_Observer $productEvent
118
+ * @return void
 
119
  */
120
+ public function beforeDeleteUpdateProductCategory(Varien_Event_Observer $productEvent)
121
  {
122
+ try {
 
 
 
 
 
123
 
124
+ $category = $productEvent->getCategory();
 
 
125
 
126
+ // TODO -- ?? should we add the token dance here as well ?? might have a problem with the race condition here
127
+ $this->liveIndexer->categoryDeleted($category);
 
 
 
128
 
129
+ } catch (Exception $e) {
130
+ $this->handleException($e);
131
+ }
132
  }
133
 
134
  /**
135
+ * After category products have changed
136
  *
137
+ * This is triggered when products are checked or unchecked for a category. Only affects products of category.
138
+ *
139
+ * @event catalog_category_change_products
140
  *
141
  * @param Varien_Event_Observer $productEvent
142
+ * @return void
143
  */
144
+ public function afterProductChangeUpdateProductCategory(Varien_Event_Observer $productEvent)
145
  {
146
+ try {
 
 
147
 
148
+ $category = $productEvent->getCategory();
149
+ $productIds = $productEvent->getProductIds();
 
 
 
 
 
150
 
151
+ $this->liveIndexer->categoryProductsUpdated($category, $productIds);
152
+
153
+ } catch (Exception $e) {
154
+ $this->handleException($e);
155
+ }
156
  }
157
 
158
+ protected function handleException(Exception $e)
 
 
 
 
 
 
 
159
  {
160
+ // TODO - is there a mage exception we can catch to send a better message to admin??
161
+
162
+ // Get some kind of context info
163
+
164
+ // log what happened
165
+ Mage::logException($e);
166
+
167
+ // Get the best message for the admin user, and notify
168
+ $this->notifyAdminUser("SearchSpring: There was a live indexing issue, please contact SearchSpring support for assistance.");
169
  }
170
 
171
  }
app/code/community/SearchSpring/Manager/Model/Observer/ConfigObserver.php CHANGED
@@ -11,10 +11,24 @@
11
  * On a category change, trigger one of these methods
12
  *
13
  */
14
- class SearchSpring_Manager_Model_Observer_ConfigObserver {
 
15
 
16
  const NEW_CONNECTION_SETUP_PARAM = 'searchspring_new_connection_fl';
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  public function afterSystemConfigSectionChanged(Varien_Event_Observer $event) {
19
 
20
  $hlp = Mage::helper('core');
@@ -24,25 +38,25 @@ class SearchSpring_Manager_Model_Observer_ConfigObserver {
24
  return;
25
  }
26
 
27
- if ($this->hasNewConnectionBeenSetup()) {
 
 
 
28
 
29
  // TODO - should we require admin permissions here
30
 
31
  try {
32
 
33
  // Initialize Resources needed for auth method
34
- $this->_initializeAuthMethod();
35
 
36
- // Register Auth Method With Search Spring API
37
- $this->_registerAuthMethodWithSearchSpring();
38
 
39
  } catch (Exception $e) {
40
 
41
  Mage::logException($e);
42
-
43
- $message = $hlp->__('There was a problem while attempting to setup your SearchSpring account [E938]');
44
- $session = Mage::getSingleton('adminhtml/session');
45
- $session->addWarning($message);
46
 
47
  }
48
 
@@ -50,27 +64,20 @@ class SearchSpring_Manager_Model_Observer_ConfigObserver {
50
 
51
  }
52
 
53
- public function hasNewConnectionBeenSetup() {
54
- $param = Mage::app()->getRequest()->getParam(self::NEW_CONNECTION_SETUP_PARAM);
55
- return (bool) $param;
56
- }
57
-
58
- protected function _initializeAuthMethod() {
59
-
60
  $hlp = Mage::helper('searchspring_manager');
61
- switch ($hlp->getAuthenticationMethod()) {
62
 
63
- case SearchSpring_Manager_Helper_Data::AUTH_METHOD_SIMPLE:
 
 
64
  // Nothing to initialize for this auth method
65
  break;
66
 
67
- case SearchSpring_Manager_Helper_Data::AUTH_METHOD_OAUTH:
68
  // Initialize oAuth Resources for API access
69
  $this->_initializeOAuthResources();
70
  break;
71
-
72
  }
73
-
74
  }
75
 
76
  protected function _initializeOAuthResources() {
@@ -78,17 +85,12 @@ class SearchSpring_Manager_Model_Observer_ConfigObserver {
78
  $oahlp->ensureOAuthResourcesInitialized();
79
  }
80
 
81
- protected function _registerAuthMethodWithSearchSpring() {
82
 
83
- $success = Mage::helper('searchspring_manager')
84
- ->registerMagentoAPIAuthenticationWithSearchSpring();
85
 
86
- if (!$success) {
87
- $hlp = Mage::helper('core');
88
- $message = $hlp->__('There was a problem while attempting to setup your SearchSpring account [E939]');
89
- $session = Mage::getSingleton('adminhtml/session');
90
- $session->addWarning($message);
91
- }
92
 
93
  }
94
 
11
  * On a category change, trigger one of these methods
12
  *
13
  */
14
+ class SearchSpring_Manager_Model_Observer_ConfigObserver extends SearchSpring_Manager_Model_Observer
15
+ {
16
 
17
  const NEW_CONNECTION_SETUP_PARAM = 'searchspring_new_connection_fl';
18
 
19
+ public function hasNewConnectionBeenSetup() {
20
+ $param = Mage::app()->getRequest()->getParam(self::NEW_CONNECTION_SETUP_PARAM);
21
+ return (bool) $param;
22
+ }
23
+
24
+ public function getStore() {
25
+ $configModel = Mage::getSingleton('adminhtml/config_data');
26
+ if ($store = $configModel->getStore()) {
27
+ return Mage::app()->getStore($store);
28
+ }
29
+ return null;
30
+ }
31
+
32
  public function afterSystemConfigSectionChanged(Varien_Event_Observer $event) {
33
 
34
  $hlp = Mage::helper('core');
38
  return;
39
  }
40
 
41
+ // Get the store context configuration being edited
42
+ $store = $this->getStore();
43
+
44
+ if ($this->hasNewConnectionBeenSetup() && $store) {
45
 
46
  // TODO - should we require admin permissions here
47
 
48
  try {
49
 
50
  // Initialize Resources needed for auth method
51
+ $this->_initializeAuthMethod($store);
52
 
53
+ // Register store/authType specific urls with Search Spring
54
+ $this->_registerWithSearchSpring($store);
55
 
56
  } catch (Exception $e) {
57
 
58
  Mage::logException($e);
59
+ $this->notifyAdminUser('There was a problem while attempting to setup your SearchSpring account [E938]');
 
 
 
60
 
61
  }
62
 
64
 
65
  }
66
 
67
+ protected function _initializeAuthMethod($store) {
 
 
 
 
 
 
68
  $hlp = Mage::helper('searchspring_manager');
 
69
 
70
+ switch ($hlp->getAuthenticationMethod($store)) {
71
+
72
+ case SearchSpring_Manager_Model_Config::AUTH_METHOD_SIMPLE:
73
  // Nothing to initialize for this auth method
74
  break;
75
 
76
+ case SearchSpring_Manager_Model_Config::AUTH_METHOD_OAUTH:
77
  // Initialize oAuth Resources for API access
78
  $this->_initializeOAuthResources();
79
  break;
 
80
  }
 
81
  }
82
 
83
  protected function _initializeOAuthResources() {
85
  $oahlp->ensureOAuthResourcesInitialized();
86
  }
87
 
88
+ protected function _registerWithSearchSpring($store) {
89
 
90
+ $hlp = Mage::helper('searchspring_manager');
 
91
 
92
+ // Register Store configuration With Search Spring
93
+ $hlp->registerMagentoAPIWithSearchSpring($store);
 
 
 
 
94
 
95
  }
96
 
app/code/community/SearchSpring/Manager/Model/Observer/LiveIndexer.php DELETED
@@ -1,64 +0,0 @@
1
- <?php
2
- /**
3
- * LiveIndexer.php
4
- *
5
- * @copyright B7 Interactive, LLC. All Rights Reserved.
6
- */
7
-
8
- /**
9
- * Class SearchSpring_Manager_Model_Observer_LiveIndexer
10
- *
11
- * @author Nate Brunette <nate@b7interactive.com>
12
- */
13
- abstract class SearchSpring_Manager_Model_Observer_LiveIndexer
14
- {
15
-
16
- /**
17
- * Api adapter for connecting to SearchSpring api
18
- *
19
- * @var SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter $api
20
- */
21
- protected $api;
22
-
23
- public function api()
24
- {
25
- if (is_null($this->api)) {
26
- $apiFactory = new SearchSpring_Manager_Factory_ApiFactory();
27
- $this->api = $apiFactory->make('index');
28
- }
29
- return $this->api;
30
- }
31
-
32
- /**
33
- * Checks if live indexing is enabled
34
- *
35
- * @return bool
36
- */
37
- protected function isEnabled()
38
- {
39
- return Mage::helper('searchspring_manager')->isLiveIndexingEnabled();
40
- }
41
-
42
- protected function apiPushProductIds($request)
43
- {
44
- try {
45
- $this->api()->pushIds($request);
46
- } catch (UnexpectedValueException $e) {
47
- $this->notifyAdminUser($e->getMessage());
48
- } catch (Exception $e) {
49
- $this->notifyAdminUser("SearchSpring: Unknown issue occurred while attempting live indexing operation.");
50
- Mage::logException($e);
51
- }
52
- }
53
-
54
- protected function notifyAdminUser($message)
55
- {
56
- // Only if we're in the admin panel
57
- if (Mage::app()->getStore()->isAdmin()) {
58
- $session = Mage::getSingleton('adminhtml/session');
59
- $session->addWarning($message);
60
- }
61
- return $this;
62
- }
63
-
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/code/community/SearchSpring/Manager/Model/Observer/ProductSaveObserver.php CHANGED
@@ -8,25 +8,27 @@
8
  /**
9
  * Class SearchSpring_Manager_Model_Observer_ProductSaveObserver
10
  *
11
- * Listens for product save events and pushes ids to api
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
 
14
  */
15
- class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpring_Manager_Model_Observer_LiveIndexer
16
  {
17
 
18
  /**
19
- * Creates a request body
20
  *
21
- * @var SearchSpring_Manager_Factory_IndexingRequestBodyFactory
22
  */
23
- private $requestBodyFactory;
24
 
25
  /**
26
- * List of parentIds for the updated product.
27
- * @var array
 
28
  */
29
- protected $_productIds = array();
30
 
31
  /**
32
  * Constructor
@@ -35,149 +37,93 @@ class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpr
35
  */
36
  public function __construct()
37
  {
38
- $this->requestBodyFactory = new SearchSpring_Manager_Factory_IndexingRequestBodyFactory();
 
39
  }
40
 
41
  /**
42
- * After a product is saved, push that product to the SearchSpring API
43
- *
44
- * @param Varien_Event_Observer $productEvent The product event data
45
- *
46
- * @return bool
47
- */
48
- public function afterSavePushProduct(Varien_Event_Observer $productEvent)
49
  {
50
- if (!$this->isEnabled()) {
51
- return true;
52
- }
53
 
54
- $product = $productEvent->getData('product');
55
- $this->_productIds = $this->_getProductIds($product);
56
 
57
- $this->_handleProductChange();
58
- return true;
59
- }
60
 
61
- /**
62
- * Before a product is deleted grab all IDs
63
- *
64
- * @param Varien_Event_Observer $productEvent The product event data
65
- *
66
- * @return bool
67
- */
68
- public function beforeDeletePushProduct(Varien_Event_Observer $productEvent) {
69
 
70
- if (!$this->isEnabled()) {
71
- return true;
72
- }
73
 
74
- $product = $productEvent->getData('product');
75
- $this->_productIds = $this->_getProductIds($product);
76
- return true;
77
  }
78
 
79
  /**
80
- * After a product is deleted, push that product delete to the SearchSpring API
81
  *
82
  * @param Varien_Event_Observer $productEvent The product event data
83
- *
84
- * @return bool
85
  */
86
  public function afterDeletePushProduct(Varien_Event_Observer $productEvent)
87
  {
88
- if (!$this->isEnabled()) {
89
- return true;
90
- }
91
-
92
- $this->_handleProductChange();
93
- return true;
94
- }
95
 
96
- protected function _handleProductChange() {
97
- $productIds = $this->_getActions();
98
 
99
- // create the request body with product ids
100
- if(!empty($productIds['delete'])) {
101
- $requestBody = $this->requestBodyFactory->make(
102
- SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT,
103
- $productIds['delete'],
104
- true
105
- );
106
- }
107
 
108
- // create the request body with product ids
109
- if(!empty($productIds['update'])) {
110
- $requestBody = $this->requestBodyFactory->make(
111
- SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT,
112
- $productIds['update']
113
- );
114
- }
115
 
116
- // send ids to api
117
- $this->apiPushProductIds($requestBody);
118
 
119
- return true;
 
 
120
  }
121
 
122
- protected function _getProductIds($product) {
123
- $productIds = array();
124
- // If the product is a simple product we may need to check its parent(s)
125
- if($product->getTypeId() == "simple"){
126
- // Check for configurable parent
127
- $productIds = Mage::getModel('catalog/product_type_configurable')->getParentIdsByChild($product->getId());
 
 
 
128
 
129
- // If there are no configurable parents check grouped
130
- if(empty($productIds)) {
131
- $productIds = Mage::getModel('catalog/product_type_grouped')->getParentIdsByChild($product->getId());
132
- }
133
- }
134
 
135
- $productIds[] = $product->getId();
136
 
137
- return $productIds;
 
 
138
  }
139
 
 
 
 
140
 
141
- /**
142
- * Get list of update/deletes for product and parents.
143
- *
144
- * @param Mage_Catalog_Model_Product $product The product being updated
145
- *
146
- * @return array
147
- */
148
- protected function _getActions() {
149
- $productActions = array();
150
-
151
- $validator = new SearchSpring_Manager_Validator_ProductValidator();
152
-
153
- // Figure out deletes
154
- foreach($this->_productIds as $productId) {
155
- $product = Mage::getModel('catalog/product')->load($productId);
156
-
157
- $isValid = $validator->isValid(
158
- $product
159
- );
160
-
161
- if(!$isValid) {
162
- $productActions['delete'][] = $productId;
163
- } else {
164
- $pricingStrategy = SearchSpring_Manager_Factory_PricingFactory::make($product);
165
- $pricingStrategy->calculatePrices();
166
-
167
- // Only check should delete if the product is otherwise valid
168
- $shouldDelete = $validator->shouldDelete(
169
- $product,
170
- $pricingStrategy
171
- );
172
-
173
- if($shouldDelete) {
174
- $productActions['delete'][] = $productId;
175
- } else {
176
- $productActions['update'][] = $productId;
177
- }
178
- }
179
- }
180
 
181
- return $productActions;
 
182
  }
 
183
  }
8
  /**
9
  * Class SearchSpring_Manager_Model_Observer_ProductSaveObserver
10
  *
11
+ * Listens for product change events and forwards to the Live Indexing Service
12
  *
13
  * @author Nate Brunette <nate@b7interactive.com>
14
+ * @author Jake Shelby <jake@b7interactive.com>
15
  */
16
+ class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpring_Manager_Model_Observer
17
  {
18
 
19
  /**
20
+ * Live Indexer Service
21
  *
22
+ * @var SearchSpring_Manager_Service_LiveIndexer $liveIndexer
23
  */
24
+ private $liveIndexer;
25
 
26
  /**
27
+ * Deletion Tokens queue
28
+ *
29
+ * @var SearchSpring_Manager_Entity_ProductDeletionToken[] $deletionTokens
30
  */
31
+ protected $deletionTokens = array();
32
 
33
  /**
34
  * Constructor
37
  */
38
  public function __construct()
39
  {
40
+ $factory = new SearchSpring_Manager_Factory_LiveIndexerFactory;
41
+ $this->liveIndexer = $factory->make();
42
  }
43
 
44
  /**
45
+ * Before a product is deleted obtain a token for deletion
46
+ *
47
+ * @param Varien_Event_Observer $productEvent The product event data
48
+ * @return void
49
+ */
50
+ public function beforeDeletePushProduct(Varien_Event_Observer $productEvent)
 
51
  {
52
+ try {
 
 
53
 
54
+ $product = $productEvent->getProduct();
 
55
 
56
+ // If we haven't already obtained a token...
57
+ if (!isset($this->deletionTokens[$product->getId()])) {
 
58
 
59
+ // Queue up a token, for after the product has been deleted
60
+ $this->deletionTokens[$product->getId()] =
61
+ $this->liveIndexer->obtainProductDeletionToken($product);
 
 
 
 
 
62
 
63
+ }
 
 
64
 
65
+ } catch (Exception $e) {
66
+ $this->handleException($e);
67
+ }
68
  }
69
 
70
  /**
71
+ * After a product is deleted, push that product deletion token to the indexer service
72
  *
73
  * @param Varien_Event_Observer $productEvent The product event data
74
+ * @return void
 
75
  */
76
  public function afterDeletePushProduct(Varien_Event_Observer $productEvent)
77
  {
78
+ try {
 
 
 
 
 
 
79
 
80
+ $product = $productEvent->getProduct();
 
81
 
82
+ // If we haven't already obtained a token...
83
+ if (isset($this->deletionTokens[$product->getId()])) {
 
 
 
 
 
 
84
 
85
+ $token = $this->deletionTokens[$product->getId()];
86
+ $this->liveIndexer->productDeleted($token);
 
 
 
 
 
87
 
88
+ }
 
89
 
90
+ } catch (Exception $e) {
91
+ $this->handleException($e);
92
+ }
93
  }
94
 
95
+ /**
96
+ * After a product is saved, push that product to the SearchSpring API
97
+ *
98
+ * @param Varien_Event_Observer $productEvent The product event data
99
+ * @return void
100
+ */
101
+ public function afterSavePushProduct(Varien_Event_Observer $productEvent)
102
+ {
103
+ try {
104
 
105
+ $product = $productEvent->getProduct();
106
+
107
+ // TODO ? should there be more logic to verify that an actual change to data was made ?
 
 
108
 
109
+ $this->liveIndexer->productSaved($product);
110
 
111
+ } catch (Exception $e) {
112
+ $this->handleException($e);
113
+ }
114
  }
115
 
116
+ protected function handleException(Exception $e)
117
+ {
118
+ // TODO - is there a mage exception we can catch to send a better message to admin??
119
 
120
+ // Get some kind of context info
121
+
122
+ // log what happened
123
+ Mage::logException($e);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ // Get the best message for the admin user, and notify
126
+ $this->notifyAdminUser("SearchSpring: There was a live indexing issue, please contact SearchSpring support for assistance.");
127
  }
128
+
129
  }
app/code/community/SearchSpring/Manager/Model/Source.php CHANGED
@@ -11,7 +11,7 @@ class SearchSpring_Manager_Model_Source extends SearchSpring_Manager_Model_Sourc
11
 
12
  switch ($this->getPath()) {
13
 
14
- case SearchSpring_Manager_Helper_Data::XML_PATH_SALES_RANK_TIMESPAN:
15
 
16
  $options = array(
17
  'day' => $hlp->__('Day(s)'),
11
 
12
  switch ($this->getPath()) {
13
 
14
+ case SearchSpring_Manager_Model_Config::PATH_SALES_RANK_TIMESPAN:
15
 
16
  $options = array(
17
  'day' => $hlp->__('Day(s)'),
app/code/community/SearchSpring/Manager/Operation/Product.php CHANGED
@@ -122,14 +122,14 @@ abstract class SearchSpring_Manager_Operation_Product implements SearchSpring_Ma
122
  /**
123
  * {@inheritdoc}
124
  */
125
- public function prepareCollection(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
126
  return $this;
127
  }
128
 
129
  /**
130
  * {@inheritdoc}
131
  */
132
- public function prepare(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
133
  return $this;
134
  }
135
 
122
  /**
123
  * {@inheritdoc}
124
  */
125
+ public function prepareCollection($productCollection) {
126
  return $this;
127
  }
128
 
129
  /**
130
  * {@inheritdoc}
131
  */
132
+ public function prepare($productCollection) {
133
  return $this;
134
  }
135
 
app/code/community/SearchSpring/Manager/Operation/Product/SetCategories.php CHANGED
@@ -29,13 +29,39 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
29
  */
30
  protected $_categoryCache = array();
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  /**
33
  * Sets category data to feed
34
  * - category_hierarchy
35
  * - category_name
36
  *
37
  * @param Mage_Catalog_Model_Product $product
38
- *
39
  * @return $this
40
  */
41
  public function perform(Mage_Catalog_Model_Product $product)
@@ -44,7 +70,6 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
44
  $categoryNames = array();
45
  $categoryIds = array();
46
 
47
-
48
  /** @var Mage_Catalog_Model_Category $category */
49
  foreach($product->getCategoryIds() as $categoryId) {
50
  // load category data
@@ -54,6 +79,11 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
54
  continue;
55
  }
56
 
 
 
 
 
 
57
  // Skip this store's root category
58
  if ($this->_isRoot($category)) {
59
  continue;
@@ -131,6 +161,14 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
131
  return $this->_categoryCache[$categoryId];
132
  }
133
 
 
 
 
 
 
 
 
 
134
  protected function _isRoot($category) {
135
  return (
136
  0 === (int)$category->getLevel() ||
29
  */
30
  protected $_categoryCache = array();
31
 
32
+ protected $_rootCategoryId;
33
+ protected $_store;
34
+
35
+ /**
36
+ * After collection has been loaded, but before operations are performed.
37
+ *
38
+ * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
39
+ * @return $this
40
+ */
41
+ public function prepare($productCollection)
42
+ {
43
+ // Do a one time fetch of all the
44
+ // category ids, so we don't hit
45
+ // the database on each product
46
+ $productCollection->addCategoryIds();
47
+
48
+ // Conform our scope to the store of the collection
49
+ $this->_store = Mage::app()->getStore( $productCollection->getStoreId() );
50
+
51
+ // If the store wasn't admin, get the root category id
52
+ if (!$this->_store->isAdmin()) {
53
+ $this->_rootCategoryId = $this->_store->getRootCategoryId();
54
+ }
55
+
56
+ return $this;
57
+ }
58
+
59
  /**
60
  * Sets category data to feed
61
  * - category_hierarchy
62
  * - category_name
63
  *
64
  * @param Mage_Catalog_Model_Product $product
 
65
  * @return $this
66
  */
67
  public function perform(Mage_Catalog_Model_Product $product)
70
  $categoryNames = array();
71
  $categoryIds = array();
72
 
 
73
  /** @var Mage_Catalog_Model_Category $category */
74
  foreach($product->getCategoryIds() as $categoryId) {
75
  // load category data
79
  continue;
80
  }
81
 
82
+ // Skip categories from other stores
83
+ if (!$this->_isInStore($category)) {
84
+ continue;
85
+ }
86
+
87
  // Skip this store's root category
88
  if ($this->_isRoot($category)) {
89
  continue;
161
  return $this->_categoryCache[$categoryId];
162
  }
163
 
164
+ protected function _isInStore($category) {
165
+ if (empty($this->_rootCategoryId)) {
166
+ // We're not in a specific scope
167
+ return true;
168
+ }
169
+ return in_array($this->_rootCategoryId, $category->getPathIds());
170
+ }
171
+
172
  protected function _isRoot($category) {
173
  return (
174
  0 === (int)$category->getLevel() ||
app/code/community/SearchSpring/Manager/Operation/Product/SetCoreFields.php CHANGED
@@ -24,7 +24,6 @@ class SearchSpring_Manager_Operation_Product_SetCoreFields extends SearchSpring_
24
  const FEED_QUANTITY = 'quantity';
25
  const FEED_IN_STOCK = 'in_stock';
26
  const FEED_WEIGHT = 'weight';
27
- const FEED_MANUFACTURER = 'manufacturer';
28
  const FEED_URL = 'url';
29
  const FEED_NAME = 'name';
30
  const FEED_CHILD_QUANTITY = 'child_quantity';
@@ -74,7 +73,6 @@ class SearchSpring_Manager_Operation_Product_SetCoreFields extends SearchSpring_
74
  $this->getRecords()->set(self::FEED_PRODUCT_TYPE, $product->getData('type_id'));
75
  $this->getRecords()->set(self::FEED_QUANTITY, number_format($this->getQuantity($product)));
76
  $this->getRecords()->set(self::FEED_WEIGHT, number_format((double)$product->getWeight(), 2));
77
- $this->getRecords()->set(self::FEED_MANUFACTURER, $phlp->getAttributeText($product, 'manufacturer'));
78
  $this->getRecords()->set(self::FEED_URL, $webBaseUrl . $product->getUrlPath());
79
 
80
  $stockItem = $product->getStockItem();
24
  const FEED_QUANTITY = 'quantity';
25
  const FEED_IN_STOCK = 'in_stock';
26
  const FEED_WEIGHT = 'weight';
 
27
  const FEED_URL = 'url';
28
  const FEED_NAME = 'name';
29
  const FEED_CHILD_QUANTITY = 'child_quantity';
73
  $this->getRecords()->set(self::FEED_PRODUCT_TYPE, $product->getData('type_id'));
74
  $this->getRecords()->set(self::FEED_QUANTITY, number_format($this->getQuantity($product)));
75
  $this->getRecords()->set(self::FEED_WEIGHT, number_format((double)$product->getWeight(), 2));
 
76
  $this->getRecords()->set(self::FEED_URL, $webBaseUrl . $product->getUrlPath());
77
 
78
  $stockItem = $product->getStockItem();
app/code/community/SearchSpring/Manager/Operation/Product/SetReport.php CHANGED
@@ -31,7 +31,7 @@ class SearchSpring_Manager_Operation_Product_SetReport extends SearchSpring_Mana
31
  const FEED_ORDERS_COUNT = 'report_orders_count';
32
  const FEED_ORDERS_QTY = 'report_orders_qty';
33
 
34
- public function prepare(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
35
 
36
  $this->fetchReportData(
37
  $productCollection->getAllIds(),
31
  const FEED_ORDERS_COUNT = 'report_orders_count';
32
  const FEED_ORDERS_QTY = 'report_orders_qty';
33
 
34
+ public function prepare($productCollection) {
35
 
36
  $this->fetchReportData(
37
  $productCollection->getAllIds(),
app/code/community/SearchSpring/Manager/Operation/ProductOperation.php CHANGED
@@ -23,7 +23,7 @@ interface SearchSpring_Manager_Operation_ProductOperation
23
  *
24
  * @return self
25
  */
26
- public function prepareCollection(Mage_Catalog_Model_Resource_Product_Collection $productCollection);
27
 
28
  /**
29
  * Prepare the operation after the collection has
@@ -37,7 +37,7 @@ interface SearchSpring_Manager_Operation_ProductOperation
37
  *
38
  * @return self
39
  */
40
- public function prepare(Mage_Catalog_Model_Resource_Product_Collection $productCollection);
41
 
42
  /**
43
  * Checks validity of operation
23
  *
24
  * @return self
25
  */
26
+ public function prepareCollection($productCollection);
27
 
28
  /**
29
  * Prepare the operation after the collection has
37
  *
38
  * @return self
39
  */
40
+ public function prepare($productCollection);
41
 
42
  /**
43
  * Checks validity of operation
app/code/community/SearchSpring/Manager/Provider/ProductCollection/FeedProvider.php CHANGED
@@ -57,12 +57,17 @@ class SearchSpring_Manager_Provider_ProductCollection_FeedProvider
57
  return $this->collection;
58
  }
59
 
 
 
60
  /** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
61
  $collection = Mage::getModel('catalog/product')->getCollection();
62
- $collection->addStoreFilter(Mage::app()->getStore($this->requestParams->getStore())->getId());
63
  $collection->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
64
  $collection->addAttributeToFilter('type_id', array('in' => SearchSpring_Manager_Validator_ProductValidator::$allowableTypes));
65
 
 
 
 
 
66
  Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($collection);
67
 
68
  if (!Mage::helper('searchspring_manager')->isOutOfStockIndexingEnabled()) {
57
  return $this->collection;
58
  }
59
 
60
+ $store = Mage::app()->getStore($this->requestParams->getStore());
61
+
62
  /** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
63
  $collection = Mage::getModel('catalog/product')->getCollection();
 
64
  $collection->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
65
  $collection->addAttributeToFilter('type_id', array('in' => SearchSpring_Manager_Validator_ProductValidator::$allowableTypes));
66
 
67
+ // Set store, and filter by it
68
+ $collection->setStoreId($store->getId());
69
+ $collection->addStoreFilter();
70
+
71
  Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($collection);
72
 
73
  if (!Mage::helper('searchspring_manager')->isOutOfStockIndexingEnabled()) {
app/code/community/SearchSpring/Manager/Provider/ProductCollection/ProductProvider.php CHANGED
@@ -66,12 +66,18 @@ class SearchSpring_Manager_Provider_ProductCollection_ProductProvider
66
  return $this->collection;
67
  }
68
 
 
 
69
  /** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
70
  $collection = Mage::getModel('catalog/product')
71
  ->getCollection()
72
  ->addAttributeToFilter('entity_id', array('in' => $this->ids))
73
  ;
74
 
 
 
 
 
75
  Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($collection);
76
 
77
  if (null !== $this->requestParams) {
66
  return $this->collection;
67
  }
68
 
69
+ $store = Mage::app()->getStore($this->requestParams->getStore());
70
+
71
  /** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
72
  $collection = Mage::getModel('catalog/product')
73
  ->getCollection()
74
  ->addAttributeToFilter('entity_id', array('in' => $this->ids))
75
  ;
76
 
77
+ // Set store, and filter by it
78
+ $collection->setStoreId($store->getId());
79
+ $collection->addStoreFilter();
80
+
81
  Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($collection);
82
 
83
  if (null !== $this->requestParams) {
app/code/community/SearchSpring/Manager/Service/LiveIndexer.php ADDED
@@ -0,0 +1,551 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * LiveIndexer.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Service_LiveIndexer
10
+ *
11
+ * Pushes product/category live indexing requests to the SearchSpring API. Exposes
12
+ * interface for simple change events, and resolves product dependencies, as
13
+ * well as multiple search spring accounts, and store/account specific configurations.
14
+ *
15
+ * @author Jake Shelby <jake@b7interactive.com>
16
+ */
17
+ class SearchSpring_Manager_Service_LiveIndexer
18
+ {
19
+
20
+ /**
21
+ * Magento App Container
22
+ *
23
+ * @var Mage_Core_Model_App $app
24
+ */
25
+ protected $app;
26
+
27
+ /**
28
+ * SearchSpring configuration
29
+ *
30
+ * @var SearchSpring_Manager_Model_Config
31
+ */
32
+ protected $config;
33
+
34
+ /**
35
+ * API Adapter Factory
36
+ *
37
+ * @var SearchSpring_Manager_Factory_ApiFactory $apiAdapterFactory
38
+ */
39
+ protected $apiAdapterFactory;
40
+
41
+ /**
42
+ * API Request Factory
43
+ *
44
+ * @var SearchSpring_Manager_Factory_IndexingRequestBodyFactory $requestBodyFactory
45
+ */
46
+ protected $requestBodyFactory;
47
+
48
+ /**
49
+ * API Request Factory
50
+ *
51
+ * @var SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter[] $apiAdapters
52
+ */
53
+ protected $apiAdapters = array();
54
+
55
+ /**
56
+ * Cache of loaded product instances by id, by store
57
+ *
58
+ * @var Mage_Catalog_Model_Product[][] $loadedProducts
59
+ */
60
+ protected $loadedProducts = array();
61
+
62
+ /**
63
+ * Cache of loaded category instances by id, by store
64
+ *
65
+ * @var Mage_Catalog_Model_Category[][] $loadedCategories
66
+ */
67
+ protected $loadedCategories = array();
68
+
69
+ /**
70
+ * Constructor
71
+ */
72
+ public function __construct(
73
+ Mage_Core_Model_App $app,
74
+ SearchSpring_Manager_Model_Config $config,
75
+ SearchSpring_Manager_Factory_ApiFactory $apiAdapterFactory,
76
+ SearchSpring_Manager_Factory_IndexingRequestBodyFactory $requestBodyFactory
77
+ ) {
78
+ $this->app = $app;
79
+ $this->config = $config;
80
+ $this->apiAdapterFactory = $apiAdapterFactory;
81
+ $this->requestBodyFactory = $requestBodyFactory;
82
+ }
83
+
84
+ /**
85
+ * Cache and return an API adapter for the given store. Throws exception
86
+ * if the store does not have a SearchSpring account tied to it.
87
+ *
88
+ * @param mixed $store Magento store object|id|code
89
+ * @return SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter
90
+ * @throws Exception
91
+ */
92
+ public function apiAdapter($store)
93
+ {
94
+ $storeId = $this->getStoreId($store);
95
+ if (!isset($this->apiAdapters[$storeId])) {
96
+
97
+ $adapter = $this->apiAdapterFactory->makeIndexingAdapter($store);
98
+
99
+ $this->apiAdapters[$storeId] = $adapter;
100
+ }
101
+ return $this->apiAdapters[$storeId];
102
+ }
103
+
104
+ public function productRequest($ids, $shouldDelete = false)
105
+ {
106
+ return $this->requestBodyFactory->make(
107
+ SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT, $ids, $shouldDelete
108
+ );
109
+ }
110
+
111
+ public function categoryRequest($ids, $shouldDelete = false)
112
+ {
113
+ return $this->requestBodyFactory->make(
114
+ SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY, $ids, $shouldDelete
115
+ );
116
+ }
117
+
118
+ public function shouldIndexStore($store)
119
+ {
120
+ // Make sure the store is configured for SearchSpring
121
+ if (!$this->config->isStoreConfigured($store)) {
122
+ return false;
123
+ }
124
+
125
+ // Make sure the store is enabled with live indexing
126
+ if (!$this->config->isLiveIndexingEnabled($store)) {
127
+ return false;
128
+ }
129
+
130
+ return true;
131
+ }
132
+
133
+ public function getStore($store)
134
+ {
135
+ return $this->app->getStore($store);
136
+ }
137
+
138
+ public function getStoreId($store)
139
+ {
140
+ return $this->getStore($store)->getId();
141
+ }
142
+
143
+ public function isProductInStore($product, $store)
144
+ {
145
+ return in_array($this->getStoreId($store), $product->getStoreIds());
146
+ }
147
+
148
+ public function getStoreWebsiteIds(array $stores)
149
+ {
150
+ $websites = array();
151
+ foreach($stores as $store) {
152
+ $store = $this->getStore($store);
153
+ if (!is_null($store->getWebsiteId())) {
154
+ $websites[] = $store->getWebsiteId();
155
+ }
156
+ }
157
+ return array_unique($websites);
158
+ }
159
+
160
+ public function getProductStores($product)
161
+ {
162
+ $stores = array();
163
+ foreach($product->getStoreIds() as $storeId) {
164
+ if ($this->shouldIndexStore($storeId)) {
165
+ $stores[] = $storeId;
166
+ }
167
+ }
168
+ return $stores;
169
+ }
170
+
171
+ public function getCategoryStores($category)
172
+ {
173
+ $stores = array();
174
+ foreach($category->getStoreIds() as $storeId) {
175
+ if ($this->shouldIndexStore($storeId)) {
176
+ $stores[] = $storeId;
177
+ }
178
+ }
179
+ return $stores;
180
+ }
181
+
182
+ public function loadProduct($product, $store)
183
+ {
184
+ $newProduct = $this->app->getConfig()->getModelInstance('catalog/product');
185
+
186
+ $newProduct->setStoreId( $this->getStoreId($store) );
187
+ $newProduct->load( $this->getModelId($product) );
188
+
189
+ return $newProduct;
190
+ }
191
+
192
+ public function getProduct($product, $store)
193
+ {
194
+ // Because of wierdness in magento, we have to load a
195
+ // fresh product in the requested store context, even
196
+ // if the passed product is for the correct store. This
197
+ // can happen if in the admin context for a certain
198
+ // store scope, and a product is saved, it's data might
199
+ // have remnants of overriden attribute values, even
200
+ // if the current attribute is set to use the default
201
+ // attribute value.
202
+
203
+ $pId = $this->getModelId($product);
204
+ $storeId = $this->getStoreId($store);
205
+ if (!isset($this->loadedProducts[$pId][$storeId])) {
206
+
207
+ $this->loadedProducts[$pId][$storeId] =
208
+ $this->loadProduct($product, $store);
209
+
210
+ }
211
+
212
+ return $this->loadedProducts[$pId][$storeId];
213
+ }
214
+
215
+ public function loadCategory($category, $store)
216
+ {
217
+ $category = $this->app->getConfig()->getModelInstance('catalog/category');
218
+
219
+ $category->setStoreId( $this->getStoreId($store) );
220
+ $category->load( $this->getModelId($category) );
221
+
222
+ return $category;
223
+ }
224
+
225
+ public function getCategory($category, $store)
226
+ {
227
+ $pId = $this->getModelId($category);
228
+ $storeId = $this->getStoreId($store);
229
+ if (!isset($this->loadedCategories[$pId][$storeId])) {
230
+
231
+ $this->loadedCategories[$pId][$storeId] =
232
+ $this->loadCategory($category, $store);
233
+
234
+ }
235
+
236
+ return $this->loadedCategories[$pId][$storeId];
237
+ }
238
+
239
+ public function getModelId($model)
240
+ {
241
+ if ($model instanceof Varien_Object) {
242
+ return $model->getId();
243
+ }
244
+ return $model;
245
+ }
246
+
247
+ public function getRelatedProductIds($product)
248
+ {
249
+ $productIds = array();
250
+ // If the product is a simple product we may need to check its parent(s)
251
+ if($product->getTypeId() == "simple"){
252
+ // Check for configurable parent
253
+ $type = $this->app->getConfig()->getModelInstance('catalog/product_type_configurable');
254
+ $productIds = $type->getParentIdsByChild($product->getId());
255
+
256
+ // If there are no configurable parents check grouped
257
+ if(empty($productIds)) {
258
+ $type = $this->app->getConfig()->getModelInstance('catalog/product_type_grouped');
259
+ $productIds = $type->getParentIdsByChild($product->getId());
260
+ }
261
+ }
262
+
263
+ return $productIds;
264
+ }
265
+
266
+ public function getProductPricingStrategy($product)
267
+ {
268
+ // TODO -- can we move this somewhere else?? should we have to do all this here
269
+ $pricingStrategy = SearchSpring_Manager_Factory_PricingFactory::make($product);
270
+
271
+ $newContext = new Varien_Object(array(
272
+ 'store_id' => $product->getStoreId(),
273
+ 'website_id' => $product->getWebsiteId(),
274
+ 'customer_group_id' => 0,
275
+ ));
276
+
277
+ // Get the current values (in case they happen to be set)
278
+ $ruleData = Mage::registry('rule_data');
279
+ $context = Mage::registry('catalog_category_store_context');
280
+
281
+ // Remove the current values (in case they happen to be set)
282
+ Mage::unregister('rule_data');
283
+ Mage::unregister('catalog_category_store_context');
284
+
285
+ // Need to declare the correct store context for catalog/rule logic
286
+ Mage::register('rule_data', $newContext, true);
287
+ Mage::register('catalog_category_store_context', $newContext, true);
288
+
289
+ // Calculate prices for this strategy
290
+ $pricingStrategy->calculatePrices();
291
+
292
+ // Remove our rule data
293
+ Mage::unregister('rule_data');
294
+ Mage::unregister('catalog_category_store_context');
295
+
296
+ // Put the values back (if they were set)
297
+ if (!is_null($ruleData)) {
298
+ Mage::register('rule_data', $ruleData, true);
299
+ }
300
+ if (!is_null($context)) {
301
+ Mage::register('catalog_category_store_context', $context, true);
302
+ }
303
+
304
+ return $pricingStrategy;
305
+ }
306
+
307
+ public function shouldProductBeDeleted($product)
308
+ {
309
+ $validator = new SearchSpring_Manager_Validator_ProductValidator($this->config);
310
+
311
+ // Is the product valid
312
+ if (!$validator->isValid($product)) {
313
+ return true;
314
+ }
315
+
316
+ // Ask validator if we should delete
317
+ $pricingStrategy = $this->getProductPricingStrategy($product);
318
+ if ($validator->shouldDelete($product, $pricingStrategy)) {
319
+ return true;
320
+ }
321
+
322
+ return false;
323
+ }
324
+
325
+ /**
326
+ * Service Action Calls
327
+ */
328
+
329
+ /**
330
+ * Send live indexing request to SearchSpring API, for the store
331
+ * if it's connected to an account, and enabled with live indexing.
332
+ *
333
+ * Throws exception for network adapter/client related issues.
334
+ *
335
+ * @param mixed $store Magento store object|id|code
336
+ * @param SearchSpring_Manager_Entity_IndexingRequestBody $request Live Indexing request
337
+ * @throws Exception
338
+ */
339
+ public function sendRequest($store, SearchSpring_Manager_Entity_IndexingRequestBody $request)
340
+ {
341
+ // Make sure there are ids in the request
342
+ if (!count($request->getIds())) {
343
+ return;
344
+ }
345
+
346
+ // Make sure we should be indexing this store at all
347
+ if (!$this->shouldIndexStore($store)) {
348
+ continue;
349
+ }
350
+
351
+ // Get the API adapter for this store
352
+ $api = $this->apiAdapter($store);
353
+
354
+ // Make actual request
355
+ $api->pushIds($request);
356
+ }
357
+
358
+ /**
359
+ * Send live indexing request to SearchSpring API, for each store that
360
+ * is connected to an account, and enabled with live indexing.
361
+ *
362
+ * Throws exception for network adapter/client related issues.
363
+ *
364
+ * @param array $stores List of Magento store object|id|code 's
365
+ * @param SearchSpring_Manager_Entity_IndexingRequestBody $request Live Indexing request
366
+ * @throws Exception
367
+ */
368
+ public function sendRequests($stores, SearchSpring_Manager_Entity_IndexingRequestBody $request)
369
+ {
370
+ foreach($stores as $store) {
371
+ $this->sendRequest($store, $request);
372
+ }
373
+ }
374
+
375
+ public function categoryProductsUpdated($category, $products) {
376
+
377
+ // Get stores for this category
378
+ $stores = $this->getCategoryStores($category);
379
+
380
+ // Make sure we need to do anything
381
+ if (!count($stores)) return;
382
+
383
+
384
+ $ids = array();
385
+ foreach($products as $product) {
386
+
387
+ // TODO - Should we skip products that are not in these stores?
388
+
389
+ $ids[] = $this->getModelId($product);
390
+ }
391
+
392
+ $request = $this->productRequest($ids);
393
+
394
+ $this->sendRequests($stores, $request);
395
+ }
396
+
397
+ public function categorySaved($category)
398
+ {
399
+ // Get stores for this category
400
+ $stores = $this->getCategoryStores($category);
401
+
402
+ // Make sure we need to do anything
403
+ if (!count($stores)) return;
404
+
405
+ $ids = $category->getAllChildren(true);
406
+
407
+ $request = $this->categoryRequest($ids);
408
+
409
+ $this->sendRequests($stores, $request);
410
+ }
411
+
412
+ /**
413
+ * Call for a category that is going to be deleted. All assocated/affected products will be requested for live indexing.
414
+ */
415
+ public function categoryDeleted($category)
416
+ {
417
+ // Get stores for this category
418
+ $stores = $this->getCategoryStores($category);
419
+
420
+ // Make sure we need to do anything
421
+ if (!count($stores)) return;
422
+
423
+ // Get the website ids for these stores
424
+ $websiteIds = $this->getStoreWebsiteIds($stores);
425
+
426
+ $collection = mage::getModel('catalog/product')->getCollection()
427
+ ->addAttributeToSelect('*')
428
+ ->joinField('category_id', 'catalog/category_product', 'category_id', 'product_id=entity_id', null, 'left')
429
+ ->addAttributeToFilter('category_id', array('in' => $category->getAllChildren(true)));
430
+ $collection->addWebsiteFilter($websiteIds);
431
+ $collection->getSelect()->group('e.entity_id');
432
+ $productIds = $collection->load(false, true)->getAllIds();
433
+
434
+ $request = $this->productRequest($productIds);
435
+
436
+ $this->sendRequests($stores, $request);
437
+ }
438
+
439
+ /**
440
+ * Register a Live Indexing request for a saved product.
441
+ * Find all related parent products, and include them
442
+ * in the request. Foreach product determine wether they
443
+ * should be deleted or just updated.
444
+ *
445
+ * The requests will only be sent to accounts that the
446
+ * main product is registered with - not the related
447
+ * parent product stores.
448
+ *
449
+ * @param Mage_Catalog_Model_Product $savedProduct
450
+ * @return void
451
+ */
452
+ public function productSaved($savedProduct)
453
+ {
454
+ // Get stores for this product
455
+ $stores = $this->getProductStores($savedProduct);
456
+
457
+ // Make sure we need to do anything
458
+ if (!count($stores)) return;
459
+
460
+ $affectedProducts = $this->getRelatedProductIds($savedProduct);
461
+ $affectedProducts[] = $savedProduct;
462
+
463
+ foreach($stores as $store) {
464
+
465
+ $pIdsRemove = array();
466
+ $pIdsUpdate = array();
467
+ foreach($affectedProducts as $affectedProduct) {
468
+
469
+ // Get loaded product for this store
470
+ $product = $this->getProduct($affectedProduct, $store);
471
+
472
+ // Make sure this product is part of this store
473
+ if (!$this->isProductInStore($product, $store)) {
474
+ continue;
475
+ }
476
+
477
+ // Find out if it should be deleted or just updated
478
+ if ($this->shouldProductBeDeleted($product)) {
479
+ $pIdsRemove[] = $product->getId();
480
+ } else {
481
+ $pIdsUpdate[] = $product->getId();
482
+ }
483
+ }
484
+
485
+ // Request update for removes
486
+ $request = $this->productRequest($pIdsRemove, true);
487
+ $this->sendRequest($store, $request);
488
+
489
+ // Request update for changes
490
+ $request = $this->productRequest($pIdsUpdate);
491
+ $this->sendRequest($store, $request);
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Before a product is going to be deleted, call this to get a token
497
+ * that can be used to call us back when the product has been deleted.
498
+ *
499
+ * This step is required to obtain data related to the product, because
500
+ * after the product is deleted, there's no way to obtain the needed
501
+ * data.
502
+ *
503
+ * @param mixed $product The magento product, or product id that will
504
+ * be potentially deleted
505
+ *
506
+ * @return SearchSpring_Manager_Entity_ProductDeletionToken
507
+ */
508
+ public function obtainProductDeletionToken($product)
509
+ {
510
+ // Get stores for this product
511
+ $stores = $this->getProductStores($product);
512
+
513
+ // Make sure we need to do anything
514
+ if (!count($stores)) {
515
+ return new SearchSpring_Manager_Entity_ProductDeletionToken(
516
+ $this->getModelId($product), array()
517
+ );
518
+ }
519
+
520
+ $related = $this->getRelatedProductIds($product);
521
+
522
+ return new SearchSpring_Manager_Entity_ProductDeletionToken(
523
+ $this->getModelId($product), $stores, $related
524
+ );
525
+ }
526
+
527
+ /**
528
+ * After a product has been deleted, pass the token to finalize/commit
529
+ * the live indexing request. Use the obtainProductDeletionToken before
530
+ * the product is deleted in order to have a token after the product has
531
+ * been deleted.
532
+ *
533
+ * @param SearchSpring_Manager_Entity_ProductDeletionToken $token
534
+ */
535
+ public function productDeleted(SearchSpring_Manager_Entity_ProductDeletionToken $token)
536
+ {
537
+ // Make sure we need to do anything
538
+ if (!count($token->getStores())) return;
539
+
540
+ $stores = $token->getStores();
541
+
542
+ // Send request to delete product
543
+ $request = $this->productRequest(array($token->getProductId()), true);
544
+ $this->sendRequests($stores, $request);
545
+
546
+ // Send request to update related products
547
+ $request = $this->productRequest($token->getRelatedProductIds());
548
+ $this->sendRequests($stores, $request);
549
+ }
550
+
551
+ }
app/code/community/SearchSpring/Manager/Service/SearchSpring/ApiAdapter.php CHANGED
@@ -47,7 +47,7 @@ class SearchSpring_Manager_Service_SearchSpring_ApiAdapter
47
  * Constructor
48
  *
49
  * @param SearchSpring_Manager_Handler_ApiErrorHandler $errorHandler
50
- * @param Varien_Http_Adapter_Curl $curl
51
  * @param string $baseUrl
52
  */
53
  public function __construct(
47
  * Constructor
48
  *
49
  * @param SearchSpring_Manager_Handler_ApiErrorHandler $errorHandler
50
+ * @param Zend_Http_Client $client
51
  * @param string $baseUrl
52
  */
53
  public function __construct(
app/code/community/SearchSpring/Manager/Service/SearchSpring/IndexingApiAdapter.php CHANGED
@@ -14,17 +14,43 @@
14
  */
15
  class SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter extends SearchSpring_Manager_Service_SearchSpring_ApiAdapter
16
  {
 
17
  const API_INDEX = 'api/index/magento/generate';
18
 
19
- /**
20
- * Push ids to api
21
- *
22
- * @param SearchSpring_Manager_Entity_IndexingRequestBody $requestBody
23
- */
24
- public function pushIds(SearchSpring_Manager_Entity_IndexingRequestBody $requestBody)
25
- {
26
- $this->call(self::API_INDEX, $requestBody);
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  /**
30
  * Generic method that accepts any api endpoint path
@@ -35,12 +61,19 @@ class SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter extends Searc
35
  public function call($method, SearchSpring_Manager_Entity_IndexingRequestBody $requestBody)
36
  {
37
  $url = $this->buildUrl($method);
38
- $this->request(Zend_Http_Client::POST, $url, json_encode($requestBody->jsonSerialize()));
 
 
 
 
 
39
  }
40
 
41
  /**
42
  * Helper method to make an api request
43
  *
 
 
44
  * @param string $httpMethod
45
  * @param string $url
46
  * @param string $body
14
  */
15
  class SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter extends SearchSpring_Manager_Service_SearchSpring_ApiAdapter
16
  {
17
+
18
  const API_INDEX = 'api/index/magento/generate';
19
 
20
+ /**
21
+ * The feed id to use for each request
22
+ *
23
+ * @var string|int $feedId
24
+ */
25
+ protected $feedId;
26
+
27
+ /**
28
+ * Constructor
29
+ *
30
+ * @param SearchSpring_Manager_Handler_ApiErrorHandler $errorHandler
31
+ * @param Zend_Http_Client $client
32
+ * @param string $baseUrl
33
+ * @param string|int $feedId
34
+ */
35
+ public function __construct(
36
+ SearchSpring_Manager_Handler_ApiErrorHandler $errorHandler,
37
+ Zend_Http_Client $client,
38
+ $baseUrl,
39
+ $feedId
40
+ ) {
41
+ parent::__construct($errorHandler, $client, $baseUrl);
42
+ $this->feedId = $feedId;
43
+ }
44
+
45
+ /**
46
+ * Push ids to api
47
+ *
48
+ * @param SearchSpring_Manager_Entity_IndexingRequestBody $requestBody
49
+ */
50
+ public function pushIds(SearchSpring_Manager_Entity_IndexingRequestBody $requestBody)
51
+ {
52
+ $this->call(self::API_INDEX, $requestBody);
53
+ }
54
 
55
  /**
56
  * Generic method that accepts any api endpoint path
61
  public function call($method, SearchSpring_Manager_Entity_IndexingRequestBody $requestBody)
62
  {
63
  $url = $this->buildUrl($method);
64
+ $this->request(Zend_Http_Client::POST, $url, $this->getRequestBodyJson($requestBody));
65
+ }
66
+
67
+ protected function getRequestBodyJson(SearchSpring_Manager_Entity_IndexingRequestBody $requestBody)
68
+ {
69
+ return Zend_Json::encode($requestBody->jsonSerialize($this->feedId));
70
  }
71
 
72
  /**
73
  * Helper method to make an api request
74
  *
75
+ * @todo Add a debug line here for details on each request (when we have the tool)
76
+ *
77
  * @param string $httpMethod
78
  * @param string $url
79
  * @param string $body
app/code/community/SearchSpring/Manager/Transformer/ProductCollectionToRecordCollectionTransformer.php CHANGED
@@ -45,7 +45,7 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
45
  *
46
  * @return SearchSpring_Manager_Entity_RecordsCollection
47
  */
48
- public function transform(Mage_Catalog_Model_Resource_Product_Collection $productCollection)
49
  {
50
  Varien_Profiler::start(__METHOD__);
51
 
@@ -123,7 +123,7 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
123
  *
124
  * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
125
  */
126
- protected function prepareCollection(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
127
 
128
  foreach ($this->operationsCollection as $operation) {
129
  $operation->prepareCollection($productCollection);
@@ -139,7 +139,7 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
139
  *
140
  * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
141
  */
142
- protected function prepareOperations(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
143
 
144
  foreach ($this->operationsCollection as $operation) {
145
  Varien_Profiler::start(__METHOD__.": operation->prepare() " . get_class($operation));
@@ -149,7 +149,7 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
149
 
150
  }
151
 
152
- protected function loadCollection(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
153
 
154
  Varien_Profiler::start(__METHOD__.": productCollection->load()");
155
  $productCollection->load();
45
  *
46
  * @return SearchSpring_Manager_Entity_RecordsCollection
47
  */
48
+ public function transform($productCollection)
49
  {
50
  Varien_Profiler::start(__METHOD__);
51
 
123
  *
124
  * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
125
  */
126
+ protected function prepareCollection($productCollection) {
127
 
128
  foreach ($this->operationsCollection as $operation) {
129
  $operation->prepareCollection($productCollection);
139
  *
140
  * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
141
  */
142
+ protected function prepareOperations($productCollection) {
143
 
144
  foreach ($this->operationsCollection as $operation) {
145
  Varien_Profiler::start(__METHOD__.": operation->prepare() " . get_class($operation));
149
 
150
  }
151
 
152
+ protected function loadCollection($productCollection) {
153
 
154
  Varien_Profiler::start(__METHOD__.": productCollection->load()");
155
  $productCollection->load();
app/code/community/SearchSpring/Manager/Validator/ProductValidator.php CHANGED
@@ -24,6 +24,13 @@ class SearchSpring_Manager_Validator_ProductValidator implements Zend_Validate_I
24
  Mage_Catalog_Model_Product_Type::TYPE_BUNDLE,
25
  );
26
 
 
 
 
 
 
 
 
27
  /**
28
  * An array of messages that details why the validator failed
29
  *
@@ -31,6 +38,14 @@ class SearchSpring_Manager_Validator_ProductValidator implements Zend_Validate_I
31
  */
32
  private $messages = array();
33
 
 
 
 
 
 
 
 
 
34
  /**
35
  * Determines if we should delete this product from the SearchSpring index
36
  *
@@ -66,7 +81,7 @@ class SearchSpring_Manager_Validator_ProductValidator implements Zend_Validate_I
66
  }
67
 
68
  // if product is out of stock
69
- $displayOos = Mage::helper('searchspring_manager')->isOutOfStockIndexingEnabled();
70
  $stockItem = $product->getStockItem();
71
 
72
  if (!$stockItem->getIsInStock() && !$displayOos) {
@@ -78,7 +93,7 @@ class SearchSpring_Manager_Validator_ProductValidator implements Zend_Validate_I
78
  }
79
 
80
  // if product has a zero price
81
- $displayZeroPrice = Mage::helper('searchspring_manager')->isZeroPriceIndexingEnabled();
82
  $productHaZeroPrice = 0 == $pricing->getNormalPrice() && 0 == $pricing->getTierPrice() && 0 == $pricing->getSalePrice();
83
  if ($productHaZeroPrice && !$displayZeroPrice) {
84
  return true;
24
  Mage_Catalog_Model_Product_Type::TYPE_BUNDLE,
25
  );
26
 
27
+ /**
28
+ * SearchSpring configuration
29
+ *
30
+ * @var SearchSpring_Manager_Model_Config
31
+ */
32
+ protected $config;
33
+
34
  /**
35
  * An array of messages that details why the validator failed
36
  *
38
  */
39
  private $messages = array();
40
 
41
+ /**
42
+ * Constructor
43
+ */
44
+ public function __construct(SearchSpring_Manager_Model_Config $config)
45
+ {
46
+ $this->config = $config;
47
+ }
48
+
49
  /**
50
  * Determines if we should delete this product from the SearchSpring index
51
  *
81
  }
82
 
83
  // if product is out of stock
84
+ $displayOos = $this->config->isOutOfStockIndexingEnabled($product->getStoreId());
85
  $stockItem = $product->getStockItem();
86
 
87
  if (!$stockItem->getIsInStock() && !$displayOos) {
93
  }
94
 
95
  // if product has a zero price
96
+ $displayZeroPrice = $this->config->isZeroPriceIndexingEnabled($product->getStoreId());
97
  $productHaZeroPrice = 0 == $pricing->getNormalPrice() && 0 == $pricing->getTierPrice() && 0 == $pricing->getSalePrice();
98
  if ($productHaZeroPrice && !$displayZeroPrice) {
99
  return true;
app/code/community/SearchSpring/Manager/Writer/Product/Params/FileWriterParams.php CHANGED
@@ -96,6 +96,12 @@ class SearchSpring_Manager_Writer_Product_Params_FileWriterParams
96
  $this->validateBaseDir();
97
  }
98
 
 
 
 
 
 
 
99
  /**
100
  * Validate base directory
101
  *
@@ -188,7 +194,7 @@ class SearchSpring_Manager_Writer_Product_Params_FileWriterParams
188
  public function getFilename()
189
  {
190
  if (null === $this->filename) {
191
- $this->filename = $this->baseDir . DS . sprintf(self::FILENAME_FINAL_PATTERN, $this->requestParams->getStore());
192
  }
193
 
194
  return $this->filename;
96
  $this->validateBaseDir();
97
  }
98
 
99
+ static public function getBaseFilename($store)
100
+ {
101
+ $storeCode = Mage::app()->getStore($store)->getCode();
102
+ return sprintf(self::FILENAME_FINAL_PATTERN, $storeCode);
103
+ }
104
+
105
  /**
106
  * Validate base directory
107
  *
194
  public function getFilename()
195
  {
196
  if (null === $this->filename) {
197
+ $this->filename = $this->baseDir . DS . $this->getBaseFilename($this->requestParams->getStore());
198
  }
199
 
200
  return $this->filename;
app/code/community/SearchSpring/Manager/controllers/GenerateController.php CHANGED
@@ -22,11 +22,6 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
22
  */
23
  const COUNT_DEFAULT = 100;
24
 
25
- /**
26
- * Default store if no parameter is set
27
- */
28
- const STORE_DEFAULT = 'default';
29
-
30
  /**
31
  * Default action for updating based on product/category id
32
  *
@@ -52,7 +47,7 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
52
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
53
  (int)$request->getParam('size', null),
54
  (int)$request->getParam('start', null),
55
- $request->getParam('store', self::STORE_DEFAULT)
56
  );
57
 
58
  $params = array('ids' => $ids);
@@ -75,7 +70,6 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
75
  * filename (required): A unique filename when creating temporary files.
76
  * start (optional): The starting point for fetching products. Defaults to 0.
77
  * count (optional): The number of products to fetch. Defaults to 100.
78
- * store (optional): The store name as a string. Defaults to 'default'
79
  */
80
  public function feedAction()
81
  {
@@ -88,7 +82,7 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
88
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
89
  (int)$this->getRequest()->getParam('count', self::COUNT_DEFAULT),
90
  (int)$this->getRequest()->getParam('start', self::OFFSET_DEFAULT),
91
- $this->getRequest()->getParam('store', self::STORE_DEFAULT)
92
  );
93
 
94
  $params = array('filename' => $uniqueFilename);
@@ -137,16 +131,16 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
137
  }
138
 
139
  protected function isEnabled() {
140
- $helper = Mage::helper('searchspring_manager');
141
  // For now, this controller only handles the 'simple' auth method
142
- $authenticationMethod = $helper->getAuthenticationMethod();
143
  return $authenticationMethod == 'simple';
144
  }
145
 
146
  protected function _authenticate() {
147
 
148
- $helper = Mage::helper('searchspring_manager/http');
149
- list($username, $password) = $helper->isSimpleAuthProvided(true); // true, itemized
150
 
151
  // Make sure auth username was provided
152
  if(empty($username)) {
@@ -167,7 +161,7 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
167
  }
168
 
169
  // Make sure auth credentials are valid
170
- if(!$helper->isSimpleAuthValid()) {
171
  $this->respondWithError(
172
  'Authentication Failed: Invalid credentials',
173
  SearchSpring_ErrorCodes::AUTH_CREDENTIALS_INVALID,
@@ -177,6 +171,10 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
177
 
178
  }
179
 
 
 
 
 
180
  /**
181
  * Action to forward/redirect/fork to when an unhandled
182
  * exception/error is encountered.
22
  */
23
  const COUNT_DEFAULT = 100;
24
 
 
 
 
 
 
25
  /**
26
  * Default action for updating based on product/category id
27
  *
47
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
48
  (int)$request->getParam('size', null),
49
  (int)$request->getParam('start', null),
50
+ Mage::app()->getStore()->getCode()
51
  );
52
 
53
  $params = array('ids' => $ids);
70
  * filename (required): A unique filename when creating temporary files.
71
  * start (optional): The starting point for fetching products. Defaults to 0.
72
  * count (optional): The number of products to fetch. Defaults to 100.
 
73
  */
74
  public function feedAction()
75
  {
82
  $requestParams = new SearchSpring_Manager_Entity_RequestParams(
83
  (int)$this->getRequest()->getParam('count', self::COUNT_DEFAULT),
84
  (int)$this->getRequest()->getParam('start', self::OFFSET_DEFAULT),
85
+ Mage::app()->getStore()->getCode()
86
  );
87
 
88
  $params = array('filename' => $uniqueFilename);
131
  }
132
 
133
  protected function isEnabled() {
134
+ $hlp = Mage::helper('searchspring_manager');
135
  // For now, this controller only handles the 'simple' auth method
136
+ $authenticationMethod = $hlp->getAuthenticationMethod( Mage::app()->getStore() );
137
  return $authenticationMethod == 'simple';
138
  }
139
 
140
  protected function _authenticate() {
141
 
142
+ $hlp = Mage::helper('searchspring_manager/http');
143
+ list($username, $password) = $hlp->isSimpleAuthProvided(true); // true, itemized
144
 
145
  // Make sure auth username was provided
146
  if(empty($username)) {
161
  }
162
 
163
  // Make sure auth credentials are valid
164
+ if(!$hlp->isSimpleAuthValid($this->_getMyCredentials())) {
165
  $this->respondWithError(
166
  'Authentication Failed: Invalid credentials',
167
  SearchSpring_ErrorCodes::AUTH_CREDENTIALS_INVALID,
171
 
172
  }
173
 
174
+ protected function _getMyCredentials() {
175
+ return Mage::helper('searchspring_manager')->getMageApiCredentials( Mage::app()->getStore() );
176
+ }
177
+
178
  /**
179
  * Action to forward/redirect/fork to when an unhandled
180
  * exception/error is encountered.
app/code/community/SearchSpring/Manager/etc/config.xml CHANGED
@@ -2,7 +2,7 @@
2
  <config>
3
  <modules>
4
  <SearchSpring_Manager>
5
- <version>1.10.2</version>
6
  </SearchSpring_Manager>
7
  </modules>
8
  <global>
@@ -29,6 +29,7 @@
29
  </helpers>
30
  <events>
31
  <!-- use catalog_product_save_commit_after otherwise we can get stale product data if our request runs before commit -->
 
32
  <catalog_product_save_commit_after>
33
  <observers>
34
  <searchspring_manager_after_save_push_product>
@@ -79,10 +80,10 @@
79
  </catalog_category_change_products>
80
  <catalog_controller_category_delete>
81
  <observers>
82
- <searchspring_manager_after_delete_update_product_category>
83
  <class>searchspring_manager/observer_categorySaveObserver</class>
84
- <method>afterDeleteUpdateProductCategory</method>
85
- </searchspring_manager_after_delete_update_product_category>
86
  </observers>
87
  </catalog_controller_category_delete>
88
  </events>
2
  <config>
3
  <modules>
4
  <SearchSpring_Manager>
5
+ <version>2.0.0</version>
6
  </SearchSpring_Manager>
7
  </modules>
8
  <global>
29
  </helpers>
30
  <events>
31
  <!-- use catalog_product_save_commit_after otherwise we can get stale product data if our request runs before commit -->
32
+ <!-- TODO ?? should can we indicate singleton, or single instances of each observer should be re-used ?? -->
33
  <catalog_product_save_commit_after>
34
  <observers>
35
  <searchspring_manager_after_save_push_product>
80
  </catalog_category_change_products>
81
  <catalog_controller_category_delete>
82
  <observers>
83
+ <searchspring_manager_before_delete_update_product_category>
84
  <class>searchspring_manager/observer_categorySaveObserver</class>
85
+ <method>beforeDeleteUpdateProductCategory</method>
86
+ </searchspring_manager_before_delete_update_product_category>
87
  </observers>
88
  </catalog_controller_category_delete>
89
  </events>
app/code/community/SearchSpring/Manager/etc/system.xml CHANGED
@@ -186,44 +186,39 @@
186
  <show_in_website>1</show_in_website>
187
  <show_in_store>1</show_in_store>
188
  <fields>
189
- <base_url translate="label">
190
- <label>Base URL</label>
191
- <frontend_type>text</frontend_type>
192
- <sort_order>15</sort_order>
193
- <show_in_default>1</show_in_default>
194
- <show_in_website>1</show_in_website>
195
- <show_in_store>1</show_in_store>
196
- </base_url>
197
  <site_id translate="label">
198
  <label>Site ID</label>
199
  <frontend_type>text</frontend_type>
200
  <sort_order>20</sort_order>
201
- <show_in_default>1</show_in_default>
202
- <show_in_website>1</show_in_website>
 
203
  <show_in_store>1</show_in_store>
204
  </site_id>
205
  <feed_id translate="label">
206
  <label>Feed ID</label>
207
  <frontend_type>text</frontend_type>
208
  <sort_order>30</sort_order>
209
- <show_in_default>1</show_in_default>
210
- <show_in_website>1</show_in_website>
 
211
  <show_in_store>1</show_in_store>
212
  </feed_id>
213
  <secret_key translate="label">
214
  <label>Secret Key</label>
215
  <frontend_type>text</frontend_type>
216
  <sort_order>40</sort_order>
217
- <show_in_default>1</show_in_default>
218
- <show_in_website>1</show_in_website>
 
219
  <show_in_store>1</show_in_store>
220
  </secret_key>
221
  <authentication_method translate="label">
222
  <label>Authentication Method</label>
223
  <frontend_type>text</frontend_type>
224
  <sort_order>15</sort_order>
225
- <show_in_default>1</show_in_default>
226
- <show_in_website>1</show_in_website>
227
  <show_in_store>1</show_in_store>
228
  </authentication_method>
229
  <magento_api_admin_user>
@@ -231,27 +226,30 @@
231
  <!-- TODO change type to dropdown -->
232
  <frontend_type>text</frontend_type>
233
  <sort_order>50</sort_order>
 
234
  <show_in_default>1</show_in_default>
235
- <show_in_website>1</show_in_website>
236
- <show_in_store>1</show_in_store>
237
  </magento_api_admin_user>
238
  <magento_api_admin_user_role>
239
  <label>Magento API - Admin User REST Role</label>
240
  <!-- TODO change type to dropdown -->
241
  <frontend_type>text</frontend_type>
242
  <sort_order>50</sort_order>
 
243
  <show_in_default>1</show_in_default>
244
- <show_in_website>1</show_in_website>
245
- <show_in_store>1</show_in_store>
246
  </magento_api_admin_user_role>
247
  <magento_api_oauth_consumer>
248
  <label>Magento API - oAuth Consumer</label>
249
  <!-- TODO change type to dropdown -->
250
  <frontend_type>text</frontend_type>
251
  <sort_order>50</sort_order>
 
252
  <show_in_default>1</show_in_default>
253
- <show_in_website>1</show_in_website>
254
- <show_in_store>1</show_in_store>
255
  </magento_api_oauth_consumer>
256
  </fields>
257
  </ssmanager_api>
186
  <show_in_website>1</show_in_website>
187
  <show_in_store>1</show_in_store>
188
  <fields>
 
 
 
 
 
 
 
 
189
  <site_id translate="label">
190
  <label>Site ID</label>
191
  <frontend_type>text</frontend_type>
192
  <sort_order>20</sort_order>
193
+ <!-- Store View Only -->
194
+ <show_in_default>0</show_in_default>
195
+ <show_in_website>0</show_in_website>
196
  <show_in_store>1</show_in_store>
197
  </site_id>
198
  <feed_id translate="label">
199
  <label>Feed ID</label>
200
  <frontend_type>text</frontend_type>
201
  <sort_order>30</sort_order>
202
+ <!-- Store View Only -->
203
+ <show_in_default>0</show_in_default>
204
+ <show_in_website>0</show_in_website>
205
  <show_in_store>1</show_in_store>
206
  </feed_id>
207
  <secret_key translate="label">
208
  <label>Secret Key</label>
209
  <frontend_type>text</frontend_type>
210
  <sort_order>40</sort_order>
211
+ <!-- Store View Only -->
212
+ <show_in_default>0</show_in_default>
213
+ <show_in_website>0</show_in_website>
214
  <show_in_store>1</show_in_store>
215
  </secret_key>
216
  <authentication_method translate="label">
217
  <label>Authentication Method</label>
218
  <frontend_type>text</frontend_type>
219
  <sort_order>15</sort_order>
220
+ <show_in_default>0</show_in_default>
221
+ <show_in_website>0</show_in_website>
222
  <show_in_store>1</show_in_store>
223
  </authentication_method>
224
  <magento_api_admin_user>
226
  <!-- TODO change type to dropdown -->
227
  <frontend_type>text</frontend_type>
228
  <sort_order>50</sort_order>
229
+ <!-- Global Only -->
230
  <show_in_default>1</show_in_default>
231
+ <show_in_website>0</show_in_website>
232
+ <show_in_store>0</show_in_store>
233
  </magento_api_admin_user>
234
  <magento_api_admin_user_role>
235
  <label>Magento API - Admin User REST Role</label>
236
  <!-- TODO change type to dropdown -->
237
  <frontend_type>text</frontend_type>
238
  <sort_order>50</sort_order>
239
+ <!-- Global Only -->
240
  <show_in_default>1</show_in_default>
241
+ <show_in_website>0</show_in_website>
242
+ <show_in_store>0</show_in_store>
243
  </magento_api_admin_user_role>
244
  <magento_api_oauth_consumer>
245
  <label>Magento API - oAuth Consumer</label>
246
  <!-- TODO change type to dropdown -->
247
  <frontend_type>text</frontend_type>
248
  <sort_order>50</sort_order>
249
+ <!-- Global Only -->
250
  <show_in_default>1</show_in_default>
251
+ <show_in_website>0</show_in_website>
252
+ <show_in_store>0</show_in_store>
253
  </magento_api_oauth_consumer>
254
  </fields>
255
  </ssmanager_api>
app/code/community/SearchSpring/Manager/sql/searchspring_manager/mysql4-data-upgrade-1.10.2-2.0.0.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // If a SearchSpring account has been linked to the default level configs ...
4
+ $feedId = getConfigValue($this, SearchSpring_Manager_Model_Config::PATH_API_FEED_ID);
5
+ $siteId = getConfigValue($this, SearchSpring_Manager_Model_Config::PATH_API_SITE_ID);
6
+ $secret = getConfigValue($this, SearchSpring_Manager_Model_Config::PATH_API_SECRET_KEY);
7
+ if ($feedId && $siteId && $secret) {
8
+ // ...then we need to move them down to a store specific config
9
+ // We'll assume that the default store is the intended store to link
10
+ $storeId = Mage::app()->getAnyStoreView()->getId();
11
+
12
+ $this->setConfigData( SearchSpring_Manager_Model_Config::PATH_API_FEED_ID, $feedId, 'stores', $storeId);
13
+ $this->setConfigData( SearchSpring_Manager_Model_Config::PATH_API_SITE_ID, $siteId, 'stores', $storeId);
14
+ $this->setConfigData(SearchSpring_Manager_Model_Config::PATH_API_SECRET_KEY, $secret, 'stores', $storeId);
15
+
16
+ // Just in case it's different, pull and persist the auth method config for the specific store
17
+ $method = Mage::getStoreConfig(SearchSpring_Manager_Model_Config::PATH_API_AUTHENTICATION_METHOD, $storeId);
18
+ $this->setConfigData(SearchSpring_Manager_Model_Config::PATH_API_AUTHENTICATION_METHOD, $method, 'stores', $storeId);
19
+
20
+ }
21
+
22
+ // Then remove the API store specific configs from the other scopes (other than stores)
23
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_FEED_ID, 'default');
24
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_FEED_ID, 'websites');
25
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_SITE_ID, 'default');
26
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_SITE_ID, 'websites');
27
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_SECRET_KEY, 'default');
28
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_SECRET_KEY, 'websites');
29
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_AUTHENTICATION_METHOD, 'default');
30
+ $this->deleteConfigData(SearchSpring_Manager_Model_Config::PATH_API_AUTHENTICATION_METHOD, 'websites');
31
+
32
+
33
+ // There was a bug earlier that would post extra uneeded configs, remove all of them...
34
+ $this->deleteConfigData('ssmanager/ssmanager_setup/username');
35
+ $this->deleteConfigData('ssmanager/ssmanager_setup/password');
36
+ $this->deleteConfigData('ssmanager/ssmanager_setup/authentication_method');
37
+ $this->deleteConfigData('ssmanager/ssmanager_setup/website');
38
+ $this->deleteConfigData('ssmanager/ssmanager_setup/feed');
39
+ $this->deleteConfigData('ssmanager/ssmanager_api/base_url');
40
+
41
+
42
+ // Reinitialize cache for the configuration
43
+ Mage::getConfig()->reinit();
44
+
45
+
46
+ // If data was migrated to a specific store...
47
+ if (isset($storeId)) {
48
+ // Register just the live indexing URL for this SearchSpring account
49
+ $hlp = Mage::helper('searchspring_manager');
50
+ $whlp = Mage::helper('searchspring_manager/webservice');
51
+ $feedId = $hlp->getApiFeedId($storeId);
52
+ $creds = $hlp->getApiCredentials($storeId);
53
+ $whlp->registerMageAPIUrls(
54
+ $feedId, $creds,
55
+ null, // Feed
56
+ null, // Batch
57
+ $hlp->getMageAPIUrlProduct($storeId) // Live
58
+ );
59
+ }
60
+
61
+
62
+ // Unfortunately, there's no way to get a config value directly from the scope you're looking for. So we need to query the data ourself.
63
+ function getConfigValue($installer, $path, $scope = 'default', $scopeId = 0)
64
+ {
65
+ $adapter = $installer->getConnection();
66
+ $select = $adapter->select();
67
+ $select->from($installer->getTable('core/config_data'));
68
+ $select->where('path = ?', $path);
69
+ $select->where('scope = ?', $scope);
70
+ $select->where('scope_id = ?', $scopeId);
71
+
72
+ $data = $adapter->fetchRow($select);
73
+
74
+ if (empty($data) || !is_array($data)) {
75
+ return false;
76
+ }
77
+
78
+ if (!isset($data['value'])) {
79
+ return false;
80
+ }
81
+
82
+ return $data['value'];
83
+ }
84
+
85
+
app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/setup.phtml CHANGED
@@ -41,7 +41,7 @@
41
  <label for="ssmanager_ssmanager_setup_username">SearchSpring Username</label>
42
  </td>
43
  <td class="value">
44
- <input id="ssmanager_ssmanager_setup_username" tabindex="1" name="groups[ssmanager_setup][fields][username][value]" class=" input-text" type="text">
45
  </td>
46
  <td class="ss-no-account" rowspan="3">
47
  <a href="http://www.searchspring.com/request-a-demo">
@@ -52,15 +52,15 @@
52
  <tr id="row_ssmanager_ssmanager_setup_password" class="ss-step1">
53
  <td class="label"><label for="ssmanager_ssmanager_setup_password">SearchSpring Password</label></td>
54
  <td class="value">
55
- <input id="ssmanager_ssmanager_setup_password" tabindex="2" name="groups[ssmanager_setup][fields][password][value]" class=" input-text" type="password">
56
  </td>
57
  <td class="scope-label"></td>
58
  <td class=""></td>
59
  </tr>
60
  <tr id="row_ssmanager_ssmanager_setup_authentication_method" class="ss-step1">
61
- <td class="label"><label for="ssmanager_ssmanager_setup_authentication_method">Authenticaion Method</label></td>
62
  <td class="value">
63
- <select id="ssmanager_ssmanager_setup_authentication_method" tabindex="2" name="groups[ssmanager_setup][fields][authentication_method][value]" class=" input-text">
64
  <option value="simple">Simple Authentication</option>
65
  <option value="oauth">OAuth API</option>
66
  </select>
@@ -72,7 +72,7 @@
72
  <tr id="row_ssmanager_ssmanager_setup_login" class="ss-step1">
73
  <td class="label"></td>
74
  <td class="value">
75
- <button id="ss-login-button" title="Login" type="button" class="scalable" style=""><span><span><span>Login</span></span></span></button>
76
  </td>
77
  <td></td>
78
  </tr>
@@ -81,7 +81,7 @@
81
  <label for="ssmanager_ssmanager_setup_website">Website</label>
82
  </td>
83
  <td class="value">
84
- <select disabled id="ssmanager_ssmanager_setup_website" name="groups[ssmanager_setup][fields][website][value]" class=" select"></select>
85
  </td>
86
  <td class="ss-no-account" rowspan="3">
87
  <a href="http://www.searchspring.com/request-a-demo">
@@ -94,7 +94,7 @@
94
  <label for="ssmanager_ssmanager_setup_feed">Feed</label>
95
  </td>
96
  <td class="value">
97
- <select disabled id="ssmanager_ssmanager_setup_feed" name="groups[ssmanager_setup][fields][feed][value]" class=" select"></select>
98
  </td>
99
  <td></td>
100
  </tr>
41
  <label for="ssmanager_ssmanager_setup_username">SearchSpring Username</label>
42
  </td>
43
  <td class="value">
44
+ <input id="ssmanager_ssmanager_setup_username" tabindex="1" class=" input-text" type="text">
45
  </td>
46
  <td class="ss-no-account" rowspan="3">
47
  <a href="http://www.searchspring.com/request-a-demo">
52
  <tr id="row_ssmanager_ssmanager_setup_password" class="ss-step1">
53
  <td class="label"><label for="ssmanager_ssmanager_setup_password">SearchSpring Password</label></td>
54
  <td class="value">
55
+ <input id="ssmanager_ssmanager_setup_password" tabindex="2" class=" input-text" type="password">
56
  </td>
57
  <td class="scope-label"></td>
58
  <td class=""></td>
59
  </tr>
60
  <tr id="row_ssmanager_ssmanager_setup_authentication_method" class="ss-step1">
61
+ <td class="label"><label for="ssmanager_ssmanager_setup_authentication_method">Authentication Method</label></td>
62
  <td class="value">
63
+ <select id="ssmanager_ssmanager_setup_authentication_method" tabindex="2" class=" input-text">
64
  <option value="simple">Simple Authentication</option>
65
  <option value="oauth">OAuth API</option>
66
  </select>
72
  <tr id="row_ssmanager_ssmanager_setup_login" class="ss-step1">
73
  <td class="label"></td>
74
  <td class="value">
75
+ <button id="ss-login-button" title="Login" type="button" class="scalable" style=""><span><span><span>Log In</span></span></span></button>
76
  </td>
77
  <td></td>
78
  </tr>
81
  <label for="ssmanager_ssmanager_setup_website">Website</label>
82
  </td>
83
  <td class="value">
84
+ <select disabled id="ssmanager_ssmanager_setup_website" class=" select"></select>
85
  </td>
86
  <td class="ss-no-account" rowspan="3">
87
  <a href="http://www.searchspring.com/request-a-demo">
94
  <label for="ssmanager_ssmanager_setup_feed">Feed</label>
95
  </td>
96
  <td class="value">
97
+ <select disabled id="ssmanager_ssmanager_setup_feed" class=" select"></select>
98
  </td>
99
  <td></td>
100
  </tr>
app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/setup_scope.phtml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="section-config active">
2
+ <div class="entry-edit-head ">
3
+ <a>SearchSpring Connection</a>
4
+ </div>
5
+ <fieldset class="config collapseable" id="ssmanager_ssmanager_setup">
6
+ <legend>SearchSpring Connection</legend>
7
+ <table cellspacing="0" class="form-list">
8
+ <colgroup class="label"></colgroup>
9
+ <colgroup class="value"></colgroup>
10
+ <colgroup class="scope-label"></colgroup>
11
+ <colgroup class=""></colgroup>
12
+ <tbody>
13
+ <tr id="row_ssmanager_ssmanager_setup_connection">
14
+ <td class="label">
15
+ <label for="ssmanager_ssmanager_setup_connection">Connection</label>
16
+ </td>
17
+ <td class="value">
18
+ <?php if ($this->isInDefaultScope()): ?>
19
+ <select id="ss-store-select">
20
+ <option class="ss-choose-select" value="na">Choose a store to connect and/or manage</option>
21
+ <?php foreach($this->getWebsites() as $website): ?>
22
+ <optgroup label="<?php echo $this->getWebsiteLabel($website) ?>" />
23
+ <?php foreach($this->getGroups($website) as $group): ?>
24
+ <optgroup label="&nbsp;&nbsp;&nbsp;<?php echo $this->getGroupLabel($group) ?>" />
25
+ <?php foreach($this->getStores($group) as $store): ?>
26
+ <option value="<?php echo $this->getStoreUrl($store) ?>">
27
+ &nbsp;&nbsp;&nbsp;<?php echo $this->getStoreLabel($store) ?> (<?php echo $this->isSetup($store) ? $this->__('Connected') : $this->__('Not Connected') ?>)
28
+ </option>
29
+ <?php endforeach; ?>
30
+ <?php endforeach; ?>
31
+ <?php endforeach; ?>
32
+ </select>
33
+ <?php elseif ($this->isInWebsiteScope()): ?>
34
+ <select id="ss-store-select">
35
+ <option class="ss-choose-select" value="na">Choose a store to connect and/or manage</option>
36
+ <?php foreach($this->getGroups() as $group): ?>
37
+ <optgroup label="&nbsp;&nbsp;&nbsp;<?php echo $this->getGroupLabel($group) ?>" />
38
+ <?php foreach($this->getStores($group) as $store): ?>
39
+ <option value="<?php echo $this->getStoreUrl($store) ?>">
40
+ &nbsp;&nbsp;&nbsp;<?php echo $this->getStoreLabel($store) ?> (<?php echo $this->isSetup($store) ? $this->__('Connected') : $this->__('Not Connected') ?>)
41
+ </option>
42
+ <?php endforeach; ?>
43
+ <?php endforeach; ?>
44
+ </select>
45
+ <?php endif; ?>
46
+ </td>
47
+ <td class="scope-label"></td>
48
+ <td class=""></td>
49
+ </tr>
50
+ </tbody>
51
+ </table>
52
+ </fieldset>
53
+ </div>
54
+
55
+ <style type="text/css">
56
+ <?php // TODO -- Can we move this to a sheet? ?>
57
+ .entry-edit-head a { text-decoration: none; }
58
+ </style>
59
+
js/searchspring/setup.js CHANGED
@@ -1,5 +1,4 @@
1
  jQuery.noConflict();
2
-
3
  (function($){
4
 
5
  $(function() {
@@ -22,15 +21,17 @@ jQuery.noConflict();
22
  var cancelButton = $('#ss-cancel-button');
23
  var confirmButton = $('#ss-confirm-button');
24
 
25
- var apiUrlEl = $('#ssmanager_ssmanager_api_base_url');
26
  var siteIdEl = $('#ssmanager_ssmanager_api_site_id');
27
  var feedIdEl = $('#ssmanager_ssmanager_api_feed_id');
28
  var secretKeyEl = $('#ssmanager_ssmanager_api_secret_key');
29
 
30
  var authEl = $('#ssmanager_ssmanager_api_authentication_method');
31
 
 
 
32
  // Hide Connection Settings Block
33
- apiUrlEl.parents('.section-config').hide();
 
34
 
35
  reconnectButton.bind('click', reconnect);
36
 
@@ -38,6 +39,8 @@ jQuery.noConflict();
38
  usernameField.bind('keypress', loginEnter);
39
  passwordField.bind('keypress', loginEnter);
40
 
 
 
41
  function reconnect() {
42
  $('.ss-login-connected').hide();
43
  $('.ss-login-not_connected').show();
@@ -169,8 +172,6 @@ jQuery.noConflict();
169
 
170
  function connectSettings() {
171
  confirmButton.addClass('loading');
172
- apiUrlEl.val('https://api-beta.searchspring.net');
173
- //apiUrlEl.val('https://23.23.23.2');
174
  siteIdEl.val(websites[websiteSelect.val()].siteId);
175
  secretKeyEl.val(websites[websiteSelect.val()].secretKey);
176
  feedIdEl.val(feedSelect.val());
@@ -182,6 +183,13 @@ jQuery.noConflict();
182
  configForm.submit();
183
  }
184
 
 
 
 
 
 
 
 
185
  function getObjectLength(obj) {
186
  var length = 0;
187
  for (var key in obj) {
1
  jQuery.noConflict();
 
2
  (function($){
3
 
4
  $(function() {
21
  var cancelButton = $('#ss-cancel-button');
22
  var confirmButton = $('#ss-confirm-button');
23
 
 
24
  var siteIdEl = $('#ssmanager_ssmanager_api_site_id');
25
  var feedIdEl = $('#ssmanager_ssmanager_api_feed_id');
26
  var secretKeyEl = $('#ssmanager_ssmanager_api_secret_key');
27
 
28
  var authEl = $('#ssmanager_ssmanager_api_authentication_method');
29
 
30
+ var storeSelect = $('#ss-store-select');
31
+
32
  // Hide Connection Settings Block
33
+ var apiHeadEl = $('#ssmanager_ssmanager_api-head');
34
+ apiHeadEl.parents('.section-config').hide();
35
 
36
  reconnectButton.bind('click', reconnect);
37
 
39
  usernameField.bind('keypress', loginEnter);
40
  passwordField.bind('keypress', loginEnter);
41
 
42
+ storeSelect.bind('change', selectStore);
43
+
44
  function reconnect() {
45
  $('.ss-login-connected').hide();
46
  $('.ss-login-not_connected').show();
172
 
173
  function connectSettings() {
174
  confirmButton.addClass('loading');
 
 
175
  siteIdEl.val(websites[websiteSelect.val()].siteId);
176
  secretKeyEl.val(websites[websiteSelect.val()].secretKey);
177
  feedIdEl.val(feedSelect.val());
183
  configForm.submit();
184
  }
185
 
186
+ function selectStore() {
187
+ var val = $(this).val();
188
+ if(val != 'na') {
189
+ location.href = val;
190
+ }
191
+ }
192
+
193
  function getObjectLength(obj) {
194
  var length = 0;
195
  for (var key in obj) {
package.xml CHANGED
@@ -1,2 +1,4 @@
1
  <?xml version="1.0"?>
2
- <package><name>SearchSpring_Manager</name><version>1.10.2</version><stability>stable</stability><license>SearchSpring Terms of Service</license><channel>community</channel><extends></extends><summary>Connects your Magento store to SearchSpring's subscription-based site search and category navigation services.</summary><description>SearchSpring improves the default Magento site search. Providing merchants with flexible, learning technology that enhances site search functionality, optimizes product results in real-time, allows for custom navigation, automates product SEO and provides fully configurable merchandising. Shoppers will find your products quickly and easily, generating repeat visits and increased sales!</description><notes>Feed generation optimization, resized image fetching.</notes><authors><author><name>SearchSpring Development Team</name><user>searchspring</user><email>info@searchspring.com</email></author></authors><date>2015-02-13</date><time>10:07:37</time><compatible></compatible><dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies><contents><target name="mage"><dir name="app"><dir name="code"><dir name="community"><dir name="SearchSpring"><file name="ErrorCodes.php" hash="fd8f65524b8f8708471f106aad294d0a"/><dir name="Manager"><file name="Collection.php" hash="0ebd621892bccd5d59ed3edcafada89b"/><file name="Iterator.php" hash="6f68effbe5452c1a6abd97cbf4b528ee"/><dir name="Block"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Field"><file name="Timespan.php" hash="04e13b7375f1d57195aae385256c1274"/></dir><dir name="Fieldset"><file name="Hint.php" hash="82ff17ab23c59b1c5fd095dfdfe153bb"/><file name="Setup.php" hash="5f467825780852b12cba72abfbe9e596"/></dir></dir></dir></dir><dir name="Layer"><file name="View.php" hash="ae69c24e04b03048cc2cd9b7cc436603"/><dir name="Filter"><file name="SearchSpringCategory.php" hash="39fc479e15275db637c227e596d040f5"/><file name="SearchSpringList.php" hash="743bb9cb690c9df3d74e9de2c206d020"/></dir></dir></dir><dir name="Builder"><file name="OperationBuilder.php" hash="d85ad0a561091d34b3b98efd95646962"/></dir><dir name="controllers"><file name="GenerateController.php" hash="a8b0e0bd6d735ce33ca7ed09ecea3a24"/></dir><dir name="Entity"><file name="IndexingRequestBody.php" hash="6bdab2264cbb8dbafa666f493bf9bd60"/><file name="OperationsCollection.php" hash="be19c06bb1be447bd57601e67ac87557"/><file name="RecordsCollection.php" hash="c336526a10625a859c093d2b25dff911"/><file name="RequestBody.php" hash="4b6d7fd23f57527e9d53dd938d69a88d"/><file name="RequestParams.php" hash="f4778075264cd441c87e8de220fdd2a9"/><file name="SearchCollection.php" hash="e64543d4315c16d7a0717da8ec081e09"/><file name="SearchRequestBody.php" hash="bdfd9a2244ce1f9a7db8daca6e613176"/><file name="SearchResult.php" hash="863a720b8a837bd7e56e8a7fa781a1a1"/><dir name="SearchResult"><file name="Result.php" hash="ea4e36fe95e4c412af8d525a4628db5d"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="879a64f061de25cbd1292f4ead6c5b15"/><file name="api2.xml" hash="c958f2f25b6fcd0b467dc20fb945f1b4"/><file name="composer.json" hash="6a36df77eee995788585f7a8a535fa27"/><file name="config.xml" hash="c2ff0057bf6d68aac70b7ba285d44f45"/><file name="readme.md" hash="30d24e1aae89696090dcf22203802ad2"/><file name="system.xml" hash="5ac00df31f8874e68022b2bd21b2ab96"/></dir><dir name="Factory"><file name="ApiFactory.php" hash="2a0b31683e75cfa4c10bf6476a7346bf"/><file name="GeneratorFactory.php" hash="0609eec402a1a5df2f51079844231a4f"/><file name="IndexingRequestBodyFactory.php" hash="10e0717f6d938a06a9cfc68f6ac1ceaf"/><file name="PricingFactory.php" hash="bbba11efb7a2fd6ebf8c47d896c365a2"/><file name="SearchRequestBodyFactory.php" hash="4eb409f25d480ca11b08f206f7f4facc"/></dir><dir name="Generator"><file name="ProductGenerator.php" hash="69147879853105d0a388596b479a0b81"/></dir><dir name="Handler"><file name="ApiErrorHandler.php" hash="a287ed86d8d83a88b5a8f555015f2e80"/></dir><dir name="Helper"><file name="Data.php" hash="eb3620bd8eea016cc280c9d0a00c6644"/><file name="Http.php" hash="13d41de08d623892f6526817bf4ee971"/><file name="Oauth.php" hash="b6dd04c07e1322c38b76668b88d7d1e4"/><file name="Product.php" hash="e576ce5f09e32654cd0c1014b85d8d5a"/><file name="Webservice.php" hash="054d23d7db9da73b44bcbad083bd813e"/><dir name="Catalog"><file name="Image.php" hash="00c7be2874011db790e9e05d394d90d4"/></dir></dir><dir name="Model"><file name="Layer.php" hash="54afafde0cff2fcf2de2240bbb6cdb20"/><file name="Source.php" hash="906ec0129e2c51551d02a52dd1736892"/><dir name="Api2"><dir name="Auth"><file name="Adapter.php" hash="909c416381a44ef80b0665926a9e4ab2"/></dir><dir name="Indexing"><dir name="Rest"><dir name="Admin"><file name="V1.php" hash="3a306d75dccab962e7c81ecb82d35c60"/></dir></dir></dir></dir><dir name="Catalog"><dir name="Category"><dir name="Attribute"><dir name="EnableSearchSpring"><file name="Backend.php" hash="12d9799f273951ca22717df93088e6bb"/><file name="Source.php" hash="03fad199c925e07fffd5e42f63cbb1bb"/></dir></dir></dir><dir name="Product"><file name="Image.php" hash="29f549e5d2fbe5c04360327fadcf79f4"/></dir></dir><dir name="Layer"><dir name="Filter"><file name="SearchSpring.php" hash="128a0a569e7d7a4e19b74bc947a4c01a"/><file name="SearchSpringCategory.php" hash="6654bdad88ca43917dbe072b6dfb21f8"/><file name="SearchSpringCategoryItem.php" hash="2dc143031706d2c6a7f4481ef7434bf4"/><file name="SearchSpringItem.php" hash="459fae0c48c411a58ae885d56124b1f3"/></dir></dir><dir name="Oauth"><file name="Server.php" hash="c5c2df03bc419caa49c8b1e3fe7d50ac"/></dir><dir name="Observer"><file name="CategorySaveObserver.php" hash="cfbbaabb8eb31d79d8106d490758f3d7"/><file name="ConfigObserver.php" hash="209a8f8b97e5b7a0c1c7539d493371e6"/><file name="LiveIndexer.php" hash="0fb8e5738d9431c9e00a928477343d2f"/><file name="ProductSaveObserver.php" hash="68bd69b0e7a810e9eb9c29c09cc61d39"/></dir><dir name="Source"><file name="Abstract.php" hash="965bfb89b539db821a75dd71ef7e7d5f"/></dir></dir><dir name="Operation"><file name="Product.php" hash="e190aa6864082e1b8dfe011f41f9f1df"/><file name="ProductOperation.php" hash="84df2df9055b52753680d4ccd94b6f68"/><dir name="Product"><file name="SetCategories.php" hash="0762e4f5975c98e87c6dcedbb9bb621c"/><file name="SetCoreFields.php" hash="b988b1a30f2d6a44b66a7ffbd33f1586"/><file name="SetFields.php" hash="8896858d190cadc5b19104120d7a1b58"/><file name="SetId.php" hash="792f08d85b399d57afca103e9d0eabd1"/><file name="SetImages.php" hash="7d0fc10b390fc446c870679e99a80a65"/><file name="SetOptions.php" hash="3db65aee252c0eb6a5c65eac1b641bfb"/><file name="SetPricing.php" hash="a2bb94df706c1c62c5b1cbe26d8afdf6"/><file name="SetRatings.php" hash="d04ad0e2f5e1820b4b8b94880a83aef7"/><file name="SetReport.php" hash="dec51d5202b0ceb826500275cf42941a"/></dir></dir><dir name="Provider"><file name="ProductCollectionProvider.php" hash="e639e1667ed0f2bc59f2272ad36d3107"/><dir name="ProductCollection"><file name="FeedProvider.php" hash="b82e2fae57e7c2d4e5b9d5a4b8440780"/><file name="ProductProvider.php" hash="dd8ce756d8f2a8117758e10ac220267e"/></dir></dir><dir name="Request"><file name="JSON.php" hash="541d9331b620bc92820abe5630824e41"/></dir><dir name="Service"><dir name="SearchSpring"><file name="ApiAdapter.php" hash="6878323855ef8a7bd3de8e707e104610"/><file name="IndexingApiAdapter.php" hash="92fa5e7682ec42e3c1f8b62c490488cc"/><file name="SearchApiAdapter.php" hash="bda32ec16f53054e75fb331ffcc0c155"/></dir></dir><dir name="sql"><dir name="searchspring_manager"><file name="mysql4-install-1.0.0.php" hash="c0de6de0b74290978f65d7896768e89f"/><file name="mysql4-upgrade-0.4.0-1.0.0.php" hash="1f59325ef181499292deb7775a5e4dc2"/></dir></dir><dir name="Strategy"><file name="PricingStrategy.php" hash="ee021066a76db7fc2710e329daed1a4b"/><dir name="Pricing"><file name="BundleStrategy.php" hash="df2aee59e4a6ff19acf13bbc5f9ad5b9"/><file name="ConfigurableStrategy.php" hash="403e8298c4799ba2b7701255db86a941"/><file name="GroupedStrategy.php" hash="383c482098f502d6cde5dd9ed5cf9e13"/><file name="SimpleStrategy.php" hash="a1b9e7bae38c2def28a7b0a9e6fd456e"/><file name="Strategy.php" hash="9d0fda2a4ff168ab319fd0c2240501fd"/></dir></dir><dir name="String"><file name="Sanitizer.php" hash="e89fa5f86714621ea554aea9aa3f796c"/></dir><dir name="ThirdParty"><dir name="Amasty"><file name="LabelsOperation.php" hash="99479aae0bb856c5b43a44ec9d741eef"/></dir><dir name="TBT"><file name="RewardsOperation.php" hash="7e17b6205037de39c4154c7f4041836d"/></dir></dir><dir name="Transformer"><file name="ProductCollectionToRecordCollectionTransformer.php" hash="44bdf6ecf2c06c6a5bc0f96cff427b4e"/></dir><dir name="Validator"><file name="ProductValidator.php" hash="08ea9751207d9fc0fedb98716331f248"/></dir><dir name="VarienObject"><file name="Data.php" hash="44f184dabbf0db1958d1f922af92f6a8"/></dir><dir name="Writer"><file name="ProductWriter.php" hash="c7b218d215e68b5e3076ef654a41b141"/><dir name="Product"><file name="FileWriter.php" hash="b296be56f236635afc20e817e6277c80"/><file name="ResponseWriter.php" hash="c1af0cc22a9e823ec07175e2d8e34c9f"/><dir name="Params"><file name="FileWriterParams.php" hash="24080fdf866bdac26ed4571c4c7fc9f9"/><file name="ResponseWriterParams.php" hash="bf66753a5952a78c0049f69ccaa6e74d"/></dir></dir></dir></dir></dir></dir></dir><dir name="design"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="searchspring"><dir name="manager"><dir name="system"><dir name="config"><dir name="fieldset"><file name="hint.phtml" hash="a0e3da8cd870432cce2849a41aa1c5b9"/><file name="setup.phtml" hash="e019e039a41992ecf72a0ad956de7630"/></dir></dir></dir></dir></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="searchspring_manager.xml" hash="32227434f5770ddfd93de42d7feeff71"/></dir><dir name="template"><dir name="searchspring_manager"><dir name="layer"><file name="category_filter.phtml" hash="a9dd8c95a1d63b514cc8e9c5939168c5"/><file name="filter.phtml" hash="79888aac8f72d3597f2980d471237d64"/><file name="state.phtml" hash="168546af0f48a42c31c5a5d66b1de1c3"/><file name="view.phtml" hash="509776f71bc643839ade8d7e025764cb"/></dir><dir name="product"><file name="list.phtml" hash="9849b76e4fefac8ce181b428b5c6a148"/><dir name="list"><file name="toolbar.phtml" hash="aa2235708f7e861c63ba3e3e33ec041f"/></dir></dir></dir></dir></dir></dir></dir></dir><dir name="etc"><dir name="modules"><file name="SearchSpring_SearchSpringManager.xml" hash="613ec81afc8d858f60dee96d91719c09"/></dir></dir><dir name="locale"><dir name="en_US"><dir name="template"><dir name="email"><file name="searchspring_api_error.html" hash="20bc8d923f96ca22dd2fe8cd876c49fe"/></dir></dir></dir></dir></dir><dir name="js"><dir name="searchspring"><file name="jquery-1.11.1.min.js" hash="8101d596b2b8fa35fe3a634ea342d7c3"/><file name="setup.js" hash="7754359957784a96bd0f9c188979b136"/></dir></dir><dir name="skin"><dir name="frontend"><dir name="base"><dir name="default"><dir name="searchspring"><dir name="css"><file name="styles.css" hash="95f1ec4a442f0e2239f9a2db9e6e517c"/></dir></dir></dir></dir></dir></dir></target></contents></package>
 
 
1
  <?xml version="1.0"?>
2
+ <package><name>SearchSpring_Manager</name><version>2.0.0</version><stability>stable</stability><license>SearchSpring Terms of Service</license><channel>community</channel><extends></extends><summary>Connects your Magento store to SearchSpring's subscription-based site search and category navigation services.</summary><description>SearchSpring improves the default Magento site search. Providing merchants with flexible, learning technology that enhances site search functionality, optimizes product results in real-time, allows for custom navigation, automates product SEO and provides fully configurable merchandising. Shoppers will find your products quickly and easily, generating repeat visits and increased sales!</description><notes>Support for multi-store installations
3
+ Fixed categories from other stores being exported in feed
4
+ Added support for registering store/installation specific URLs with the SearchSpring account feed settings.</notes><authors><author><name>SearchSpring Development Team</name><user>searchspring</user><email>info@searchspring.com</email></author></authors><date>2015-03-04</date><time>15:05:25</time><compatible></compatible><dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies><contents><target name="mage"><dir name="app"><dir name="code"><dir name="community"><dir name="SearchSpring"><file name="ErrorCodes.php" hash="fd8f65524b8f8708471f106aad294d0a"/><dir name="Manager"><file name="Collection.php" hash="0ebd621892bccd5d59ed3edcafada89b"/><file name="Iterator.php" hash="6f68effbe5452c1a6abd97cbf4b528ee"/><dir name="Block"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Field"><file name="Timespan.php" hash="04e13b7375f1d57195aae385256c1274"/></dir><dir name="Fieldset"><file name="Hint.php" hash="68f5052c08e8887d20029e19a2fdc978"/><file name="Setup.php" hash="eec3e7141cf00de064030c3d1de087ec"/></dir></dir></dir></dir><dir name="Layer"><file name="View.php" hash="ae69c24e04b03048cc2cd9b7cc436603"/><dir name="Filter"><file name="SearchSpringCategory.php" hash="39fc479e15275db637c227e596d040f5"/><file name="SearchSpringList.php" hash="743bb9cb690c9df3d74e9de2c206d020"/></dir></dir></dir><dir name="Builder"><file name="OperationBuilder.php" hash="d85ad0a561091d34b3b98efd95646962"/></dir><dir name="controllers"><file name="GenerateController.php" hash="5a87625f1b9f824365ac67c18c349277"/></dir><dir name="Entity"><file name="Credentials.php" hash="5b151a569bceffed81350454655bf9d9"/><file name="IndexingRequestBody.php" hash="5a492e6c16b738ee352ca517af44e479"/><file name="OperationsCollection.php" hash="be19c06bb1be447bd57601e67ac87557"/><file name="ProductDeletionToken.php" hash="978e8c51d9a31aa7bc35cbd5fbd5ae35"/><file name="RecordsCollection.php" hash="c336526a10625a859c093d2b25dff911"/><file name="RequestBody.php" hash="4b6d7fd23f57527e9d53dd938d69a88d"/><file name="RequestCredentials.php" hash="1c2936cf0779322225ceb27e4b8fafb1"/><file name="RequestParams.php" hash="f4778075264cd441c87e8de220fdd2a9"/><file name="SearchCollection.php" hash="e64543d4315c16d7a0717da8ec081e09"/><file name="SearchRequestBody.php" hash="f3f2993d0e8bfac4c0686ef364f978eb"/><file name="SearchResult.php" hash="863a720b8a837bd7e56e8a7fa781a1a1"/><dir name="SearchResult"><file name="Result.php" hash="ea4e36fe95e4c412af8d525a4628db5d"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="879a64f061de25cbd1292f4ead6c5b15"/><file name="api2.xml" hash="c958f2f25b6fcd0b467dc20fb945f1b4"/><file name="composer.json" hash="6a36df77eee995788585f7a8a535fa27"/><file name="config.xml" hash="49edee0529ab328d2602eefb077200be"/><file name="readme.md" hash="30d24e1aae89696090dcf22203802ad2"/><file name="system.xml" hash="a2854dfd977765e195ddf4a95ffa5abe"/></dir><dir name="Factory"><file name="ApiFactory.php" hash="ad09b49e1b294325bd3eb99e17849bb2"/><file name="GeneratorFactory.php" hash="8d61b22ccae2ac404bfbbd0ac1a727d7"/><file name="IndexingRequestBodyFactory.php" hash="10e0717f6d938a06a9cfc68f6ac1ceaf"/><file name="LiveIndexerFactory.php" hash="18758a1c85075231ce995506f10a2440"/><file name="PricingFactory.php" hash="bbba11efb7a2fd6ebf8c47d896c365a2"/><file name="SearchRequestBodyFactory.php" hash="f2e1f13f20bc15255f9d755ba6f20b23"/></dir><dir name="Generator"><file name="ProductGenerator.php" hash="69147879853105d0a388596b479a0b81"/></dir><dir name="Handler"><file name="ApiErrorHandler.php" hash="a287ed86d8d83a88b5a8f555015f2e80"/></dir><dir name="Helper"><file name="Data.php" hash="4b6adcb5e3dd09d679043ecea0dd845f"/><file name="Http.php" hash="b5922b50413d60818d8ef63649edfff3"/><file name="Oauth.php" hash="b6dd04c07e1322c38b76668b88d7d1e4"/><file name="Product.php" hash="2d49e1559dc1fb38a5796a42293ae796"/><file name="Webservice.php" hash="88b592aa459f8e6fa1d855e5b94e5597"/><dir name="Catalog"><file name="Image.php" hash="00c7be2874011db790e9e05d394d90d4"/></dir></dir><dir name="Model"><file name="Config.php" hash="2c508f903cd39c251e993fa1ae95b59d"/><file name="Layer.php" hash="eede52743afa3f0f178ad81a4e5681bb"/><file name="Observer.php" hash="bf0973d689ac56e9de59f9ddeed95eeb"/><file name="Source.php" hash="a27000dc01a8778c33bccf88dc76346e"/><dir name="Api2"><dir name="Auth"><file name="Adapter.php" hash="bdf620903d412f1da7cc596d6c54b618"/></dir><dir name="Indexing"><dir name="Rest"><dir name="Admin"><file name="V1.php" hash="6a52b0077bfb5376f749ff60b77bb6d8"/></dir></dir></dir></dir><dir name="Catalog"><dir name="Category"><dir name="Attribute"><dir name="EnableSearchSpring"><file name="Backend.php" hash="12d9799f273951ca22717df93088e6bb"/><file name="Source.php" hash="03fad199c925e07fffd5e42f63cbb1bb"/></dir></dir></dir><dir name="Product"><file name="Image.php" hash="29f549e5d2fbe5c04360327fadcf79f4"/></dir></dir><dir name="Layer"><dir name="Filter"><file name="SearchSpring.php" hash="128a0a569e7d7a4e19b74bc947a4c01a"/><file name="SearchSpringCategory.php" hash="6654bdad88ca43917dbe072b6dfb21f8"/><file name="SearchSpringCategoryItem.php" hash="2dc143031706d2c6a7f4481ef7434bf4"/><file name="SearchSpringItem.php" hash="459fae0c48c411a58ae885d56124b1f3"/></dir></dir><dir name="Oauth"><file name="Server.php" hash="c5c2df03bc419caa49c8b1e3fe7d50ac"/></dir><dir name="Observer"><file name="CategorySaveObserver.php" hash="c7906db422635d9f2b19831f52ecc896"/><file name="ConfigObserver.php" hash="0768660a51601e2d17372f815ae84f74"/><file name="ProductSaveObserver.php" hash="4a1926a7e8c598e20bdb4724036506c0"/></dir><dir name="Source"><file name="Abstract.php" hash="965bfb89b539db821a75dd71ef7e7d5f"/></dir></dir><dir name="Operation"><file name="Product.php" hash="03fa9bdacbfc9b9217c2c6bc648f4608"/><file name="ProductOperation.php" hash="a6b554a4314461d9e5b48885169e680e"/><dir name="Product"><file name="SetCategories.php" hash="3db2105af06d6e1851a965289dcff85d"/><file name="SetCoreFields.php" hash="c4944e28815716a81d591769d0ef4776"/><file name="SetFields.php" hash="8896858d190cadc5b19104120d7a1b58"/><file name="SetId.php" hash="792f08d85b399d57afca103e9d0eabd1"/><file name="SetImages.php" hash="7d0fc10b390fc446c870679e99a80a65"/><file name="SetOptions.php" hash="3db65aee252c0eb6a5c65eac1b641bfb"/><file name="SetPricing.php" hash="a2bb94df706c1c62c5b1cbe26d8afdf6"/><file name="SetRatings.php" hash="d04ad0e2f5e1820b4b8b94880a83aef7"/><file name="SetReport.php" hash="a87c8a98c7f474a19618c7a7be8fdab1"/></dir></dir><dir name="Provider"><file name="ProductCollectionProvider.php" hash="e639e1667ed0f2bc59f2272ad36d3107"/><dir name="ProductCollection"><file name="FeedProvider.php" hash="2238e8b44f24178ef71af1df1b3cba6c"/><file name="ProductProvider.php" hash="56a0aae894f910eddbdced5e4c828668"/></dir></dir><dir name="Request"><file name="JSON.php" hash="541d9331b620bc92820abe5630824e41"/></dir><dir name="Service"><file name="LiveIndexer.php" hash="e952b80d171fef13c3e96c24d9313963"/><dir name="SearchSpring"><file name="ApiAdapter.php" hash="9ce66dddd09040d69457dc1735440b71"/><file name="IndexingApiAdapter.php" hash="da876af1440547abae4f1dbe6b25b3bf"/><file name="SearchApiAdapter.php" hash="bda32ec16f53054e75fb331ffcc0c155"/></dir></dir><dir name="sql"><dir name="searchspring_manager"><file name="mysql4-data-upgrade-1.10.2-2.0.0.php" hash="b27aae0f397c5d938a29dc12f6bbd4c8"/><file name="mysql4-install-1.0.0.php" hash="c0de6de0b74290978f65d7896768e89f"/><file name="mysql4-upgrade-0.4.0-1.0.0.php" hash="1f59325ef181499292deb7775a5e4dc2"/></dir></dir><dir name="Strategy"><file name="PricingStrategy.php" hash="ee021066a76db7fc2710e329daed1a4b"/><dir name="Pricing"><file name="BundleStrategy.php" hash="df2aee59e4a6ff19acf13bbc5f9ad5b9"/><file name="ConfigurableStrategy.php" hash="403e8298c4799ba2b7701255db86a941"/><file name="GroupedStrategy.php" hash="383c482098f502d6cde5dd9ed5cf9e13"/><file name="SimpleStrategy.php" hash="a1b9e7bae38c2def28a7b0a9e6fd456e"/><file name="Strategy.php" hash="9d0fda2a4ff168ab319fd0c2240501fd"/></dir></dir><dir name="String"><file name="Sanitizer.php" hash="e89fa5f86714621ea554aea9aa3f796c"/></dir><dir name="ThirdParty"><dir name="Amasty"><file name="LabelsOperation.php" hash="99479aae0bb856c5b43a44ec9d741eef"/></dir><dir name="TBT"><file name="RewardsOperation.php" hash="7e17b6205037de39c4154c7f4041836d"/></dir></dir><dir name="Transformer"><file name="ProductCollectionToRecordCollectionTransformer.php" hash="d9cb83ae72b6a30ed0c3b978f16ebdef"/></dir><dir name="Validator"><file name="ProductValidator.php" hash="b73a3cee6124c74c6433acb0552ae4bc"/></dir><dir name="VarienObject"><file name="Data.php" hash="44f184dabbf0db1958d1f922af92f6a8"/></dir><dir name="Writer"><file name="ProductWriter.php" hash="c7b218d215e68b5e3076ef654a41b141"/><dir name="Product"><file name="FileWriter.php" hash="b296be56f236635afc20e817e6277c80"/><file name="ResponseWriter.php" hash="c1af0cc22a9e823ec07175e2d8e34c9f"/><dir name="Params"><file name="FileWriterParams.php" hash="728da0fc1385ed651be354af370b34d0"/><file name="ResponseWriterParams.php" hash="bf66753a5952a78c0049f69ccaa6e74d"/></dir></dir></dir></dir></dir></dir></dir><dir name="design"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="searchspring"><dir name="manager"><dir name="system"><dir name="config"><dir name="fieldset"><file name="hint.phtml" hash="a0e3da8cd870432cce2849a41aa1c5b9"/><file name="setup.phtml" hash="d88bd7a5d08e8096050e43b3fa3cf80f"/><file name="setup_scope.phtml" hash="95e8d1fd92c9f10a940455a3f74e01bb"/></dir></dir></dir></dir></dir></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="searchspring_manager.xml" hash="32227434f5770ddfd93de42d7feeff71"/></dir><dir name="template"><dir name="searchspring_manager"><dir name="layer"><file name="category_filter.phtml" hash="a9dd8c95a1d63b514cc8e9c5939168c5"/><file name="filter.phtml" hash="79888aac8f72d3597f2980d471237d64"/><file name="state.phtml" hash="168546af0f48a42c31c5a5d66b1de1c3"/><file name="view.phtml" hash="509776f71bc643839ade8d7e025764cb"/></dir><dir name="product"><file name="list.phtml" hash="9849b76e4fefac8ce181b428b5c6a148"/><dir name="list"><file name="toolbar.phtml" hash="aa2235708f7e861c63ba3e3e33ec041f"/></dir></dir></dir></dir></dir></dir></dir></dir><dir name="etc"><dir name="modules"><file name="SearchSpring_SearchSpringManager.xml" hash="613ec81afc8d858f60dee96d91719c09"/></dir></dir><dir name="locale"><dir name="en_US"><dir name="template"><dir name="email"><file name="searchspring_api_error.html" hash="20bc8d923f96ca22dd2fe8cd876c49fe"/></dir></dir></dir></dir></dir><dir name="js"><dir name="searchspring"><file name="jquery-1.11.1.min.js" hash="8101d596b2b8fa35fe3a634ea342d7c3"/><file name="setup.js" hash="0950cc8acffb14cb9ce25b9b3a2ffd4a"/></dir></dir><dir name="skin"><dir name="frontend"><dir name="base"><dir name="default"><dir name="searchspring"><dir name="css"><file name="styles.css" hash="95f1ec4a442f0e2239f9a2db9e6e517c"/></dir></dir></dir></dir></dir></dir></target></contents></package>