SearchSpring_Manager - Version 1.9.0

Version Notes

Speed Optimizations for Feed Regeneration
Better Root Category skipping logic
Added Support for Amasty Labels
Added Support for TBT Rewards (product earnable points)
Added Support for product qty sales data

Download this release

Release Info

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


Code changes from version 1.1.0 to 1.9.0

Files changed (49) hide show
  1. app/code/community/SearchSpring/ErrorCodes.php +21 -0
  2. app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Hint.php +49 -0
  3. app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Setup.php +53 -0
  4. app/code/community/SearchSpring/Manager/Entity/IndexingRequestBody.php +3 -1
  5. app/code/community/SearchSpring/Manager/Entity/RecordsCollection.php +4 -0
  6. app/code/community/SearchSpring/Manager/Factory/ApiFactory.php +38 -33
  7. app/code/community/SearchSpring/Manager/Factory/GeneratorFactory.php +46 -1
  8. app/code/community/SearchSpring/Manager/Generator/ProductGenerator.php +8 -0
  9. app/code/community/SearchSpring/Manager/Handler/ApiErrorHandler.php +5 -3
  10. app/code/community/SearchSpring/Manager/Helper/Data.php +131 -3
  11. app/code/community/SearchSpring/Manager/Helper/Http.php +76 -0
  12. app/code/community/SearchSpring/Manager/Helper/Oauth.php +205 -0
  13. app/code/community/SearchSpring/Manager/Helper/Product.php +33 -0
  14. app/code/community/SearchSpring/Manager/Helper/Webservice.php +115 -0
  15. app/code/community/SearchSpring/Manager/Model/Api2/Auth/Adapter.php +66 -0
  16. app/code/community/SearchSpring/Manager/Model/Api2/Indexing/Rest/Admin/V1.php +197 -0
  17. app/code/community/SearchSpring/Manager/Model/Oauth/Server.php +33 -0
  18. app/code/community/SearchSpring/Manager/Model/Observer/CategorySaveObserver.php +4 -13
  19. app/code/community/SearchSpring/Manager/Model/Observer/ConfigObserver.php +95 -0
  20. app/code/community/SearchSpring/Manager/Model/Observer/LiveIndexer.php +38 -0
  21. app/code/community/SearchSpring/Manager/Model/Observer/ProductSaveObserver.php +6 -9
  22. app/code/community/SearchSpring/Manager/Operation/Product.php +15 -0
  23. app/code/community/SearchSpring/Manager/Operation/Product/SetCategories.php +29 -13
  24. app/code/community/SearchSpring/Manager/Operation/Product/SetCoreFields.php +168 -152
  25. app/code/community/SearchSpring/Manager/Operation/Product/SetFields.php +8 -1
  26. app/code/community/SearchSpring/Manager/Operation/Product/SetReport.php +182 -0
  27. app/code/community/SearchSpring/Manager/Operation/ProductOperation.php +32 -4
  28. app/code/community/SearchSpring/Manager/Provider/ProductCollection/FeedProvider.php +1 -3
  29. app/code/community/SearchSpring/Manager/Request/JSON.php +48 -48
  30. app/code/community/SearchSpring/Manager/Service/SearchSpring/ApiAdapter.php +5 -5
  31. app/code/community/SearchSpring/Manager/Service/SearchSpring/IndexingApiAdapter.php +7 -6
  32. app/code/community/SearchSpring/Manager/Service/SearchSpring/SearchApiAdapter.php +3 -0
  33. app/code/community/SearchSpring/Manager/Strategy/Pricing/Strategy.php +0 -1
  34. app/code/community/SearchSpring/Manager/ThirdParty/Amasty/LabelsOperation.php +30 -0
  35. app/code/community/SearchSpring/Manager/ThirdParty/TBT/RewardsOperation.php +32 -0
  36. app/code/community/SearchSpring/Manager/Transformer/ProductCollectionToRecordCollectionTransformer.php +69 -0
  37. app/code/community/SearchSpring/Manager/Validator/ProductValidator.php +4 -2
  38. app/code/community/SearchSpring/Manager/Writer/Product/Params/FileWriterParams.php +45 -3
  39. app/code/community/SearchSpring/Manager/controllers/GenerateController.php +203 -95
  40. app/code/community/SearchSpring/Manager/etc/api2.xml +59 -0
  41. app/code/community/SearchSpring/Manager/etc/config.xml +78 -6
  42. app/code/community/SearchSpring/Manager/etc/system.xml +81 -5
  43. app/code/community/SearchSpring/Manager/sql/searchspring_manager/mysql4-install-1.0.0.php +49 -0
  44. app/code/community/SearchSpring/Manager/sql/searchspring_manager/mysql4-upgrade-0.4.0-1.0.0.php +49 -0
  45. app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/hint.phtml +77 -0
  46. app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/setup.phtml +186 -0
  47. js/searchspring/jquery-1.11.1.min.js +4 -0
  48. js/searchspring/setup.js +194 -0
  49. package.xml +5 -1
app/code/community/SearchSpring/ErrorCodes.php CHANGED
@@ -43,4 +43,25 @@ class SearchSpring_ErrorCodes
43
  * Ids parameter not found
44
  */
45
  const IDS_NOT_SET = 6;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
43
  * Ids parameter not found
44
  */
45
  const IDS_NOT_SET = 6;
46
+
47
+ /**
48
+ * BAD REQUEST FORMAT
49
+ */
50
+ const BAD_REQUEST = 7;
51
+
52
+ /**
53
+ * Unknown Error
54
+ */
55
+ const UNKNOWN_EXCEPTION = 8;
56
+
57
+ /**
58
+ * Authentication Credentials - Invalid
59
+ */
60
+ const AUTH_CREDENTIALS_INVALID = 9;
61
+
62
+ /**
63
+ * Authentication Credentials - Missing
64
+ */
65
+ const AUTH_CREDENTIALS_MISSING = 10;
66
+
67
  }
app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Hint.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Hint.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Hint
10
+ *
11
+ * Display hint above SearchSpring Manager Settings
12
+ *
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
+ }
45
+
46
+ public function getModuleUUID() {
47
+ return Mage::helper('searchspring_manager')->getUUID();
48
+ }
49
+ }
app/code/community/SearchSpring/Manager/Block/Adminhtml/System/Config/Fieldset/Setup.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Setup.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Block_Adminhtml_System_Config_Fieldset_Setup
10
+ *
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
30
+ *
31
+ * @param Varien_Data_Form_Element_Abstract $element
32
+ * @return string
33
+ */
34
+ public function render(Varien_Data_Form_Element_Abstract $element)
35
+ {
36
+ return $this->toHtml();
37
+ }
38
+
39
+ public function _prepareLayout() {
40
+ $head = $this->getLayout()->getBlock('head');
41
+ $head->addItem('js', 'searchspring/jquery-1.11.1.min.js');
42
+ $head->addItem('js', 'searchspring/setup.js');
43
+
44
+ parent::_prepareLayout();
45
+ return $this;
46
+ }
47
+
48
+ public function isSetup() {
49
+ $hlp = Mage::helper('searchspring_manager');
50
+ return $hlp->registerMagentoAPIAuthenticationWithSearchSpring(true);
51
+ }
52
+
53
+ }
app/code/community/SearchSpring/Manager/Entity/IndexingRequestBody.php CHANGED
@@ -78,12 +78,14 @@ class SearchSpring_Manager_Entity_IndexingRequestBody extends SearchSpring_Manag
78
  */
79
  public function jsonSerialize()
80
  {
 
 
81
  $body = array(
82
  'type' => $this->type,
83
  'ids' => $this->ids,
84
  'delete' => $this->shouldDelete,
85
  'feedId' => $this->feedId,
86
- 'generateUrl' => Mage::getUrl('searchspring/generate/index',array('_secure'=>true))
87
  );
88
 
89
  return $body;
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;
app/code/community/SearchSpring/Manager/Entity/RecordsCollection.php CHANGED
@@ -115,6 +115,10 @@ class SearchSpring_Manager_Entity_RecordsCollection
115
  */
116
  public function add($key, $element)
117
  {
 
 
 
 
118
  $this->fields[$this->key()][$key][] = $element;
119
 
120
  return $this;
115
  */
116
  public function add($key, $element)
117
  {
118
+ if(isset($this->fields[$this->key()][$key]) && !is_array($this->fields[$this->key()][$key])) {
119
+ $this->fields[$this->key()][$key] = array($this->fields[$this->key()][$key]);
120
+ }
121
+
122
  $this->fields[$this->key()][$key][] = $element;
123
 
124
  return $this;
app/code/community/SearchSpring/Manager/Factory/ApiFactory.php CHANGED
@@ -14,57 +14,62 @@
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
- $baseUrl = Mage::helper('searchspring_manager')->getApiBaseUrl();
31
- $siteId = Mage::helper('searchspring_manager')->getApiSiteId();
32
- $secretKey = Mage::helper('searchspring_manager')->getApiSecretKey();
 
 
 
 
 
33
 
34
- if (null === $baseUrl) {
35
- throw new UnexpectedValueException('SearchSpring: Base URL must be set');
36
- }
37
 
38
- if (null === $siteId) {
39
- throw new UnexpectedValueException('SearchSpring: Site ID must be set');
40
- }
41
 
42
- if (null === $secretKey) {
43
- throw new UnexpectedValueException('SearchSpring: Secret key must be set');
44
- }
45
 
46
- $apiErrorHandler = new SearchSpring_Manager_Handler_ApiErrorHandler();
 
 
 
 
 
 
47
 
48
  if($type == 'index') {
49
- $curl = new Varien_Http_Adapter_Curl();
50
- $curl->setConfig(array('timeout' => 15, 'userpwd' => $siteId . ':' . $secretKey));
51
 
52
  $api = new SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter(
53
  $apiErrorHandler,
54
- $curl,
55
  $baseUrl
56
  );
57
  } else {
58
- $curl = new Varien_Http_Adapter_Curl();
59
- $curl->setConfig(array('timeout' => 15));
60
-
61
  $api = new SearchSpring_Manager_Service_SearchSpring_SearchApiAdapter(
62
  $apiErrorHandler,
63
- $curl,
64
  $baseUrl
65
  );
66
  }
67
 
68
- return $api;
69
- }
70
  }
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();
 
 
48
 
49
+ $client = new Zend_Http_Client();
50
+
51
+ $client->setConfig(array(
52
+ 'maxredirects' => 0,
53
+ 'timeout' => 15,
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
  }
app/code/community/SearchSpring/Manager/Factory/GeneratorFactory.php CHANGED
@@ -22,6 +22,8 @@ class SearchSpring_Manager_Factory_GeneratorFactory
22
  const TYPE_CATEGORY = 'category';
23
  /**#@-*/
24
 
 
 
25
  /**
26
  * Create a generator
27
  *
@@ -140,6 +142,7 @@ class SearchSpring_Manager_Factory_GeneratorFactory
140
  $operationsCollection->append($operationsBuilder->build('SetImages'));
141
  $operationsCollection->append($operationsBuilder->build('SetOptions'));
142
  $operationsCollection->append($operationsBuilder->build('SetCategories'));
 
143
 
144
  // add pricing factory and if we should display zero priced products as additional data
145
  $operationsCollection->append($operationsBuilder->build('SetPricing',
@@ -152,6 +155,8 @@ class SearchSpring_Manager_Factory_GeneratorFactory
152
 
153
  $operationsCollection->append($operationsBuilder->build('SetRatings'));
154
 
 
 
155
  // dispatch event allowing additional operations to be added before loop starts
156
  $operationsBuilder->setClassPrefix(null);
157
  Mage::dispatchEvent('searchspring_add_operations', array(
@@ -183,7 +188,8 @@ class SearchSpring_Manager_Factory_GeneratorFactory
183
  $writerParams = new SearchSpring_Manager_Writer_Product_Params_FileWriterParams(
184
  $requestParams,
185
  $collectionProvider->getCollectionCount(),
186
- $filename
 
187
  );
188
  $writer = new SearchSpring_Manager_Writer_Product_FileWriter($xmlWriter, $writerParams);
189
  $generator = new SearchSpring_Manager_Generator_ProductGenerator($collectionProvider, $writer, $transformer);
@@ -219,4 +225,43 @@ class SearchSpring_Manager_Factory_GeneratorFactory
219
 
220
 
221
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
22
  const TYPE_CATEGORY = 'category';
23
  /**#@-*/
24
 
25
+ const XML_PATH_OPERATION_THIRD_PARTY = 'global/searchspring/operation/third_party';
26
+
27
  /**
28
  * Create a generator
29
  *
142
  $operationsCollection->append($operationsBuilder->build('SetImages'));
143
  $operationsCollection->append($operationsBuilder->build('SetOptions'));
144
  $operationsCollection->append($operationsBuilder->build('SetCategories'));
145
+ $operationsCollection->append($operationsBuilder->build('SetReport'));
146
 
147
  // add pricing factory and if we should display zero priced products as additional data
148
  $operationsCollection->append($operationsBuilder->build('SetPricing',
155
 
156
  $operationsCollection->append($operationsBuilder->build('SetRatings'));
157
 
158
+ $this->addThirdPartyOperations($operationsCollection, $operationsBuilder);
159
+
160
  // dispatch event allowing additional operations to be added before loop starts
161
  $operationsBuilder->setClassPrefix(null);
162
  Mage::dispatchEvent('searchspring_add_operations', array(
188
  $writerParams = new SearchSpring_Manager_Writer_Product_Params_FileWriterParams(
189
  $requestParams,
190
  $collectionProvider->getCollectionCount(),
191
+ $filename,
192
+ Mage::helper('searchspring_manager')->getFeedPath()
193
  );
194
  $writer = new SearchSpring_Manager_Writer_Product_FileWriter($xmlWriter, $writerParams);
195
  $generator = new SearchSpring_Manager_Generator_ProductGenerator($collectionProvider, $writer, $transformer);
225
 
226
 
227
  }
228
+
229
+ /**
230
+ * Add external/third party operations, these are specified via
231
+ * core config. These are based on the existence and status of
232
+ * external modules.
233
+ *
234
+ * @param SearchSpring_Manager_Entity_OperationsCollection $operationsCollection
235
+ * @param SearchSpring_Manager_Builder_OperationBuilder $operationsBuilder
236
+ */
237
+ public function addThirdPartyOperations($operationsCollection, $operationsBuilder) {
238
+
239
+ $hlp = Mage::helper('searchspring_manager');
240
+
241
+ if (Mage::getConfig()->getNode(self::XML_PATH_OPERATION_THIRD_PARTY)) {
242
+
243
+ foreach (Mage::getConfig()->getNode(self::XML_PATH_OPERATION_THIRD_PARTY)->children() as $moduleName => $operations) {
244
+
245
+ if (!$operations) continue;
246
+
247
+ // Make Sure Third Party Module is enabled
248
+ if (!$hlp->isModuleEnabled($moduleName)) {
249
+ continue;
250
+ }
251
+
252
+ // Each Third Party Module Mentioned can have multiple operations
253
+ foreach($operations->children() as $operationKey => $operationInfo) {
254
+
255
+ // TODO - Maybe allow an operation to be disabled as a config within $operationInfo
256
+ // TODO - Maybe allow an parameters to be passed to operation via $operationInfo
257
+ $params = array();
258
+
259
+ // Create Operation and Append to Collection
260
+ $operationsCollection->append($operationsBuilder->build((string)$operationInfo->class, $params, false));
261
+
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
  }
app/code/community/SearchSpring/Manager/Generator/ProductGenerator.php CHANGED
@@ -59,9 +59,17 @@ class SearchSpring_Manager_Generator_ProductGenerator
59
  */
60
  public function generate()
61
  {
 
62
  $productCollection = $this->collectionProvider->getCollection();
 
 
 
63
  $recordCollection = $this->transformer->transform($productCollection);
 
 
 
64
  $resultMessage = $this->writer->write($recordCollection);
 
65
 
66
  return $resultMessage;
67
  }
59
  */
60
  public function generate()
61
  {
62
+ Varien_Profiler::start(__METHOD__.": getCollection()");
63
  $productCollection = $this->collectionProvider->getCollection();
64
+ Varien_Profiler::stop(__METHOD__.": getCollection()");
65
+
66
+ Varien_Profiler::start(__METHOD__.": transform()");
67
  $recordCollection = $this->transformer->transform($productCollection);
68
+ Varien_Profiler::stop(__METHOD__.": transform()");
69
+
70
+ Varien_Profiler::start(__METHOD__.": write()");
71
  $resultMessage = $this->writer->write($recordCollection);
72
+ Varien_Profiler::stop(__METHOD__.": write()");
73
 
74
  return $resultMessage;
75
  }
app/code/community/SearchSpring/Manager/Handler/ApiErrorHandler.php CHANGED
@@ -61,9 +61,11 @@ class SearchSpring_Manager_Handler_ApiErrorHandler
61
  */
62
  private function notifyUser()
63
  {
64
- /** @var Mage_Core_Model_Session $session */
65
- $session = Mage::getSingleton('core/session');
66
- $session->addNotice('There was a problem while trying to update SearchSpring. This issue will be investigated.');
 
 
67
  }
68
 
69
  /**
61
  */
62
  private function notifyUser()
63
  {
64
+ if(Mage::app()->getStore()->isAdmin()) {
65
+ /** @var Mage_Core_Model_Session $session */
66
+ $session = Mage::getSingleton('core/session');
67
+ $session->addNotice('There was a problem while trying to update SearchSpring. This issue will be investigated.');
68
+ }
69
  }
70
 
71
  /**
app/code/community/SearchSpring/Manager/Helper/Data.php CHANGED
@@ -16,15 +16,23 @@ 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_GLOBAL_CATEGORY_ENABLE_FL = 'ssmanager/ssmanager_catalog/enable_categories';
20
 
21
- const XML_PATH_INDEX_ZERO_PRICE = 'ssmanager/ssmanager_general/index_zero_price';
 
22
 
23
  const XML_PATH_API_FEED_ID = 'ssmanager/ssmanager_api/feed_id';
24
  const XML_PATH_API_SITE_ID = 'ssmanager/ssmanager_api/site_id';
25
- const XML_PATH_API_BASE_URL = 'ssmanager/ssmanager_api/base_url';
26
  const XML_PATH_API_SECRET_KEY = 'ssmanager/ssmanager_api/secret_key';
27
 
 
 
 
 
28
  const XML_PATH_GENERATE_CACHE_IMAGES = 'ssmanager/ssmanager_images/generate_cache_images';
29
  const XML_PATH_IMAGE_WIDTH = 'ssmanager/ssmanager_images/image_width';
30
  const XML_PATH_IMAGE_HEIGHT = 'ssmanager/ssmanager_images/image_height';
@@ -33,6 +41,17 @@ class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
33
  const XML_PATH_SWATCH_WIDTH = 'ssmanager/ssmanager_images/swatch_width';
34
  const XML_PATH_SWATCH_HEIGHT = 'ssmanager/ssmanager_images/swatch_height';
35
 
 
 
 
 
 
 
 
 
 
 
 
36
  public function isLiveIndexingEnabled()
37
  {
38
  return Mage::getStoreConfigFlag(self::XML_PATH_GLOBAL_LIVE_INDEXING_ENABLE_FL);
@@ -48,6 +67,16 @@ class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
48
  return Mage::getStoreConfigFlag(self::XML_PATH_INDEX_ZERO_PRICE);
49
  }
50
 
 
 
 
 
 
 
 
 
 
 
51
  public function getApiFeedId()
52
  {
53
  return Mage::getStoreConfig(self::XML_PATH_API_FEED_ID);
@@ -60,7 +89,11 @@ class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
60
 
61
  public function getApiBaseUrl()
62
  {
63
- return Mage::getStoreConfig(self::XML_PATH_API_BASE_URL);
 
 
 
 
64
  }
65
 
66
  public function getApiSecretKey()
@@ -68,6 +101,10 @@ class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
68
  return Mage::getStoreConfig(self::XML_PATH_API_SECRET_KEY);
69
  }
70
 
 
 
 
 
71
  public function isCacheImagesEnabled()
72
  {
73
  return Mage::getStoreConfig(self::XML_PATH_GENERATE_CACHE_IMAGES);
@@ -98,6 +135,76 @@ class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
98
  return Mage::getStoreConfig(self::XML_PATH_SWATCH_WIDTH);
99
  }
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  /**
102
  * Intended for layout action parameter helper calls
103
  *
@@ -113,5 +220,26 @@ class SearchSpring_Manager_Helper_Data extends Mage_Core_Helper_Abstract
113
  return Mage::app()->getLayout()->getBlock($block)->getTemplate();
114
  }
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  }
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
+ const XML_PATH_FEED_PATH = 'ssmanager/ssmanager_general/feed_path';
22
+
23
  const XML_PATH_GLOBAL_CATEGORY_ENABLE_FL = 'ssmanager/ssmanager_catalog/enable_categories';
24
 
25
+ const XML_PATH_API_BASE_URL = 'ssmanager/ssmanager_api/base_url';
26
+ const ENV_VAR_API_BASE_URL = 'SEARCHSPRING_API_HOST';
27
 
28
  const XML_PATH_API_FEED_ID = 'ssmanager/ssmanager_api/feed_id';
29
  const XML_PATH_API_SITE_ID = 'ssmanager/ssmanager_api/site_id';
 
30
  const XML_PATH_API_SECRET_KEY = 'ssmanager/ssmanager_api/secret_key';
31
 
32
+ const XML_PATH_API_AUTHENTICATION_METHOD = 'ssmanager/ssmanager_api/authentication_method';
33
+ const AUTH_METHOD_SIMPLE = 'simple';
34
+ const AUTH_METHOD_OAUTH = 'oauth';
35
+
36
  const XML_PATH_GENERATE_CACHE_IMAGES = 'ssmanager/ssmanager_images/generate_cache_images';
37
  const XML_PATH_IMAGE_WIDTH = 'ssmanager/ssmanager_images/image_width';
38
  const XML_PATH_IMAGE_HEIGHT = 'ssmanager/ssmanager_images/image_height';
41
  const XML_PATH_SWATCH_WIDTH = 'ssmanager/ssmanager_images/swatch_width';
42
  const XML_PATH_SWATCH_HEIGHT = 'ssmanager/ssmanager_images/swatch_height';
43
 
44
+ const XML_PATH_UUID = 'ssmanager/ssmanager_track/uuid';
45
+
46
+ const MANAGER_API_PATH_PRODUCT_SIMPLE = 'searchspring/generate/index';
47
+ const MANAGER_API_PATH_GENERATE_SIMPLE = 'searchspring/generate/feed';
48
+ const MANAGER_API_PATH_PRODUCT_OAUTH = 'api/rest/searchspring/index';
49
+ const MANAGER_API_PATH_GENERATE_OAUTH = 'api/rest/searchspring/feed';
50
+
51
+ public function getVersion() {
52
+ return Mage::getConfig()->getNode('modules/SearchSpring_Manager/version');
53
+ }
54
+
55
  public function isLiveIndexingEnabled()
56
  {
57
  return Mage::getStoreConfigFlag(self::XML_PATH_GLOBAL_LIVE_INDEXING_ENABLE_FL);
67
  return Mage::getStoreConfigFlag(self::XML_PATH_INDEX_ZERO_PRICE);
68
  }
69
 
70
+ public function isOutOfStockIndexingEnabled()
71
+ {
72
+ return Mage::getStoreConfigFlag(self::XML_PATH_INDEX_OUT_OF_STOCK);
73
+ }
74
+
75
+ public function getFeedPath()
76
+ {
77
+ return Mage::getStoreConfig(self::XML_PATH_FEED_PATH);
78
+ }
79
+
80
  public function getApiFeedId()
81
  {
82
  return Mage::getStoreConfig(self::XML_PATH_API_FEED_ID);
89
 
90
  public function getApiBaseUrl()
91
  {
92
+ if($env = getenv(self::ENV_VAR_API_BASE_URL)) {
93
+ return $env;
94
+ } else {
95
+ return Mage::getStoreConfig(self::XML_PATH_API_BASE_URL);
96
+ }
97
  }
98
 
99
  public function getApiSecretKey()
101
  return Mage::getStoreConfig(self::XML_PATH_API_SECRET_KEY);
102
  }
103
 
104
+ public function getAuthenticationMethod() {
105
+ return Mage::getStoreConfig(self::XML_PATH_API_AUTHENTICATION_METHOD);
106
+ }
107
+
108
  public function isCacheImagesEnabled()
109
  {
110
  return Mage::getStoreConfig(self::XML_PATH_GENERATE_CACHE_IMAGES);
135
  return Mage::getStoreConfig(self::XML_PATH_SWATCH_WIDTH);
136
  }
137
 
138
+ public function getUUID() {
139
+ return Mage::getStoreConfig(self::XML_PATH_UUID);
140
+ }
141
+
142
+ public function getMageAPIPathGenerate() {
143
+ switch ($this->getAuthenticationMethod()) {
144
+ case self::AUTH_METHOD_SIMPLE:
145
+ return self::MANAGER_API_PATH_GENERATE_SIMPLE;
146
+ case self::AUTH_METHOD_OAUTH:
147
+ return self::MANAGER_API_PATH_GENERATE_OAUTH;
148
+ }
149
+ return false;
150
+ }
151
+
152
+ public function getMageAPIPathProduct() {
153
+ switch ($this->getAuthenticationMethod()) {
154
+ case self::AUTH_METHOD_SIMPLE:
155
+ return self::MANAGER_API_PATH_PRODUCT_SIMPLE;
156
+ case self::AUTH_METHOD_OAUTH:
157
+ return self::MANAGER_API_PATH_PRODUCT_OAUTH;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ public function getMageAPIUrlGenerate() {
163
+ return Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, true) . $this->getMageAPIPathGenerate();
164
+ }
165
+
166
+ public function getMageAPIUrlProduct() {
167
+ return Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, true) . $this->getMageAPIPathProduct();
168
+ }
169
+
170
+ public function writeStoreConfig($path, $value, $scope = 'default', $scopeId = 0) {
171
+ Mage::getConfig()->saveConfig($path, $value, $scope, $scopeId)->reinit();
172
+ }
173
+
174
+ public function registerMagentoAPIAuthenticationWithSearchSpring($verify = false) {
175
+
176
+ $whlp = Mage::helper('searchspring_manager/webservice');
177
+
178
+ // TODO -- should we cache responses from verify??
179
+
180
+ try {
181
+
182
+ switch ($this->getAuthenticationMethod()) {
183
+ case self::AUTH_METHOD_SIMPLE:
184
+ if ($verify)
185
+ $response = $whlp->verifyMageAPIAuthSimple();
186
+ else
187
+ $response = $whlp->registerMageAPIAuthSimple();
188
+ break;
189
+ case self::AUTH_METHOD_OAUTH:
190
+ if ($verify)
191
+ $response = $whlp->verifyMageAPIAuthOAuth();
192
+ else
193
+ $response = $whlp->registerMageAPIAuthOAuth();
194
+ break;
195
+ default:
196
+ // TODO - Should we be returning false, this means the setting hasn't been initialized?
197
+ return false;
198
+ }
199
+
200
+ } catch (Exception $e) {
201
+ Mage::log(__METHOD__.": Problem while attempting to access the SearchSpring service: " . $e->getMessage());
202
+ return false;
203
+ }
204
+
205
+ return $response;
206
+ }
207
+
208
  /**
209
  * Intended for layout action parameter helper calls
210
  *
220
  return Mage::app()->getLayout()->getBlock($block)->getTemplate();
221
  }
222
 
223
+ /**
224
+ * Check if module exists and is enabled in global config.
225
+ *
226
+ * NOTE: This function most likely exists in the parent, but
227
+ * may not depending on the version of magento installed.
228
+ *
229
+ * @param string $moduleName the full module name, example Mage_Core
230
+ * @return boolean
231
+ */
232
+ public function isModuleEnabled($moduleName = null)
233
+ {
234
+ if (!Mage::getConfig()->getNode('modules/' . $moduleName)) {
235
+ return false;
236
+ }
237
+
238
+ $isActive = Mage::getConfig()->getNode('modules/' . $moduleName . '/active');
239
+ if (!$isActive || !in_array((string)$isActive, array('true', '1'))) {
240
+ return false;
241
+ }
242
+ return true;
243
+ }
244
 
245
  }
app/code/community/SearchSpring/Manager/Helper/Http.php ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File Data.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Helper_Http
10
+ *
11
+ * Helper for all things HTTP
12
+ *
13
+ * @author Jake Shelby <jake@searchspring.com>
14
+ */
15
+ class SearchSpring_Manager_Helper_Http extends Mage_Core_Helper_Http
16
+ {
17
+
18
+ /**
19
+ * Validate Simple Authentication, just return wether or
20
+ * not the basic auth credentials were provided
21
+ *
22
+ * @return mixed bool|array
23
+ */
24
+ public function isSimpleAuthProvided($itemized = false) {
25
+
26
+ list($username, $password) = $this->getBasicAuthCredentials();
27
+
28
+ if ($itemized) {
29
+ return array(!empty($username), !empty($password));
30
+ } else {
31
+ return !(empty($username) || empty($password));
32
+ }
33
+
34
+ }
35
+
36
+ /**
37
+ * Validate Simple Authentication, just return wether or
38
+ * not the basic auth credentials match for Manager's settings
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
+ /**
59
+ * Get Basic Auth Credentials
60
+ *
61
+ * @param array $headers
62
+ * @return array
63
+ */
64
+ public function getBasicAuthCredentials($headers = null)
65
+ {
66
+ $creds = $this->authValidate($headers);
67
+ return $creds;
68
+ }
69
+
70
+ public function authFailed()
71
+ {
72
+ // Overriding this to not do anything
73
+ // Original function sends 'require auth' headers and exits
74
+ }
75
+
76
+ }
app/code/community/SearchSpring/Manager/Helper/Oauth.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File Oauth.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Helper_Oauth
10
+ *
11
+ * @author Jake Shelby <jake@b7interactive.com>
12
+ */
13
+ class SearchSpring_Manager_Helper_Oauth extends Mage_Core_Helper_Abstract
14
+ {
15
+
16
+ const XML_PATH_REGISTERED_USER = 'ssmanager/ssmanager_api/magento_api_admin_user';
17
+ const XML_PATH_REGISTERED_USER_ROLE = 'ssmanager/ssmanager_api/magento_api_admin_user_role';
18
+ const XML_PATH_REGISTERED_CONSUMER = 'ssmanager/ssmanager_api/magento_api_oauth_consumer';
19
+
20
+ protected $_adminUser;
21
+ protected $_adminUserRole;
22
+ protected $_consumer;
23
+
24
+ public function hlp() {
25
+ return Mage::helper('searchspring_manager');
26
+ }
27
+
28
+ public function getAdminUser() {
29
+ if (is_null($this->_adminUser)) {
30
+ $model = Mage::getModel('admin/user')->load($this->getAdminUserId());
31
+ if (!$model->getId()) $model = false;
32
+ $this->_adminUser = $model;
33
+ }
34
+
35
+ return $this->_adminUser;
36
+ }
37
+
38
+ public function getAdminUserRole() {
39
+ if (is_null($this->_adminUserRole)) {
40
+ $model = Mage::getModel('api2/acl_global_role')->load($this->getAdminUserRoleId());
41
+ if (!$model->getId()) $model = false;
42
+ $this->_adminUserRole = $model;
43
+ }
44
+
45
+ return $this->_adminUserRole;
46
+ }
47
+
48
+ public function getConsumer() {
49
+ if (is_null($this->_consumer)) {
50
+ $model = Mage::getModel('oauth/consumer')->load($this->getConsumerId());
51
+ if (!$model->getId()) $model = false;
52
+ $this->_consumer = $model;
53
+ }
54
+
55
+ return $this->_consumer;
56
+ }
57
+
58
+ public function ensureOAuthResourcesInitialized() {
59
+
60
+ return $this->_initializeOAuthResources();
61
+
62
+ }
63
+
64
+ public function getAdminUserId() {
65
+ return Mage::getStoreConfig(self::XML_PATH_REGISTERED_USER);
66
+ }
67
+
68
+ public function getAdminUserRoleId() {
69
+ return Mage::getStoreConfig(self::XML_PATH_REGISTERED_USER_ROLE);
70
+ }
71
+
72
+ public function getConsumerId() {
73
+ return Mage::getStoreConfig(self::XML_PATH_REGISTERED_CONSUMER);
74
+ }
75
+
76
+ protected function _setAdminUser($model) {
77
+ $this->hlp()->writeStoreConfig(self::XML_PATH_REGISTERED_USER, $model->getId());
78
+ return $this->_adminUser = $model;
79
+ }
80
+
81
+ protected function _setAdminUserRole($model) {
82
+ $this->hlp()->writeStoreConfig(self::XML_PATH_REGISTERED_USER_ROLE, $model->getId());
83
+ return $this->_adminUserRole = $model;
84
+ }
85
+
86
+ protected function _setConsumer($model) {
87
+ $this->hlp()->writeStoreConfig(self::XML_PATH_REGISTERED_CONSUMER, $model->getId());
88
+ return $this->_consumer = $model;
89
+ }
90
+
91
+ protected function _initializeOAuthResources() {
92
+
93
+ // Ensure Admin User exists
94
+ $user = $this->_ensureAdminUser();
95
+
96
+ // Ensure REST role exists
97
+ $this->_ensureRole($user->getId());
98
+
99
+ // Ensure oAuth Consumer exists
100
+ $this->_ensureConsumer();
101
+
102
+ }
103
+
104
+ protected function _ensureAdminUser() {
105
+ if ($user = $this->getAdminUser()) return $user;
106
+ $user = $this->_createAdminUser();
107
+ return $this->_setAdminUser($user);
108
+ }
109
+
110
+ protected function _ensureRole($userId) {
111
+ if ($role = $this->getAdminUserRole()) return $role;
112
+ $role = $this->_createRole($userId);
113
+ return $this->_setAdminUserRole($role);
114
+ }
115
+
116
+ protected function _ensureConsumer() {
117
+ if ($consumer = $this->getConsumer()) return $consumer;
118
+ $consumer = $this->_createConsumer();
119
+ return $this->_setConsumer($consumer);
120
+ }
121
+
122
+ protected function _createAdminUser() {
123
+ $ohlp = Mage::helper('oauth');
124
+ $email = Mage::getConfig()->getNode('global/searchspring/api_auth/admin_user/email');
125
+
126
+ $data = array(
127
+ 'username' => Mage::getConfig()->getNode('global/searchspring/api_auth/admin_user/username'),
128
+ 'firstname' => Mage::getConfig()->getNode('global/searchspring/api_auth/admin_user/firstname'),
129
+ 'lastname' => Mage::getConfig()->getNode('global/searchspring/api_auth/admin_user/lastname'),
130
+ 'email' => $email,
131
+ 'password' => $ohlp->generateConsumerKey(), // Something random for security
132
+ 'is_active' => '1',
133
+ );
134
+
135
+ // First Check for user with email, remove it
136
+ $this->_removeAdminUserWithEmail($email);
137
+
138
+ // Create a brand new user with data
139
+ $user = Mage::getModel('admin/user');
140
+ $user->setData($data);
141
+
142
+ return $user->save();
143
+ }
144
+
145
+ protected function _createRole($userId) {
146
+ $name = Mage::getConfig()->getNode('global/searchspring/api_auth/role/label');
147
+
148
+ $role = Mage::getModel('api2/acl_global_role');
149
+ $role->setRoleName($name);
150
+ $role->save();
151
+
152
+ $this->_addUserToRole($userId, $role->getId());
153
+
154
+ // TODO -- pull resource id and privilege keys from api2 config...
155
+ $resourceId = 'searchspring';
156
+ $privileges = array(
157
+ 'create',
158
+ 'retrieve',
159
+ 'update',
160
+ 'delete',
161
+ );
162
+ $rule = Mage::getModel('api2/acl_global_rule');
163
+ foreach($privileges as $privilege) {
164
+ $rule->setId(null)->isObjectNew(true);
165
+ $rule->setRoleId($role->getId())
166
+ ->setResourceId($resourceId)
167
+ ->setPrivilege($privilege)
168
+ ->save();
169
+ }
170
+
171
+ return $role;
172
+ }
173
+
174
+ protected function _addUserToRole($userId, $roleId) {
175
+ $resourceModel = Mage::getResourceModel('api2/acl_global_role');
176
+ $resourceModel->saveAdminToRoleRelation($userId, $roleId);
177
+ }
178
+
179
+ protected function _createConsumer() {
180
+ $ohlp = Mage::helper('oauth');
181
+
182
+ $consumerName = Mage::getConfig()->getNode('global/searchspring/api_auth/consumer/label');
183
+ $consumerKey = $ohlp->generateConsumerKey();
184
+ $consumerSecret = $ohlp->generateConsumerSecret();
185
+
186
+ $consumer = Mage::getModel('oauth/consumer');
187
+ $consumer->setName($consumerName);
188
+ $consumer->setKey($consumerKey);
189
+ $consumer->setSecret($consumerSecret);
190
+
191
+ return $consumer->save();
192
+ }
193
+
194
+ protected function _removeAdminUserWithEmail($email) {
195
+ $collection = Mage::getModel('admin/user')->getCollection();
196
+ $collection->addFieldToFilter('email', $email);
197
+
198
+ // If admin user exists, delete it
199
+ $user = $collection->getFirstItem();
200
+ if ($user->getId()) {
201
+ $user->delete();
202
+ }
203
+ }
204
+
205
+ }
app/code/community/SearchSpring/Manager/Helper/Product.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File Product.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Helper_Product
10
+ *
11
+ * Some helpful utilities related to products
12
+ *
13
+ * @author Jake Shelby <jake@searchspring.com>
14
+ */
15
+ class SearchSpring_Manager_Helper_Product extends Mage_Core_Helper_Abstract
16
+ {
17
+
18
+ public function getAttributeText($product, $attributeCode) {
19
+
20
+ $attribute = $product->getResource()->getAttribute($attributeCode);
21
+
22
+ // If the attribute type uses a set number of options, we need to resolve the id
23
+ if ($attribute->getFrontendInput() === 'select' ||
24
+ $attribute->getFrontendInput() === 'multiselect'
25
+ ) {
26
+ return $product->getAttributeText($attributeCode);
27
+ } else {
28
+ return $product->getData($attributeCode);
29
+ }
30
+
31
+ }
32
+
33
+ }
app/code/community/SearchSpring/Manager/Helper/Webservice.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File Webservice.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Helper_Webservice
10
+ *
11
+ * @author Jake Shelby <jake@searchspring.com>
12
+ */
13
+ class SearchSpring_Manager_Helper_Webservice extends Mage_Core_Helper_Abstract
14
+ {
15
+
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
+
63
+ $client = new Zend_Http_Client($url, array(
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
+
73
+ $response = $client->request();
74
+
75
+ return $response;
76
+ }
77
+
78
+ public function isResponseSuccess($response) {
79
+ if (!$response) {
80
+ return false;
81
+ }
82
+ return $response->isSuccessful();
83
+ }
84
+
85
+ public function isResponseStatusSuccess($response) {
86
+ if (!$this->isResponseSuccess($response)) return false;
87
+ $responseData = Zend_Json::decode($response->getBody(), Zend_Json::TYPE_OBJECT);
88
+ if (!is_object($responseData)) return false;
89
+ if ($responseData->status !== 'success') return false;
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())) {
102
+ // Can't do much without these
103
+ return array();
104
+ }
105
+ $cKey = $consumer->getKey();
106
+ $cSecret = $consumer->getSecret();
107
+
108
+ return array(
109
+ 'consumerKey' => $cKey,
110
+ 'consumerSecret' => $cSecret,
111
+ 'type' => 'magento_indexing',
112
+ );
113
+ }
114
+
115
+ }
app/code/community/SearchSpring/Manager/Model/Api2/Auth/Adapter.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * 0-Legged oAuth Authentication adapter, oAuth direct consumer - provider, minus the user
4
+ *
5
+ */
6
+ class SearchSpring_Manager_Model_Api2_Auth_Adapter extends Mage_Api2_Model_Auth_Adapter_Abstract
7
+ {
8
+ /**
9
+ * Process request and figure out an API user type and its identifier
10
+ *
11
+ * Returns stdClass object with two properties: type and id
12
+ *
13
+ * @param Mage_Api2_Model_Request $request
14
+ * @return stdClass
15
+ */
16
+ public function getUserParams(Mage_Api2_Model_Request $request)
17
+ {
18
+ // Get our oauth server, with 0-Legged Support
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
+
30
+ // Authenticate Consumer
31
+ $consumer = $oauthServer->checkDirectConsumerRequest();
32
+
33
+ // Validate Consumer Identity, with the one configured to use this API
34
+ if ($consumer->getId() != $oahlp->getConsumerId()) {
35
+ throw new Mage_Api2_Exception('Not authorized', Mage_Api2_Model_Server::HTTP_UNAUTHORIZED);
36
+ }
37
+
38
+ // Return user configured to use this API
39
+ $adminId = $oahlp->getAdminUserId();
40
+ $userParamsObj = (object) array('type' => Mage_Oauth_Model_Token::USER_TYPE_ADMIN, 'id' => $adminId);
41
+
42
+ } catch (Exception $e) {
43
+ throw new Mage_Api2_Exception($oauthServer->reportProblem($e), Mage_Api2_Model_Server::HTTP_UNAUTHORIZED);
44
+ }
45
+
46
+ return $userParamsObj;
47
+ }
48
+
49
+ /**
50
+ * Check if request contains authentication info for adapter
51
+ *
52
+ * @param Mage_Api2_Model_Request $request
53
+ * @return boolean
54
+ */
55
+ public function isApplicableToRequest(Mage_Api2_Model_Request $request)
56
+ {
57
+ // Check if request is for searchspring apis, and the request is for oauth
58
+ $uri = $request->getRequestUri();
59
+ if (preg_match('!/api/rest/searchspring/.*!i', $uri)) {
60
+ return true;
61
+ }
62
+
63
+ return false;
64
+ }
65
+
66
+ }
app/code/community/SearchSpring/Manager/Model/Api2/Indexing/Rest/Admin/V1.php ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class SearchSpring_Manager_Model_Api2_Indexing_Rest_Admin_V1 extends Mage_Api2_Model_Resource
3
+ {
4
+ /**
5
+ * Default starting point if no parameter is set
6
+ */
7
+ const OFFSET_DEFAULT = 0;
8
+
9
+ /**
10
+ * Default product limit if no parameter is set
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
+ {
23
+ $this->fetchById();
24
+ }
25
+
26
+ // Data Feed Generation
27
+ public function _retrieve()
28
+ {
29
+ $this->generateFeed();
30
+ }
31
+
32
+ public function _update()
33
+ {
34
+ throw new Exception('Not Implemented');
35
+ }
36
+
37
+ public function _delete()
38
+ {
39
+ throw new Exception('Not Implemented');
40
+ }
41
+
42
+ /**
43
+ * Default action for updating based on product/category id
44
+ *
45
+ * Parameters:
46
+ * type (required): The type of id passed in. Can be 'product' or 'category'
47
+ * ids (required): An array of ids
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)) {
57
+ $this->setJsonResponse(
58
+ array(
59
+ 'status' => 'error',
60
+ 'errorCode' => SearchSpring_ErrorCodes::BAD_REQUEST,
61
+ 'message' => 'Invalid request format'
62
+ ),
63
+ 400
64
+ );
65
+
66
+ return;
67
+ }
68
+
69
+ $type = $request['type'];
70
+ $ids = $request['ids'];
71
+
72
+ if (null === $type) {
73
+ $this->setJsonResponse(
74
+ array(
75
+ 'status' => 'error',
76
+ 'errorCode' => SearchSpring_ErrorCodes::TYPE_NOT_SET,
77
+ 'message' => 'Type must be specified'
78
+ ),
79
+ 400
80
+ );
81
+
82
+ return;
83
+ }
84
+
85
+ if (null === $ids) {
86
+ $this->setJsonResponse(
87
+ array(
88
+ 'status' => 'error',
89
+ 'errorCode' => SearchSpring_ErrorCodes::IDS_NOT_SET,
90
+ 'message' => 'Ids must be specified'
91
+ ),
92
+ 400
93
+ );
94
+
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);
110
+
111
+ $generatorFactory = new SearchSpring_Manager_Factory_GeneratorFactory();
112
+ $generator = $generatorFactory->make($type, $requestParams, $params);
113
+ $message = $generator->generate();
114
+
115
+ $this->setJsonResponse($message);
116
+ }
117
+
118
+ function generateFeed() {
119
+ // check file is writable first
120
+ if (!is_writable(Mage::getBaseDir())) {
121
+ $this->setJsonResponse(
122
+ array(
123
+ 'status' => 'error',
124
+ 'errorCode' => SearchSpring_ErrorCodes::DIR_NOT_WRITABLE,
125
+ 'message' => 'Magento base directory is not writable'
126
+ ),
127
+ 500
128
+ );
129
+
130
+ return;
131
+ }
132
+
133
+ $uniqueFilename = $this->getRequest()->getParam('filename');
134
+
135
+ if (null === $uniqueFilename) {
136
+ $this->setJsonResponse(
137
+ array(
138
+ 'status' => 'error',
139
+ 'errorCode' => SearchSpring_ErrorCodes::FILENAME_NOT_SET,
140
+ 'message' => 'Unique filename must be passed in'
141
+ ),
142
+ 400
143
+ );
144
+
145
+ return;
146
+ }
147
+
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);
155
+
156
+ $generatorFactory = new SearchSpring_Manager_Factory_GeneratorFactory();
157
+ $generator = $generatorFactory->make(
158
+ SearchSpring_Manager_Factory_GeneratorFactory::TYPE_FEED,
159
+ $requestParams,
160
+ $params
161
+ );
162
+
163
+ $message = $generator->generate();
164
+
165
+ $this->setTextResponse($message);
166
+
167
+ return;
168
+ }
169
+
170
+ /**
171
+ * Set appropriate response variables for a json response
172
+ *
173
+ * @param array $message The message that should be sent back
174
+ * @param int $responseCode The Http response code
175
+ */
176
+ private function setJsonResponse(array $message, $responseCode = 200)
177
+ {
178
+ header('Content-type: application/json', true, $responseCode);
179
+ echo json_encode($message);
180
+ exit();
181
+ }
182
+
183
+ /**
184
+ * Set a text based response
185
+ *
186
+ * @param string $message
187
+ * @param int $responseCode
188
+ */
189
+ private function setTextResponse($message, $responseCode = 200)
190
+ {
191
+ header('Content-type: text/plain', true, $responseCode);
192
+ echo $message;
193
+ exit();
194
+
195
+ }
196
+ }
197
+ ?>
app/code/community/SearchSpring/Manager/Model/Oauth/Server.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * oAuth Server With 0-Legged Support
5
+ *
6
+ */
7
+ class SearchSpring_Manager_Model_Oauth_Server extends Mage_Oauth_Model_Server
8
+ {
9
+
10
+ /**
11
+ * Validate request with consumer token, and empty access token
12
+ */
13
+ public function checkDirectConsumerRequest()
14
+ {
15
+ // get parameters from request
16
+ $this->_fetchParams();
17
+
18
+ // make generic validation of request parameters
19
+ $this->_validateProtocolParams();
20
+
21
+ // initialize consumer
22
+ $this->_initConsumer();
23
+
24
+ // generate empty token (to validate against)
25
+ $this->_token = new Varien_Object(array('secret' => ''));
26
+
27
+ // validate signature
28
+ $this->_validateSignature();
29
+
30
+ return $this->_consumer;
31
+ }
32
+
33
+ }
app/code/community/SearchSpring/Manager/Model/Observer/CategorySaveObserver.php CHANGED
@@ -21,13 +21,6 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
21
  */
22
  private $varienObjectData;
23
 
24
- /**
25
- * Api adapter for connecting to SearchSpring api
26
- *
27
- * @var SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter $api
28
- */
29
- private $api;
30
-
31
  /**
32
  * Create a request body
33
  *
@@ -40,8 +33,6 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
40
  */
41
  public function __construct()
42
  {
43
- $apiFactory = new SearchSpring_Manager_Factory_ApiFactory();
44
- $this->api = $apiFactory->make('index');
45
  $this->requestBodyFactory = new SearchSpring_Manager_Factory_IndexingRequestBodyFactory();
46
  $this->varienObjectData = new SearchSpring_Manager_VarienObject_Data();
47
  }
@@ -82,7 +73,7 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
82
  SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY,
83
  $category->getAllChildren(true)
84
  );
85
- $this->api->pushIds($requestBody);
86
 
87
  return true;
88
  }
@@ -117,7 +108,7 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
117
  $productIds
118
  );
119
 
120
- $this->api->pushIds($requestBody);
121
 
122
  return true;
123
  }
@@ -149,7 +140,7 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
149
  SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY,
150
  $category->getAllChildren(true)
151
  );
152
- $this->api->pushIds($requestBody);
153
 
154
  return true;
155
  }
@@ -174,7 +165,7 @@ class SearchSpring_Manager_Model_Observer_CategorySaveObserver extends SearchSpr
174
  SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT,
175
  $this->getCategoryProductIds($category)
176
  );
177
- $this->api->pushIds($requestBody);
178
 
179
  return true;
180
  }
21
  */
22
  private $varienObjectData;
23
 
 
 
 
 
 
 
 
24
  /**
25
  * Create a request body
26
  *
33
  */
34
  public function __construct()
35
  {
 
 
36
  $this->requestBodyFactory = new SearchSpring_Manager_Factory_IndexingRequestBodyFactory();
37
  $this->varienObjectData = new SearchSpring_Manager_VarienObject_Data();
38
  }
73
  SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY,
74
  $category->getAllChildren(true)
75
  );
76
+ $this->apiPushProductIds($requestBody);
77
 
78
  return true;
79
  }
108
  $productIds
109
  );
110
 
111
+ $this->apiPushProductIds($requestBody);
112
 
113
  return true;
114
  }
140
  SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_CATEGORY,
141
  $category->getAllChildren(true)
142
  );
143
+ $this->apiPushProductIds($requestBody);
144
 
145
  return true;
146
  }
165
  SearchSpring_Manager_Entity_IndexingRequestBody::TYPE_PRODUCT,
166
  $this->getCategoryProductIds($category)
167
  );
168
+ $this->apiPushProductIds($requestBody);
169
 
170
  return true;
171
  }
app/code/community/SearchSpring/Manager/Model/Observer/ConfigObserver.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * ConfigObserver.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Model_Observer_ConfigObserver
10
+ *
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');
21
+
22
+ // Make sure we are in the admin panel
23
+ if (!Mage::app()->getStore()->isAdmin()) {
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
+
49
+ }
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() {
77
+ $oahlp = Mage::helper('searchspring_manager/oauth');
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
+
95
+ }
app/code/community/SearchSpring/Manager/Model/Observer/LiveIndexer.php CHANGED
@@ -13,6 +13,22 @@
13
  abstract class SearchSpring_Manager_Model_Observer_LiveIndexer
14
  {
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  /**
17
  * Checks if live indexing is enabled
18
  *
@@ -23,4 +39,26 @@ abstract class SearchSpring_Manager_Model_Observer_LiveIndexer
23
  return Mage::helper('searchspring_manager')->isLiveIndexingEnabled();
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
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
  *
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
@@ -14,12 +14,6 @@
14
  */
15
  class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpring_Manager_Model_Observer_LiveIndexer
16
  {
17
- /**
18
- * Create an api adapter for SearchSpring
19
- *
20
- * @var SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter $api
21
- */
22
- private $api;
23
 
24
  /**
25
  * Creates a request body
@@ -41,8 +35,6 @@ class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpr
41
  */
42
  public function __construct()
43
  {
44
- $apiFactory = new SearchSpring_Manager_Factory_ApiFactory();
45
- $this->api = $apiFactory->make('index');
46
  $this->requestBodyFactory = new SearchSpring_Manager_Factory_IndexingRequestBodyFactory();
47
  }
48
 
@@ -74,6 +66,11 @@ class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpr
74
  * @return bool
75
  */
76
  public function beforeDeletePushProduct(Varien_Event_Observer $productEvent) {
 
 
 
 
 
77
  $product = $productEvent->getData('product');
78
  $this->_productIds = $this->_getProductIds($product);
79
  return true;
@@ -117,7 +114,7 @@ class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpr
117
  }
118
 
119
  // send ids to api
120
- $this->api->pushIds($requestBody);
121
 
122
  return true;
123
  }
14
  */
15
  class SearchSpring_Manager_Model_Observer_ProductSaveObserver extends SearchSpring_Manager_Model_Observer_LiveIndexer
16
  {
 
 
 
 
 
 
17
 
18
  /**
19
  * Creates a request body
35
  */
36
  public function __construct()
37
  {
 
 
38
  $this->requestBodyFactory = new SearchSpring_Manager_Factory_IndexingRequestBodyFactory();
39
  }
40
 
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;
114
  }
115
 
116
  // send ids to api
117
+ $this->apiPushProductIds($requestBody);
118
 
119
  return true;
120
  }
app/code/community/SearchSpring/Manager/Operation/Product.php CHANGED
@@ -118,4 +118,19 @@ abstract class SearchSpring_Manager_Operation_Product implements SearchSpring_Ma
118
 
119
  return $this->parameters[$key];
120
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  }
118
 
119
  return $this->parameters[$key];
120
  }
121
+
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
+
136
  }
app/code/community/SearchSpring/Manager/Operation/Product/SetCategories.php CHANGED
@@ -18,16 +18,16 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
18
  * Feed Constants
19
  */
20
  const FEED_CATEGORY_HIERARCHY = 'category_hierarchy';
21
- const FEED_CATEGORY_NAME = 'category_name';
22
  const FEED_CATEGORY_IDS = 'category_ids';
23
  /**#@-*/
24
 
25
  /**
26
- * Category names we should skip
27
  *
28
- * @var array
29
  */
30
- private $skipCategories = array('Root Catalog', 'Default Category', null);
31
 
32
  /**
33
  * Sets category data to feed
@@ -48,14 +48,14 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
48
  /** @var Mage_Catalog_Model_Category $category */
49
  foreach($product->getCategoryIds() as $categoryId) {
50
  // load category data
51
- $category = Mage::getModel('catalog/category')->load($categoryId);
52
 
53
  if (!$category->getData('is_active')) {
54
  continue;
55
  }
56
 
57
- $categoryName = $category->getData('name');
58
- if (in_array($categoryName, $this->skipCategories)) {
59
  continue;
60
  }
61
 
@@ -69,7 +69,7 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
69
  $categoryHierarchies[] = $value;
70
  }
71
 
72
- $categoryNames[] = $categoryName;
73
  }
74
 
75
  $categoryHierarchies = array_unique($categoryHierarchies);
@@ -110,16 +110,32 @@ class SearchSpring_Manager_Operation_Product_SetCategories extends SearchSpring_
110
  $hierarchy = array();
111
  $currentHierarchy = array();
112
  foreach ($categoryPath as $categoryId) {
113
- $category = Mage::getModel('catalog/category')->load($categoryId);
114
 
115
- $categoryName = $category->getData('name');
116
- if (in_array($categoryName, $this->skipCategories)) {
117
  continue;
118
- }
119
- $currentHierarchy[] = $categoryName;
 
120
  $hierarchy[] = implode('/', $currentHierarchy);
121
  }
122
 
123
  return $hierarchy;
124
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
18
  * Feed Constants
19
  */
20
  const FEED_CATEGORY_HIERARCHY = 'category_hierarchy';
21
+ const FEED_CATEGORY_NAME = 'category';
22
  const FEED_CATEGORY_IDS = 'category_ids';
23
  /**#@-*/
24
 
25
  /**
26
+ * Loaded category model cache
27
  *
28
+ * @var Mage_Catalog_Model_Category[]
29
  */
30
+ protected $_categoryCache = array();
31
 
32
  /**
33
  * Sets category data to feed
48
  /** @var Mage_Catalog_Model_Category $category */
49
  foreach($product->getCategoryIds() as $categoryId) {
50
  // load category data
51
+ $category = $this->_getCategory($categoryId);
52
 
53
  if (!$category->getData('is_active')) {
54
  continue;
55
  }
56
 
57
+ // Skip this store's root category
58
+ if ($this->_isRoot($category)) {
59
  continue;
60
  }
61
 
69
  $categoryHierarchies[] = $value;
70
  }
71
 
72
+ $categoryNames[] = $category->getName();
73
  }
74
 
75
  $categoryHierarchies = array_unique($categoryHierarchies);
110
  $hierarchy = array();
111
  $currentHierarchy = array();
112
  foreach ($categoryPath as $categoryId) {
113
+ $category = $this->_getCategory($categoryId);
114
 
115
+ // Skip this store's root category
116
+ if ($this->_isRoot($category)) {
117
  continue;
118
+ }
119
+
120
+ $currentHierarchy[] = $category->getName();
121
  $hierarchy[] = implode('/', $currentHierarchy);
122
  }
123
 
124
  return $hierarchy;
125
  }
126
+
127
+ protected function _getCategory($categoryId) {
128
+ if (!isset($this->_categoryCache[$categoryId])) {
129
+ $this->_categoryCache[$categoryId] = Mage::getModel('catalog/category')->load($categoryId);
130
+ }
131
+ return $this->_categoryCache[$categoryId];
132
+ }
133
+
134
+ protected function _isRoot($category) {
135
+ return (
136
+ 0 === (int)$category->getLevel() ||
137
+ 1 === (int)$category->getLevel()
138
+ );
139
+ }
140
+
141
  }
app/code/community/SearchSpring/Manager/Operation/Product/SetCoreFields.php CHANGED
@@ -14,156 +14,172 @@
14
  */
15
  class SearchSpring_Manager_Operation_Product_SetCoreFields extends SearchSpring_Manager_Operation_Product
16
  {
17
- /**#@+
18
- * Feed constants
19
- */
20
- const FEED_SKU = 'sku';
21
- const FEED_PRODUCT_TYPE = 'product_type';
22
- const FEED_DESCRIPTION = 'description';
23
- const FEED_SHORT_DESCRIPTION = 'short_description';
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';
31
- const FEED_CHILD_SKU = 'child_sku';
32
- const FEED_CHILD_NAME = 'child_name';
33
- /**#@-*/
34
-
35
-
36
-
37
- /**
38
- * Add SearchSpring core fields to the feed
39
- * - sku
40
- * - product_type
41
- * - quantity
42
- * - in_stock
43
- * - weight
44
- * - manufacturer
45
- * - url
46
- * - url
47
- * - image_url
48
- * - thumbnail_url
49
- * - cached_thumbnail_url
50
- * - name
51
- * - description
52
- * - short_description
53
- * - child_quantity
54
- * - child_name
55
- * - child_sku
56
- *
57
- * @param Mage_Catalog_Model_Product $product
58
- *
59
- * @return $this
60
- */
61
- public function perform(Mage_Catalog_Model_Product $product)
62
- {
63
- $webBaseUrl = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB);
64
-
65
- $this->getRecords()->set(self::FEED_SKU, $product->getSku());
66
- $this->getRecords()->set(self::FEED_PRODUCT_TYPE, $product->getData('type_id'));
67
- $this->getRecords()->set(self::FEED_QUANTITY, number_format($this->getQuantity($product)));
68
- $this->getRecords()->set(self::FEED_IN_STOCK, $product->isInStock());
69
- $this->getRecords()->set(self::FEED_WEIGHT, number_format((double)$product->getWeight(), 2));
70
- $this->getRecords()->set(self::FEED_MANUFACTURER, $product->getAttributeText('manufacturer'));
71
- $this->getRecords()->set(self::FEED_URL, $webBaseUrl . $product->getUrlPath());
72
-
73
- $productName = $this->getSanitizer()->removeNewlinesAndStripTags($product->getName());
74
- $description = $this->getSanitizer()->sanitizeForRequest($product->getData('description'));
75
- $shortDescription = $this->getSanitizer()->sanitizeForRequest($product->getData('short_description'));
76
- $this->getRecords()->set(self::FEED_NAME, $productName);
77
- $this->getRecords()->set(self::FEED_DESCRIPTION, $description);
78
- $this->getRecords()->set(self::FEED_SHORT_DESCRIPTION, $shortDescription);
79
-
80
- $this->setChildQuantity($product);
81
-
82
- return $this;
83
- }
84
-
85
- /**
86
- * Helper method to get the quantity from a product
87
- *
88
- * @param Mage_Catalog_Model_Product $product
89
- * @return int
90
- */
91
- private function getQuantity(Mage_Catalog_Model_Product $product)
92
- {
93
- $quantity = $product->getData('stock_item')->getData('qty');
94
-
95
- return (int)$quantity;
96
- }
97
-
98
- /**
99
- * If the product is composite, find the child quantities and set that to the field
100
- */
101
- private function setChildQuantity(Mage_Catalog_Model_Product $product)
102
- {
103
- // default to normal quantity
104
- $childQuantity = $this->getRecords()->get(self::FEED_QUANTITY);
105
-
106
- // find the child quantity if it exists
107
- switch ($product->getTypeId()) {
108
- case Mage_Catalog_Model_Product_Type::TYPE_GROUPED:
109
- /** @var Mage_Catalog_Model_Product_Type_Grouped $typeInstance */
110
- $typeInstance = $product->getTypeInstance(true);
111
- $associated = $typeInstance->getAssociatedProducts($product);
112
- $childQuantity = $this->getQuantityForChildren($associated);
113
-
114
- break;
115
- case Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE:
116
- /** @var Mage_Catalog_Model_Product_Type_Configurable $typeInstance */
117
- $typeInstance = $product->getTypeInstance(true);
118
-
119
- /** @var Mage_Catalog_Model_Product_Type_Configurable_Attribute $attribute */
120
- foreach ($typeInstance->getConfigurableAttributesAsArray($product) as $attribute) {
121
- foreach ($attribute['values'] as $value) {
122
- $this->getRecords()->add($attribute['attribute_code'], $value['label']);
123
- }
124
- }
125
-
126
- $children = $typeInstance->getUsedProducts(null, $product);
127
- $childQuantity = $this->getQuantityForChildren($children);
128
-
129
- break;
130
- case Mage_Catalog_Model_Product_Type::TYPE_BUNDLE:
131
- /** @var Mage_Bundle_Model_Product_Type $typeInstance */
132
- $typeInstance = $product->getTypeInstance(true);
133
- $optionsIds = $typeInstance->getOptionsIds($product);
134
- $selections = $typeInstance->getSelectionsCollection($optionsIds, $product);
135
- $bundleOptions = $typeInstance->getOptionsByIds($optionsIds, $product);
136
- $bundleOptions->appendSelections($selections);
137
-
138
- /** @var Mage_Bundle_Model_Option $bundleOption */
139
- foreach ($bundleOptions as $bundleOption) {
140
- $childQuantity += $this->getQuantityForChildren($bundleOption->getData('selections'));
141
- }
142
-
143
- break;
144
- }
145
-
146
- $this->getRecords()->set(self::FEED_CHILD_QUANTITY, $childQuantity);
147
- }
148
-
149
- /**
150
- * Loop over an array of products to set field data and calculate total child quantity
151
- *
152
- * @param array $products
153
- * @return int
154
- */
155
- private function getQuantityForChildren(array $products)
156
- {
157
- $quantity = 0;
158
-
159
- /** @var Mage_Catalog_Model_Product $product */
160
- foreach ($products as $product) {
161
- $this->getRecords()->add(self::FEED_CHILD_SKU, $product->getSku());
162
- $this->getRecords()->add(self::FEED_CHILD_NAME, $product->getName());
163
-
164
- $quantity += $this->getQuantity($product);
165
- }
166
-
167
- return $quantity;
168
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
14
  */
15
  class SearchSpring_Manager_Operation_Product_SetCoreFields extends SearchSpring_Manager_Operation_Product
16
  {
17
+ /**#@+
18
+ * Feed constants
19
+ */
20
+ const FEED_SKU = 'sku';
21
+ const FEED_PRODUCT_TYPE = 'product_type';
22
+ const FEED_DESCRIPTION = 'description';
23
+ const FEED_SHORT_DESCRIPTION = 'short_description';
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';
31
+ const FEED_CHILD_SKU = 'child_sku';
32
+ const FEED_CHILD_NAME = 'child_name';
33
+ const FEED_DAYS_OLD = 'days_old';
34
+ /**#@-*/
35
+
36
+
37
+
38
+ /**
39
+ * Add SearchSpring core fields to the feed
40
+ * - sku
41
+ * - product_type
42
+ * - quantity
43
+ * - in_stock
44
+ * - weight
45
+ * - manufacturer
46
+ * - url
47
+ * - url
48
+ * - image_url
49
+ * - thumbnail_url
50
+ * - cached_thumbnail_url
51
+ * - name
52
+ * - description
53
+ * - short_description
54
+ * - child_quantity
55
+ * - child_name
56
+ * - child_sku
57
+ * - days_old
58
+ *
59
+ * @param Mage_Catalog_Model_Product $product
60
+ *
61
+ * @return $this
62
+ */
63
+ public function perform(Mage_Catalog_Model_Product $product)
64
+ {
65
+ $phlp = Mage::helper('searchspring_manager/product');
66
+
67
+ $webBaseUrl = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB);
68
+
69
+ $this->getRecords()->set(self::FEED_SKU, $product->getSku());
70
+ $this->getRecords()->set(self::FEED_PRODUCT_TYPE, $product->getData('type_id'));
71
+ $this->getRecords()->set(self::FEED_QUANTITY, number_format($this->getQuantity($product)));
72
+ $this->getRecords()->set(self::FEED_WEIGHT, number_format((double)$product->getWeight(), 2));
73
+ $this->getRecords()->set(self::FEED_MANUFACTURER, $phlp->getAttributeText($product, 'manufacturer'));
74
+ $this->getRecords()->set(self::FEED_URL, $webBaseUrl . $product->getUrlPath());
75
+
76
+ $stockItem = $product->getStockItem();
77
+ $this->getRecords()->set(self::FEED_IN_STOCK, $stockItem->getIsInStock());
78
+
79
+ $productName = $this->getSanitizer()->removeNewlinesAndStripTags($product->getName());
80
+ $description = $this->getSanitizer()->sanitizeForRequest($product->getData('description'));
81
+ $shortDescription = $this->getSanitizer()->sanitizeForRequest($product->getData('short_description'));
82
+ $this->getRecords()->set(self::FEED_NAME, $productName);
83
+ $this->getRecords()->set(self::FEED_DESCRIPTION, $description);
84
+ $this->getRecords()->set(self::FEED_SHORT_DESCRIPTION, $shortDescription);
85
+
86
+ $this->setChildQuantity($product);
87
+
88
+ $this->setDaysOld($product);
89
+
90
+ return $this;
91
+ }
92
+
93
+ /**
94
+ * Helper method to get the quantity from a product
95
+ *
96
+ * @param Mage_Catalog_Model_Product $product
97
+ * @return int
98
+ */
99
+ private function getQuantity(Mage_Catalog_Model_Product $product)
100
+ {
101
+ $quantity = $product->getData('stock_item')->getData('qty');
102
+
103
+ return (int)$quantity;
104
+ }
105
+
106
+ /**
107
+ * If the product is composite, find the child quantities and set that to the field
108
+ */
109
+ private function setChildQuantity(Mage_Catalog_Model_Product $product)
110
+ {
111
+ // default to normal quantity
112
+ $childQuantity = $this->getRecords()->get(self::FEED_QUANTITY);
113
+
114
+ // find the child quantity if it exists
115
+ switch ($product->getTypeId()) {
116
+ case Mage_Catalog_Model_Product_Type::TYPE_GROUPED:
117
+ /** @var Mage_Catalog_Model_Product_Type_Grouped $typeInstance */
118
+ $typeInstance = $product->getTypeInstance(true);
119
+ $associated = $typeInstance->getAssociatedProducts($product);
120
+ $childQuantity = $this->getQuantityForChildren($associated);
121
+
122
+ break;
123
+ case Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE:
124
+ /** @var Mage_Catalog_Model_Product_Type_Configurable $typeInstance */
125
+ $typeInstance = $product->getTypeInstance(true);
126
+
127
+ /** @var Mage_Catalog_Model_Product_Type_Configurable_Attribute $attribute */
128
+ foreach ($typeInstance->getConfigurableAttributesAsArray($product) as $attribute) {
129
+ foreach ($attribute['values'] as $value) {
130
+ $this->getRecords()->add($attribute['attribute_code'], $value['label']);
131
+ }
132
+ }
133
+
134
+ $children = $typeInstance->getUsedProducts(null, $product);
135
+ $childQuantity = $this->getQuantityForChildren($children);
136
+
137
+ break;
138
+ case Mage_Catalog_Model_Product_Type::TYPE_BUNDLE:
139
+ /** @var Mage_Bundle_Model_Product_Type $typeInstance */
140
+ $typeInstance = $product->getTypeInstance(true);
141
+ $optionsIds = $typeInstance->getOptionsIds($product);
142
+ $selections = $typeInstance->getSelectionsCollection($optionsIds, $product);
143
+ $bundleOptions = $typeInstance->getOptionsByIds($optionsIds, $product);
144
+ $bundleOptions->appendSelections($selections);
145
+
146
+ /** @var Mage_Bundle_Model_Option $bundleOption */
147
+ foreach ($bundleOptions as $bundleOption) {
148
+ $childQuantity += $this->getQuantityForChildren($bundleOption->getData('selections'));
149
+ }
150
+
151
+ break;
152
+ }
153
+
154
+ $this->getRecords()->set(self::FEED_CHILD_QUANTITY, $childQuantity);
155
+ }
156
+
157
+ /**
158
+ * Set the hos many days old the product is
159
+ */
160
+ private function setDaysOld($product) {
161
+ $createdAt = strtotime($product->getCreatedAt());
162
+ $this->getRecords()->set(self::FEED_DAYS_OLD, floor((time() - $createdAt) / 60 / 60 / 24));
163
+ }
164
+
165
+ /**
166
+ * Loop over an array of products to set field data and calculate total child quantity
167
+ *
168
+ * @param array $products
169
+ * @return int
170
+ */
171
+ private function getQuantityForChildren(array $products)
172
+ {
173
+ $quantity = 0;
174
+
175
+ /** @var Mage_Catalog_Model_Product $product */
176
+ foreach ($products as $product) {
177
+ $this->getRecords()->add(self::FEED_CHILD_SKU, $product->getSku());
178
+ $this->getRecords()->add(self::FEED_CHILD_NAME, $product->getName());
179
+
180
+ $quantity += $this->getQuantity($product);
181
+ }
182
+
183
+ return $quantity;
184
+ }
185
  }
app/code/community/SearchSpring/Manager/Operation/Product/SetFields.php CHANGED
@@ -24,7 +24,14 @@ class SearchSpring_Manager_Operation_Product_SetFields extends SearchSpring_Mana
24
  {
25
  /** @var Mage_Catalog_Model_Resource_Eav_Attribute $attribute */
26
  foreach($product->getAttributes() as $key => $attribute) {
27
- $this->getRecords()->set($key, $this->getAttributeValue($product, $attribute));
 
 
 
 
 
 
 
28
  }
29
 
30
  return $this;
24
  {
25
  /** @var Mage_Catalog_Model_Resource_Eav_Attribute $attribute */
26
  foreach($product->getAttributes() as $key => $attribute) {
27
+ $value = $this->getAttributeValue($product, $attribute);
28
+ if (!is_array($value)) {
29
+ $this->getRecords()->add($key, $value);
30
+ } else {
31
+ foreach($value as $v) {
32
+ $this->getRecords()->add($key, $v);
33
+ }
34
+ }
35
  }
36
 
37
  return $this;
app/code/community/SearchSpring/Manager/Operation/Product/SetReport.php ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * SetReport.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_Operation_Product_SetReport
10
+ *
11
+ * Set Sales/Customer/Reporting related product data
12
+ *
13
+ * Available reporting functions (from CE version 1.3)
14
+ * public function addCartsCount()
15
+ * public function addOrdersCount($from = '', $to = '')
16
+ * public function addOrderedQty($from = '', $to = '')
17
+ * public function addViewsCount($from = '', $to = '')
18
+ *
19
+ * @author Jake Shelby <jake@b7interactive.com>
20
+ */
21
+ class SearchSpring_Manager_Operation_Product_SetReport extends SearchSpring_Manager_Operation_Product
22
+ {
23
+
24
+ protected $_enabled = true;
25
+ protected $_reportData;
26
+
27
+ /**
28
+ * Feed constants
29
+ */
30
+ const FEED_CART_COUNT = 'report_cart_count';
31
+ const FEED_ORDERS_COUNT = 'report_orders_count';
32
+ const FEED_ORDERS_QTY = 'report_orders_qty';
33
+
34
+ // Default timespan, 1 year, so we don't accidentally overload their database
35
+ const DEFAULT_REPORT_TIMESPAN = '1 year';
36
+
37
+ public function prepare(Mage_Catalog_Model_Resource_Product_Collection $productCollection) {
38
+
39
+ $this->fetchReportData(
40
+ $productCollection->getAllIds(),
41
+ $productCollection->getStoreId()
42
+ );
43
+
44
+ return $this;
45
+ }
46
+
47
+ public function perform(Mage_Catalog_Model_Product $product)
48
+ {
49
+
50
+ if ($this->_enabled) {
51
+
52
+ $this->setOrderedQty($product);
53
+
54
+ // We're not using these just yet
55
+ // $this->setCartCount($product);
56
+ // $this->setOrdersCount($product);
57
+
58
+ }
59
+
60
+ return $this;
61
+ }
62
+
63
+ public function setCartCount(Mage_Catalog_Model_Product $product) {
64
+ if ($productReport = $this->getProductReport($product)) {
65
+ $this->getRecords()->set(self::FEED_CART_COUNT, $productReport->getCounts());
66
+ }
67
+ }
68
+
69
+ public function setOrdersCount(Mage_Catalog_Model_Product $product) {
70
+ if ($productReport = $this->getProductReport($product)) {
71
+ $this->getRecords()->set(self::FEED_ORDERS_COUNT, $productReport->getOrders());
72
+ }
73
+ }
74
+
75
+ public function setOrderedQty(Mage_Catalog_Model_Product $product) {
76
+ if ($productReport = $this->getProductReport($product)) {
77
+ $this->getRecords()->set(self::FEED_ORDERS_QTY, $productReport->getOrderedQty());
78
+ }
79
+ }
80
+
81
+ public function getProductReport(Mage_Catalog_Model_Product $product) {
82
+
83
+ // Make sure we have report data
84
+ if (!$this->_reportData) {
85
+ // If we don't have data, then we'll fetch it with the requested product
86
+ // NOTE: This should only happen if the person using the class didn't request
87
+ // preparation with a product collection
88
+ $this->fetchReportData(array($product->getId()), $product->getStoreId());
89
+
90
+ // If we still don't have the data, then we can't support this feature
91
+ if (!$this->_reportData) {
92
+ return false;
93
+ }
94
+ }
95
+
96
+ // Make sure we have data for this product
97
+ if (!isset($this->_reportData[$product->getId()])) {
98
+ return false;
99
+ }
100
+
101
+ // Return as an object
102
+ return new Varien_Object($this->_reportData[$product->getId()]);
103
+ }
104
+
105
+ protected function fetchReportData($productIds, $store) {
106
+
107
+ // Start by using the reports collection
108
+ $reportCollection = $this->createReportCollection($productIds, $store);
109
+
110
+ // If we don't have a collection now, we won't ever have one, disable this operation
111
+ if (!$reportCollection) {
112
+ $this->_enabled = false;
113
+ $this->_reportData = null;
114
+ return;
115
+ }
116
+
117
+ // Get From and To Dates
118
+ $from = $this->getParamReportStartDate();
119
+ $to = $this->getParamReportEndDate();
120
+
121
+ // Start our resulting data
122
+ $reportData = array();
123
+
124
+ // Ordered Qty
125
+ $reportCollection->addOrderedQty($from,$to);
126
+ foreach($reportCollection as $productReport) {
127
+ $reportData[$productReport->getId()] = array(
128
+ 'ordered_qty' => $productReport->getOrderedQty(),
129
+ );
130
+ }
131
+
132
+ /* // Order Counts - Add this in when needed
133
+ $reportCollection = $this->createReportCollection($productIds, $store);
134
+ $reportCollection->addOrdersCount($from,$to); // TODO Figure out how to add this without breaking the query
135
+ foreach($reportCollection as $productReport) {
136
+ $reportData[$productReport->getId()] = array(
137
+ 'orders' => $productReport->getOrders(),
138
+ );
139
+ } */
140
+
141
+ /* // Cart Counts - Add this in when needed
142
+ $reportCollection = $this->createReportCollection($productIds, $store);
143
+ $reportCollection->addCartsCount(); // TODO Figure out how to add this without breaking the query
144
+ foreach($reportCollection as $productReport) {
145
+ $reportData[$productReport->getId()] = array(
146
+ 'counts' => $productReport->getCounts(),
147
+ );
148
+ } */
149
+
150
+ $this->_reportData = $reportData;
151
+ }
152
+
153
+ protected function createReportCollection($productIds, $store) {
154
+
155
+ // Start by using the reports collection
156
+ $reportCollection = Mage::getResourceModel('reports/product_collection');
157
+
158
+ // Make sure this magento installation has this report collection
159
+ if (!is_object($reportCollection)) {
160
+ return;
161
+ }
162
+
163
+ // Filter By Store ID
164
+ $reportCollection->setStoreId($store)->addStoreFilter($store);
165
+
166
+ // Filter to just certain products
167
+ $reportCollection->addAttributeToFilter('entity_id', array('in' => $productIds));
168
+
169
+ return $reportCollection;
170
+ }
171
+
172
+ // TODO -- Figure out how to add from and to dates as parameters, might need to add them as configuration items as well
173
+ public function getParamReportStartDate() {
174
+ return date('Y-m-d H:i:s', strtotime('-' . self::DEFAULT_REPORT_TIMESPAN));
175
+ }
176
+
177
+ public function getParamReportEndDate() {
178
+ // Up till now
179
+ return date('Y-m-d H:i:s');
180
+ }
181
+
182
+ }
app/code/community/SearchSpring/Manager/Operation/ProductOperation.php CHANGED
@@ -12,14 +12,32 @@
12
  */
13
  interface SearchSpring_Manager_Operation_ProductOperation
14
  {
 
15
  /**
16
- * Perform an operation
 
 
 
17
  *
18
- * @param Mage_Catalog_Model_Product $product
19
  *
20
  * @return self
21
  */
22
- public function perform(Mage_Catalog_Model_Product $product);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  /**
25
  * Checks validity of operation
@@ -31,7 +49,17 @@ interface SearchSpring_Manager_Operation_ProductOperation
31
  public function isValid(Mage_Catalog_Model_Product $product);
32
 
33
  /**
34
- * Set records collection to operation
 
 
 
 
 
 
 
 
 
 
35
  *
36
  * @param SearchSpring_Manager_Entity_RecordsCollection $records
37
  *
12
  */
13
  interface SearchSpring_Manager_Operation_ProductOperation
14
  {
15
+
16
  /**
17
+ * Prepare the collection, before it's loaded.
18
+ * This allows for the operation to add filters,
19
+ * joins, extra data to the collection before
20
+ * it's loaded.
21
  *
22
+ * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
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
30
+ * been loaded, but before isValid and perform have
31
+ * been called. This allows for pulling in data in
32
+ * mass, since you have been called ahead of time
33
+ * with the products that you will be performing
34
+ * operations on.
35
+ *
36
+ * @param Mage_Catalog_Model_Resource_Product_Collection $productCollection
37
+ *
38
+ * @return self
39
+ */
40
+ public function prepare(Mage_Catalog_Model_Resource_Product_Collection $productCollection);
41
 
42
  /**
43
  * Checks validity of operation
49
  public function isValid(Mage_Catalog_Model_Product $product);
50
 
51
  /**
52
+ * Perform an operation
53
+ *
54
+ * @param Mage_Catalog_Model_Product $product
55
+ *
56
+ * @return self
57
+ */
58
+ public function perform(Mage_Catalog_Model_Product $product);
59
+
60
+ /**
61
+ * Set records collection to operation. This is the
62
+ * collection that output data should be written to.
63
  *
64
  * @param SearchSpring_Manager_Entity_RecordsCollection $records
65
  *
app/code/community/SearchSpring/Manager/Provider/ProductCollection/FeedProvider.php CHANGED
@@ -57,8 +57,6 @@ class SearchSpring_Manager_Provider_ProductCollection_FeedProvider
57
  return $this->collection;
58
  }
59
 
60
- $showOos = Mage::helper('cataloginventory')->isShowOutOfStock();
61
-
62
  /** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
63
  $collection = Mage::getModel('catalog/product')->getCollection();
64
  $collection->addStoreFilter(Mage::app()->getStore($this->requestParams->getStore())->getId());
@@ -67,7 +65,7 @@ class SearchSpring_Manager_Provider_ProductCollection_FeedProvider
67
  $collection->addAttributeToFilter('visibility', Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH);
68
 
69
 
70
- if (!$showOos) {
71
  Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($collection);
72
  }
73
 
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());
65
  $collection->addAttributeToFilter('visibility', Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH);
66
 
67
 
68
+ if (!Mage::helper('searchspring_manager')->isOutOfStockIndexingEnabled()) {
69
  Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($collection);
70
  }
71
 
app/code/community/SearchSpring/Manager/Request/JSON.php CHANGED
@@ -1,56 +1,56 @@
1
  <?php
2
  /**
3
- * JSON.php
4
- *
5
- * @copyright B7 Interactive, LLC. All Rights Reserved.
6
- */
7
 
8
  /**
9
- * Class SearchSpring_Manager_Request_JSON
10
- *
11
- * Wrapper for Request object that allows you to easily access JSON POST Data.
12
- *
13
- * @author James Bathgate <james@b7interactive.com>
14
- */
15
  class SearchSpring_Manager_Request_JSON
16
  {
17
 
18
- /**
19
- * Request parameters parsed from JSON
20
- *
21
- * @var object $request
22
- */
23
- private $params;
24
-
25
- /**
26
- * Constructor
27
- *
28
- * @param Mage_Core_Controller_Request_Http $request
29
- */
30
- public function __construct(Mage_Core_Controller_Request_Http $request) {
31
- $rawBody = $request->getRawBody();
32
-
33
- if(!empty($rawBody)) {
34
- $this->params = json_decode($rawBody, true);
35
-
36
- if(null === $this->params) {
37
- throw new Exception('Invalid JSON in Request');
38
- }
39
- } else {
40
- $this->params = array();
41
- }
42
-
43
-
44
- }
45
-
46
- /**
47
- * Returns parameter from JSON request
48
- *
49
- * @param string $parameter
50
- *
51
- * @return mixed
52
- */
53
- public function getParam($parameter, $default=NULL) {
54
- return isset($this->params[$parameter])?$this->params[$parameter]:$default;
55
- }
56
  }
1
  <?php
2
  /**
3
+ * JSON.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
 
8
  /**
9
+ * Class SearchSpring_Manager_Request_JSON
10
+ *
11
+ * Wrapper for Request object that allows you to easily access JSON POST Data.
12
+ *
13
+ * @author James Bathgate <james@b7interactive.com>
14
+ */
15
  class SearchSpring_Manager_Request_JSON
16
  {
17
 
18
+ /**
19
+ * Request parameters parsed from JSON
20
+ *
21
+ * @var object $request
22
+ */
23
+ private $params;
24
+
25
+ /**
26
+ * Constructor
27
+ *
28
+ * @param Mage_Core_Controller_Request_Http $request
29
+ */
30
+ public function __construct(Mage_Core_Controller_Request_Http $request) {
31
+ $rawBody = $request->getRawBody();
32
+
33
+ if(!empty($rawBody)) {
34
+ $this->params = Zend_Json::decode($rawBody);
35
+
36
+ if(null === $this->params) {
37
+ throw new Exception('Invalid JSON in Request');
38
+ }
39
+ } else {
40
+ $this->params = array();
41
+ }
42
+
43
+
44
+ }
45
+
46
+ /**
47
+ * Returns parameter from JSON request
48
+ *
49
+ * @param string $parameter
50
+ *
51
+ * @return mixed
52
+ */
53
+ public function getParam($parameter, $default=NULL) {
54
+ return isset($this->params[$parameter])?$this->params[$parameter]:$default;
55
+ }
56
  }
app/code/community/SearchSpring/Manager/Service/SearchSpring/ApiAdapter.php CHANGED
@@ -23,11 +23,11 @@ class SearchSpring_Manager_Service_SearchSpring_ApiAdapter
23
  const URL_FORMAT = '%s/%s';
24
 
25
  /**
26
- * The curl adapter
27
  *
28
- * @var Varien_Http_Adapter_Curl $curl
29
  */
30
- protected $curl;
31
 
32
  /**
33
  * An API error handler if a request fails
@@ -52,11 +52,11 @@ class SearchSpring_Manager_Service_SearchSpring_ApiAdapter
52
  */
53
  public function __construct(
54
  SearchSpring_Manager_Handler_ApiErrorHandler $errorHandler,
55
- Varien_Http_Adapter_Curl $curl,
56
  $baseUrl
57
  ) {
58
  $this->errorHandler = $errorHandler;
59
- $this->curl = $curl;
60
  $this->baseUrl = $baseUrl;
61
  }
62
 
23
  const URL_FORMAT = '%s/%s';
24
 
25
  /**
26
+ * The http client
27
  *
28
+ * @var Zend_Http_Client
29
  */
30
+ protected $client;
31
 
32
  /**
33
  * An API error handler if a request fails
52
  */
53
  public function __construct(
54
  SearchSpring_Manager_Handler_ApiErrorHandler $errorHandler,
55
+ Zend_Http_Client $client,
56
  $baseUrl
57
  ) {
58
  $this->errorHandler = $errorHandler;
59
+ $this->client = $client;
60
  $this->baseUrl = $baseUrl;
61
  }
62
 
app/code/community/SearchSpring/Manager/Service/SearchSpring/IndexingApiAdapter.php CHANGED
@@ -49,15 +49,16 @@ class SearchSpring_Manager_Service_SearchSpring_IndexingApiAdapter extends Searc
49
  */
50
  private function request($httpMethod, $url, $body)
51
  {
52
- $this->curl->write($httpMethod, $url, Zend_Http_Client::HTTP_1, array(), $body);
53
- $response = $this->curl->read();
54
- $responseCode = Zend_Http_Response::extractCode($response);
55
- $this->curl->close();
56
 
57
- if ($this->errorHandler->shouldRetry($response)) {
58
- $this->request($httpMethod, $url, $body);
59
  }
60
 
 
 
61
  return $responseCode;
62
  }
63
 
49
  */
50
  private function request($httpMethod, $url, $body)
51
  {
52
+ $this->client->setUri($url);
53
+ $this->client->setRawData($body, 'application/json');
54
+ $response = $this->client->request($httpMethod);
 
55
 
56
+ while($this->errorHandler->shouldRetry($response)) {
57
+ $response = $this->client->request($httpMethod);
58
  }
59
 
60
+ $responseCode = $response->getStatus();
61
+
62
  return $responseCode;
63
  }
64
 
app/code/community/SearchSpring/Manager/Service/SearchSpring/SearchApiAdapter.php CHANGED
@@ -56,6 +56,8 @@ class SearchSpring_Manager_Service_SearchSpring_SearchApiAdapter extends SearchS
56
  */
57
  private function request($httpMethod, $url, $parameters)
58
  {
 
 
59
  $this->curl->write($httpMethod, $url . '?' . $parameters, Zend_Http_Client::HTTP_1, array());
60
  $response = $this->curl->read();
61
 
@@ -67,6 +69,7 @@ class SearchSpring_Manager_Service_SearchSpring_SearchApiAdapter extends SearchS
67
  }
68
 
69
  return $responseBody;
 
70
  }
71
 
72
  }
56
  */
57
  private function request($httpMethod, $url, $parameters)
58
  {
59
+
60
+ // TODO Change this to Zend_Http_Client, parameters shouldn't be a string but an array
61
  $this->curl->write($httpMethod, $url . '?' . $parameters, Zend_Http_Client::HTTP_1, array());
62
  $response = $this->curl->read();
63
 
69
  }
70
 
71
  return $responseBody;
72
+
73
  }
74
 
75
  }
app/code/community/SearchSpring/Manager/Strategy/Pricing/Strategy.php CHANGED
@@ -183,7 +183,6 @@ abstract class SearchSpring_Manager_Strategy_Pricing_Strategy implements SearchS
183
  private function formatPrice($price)
184
  {
185
  $price = (double)$price;
186
- $price = (double)number_format($price, 2);
187
 
188
  return $price;
189
  }
183
  private function formatPrice($price)
184
  {
185
  $price = (double)$price;
 
186
 
187
  return $price;
188
  }
app/code/community/SearchSpring/Manager/ThirdParty/Amasty/LabelsOperation.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * LabelsOperation.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_ThirdParty_Amasty_LabelsOperation
10
+ *
11
+ * Set Amasty labels HTML to the feed
12
+ *
13
+ * @author Will Wesley <will@b7interactive.com>
14
+ */
15
+ class SearchSpring_Manager_ThirdParty_Amasty_LabelsOperation extends SearchSpring_Manager_Operation_Product
16
+ {
17
+ const FEED_LABEL = 'Amasty_Label_product_labels_html';
18
+
19
+ /**
20
+ * Add label HTML
21
+ *
22
+ * @param Mage_Catalog_Model_Product $product
23
+ * @return SearchSpring_Manager_Operation_ProductOperation|void
24
+ */
25
+ public function perform(Mage_Catalog_Model_Product $product)
26
+ {
27
+ $this->getRecords()->add(self::FEED_LABEL, Mage::helper('amlabel')->getLabels($product));
28
+ return $this;
29
+ }
30
+ }
app/code/community/SearchSpring/Manager/ThirdParty/TBT/RewardsOperation.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * RewardsOperation.php
4
+ *
5
+ * @copyright B7 Interactive, LLC. All Rights Reserved.
6
+ */
7
+
8
+ /**
9
+ * Class SearchSpring_Manager_ThirdParty_TBT_RewardsOperation
10
+ *
11
+ * Set Rewards Related Data
12
+ *
13
+ * @author Jake Shelby <jake@b7interactive.com>
14
+ */
15
+ class SearchSpring_Manager_ThirdParty_TBT_RewardsOperation extends SearchSpring_Manager_Operation_Product
16
+ {
17
+
18
+ public function perform(Mage_Catalog_Model_Product $product)
19
+ {
20
+
21
+ $value = $product->getEarnablePoints();
22
+
23
+ if (is_array($value)) {
24
+ $value = current($value);
25
+ }
26
+
27
+ $this->getRecords()->add('TBT_Rewards_earnable_points', $value);
28
+
29
+ return $this;
30
+ }
31
+
32
+ }
app/code/community/SearchSpring/Manager/Transformer/ProductCollectionToRecordCollectionTransformer.php CHANGED
@@ -47,10 +47,24 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
47
  */
48
  public function transform(Mage_Catalog_Model_Resource_Product_Collection $productCollection)
49
  {
 
 
 
 
 
 
 
 
 
 
 
50
  /** @var Mage_Catalog_Model_Product $product */
51
  foreach ($productCollection as $product) {
 
52
  // load product data
 
53
  $product->load($product->getId());
 
54
 
55
  // default to valid
56
  $productValid = true;
@@ -58,13 +72,18 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
58
  /** @var SearchSpring_Manager_Operation_Product $operation */
59
  foreach ($this->operationsCollection as $operation) {
60
  // check if any operation validation invalidates this product
 
 
61
  if (false === $operation->isValid($product)) {
62
  $productValid = false;
63
  }
 
 
64
  }
65
 
66
  // only set id if product is invalid and continue to next product
67
  if (false === $productValid) {
 
68
  $operation = new SearchSpring_Manager_Operation_Product_SetId(
69
  new SearchSpring_Manager_String_Sanitizer(),
70
  $this->recordsCollection
@@ -75,17 +94,67 @@ class SearchSpring_Manager_Transformer_ProductCollectionToRecordCollectionTransf
75
  // increment record
76
  $this->recordsCollection->next();
77
 
 
 
78
  continue;
79
  }
80
 
81
  foreach ($this->operationsCollection as $operation) {
 
 
82
  $operation->perform($product);
 
 
83
  }
84
 
85
  // increment record
86
  $this->recordsCollection->next();
87
  }
88
 
 
 
89
  return $this->recordsCollection;
90
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
47
  */
48
  public function transform(Mage_Catalog_Model_Resource_Product_Collection $productCollection)
49
  {
50
+ Varien_Profiler::start(__METHOD__);
51
+
52
+ // Prep our collection
53
+ $this->prepareCollection($productCollection);
54
+
55
+ // Finally load the collection
56
+ $this->loadCollection($productCollection);
57
+
58
+ // Prep our operations with the loaded collection
59
+ $this->prepareOperations($productCollection);
60
+
61
  /** @var Mage_Catalog_Model_Product $product */
62
  foreach ($productCollection as $product) {
63
+
64
  // load product data
65
+ Varien_Profiler::start(__METHOD__.": product->load()");
66
  $product->load($product->getId());
67
+ Varien_Profiler::stop(__METHOD__.": product->load()");
68
 
69
  // default to valid
70
  $productValid = true;
72
  /** @var SearchSpring_Manager_Operation_Product $operation */
73
  foreach ($this->operationsCollection as $operation) {
74
  // check if any operation validation invalidates this product
75
+ Varien_Profiler::start(__METHOD__.": operation->isValid()");
76
+ Varien_Profiler::start(__METHOD__.": operation->isValid() : " . get_class($operation));
77
  if (false === $operation->isValid($product)) {
78
  $productValid = false;
79
  }
80
+ Varien_Profiler::stop(__METHOD__.": operation->isValid() : " . get_class($operation));
81
+ Varien_Profiler::stop(__METHOD__.": operation->isValid()");
82
  }
83
 
84
  // only set id if product is invalid and continue to next product
85
  if (false === $productValid) {
86
+ Varien_Profiler::start(__METHOD__.": invalid product, operation set id");
87
  $operation = new SearchSpring_Manager_Operation_Product_SetId(
88
  new SearchSpring_Manager_String_Sanitizer(),
89
  $this->recordsCollection
94
  // increment record
95
  $this->recordsCollection->next();
96
 
97
+ Varien_Profiler::stop(__METHOD__.": invalid product, operation set id");
98
+
99
  continue;
100
  }
101
 
102
  foreach ($this->operationsCollection as $operation) {
103
+ Varien_Profiler::start(__METHOD__.": operation->perform()");
104
+ Varien_Profiler::start(__METHOD__.": operation->perform() : " . get_class($operation));
105
  $operation->perform($product);
106
+ Varien_Profiler::stop(__METHOD__.": operation->perform()");
107
+ Varien_Profiler::stop(__METHOD__.": operation->perform() : " . get_class($operation));
108
  }
109
 
110
  // increment record
111
  $this->recordsCollection->next();
112
  }
113
 
114
+ Varien_Profiler::stop(__METHOD__);
115
+
116
  return $this->recordsCollection;
117
  }
118
+
119
+ /**
120
+ * Before the operations validate or perform on each product, we want to
121
+ * make sure the product collection is prepared. This allows for things
122
+ * like adding joins/filters/selects on the collection.
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);
130
+ }
131
+
132
+ }
133
+
134
+ /**
135
+ * Before the operations validate or perform on each product, but after the
136
+ * product collection has been loaded, prepare each operation. This allows
137
+ * for pulling in extra data in mass, rather than calling the database for
138
+ * very single product.
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));
146
+ $operation->prepare($productCollection);
147
+ Varien_Profiler::stop(__METHOD__.": operation->prepare() " . get_class($operation));
148
+ }
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();
156
+ Varien_Profiler::stop(__METHOD__.": productCollection->load()");
157
+
158
+ }
159
+
160
  }
app/code/community/SearchSpring/Manager/Validator/ProductValidator.php CHANGED
@@ -66,8 +66,10 @@ class SearchSpring_Manager_Validator_ProductValidator implements Zend_Validate_I
66
  }
67
 
68
  // if product is out of stock
69
- $displayOos = Mage::helper('cataloginventory')->isShowOutOfStock();
70
- if (!$product->isInStock() && !$displayOos) {
 
 
71
  return true;
72
  }
73
 
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) {
73
  return true;
74
  }
75
 
app/code/community/SearchSpring/Manager/Writer/Product/Params/FileWriterParams.php CHANGED
@@ -66,6 +66,13 @@ class SearchSpring_Manager_Writer_Product_Params_FileWriterParams
66
  */
67
  private $filename;
68
 
 
 
 
 
 
 
 
69
  /**
70
  * @var SearchSpring_Manager_Entity_RequestParams
71
  */
@@ -77,14 +84,48 @@ class SearchSpring_Manager_Writer_Product_Params_FileWriterParams
77
  * @param SearchSpring_Manager_Entity_RequestParams $requestParams
78
  * @param int $totalProducts
79
  * @param string $uniqueFilename
 
80
  */
81
- public function __construct(SearchSpring_Manager_Entity_RequestParams $requestParams, $totalProducts, $uniqueFilename)
82
  {
83
  $this->requestParams = $requestParams;
84
  $this->totalProducts = $totalProducts;
85
  $this->uniqueFilename = $uniqueFilename;
 
 
 
86
  }
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  /**
89
  * Get the request parameters object
90
  *
@@ -131,7 +172,7 @@ class SearchSpring_Manager_Writer_Product_Params_FileWriterParams
131
  public function getTempFilename()
132
  {
133
  if (null === $this->tempFilename) {
134
- $this->tempFilename = Mage::getBaseDir()
135
  . DS
136
  . sprintf(self::FILENAME_TEMP_PATTERN, $this->requestParams->getStore(), $this->uniqueFilename);
137
  }
@@ -147,9 +188,10 @@ class SearchSpring_Manager_Writer_Product_Params_FileWriterParams
147
  public function getFilename()
148
  {
149
  if (null === $this->filename) {
150
- $this->filename = Mage::getBaseDir() . DS . sprintf(self::FILENAME_FINAL_PATTERN, $this->requestParams->getStore());
151
  }
152
 
153
  return $this->filename;
154
  }
 
155
  }
66
  */
67
  private $filename;
68
 
69
+ /**
70
+ * The base directory
71
+ *
72
+ * @var string $filename
73
+ */
74
+ private $baseDir;
75
+
76
  /**
77
  * @var SearchSpring_Manager_Entity_RequestParams
78
  */
84
  * @param SearchSpring_Manager_Entity_RequestParams $requestParams
85
  * @param int $totalProducts
86
  * @param string $uniqueFilename
87
+ * @param string $baseDir Relative to the magento installation directory
88
  */
89
+ public function __construct(SearchSpring_Manager_Entity_RequestParams $requestParams, $totalProducts, $uniqueFilename, $baseDir)
90
  {
91
  $this->requestParams = $requestParams;
92
  $this->totalProducts = $totalProducts;
93
  $this->uniqueFilename = $uniqueFilename;
94
+ $this->baseDir = Mage::getBaseDir() . DS . $baseDir;
95
+
96
+ $this->validateBaseDir();
97
  }
98
 
99
+ /**
100
+ * Validate base directory
101
+ *
102
+ * @return string
103
+ */
104
+ public function validateBaseDir() {
105
+
106
+ // Make sure the directory exists
107
+ if (file_exists($this->baseDir)) {
108
+ // Make sure the directory isn't a file
109
+ if (!is_dir($this->baseDir)) {
110
+ throw new Exception("Output directory is not a directory; cannot write file.");
111
+ }
112
+ // Make sure the directory is writable
113
+ if (!is_dir_writeable($this->baseDir)) {
114
+ throw new Exception("Output directory is not a writable; cannot write file.");
115
+ }
116
+ }
117
+
118
+ // Try to create the directory path
119
+ else {
120
+ $oldUmask = umask(0);
121
+ if (!@mkdir($this->baseDir, 0777, true)) {
122
+ throw new Exception("Output directory path could not be created, most likely because of insufficient privileges; cannot write file.");
123
+ }
124
+ umask($oldUmask);
125
+ }
126
+
127
+ }
128
+
129
  /**
130
  * Get the request parameters object
131
  *
172
  public function getTempFilename()
173
  {
174
  if (null === $this->tempFilename) {
175
+ $this->tempFilename = $this->baseDir
176
  . DS
177
  . sprintf(self::FILENAME_TEMP_PATTERN, $this->requestParams->getStore(), $this->uniqueFilename);
178
  }
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;
195
  }
196
+
197
  }
app/code/community/SearchSpring/Manager/controllers/GenerateController.php CHANGED
@@ -27,135 +27,242 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
27
  */
28
  const STORE_DEFAULT = 'default';
29
 
30
- /**
31
- * Default action for updating based on product/category id
32
- *
33
- * Parameters:
34
- * type (required): The type of id passed in. Can be 'product' or 'category'
35
- * ids (required): An array of ids
36
- */
37
- public function indexAction()
38
- {
39
  $request = new SearchSpring_Manager_Request_JSON($this->getRequest());
40
 
41
  $type = $request->getParam('type');
42
  $ids = $request->getParam('ids');
43
 
44
- if (null === $type) {
45
- $this->setJsonResponse(
46
- array(
47
- 'status' => 'error',
48
- 'errorCode' => SearchSpring_ErrorCodes::TYPE_NOT_SET,
49
- 'message' => 'Type must be specified'
50
- ),
51
- 400
52
- );
53
-
54
- return;
55
- }
56
-
57
- if (null === $ids) {
58
- $this->setJsonResponse(
59
- array(
60
- 'status' => 'error',
61
- 'errorCode' => SearchSpring_ErrorCodes::IDS_NOT_SET,
62
- 'message' => 'Ids must be specified'
63
- ),
64
- 400
65
- );
66
-
67
- return;
68
- }
69
 
70
- $requestParams = new SearchSpring_Manager_Entity_RequestParams(
71
- (int)$request->getParam('size', null),
72
- (int)$request->getParam('start', null),
73
- $request->getParam('store', self::STORE_DEFAULT)
74
- );
75
 
76
- $params = array('ids' => $ids);
 
 
 
 
77
 
78
- $generatorFactory = new SearchSpring_Manager_Factory_GeneratorFactory();
79
- $generator = $generatorFactory->make($type, $requestParams, $params);
80
- $message = $generator->generate();
81
 
82
- $this->setJsonResponse($message);
83
- }
 
 
 
 
 
84
 
 
 
85
 
86
  /**
87
  * Generate an xml feed of all products
88
  *
89
  * Parameters:
90
- * filename (required): A unique filename when creating temporary files.
91
- * start (optional): The starting point for fetching products. Defaults to 0.
92
- * count (optional): The number of products to fetch. Defaults to 100.
93
- * store (optional): The store name as a string. Defaults to 'default'
94
  */
95
  public function feedAction()
96
  {
97
- // check file is writable first
98
- if (!is_writable(Mage::getBaseDir())) {
99
- $this->setJsonResponse(
100
- array(
101
- 'status' => 'error',
102
- 'errorCode' => SearchSpring_ErrorCodes::DIR_NOT_WRITABLE,
103
- 'message' => 'Magento base directory is not writable'
104
- ),
105
- 500
106
- );
107
-
108
- return;
109
- }
110
-
111
  $uniqueFilename = $this->getRequest()->getParam('filename');
112
 
113
  if (null === $uniqueFilename) {
114
- $this->setJsonResponse(
115
- array(
116
- 'status' => 'error',
117
- 'errorCode' => SearchSpring_ErrorCodes::FILENAME_NOT_SET,
118
- 'message' => 'Unique filename must be passed in'
119
- ),
120
- 400
121
- );
122
-
123
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
 
126
- $requestParams = new SearchSpring_Manager_Entity_RequestParams(
127
- (int)$this->getRequest()->getParam('count', self::COUNT_DEFAULT),
128
- (int)$this->getRequest()->getParam('start', self::OFFSET_DEFAULT),
129
- $this->getRequest()->getParam('store', self::STORE_DEFAULT)
130
- );
131
 
132
- $params = array('filename' => $uniqueFilename);
 
 
 
133
 
134
- $generatorFactory = new SearchSpring_Manager_Factory_GeneratorFactory();
135
- $generator = $generatorFactory->make(
136
- SearchSpring_Manager_Factory_GeneratorFactory::TYPE_FEED,
137
- $requestParams,
138
- $params
139
- );
140
- $message = $generator->generate();
141
 
142
- $this->setTextResponse($message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- return;
145
  }
146
 
147
- /**
148
- * Set appropriate response variables for a json response
149
- *
150
- * @param array $message The message that should be sent back
151
- * @param int $responseCode The Http response code
152
- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  private function setJsonResponse(array $message, $responseCode = 200)
154
  {
 
 
 
 
 
155
  $this->getResponse()->setHeader('Content-type', 'application/json');
156
  $this->getResponse()->setHttpResponseCode($responseCode);
157
 
158
- $responseBody = json_encode($message);
159
  $this->getResponse()->setBody($responseBody);
160
  }
161
 
@@ -171,4 +278,5 @@ class SearchSpring_Manager_GenerateController extends Mage_Core_Controller_Front
171
  $this->getResponse()->setHttpResponseCode($responseCode);
172
  $this->getResponse()->setBody($message);
173
  }
 
174
  }
27
  */
28
  const STORE_DEFAULT = 'default';
29
 
30
+ /**
31
+ * Default action for updating based on product/category id
32
+ *
33
+ * Parameters:
34
+ * type (required): The type of id passed in. Can be 'product' or 'category'
35
+ * ids (required): An array of ids
36
+ */
37
+ public function indexAction()
38
+ {
39
  $request = new SearchSpring_Manager_Request_JSON($this->getRequest());
40
 
41
  $type = $request->getParam('type');
42
  $ids = $request->getParam('ids');
43
 
44
+ if (null === $type) {
45
+ $this->respondWithError('Type must be specified', SearchSpring_ErrorCodes::TYPE_NOT_SET, 400);
46
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ if (null === $ids) {
49
+ $this->respondWithError('Ids must be specified', SearchSpring_ErrorCodes::IDS_NOT_SET, 400);
50
+ }
 
 
51
 
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);
 
 
59
 
60
+ try {
61
+ $generatorFactory = new SearchSpring_Manager_Factory_GeneratorFactory();
62
+ $generator = $generatorFactory->make($type, $requestParams, $params);
63
+ $message = $generator->generate();
64
+ } catch (Exception $e) {
65
+ $this->handleException($e);
66
+ }
67
 
68
+ $this->setJsonResponse($message);
69
+ }
70
 
71
  /**
72
  * Generate an xml feed of all products
73
  *
74
  * Parameters:
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
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  $uniqueFilename = $this->getRequest()->getParam('filename');
83
 
84
  if (null === $uniqueFilename) {
85
+ $this->respondWithError('Filename must be specified', SearchSpring_ErrorCodes::FILENAME_NOT_SET, 400);
86
+ }
87
+
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);
95
+
96
+ try {
97
+
98
+ $generatorFactory = new SearchSpring_Manager_Factory_GeneratorFactory();
99
+ $generator = $generatorFactory->make(
100
+ SearchSpring_Manager_Factory_GeneratorFactory::TYPE_FEED,
101
+ $requestParams,
102
+ $params
103
+ );
104
+ $message = $generator->generate();
105
+
106
+ } catch (Exception $e) {
107
+ $this->handleException($e);
108
  }
109
 
110
+ $this->setTextResponse($message);
111
+ }
 
 
 
112
 
113
+ public function preDispatch()
114
+ {
115
+ // Do not start standart session
116
+ $this->setFlag('', self::FLAG_NO_START_SESSION, 1);
117
 
118
+ parent::preDispatch();
 
 
 
 
 
 
119
 
120
+ try {
121
+
122
+ // Make sure this access method is enabled
123
+ if (!$this->isEnabled()) {
124
+ $this->_redirect('noroute');
125
+ $this->setFlag('',self::FLAG_NO_DISPATCH,true);
126
+ return $this;
127
+ }
128
+
129
+ // Validate Authentication
130
+ $this->_authenticate();
131
+
132
+ } catch (Exception $e) {
133
+ $this->handleException($e);
134
+ }
135
 
136
+ return $this;
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)) {
153
+ $this->respondWithError(
154
+ 'Authentication Failed: Missing username',
155
+ SearchSpring_ErrorCodes::AUTH_CREDENTIALS_MISSING,
156
+ 401
157
+ );
158
+ }
159
+
160
+ // Make sure auth password was provided
161
+ if(empty($password)) {
162
+ $this->respondWithError(
163
+ 'Authentication Failed: Missing password',
164
+ SearchSpring_ErrorCodes::AUTH_CREDENTIALS_MISSING,
165
+ 401
166
+ );
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,
174
+ 401
175
+ );
176
+ }
177
+
178
+ }
179
+
180
+ /**
181
+ * Action to forward/redirect/fork to when an unhandled
182
+ * exception/error is encountered.
183
+ *
184
+ * @return void
185
+ */
186
+ public function exceptionAction() {
187
+
188
+ // Get Message and Status Code from controller flag
189
+ $message = $this->getFlag('', 'unhandled-exception-message');
190
+ $errorCode = $this->getFlag('', 'unhandled-exception-error-code');
191
+ $responseCode = $this->getFlag('', 'unhandled-exception-http-response-code');
192
+
193
+ // Default Message and Code
194
+ if (!$message) $message = "Unknown Issue";
195
+ if (!$errorCode) $errorCode = "Unknown Issue";
196
+ if (!$responseCode) $responseCode = 500;
197
+
198
+ // Return all issues as json response
199
+ $this->setJsonResponse(
200
+ array(
201
+ 'status' => 'error',
202
+ 'errorCode' => $errorCode,
203
+ 'message' => $message,
204
+ ),
205
+ $responseCode
206
+ );
207
+ }
208
+
209
+ /**
210
+ * Respond with error, and optional error code / http status codes
211
+ *
212
+ * @param Exception $e Any exception that needs to be handled
213
+ *
214
+ * @throws Mage_Core_Controller_Varien_Exception always, to bubble up to parent
215
+ */
216
+ protected function handleException(Exception $e) {
217
+
218
+ // If it's already a controller exception, just bubble it up
219
+ if ($e instanceof Mage_Core_Controller_Varien_Exception) {
220
+ throw $e;
221
+ }
222
+
223
+ // Convert to controller exception, as unknown, 500
224
+ $this->respondWithError($e->getMessage());
225
+ }
226
+
227
+ /**
228
+ * Respond with error, and optional error code / http status codes
229
+ *
230
+ * @param string $message The message that should be sent back
231
+ * @param int $errorCode The SpringSpring Error Code
232
+ * @param int $responseCode The Http response code
233
+ *
234
+ * @throws Mage_Core_Controller_Varien_Exception always, to bubble up to parent
235
+ */
236
+ protected function respondWithError(
237
+ $message,
238
+ $errorCode = SearchSpring_ErrorCodes::UNKNOWN_EXCEPTION,
239
+ $responseCode = 500
240
+ ) {
241
+ $controllerException = new Mage_Core_Controller_Varien_Exception;
242
+ $controllerException->prepareFork('exception');
243
+ $controllerException->prepareFlag('exception', 'unhandled-exception-message', $message);
244
+ $controllerException->prepareFlag('exception', 'unhandled-exception-error-code', $errorCode);
245
+ $controllerException->prepareFlag('exception', 'unhandled-exception-http-response-code', $responseCode);
246
+ throw $controllerException;
247
+ }
248
+
249
+ /**
250
+ * Set appropriate response variables for a json response
251
+ *
252
+ * @param array $message The message that should be sent back
253
+ * @param int $responseCode The Http response code
254
+ */
255
  private function setJsonResponse(array $message, $responseCode = 200)
256
  {
257
+ // If we are requiring authentication, set the correct header as well
258
+ if ($responseCode == 401) {
259
+ $this->getResponse()->setHeader('WWW-Authenticate','Basic realm="SearchSpring Manager"');
260
+ }
261
+
262
  $this->getResponse()->setHeader('Content-type', 'application/json');
263
  $this->getResponse()->setHttpResponseCode($responseCode);
264
 
265
+ $responseBody = Zend_Json::encode($message);
266
  $this->getResponse()->setBody($responseBody);
267
  }
268
 
278
  $this->getResponse()->setHttpResponseCode($responseCode);
279
  $this->getResponse()->setBody($message);
280
  }
281
+
282
  }
app/code/community/SearchSpring/Manager/etc/api2.xml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <api2>
4
+ <resource_groups>
5
+ <searchspring translate="title" module="searchspring_manager">
6
+ <title>SearchSpring Indexing</title>
7
+ <sort_order>10</sort_order>
8
+ </searchspring>
9
+ </resource_groups>
10
+ <resources>
11
+ <searchspring translate="title" module="searchspring_manager">
12
+ <group>searchspring</group>
13
+ <model>searchspring_manager/api2_indexing</model>
14
+ <title>SearchSpring Indexing</title>
15
+ <sort_order>10</sort_order>
16
+ <privileges>
17
+ <admin>
18
+ <!-- We don't actually need these all yet, but in case we do let's set them up for permissions -->
19
+ <create>1</create>
20
+ <retrieve>1</retrieve>
21
+ <update>1</update>
22
+ <delete>1</delete>
23
+ </admin>
24
+ </privileges>
25
+ <attributes>
26
+ <type>Request Type</type>
27
+ <ids>Request Ids</ids>
28
+ <size>Request Size</size>
29
+ <start>Request Start</start>
30
+ <count>Request Feed Count</count>
31
+ <filename>Feed Filename</filename>
32
+ <store>Store</store>
33
+ </attributes>
34
+ <force_attributes>
35
+ <admin>
36
+ <type>Request Type</type>
37
+ <ids>Request Ids</ids>
38
+ <size>Request Size</size>
39
+ <start>Request Start</start>
40
+ <count>Request Feed Count</count>
41
+ <filename>Feed Filename</filename>
42
+ <store>Store</store>
43
+ </admin>
44
+ </force_attributes>
45
+ <routes>
46
+ <route_feed>
47
+ <route>/searchspring/feed</route>
48
+ <action_type>entity</action_type>
49
+ </route_feed>
50
+ <route_index>
51
+ <route>/searchspring/index</route>
52
+ <action_type>collection</action_type>
53
+ </route_index>
54
+ </routes>
55
+ <versions>1</versions>
56
+ </searchspring>
57
+ </resources>
58
+ </api2>
59
+ </config>
app/code/community/SearchSpring/Manager/etc/config.xml CHANGED
@@ -2,7 +2,7 @@
2
  <config>
3
  <modules>
4
  <SearchSpring_Manager>
5
- <version>1.1.0</version>
6
  </SearchSpring_Manager>
7
  </modules>
8
  <global>
@@ -28,14 +28,15 @@
28
  </searchspring_manager>
29
  </helpers>
30
  <events>
31
- <catalog_product_save_after>
 
32
  <observers>
33
  <searchspring_manager_after_save_push_product>
34
  <class>searchspring_manager/observer_productSaveObserver</class>
35
  <method>afterSavePushProduct</method>
36
  </searchspring_manager_after_save_push_product>
37
  </observers>
38
- </catalog_product_save_after>
39
  <catalog_product_delete_before>
40
  <observers>
41
  <searchspring_manager_before_delete_push_product>
@@ -52,14 +53,14 @@
52
  </searchspring_manager_after_delete_push_product>
53
  </observers>
54
  </catalog_product_delete_after_done>
55
- <catalog_category_save_after>
56
  <observers>
57
  <searchspring_manager_after_save_update_product_category>
58
  <class>searchspring_manager/observer_categorySaveObserver</class>
59
  <method>afterSaveUpdateProductCategory</method>
60
  </searchspring_manager_after_save_update_product_category>
61
  </observers>
62
- </catalog_category_save_after>
63
  <category_move>
64
  <observers>
65
  <searchspring_manager_after_move_update_product_category>
@@ -98,13 +99,40 @@
98
  <searchspring_manager>
99
  <setup>
100
  <module>SearchSpring_Manager</module>
101
- <class>Mage_Catalog_Model_Resource_Setup</class>
102
  </setup>
103
  <connection>
104
  <use>core_setup</use>
105
  </connection>
106
  </searchspring_manager>
107
  </resources>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </global>
109
  <frontend>
110
  <routers>
@@ -125,14 +153,58 @@
125
  <!-- </updates> -->
126
  <!-- </layout> -->
127
  </frontend>
 
 
 
 
 
 
 
 
 
 
 
 
128
  <default>
129
  <ssmanager>
130
  <ssmanager_general>
131
  <live_indexing>0</live_indexing>
 
132
  </ssmanager_general>
133
  <ssmanager_catalog>
134
  <enable_categories>0</enable_categories>
135
  </ssmanager_catalog>
 
 
 
 
 
 
136
  </ssmanager>
137
  </default>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  </config>
2
  <config>
3
  <modules>
4
  <SearchSpring_Manager>
5
+ <version>1.9.0</version>
6
  </SearchSpring_Manager>
7
  </modules>
8
  <global>
28
  </searchspring_manager>
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>
35
  <class>searchspring_manager/observer_productSaveObserver</class>
36
  <method>afterSavePushProduct</method>
37
  </searchspring_manager_after_save_push_product>
38
  </observers>
39
+ </catalog_product_save_commit_after>
40
  <catalog_product_delete_before>
41
  <observers>
42
  <searchspring_manager_before_delete_push_product>
53
  </searchspring_manager_after_delete_push_product>
54
  </observers>
55
  </catalog_product_delete_after_done>
56
+ <catalog_category_save_commit_after>
57
  <observers>
58
  <searchspring_manager_after_save_update_product_category>
59
  <class>searchspring_manager/observer_categorySaveObserver</class>
60
  <method>afterSaveUpdateProductCategory</method>
61
  </searchspring_manager_after_save_update_product_category>
62
  </observers>
63
+ </catalog_category_save_commit_after>
64
  <category_move>
65
  <observers>
66
  <searchspring_manager_after_move_update_product_category>
99
  <searchspring_manager>
100
  <setup>
101
  <module>SearchSpring_Manager</module>
 
102
  </setup>
103
  <connection>
104
  <use>core_setup</use>
105
  </connection>
106
  </searchspring_manager>
107
  </resources>
108
+ <searchspring>
109
+ <api_host>https://api-beta.searchspring.net</api_host>
110
+ <!--<api_host>http://23.23.23.2</api_host>-->
111
+ <api_auth>
112
+ <consumer>
113
+ <label>SearchSpring</label>
114
+ </consumer>
115
+ <role>
116
+ <label>SearchSpring</label>
117
+ </role>
118
+ <admin_user>
119
+ <username>searchspring_api</username>
120
+ <firstname>SearchSpring</firstname>
121
+ <lastname>SearchSpring</lastname>
122
+ <email>dev+magentoapi@searchspring.com</email>
123
+ </admin_user>
124
+ </api_auth>
125
+ </searchspring>
126
+ <api2>
127
+ <auth_adapters>
128
+ <oauth_zero_legged>
129
+ <model>searchspring_manager/api2_auth_adapter</model>
130
+ <label>oAuth Zero Legged</label>
131
+ <enabled>1</enabled>
132
+ <order>5</order>
133
+ </oauth_zero_legged>
134
+ </auth_adapters>
135
+ </api2>
136
  </global>
137
  <frontend>
138
  <routers>
153
  <!-- </updates> -->
154
  <!-- </layout> -->
155
  </frontend>
156
+ <adminhtml>
157
+ <events>
158
+ <admin_system_config_changed_section_ssmanager>
159
+ <observers>
160
+ <searchspring_manager_after_config_section_changed>
161
+ <class>searchspring_manager/observer_configObserver</class>
162
+ <method>afterSystemConfigSectionChanged</method>
163
+ </searchspring_manager_after_config_section_changed>
164
+ </observers>
165
+ </admin_system_config_changed_section_ssmanager>
166
+ </events>
167
+ </adminhtml>
168
  <default>
169
  <ssmanager>
170
  <ssmanager_general>
171
  <live_indexing>0</live_indexing>
172
+ <feed_path>media/searchspring/feed/</feed_path>
173
  </ssmanager_general>
174
  <ssmanager_catalog>
175
  <enable_categories>0</enable_categories>
176
  </ssmanager_catalog>
177
+ <ssmanager_images>
178
+ <generate_cache_images>1</generate_cache_images>
179
+ </ssmanager_images>
180
+ <ssmanager_api>
181
+ <authentication_method>simple</authentication_method>
182
+ </ssmanager_api>
183
  </ssmanager>
184
  </default>
185
+
186
+ <global>
187
+ <searchspring>
188
+ <operation>
189
+ <third_party>
190
+
191
+ <!-- Feed Operations for Third Party Modules -->
192
+
193
+ <TBT_Rewards>
194
+ <loyalty>
195
+ <class>SearchSpring_Manager_ThirdParty_TBT_RewardsOperation</class>
196
+ </loyalty>
197
+ </TBT_Rewards>
198
+
199
+ <Amasty_Label>
200
+ <labels>
201
+ <class>SearchSpring_Manager_ThirdParty_Amasty_LabelsOperation</class>
202
+ </labels>
203
+ </Amasty_Label>
204
+
205
+ </third_party>
206
+ </operation>
207
+ </searchspring>
208
+ </global>
209
+
210
  </config>
app/code/community/SearchSpring/Manager/etc/system.xml CHANGED
@@ -16,6 +16,31 @@
16
  <show_in_website>1</show_in_website>
17
  <show_in_store>1</show_in_store>
18
  <groups>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  <ssmanager_general translate="label" module="searchspring_manager">
20
  <label>Indexing Configuration</label>
21
  <frontend_type>text</frontend_type>
@@ -43,12 +68,30 @@
43
  <show_in_website>1</show_in_website>
44
  <show_in_store>1</show_in_store>
45
  </index_zero_price>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  </fields>
47
  </ssmanager_general>
48
  <ssmanager_images translate="label" module="searchspring_manager">
49
  <label>Image Settings</label>
50
  <frontend_type>text</frontend_type>
51
- <sort_order>100</sort_order>
52
  <show_in_default>1</show_in_default>
53
  <show_in_website>1</show_in_website>
54
  <show_in_store>1</show_in_store>
@@ -105,7 +148,7 @@
105
  </fields>
106
  </ssmanager_catalog>
107
  <ssmanager_api translate="label" module="searchspring_manager">
108
- <label>SearchSpring Connection Settings</label>
109
  <frontend_type>select</frontend_type>
110
  <sort_order>300</sort_order>
111
  <show_in_default>1</show_in_default>
@@ -114,7 +157,6 @@
114
  <fields>
115
  <base_url translate="label">
116
  <label>Base URL</label>
117
- <comment>https://example.com</comment>
118
  <frontend_type>text</frontend_type>
119
  <sort_order>15</sort_order>
120
  <show_in_default>1</show_in_default>
@@ -123,7 +165,6 @@
123
  </base_url>
124
  <site_id translate="label">
125
  <label>Site ID</label>
126
- <comment>You can find this in the upper right corner of the SearchSpring Management Console</comment>
127
  <frontend_type>text</frontend_type>
128
  <sort_order>20</sort_order>
129
  <show_in_default>1</show_in_default>
@@ -146,9 +187,44 @@
146
  <show_in_website>1</show_in_website>
147
  <show_in_store>1</show_in_store>
148
  </secret_key>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  </fields>
150
  </ssmanager_api>
151
  </groups>
152
  </ssmanager>
153
  </sections>
154
- </config>
16
  <show_in_website>1</show_in_website>
17
  <show_in_store>1</show_in_store>
18
  <groups>
19
+ <hint>
20
+ <frontend_model>searchspring_manager/adminhtml_system_config_fieldset_hint</frontend_model>
21
+ <sort_order>0</sort_order>
22
+ <show_in_default>1</show_in_default>
23
+ <show_in_website>1</show_in_website>
24
+ <show_in_store>1</show_in_store>
25
+ </hint>
26
+ <setup>
27
+ <frontend_model>searchspring_manager/adminhtml_system_config_fieldset_setup</frontend_model>
28
+ <sort_order>10</sort_order>
29
+ <show_in_default>1</show_in_default>
30
+ <show_in_website>1</show_in_website>
31
+ <show_in_store>1</show_in_store>
32
+ </setup>
33
+ <ssmanager_track translate="label" moudle="searchspring_manager">
34
+ <label>Tracking Data</label>
35
+ <show_in_default>0</show_in_default>
36
+ <show_in_website>0</show_in_website>
37
+ <show_in_store>0</show_in_store>
38
+ <fields>
39
+ <uuid translate="label">
40
+ <label>UUID</label>
41
+ </uuid>
42
+ </fields>
43
+ </ssmanager_track>
44
  <ssmanager_general translate="label" module="searchspring_manager">
45
  <label>Indexing Configuration</label>
46
  <frontend_type>text</frontend_type>
68
  <show_in_website>1</show_in_website>
69
  <show_in_store>1</show_in_store>
70
  </index_zero_price>
71
+ <index_out_of_stock translate="label">
72
+ <label>Display out of stock products</label>
73
+ <frontend_type>select</frontend_type>
74
+ <source_model>adminhtml/system_config_source_yesno</source_model>
75
+ <sort_order>15</sort_order>
76
+ <show_in_default>1</show_in_default>
77
+ <show_in_website>1</show_in_website>
78
+ <show_in_store>1</show_in_store>
79
+ </index_out_of_stock>
80
+ <feed_path translate="label">
81
+ <label>Feed Path</label>
82
+ <comment>Relative to the base Magento installation directory. Must be writable. Recommended: media/searchspring/feed/</comment>
83
+ <frontend_type>text</frontend_type>
84
+ <sort_order>20</sort_order>
85
+ <show_in_default>1</show_in_default>
86
+ <show_in_website>0</show_in_website>
87
+ <show_in_store>0</show_in_store>
88
+ </feed_path>
89
  </fields>
90
  </ssmanager_general>
91
  <ssmanager_images translate="label" module="searchspring_manager">
92
  <label>Image Settings</label>
93
  <frontend_type>text</frontend_type>
94
+ <sort_order>150</sort_order>
95
  <show_in_default>1</show_in_default>
96
  <show_in_website>1</show_in_website>
97
  <show_in_store>1</show_in_store>
148
  </fields>
149
  </ssmanager_catalog>
150
  <ssmanager_api translate="label" module="searchspring_manager">
151
+ <label>SearchSpring Connection Settings (Advanced)</label>
152
  <frontend_type>select</frontend_type>
153
  <sort_order>300</sort_order>
154
  <show_in_default>1</show_in_default>
157
  <fields>
158
  <base_url translate="label">
159
  <label>Base URL</label>
 
160
  <frontend_type>text</frontend_type>
161
  <sort_order>15</sort_order>
162
  <show_in_default>1</show_in_default>
165
  </base_url>
166
  <site_id translate="label">
167
  <label>Site ID</label>
 
168
  <frontend_type>text</frontend_type>
169
  <sort_order>20</sort_order>
170
  <show_in_default>1</show_in_default>
187
  <show_in_website>1</show_in_website>
188
  <show_in_store>1</show_in_store>
189
  </secret_key>
190
+ <authentication_method translate="label">
191
+ <label>Authentication Method</label>
192
+ <frontend_type>text</frontend_type>
193
+ <sort_order>15</sort_order>
194
+ <show_in_default>1</show_in_default>
195
+ <show_in_website>1</show_in_website>
196
+ <show_in_store>1</show_in_store>
197
+ </authentication_method>
198
+ <magento_api_admin_user>
199
+ <label>Magento API - Admin User</label>
200
+ <!-- TODO change type to dropdown -->
201
+ <frontend_type>text</frontend_type>
202
+ <sort_order>50</sort_order>
203
+ <show_in_default>1</show_in_default>
204
+ <show_in_website>1</show_in_website>
205
+ <show_in_store>1</show_in_store>
206
+ </magento_api_admin_user>
207
+ <magento_api_admin_user_role>
208
+ <label>Magento API - Admin User REST Role</label>
209
+ <!-- TODO change type to dropdown -->
210
+ <frontend_type>text</frontend_type>
211
+ <sort_order>50</sort_order>
212
+ <show_in_default>1</show_in_default>
213
+ <show_in_website>1</show_in_website>
214
+ <show_in_store>1</show_in_store>
215
+ </magento_api_admin_user_role>
216
+ <magento_api_oauth_consumer>
217
+ <label>Magento API - oAuth Consumer</label>
218
+ <!-- TODO change type to dropdown -->
219
+ <frontend_type>text</frontend_type>
220
+ <sort_order>50</sort_order>
221
+ <show_in_default>1</show_in_default>
222
+ <show_in_website>1</show_in_website>
223
+ <show_in_store>1</show_in_store>
224
+ </magento_api_oauth_consumer>
225
  </fields>
226
  </ssmanager_api>
227
  </groups>
228
  </ssmanager>
229
  </sections>
230
+ </config>
app/code/community/SearchSpring/Manager/sql/searchspring_manager/mysql4-install-1.0.0.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $this->startSetup();
4
+
5
+ $apiHost = Mage::getConfig()->getNode('global/searchspring/api_host');
6
+
7
+ $config = new Mage_Core_Model_Config();
8
+ $uuid = getUUIDv4();
9
+ $config->saveConfig('ssmanager/ssmanager_track/uuid', $uuid);
10
+
11
+ //file_get_contents($apiHost . "/api/track/ga-track.png?ec=Magento&ea=Install&el=" . $_SERVER['HTTP_HOST'] . "&cid=" . $uuid);
12
+
13
+ $client = new Zend_Http_Client();
14
+ $client->setUri($apiHost . "/api/track/ga-track.json");
15
+ $client->setConfig(array('maxredirects'=>0, 'timeout'=>30));
16
+ $client->setParameterGet(array(
17
+ 'ec' => 'Magento',
18
+ 'ea' => 'Install',
19
+ 'el' => $_SERVER['HTTP_HOST'],
20
+ 'cid' => $uuid
21
+ ));
22
+ $response = $client->request();
23
+
24
+ $this->endSetup();
25
+
26
+ function getUUIDv4()
27
+ {
28
+ return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
29
+ // 32 bits for "time_low"
30
+ mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
31
+
32
+ // 16 bits for "time_mid"
33
+ mt_rand( 0, 0xffff ),
34
+
35
+ // 16 bits for "time_hi_and_version",
36
+ // four most significant bits holds version number 4
37
+ mt_rand( 0, 0x0fff ) | 0x4000,
38
+
39
+ // 16 bits, 8 bits for "clk_seq_hi_res",
40
+ // 8 bits for "clk_seq_low",
41
+ // two most significant bits holds zero and one for variant DCE1.1
42
+ mt_rand( 0, 0x3fff ) | 0x8000,
43
+
44
+ // 48 bits for "node"
45
+ mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
46
+ );
47
+ }
48
+
49
+
app/code/community/SearchSpring/Manager/sql/searchspring_manager/mysql4-upgrade-0.4.0-1.0.0.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+
5
+ $this->startSetup();
6
+
7
+ $apiHost = Mage::getConfig()->getNode('global/searchspring/api_host');
8
+
9
+ $config = new Mage_Core_Model_Config();
10
+ $uuid = getUUIDv4();
11
+ $config->saveConfig('ssmanager/ssmanager_track/uuid', $uuid);
12
+
13
+ $client = new Zend_Http_Client();
14
+ $client->setUri($apiHost . "/api/track/ga-track.json");
15
+ $client->setConfig(array('maxredirects'=>0, 'timeout'=>30));
16
+ $client->setParameterGet(array(
17
+ 'ec' => 'Magento',
18
+ 'ea' => 'Install',
19
+ 'el' => $_SERVER['HTTP_HOST'],
20
+ 'cid' => $uuid
21
+ ));
22
+ $response = $client->request();
23
+
24
+ $this->endSetup();
25
+
26
+ function getUUIDv4()
27
+ {
28
+ return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
29
+ // 32 bits for "time_low"
30
+ mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
31
+
32
+ // 16 bits for "time_mid"
33
+ mt_rand( 0, 0xffff ),
34
+
35
+ // 16 bits for "time_hi_and_version",
36
+ // four most significant bits holds version number 4
37
+ mt_rand( 0, 0x0fff ) | 0x4000,
38
+
39
+ // 16 bits, 8 bits for "clk_seq_hi_res",
40
+ // 8 bits for "clk_seq_low",
41
+ // two most significant bits holds zero and one for variant DCE1.1
42
+ mt_rand( 0, 0x3fff ) | 0x8000,
43
+
44
+ // 48 bits for "node"
45
+ mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
46
+ );
47
+ }
48
+
49
+
app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/hint.phtml ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script type="text/javascript">
2
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
3
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
4
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
5
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
6
+
7
+ ga('create', 'UA-90531-3', 'auto');
8
+ ga('send', 'event', 'Magento', 'Load Settings Web', '<?php echo $_SERVER['HTTP_HOST'] ?>');
9
+ </script>
10
+
11
+ <div class="searchspring-hint">
12
+ <a onClick="ga('send', 'event', 'Magento', 'Click Logo', '<?php echo $_SERVER['HTTP_HOST'] ?>')" class="searchspring-logo" href="http://www.searchspring.com"><img src="//cdn.searchspring.net/magento/logo.png" /></a>
13
+
14
+ <div class="main-text">
15
+ <?php if($this->isSetup()): ?>
16
+ <h3>Thanks!</h3>
17
+ <p>
18
+ <strong>It looks like you're all setup.</strong> If you need any questions or concerns please contact
19
+ <a onClick="ga('send', 'event', 'Magento', 'Click Support', '<?php echo $_SERVER['HTTP_HOST'] ?>')" target="_new" href="https://searchspring.zendesk.com/hc/en-us/requests/new">SearchSpring Support</a>.
20
+ We're here to help.
21
+ </p>
22
+ <?php else: ?>
23
+ <h3>Welcome!</h3>
24
+ <p>
25
+ <strong>It looks like you're not setup yet.</strong> If you're not a SearchSpring customer yet you can
26
+ <a onClick="ga('send', 'event', 'Magento', 'Request Demo', '<?php echo $_SERVER['HTTP_HOST'] ?>')" target="_new" href="http://www.searchspring.com/request-a-demo">request a demo</a>. If
27
+ you need help with setup please contact <a onClick="ga('send', 'event', 'Magento', 'Click Support', '<?php echo $_SERVER['HTTP_HOST'] ?>')" target="_new" href="https://searchspring.zendesk.com/hc/en-us/requests/new">SearchSpring Support</a>.
28
+ </p>
29
+ <?php endif; ?>
30
+ <p>This module links your Magento store to your SearchSpring account. This allows us to update your SearchSpring Search & Navigation in near real-time.</p>
31
+ </div>
32
+
33
+ <div class="bottom-links">
34
+ <a onClick="ga('send', 'event', 'Magento', 'Click Support', '<?php echo $_SERVER['HTTP_HOST'] ?>')" href="https://searchspring.zendesk.com/hc/en-us/requests/new" target="_new">SearchSpring Support</a> |
35
+ <a onClick="ga('send', 'event', 'Magento', 'Click Knowledge Base', '<?php echo $_SERVER['HTTP_HOST'] ?>')" href="https://searchspring.zendesk.com/hc/en-us/categories/200117639-Knowledge-Base" target="_new">Knowledge Base</a>
36
+ <span class="version">
37
+ v<?php echo $this->getVersion() ?>
38
+ </span>
39
+ </div>
40
+
41
+ <div style="clear:both"></div>
42
+ </div>
43
+
44
+
45
+ <style type="text/css">
46
+ .searchspring-hint {
47
+ margin: 5px 0;
48
+ padding: 5px 8px;
49
+ background-color: #e6eef0;
50
+ border:1px solid #cadcdc;
51
+ position: relative;
52
+ }
53
+
54
+ .searchspring-logo {
55
+ float: left;
56
+ margin-right: 15px;
57
+ }
58
+
59
+ .bottom-links {
60
+ position: absolute;
61
+ bottom: 5px;
62
+ right: 8px;
63
+ }
64
+
65
+ .main-text {
66
+ margin: 15px 0;
67
+ }
68
+
69
+ .main-text h3 {
70
+ color: #01214f;
71
+ }
72
+
73
+ .version {
74
+ color: #999999;
75
+ margin-left: 20px;
76
+ }
77
+ </style>
app/design/adminhtml/default/default/template/searchspring/manager/system/config/fieldset/setup.phtml ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="section-config active ss-login">
2
+ <input id="searchspring_new_connection_fl" name="searchspring_new_connection_fl" type="hidden" value="0"/>
3
+ <div class="entry-edit-head ">
4
+ <a>SearchSpring Connection</a>
5
+ </div>
6
+ <fieldset class="ss-login-connected config collapseable" id="ssmanager_ssmanager_setup">
7
+ <legend>SearchSpring Connection</legend>
8
+ <table cellspacing="0" class="form-list">
9
+ <colgroup class="label"></colgroup>
10
+ <colgroup class="value"></colgroup>
11
+ <colgroup class="scope-label"></colgroup>
12
+ <colgroup class=""></colgroup>
13
+ <tbody>
14
+ <tr id="row_ssmanager_ssmanager_setup_connection" class="ss-step1">
15
+ <td class="label">
16
+ <label for="ssmanager_ssmanager_setup_connection">Connection</label>
17
+ </td>
18
+ <td class="value">
19
+ <div class="ss-connect-message">Connected</div>
20
+ <button id="ss-reconnect-button" title="Re-connect" type="button" class="scalable" style=""><span><span><span>Reset Connection</span></span></span></button>
21
+ </td>
22
+ <td class="scope-label"></td>
23
+ <td class=""></td>
24
+ </tr>
25
+ </tbody>
26
+ </table>
27
+ </fieldset>
28
+ <fieldset class="ss-login-not_connected config collapseable" id="ssmanager_ssmanager_setup">
29
+ <legend>SearchSpring Connection</legend>
30
+ <table cellspacing="0" class="form-list">
31
+ <colgroup class="label"></colgroup>
32
+ <colgroup class="value"></colgroup>
33
+ <colgroup></colgroup>
34
+ <tbody>
35
+ <tr>
36
+ <td class="ss-has-account-header" colspan="2">Have a SearchSpring Account?</td>
37
+ <td class="ss-no-account-header ss-no-account">Don't have a SearchSpring Account yet?</td>
38
+ </tr>
39
+ <tr id="row_ssmanager_ssmanager_setup_username" class="ss-step1">
40
+ <td class="label">
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">
48
+ <button id="ss-demo-button" title="Request A Demo" type="button" class="scalable" style=""><span><span><span>Request A Demo</span></span></span></button>
49
+ </a>
50
+ </td>
51
+ </tr>
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>
67
+ <p class="note">If you're not sure your Magento install supports OAuth API use Simple Authentication</p>
68
+ </td>
69
+ <td class="scope-label"></td>
70
+ <td class=""></td>
71
+ </tr>
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>
79
+ <tr id="row_ssmanager_ssmanager_setup_website" class="ss-step2">
80
+ <td class="label">
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">
88
+ <button id="ss-demo-button" title="Request A Demo" type="button" class="scalable" style=""><span><span><span>Request A Demo</span></span></span></button>
89
+ </a>
90
+ </td>
91
+ </tr>
92
+ <tr id="row_ssmanager_ssmanager_setup_feed" class="ss-step2">
93
+ <td class="label">
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>
101
+ <tr id="row_ssmanager_ssmanager_setup_connect" class="ss-step2">
102
+ <td class="label"></td>
103
+ <td class="value">
104
+ <button disabled id="ss-connect-button" title="Connect" type="button" class="scalable disabled" style=""><span><span><span>Connect</span></span></span></button>
105
+ </td>
106
+ <td></td>
107
+ </tr>
108
+ </tbody>
109
+ </table>
110
+ </fieldset>
111
+ </div>
112
+
113
+ <div id="ss-confirm-connect">
114
+ <h2>SearchSpring OAuth</h2>
115
+ <p class="ss-disclaimer">
116
+ In order to keep your data secure SearchSpring will now create a Magento API User, Role and Consumer and send
117
+ the API information to SearchSpring.
118
+ </p>
119
+ <p class="ss-details">
120
+ SearchSpring prides itself in keeping your data safe. We use OAuth to secure your data. This way only
121
+ SearchSpring can access your product data. The API User and Role created only have access to the SearchSpring
122
+ created endpoints to access your data.
123
+ </p>
124
+ <div class="ss-buttons">
125
+ <button id="ss-cancel-button" title="Cancel" type="button" class="scalable back" style=""><span><span><span>Cancel</span></span></span></button>
126
+ <button id="ss-confirm-button" title="OK" type="button" class="scalable save" style=""><span><span><span>Connect</span></span></span></button>
127
+ </div>
128
+ </div>
129
+
130
+ <style type="text/css">
131
+ <?php if(!$this->isSetup()): ?>
132
+ .section-config .ss-login-connected { display: none; }
133
+ <?php else: ?>
134
+ .section-config .ss-login-not_connected { display: none; }
135
+ <?php endif; ?>
136
+ .ss-connect-message { font-weight: bold; color: #007700;}
137
+ .section-config.ss-login .entry-edit-head a { text-decoration: none; }
138
+ .ss-step2 { display: none; }
139
+ .ss-has-account-header { color: #007700; font-weight: bold; text-align: center; width: 50%;}
140
+ .ss-no-account-header { color: #770000; font-weight: bold; width: 50%;}
141
+ .ss-no-account { border-left: 1px solid #CCC; padding-left: 15px; text-align: center;}
142
+ #ss-demo-button { padding: 5px 15px; margin-top: 15px;}
143
+
144
+ #ss-confirm-connect {
145
+ display: none;
146
+ position:absolute;
147
+ top:200px;
148
+ left:50%;
149
+ width:400px; /* adjust as per your needs */
150
+ height:180px; /* adjust as per your needs */
151
+ margin-left:-200px; /* negative half of width above */
152
+ border: 1px solid #d6d6d6;
153
+ background-color: #fafafa;
154
+ z-index: 999;
155
+ padding: 10px;
156
+ -webkit-box-shadow: 3px 3px 5px 0px rgba(61,61,61,.2);
157
+ -moz-box-shadow: 3px 3px 5px 0px rgba(61,61,61,.2);
158
+ box-shadow: 3px 3px 5px 0px rgba(61,61,61,.2);
159
+ }
160
+
161
+ #ss-confirm-connect h2 {
162
+ text-align: center;
163
+ font-size: 16px;
164
+ }
165
+
166
+ #ss-confirm-connect .ss-disclaimer {
167
+ font-weight: bold;
168
+ }
169
+
170
+ #ss-confirm-connect .ss-details {
171
+ margin-top: 10px;
172
+ font-size: 10px;
173
+ color: #777;
174
+ }
175
+
176
+ #ss-cancel-button span {
177
+ padding: 0;
178
+ background-image: none;
179
+ }
180
+
181
+ #ss-confirm-connect .ss-buttons {
182
+ margin-top: 10px;
183
+ text-align: center;
184
+ }
185
+ </style>
186
+
js/searchspring/jquery-1.11.1.min.js ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ /*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
2
+ !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;
3
+ if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e)}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")
4
+ },cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.ActiveXObject&&m(a).on("unload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
js/searchspring/setup.js ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery.noConflict();
2
+
3
+ (function($){
4
+
5
+ $(function() {
6
+ var websites;
7
+
8
+ var configForm = $('#config_edit_form');
9
+
10
+ var reconnectButton = $('#ss-reconnect-button');
11
+
12
+ var usernameField = $('#ssmanager_ssmanager_setup_username');
13
+ var passwordField = $('#ssmanager_ssmanager_setup_password');
14
+ var authSelect = $('#ssmanager_ssmanager_setup_authentication_method');
15
+ var loginButton = $('#ss-login-button');
16
+
17
+ var websiteSelect = $('#ssmanager_ssmanager_setup_website');
18
+ var feedSelect = $('#ssmanager_ssmanager_setup_feed');
19
+ var connectButton = $('#ss-connect-button');
20
+
21
+ var confirmConnectWindow = $('#ss-confirm-connect');
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
+
37
+ loginButton.bind('click', loginSearchSpring);
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();
44
+ }
45
+
46
+ function loginEnter(ev) {
47
+ if (ev.keyCode == 13) {
48
+ loginSearchSpring();
49
+ return false;
50
+ }
51
+ return true;
52
+ }
53
+
54
+ function loginSearchSpring() {
55
+ loginButton.addClass('loading');
56
+ $.ajax({
57
+ url: 'https://api.searchspring.net/api/manage/users/get-feeds.json',
58
+ //url: 'http://23.23.23.2/api/manage/users/get-feeds.json',
59
+ type: 'POST',
60
+ dataType: 'jsonp',
61
+ success : login,
62
+ data : {
63
+ username : usernameField.val(),
64
+ password : passwordField.val()
65
+ }
66
+ });
67
+
68
+ }
69
+
70
+ function login(data, status, req) {
71
+ loginButton.removeClass('loading');
72
+
73
+ if(data.status == 'error') {
74
+ alert(data.message);
75
+ return;
76
+ }
77
+
78
+ websites = data.websites;
79
+
80
+ $('.ss-step1').hide();
81
+ setSiteDropdown();
82
+ $('.ss-step2').show();
83
+ }
84
+
85
+ function setSiteDropdown() {
86
+
87
+ websiteSelect.empty();
88
+ feedSelect.empty().attr('disabled', '').unbind('change');
89
+ disableConnect();
90
+
91
+ var websitesLength = getObjectLength(websites);
92
+ var option;
93
+
94
+ if(websitesLength > 1) {
95
+ option = $('<option />').val(0).text('Select a website');
96
+ websiteSelect.append(option);
97
+ }
98
+
99
+ for(var websiteId in websites) {
100
+ if(websites.hasOwnProperty(websiteId)) {
101
+ var website = websites[websiteId];
102
+ option = $('<option />').val(websiteId).data('siteId', website.siteId).data('secretKey', website.secretKey).text(website.name);
103
+ websiteSelect.append(option);
104
+ }
105
+ }
106
+
107
+ websiteSelect.removeAttr('disabled').bind('change', setFeedDropdown);
108
+
109
+ if(websitesLength == 1) {
110
+ setFeedDropdown();
111
+ }
112
+ }
113
+
114
+ function setFeedDropdown() {
115
+ var feeds = websites[websiteSelect.val()].feeds;
116
+
117
+ feedSelect.empty();
118
+ disableConnect();
119
+
120
+ if(!websites[websiteSelect.val()].secretKey) {
121
+ alert('Secret Key not found. Please contact SearchSpring Support (support@searchspring.com)');
122
+ return;
123
+ }
124
+
125
+ var feedsLength = getObjectLength(feeds);
126
+ var option;
127
+ if(feedsLength > 1) {
128
+ option = $('<option />').val(0).text('Select a feed');
129
+ feedSelect.append(option);
130
+ }
131
+
132
+ for(var feedId in feeds) {
133
+ if(feeds.hasOwnProperty(feedId)) {
134
+ option = $('<option />').val(feedId).text(feeds[feedId]);
135
+ feedSelect.append(option);
136
+ }
137
+ }
138
+
139
+ feedSelect.removeAttr('disabled').bind('change', enableConnect);
140
+
141
+ if(feedsLength == 1) {
142
+ enableConnect();
143
+ }
144
+ }
145
+
146
+ function enableConnect() {
147
+ if(authSelect.val() == 'simple') {
148
+ connectButton.removeClass('disabled').removeAttr('disabled').bind('click', connectSettings);
149
+ } else {
150
+ connectButton.removeClass('disabled').removeAttr('disabled').bind('click', confirmConnect);
151
+ }
152
+ }
153
+
154
+ function disableConnect() {
155
+ connectButton.addClass('disabled').attr('disabled', '').unbind('click');
156
+ }
157
+
158
+ function confirmConnect() {
159
+ confirmConnectWindow.show();
160
+ confirmButton.bind('click', connectSettings);
161
+ cancelButton.bind('click', cancelConnect);
162
+ }
163
+
164
+ function cancelConnect() {
165
+ confirmButton.unbind('click');
166
+ cancelButton.unbind('click');
167
+ confirmConnectWindow.hide();
168
+ }
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());
177
+ authEl.val(authSelect.val());
178
+
179
+ // Send flag to setup new connection
180
+ $('#searchspring_new_connection_fl').val('1');
181
+
182
+ configForm.submit();
183
+ }
184
+
185
+ function getObjectLength(obj) {
186
+ var length = 0;
187
+ for (var key in obj) {
188
+ if (obj.hasOwnProperty(key)) length++;
189
+ }
190
+ return length;
191
+ }
192
+ });
193
+
194
+ })(jQuery);
package.xml CHANGED
@@ -1,2 +1,6 @@
1
  <?xml version="1.0"?>
2
- <package><name>SearchSpring_Manager</name><version>1.1.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>Bringing Live Indexing up to stable state</notes><authors><author><name>SearchSpring Development Team</name><user>searchspring</user><email>info@searchspring.com</email></author></authors><date>2014-09-30</date><time>11:46:04</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="0cd381b82de093b811a2b6b38b5977f0"/><dir name="Manager"><file name="Collection.php" hash="0ebd621892bccd5d59ed3edcafada89b"/><file name="Iterator.php" hash="6f68effbe5452c1a6abd97cbf4b528ee"/><dir name="Block"><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="895d4dc9abc957bd6fbe177df313b0e9"/></dir><dir name="Entity"><file name="IndexingRequestBody.php" hash="673c2bbc190f59c43204f7c155baa912"/><file name="OperationsCollection.php" hash="be19c06bb1be447bd57601e67ac87557"/><file name="RecordsCollection.php" hash="845b1967928f9a2d7fd33cecdfd517a2"/><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="composer.json" hash="6a36df77eee995788585f7a8a535fa27"/><file name="config.xml" hash="dd8330fdccb265d7f17b6f5c8340d2b2"/><file name="readme.md" hash="30d24e1aae89696090dcf22203802ad2"/><file name="system.xml" hash="89909040b5ecbf08dc4eaee9ff702411"/></dir><dir name="Factory"><file name="ApiFactory.php" hash="3532daf1450984a876c952f33ae76c82"/><file name="GeneratorFactory.php" hash="0920740294f9e8502519e2a4adb75ffc"/><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="9459fddaba8a85cd300b1063c3f3e76e"/></dir><dir name="Handler"><file name="ApiErrorHandler.php" hash="bcce8cf5cd9f8b2f2107f004338728fd"/></dir><dir name="Helper"><file name="Data.php" hash="3a1cecacc1b62dc0285c8bb49cc426ba"/></dir><dir name="Model"><file name="Layer.php" hash="54afafde0cff2fcf2de2240bbb6cdb20"/><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><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="Observer"><file name="CategorySaveObserver.php" hash="9df9bb087d1718d699e46f9bb47fcdde"/><file name="LiveIndexer.php" hash="f1a68b949648a16cde42df0eb92cc806"/><file name="ProductSaveObserver.php" hash="5739f9536f160e51b111b43bb06c035a"/></dir></dir><dir name="Operation"><file name="Product.php" hash="d1b3114f1463205340604be47ce9e001"/><file name="ProductOperation.php" hash="5e6d97d2aab321d2204b0cc2a774ff9b"/><dir name="Product"><file name="SetCategories.php" hash="9d064f046551e1e12870013aad0ffcaa"/><file name="SetCoreFields.php" hash="7776df7b6c63f3e18c079944a63558d8"/><file name="SetFields.php" hash="a9eaaac10e7ca9ae1719b09c0cacf6a6"/><file name="SetId.php" hash="792f08d85b399d57afca103e9d0eabd1"/><file name="SetImages.php" hash="964cd029a1a7bbffb21039bffb5ee68f"/><file name="SetOptions.php" hash="3db65aee252c0eb6a5c65eac1b641bfb"/><file name="SetPricing.php" hash="a2bb94df706c1c62c5b1cbe26d8afdf6"/><file name="SetRatings.php" hash="d04ad0e2f5e1820b4b8b94880a83aef7"/></dir></dir><dir name="Provider"><file name="ProductCollectionProvider.php" hash="e639e1667ed0f2bc59f2272ad36d3107"/><dir name="ProductCollection"><file name="FeedProvider.php" hash="79f2700320c0f48342f63075bb82ff95"/><file name="ProductProvider.php" hash="bdb5b820343e060bb7cf2bea807a7607"/></dir></dir><dir name="Request"><file name="JSON.php" hash="d88633c88c49d97abf5e92c7c71fe904"/></dir><dir name="Service"><dir name="SearchSpring"><file name="ApiAdapter.php" hash="819f9d17cf64603b360c66ad1e12ddf0"/><file name="IndexingApiAdapter.php" hash="a5d324c30d82d335f745ec601b5db243"/><file name="SearchApiAdapter.php" hash="4de843465c9eea8646402bcbd289aef9"/></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="78ffa36dd4f52d725178e3cbe716bc24"/></dir></dir><dir name="String"><file name="Sanitizer.php" hash="e89fa5f86714621ea554aea9aa3f796c"/></dir><dir name="Transformer"><file name="ProductCollectionToRecordCollectionTransformer.php" hash="92b10f9ffc3999193d2733ec0dabf276"/></dir><dir name="Validator"><file name="ProductValidator.php" hash="6af19c3c4b696a5e28d8d61aa73cabf4"/></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="56cd0166b08f8df5750ad5c98484603a"/><file name="ResponseWriterParams.php" hash="bf66753a5952a78c0049f69ccaa6e74d"/></dir></dir></dir></dir></dir></dir></dir><dir name="design"><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="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>1.9.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>Speed Optimizations for Feed Regeneration
3
+ Better Root Category skipping logic
4
+ Added Support for Amasty Labels
5
+ Added Support for TBT Rewards (product earnable points)
6
+ Added Support for product qty sales data</notes><authors><author><name>SearchSpring Development Team</name><user>searchspring</user><email>info@searchspring.com</email></author></authors><date>2015-02-05</date><time>10:12:29</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="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="6d87f085ce5b36604a47db301a0b4527"/><file name="readme.md" hash="30d24e1aae89696090dcf22203802ad2"/><file name="system.xml" hash="2d51547a4619f7f81eab4bdba265211e"/></dir><dir name="Factory"><file name="ApiFactory.php" hash="2a0b31683e75cfa4c10bf6476a7346bf"/><file name="GeneratorFactory.php" hash="83662be86412dff1705b32bf2ba76402"/><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="78fddf7a5cae29ea3054309d4c4f46bd"/><file name="Http.php" hash="13d41de08d623892f6526817bf4ee971"/><file name="Oauth.php" hash="b6dd04c07e1322c38b76668b88d7d1e4"/><file name="Product.php" hash="6cdbcdaa30691ff3023b93557018b497"/><file name="Webservice.php" hash="054d23d7db9da73b44bcbad083bd813e"/></dir><dir name="Model"><file name="Layer.php" hash="54afafde0cff2fcf2de2240bbb6cdb20"/><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><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><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="bbd2f4a5506da61201edd0dfe783efe8"/><file name="SetFields.php" hash="4ced933a717710d1a4ad80c533570ff5"/><file name="SetId.php" hash="792f08d85b399d57afca103e9d0eabd1"/><file name="SetImages.php" hash="964cd029a1a7bbffb21039bffb5ee68f"/><file name="SetOptions.php" hash="3db65aee252c0eb6a5c65eac1b641bfb"/><file name="SetPricing.php" hash="a2bb94df706c1c62c5b1cbe26d8afdf6"/><file name="SetRatings.php" hash="d04ad0e2f5e1820b4b8b94880a83aef7"/><file name="SetReport.php" hash="834e7c62388c3e3655c80e2a96020ba2"/></dir></dir><dir name="Provider"><file name="ProductCollectionProvider.php" hash="e639e1667ed0f2bc59f2272ad36d3107"/><dir name="ProductCollection"><file name="FeedProvider.php" hash="50ea56af40e5dc044d6c6cbb4b7c64c3"/><file name="ProductProvider.php" hash="bdb5b820343e060bb7cf2bea807a7607"/></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="a1536050cafdf2ede093cb11530d723c"/></dir><dir name="TBT"><file name="RewardsOperation.php" hash="cd83a97c7231b1ec623b78247f2bd93e"/></dir></dir><dir name="Transformer"><file name="ProductCollectionToRecordCollectionTransformer.php" hash="44bdf6ecf2c06c6a5bc0f96cff427b4e"/></dir><dir name="Validator"><file name="ProductValidator.php" hash="6051f15723e69b05a2fe5fbae62a841d"/></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>