antidot - Version 1.0.2

Version Notes

First stable release including ACP & Search

Download this release

Release Info

Developer BoostMyShop
Extension antidot
Version 1.0.2
Comparing to
See all releases


Version 1.0.2

Files changed (216) hide show
  1. app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Attribute.php +53 -0
  2. app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Boolean.php +28 -0
  3. app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Category.php +41 -0
  4. app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Decimal.php +53 -0
  5. app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Price.php +55 -0
  6. app/code/community/MDN/Antidot/Block/Catalog/Layer/View.php +117 -0
  7. app/code/community/MDN/Antidot/Block/Catalogsearch/Category.php +67 -0
  8. app/code/community/MDN/Antidot/Block/Catalogsearch/Layer.php +101 -0
  9. app/code/community/MDN/Antidot/Block/Catalogsearch/Layer/Filter/Attribute.php +67 -0
  10. app/code/community/MDN/Antidot/Block/Catalogsearch/Result.php +64 -0
  11. app/code/community/MDN/Antidot/Block/Html/Select.php +25 -0
  12. app/code/community/MDN/Antidot/Block/System/Config/Button/PushArticles.php +36 -0
  13. app/code/community/MDN/Antidot/Block/System/Config/Button/PushBrands.php +36 -0
  14. app/code/community/MDN/Antidot/Block/System/Config/Button/PushCategories.php +36 -0
  15. app/code/community/MDN/Antidot/Block/System/Config/Button/PushProducts.php +36 -0
  16. app/code/community/MDN/Antidot/Block/System/Config/Button/RestoreTemplate.php +45 -0
  17. app/code/community/MDN/Antidot/Block/System/Config/Button/ShowXml.php +47 -0
  18. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Additional.php +67 -0
  19. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ArticleAdditional.php +36 -0
  20. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ArticleIdentifier.php +37 -0
  21. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/DefaultSort.php +103 -0
  22. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Facet.php +132 -0
  23. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Identifier.php +69 -0
  24. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ProductAdditionalFacet.php +81 -0
  25. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ProductAdditionalField.php +35 -0
  26. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ProductIdentifier.php +36 -0
  27. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Sort.php +78 -0
  28. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/CategoryAttribute.php +37 -0
  29. app/code/community/MDN/Antidot/Block/System/Config/Form/Field/ProductAttribute.php +37 -0
  30. app/code/community/MDN/Antidot/Block/System/Config/Html/Export.php +59 -0
  31. app/code/community/MDN/Antidot/Block/System/Config/Html/ShowXml.php +62 -0
  32. app/code/community/MDN/Antidot/Block/System/Config/Html/Version.php +26 -0
  33. app/code/community/MDN/Antidot/Helper/Antidot.php +35 -0
  34. app/code/community/MDN/Antidot/Helper/CatalogSearch/Data.php +28 -0
  35. app/code/community/MDN/Antidot/Helper/Compress.php +49 -0
  36. app/code/community/MDN/Antidot/Helper/Data.php +202 -0
  37. app/code/community/MDN/Antidot/Helper/LogExport.php +70 -0
  38. app/code/community/MDN/Antidot/Helper/Url.php +74 -0
  39. app/code/community/MDN/Antidot/Helper/XmlWriter.php +205 -0
  40. app/code/community/MDN/Antidot/Model/Catalog/Layer.php +40 -0
  41. app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Attribute.php +226 -0
  42. app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Boolean.php +48 -0
  43. app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Category.php +139 -0
  44. app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Decimal.php +197 -0
  45. app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Item.php +151 -0
  46. app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Price.php +183 -0
  47. app/code/community/MDN/Antidot/Model/Catalogsearch/Layer.php +66 -0
  48. app/code/community/MDN/Antidot/Model/Catalogsearch/Layer/Filter/Attribute.php +22 -0
  49. app/code/community/MDN/Antidot/Model/Catalogsearch/Resource/Attribute.php +64 -0
  50. app/code/community/MDN/Antidot/Model/Export/Abstract.php +140 -0
  51. app/code/community/MDN/Antidot/Model/Export/Article.php +132 -0
  52. app/code/community/MDN/Antidot/Model/Export/Brand.php +130 -0
  53. app/code/community/MDN/Antidot/Model/Export/Category.php +106 -0
  54. app/code/community/MDN/Antidot/Model/Export/Product.php +818 -0
  55. app/code/community/MDN/Antidot/Model/Observer.php +275 -0
  56. app/code/community/MDN/Antidot/Model/Resource/Advanced.php +19 -0
  57. app/code/community/MDN/Antidot/Model/Resource/Catalog/Product/Collection.php +387 -0
  58. app/code/community/MDN/Antidot/Model/Resource/Engine/Abstract.php +264 -0
  59. app/code/community/MDN/Antidot/Model/Resource/Engine/Antidot.php +373 -0
  60. app/code/community/MDN/Antidot/Model/Search/Abstract.php +56 -0
  61. app/code/community/MDN/Antidot/Model/Search/Search.php +262 -0
  62. app/code/community/MDN/Antidot/Model/Search/Suggest.php +321 -0
  63. app/code/community/MDN/Antidot/Model/System/Config/ArticleAttribute.php +49 -0
  64. app/code/community/MDN/Antidot/Model/System/Config/Backend/Engine.php +30 -0
  65. app/code/community/MDN/Antidot/Model/System/Config/CategoryAttribute.php +78 -0
  66. app/code/community/MDN/Antidot/Model/System/Config/Dir.php +28 -0
  67. app/code/community/MDN/Antidot/Model/System/Config/DisableEnable.php +25 -0
  68. app/code/community/MDN/Antidot/Model/System/Config/Engine.php +32 -0
  69. app/code/community/MDN/Antidot/Model/System/Config/Facet.php +45 -0
  70. app/code/community/MDN/Antidot/Model/System/Config/Number.php +31 -0
  71. app/code/community/MDN/Antidot/Model/System/Config/Options.php +32 -0
  72. app/code/community/MDN/Antidot/Model/System/Config/ProductAttribute.php +103 -0
  73. app/code/community/MDN/Antidot/Model/System/Config/PromoteRedirect.php +32 -0
  74. app/code/community/MDN/Antidot/Model/System/Config/Sort.php +75 -0
  75. app/code/community/MDN/Antidot/Model/System/Config/Suggest/Type.php +55 -0
  76. app/code/community/MDN/Antidot/Model/System/Config/WSStatus.php +31 -0
  77. app/code/community/MDN/Antidot/Model/Transport.php +37 -0
  78. app/code/community/MDN/Antidot/Model/Transport/Abstract.php +19 -0
  79. app/code/community/MDN/Antidot/Model/Transport/File.php +28 -0
  80. app/code/community/MDN/Antidot/Model/Transport/Ftp.php +51 -0
  81. app/code/community/MDN/Antidot/Model/Transport/Http.php +26 -0
  82. app/code/community/MDN/Antidot/Model/Transport/Interface.php +24 -0
  83. app/code/community/MDN/Antidot/controllers/Admin/PushController.php +36 -0
  84. app/code/community/MDN/Antidot/controllers/Front/SearchController.php +35 -0
  85. app/code/community/MDN/Antidot/etc/config.xml +393 -0
  86. app/code/community/MDN/Antidot/etc/system.xml +636 -0
  87. app/code/community/MDN/Antidot/sql/Antidot_setup/mysql4-install-0.9.php +25 -0
  88. app/design/frontend/base/default/template/antidot/catalog/layer/category.phtml +87 -0
  89. app/design/frontend/base/default/template/antidot/catalog/layer/filter.phtml +45 -0
  90. app/design/frontend/base/default/template/antidot/catalogsearch/result.phtml +59 -0
  91. app/design/frontend/default/default/layout/antidot.xml +11 -0
  92. app/design/frontend/default/default/template/antidot/catalogsearch/result/category.phtml +10 -0
  93. app/etc/modules/MDN_Antidot.xml +9 -0
  94. app/locale/de_AT/MDN_Antidot.csv +134 -0
  95. app/locale/de_CH/MDN_Antidot.csv +134 -0
  96. app/locale/de_DE/MDN_Antidot.csv +134 -0
  97. app/locale/es_AR/MDN_Antidot.csv +134 -0
  98. app/locale/es_CL/MDN_Antidot.csv +134 -0
  99. app/locale/es_CO/MDN_Antidot.csv +134 -0
  100. app/locale/es_CR/MDN_Antidot.csv +134 -0
  101. app/locale/es_ES/MDN_Antidot.csv +134 -0
  102. app/locale/es_MX/MDN_Antidot.csv +134 -0
  103. app/locale/es_PA/MDN_Antidot.csv +134 -0
  104. app/locale/es_PE/MDN_Antidot.csv +134 -0
  105. app/locale/es_VE/MDN_Antidot.csv +134 -0
  106. app/locale/fr_CA/MDN_Antidot.csv +134 -0
  107. app/locale/fr_FR/MDN_Antidot.csv +134 -0
  108. js/mdn/antidot/CollapsibleLists.js +152 -0
  109. lib/antidot/.gitignore +5 -0
  110. lib/antidot/AFS/ACP/Makefile +8 -0
  111. lib/antidot/AFS/ACP/TEST/Makefile +8 -0
  112. lib/antidot/AFS/ACP/TEST/acpConnectorTest.php +122 -0
  113. lib/antidot/AFS/ACP/TEST/acpQueryTest.php +209 -0
  114. lib/antidot/AFS/ACP/TEST/acpReplysetHelperTest.php +122 -0
  115. lib/antidot/AFS/ACP/TEST/acpResponseHelperTest.php +163 -0
  116. lib/antidot/AFS/ACP/TEST/acpTest.php +67 -0
  117. lib/antidot/AFS/ACP/afs_acp.php +129 -0
  118. lib/antidot/AFS/ACP/afs_acp_configuration.php +9 -0
  119. lib/antidot/AFS/ACP/afs_acp_connector.php +40 -0
  120. lib/antidot/AFS/ACP/afs_acp_exception.php +35 -0
  121. lib/antidot/AFS/ACP/afs_acp_query.php +23 -0
  122. lib/antidot/AFS/ACP/afs_acp_query_manager.php +58 -0
  123. lib/antidot/AFS/ACP/afs_acp_reply_helper.php +107 -0
  124. lib/antidot/AFS/ACP/afs_acp_replyset_helper.php +118 -0
  125. lib/antidot/AFS/ACP/afs_acp_response_helper.php +126 -0
  126. lib/antidot/AFS/Makefile +8 -0
  127. lib/antidot/AFS/SEARCH/FILTER/Makefile +8 -0
  128. lib/antidot/AFS/SEARCH/FILTER/TEST/Makefile +8 -0
  129. lib/antidot/AFS/SEARCH/FILTER/TEST/filterBuilderTest.php +35 -0
  130. lib/antidot/AFS/SEARCH/FILTER/TEST/filterCombinationTest.php +34 -0
  131. lib/antidot/AFS/SEARCH/FILTER/TEST/filterTest.php +100 -0
  132. lib/antidot/AFS/SEARCH/FILTER/TEST/groupFilterTest.php +44 -0
  133. lib/antidot/AFS/SEARCH/FILTER/afs_combinable_filter.php +27 -0
  134. lib/antidot/AFS/SEARCH/FILTER/afs_combinator_filter.php +108 -0
  135. lib/antidot/AFS/SEARCH/FILTER/afs_filter.php +62 -0
  136. lib/antidot/AFS/SEARCH/FILTER/afs_filter_exception.php +29 -0
  137. lib/antidot/AFS/SEARCH/FILTER/afs_filter_wrapper.php +82 -0
  138. lib/antidot/AFS/SEARCH/FILTER/afs_group_filter.php +41 -0
  139. lib/antidot/AFS/SEARCH/FILTER/afs_operator_filter.php +162 -0
  140. lib/antidot/AFS/SEARCH/FILTER/afs_valued_filter.php +34 -0
  141. lib/antidot/AFS/SEARCH/Makefile +8 -0
  142. lib/antidot/AFS/SEARCH/TEST/Makefile +8 -0
  143. lib/antidot/AFS/SEARCH/TEST/clientDataHelperTest.php +305 -0
  144. lib/antidot/AFS/SEARCH/TEST/clusterHelperTest.php +225 -0
  145. lib/antidot/AFS/SEARCH/TEST/conceptHelperTest.php +295 -0
  146. lib/antidot/AFS/SEARCH/TEST/facetDefaultTest.php +43 -0
  147. lib/antidot/AFS/SEARCH/TEST/facetHelperTest.php +614 -0
  148. lib/antidot/AFS/SEARCH/TEST/facetManagerTest.php +156 -0
  149. lib/antidot/AFS/SEARCH/TEST/facetTest.php +88 -0
  150. lib/antidot/AFS/SEARCH/TEST/facetValuesSortOrderTest.php +38 -0
  151. lib/antidot/AFS/SEARCH/TEST/feedCoderTest.php +147 -0
  152. lib/antidot/AFS/SEARCH/TEST/filterCoderTest.php +187 -0
  153. lib/antidot/AFS/SEARCH/TEST/headerHelperTest.php +81 -0
  154. lib/antidot/AFS/SEARCH/TEST/helperConfigurationTest.php +131 -0
  155. lib/antidot/AFS/SEARCH/TEST/intervalTest.php +137 -0
  156. lib/antidot/AFS/SEARCH/TEST/metaHelperTest.php +122 -0
  157. lib/antidot/AFS/SEARCH/TEST/pagerHelperTest.php +395 -0
  158. lib/antidot/AFS/SEARCH/TEST/promoteReplyHelperTest.php +112 -0
  159. lib/antidot/AFS/SEARCH/TEST/promoteReplysetHelperTest.php +177 -0
  160. lib/antidot/AFS/SEARCH/TEST/queryCoderTest.php +46 -0
  161. lib/antidot/AFS/SEARCH/TEST/queryTest.php +1027 -0
  162. lib/antidot/AFS/SEARCH/TEST/replyHelperTest.php +56 -0
  163. lib/antidot/AFS/SEARCH/TEST/replysetHelperTest.php +1544 -0
  164. lib/antidot/AFS/SEARCH/TEST/responseHelperTest.php +540 -0
  165. lib/antidot/AFS/SEARCH/TEST/searchConnectorTest.php +122 -0
  166. lib/antidot/AFS/SEARCH/TEST/searchQueryManagerTest.php +333 -0
  167. lib/antidot/AFS/SEARCH/TEST/searchTest.php +154 -0
  168. lib/antidot/AFS/SEARCH/TEST/spellcheckHelperTest.php +263 -0
  169. lib/antidot/AFS/SEARCH/TEST/spellcheckTextVisitorTest.php +69 -0
  170. lib/antidot/AFS/SEARCH/TEST/textVisitorTest.php +82 -0
  171. lib/antidot/AFS/SEARCH/afs_base_reply_helper.php +88 -0
  172. lib/antidot/AFS/SEARCH/afs_base_replyset_helper.php +105 -0
  173. lib/antidot/AFS/SEARCH/afs_client_data_exception.php +18 -0
  174. lib/antidot/AFS/SEARCH/afs_client_data_helper.php +441 -0
  175. lib/antidot/AFS/SEARCH/afs_cluster_exception.php +17 -0
  176. lib/antidot/AFS/SEARCH/afs_cluster_helper.php +139 -0
  177. lib/antidot/AFS/SEARCH/afs_coder_base.php +87 -0
  178. lib/antidot/AFS/SEARCH/afs_coder_interface.php +31 -0
  179. lib/antidot/AFS/SEARCH/afs_concept_helper.php +217 -0
  180. lib/antidot/AFS/SEARCH/afs_count.php +24 -0
  181. lib/antidot/AFS/SEARCH/afs_facet.php +197 -0
  182. lib/antidot/AFS/SEARCH/afs_facet_combination.php +26 -0
  183. lib/antidot/AFS/SEARCH/afs_facet_default.php +100 -0
  184. lib/antidot/AFS/SEARCH/afs_facet_exception.php +16 -0
  185. lib/antidot/AFS/SEARCH/afs_facet_helper.php +351 -0
  186. lib/antidot/AFS/SEARCH/afs_facet_helper_retriever.php +53 -0
  187. lib/antidot/AFS/SEARCH/afs_facet_layout.php +33 -0
  188. lib/antidot/AFS/SEARCH/afs_facet_manager.php +313 -0
  189. lib/antidot/AFS/SEARCH/afs_facet_mode.php +125 -0
  190. lib/antidot/AFS/SEARCH/afs_facet_sort.php +29 -0
  191. lib/antidot/AFS/SEARCH/afs_facet_type.php +33 -0
  192. lib/antidot/AFS/SEARCH/afs_facet_value_formatter.php +41 -0
  193. lib/antidot/AFS/SEARCH/afs_facet_values_sort_mode.php +24 -0
  194. lib/antidot/AFS/SEARCH/afs_facet_values_sort_order.php +47 -0
  195. lib/antidot/AFS/SEARCH/afs_feed_coder.php +77 -0
  196. lib/antidot/AFS/SEARCH/afs_filter_coder.php +93 -0
  197. lib/antidot/AFS/SEARCH/afs_header_helper.php +92 -0
  198. lib/antidot/AFS/SEARCH/afs_helper_configuration.php +103 -0
  199. lib/antidot/AFS/SEARCH/afs_interval.php +164 -0
  200. lib/antidot/AFS/SEARCH/afs_interval_exception.php +24 -0
  201. lib/antidot/AFS/SEARCH/afs_meta_helper.php +128 -0
  202. lib/antidot/AFS/SEARCH/afs_pager_helper.php +201 -0
  203. lib/antidot/AFS/SEARCH/afs_producer.php +35 -0
  204. lib/antidot/AFS/SEARCH/afs_promote_reply_helper.php +79 -0
  205. lib/antidot/AFS/SEARCH/afs_promote_replyset_helper.php +27 -0
  206. lib/antidot/AFS/SEARCH/afs_query.php +830 -0
  207. lib/antidot/AFS/SEARCH/afs_query_coder.php +108 -0
  208. lib/antidot/AFS/SEARCH/afs_query_coder_interface.php +46 -0
  209. lib/antidot/AFS/SEARCH/afs_query_object_interface.php +23 -0
  210. lib/antidot/AFS/SEARCH/afs_raw_text_visitor.php +36 -0
  211. lib/antidot/AFS/SEARCH/afs_reply_helper.php +71 -0
  212. lib/antidot/AFS/SEARCH/afs_reply_helper_factory.php +53 -0
  213. lib/antidot/AFS/SEARCH/afs_replyset_helper.php +185 -0
  214. lib/antidot/AFS/SEARCH/afs_response_exception.php +10 -0
  215. lib/antidot/AFS/SEARCH/afs_response_helper.php +302 -0
  216. lib/antidot/AFS/SEARCH/afs_search.php +77 -0
app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Attribute.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalog_Layer_Filter_Attribute extends Mage_Catalog_Block_Layer_Filter_Abstract
17
+ {
18
+ /**
19
+ * Defines specific filter model name.
20
+ *
21
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Attribute
22
+ */
23
+ public function __construct()
24
+ {
25
+ parent::__construct();
26
+ $this->_filterModelName = 'Antidot/catalog_layer_filter_attribute';
27
+ }
28
+
29
+ /**
30
+ * Prepares filter model.
31
+ *
32
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Attribute
33
+ */
34
+ protected function _prepareFilter()
35
+ {
36
+ $this->_filter->setAttributeModel($this->getAttributeModel());
37
+
38
+ return $this;
39
+ }
40
+
41
+ /**
42
+ * Adds facet condition to filter.
43
+ *
44
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Attribute::addFacetCondition()
45
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Attribute
46
+ */
47
+ public function addFacetCondition()
48
+ {
49
+ $this->_filter->addFacetCondition();
50
+
51
+ return $this;
52
+ }
53
+ }
app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Boolean.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalog_Layer_Filter_Boolean extends MDN_Antidot_Block_Catalog_Layer_Filter_Attribute
17
+ {
18
+ /**
19
+ * Defines specific filter model name.
20
+ *
21
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Boolean
22
+ */
23
+ public function __construct()
24
+ {
25
+ parent::__construct();
26
+ $this->_filterModelName = 'Antidot/catalog_layer_filter_boolean';
27
+ }
28
+ }
app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Category.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalog_Layer_Filter_Category extends Mage_Catalog_Block_Layer_Filter_Abstract
17
+ {
18
+ /**
19
+ * Defines specific filter model name.
20
+ *
21
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Category
22
+ */
23
+ public function __construct()
24
+ {
25
+ parent::__construct();
26
+ $this->_filterModelName = 'Antidot/catalog_layer_filter_category';
27
+ }
28
+
29
+ /**
30
+ * Adds facet condition to filter.
31
+ *
32
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Category::addFacetCondition()
33
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Attribute
34
+ */
35
+ public function addFacetCondition()
36
+ {
37
+ $this->_filter->addFacetCondition();
38
+
39
+ return $this;
40
+ }
41
+ }
app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Decimal.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalog_Layer_Filter_Decimal extends Mage_Catalog_Block_Layer_Filter_Abstract
17
+ {
18
+ /**
19
+ * Defines specific filter model name.
20
+ *
21
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Decimal
22
+ */
23
+ public function __construct()
24
+ {
25
+ parent::__construct();
26
+ $this->_filterModelName = 'Antidot/catalog_layer_filter_decimal';
27
+ }
28
+
29
+ /**
30
+ * Prepares filter model.
31
+ *
32
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Decimal
33
+ */
34
+ protected function _prepareFilter()
35
+ {
36
+ $this->_filter->setAttributeModel($this->getAttributeModel());
37
+
38
+ return $this;
39
+ }
40
+
41
+ /**
42
+ * Adds facet condition to filter.
43
+ *
44
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Decimal::addFacetCondition()
45
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Decimal
46
+ */
47
+ public function addFacetCondition()
48
+ {
49
+ $this->_filter->addFacetCondition();
50
+
51
+ return $this;
52
+ }
53
+ }
app/code/community/MDN/Antidot/Block/Catalog/Layer/Filter/Price.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalog_Layer_Filter_Price extends Mage_Catalog_Block_Layer_Filter_Abstract
17
+ {
18
+ /**
19
+ * Defines specific filter model name.
20
+ *
21
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Price
22
+ */
23
+ public function __construct()
24
+ {
25
+ parent::__construct();
26
+ $this->_filterModelName = 'Antidot/catalog_layer_filter_price';
27
+ }
28
+
29
+ /**
30
+ * Prepares filter model.
31
+ *
32
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Price
33
+ */
34
+ protected function _prepareFilter()
35
+ {
36
+ $this->_filter->setAttributeModel($this->getAttributeModel());
37
+
38
+ return $this;
39
+ }
40
+
41
+ /**
42
+ * Adds facet condition to filter.
43
+ *
44
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Price::addFacetCondition()
45
+ * @return MDN_Antidot_Block_Catalog_Layer_Filter_Price
46
+ */
47
+ public function addFacetCondition()
48
+ {
49
+ if (!$this->getRequest()->getParam('price')) {
50
+ $this->_filter->addFacetCondition();
51
+ }
52
+
53
+ return $this;
54
+ }
55
+ }
app/code/community/MDN/Antidot/Block/Catalog/Layer/View.php ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalog_Layer_View extends Mage_Catalog_Block_Layer_View
17
+ {
18
+ /**
19
+ * Boolean block name.
20
+ *
21
+ * @var string
22
+ */
23
+ protected $_booleanFilterBlockName;
24
+
25
+ /**
26
+ * Registers current layer in registry.
27
+ *
28
+ * @see Mage_Catalog_Block_Product_List::getLayer()
29
+ */
30
+ protected function _construct()
31
+ {
32
+ parent::_construct();
33
+ Mage::register('current_layer', $this->getLayer());
34
+ }
35
+
36
+ /**
37
+ * Modifies default block names to specific ones if engine is active.
38
+ */
39
+ protected function _initBlocks()
40
+ {
41
+ parent::_initBlocks();
42
+
43
+ if (Mage::helper('Antidot')->isActiveEngine()) {
44
+ $this->_categoryBlockName = 'Antidot/catalog_layer_filter_category';
45
+ $this->_attributeFilterBlockName = 'Antidot/catalog_layer_filter_attribute';
46
+ $this->_priceFilterBlockName = 'Antidot/catalog_layer_filter_price';
47
+ $this->_decimalFilterBlockName = 'Antidot/catalog_layer_filter_decimal';
48
+ $this->_booleanFilterBlockName = 'Antidot/catalog_layer_filter_boolean';
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Prepares layout if engine is active.
54
+ * Difference between parent method is addFacetCondition() call on each created block.
55
+ *
56
+ * @return MDN_Antidot_Block_Catalog_Layer_View
57
+ */
58
+ protected function _prepareLayout()
59
+ {
60
+ $helper = Mage::helper('Antidot');
61
+ if (!$helper->isActiveEngine()) {
62
+ parent::_prepareLayout();
63
+ } else {
64
+ $stateBlock = $this->getLayout()->createBlock($this->_stateBlockName)
65
+ ->setLayer($this->getLayer());
66
+
67
+ $categoryBlock = $this->getLayout()->createBlock($this->_categoryBlockName)
68
+ ->setLayer($this->getLayer())
69
+ ->init();
70
+
71
+ $this->setChild('layer_state', $stateBlock);
72
+ $this->setChild('category_filter', $categoryBlock->addFacetCondition());
73
+
74
+ $filterableAttributes = $this->_getFilterableAttributes();
75
+ $filters = array();
76
+ foreach ($filterableAttributes as $attribute) {
77
+ if ($attribute->getAttributeCode() == 'price') {
78
+ $filterBlockName = $this->_priceFilterBlockName;
79
+ } elseif ($attribute->getBackendType() == 'decimal') {
80
+ $filterBlockName = $this->_decimalFilterBlockName;
81
+ } elseif ($attribute->getSourceModel() == 'eav/entity_attribute_source_boolean') {
82
+ $filterBlockName = $this->_booleanFilterBlockName;
83
+ } else {
84
+ $filterBlockName = $this->_attributeFilterBlockName;
85
+ }
86
+
87
+ $filters[$attribute->getAttributeCode() . '_filter'] = $this->getLayout()->createBlock($filterBlockName)
88
+ ->setLayer($this->getLayer())
89
+ ->setAttributeModel($attribute)
90
+ ->init();
91
+ }
92
+
93
+ foreach ($filters as $filterName => $block) {
94
+ $this->setChild($filterName, $block->addFacetCondition());
95
+ }
96
+
97
+ $this->getLayer()->apply();
98
+ }
99
+
100
+ return $this;
101
+ }
102
+
103
+ /**
104
+ * Returns current catalog layer.
105
+ *
106
+ * @return MDN_Antidot_Model_Catalog_Layer|Mage_Catalog_Model_Layer
107
+ */
108
+ public function getLayer()
109
+ {
110
+ $helper = Mage::helper('Antidot');
111
+ if ($helper->isActiveEngine()) {
112
+ return Mage::getSingleton('Antidot/catalog_layer');
113
+ }
114
+
115
+ return parent::getLayer();
116
+ }
117
+ }
app/code/community/MDN/Antidot/Block/Catalogsearch/Category.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalogsearch_Category extends Mage_Core_Block_Template
17
+ {
18
+ protected $_categories = null;
19
+
20
+ /**
21
+ * Return categories
22
+ * @return type
23
+ */
24
+ public function getCategories()
25
+ {
26
+ if ($this->_categories == null)
27
+ {
28
+ $this->loadCategories();
29
+ }
30
+ return $this->_categories;
31
+ }
32
+
33
+ /**
34
+ *
35
+ * @param type $cat
36
+ */
37
+ public function getCategoryUrl($cat)
38
+ {
39
+ return $cat->getUrl();
40
+ }
41
+
42
+ /**
43
+ * Load category based on antidot results
44
+ */
45
+ protected function loadCategories()
46
+ {
47
+ $categoryIds = $this->getLayer()->getProductCollection()->getCategoryIds();
48
+
49
+ $this->_categories = Mage::getModel('catalog/category')->getCollection()->addAttributeToSelect('*')->addFieldToFilter('entity_id', array('in' => $categoryIds));
50
+ }
51
+
52
+ /**
53
+ * Returns current catalog layer.
54
+ *
55
+ * @return MDN_Antidot_Model_Catalogsearch_Layer|Mage_Catalog_Model_Layer
56
+ */
57
+ public function getLayer()
58
+ {
59
+ $helper = Mage::helper('Antidot');
60
+ if ($helper->isActiveEngine()) {
61
+ return Mage::getSingleton('Antidot/catalogsearch_layer');
62
+ }
63
+
64
+ return parent::getLayer();
65
+ }
66
+
67
+ }
app/code/community/MDN/Antidot/Block/Catalogsearch/Layer.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalogsearch_Layer extends Mage_CatalogSearch_Block_Layer
17
+ {
18
+ /**
19
+ * Boolean block name.
20
+ *
21
+ * @var string
22
+ */
23
+ protected $_booleanFilterBlockName;
24
+
25
+ /**
26
+ * Modifies default block names to specific ones if engine is active.
27
+ */
28
+ protected function _initBlocks()
29
+ {
30
+ parent::_initBlocks();
31
+
32
+ if (Mage::helper('Antidot')->isActiveEngine()) {
33
+ $this->_categoryBlockName = 'Antidot/catalog_layer_filter_category';
34
+ $this->_attributeFilterBlockName = 'Antidot/catalogsearch_layer_filter_attribute';
35
+ $this->_priceFilterBlockName = 'Antidot/catalog_layer_filter_price';
36
+ $this->_decimalFilterBlockName = 'Antidot/catalog_layer_filter_decimal';
37
+ $this->_booleanFilterBlockName = 'Antidot/catalog_layer_filter_boolean';
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Prepares layout if engine is active.
43
+ * Difference between parent method is addFacetCondition() call on each created block.
44
+ *
45
+ * @return MDN_Antidot_Block_Catalogsearch_Layer
46
+ */
47
+ protected function _prepareLayout()
48
+ {
49
+ $helper = Mage::helper('Antidot');
50
+ if (!$helper->isActiveEngine()) {
51
+ parent::_prepareLayout();
52
+ } else {
53
+ $stateBlock = $this->getLayout()->createBlock($this->_stateBlockName)
54
+ ->setLayer($this->getLayer());
55
+
56
+ $this->setChild('layer_state', $stateBlock);
57
+
58
+ $filterableAttributes = $this->_getFilterableAttributes();
59
+ $filters = array();
60
+ foreach ($filterableAttributes as $attribute) {
61
+ $filters[$attribute->getAttributeCode() . '_filter'] = $this->getLayout()->createBlock($this->_attributeFilterBlockName)
62
+ ->setLayer($this->getLayer())
63
+ ->setAttributeModel($attribute)
64
+ ->init();
65
+ }
66
+
67
+ foreach ($filters as $filterName => $block) {
68
+ $this->setChild($filterName, $block->addFacetCondition());
69
+ }
70
+
71
+ $this->getLayer()->apply();
72
+ }
73
+
74
+ return $this;
75
+ }
76
+
77
+ /**
78
+ * Checks display availability of layer block.
79
+ *
80
+ * @return bool
81
+ */
82
+ public function canShowBlock()
83
+ {
84
+ return ($this->canShowOptions() || count($this->getLayer()->getState()->getFilters()));
85
+ }
86
+
87
+ /**
88
+ * Returns current catalog layer.
89
+ *
90
+ * @return MDN_Antidot_Model_Catalogsearch_Layer|Mage_Catalog_Model_Layer
91
+ */
92
+ public function getLayer()
93
+ {
94
+ $helper = Mage::helper('Antidot');
95
+ if ($helper->isActiveEngine()) {
96
+ return Mage::getSingleton('Antidot/catalogsearch_layer');
97
+ }
98
+
99
+ return parent::getLayer();
100
+ }
101
+ }
app/code/community/MDN/Antidot/Block/Catalogsearch/Layer/Filter/Attribute.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Catalogsearch_Layer_Filter_Attribute extends Mage_Catalog_Block_Layer_Filter_Abstract
17
+ {
18
+ /**
19
+ * Defines specific filter model name.
20
+ *
21
+ * @see MDN_Antidot_Model_Catalogsearch_Layer_Filter_Attribute
22
+ */
23
+ public function __construct()
24
+ {
25
+ parent::__construct();
26
+ $this->_filterModelName = 'Antidot/catalogsearch_layer_filter_attribute';
27
+ }
28
+
29
+ /**
30
+ * Prepares filter model.
31
+ *
32
+ * @return MDN_Antidot_Block_Catalogsearch_Layer_Filter_Attribute
33
+ */
34
+ protected function _prepareFilter()
35
+ {
36
+ $this->_filter->setAttributeModel($this->getAttributeModel());
37
+
38
+ return $this;
39
+ }
40
+
41
+ /**
42
+ * Adds facet condition to filter.
43
+ *
44
+ * @see MDN_Antidot_Model_Catalog_Layer_Filter_Attribute::addFacetCondition()
45
+ * @return MDN_Antidot_Block_Catalogsearch_Layer_Filter_Attribute
46
+ */
47
+ public function addFacetCondition()
48
+ {
49
+ $this->_filter->addFacetCondition();
50
+
51
+ return $this;
52
+ }
53
+
54
+ /**
55
+ * {@inherit}
56
+ */
57
+ public function getHtml()
58
+ {
59
+ if($this->_filter->getCode() === 'classification' && !Mage::helper('Antidot')->hasFacetMultiple($this->_filter->getCode())) {
60
+ $this->setTemplate('antidot/catalog/layer/category.phtml');
61
+ } elseif(Mage::helper('Antidot')->hasFacetMultiple($this->_filter->getCode())) {
62
+ $this->setTemplate('antidot/catalog/layer/filter.phtml');
63
+ }
64
+
65
+ return parent::_toHtml();
66
+ }
67
+ }
app/code/community/MDN/Antidot/Block/Catalogsearch/Result.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_CatalogSearch_Result extends Mage_CatalogSearch_Block_Result
17
+ {
18
+ /**
19
+ * Set default order
20
+ *
21
+ * @return Mage_CatalogSearch_Block_Result
22
+ */
23
+ public function setListOrders()
24
+ {
25
+ $config = Mage::getStoreConfig('antidot/engine/default_sort');
26
+ $defaultSort = current(unserialize($config));
27
+ list($field) = explode('|', $defaultSort['field']);
28
+ $this->getListBlock()
29
+ ->setAvailableOrders($this->getAvailableOrders())
30
+ ->setDefaultDirection($defaultSort['dir'])
31
+ ->setSortBy($field);
32
+
33
+ return $this;
34
+ }
35
+
36
+ /**
37
+ * Return available list orders
38
+ *
39
+ * @return array
40
+ */
41
+ protected function getAvailableOrders()
42
+ {
43
+ $config = Mage::getStoreConfig('antidot/engine/sortable');
44
+ $availableSortable = unserialize($config);
45
+
46
+ $availableOrders = array();
47
+ foreach($availableSortable as $sort) {
48
+ list($field, $label) = explode('|', $sort['sort']);
49
+ $availableOrders[$field] = $label;
50
+ }
51
+
52
+ return $availableOrders;
53
+ }
54
+
55
+ /**
56
+ * {@inherit}
57
+ */
58
+ public function _toHtml()
59
+ {
60
+ $this->setTemplate('antidot/catalogsearch/result.phtml');
61
+
62
+ return parent::_toHtml();
63
+ }
64
+ }
app/code/community/MDN/Antidot/Block/Html/Select.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_Html_Select extends Mage_Core_Block_Html_Select
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ protected function _toHtml()
22
+ {
23
+ return parent::_toHtml();
24
+ }
25
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/PushArticles.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_PushArticles extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $this->setElement($element);
25
+ $url = $this->getUrl('Antidot/Admin_Push/Article');
26
+
27
+ $html = $this->getLayout()->createBlock('adminhtml/widget_button')
28
+ ->setType('button')
29
+ ->setClass('scalable')
30
+ ->setLabel(Mage::helper('Antidot')->__('Push'))
31
+ ->setOnClick("setLocation('$url')")
32
+ ->toHtml();
33
+
34
+ return $html;
35
+ }
36
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/PushBrands.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_PushBrands extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $this->setElement($element);
25
+ $url = $this->getUrl('Antidot/Admin_Push/Brand');
26
+
27
+ $html = $this->getLayout()->createBlock('adminhtml/widget_button')
28
+ ->setType('button')
29
+ ->setClass('scalable')
30
+ ->setLabel(Mage::helper('Antidot')->__('Push'))
31
+ ->setOnClick("setLocation('$url')")
32
+ ->toHtml();
33
+
34
+ return $html;
35
+ }
36
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/PushCategories.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_PushCategories extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $this->setElement($element);
25
+ $url = $this->getUrl('Antidot/Admin_Push/Category');
26
+
27
+ $html = $this->getLayout()->createBlock('adminhtml/widget_button')
28
+ ->setType('button')
29
+ ->setClass('scalable')
30
+ ->setLabel(Mage::helper('Antidot')->__('Push'))
31
+ ->setOnClick("setLocation('$url')")
32
+ ->toHtml();
33
+
34
+ return $html;
35
+ }
36
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/PushProducts.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_PushProducts extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $this->setElement($element);
25
+ $url = $this->getUrl('Antidot/Admin_Push/Product');
26
+
27
+ $html = $this->getLayout()->createBlock('adminhtml/widget_button')
28
+ ->setType('button')
29
+ ->setClass('scalable')
30
+ ->setLabel(Mage::helper('Antidot')->__('Push'))
31
+ ->setOnClick("setLocation('$url')")
32
+ ->toHtml();
33
+
34
+ return $html;
35
+ }
36
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/RestoreTemplate.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_RestoreTemplate extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $this->setElement($element);
25
+
26
+ $xslt = '';
27
+ $configFile = dirname(__FILE__).'/../../../../etc/config.xml';
28
+ if($sxe = @simplexml_load_file($configFile)) {
29
+ if($template = $sxe->xpath('//template')) {
30
+ $search = array('"', "'", "\n");
31
+ $replace = array('\"', "\'", '\n');
32
+ $xslt = str_replace($search, $replace, trim(htmlentities((string)$template[0])));
33
+ }
34
+ }
35
+
36
+ $html = $this->getLayout()->createBlock('adminhtml/widget_button')
37
+ ->setType('button')
38
+ ->setClass('scalable')
39
+ ->setLabel(Mage::helper('Antidot')->__('Restore the default template'))
40
+ ->setOnClick("$('antidot_suggest_template').setValue('".$xslt."'); return false;")
41
+ ->toHtml();
42
+
43
+ return $html;
44
+ }
45
+ }
app/code/community/MDN/Antidot/Block/System/Config/Button/ShowXml.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Button_ShowXml extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $this->setElement($element);
25
+
26
+ $suggestXml = '';
27
+ $configFile = dirname(__FILE__).'/../../../../etc/config.xml';
28
+ if($sxe = @simplexml_load_file($configFile)) {
29
+ if($suggestXml = $sxe->xpath('//suggest_xml')) {
30
+ $suggestXml = str_replace('"', '\"', trim(htmlentities((string)$suggestXml[0])));
31
+ $suggestXml = str_replace("\n", '\n', $suggestXml);
32
+ $suggestXml = str_replace(" ", '&nbsp;&nbsp;&nbsp;&nbsp;', $suggestXml);
33
+ }
34
+ }
35
+
36
+ return $this->getLayout()->createBlock('adminhtml/widget_button')
37
+ ->setType('button')
38
+ ->setClass('scalable')
39
+ ->setLabel(Mage::helper('Antidot')->__('Display XML'))
40
+ ->setOnClick(
41
+ "var w = window.open('', '', 'width=400,height=400,resizeable,scrollbars');"
42
+ ."w.document.write('".$suggestXml."');"
43
+ ."return false;"
44
+ )
45
+ ->toHtml();
46
+ }
47
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Additional.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_Additional extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract
17
+ {
18
+ protected $_valueRenderer;
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ protected function _prepareToRender()
24
+ {
25
+ $this->_valueRenderer = null;
26
+
27
+ $this->addColumn('value', array('label' => Mage::helper('Antidot')->__('Attribute')));
28
+
29
+ // Disables "Add after" button
30
+ $this->_addAfter = false;
31
+ $this->_addButtonLabel = Mage::helper('Antidot')->__('Add a field');
32
+ }
33
+
34
+ /**
35
+ * {@inherit}
36
+ */
37
+ protected function _renderCellTemplate($columnName)
38
+ {
39
+ return parent::_renderCellTemplate($columnName);
40
+ }
41
+
42
+ /**
43
+ * {@inherit}
44
+ */
45
+ protected function _getValueRenderer()
46
+ {
47
+ if (!$this->_valueRenderer) {
48
+ $this->_valueRenderer = $this->getLayout()
49
+ ->createBlock('Antidot/Html_Select')
50
+ ->setIsRenderToJsTemplate(true);
51
+ }
52
+ return $this->_valueRenderer;
53
+ }
54
+
55
+ /**
56
+ * Assign extra parameters to row
57
+ *
58
+ * @param Varien_Object $row
59
+ */
60
+ protected function _prepareArrayRow(Varien_Object $row)
61
+ {
62
+ $row->setData(
63
+ 'option_extra_attr_'.$this->_getValueRenderer()->calcOptionHash($row->getData('value')),
64
+ 'selected="selected"'
65
+ );
66
+ }
67
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ArticleAdditional.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_ArticleAdditional extends MDN_Antidot_Block_System_Config_Form_Field_Array_Additional
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _renderCellTemplate($columnName)
23
+ {
24
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
25
+ switch($columnName) {
26
+ case 'value':
27
+ return $this->_getValueRenderer()
28
+ ->setName($inputName)
29
+ ->setTitle($columnName)
30
+ ->setOptions(Mage::getModel("Antidot/System_Config_ArticleAttribute")->toOptionArray(null))
31
+ ->toHtml();
32
+ }
33
+
34
+ return parent::_renderCellTemplate($columnName);
35
+ }
36
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ArticleIdentifier.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_ArticleIdentifier extends MDN_Antidot_Block_System_Config_Form_Field_Array_Identifier
17
+ {
18
+
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ protected function _renderCellTemplate($columnName)
24
+ {
25
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
26
+ switch($columnName) {
27
+ case 'value':
28
+ return $this->_getValueRenderer()
29
+ ->setName($inputName)
30
+ ->setTitle($columnName)
31
+ ->setOptions(Mage::getModel("Antidot/System_Config_ArticleAttribute")->toOptionArray(null))
32
+ ->toHtml();
33
+ }
34
+
35
+ return parent::_renderCellTemplate($columnName);
36
+ }
37
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/DefaultSort.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_DefaultSort extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract
17
+ {
18
+ protected $_fieldRenderer;
19
+ protected $_dirRenderer;
20
+
21
+ /**
22
+ * {@inherit}
23
+ */
24
+ protected function _prepareToRender()
25
+ {
26
+ $this->_fieldRenderer = null;
27
+ $this->_dirRenderer = null;
28
+
29
+ $this->addColumn('field', array('label' => Mage::helper('Antidot')->__('Sortable')));
30
+ $this->addColumn('dir', array('label' => Mage::helper('Antidot')->__('Direction')));
31
+
32
+ $this->_addAfter = false;
33
+ $this->_add = false;
34
+ }
35
+
36
+ /**
37
+ * {@inherit}
38
+ */
39
+ protected function _renderCellTemplate($columnName)
40
+ {
41
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
42
+ switch($columnName) {
43
+ case 'field':
44
+ return $this->_getFieldRenderer()
45
+ ->setName($inputName)
46
+ ->setTitle($columnName)
47
+ ->setOptions(Mage::getModel("Antidot/System_Config_Sort")->toOptionArray())
48
+ ->toHtml();
49
+ case 'dir':
50
+ return $this->_getDirRenderer()
51
+ ->setName($inputName)
52
+ ->setTitle($columnName)
53
+ ->setOptions(Mage::getModel("Antidot/System_Config_Dir")->toOptionArray())
54
+ ->toHtml();
55
+ }
56
+
57
+ return parent::_renderCellTemplate($columnName);
58
+ }
59
+
60
+ /**
61
+ * {@inherit}
62
+ */
63
+ protected function _getFieldRenderer()
64
+ {
65
+ if (!$this->_fieldRenderer) {
66
+ $this->_fieldRenderer = $this->getLayout()
67
+ ->createBlock('Antidot/Html_Select')
68
+ ->setIsRenderToJsTemplate(true);
69
+ }
70
+ return $this->_fieldRenderer;
71
+ }
72
+
73
+ /**
74
+ * {@inherit}
75
+ */
76
+ protected function _getDirRenderer()
77
+ {
78
+ if (!$this->_dirRenderer) {
79
+ $this->_dirRenderer = $this->getLayout()
80
+ ->createBlock('Antidot/Html_Select')
81
+ ->setIsRenderToJsTemplate(true);
82
+ }
83
+ return $this->_dirRenderer;
84
+ }
85
+
86
+ /**
87
+ * Assign extra parameters to row
88
+ *
89
+ * @param Varien_Object $row
90
+ */
91
+ protected function _prepareArrayRow(Varien_Object $row)
92
+ {
93
+ $row->setData(
94
+ 'option_extra_attr_'.$this->_getFieldRenderer()->calcOptionHash($row->getData('field')),
95
+ 'selected="selected"'
96
+ );
97
+
98
+ $row->setData(
99
+ 'option_extra_attr_'.$this->_getDirRenderer()->calcOptionHash($row->getData('dir')),
100
+ 'selected="selected"'
101
+ );
102
+ }
103
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Facet.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_Facet extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract
17
+ {
18
+ protected $_facetRenderer;
19
+ protected $_orderRenderer;
20
+ protected $_multipleRenderer;
21
+
22
+ /**
23
+ * {@inherit}
24
+ */
25
+ protected function _prepareToRender()
26
+ {
27
+ $this->_facetRenderer = null;
28
+ $this->_orderRenderer = null;
29
+ $this->_multipleRenderer = null;
30
+
31
+ $this->addColumn('facet', array('label' => Mage::helper('Antidot')->__('Facet')));
32
+ $this->addColumn('order', array('label' => Mage::helper('Antidot')->__('Sort')));
33
+ $this->addColumn('multiple', array('label' => Mage::helper('Antidot')->__('Multiple selections')));
34
+
35
+ // Disables "Add after" button
36
+ $this->_addAfter = false;
37
+ $this->_addButtonLabel = Mage::helper('Antidot')->__('Add a field');
38
+ }
39
+
40
+ /**
41
+ * {@inherit}
42
+ */
43
+ protected function _renderCellTemplate($columnName)
44
+ {
45
+ $inputName = $this->getElement()->getName() . '[#{_id}]['.$columnName.']';
46
+ switch($columnName) {
47
+ case 'facet':
48
+ return $this->_getFacetRenderer()
49
+ ->setName($inputName)
50
+ ->setTitle($columnName)
51
+ ->setOptions(Mage::getModel("Antidot/System_Config_Facet")->toOptionArray())
52
+ ->toHtml();
53
+ case 'order':
54
+ return $this->_getOrderRenderer()
55
+ ->setName($inputName)
56
+ ->setTitle($columnName)
57
+ ->setWidth(50)
58
+ ->setOptions(Mage::getModel("Antidot/System_Config_Number")->toOptionArray(10))
59
+ ->toHtml();
60
+ case 'multiple':
61
+ return $this->_getMultipleRenderer()
62
+ ->setName($inputName)
63
+ ->setTitle($columnName)
64
+ ->setOptions(Mage::getModel("Antidot/System_Config_DisableEnable")->toOptionArray())
65
+ ->toHtml();
66
+ }
67
+
68
+ return parent::_renderCellTemplate($columnName);
69
+ }
70
+
71
+ /**
72
+ * {@inherit}
73
+ */
74
+ protected function _getFacetRenderer()
75
+ {
76
+ if (!$this->_facetRenderer) {
77
+ $this->_facetRenderer = $this->getLayout()
78
+ ->createBlock('Antidot/Html_Select')
79
+ ->setIsRenderToJsTemplate(true);
80
+ }
81
+ return $this->_facetRenderer;
82
+ }
83
+
84
+ /**
85
+ * {@inherit}
86
+ */
87
+ protected function _getOrderRenderer()
88
+ {
89
+ if (!$this->_orderRenderer) {
90
+ $this->_orderRenderer = $this->getLayout()
91
+ ->createBlock('Antidot/Html_Select')
92
+ ->setIsRenderToJsTemplate(true);
93
+ }
94
+ return $this->_orderRenderer;
95
+ }
96
+
97
+ /**
98
+ * {@inherit}
99
+ */
100
+ protected function _getMultipleRenderer()
101
+ {
102
+ if (!$this->_multipleRenderer) {
103
+ $this->_multipleRenderer = $this->getLayout()
104
+ ->createBlock('Antidot/Html_Select')
105
+ ->setIsRenderToJsTemplate(true);
106
+ }
107
+ return $this->_multipleRenderer;
108
+ }
109
+
110
+ /**
111
+ * Assign extra parameters to row
112
+ *
113
+ * @param Varien_Object $row
114
+ */
115
+ protected function _prepareArrayRow(Varien_Object $row)
116
+ {
117
+ $row->setData(
118
+ 'option_extra_attr_'.$this->_getFacetRenderer()->calcOptionHash($row->getData('facet')),
119
+ 'selected="selected"'
120
+ );
121
+
122
+ $row->setData(
123
+ 'option_extra_attr_'.$this->_getOrderRenderer()->calcOptionHash($row->getData('order')),
124
+ 'selected="selected"'
125
+ );
126
+
127
+ $row->setData(
128
+ 'option_extra_attr_'.$this->_getMultipleRenderer()->calcOptionHash($row->getData('multiple')),
129
+ 'selected="selected"'
130
+ );
131
+ }
132
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Identifier.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_Identifier extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract
17
+ {
18
+ protected $_valueRenderer;
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ protected function _prepareToRender()
24
+ {
25
+ $this->_fieldRenderer = null;
26
+ $this->_valueRenderer = null;
27
+ $this->_indexRenderer = null;
28
+
29
+ $this->addColumn('value', array('label' => Mage::helper('Antidot')->__('Attribute')));
30
+
31
+ // Disables "Add after" button
32
+ $this->_addAfter = false;
33
+ $this->_addButtonLabel = Mage::helper('Antidot')->__('Add a field');
34
+ }
35
+
36
+ /**
37
+ * {@inherit}
38
+ */
39
+ protected function _renderCellTemplate($columnName)
40
+ {
41
+ return parent::_renderCellTemplate($columnName);
42
+ }
43
+
44
+ /**
45
+ * {@inherit}
46
+ */
47
+ protected function _getValueRenderer()
48
+ {
49
+ if (!$this->_valueRenderer) {
50
+ $this->_valueRenderer = $this->getLayout()
51
+ ->createBlock('Antidot/Html_Select')
52
+ ->setIsRenderToJsTemplate(true);
53
+ }
54
+ return $this->_valueRenderer;
55
+ }
56
+
57
+ /**
58
+ * Assign extra parameters to row
59
+ *
60
+ * @param Varien_Object $row
61
+ */
62
+ protected function _prepareArrayRow(Varien_Object $row)
63
+ {
64
+ $row->setData(
65
+ 'option_extra_attr_'.$this->_getValueRenderer()->calcOptionHash($row->getData('value')),
66
+ 'selected="selected"'
67
+ );
68
+ }
69
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ProductAdditionalFacet.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_ProductAdditionalFacet extends MDN_Antidot_Block_System_Config_Form_Field_Array_Additional
17
+ {
18
+ protected $_autocompleteRenderer;
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ protected function _prepareToRender()
24
+ {
25
+ parent::_prepareToRender();
26
+ $this->addColumn('autocomplete', array('label' => Mage::helper('Antidot')->__('Auto Complete')));
27
+ }
28
+
29
+ /**
30
+ * {@inherit}
31
+ */
32
+ protected function _renderCellTemplate($columnName)
33
+ {
34
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
35
+ switch($columnName) {
36
+ case 'value':
37
+ return $this->_getValueRenderer()
38
+ ->setName($inputName)
39
+ ->setTitle($columnName)
40
+ ->setOptions(Mage::getModel("Antidot/System_Config_ProductAttribute")->toOptionArray(null))
41
+ ->toHtml();
42
+ case 'autocomplete':
43
+ return $this->_getAutocompleteRenderer()
44
+ ->setName($inputName)
45
+ ->setTitle($columnName)
46
+ ->setExtraParams('style="width:100px"')
47
+ ->setOptions(Mage::getModel("Antidot/System_Config_DisableEnable")->toOptionArray(null))
48
+ ->toHtml();
49
+ }
50
+
51
+ return parent::_renderCellTemplate($columnName);
52
+ }
53
+
54
+ /**
55
+ * {@inherit}
56
+ */
57
+ protected function _getAutocompleteRenderer()
58
+ {
59
+ if (!$this->_autocompleteRenderer) {
60
+ $this->_autocompleteRenderer = $this->getLayout()
61
+ ->createBlock('Antidot/Html_Select')
62
+ ->setIsRenderToJsTemplate(true);
63
+ }
64
+ return $this->_autocompleteRenderer;
65
+ }
66
+
67
+ /**
68
+ * Assign extra parameters to row
69
+ *
70
+ * @param Varien_Object $row
71
+ */
72
+ protected function _prepareArrayRow(Varien_Object $row)
73
+ {
74
+ $row->setData(
75
+ 'option_extra_attr_'.$this->_getAutocompleteRenderer()->calcOptionHash($row->getData('autocomplete')),
76
+ 'selected="selected"'
77
+ );
78
+
79
+ parent::_prepareArrayRow($row);
80
+ }
81
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ProductAdditionalField.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_ProductAdditionalField extends MDN_Antidot_Block_System_Config_Form_Field_Array_Additional
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ protected function _renderCellTemplate($columnName)
22
+ {
23
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
24
+ switch($columnName) {
25
+ case 'value':
26
+ return $this->_getValueRenderer()
27
+ ->setName($inputName)
28
+ ->setTitle($columnName)
29
+ ->setOptions(Mage::getModel("Antidot/System_Config_ProductAttribute")->toOptionArray(null))
30
+ ->toHtml();
31
+ }
32
+
33
+ return parent::_renderCellTemplate($columnName);
34
+ }
35
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/ProductIdentifier.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_ProductIdentifier extends MDN_Antidot_Block_System_Config_Form_Field_Array_Identifier
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _renderCellTemplate($columnName)
23
+ {
24
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
25
+ switch($columnName) {
26
+ case 'value':
27
+ return $this->_getValueRenderer()
28
+ ->setName($inputName)
29
+ ->setTitle($columnName)
30
+ ->setOptions(Mage::getModel("Antidot/System_Config_ProductAttribute")->toOptionArray($this->getElement()->getName()))
31
+ ->toHtml();
32
+ }
33
+
34
+ return parent::_renderCellTemplate($columnName);
35
+ }
36
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/Array/Sort.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_Array_Sort extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract
17
+ {
18
+ protected $_sortRenderer;
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ protected function _prepareToRender()
24
+ {
25
+ $this->_sortRenderer = null;
26
+ $this->_labelRenderer = null;
27
+
28
+ $this->addColumn('sort', array('label' => Mage::helper('Antidot')->__('Sortable')));
29
+
30
+ // Disables "Add after" button
31
+ $this->_addAfter = false;
32
+ $this->_addButtonLabel = Mage::helper('Antidot')->__('Add a field');
33
+ }
34
+
35
+ /**
36
+ * {@inherit}
37
+ */
38
+ protected function _renderCellTemplate($columnName)
39
+ {
40
+ $inputName = $this->getElement()->getName() . '[#{_id}][' . $columnName . ']';
41
+ switch($columnName) {
42
+ case 'sort':
43
+ return $this->_getSortRenderer()
44
+ ->setName($inputName)
45
+ ->setTitle($columnName)
46
+ ->setOptions(Mage::getModel("Antidot/System_Config_Sort")->toOptionArray())
47
+ ->toHtml();
48
+ }
49
+
50
+ return parent::_renderCellTemplate($columnName);
51
+ }
52
+
53
+ /**
54
+ * {@inherit}
55
+ */
56
+ protected function _getSortRenderer()
57
+ {
58
+ if (!$this->_sortRenderer) {
59
+ $this->_sortRenderer = $this->getLayout()
60
+ ->createBlock('Antidot/Html_Select')
61
+ ->setIsRenderToJsTemplate(true);
62
+ }
63
+ return $this->_sortRenderer;
64
+ }
65
+
66
+ /**
67
+ * Assign extra parameters to row
68
+ *
69
+ * @param Varien_Object $row
70
+ */
71
+ protected function _prepareArrayRow(Varien_Object $row)
72
+ {
73
+ $row->setData(
74
+ 'option_extra_attr_'.$this->_getSortRenderer()->calcOptionHash($row->getData('sort')),
75
+ 'selected="selected"'
76
+ );
77
+ }
78
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/CategoryAttribute.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_CategoryAttribute extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ return $this->_getRenderer()
25
+ ->setOptions(Mage::getModel("Antidot/System_Config_CategoryAttribute")->toOptionArray($element->getName()))
26
+ ->setValue($element->getValue())
27
+ ->setName($element->getName())
28
+ ->toHtml();
29
+ }
30
+
31
+ protected function _getRenderer()
32
+ {
33
+ return $this->getLayout()
34
+ ->createBlock('Antidot/Html_Select')
35
+ ->setIsRenderToJsTemplate(true);
36
+ }
37
+ }
app/code/community/MDN/Antidot/Block/System/Config/Form/Field/ProductAttribute.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Form_Field_ProductAttribute extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ return $this->_getRenderer()
25
+ ->setOptions(Mage::getModel("Antidot/System_Config_ProductAttribute")->toOptionArray($element->getName()))
26
+ ->setValue($element->getValue())
27
+ ->setName($element->getName())
28
+ ->toHtml();
29
+ }
30
+
31
+ protected function _getRenderer()
32
+ {
33
+ return $this->getLayout()
34
+ ->createBlock('Antidot/Html_Select')
35
+ ->setIsRenderToJsTemplate(true);
36
+ }
37
+ }
app/code/community/MDN/Antidot/Block/System/Config/Html/Export.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Html_Export extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $table = '<div class="grid">'
25
+ .'<table class="border" cellspacing="0" cellpadding="0">'
26
+ .'<thead>'
27
+ .'<tr class="headings">'
28
+ .'<th width="120px">'.Mage::helper('Antidot')->__('Date').'</th>'
29
+ .'<th>'.Mage::helper('Antidot')->__('Reference').'</th>'
30
+ .'<th>'.Mage::helper('Antidot')->__('Type').'</th>'
31
+ .'<th>'.Mage::helper('Antidot')->__('Element').'</th>'
32
+ .'<th>'.Mage::helper('Antidot')->__('Products').'</th>'
33
+ .'<th>'.Mage::helper('Antidot')->__('Status').'</th>'
34
+ .'</tr>'
35
+ .'</thead>'
36
+ .'<tbody>'
37
+ .'%s'
38
+ .'</tbody>'
39
+ .'</table>'
40
+ .'</div>'
41
+ ;
42
+
43
+ $rows = '';
44
+ $rowExport = '<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>';
45
+ foreach(Mage::helper('Antidot/LogExport')->getAllLastGeneration() as $export) {
46
+ $rows.= sprintf(
47
+ $rowExport,
48
+ $export['begin_at'],
49
+ $export['reference'],
50
+ $export['type'],
51
+ $export['element'],
52
+ $export['items_processed'],
53
+ $export['status']
54
+ );
55
+ }
56
+
57
+ return sprintf($table, $rows);
58
+ }
59
+ }
app/code/community/MDN/Antidot/Block/System/Config/Html/ShowXml.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Html_ShowXml extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ $button = $this->getLayout()->createBlock('adminhtml/widget_button')
25
+ ->setType('button')
26
+ ->setClass('scalable')
27
+ ->setLabel(Mage::helper('Antidot')->__('Display XML'))
28
+ ->setOnClick(
29
+ "var term = document.getElementById('suggest_term').value;"
30
+ ."var url = document.getElementById('suggest_url').value;"
31
+ ."var w = window.open(url+'/Antidot/Front_Search/Suggest/?q='+term+'&format=xml', '', 'width=1000,height=750,resizeable,scrollbars');"
32
+ ."return false;"
33
+ )
34
+ ->toHtml();
35
+
36
+ $storeOptions = '';
37
+ foreach (Mage::app()->getStores() as $store) {
38
+ $url = Mage::app()->getStore($store->getId())->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK);
39
+ $storeOptions.= '<option value="'.$url.'">'.$store->getName().'</option>';
40
+ }
41
+
42
+ return '<div class="grid">'
43
+ .'<table class="border" cellspacing="0" cellpadding="0">'
44
+ .'<thead>'
45
+ .'<tr>'
46
+ .'<td>'.Mage::helper('Antidot')->__('Store').'</td>'
47
+ .'<td>'.Mage::helper('Antidot')->__('Term').'</td>'
48
+ .'<td></td>'
49
+ .'</tr>'
50
+ .'</thead>'
51
+ .'<tbody>'
52
+ .'<tr>'
53
+ .'<td><select name="suggest_url" id="suggest_url">'.$storeOptions.'</select></td>'
54
+ .'<td><input type="text" name="suggest_term" id="suggest_term" value="" /></td>'
55
+ .'<td>'.$button.'</td>'
56
+ .'</tr>'
57
+ .'</tbody>'
58
+ .'</table>'
59
+ .'</div>'
60
+ ;
61
+ }
62
+ }
app/code/community/MDN/Antidot/Block/System/Config/Html/Version.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Block_System_Config_Html_Version extends Mage_Adminhtml_Block_System_Config_Form_Field
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element)
23
+ {
24
+ return (string) Mage::getConfig()->getNode()->modules->MDN_Antidot->version;
25
+ }
26
+ }
app/code/community/MDN/Antidot/Helper/Antidot.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_Antidot extends MDN_Antidot_Helper_Data
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function getEngineConfigData($prefix = '', $store = null)
22
+ {
23
+ return parent::getEngineConfigData('antidot_', $store);
24
+ }
25
+
26
+ /**
27
+ * Should Antidot also search on options?
28
+ *
29
+ * @return bool
30
+ */
31
+ public function shouldSearchOnOptions()
32
+ {
33
+ return Mage::getStoreConfigFlag('catalog/search/andidot_enable_options_search');
34
+ }
35
+ }
app/code/community/MDN/Antidot/Helper/CatalogSearch/Data.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_CatalogSearch_Data extends Mage_CatalogSearch_Helper_Data {
17
+
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function getSuggestUrl()
22
+ {
23
+ $url = Mage::getStoreConfig('antidot/suggest/enable') === 'Antidot/engine_antidot' ? 'Antidot/Front_Search/Suggest' : 'catalogsearch/ajax/suggest';
24
+ return $this->_getUrl($url, array(
25
+ '_secure' => Mage::app()->getFrontController()->getRequest()->isSecure()
26
+ ));
27
+ }
28
+ }
app/code/community/MDN/Antidot/Helper/Compress.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_Compress extends Mage_Core_Helper_Abstract {
17
+
18
+ /**
19
+ * Compress files
20
+ *
21
+ * @param array|string $files
22
+ * @param string $zipFile
23
+ * @return boolean
24
+ */
25
+ public function zip($files, $zipFile)
26
+ {
27
+ $files = (array)$files;
28
+ try {
29
+ if(class_exists('ZipArchive')) {
30
+ $zip = new ZipArchive();
31
+ if(!$zip->open($zipFile, ZipArchive::CREATE)) {
32
+ throw new Exception("cannot open ".$zipFile." for writing");
33
+ }
34
+
35
+ foreach($files as $file) {
36
+ $zip->addFile($file, basename($file));
37
+ }
38
+ $zip->close($zip);
39
+ }
40
+ } catch (Exception $e) {
41
+ $files = array_map('basename', $files);
42
+ exec('cd /tmp && zip '.$zipFile.' '.implode(' ', $files));
43
+ }
44
+
45
+ if(!file_exists($zipFile)) {
46
+ throw new Exception('Could not zip the file');
47
+ }
48
+ }
49
+ }
app/code/community/MDN/Antidot/Helper/Data.php ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_Data extends Mage_Core_Helper_Abstract
17
+ {
18
+
19
+ /**
20
+ * @var array Searchable attributes
21
+ */
22
+ protected $_searchableAttributes;
23
+
24
+ /**
25
+ * @var array Facets configuration
26
+ */
27
+ protected $facetConfiguration;
28
+
29
+ /**
30
+ * Returns attribute field name (localized if needed).
31
+ *
32
+ * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute
33
+ * @param string $localeCode
34
+ * @return string
35
+ */
36
+ public function getAttributeFieldName($attribute, $localeCode = null)
37
+ {
38
+ if (is_string($attribute)) {
39
+ $this->getSearchableAttributes();
40
+ if (!isset($this->_searchableAttributes[$attribute])) {
41
+ return $attribute;
42
+ }
43
+ $attribute = $this->_searchableAttributes[$attribute];
44
+ }
45
+ $attributeCode = $attribute->getAttributeCode();
46
+
47
+ return $attributeCode;
48
+ }
49
+
50
+ /**
51
+ * Returns search engine config data.
52
+ *
53
+ * @param string $prefix
54
+ * @param mixed $store
55
+ * @return array
56
+ */
57
+ public function getEngineConfigData($prefix = '', $store = null)
58
+ {
59
+ $config = Mage::getStoreConfig('catalog/search', $store);
60
+ $data = array();
61
+ if ($prefix) {
62
+ foreach ($config as $key => $value) {
63
+ $matches = array();
64
+ if (preg_match("#^{$prefix}(.*)#", $key, $matches)) {
65
+ $data[$matches[1]] = $value;
66
+ }
67
+ }
68
+ } else {
69
+ $data = $config;
70
+ }
71
+
72
+ return $data;
73
+ }
74
+
75
+ /**
76
+ * Returns EAV config singleton.
77
+ *
78
+ * @return Mage_Eav_Model_Config
79
+ */
80
+ public function getEavConfig()
81
+ {
82
+ return Mage::getSingleton('eav/config');
83
+ }
84
+
85
+ /**
86
+ * Returns seach config data.
87
+ *
88
+ * @param string $field
89
+ * @param mixed $store
90
+ * @return array
91
+ */
92
+ public function getSearchConfigData($field, $store = null)
93
+ {
94
+ $path = 'catalog/search/' . $field;
95
+
96
+ return Mage::getStoreConfig($path, $store);
97
+ }
98
+
99
+ /**
100
+ * Check if the facet accepts multiple options
101
+ *
102
+ * @param string $facetId
103
+ * @return boolean
104
+ */
105
+ public function hasFacetMultiple($facetId)
106
+ {
107
+ $facets = $this->getFacetsFilter();
108
+
109
+ return array_key_exists($facetId, $facets) && $facets[$facetId]['multiple'] === '1';
110
+ }
111
+
112
+ /**
113
+ * Retrieve the facets configuration
114
+ *
115
+ * @return array
116
+ */
117
+ public function getFacetsFilter()
118
+ {
119
+ if($this->facetConfiguration === null) {
120
+ $this->facetConfiguration = array();
121
+ if($serializeFacets = Mage::getStoreConfig('antidot/engine/facets')) {
122
+ $facets = unserialize($serializeFacets);
123
+ foreach($facets as $facet) {
124
+ list($facetId) = explode('|', $facet['facet']);
125
+ $this->facetConfiguration[$facetId] = $facet;
126
+ }
127
+ }
128
+ }
129
+
130
+ return $this->facetConfiguration;
131
+ }
132
+
133
+ /**
134
+ * Returns searched parameter as array.
135
+ *
136
+ * @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute
137
+ * @param mixed $value
138
+ * @return array
139
+ */
140
+ public function getSearchParam($attribute, $value)
141
+ {
142
+ if (empty($value) ||
143
+ (isset($value['from']) && empty($value['from']) &&
144
+ isset($value['to']) && empty($value['to']))) {
145
+ return false;
146
+ }
147
+
148
+ $field = $this->getAttributeFieldName($attribute);
149
+ if ($attribute->usesSource()) {
150
+ $attribute->setStoreId(Mage::app()->getStore()->getId());
151
+ }
152
+
153
+ return array($field => $value);
154
+ }
155
+
156
+ /**
157
+ * Checks if configured engine is active.
158
+ *
159
+ * @return bool
160
+ */
161
+ public function isActiveEngine()
162
+ {
163
+ $engine = $this->getSearchConfigData('engine');
164
+ if ($engine && Mage::getConfig()->getResourceModelClassName($engine)) {
165
+ $model = Mage::getResourceSingleton($engine);
166
+ return $model
167
+ && $model instanceof MDN_Antidot_Model_Resource_Engine_Abstract
168
+ && $model->test();
169
+ }
170
+
171
+ return false;
172
+ }
173
+
174
+ /**
175
+ * Send an email to admin
176
+ *
177
+ * @param string $subject
178
+ * @param string $message
179
+ */
180
+ public function sendMail($subject, $message)
181
+ {
182
+ if(!$email = Mage::getStoreConfig('antidot/general/email')) {
183
+ return;
184
+ }
185
+
186
+ $mail = Mage::getModel('core/email');
187
+ $mail->setToEmail($email);
188
+ $mail->setBody($message);
189
+ $mail->setSubject(Mage::getStoreConfig('system/website/name').': '. $subject);
190
+ $mail->setFromEmail('no-reply@boostmyshop.com');
191
+ $mail->setFromName("BoostMyShop");
192
+ $mail->setType('text');
193
+
194
+ try {
195
+ $mail->send();
196
+ Mage::getSingleton('core/session')->addSuccess('Your request has been sent');
197
+ }
198
+ catch (Exception $e) {
199
+ Mage::getSingleton('core/session')->addError('Unable to send.');
200
+ }
201
+ }
202
+ }
app/code/community/MDN/Antidot/Helper/LogExport.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_LogExport extends Mage_Core_Helper_Abstract
17
+ {
18
+
19
+ /**
20
+ * Add a row to antidot_export
21
+ *
22
+ * @param string $uid Reference to
23
+ * @param string $type FULL|INC
24
+ * @param string $element CATALOG|CATEGORY
25
+ * @param string $begin
26
+ * @param string $end
27
+ * @param int $items
28
+ */
29
+ public function add($reference, $type, $element, $begin, $end, $items, $status, $error = '')
30
+ {
31
+ $query = "INSERT INTO antidot_export(reference, type, element, begin_at, end_at, items_processed, status, error) "
32
+ . "VALUES('".$reference."', '".$type."', '".$element."', '".date('Y-m-d H:i:s', $begin)."', '".date('Y-m-d H:i:s', $end)."', ".(int)$items.", '".$status."', '".$error."')";
33
+
34
+ Mage::getSingleton('core/resource')->getConnection('core_write')->query($query);
35
+ }
36
+
37
+ /**
38
+ * Return the last export
39
+ *
40
+ * @param string $element
41
+ * @return array
42
+ */
43
+ public function getLastGeneration($element)
44
+ {
45
+ $query = "SELECT begin_at "
46
+ . "FROM antidot_export "
47
+ . "WHERE element = '".$element."' "
48
+ . "ORDER BY begin_at DESC "
49
+ . "LIMIT 1";
50
+
51
+ return Mage::getSingleton('core/resource')->getConnection('core_read')->fetchOne($query);
52
+ }
53
+
54
+ /**
55
+ * Return the last export
56
+ *
57
+ * @param int Since x hours
58
+ * @return array
59
+ */
60
+ public function getAllLastGeneration($sinceHour = 24)
61
+ {
62
+ $since = date('Y-m-d H:i:s', time()-(int)$sinceHour*60*60);
63
+ $query = "SELECT reference, type, element, begin_at, items_processed, status "
64
+ . "FROM antidot_export "
65
+ . "WHERE begin_at > '".$since."' "
66
+ . "ORDER BY begin_at DESC";
67
+
68
+ return Mage::getSingleton('core/resource')->getConnection('core_read')->fetchAll($query);
69
+ }
70
+ }
app/code/community/MDN/Antidot/Helper/Url.php ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_Url extends Mage_Core_Helper_Abstract
17
+ {
18
+
19
+ /**
20
+ * Urlize
21
+ *
22
+ * @param string $string
23
+ * @return string
24
+ */
25
+ public function url($string, $separator = '-')
26
+ {
27
+ $string = self::removeAccent($string);
28
+ $string = preg_replace('#([^a-zA-Z0-9])#', $separator, $string);
29
+ $string = preg_replace('#\\'.preg_quote($separator).'{2,}#', $separator, $string);
30
+ $string = preg_replace('#(^'.preg_quote($separator).')|('.preg_quote($separator).'$)#', '', $string);
31
+
32
+ return strtolower($string);
33
+ }
34
+
35
+ /**
36
+ * Check if the string is utf8
37
+ *
38
+ * @see http://fr2.php.net/manual/fr/function.mb-detect-encoding.php#68607
39
+ * @param string $string
40
+ * @return bool
41
+ */
42
+ public function isUtf8($string)
43
+ {
44
+ return preg_match(
45
+ '%(?:
46
+ [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
47
+ |\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
48
+ |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
49
+ |\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
50
+ |\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
51
+ |[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
52
+ |\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
53
+ )+%xs',
54
+ $string
55
+ ) === 1;
56
+ }
57
+
58
+ /**
59
+ * Remove accents from the string
60
+ *
61
+ * @param string $string
62
+ * @return string
63
+ */
64
+ protected function removeAccent($string)
65
+ {
66
+ if (!self::isUtf8($string)) {
67
+ $string = utf8_encode($string);
68
+ }
69
+
70
+ $string = htmlentities($string, ENT_NOQUOTES, 'UTF-8');
71
+
72
+ return preg_replace('#&([a-zA-Z])[a-zA-Z]+;#', '$1', $string);
73
+ }
74
+ }
app/code/community/MDN/Antidot/Helper/XmlWriter.php ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Helper_XmlWriter extends Mage_Core_Helper_Abstract
17
+ {
18
+
19
+ protected $xml;
20
+ protected $indent;
21
+ protected $stack = array();
22
+
23
+ /**
24
+ * Init the xml string
25
+ *
26
+ * @param string $indent
27
+ */
28
+ public function init($indent = ' ')
29
+ {
30
+ $this->indent = $indent;
31
+ $this->xml = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
32
+ }
33
+
34
+ /**
35
+ * Added an indent to xml
36
+ */
37
+ protected function indent()
38
+ {
39
+ for ($i = 0, $j = count($this->stack); $i < $j; $i++) {
40
+ $this->xml.= $this->indent;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Open a new element
46
+ *
47
+ * @param string $element
48
+ * @param array $attributes
49
+ */
50
+ public function push($element, $attributes = array())
51
+ {
52
+ $this->indent();
53
+ $this->xml.= '<' . $element;
54
+ foreach ($this->cleanAttributes($attributes) as $key => $value) {
55
+ if ($value !== '') {
56
+ $this->xml.= ' ' . $key . '="' . $value . '"';
57
+ }
58
+ }
59
+ $this->xml.= ">\n";
60
+ $this->stack[] = $element;
61
+ }
62
+
63
+ /**
64
+ * Add a new element
65
+ *
66
+ * @param string $element
67
+ * @param string $content
68
+ * @param array $attributes
69
+ */
70
+ public function element($element, $content, $attributes = array())
71
+ {
72
+ $this->indent();
73
+ $this->xml.= '<' . $element;
74
+ foreach ($this->cleanAttributes($attributes) as $key => $value) {
75
+ if ($value !== '') {
76
+ $this->xml.= ' ' . $key . '="' . $value . '"';
77
+ }
78
+ }
79
+
80
+ $content = Mage::helper('Antidot/Url')->isUtf8($content) === false ? mb_convert_encoding($content, "UTF-8") : $content;
81
+ $this->xml.= '>' . ($content) . '</' . $element . '>' . "\n";
82
+ }
83
+
84
+ /**
85
+ * Add a new empty element
86
+ *
87
+ * @param string $element
88
+ * @param array $attributes
89
+ */
90
+ public function emptyelement($element, $attributes = array())
91
+ {
92
+ $this->indent();
93
+ $this->xml.= '<' . $element;
94
+ foreach ($this->cleanAttributes($attributes) as $key => $value) {
95
+ if ($value !== '') {
96
+ $this->xml.= ' ' . $key . '="' . $value . '"';
97
+ }
98
+ }
99
+ $this->xml.= " />\n";
100
+ }
101
+
102
+ /**
103
+ * Clean attributes
104
+ *
105
+ * @param array $attributes
106
+ * @return array
107
+ */
108
+ protected function cleanAttributes($attributes)
109
+ {
110
+ foreach ($attributes as &$value) {
111
+ $value = htmlspecialchars($value);
112
+ }
113
+
114
+ return $attributes;
115
+ }
116
+
117
+ /**
118
+ * Close an element
119
+ */
120
+ public function pop()
121
+ {
122
+ $element = array_pop($this->stack);
123
+ $this->indent();
124
+ $this->xml.= "</$element>\n";
125
+ }
126
+
127
+ /**
128
+ * Return the xml
129
+ *
130
+ * @return string
131
+ */
132
+ public function getXml()
133
+ {
134
+ return $this->xml;
135
+ }
136
+
137
+ /**
138
+ * Return the xml and set to empty
139
+ *
140
+ * @return string
141
+ */
142
+ public function flush()
143
+ {
144
+ $content = $this->xml;
145
+ $this->xml = '';
146
+
147
+ return $content;
148
+ }
149
+
150
+ /**
151
+ * Add an enclose CData
152
+ *
153
+ * @param string $value
154
+ * @return string
155
+ */
156
+ public function encloseCData($value)
157
+ {
158
+ return '<![CDATA['.$value.']]>';
159
+ }
160
+
161
+ /**
162
+ * Return last errors generated
163
+ *
164
+ * @return array
165
+ */
166
+ public function getErrors()
167
+ {
168
+ $errors = array();
169
+ foreach (libxml_get_errors() as $error) {
170
+ $errors[] = $this->getError($error);
171
+ }
172
+ libxml_clear_errors();
173
+
174
+ return $errors;
175
+ }
176
+
177
+ /**
178
+ * Return an error
179
+ *
180
+ * @param XmlError $error
181
+ * @return string
182
+ */
183
+ protected function getError($error)
184
+ {
185
+ switch ($error->level) {
186
+ case LIBXML_ERR_WARNING:
187
+ $return = "Warning $error->code: ";
188
+ break;
189
+ case LIBXML_ERR_ERROR:
190
+ $return = "Error $error->code: ";
191
+ break;
192
+ case LIBXML_ERR_FATAL:
193
+ $return = "Fatal Error $error->code: ";
194
+ break;
195
+ }
196
+
197
+ $return.= trim($error->message);
198
+ if ($error->file) {
199
+ $return.= " in $error->file";
200
+ }
201
+ $return.= " on line <b>$error->line";
202
+
203
+ return $return;
204
+ }
205
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalog_Layer extends Mage_Catalog_Model_Layer
17
+ {
18
+ /**
19
+ * Returns product collection for current category.
20
+ *
21
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
22
+ */
23
+ public function getProductCollection()
24
+ {
25
+ $category = $this->getCurrentCategory();
26
+
27
+ if (isset($this->_productCollections[$category->getId()])) {
28
+ $collection = $this->_productCollections[$category->getId()];
29
+ } else {
30
+ $collection = Mage::helper('catalogsearch')
31
+ ->getEngine()
32
+ ->getResultCollection()
33
+ ->setStoreId($category->getStoreId());
34
+ $this->prepareProductCollection($collection);
35
+ $this->_productCollections[$category->getId()] = $collection;
36
+ }
37
+
38
+ return $collection;
39
+ }
40
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Attribute.php ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalog_Layer_Filter_Attribute extends Mage_Catalog_Model_Layer_Filter_Attribute
17
+ {
18
+ /**
19
+ * Adds facet condition to product collection.
20
+ *
21
+ * @see MDN_Antidot_Model_Resource_Catalog_Product_Collection::addFacetCondition()
22
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Attribute
23
+ */
24
+ public function addFacetCondition()
25
+ {
26
+ $this->getLayer()
27
+ ->getProductCollection()
28
+ ->addFacetCondition($this->_getFilterField());
29
+
30
+ return $this;
31
+ }
32
+
33
+ /**
34
+ * Retrieves request parameter and applies it to product collection.
35
+ *
36
+ * @param Zend_Controller_Request_Abstract $request
37
+ * @param Mage_Core_Block_Abstract $filterBlock
38
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Attribute
39
+ */
40
+ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock)
41
+ {
42
+ $attribute = $this->getAttributeModel();
43
+ $this->_requestVar = $attribute->getAttributeCode();
44
+
45
+ $filter = $request->getParam($this->_requestVar);
46
+ if (is_array($filter) || null === $filter) {
47
+ return $this;
48
+ }
49
+
50
+ $text = $this->_getOptionText($filter);
51
+ if ($this->_isValidFilter($filter) && strlen($text)) {
52
+ $this->applyFilterToCollection($this, $filter);
53
+ if(!Mage::helper('Antidot')->hasFacetMultiple($this->_requestVar)) {
54
+ $this->_items = array();
55
+
56
+ if(Mage::getSingleton('core/session')->getData($this->_requestVar.$text)) {
57
+ $text = Mage::getSingleton('core/session')->getData($this->_requestVar.$text);
58
+ }
59
+
60
+ $this->getLayer()->getState()->addFilter($this->_createItem($text, $filter));
61
+ }
62
+ }
63
+
64
+ return $this;
65
+ }
66
+
67
+ /**
68
+ * Return the attribute code
69
+ *
70
+ * @return string Attribute code
71
+ */
72
+ public function getCode()
73
+ {
74
+ return $this->getAttributeModel()->getAttributeCode();
75
+ }
76
+
77
+ /**
78
+ * Applies filter to product collection.
79
+ *
80
+ * @param $filter
81
+ * @param $value
82
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Attribute
83
+ */
84
+ public function applyFilterToCollection($filter, $value)
85
+ {
86
+ if (!$this->_isValidFilter($value)) {
87
+ $value = array();
88
+ } else if (!is_array($value)) {
89
+ $value = array($value);
90
+ }
91
+
92
+ $attribute = $filter->getAttributeModel();
93
+ $param = Mage::helper('Antidot')->getSearchParam($attribute, $value);
94
+
95
+ $this->getLayer()
96
+ ->getProductCollection()
97
+ ->addSearchQfFilter($param);
98
+
99
+ return $this;
100
+ }
101
+
102
+ /**
103
+ * Returns facets data of current attribute.
104
+ *
105
+ * @return array
106
+ */
107
+ protected function _getFacets()
108
+ {
109
+ $productCollection = $this->getLayer()->getProductCollection();
110
+ $fieldName = $this->_getFilterField();
111
+ $facets = $productCollection->getFacetedData($fieldName);
112
+
113
+ return $facets;
114
+ }
115
+
116
+ /**
117
+ * Returns attribute field name.
118
+ *
119
+ * @return string
120
+ */
121
+ protected function _getFilterField()
122
+ {
123
+ $attribute = $this->getAttributeModel();
124
+ $fieldName = Mage::helper('Antidot')->getAttributeFieldName($attribute);
125
+
126
+ return $fieldName;
127
+ }
128
+
129
+ /**
130
+ * Retrieves current items data.
131
+ *
132
+ * @return array
133
+ */
134
+ protected function _getItemsData()
135
+ {
136
+ $attribute = $this->getAttributeModel();
137
+ $this->_requestVar = $attribute->getAttributeCode();
138
+
139
+ $facets = $this->_getFacets();
140
+
141
+ $data = array();
142
+ if (count($facets) > 0) {
143
+ if ($attribute->getFrontendInput() === 'text') {
144
+ $data = $this->getFacetsData($facets);
145
+ }
146
+ }
147
+
148
+ Mage::getSingleton('core/session')->setData(md5($_GET['q'].$this->_requestVar), serialize($data));
149
+
150
+ return $data;
151
+ }
152
+
153
+ /**
154
+ * @param array $facets
155
+ */
156
+ protected function getFacetsData($facets)
157
+ {
158
+ $data = array();
159
+ foreach ($facets as $facetKey => $facet) {
160
+ $data[$facetKey] = array(
161
+ 'label' => $facet['label'],
162
+ 'value' => $facetKey,
163
+ 'count' => $facet['count'],
164
+ );
165
+
166
+ Mage::getSingleton('core/session')->setData($this->_requestVar.$facetKey, $facet['label']);
167
+ if(isset($facet['child'])) {
168
+ $data[$facetKey]['child'] = $this->getFacetsData($facet['child']);
169
+ Mage::getSingleton('core/session')->setData('child'.$this->_requestVar.$facetKey, $data[$facetKey]['child']);
170
+ }
171
+ }
172
+
173
+ return $data;
174
+ }
175
+
176
+ /**
177
+ * Returns option label if attribute uses options.
178
+ *
179
+ * @param int $optionId
180
+ * @return bool|int|string
181
+ */
182
+ protected function _getOptionText($optionId)
183
+ {
184
+ if ($this->getAttributeModel()->getFrontendInput() == 'text') {
185
+ return $optionId;
186
+ }
187
+
188
+ return parent::_getOptionText($optionId);
189
+ }
190
+
191
+ /**
192
+ * Checks if given filter is valid before being applied to product collection.
193
+ *
194
+ * @param string $filter
195
+ * @return bool
196
+ */
197
+ protected function _isValidFilter($filter)
198
+ {
199
+ return !empty($filter);
200
+ }
201
+
202
+ /**
203
+ * Create filter item object
204
+ *
205
+ * @param string $label
206
+ * @param mixed $value
207
+ * @param int $count
208
+ * @return Mage_Catalog_Model_Layer_Filter_Item
209
+ */
210
+ protected function _createItem($label, $value, $count = 0)
211
+ {
212
+ $children = (array)Mage::getSingleton('core/session')->getData('child'.$this->_requestVar.$value);
213
+
214
+ $itemChildren = array();
215
+ foreach($children as $child) {
216
+ $itemChildren[] = $this->_createItem($child['label'], $child['value'], $child['count']);
217
+ }
218
+
219
+ return Mage::getModel('Antidot/catalog_layer_filter_item')
220
+ ->setFilter($this)
221
+ ->setLabel($label)
222
+ ->setValue($value)
223
+ ->setCount($count)
224
+ ->setChild($itemChildren);
225
+ }
226
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Boolean.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalog_Layer_Filter_Boolean extends MDN_Antidot_Model_Catalog_Layer_Filter_Attribute
17
+ {
18
+ /**
19
+ * Returns facets data of current attribute.
20
+ *
21
+ * @return array
22
+ */
23
+ protected function _getFacets()
24
+ {
25
+ $facets = parent::_getFacets();
26
+ $result = array();
27
+ foreach ($facets as $value => $count) {
28
+ $key = 0; // false by default
29
+ if ($value === 'true' || $value === 'T' || $value === '1' || $value === 1 || $value === true) {
30
+ $key = 1;
31
+ }
32
+ $result[$key] = $count;
33
+ }
34
+
35
+ return $result;
36
+ }
37
+
38
+ /**
39
+ * Checks if given filter is valid before being applied to product collection.
40
+ *
41
+ * @param string $filter
42
+ * @return bool
43
+ */
44
+ protected function _isValidFilter($filter)
45
+ {
46
+ return $filter === '0' || $filter === '1' || false === $filter || true === $filter;
47
+ }
48
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Category.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalog_Layer_Filter_Category extends Mage_Catalog_Model_Layer_Filter_Category
17
+ {
18
+ /**
19
+ * Adds category filter to product collection.
20
+ *
21
+ * @param Mage_Catalog_Model_Category $category
22
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Category
23
+ */
24
+ public function addCategoryFilter($category)
25
+ {
26
+ $value = array(
27
+ 'categories' => $category->getId()
28
+ );
29
+ $this->getLayer()->getProductCollection()
30
+ ->addFqFilter($value);
31
+
32
+ return $this;
33
+ }
34
+
35
+ /**
36
+ * Adds facet condition to product collection.
37
+ *
38
+ * @see MDN_Antidot_Model_Resource_Catalog_Product_Collection::addFacetCondition()
39
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Category
40
+ */
41
+ public function addFacetCondition()
42
+ {
43
+ /** @var $category Mage_Catalog_Model_Category */
44
+ $category = $this->getCategory();
45
+ $childrenCategories = $category->getChildrenCategories();
46
+
47
+ $useFlat = (bool) Mage::getStoreConfig('catalog/frontend/flat_catalog_category');
48
+ $categories = ($useFlat)
49
+ ? array_keys($childrenCategories)
50
+ : array_keys($childrenCategories->toArray());
51
+
52
+ $this->getLayer()->getProductCollection()->addFacetCondition('categories', $categories);
53
+
54
+ return $this;
55
+ }
56
+
57
+ /**
58
+ * Retrieves request parameter and applies it to product collection.
59
+ *
60
+ * @param Zend_Controller_Request_Abstract $request
61
+ * @param Mage_Core_Block_Abstract $filterBlock
62
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Category
63
+ */
64
+ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock)
65
+ {
66
+ $filter = (int) $request->getParam($this->getRequestVar());
67
+ if ($filter) {
68
+ $this->_categoryId = $filter;
69
+ }
70
+
71
+ /** @var $category Mage_Catalog_Model_Category */
72
+ $category = $this->getCategory();
73
+ if (!Mage::registry('current_category_filter')) {
74
+ Mage::register('current_category_filter', $category);
75
+ }
76
+
77
+ if (!$filter) {
78
+ $this->addCategoryFilter($category, null);
79
+ return $this;
80
+ }
81
+
82
+ $this->_appliedCategory = Mage::getModel('catalog/category')
83
+ ->setStoreId(Mage::app()->getStore()->getId())
84
+ ->load($filter);
85
+
86
+ if ($this->_isValidCategory($this->_appliedCategory)) {
87
+ $this->getLayer()->getProductCollection()
88
+ ->addCategoryFilter($this->_appliedCategory);
89
+ $this->addCategoryFilter($this->_appliedCategory);
90
+ $this->getLayer()->getState()->addFilter(
91
+ $this->_createItem($this->_appliedCategory->getName(), $filter)
92
+ );
93
+ }
94
+
95
+ return $this;
96
+ }
97
+
98
+ /**
99
+ * Retrieves current items data.
100
+ *
101
+ * @return array
102
+ */
103
+ protected function _getItemsData()
104
+ {
105
+ $layer = $this->getLayer();
106
+ $key = $layer->getStateKey().'_SUBCATEGORIES';
107
+ $data = $layer->getCacheData($key);
108
+
109
+ if ($data === null) {
110
+ $categories = $this->getCategory()->getChildrenCategories();
111
+
112
+ /** @var $productCollection MDN_Antidot_Model_Resource_Catalog_Product_Collection */
113
+ $productCollection = $layer->getProductCollection();
114
+ $facets = $productCollection->getFacetedData('categories');
115
+
116
+ $data = array();
117
+ foreach ($categories as $category) {
118
+ /** @var $category Mage_Catalog_Model_Category */
119
+ $categoryId = $category->getId();
120
+ if (isset($facets[$categoryId])) {
121
+ $category->setProductCount($facets[$categoryId]);
122
+ } else {
123
+ $category->setProductCount(0);
124
+ }
125
+ if ($category->getIsActive() && $category->getProductCount()) {
126
+ $data[] = array(
127
+ 'label' => Mage::helper('core')->escapeHtml($category->getName()),
128
+ 'value' => $categoryId,
129
+ 'count' => $category->getProductCount(),
130
+ );
131
+ }
132
+ }
133
+ $tags = $layer->getStateTags();
134
+ $layer->getAggregator()->saveCacheData($data, $key, $tags);
135
+ }
136
+
137
+ return $data;
138
+ }
139
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Decimal.php ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalog_Layer_Filter_Decimal extends Mage_Catalog_Model_Layer_Filter_Decimal
17
+ {
18
+ const CACHE_TAG = 'MAXVALUE';
19
+
20
+ /**
21
+ * Adds facet condition to product collection.
22
+ *
23
+ * @see MDN_Antidot_Model_Resource_Catalog_Product_Collection::addFacetCondition()
24
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Decimal
25
+ */
26
+ public function addFacetCondition()
27
+ {
28
+ $range = $this->getRange();
29
+ $maxValue = $this->getMaxValue();
30
+ if ($maxValue > 0) {
31
+ $facets = array();
32
+ $facetCount = (int) ceil($maxValue / $range);
33
+
34
+ for ($i = 0; $i < $facetCount + 1; $i++) {
35
+ $facets[] = array(
36
+ 'from' => $i * $range,
37
+ 'to' => ($i + 1) * $range,
38
+ 'include_upper' => !($i < $facetCount)
39
+ );
40
+ }
41
+
42
+ $fieldName = $this->_getFilterField();
43
+ $this->getLayer()->getProductCollection()->addFacetCondition($fieldName, $facets);
44
+ }
45
+
46
+ return $this;
47
+ }
48
+
49
+ /**
50
+ * Retrieves request parameter and applies it to product collection.
51
+ *
52
+ * @param Zend_Controller_Request_Abstract $request
53
+ * @param Mage_Core_Block_Abstract $filterBlock
54
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Decimal
55
+ */
56
+ public function apply(Zend_Controller_Request_Abstract $request, $filterBlock)
57
+ {
58
+ $filter = $request->getParam($this->getRequestVar());
59
+ if (!$filter) {
60
+ return $this;
61
+ }
62
+
63
+ $filter = explode(',', $filter);
64
+ if (count($filter) != 2) {
65
+ return $this;
66
+ }
67
+
68
+ list($index, $range) = $filter;
69
+
70
+ if ((int) $index && (int) $range) {
71
+ $this->setRange((int) $range);
72
+
73
+ $this->applyFilterToCollection($this, $range, $index);
74
+ $this->getLayer()->getState()->addFilter(
75
+ $this->_createItem($this->_renderItemLabel($range, $index), $filter)
76
+ );
77
+
78
+ $this->_items = array();
79
+ }
80
+
81
+ return $this;
82
+ }
83
+
84
+ /**
85
+ * Apply decimal filter range to product collection.
86
+ *
87
+ * @param MDN_Antidot_Model_Catalog_Layer_Filter_Decimal $filter
88
+ * @param int $range
89
+ * @param int $index
90
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Decimal
91
+ */
92
+ public function applyFilterToCollection($filter, $range, $index)
93
+ {
94
+ $value = array(
95
+ $this->_getFilterField() => array(
96
+ 'from' => ($range * ($index - 1)),
97
+ 'to' => $range * $index,
98
+ )
99
+ );
100
+ $filter->getLayer()->getProductCollection()->addFqFilter($value);
101
+
102
+ return $this;
103
+ }
104
+
105
+ public function getMaxValue()
106
+ {
107
+ $searchParams = $this->getLayer()->getProductCollection()->getExtendedSearchParams();
108
+ $uniquePart = strtoupper(md5(serialize($searchParams)));
109
+ $cacheKey = 'MAXVALUE_' . $this->getLayer()->getStateKey() . '_' . $uniquePart;
110
+
111
+ $cachedData = Mage::app()->loadCache($cacheKey);
112
+ if (!$cachedData) {
113
+ $stats = $this->getLayer()->getProductCollection()->getStats($this->_getFilterField());
114
+
115
+ $max = $stats[$this->_getFilterField()]['max'];
116
+ if (!is_numeric($max)) {
117
+ $max = parent::getMaxValue();
118
+ }
119
+
120
+ $cachedData = (float) $max;
121
+ $tags = $this->getLayer()->getStateTags();
122
+ $tags[] = self::CACHE_TAG;
123
+ Mage::app()->saveCache($cachedData, $cacheKey, $tags);
124
+ }
125
+
126
+ return $cachedData;
127
+ }
128
+
129
+ /**
130
+ * Returns decimal field name.
131
+ *
132
+ * @return string
133
+ */
134
+ protected function _getFilterField()
135
+ {
136
+ $fieldName = Mage::helper('Antidot')->getAttributeFieldName($this->getAttributeModel());
137
+
138
+ return $fieldName;
139
+ }
140
+
141
+ /**
142
+ * Retrieves current items data.
143
+ *
144
+ * @return array
145
+ */
146
+ protected function _getItemsData()
147
+ {
148
+ $range = $this->getRange();
149
+ $fieldName = $this->_getFilterField();
150
+ $facets = $this->getLayer()->getProductCollection()->getFacetedData($fieldName);
151
+
152
+ $data = array();
153
+ if (!empty($facets)) {
154
+ foreach ($facets as $key => $count) {
155
+ if ($count > 0) {
156
+ preg_match('/TO ([\d\.]+)\]$/', $key, $rangeKey);
157
+ $rangeKey = round($rangeKey[1] / $range);
158
+ $data[] = array(
159
+ 'label' => $this->_renderItemLabel($range, $rangeKey),
160
+ 'value' => $rangeKey . ',' . $range,
161
+ 'count' => $count,
162
+ );
163
+ }
164
+ }
165
+ }
166
+
167
+ return $data;
168
+ }
169
+
170
+ /**
171
+ * Renders decimal ranges.
172
+ *
173
+ * @param int $range
174
+ * @param float $value
175
+ * @return string
176
+ */
177
+ protected function _renderItemLabel($range, $value)
178
+ {
179
+ /** @var $attribute Mage_Catalog_Model_Resource_Eav_Attribute */
180
+ $attribute = $this->getAttributeModel();
181
+
182
+ if ($attribute->getFrontendInput() == 'price') {
183
+ return parent::_renderItemLabel($range, $value);
184
+ }
185
+
186
+ $from = ($value - 1) * $range;
187
+ $to = $value * $range;
188
+
189
+ if ($from != $to) {
190
+ $to -= 0.01;
191
+ }
192
+
193
+ $to = Zend_Locale_Format::toFloat($to, array('locale' => Mage::helper('Antidot')->getLocaleCode()));
194
+
195
+ return Mage::helper('catalog')->__('%s - %s', $from, $to);
196
+ }
197
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Item.php ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Open Software License (OSL 3.0)
8
+ * that is bundled with this package in the file LICENSE.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category Mage
22
+ * @package Mage_Catalog
23
+ * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
24
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
25
+ */
26
+
27
+ /**
28
+ * Filter item model
29
+ *
30
+ * @category Mage
31
+ * @package Mage_Catalog
32
+ * @author Magento Core Team <core@magentocommerce.com>
33
+ */
34
+ class MDN_Antidot_Model_Catalog_Layer_Filter_Item extends Mage_Catalog_Model_Layer_Filter_Item
35
+ {
36
+ /**
37
+ * Get filter instance
38
+ *
39
+ * @return Mage_Catalog_Model_Layer_Filter_Abstract
40
+ */
41
+ public function getFilter()
42
+ {
43
+ $filter = $this->getData('filter');
44
+ if (!is_object($filter)) {
45
+ Mage::throwException(
46
+ Mage::helper('catalog')->__('Filter must be an object. Please set correct filter.')
47
+ );
48
+ }
49
+ return $filter;
50
+ }
51
+
52
+ /**
53
+ * Get filter item url
54
+ *
55
+ * @return string
56
+ */
57
+ public function getUrl()
58
+ {
59
+ $values = array();
60
+ if($currentValues = Mage::getSingleton('core/app')->getRequest()->getParam($this->getFilter()->getRequestVar())) {
61
+ $values = explode(',', $currentValues);
62
+ }
63
+
64
+ if(false !== $key = array_search($this->getValue(), $values)) {
65
+ unset($values[$key]);
66
+ } else {
67
+ $values[] = $this->getValue();
68
+ }
69
+
70
+ $values = count($values) > 1 ? implode(',', $values) : current($values);
71
+ $query = array(
72
+ $this->getFilter()->getRequestVar() => (empty($values) ? null : $values),
73
+ Mage::getBlockSingleton('page/html_pager')->getPageVarName() => null // exclude current page from urls
74
+ );
75
+
76
+ return Mage::getUrl('*/*/*', array('_current'=>true, '_use_rewrite'=>true, '_query'=>$query));
77
+ }
78
+
79
+ /**
80
+ * Get url for remove item from filter
81
+ *
82
+ * @return string
83
+ */
84
+ public function getRemoveUrl()
85
+ {
86
+ $query = array($this->getFilter()->getRequestVar()=>$this->getFilter()->getResetValue());
87
+ $params['_current'] = true;
88
+ $params['_use_rewrite'] = true;
89
+ $params['_query'] = $query;
90
+ $params['_escape'] = true;
91
+ return Mage::getUrl('*/*/*', $params);
92
+ }
93
+
94
+ /**
95
+ * Get url for "clear" link
96
+ *
97
+ * @return false|string
98
+ */
99
+ public function getClearLinkUrl()
100
+ {
101
+ $clearLinkText = $this->getFilter()->getClearLinkText();
102
+ if (!$clearLinkText) {
103
+ return false;
104
+ }
105
+
106
+ $urlParams = array(
107
+ '_current' => true,
108
+ '_use_rewrite' => true,
109
+ '_query' => array($this->getFilter()->getRequestVar() => null),
110
+ '_escape' => true,
111
+ );
112
+
113
+ return Mage::getUrl('*/*/*', $urlParams);
114
+ }
115
+
116
+ /**
117
+ * Get item filter name
118
+ *
119
+ * @return string
120
+ */
121
+ public function getName()
122
+ {
123
+ return $this->getFilter()->getName();
124
+ }
125
+
126
+ /**
127
+ * Get item value as string
128
+ *
129
+ * @return string
130
+ */
131
+ public function getValueString()
132
+ {
133
+ $value = $this->getValue();
134
+ if (is_array($value)) {
135
+ return implode(',', $value);
136
+ }
137
+ return $value;
138
+ }
139
+
140
+ /**
141
+ * Check if the item is selected
142
+ *
143
+ * @return true
144
+ */
145
+ public function isSelected()
146
+ {
147
+ $selected = Mage::getSingleton('core/app')->getRequest()->getParam($this->getFilter()->getRequestVar());
148
+
149
+ return in_array($this->getValue(), explode(',', $selected), true);
150
+ }
151
+ }
app/code/community/MDN/Antidot/Model/Catalog/Layer/Filter/Price.php ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalog_Layer_Filter_Price extends Mage_Catalog_Model_Layer_Filter_Price
17
+ {
18
+ const CACHE_TAG = 'MAXPRICE';
19
+
20
+ /**
21
+ * Adds facet condition to product collection.
22
+ *
23
+ * @see MDN_Antidot_Model_Resource_Catalog_Product_Collection::addFacetCondition()
24
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Price
25
+ */
26
+ public function addFacetCondition()
27
+ {
28
+ $range = $this->getPriceRange();
29
+ $maxPrice = $this->getMaxPriceInt();
30
+ if ($maxPrice > 0) {
31
+ $priceFacets = array();
32
+ $facetCount = (int) ceil($maxPrice / $range);
33
+
34
+ for ($i = 0; $i < $facetCount + 1; $i++) {
35
+ $from = ($i === 0) ? '' : ($i * $range);
36
+ $to = ($i === $facetCount) ? '' : (($i + 1) * $range);
37
+ $priceFacets[] = array(
38
+ 'from' => $from,
39
+ 'to' => $to,
40
+ 'include_upper' => !($i < $facetCount)
41
+ );
42
+ }
43
+
44
+ $this->getLayer()->getProductCollection()->addFacetCondition($this->_getFilterField(), $priceFacets);
45
+ }
46
+
47
+ return $this;
48
+ }
49
+
50
+ /**
51
+ * Returns cache tag.
52
+ *
53
+ * @return string
54
+ */
55
+ public function getCacheTag()
56
+ {
57
+ return self::CACHE_TAG;
58
+ }
59
+
60
+ /**
61
+ * Retrieves max price for ranges definition.
62
+ *
63
+ * @return float
64
+ */
65
+ public function getMaxPriceInt()
66
+ {
67
+ $searchParams = $this->getLayer()->getProductCollection()->getExtendedSearchParams();
68
+ $uniquePart = strtoupper(md5(serialize($searchParams)));
69
+ $cacheKey = 'MAXPRICE_' . $this->getLayer()->getStateKey() . '_' . $uniquePart;
70
+
71
+ $cachedData = Mage::app()->loadCache($cacheKey);
72
+ if (!$cachedData) {
73
+ $stats = $this->getLayer()->getProductCollection()->getStats($this->_getFilterField());
74
+
75
+ $max = $stats[$this->_getFilterField()]['max'];
76
+ if (!is_numeric($max)) {
77
+ $max = parent::getMaxPriceInt();
78
+ }
79
+
80
+ $cachedData = (float) $max;
81
+ $tags = $this->getLayer()->getStateTags();
82
+ $tags[] = self::CACHE_TAG;
83
+ Mage::app()->saveCache($cachedData, $cacheKey, $tags);
84
+ }
85
+
86
+ return $cachedData;
87
+ }
88
+
89
+ /**
90
+ * Apply price range filter to product collection.
91
+ *
92
+ * @return MDN_Antidot_Model_Catalog_Layer_Filter_Price
93
+ */
94
+ protected function _applyPriceRange()
95
+ {
96
+ $interval = $this->getInterval();
97
+ if (!$interval) {
98
+ return $this;
99
+ }
100
+
101
+ list($from, $to) = $interval;
102
+ if ($from === '' && $to === '') {
103
+ return $this;
104
+ }
105
+
106
+ if ($to !== '') {
107
+ $to = (float) $to;
108
+ if ($from == $to) {
109
+ $to += .01;
110
+ }
111
+ }
112
+
113
+ $field = $this->_getFilterField();
114
+ $value = array(
115
+ $field => array(
116
+ 'include_upper' => !($to < $this->getMaxPriceInt())
117
+ )
118
+ );
119
+
120
+ if (!empty($from)) {
121
+ $value[$field]['from'] = $from;
122
+ }
123
+ if (!empty($to)) {
124
+ $value[$field]['to'] = $to;
125
+ }
126
+
127
+ $this->getLayer()->getProductCollection()->addFqRangeFilter($value);
128
+
129
+ return $this;
130
+ }
131
+
132
+ /**
133
+ * Returns price field according to current customer group and website.
134
+ *
135
+ * @return string
136
+ */
137
+ protected function _getFilterField()
138
+ {
139
+ $websiteId = Mage::app()->getStore()->getWebsiteId();
140
+ $customerGroupId = Mage::getSingleton('customer/session')->getCustomerGroupId();
141
+ $priceField = 'price_' . $customerGroupId . '_' . $websiteId;
142
+
143
+ return $priceField;
144
+ }
145
+
146
+ /**
147
+ * Retrieves current items data.
148
+ *
149
+ * @return array
150
+ */
151
+ protected function _getItemsData()
152
+ {
153
+ if (Mage::app()->getStore()->getConfig(self::XML_PATH_RANGE_CALCULATION) == self::RANGE_CALCULATION_IMPROVED) {
154
+ return $this->_getCalculatedItemsData();
155
+ } elseif ($this->getInterval()) {
156
+ return array();
157
+ }
158
+
159
+ $data = array();
160
+ $facets = $this->getLayer()->getProductCollection()->getFacetedData($this->_getFilterField());
161
+ if (!empty($facets)) {
162
+ foreach ($facets as $key => $count) {
163
+ if (!$count) {
164
+ unset($facets[$key]);
165
+ }
166
+ }
167
+ $i = 0;
168
+ foreach ($facets as $key => $count) {
169
+ $i++;
170
+ preg_match('/^\[(\d*) TO (\d*)\]$/', $key, $rangeKey);
171
+ $fromPrice = $rangeKey[1];
172
+ $toPrice = ($i < count($facets)) ? $rangeKey[2] : '';
173
+ $data[] = array(
174
+ 'label' => $this->_renderRangeLabel($fromPrice, $toPrice),
175
+ 'value' => $fromPrice . '-' . $toPrice,
176
+ 'count' => $count
177
+ );
178
+ }
179
+ }
180
+
181
+ return $data;
182
+ }
183
+ }
app/code/community/MDN/Antidot/Model/Catalogsearch/Layer.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalogsearch_Layer extends Mage_CatalogSearch_Model_Layer
17
+ {
18
+
19
+ /**
20
+ * Return the product collection
21
+ *
22
+ * @return ProductCollection
23
+ */
24
+ public function getProductCollection()
25
+ {
26
+ $category = $this->getCurrentCategory();
27
+ if (isset($this->_productCollections[$category->getId()])) {
28
+ $collection = $this->_productCollections[$category->getId()];
29
+ } else {
30
+ $collection = Mage::helper('catalogsearch')
31
+ ->getEngine()
32
+ ->getResultCollection()
33
+ ->setStoreId($category->getStoreId());
34
+ $this->prepareProductCollection($collection);
35
+ $this->_productCollections[$category->getId()] = $collection;
36
+ }
37
+
38
+ return $collection;
39
+ }
40
+
41
+ /**
42
+ * Return the filterable attributes
43
+ *
44
+ * @return \MDN_Antidot_Model_Catalogsearch_Resource_Attribute
45
+ */
46
+ public function getFilterableAttributes()
47
+ {
48
+ $facets = array();
49
+ if($config = Mage::getStoreConfig('antidot/engine/facets')) {
50
+ $config = unserialize($config);
51
+ foreach($config as $facet) {
52
+ list($id, $label) = explode('|', $facet['facet']);
53
+ $key = $facet['order'].'_'.$id;
54
+ $facets[$key] = array('id' => $id, 'label' => $label);
55
+ }
56
+ }
57
+ ksort($facets);
58
+
59
+ $attributes = array();
60
+ foreach($facets as $facet) {
61
+ $attributes[] = new MDN_Antidot_Model_Catalogsearch_Resource_Attribute($facet);
62
+ }
63
+
64
+ return $attributes;
65
+ }
66
+ }
app/code/community/MDN/Antidot/Model/Catalogsearch/Layer/Filter/Attribute.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalogsearch_Layer_Filter_Attribute extends MDN_Antidot_Model_Catalog_Layer_Filter_Attribute
17
+ {
18
+ protected function _getIsFilterableAttribute($attribute)
19
+ {
20
+ return $attribute->getIsFilterableInSearch();
21
+ }
22
+ }
app/code/community/MDN/Antidot/Model/Catalogsearch/Resource/Attribute.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Catalogsearch_Resource_Attribute
17
+ {
18
+
19
+ public function __construct($facet)
20
+ {
21
+ $this->name = $facet['label'];
22
+ $this->id = $facet['id'];
23
+ }
24
+
25
+ public function getId()
26
+ {
27
+ return $this->id;
28
+ }
29
+
30
+ public function getAttributeCode()
31
+ {
32
+ return $this->id;
33
+ }
34
+
35
+ public function getName()
36
+ {
37
+ return $this->name;
38
+ }
39
+
40
+ public function getStoreLabel()
41
+ {
42
+ return $this->name;
43
+ }
44
+
45
+ public function getSourceModel()
46
+ {
47
+ return 'text';
48
+ }
49
+
50
+ public function getBackendType()
51
+ {
52
+ return '';
53
+ }
54
+
55
+ public function getFrontendInput()
56
+ {
57
+ return 'text';
58
+ }
59
+
60
+ public function usesSource()
61
+ {
62
+ return false;
63
+ }
64
+ }
app/code/community/MDN/Antidot/Model/Export/Abstract.php ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Abstract extends Mage_Core_Model_Abstract
17
+ {
18
+ /**
19
+ * Instance of XmlWriter
20
+ *
21
+ * @var XmlWriter
22
+ */
23
+ protected $xml;
24
+
25
+ /**
26
+ * List website loaded
27
+ *
28
+ * @var array
29
+ */
30
+ protected $website = array();
31
+
32
+ protected $storeLang = array();
33
+
34
+ /**
35
+ * The fields to load
36
+ *
37
+ * @var array
38
+ */
39
+ protected $fields = array();
40
+
41
+ protected $fieldsSerialized = array(
42
+ 'properties',
43
+ 'misc',
44
+ 'identifier',
45
+ 'description'
46
+ );
47
+
48
+ /**
49
+ * Init the xml writer
50
+ */
51
+ protected function initXml()
52
+ {
53
+ if($this->xml === null) {
54
+ $this->xml = Mage::helper('Antidot/XmlWriter');
55
+ $this->xml->init();
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Extract the uri from an url
61
+ *
62
+ * @param string $url
63
+ * @return string
64
+ */
65
+ protected function getUri($url)
66
+ {
67
+ $urls = parse_url($url);
68
+
69
+
70
+ return $urls['path'];
71
+ }
72
+
73
+ /**
74
+ * Init the fields
75
+ *
76
+ * @param string $section The section to load
77
+ */
78
+ protected function initFields($section)
79
+ {
80
+ $this->fields = array();
81
+ $values = Mage::getStoreConfig('antidot/fields_'.$section);
82
+ foreach($values as $key => $value) {
83
+ if(in_array($key, $this->fieldsSerialized) && $value = @unserialize($value)) {
84
+ $values = array_values($value);
85
+ foreach($values as $value) {
86
+ if($key !== 'properties') {
87
+ $this->fields[$key][] = $value['value'];
88
+ } else {
89
+ $this->fields[$key][] = $value;
90
+ }
91
+ }
92
+ continue;
93
+ }
94
+ $this->fields[$key] = $value;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Rertrieve a data from an entity
100
+ *
101
+ * @param Entity $entity
102
+ * @param string $field
103
+ * @return string
104
+ */
105
+ protected function getField($entity, $field)
106
+ {
107
+ $field = isset($this->fields[$field]) && !is_array($this->fields[$field]) ? $this->fields[$field] : $field;
108
+ if(empty($field)) {
109
+ return false;
110
+ }
111
+
112
+ $method = 'get'.ucfirst(strtolower($field));
113
+
114
+ return $entity->$method();
115
+ }
116
+
117
+ /**
118
+ * Get website by store
119
+ *
120
+ * @param Store $store
121
+ * @return WebSite
122
+ */
123
+ protected function getWebSiteByStore($store)
124
+ {
125
+ if(!isset($this->website[$store->getId()])) {
126
+ $this->website[$store->getId()] = Mage::getModel('core/website')->load($store->getWebSiteId());
127
+ }
128
+
129
+ return $this->website[$store->getId()];
130
+ }
131
+
132
+ protected function getStoreLang($storeId)
133
+ {
134
+ if(!isset($this->storeLang[$storeId])) {
135
+ list($this->storeLang[$storeId]) = explode('_', Mage::getStoreConfig('general/locale/code', $storeId));
136
+ }
137
+
138
+ return $this->storeLang[$storeId];
139
+ }
140
+ }
app/code/community/MDN/Antidot/Model/Export/Article.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Article extends MDN_Antidot_Model_Export_Product
17
+ {
18
+
19
+ const TYPE = 'ARTICLE';
20
+ const FILENAME_XML = 'articles-mdn-fr.xml';
21
+ const FILENAME_ZIP = '%s_full_mdn_articles.zip';
22
+ const XSD = 'http://ref.antidot.net/store/latest/articles.xsd';
23
+
24
+ const imagePrefix = 'media/catalog/article';
25
+
26
+ const ARTICLE_LIMIT = 1000;
27
+
28
+ /**
29
+ * Write the xml file
30
+ *
31
+ * @param array $context
32
+ * @param string $filename
33
+ */
34
+ public function writeXml($context, $filename)
35
+ {
36
+ $this->initXml();
37
+ $this->initFields('article');
38
+ $this->setFilename($filename);
39
+
40
+
41
+
42
+ $this->xml->push('articles', array('xmlns' => "http://ref.antidot.net/store/afs#"));
43
+ $this->writeHeader($context);
44
+ $this->writePart($this->xml->flush());
45
+
46
+ foreach($context['store_id'] as $storeId) {
47
+ $store = Mage::getModel('core/store')->load($storeId);
48
+ $page = 1;
49
+ while($articles = $this->getProducts($store, $page, self::ARTICLE_LIMIT)) {
50
+ foreach($articles as $article) {
51
+ $this->xml->push('article', array('id' => $article->getId(), 'xml:lang' => $context['lang']));
52
+
53
+ $this->xml->push('websites');
54
+ $this->xml->element('website', $store->getWebsite()->getName(), array('id' => $store->getWebsite()->getId()));
55
+ $this->xml->pop();
56
+
57
+ $this->xml->element('created_at', $article->getCreated_at());
58
+ $this->xml->element('last_updated_at', $article->getUpdated_at());
59
+ //$this->xml->element('published_at', $article->getPublished_at());
60
+
61
+ $this->xml->element('title', $this->xml->encloseCData($this->getField($article, 'title')));
62
+ $this->xml->element('subtitle', $this->xml->encloseCData($this->getField($article, 'subtitle')));
63
+ $this->xml->element('type', $this->xml->encloseCData($this->getField($article, 'type')));
64
+ $this->xml->element('text', $this->xml->encloseCData($this->getField($article, 'text')));
65
+
66
+ $this->writeDescriptions($article);
67
+ $this->writeIdentifiers($article);
68
+ $this->writeClassification($article);
69
+ //$this->writeBrands($article);
70
+
71
+ $this->writeUrl($article, false);
72
+ $this->writeMisc($article);
73
+
74
+ $this->xml->pop();
75
+ }
76
+ $page++;
77
+
78
+ $this->writePart($this->xml->flush());
79
+ }
80
+ }
81
+ $this->xml->pop();
82
+
83
+ $this->writePart($this->xml->flush(), true);
84
+ }
85
+
86
+ /**
87
+ * Write the xml header
88
+ *
89
+ * @param array $context
90
+ */
91
+ protected function writeHeader($context)
92
+ {
93
+ $this->xml->push('header');
94
+ $this->xml->element('owner', $context['owner']);
95
+ $this->xml->element('feed', 'article');
96
+ $this->xml->element('generated_at', date('c', Mage::getModel('core/date')->timestamp(time())));
97
+ $this->xml->pop();
98
+ }
99
+
100
+ /**
101
+ * Write the article identifiers
102
+ *
103
+ * @param Product $article
104
+ */
105
+ protected function writeBrands($article)
106
+ {
107
+ if ($manufacturer = $this->getField($article, 'manufacturer')) {
108
+ $this->xml->push('brands');
109
+ $brandUrl = Mage::getModel('Antidot/Export_Brand')->getUrl($article->getAttributeText('manufacturer'));
110
+ $this->xml->element('brand', $this->xml->encloseCData($article->getAttributeText('manufacturer')), array('id' => $manufacturer, 'url' => $brandUrl));
111
+ $this->xml->pop();
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Get articles to generate
117
+ *
118
+ * @param int $store
119
+ * @param int $page
120
+ * @param int $limit
121
+ * @return array
122
+ */
123
+ protected function getArticles($store, $page, $limit)
124
+ {
125
+ return Mage::getModel('cms/page')
126
+ ->getCollection()
127
+ ->addStoreFilter($store->getId())
128
+ ->addAttributeToSelect('*')
129
+ ->setPage($page, $limit)
130
+ ;
131
+ }
132
+ }
app/code/community/MDN/Antidot/Model/Export/Brand.php ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Brand extends MDN_Antidot_Model_Export_Abstract
17
+ {
18
+ const TYPE = 'BRAND';
19
+ const FILENAME_XML = 'brands-mdn-fr.xml';
20
+ const FILENAME_ZIP = '%s_full_mdn_brands.zip';
21
+ const XSD = 'http://ref.antidot.net/store/latest/brands.xsd';
22
+
23
+ const PATTERN_URL = '/brands/{brand}';
24
+
25
+ protected $urlHelper;
26
+
27
+ /**
28
+ * Get xml
29
+ *
30
+ * @param type $context
31
+ */
32
+ public function getXml($context)
33
+ {
34
+ $this->initXml();
35
+ $this->initFields('brand');
36
+
37
+ $brandPattern = $this->getBrandPattern();
38
+
39
+ $this->xml->push('brands', array('xmlns' => "http://ref.antidot.net/store/afs#"));
40
+
41
+ $this->xml->push('header');
42
+ $this->xml->element('owner', $context['owner']);
43
+ $this->xml->element('feed', 'brand');
44
+ $this->xml->element('generated_at', date('c', Mage::getModel('core/date')->timestamp(time())));
45
+ $this->xml->pop();
46
+
47
+ foreach($context['store_id']as $storeId) {
48
+ $store = Mage::getModel('core/store')->load($storeId);
49
+ foreach($this->getBrands() as $brandId => $brand) {
50
+ $this->xml->push('brand', array('id' => $brandId, 'xml:lang' => $context['lang']));
51
+
52
+ $this->xml->element('name', $this->xml->encloseCData($brand));
53
+ $this->xml->element('url', $this->getUrl($brand, $brandPattern));
54
+
55
+ $this->xml->push('websites');
56
+ $this->xml->element('website', '', array('id' => $storeId, 'name' => $store->getName()));
57
+ $this->xml->pop();
58
+
59
+ $this->xml->pop();
60
+ }
61
+ }
62
+
63
+ $this->xml->pop();
64
+
65
+ return $this->xml->getXml();
66
+ }
67
+
68
+ /**
69
+ * Return categories
70
+ *
71
+ * @param int $rootCategoryId
72
+ * @param array
73
+ */
74
+ protected function getBrands()
75
+ {
76
+ $attribute = Mage::getModel('eav/config')
77
+ ->getAttribute('catalog_product', 'manufacturer');
78
+
79
+ $brands = array();
80
+ foreach($attribute->getSource()->getAllOptions(true, true) as $option) {
81
+ if(!empty($option['value'])) {
82
+ $brands[$option['value']] = $option['label'];
83
+ }
84
+ }
85
+
86
+ return $brands;
87
+ }
88
+
89
+ /**
90
+ * Return brand url
91
+ *
92
+ * @param string $brand
93
+ * @return string
94
+ */
95
+ public function getUrl($brand, $brandPattern = null)
96
+ {
97
+ $brandPattern = $brandPattern === null ? $this->getBrandPattern() : $brandPattern;
98
+
99
+ return preg_replace('/\{brand\}/', $this->getUrlHelper()->url($brand), $brandPattern);
100
+ }
101
+
102
+ /**
103
+ * Get the brand pattern
104
+ *
105
+ * @return string
106
+ */
107
+ protected function getBrandPattern()
108
+ {
109
+ $brandPattern = Mage::getStoreConfig('antidot/general/brand');
110
+ if(strpos($brandPattern, '{brand}') === false) {
111
+ $brandPattern = self::PATTERN_URL;
112
+ }
113
+
114
+ return $brandPattern;
115
+ }
116
+
117
+ /**
118
+ * Return url Helper
119
+ *
120
+ * @return Antidot/Url
121
+ */
122
+ protected function getUrlHelper()
123
+ {
124
+ if($this->urlHelper === null) {
125
+ $this->urlHelper = Mage::helper('Antidot/Url');
126
+ }
127
+
128
+ return $this->urlHelper;
129
+ }
130
+ }
app/code/community/MDN/Antidot/Model/Export/Category.php ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Category extends MDN_Antidot_Model_Export_Abstract
17
+ {
18
+ const TYPE = 'CATEGORY';
19
+ const FILENAME_XML = 'categories-mdn-%s.xml';
20
+ const FILENAME_ZIP = '%s_full_mdn_categories.zip';
21
+ const XSD = 'http://ref.antidot.net/store/latest/categories.xsd';
22
+
23
+ /**
24
+ * Get xml
25
+ *
26
+ * @param type $context
27
+ */
28
+ public function writeXml($context, $filename)
29
+ {
30
+ $this->initXml();
31
+ $this->initFields('category');
32
+
33
+ $this->xml->push('categories', array('xmlns' => "http://ref.antidot.net/store/afs#"));
34
+
35
+ $this->xml->push('header');
36
+ $this->xml->element('owner', $context['owner']);
37
+ $this->xml->element('feed', 'category');
38
+ $this->xml->element('generated_at', date('c', Mage::getModel('core/date')->timestamp(time())));
39
+ $this->xml->pop();
40
+
41
+ $nbItems = 0;
42
+ foreach($context['stores'] as $store) {
43
+ foreach($this->getCategories($store) as $cat) {
44
+ $this->xml->push('category', array('id' => $cat->getId(), 'xml:lang' => $context['lang']));
45
+
46
+ $this->xml->element('name', $this->xml->encloseCData($this->getField($cat, 'name')));
47
+ $this->xml->element('url', $this->getUri($cat->getUrl()));
48
+
49
+ if ($cat->getImageUrl()) {
50
+ $this->xml->element('image', $cat->getImageUrl());
51
+ }
52
+
53
+ if ($keywords = $this->getField($cat, 'keywords')) {
54
+ $this->xml->element('keywords', $this->xml->encloseCData($keywords));
55
+ }
56
+
57
+ if ($description = $this->getField($cat, 'description')) {
58
+ $this->xml->element('description', $this->xml->encloseCData($description));
59
+ }
60
+
61
+ if ($cat->getProductCount() > 0) {
62
+ $this->xml->element('productsCount', $cat->getProductCount());
63
+ }
64
+
65
+ if ($cat->getParentId() && ($cat->getParentId() != $store->getRootCategoryId())) {
66
+ $this->xml->emptyelement('broader', array('idref' => $cat->getParentId()));
67
+ }
68
+
69
+ $storeIds = array_intersect($context['store_id'], $cat->getStoreIds());
70
+ $this->xml->push('websites');
71
+ foreach($storeIds as $storeId) {
72
+ $website = $this->getWebSiteByStore($context['stores'][$storeId]);
73
+ $this->xml->element('website', '', array('id' => $website->getId(), 'name' => $website->getName()));
74
+ }
75
+ $this->xml->pop();
76
+
77
+ $this->xml->pop();
78
+
79
+ $nbItems++;
80
+ }
81
+ }
82
+ $this->xml->pop();
83
+
84
+ file_put_contents($filename, $this->xml->flush());
85
+
86
+ return $nbItems;
87
+ }
88
+
89
+ /**
90
+ * Return categories
91
+ *
92
+ * @param Store $store
93
+ * @return array
94
+ */
95
+ protected function getCategories($store)
96
+ {
97
+ return Mage::getModel('catalog/category')
98
+ ->getCollection()
99
+ ->setStoreId($store->getId())
100
+ ->addAttributeToSelect('url_key')
101
+ ->addAttributeToSelect('name')
102
+ ->addAttributeToFilter('is_active', 1)
103
+ ->addFieldToFilter('path', array('like' => Mage::getModel('catalog/category')->load($store->getRootCategoryId())->getPath().'/%'))
104
+ ;
105
+ }
106
+ }
app/code/community/MDN/Antidot/Model/Export/Product.php ADDED
@@ -0,0 +1,818 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Export_Product extends MDN_Antidot_Model_Export_Abstract
17
+ {
18
+
19
+ const TYPE = 'CATALOG';
20
+ const FILENAME_XML = 'catalog-mdn-%s.xml';
21
+ const FILENAME_ZIP = '%s_full_mdn_catalog.zip';
22
+ const FILENAME_ZIP_INC = '%s_inc_mdn_catalog.zip';
23
+ const XSD = 'http://ref.antidot.net/store/latest/catalog.xsd';
24
+
25
+ const PRODUCT_LIMIT = 1000;
26
+
27
+ protected $file;
28
+
29
+ protected $productGenerated = array();
30
+
31
+ protected $categories = array();
32
+
33
+ protected $onlyProductsWithStock;
34
+
35
+ protected $autoCompleteProducts;
36
+
37
+ protected $propertyLabel = array();
38
+
39
+ protected $productVisible = array(
40
+ Mage_Catalog_Model_Product_Visibility::VISIBILITY_IN_SEARCH,
41
+ Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH,
42
+ );
43
+
44
+ protected $productMultiple = array(
45
+ Mage_Catalog_Model_Product_Type::TYPE_BUNDLE,
46
+ Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE
47
+ );
48
+
49
+ /**
50
+ * Write the xml file
51
+ *
52
+ * @param array $context
53
+ * @param string $filename
54
+ * @param string $type Incremantal or full
55
+ * @return int nb items generated
56
+ */
57
+ public function writeXml($context, $filename, $type)
58
+ {
59
+ $db = Mage::getSingleton('core/resource')->getConnection('core_read');
60
+ $db->getProfiler()->setEnabled(false);
61
+
62
+ $this->onlyProductsWithStock = !(boolean)Mage::getStoreConfig('antidot/fields_product/in_stock_only');
63
+ $this->autoCompleteProducts = Mage::getStoreConfig('antidot/suggest/enable') === 'Antidot/engine_antidot' ? 'on' : 'off';
64
+
65
+ $this->initXml();
66
+ $this->initPropertyLabel();
67
+ $this->initFields('product');
68
+ $this->setFilename($filename);
69
+
70
+ $this->xml->push('catalog', array('xmlns' => "http://ref.antidot.net/store/afs#"));
71
+ $this->writeHeader($context);
72
+ $this->writePart($this->xml->flush());
73
+
74
+ $this->lang = $context['lang'];
75
+ $productIds = $this->getProductIds($context['store_id'], $type);
76
+ foreach(array_chunk($productIds, 500) as $productId) {
77
+ $collection = Mage::getModel('catalog/product')
78
+ ->getCollection()
79
+ ->addAttributeToSelect('*')
80
+ ->addAttributeToFilter('entity_id', array('in', $productId))
81
+ ->joinField('qty',
82
+ 'cataloginventory/stock_item',
83
+ 'qty',
84
+ 'product_id = entity_id')
85
+ ;
86
+
87
+ foreach($collection as $product) {
88
+ if($context['langs'] > 1) {
89
+ $store = current($this->getProductStores($product, $context));
90
+ $product = Mage::getModel('catalog/product')->setStoreId($store->getId())->load($product->getId());
91
+ }
92
+ $this->writeProduct($product, $context);
93
+ }
94
+ $this->writePart($this->xml->flush());
95
+ }
96
+ $this->xml->pop();
97
+
98
+ $this->writePart($this->xml->flush(), true);
99
+
100
+ return count($productIds);
101
+ }
102
+
103
+ /**
104
+ * Init properties label
105
+ */
106
+ protected function initPropertyLabel()
107
+ {
108
+ $attributes = Mage::getResourceModel('catalog/product_attribute_collection');
109
+ foreach($attributes as $att) {
110
+ $k = $att->getAttributeCode();
111
+ $this->propertyLabel[$k] = array();
112
+ $this->propertyLabel[$k]['default'] = $att->getfrontend_label();
113
+ $this->propertyLabel[$k]['per_store'] = $att->getStoreLabels();
114
+
115
+ $this->propertyLabel[$k]['options'] = array();
116
+ $options = $att->getSource()->getAllOptions(true);
117
+ foreach($options as $option) {
118
+ if (empty($option['value']) || is_array($option['value'])) {
119
+ continue;
120
+ }
121
+
122
+ $this->propertyLabel[$k]['options'][$option['value']] = array();
123
+ $this->propertyLabel[$k]['options'][$option['value']]['per_store'] = array();
124
+ $query = 'SELECT store_id, value FROM '
125
+ . Mage::getConfig()->getTablePrefix().'eav_attribute_option_value '
126
+ . 'WHERE option_id = "'.$option['value'].'"';
127
+
128
+ $valuesCollection = mage::getResourceModel('sales/order_item_collection')->getConnection()->fetchAll($query);
129
+ foreach($valuesCollection as $item) {
130
+ $this->propertyLabel[$k]['options'][$option['value']]['per_store'][$item['store_id']] = $item['value'];
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Write the xml header
138
+ *
139
+ * @param array $context
140
+ */
141
+ protected function writeHeader($context)
142
+ {
143
+ $this->xml->push('header');
144
+ $this->xml->element('owner', $context['owner']);
145
+ $this->xml->element('feed', 'product');
146
+ $this->xml->element('generated_at', date('c', Mage::getModel('core/date')->timestamp(time())));
147
+ $this->xml->pop();
148
+ }
149
+
150
+ /**
151
+ * Write the product
152
+ *
153
+ * @param Product $product
154
+ * @param Array $context
155
+ */
156
+ protected function writeProduct($product, $context)
157
+ {
158
+ $stores = $this->getProductStores($product, $context);
159
+
160
+ $this->xml->push('product', array('id' => $product->getId(), 'xml:lang' => $this->lang, 'autocomplete' => $this->autoCompleteProducts));
161
+
162
+ $this->xml->push('websites');
163
+ foreach($stores as $store) {
164
+ $website = $this->getWebSiteByStore($store);
165
+ $this->xml->element('website', $website->getName(), array('id' => $website->getId()));
166
+ }
167
+ $this->xml->pop();
168
+
169
+ $this->xml->element('created_at', $product->getCreated_at());
170
+ $this->xml->element('last_updated_at', $product->getUpdated_at());
171
+
172
+ $this->xml->element('name', $this->xml->encloseCData($this->getField($product, 'name')));
173
+ if($shortName = $this->getField($product, 'short_name')) {
174
+ $this->xml->element('short_name', $this->xml->encloseCData(mb_substr($shortName, 0, 45, 'UTF-8')), array('autocomplete' => 'off'));
175
+ }
176
+
177
+ if ($keywords = $this->getField($product, 'keywords')) {
178
+ $this->xml->element('keywords', $this->xml->encloseCData($keywords));
179
+ }
180
+ $this->writeDescriptions($product);
181
+ $this->xml->element('url', $this->xml->encloseCData($product->getProductUrl()));
182
+ $this->writeImageUrl($product);
183
+ $this->writeClassification($product);
184
+ $this->writeProperties($product, $stores);
185
+ $this->writeBrand($product);
186
+ $this->writeMaterials($product);
187
+ $this->writeColors($product);
188
+ $this->writeModels($product);
189
+ $this->writeSizes($product);
190
+ $this->writeGenders($product);
191
+ $this->writeMisc($product);
192
+
193
+ $this->writeVariants($product, $stores);
194
+
195
+ $this->xml->pop();
196
+ }
197
+
198
+ /**
199
+ * Write the store's informations
200
+ *
201
+ * @param Product $product
202
+ * @param array $stores
203
+ */
204
+ protected function writeStore($product, $stores, $variantProduct)
205
+ {
206
+ $this->xml->push('stores');
207
+ foreach($stores as $store) {
208
+ Mage::app()->setCurrentStore($store->getId());
209
+
210
+ $this->xml->push('store', array('id' => $store->getId(), 'name' => $store->getName()));
211
+ $storeContext['currency'] = $store->getCurrentCurrencyCode();
212
+ $storeContext['country'] = $this->getStoreLang($store->getId());
213
+
214
+ $operations = $this->getOperations($product, $store);
215
+ $this->writePrices($variantProduct, $storeContext, $store, $operations);
216
+ $this->writeMarketing($variantProduct, $operations);
217
+
218
+ $isAvailable = $variantProduct->isSalable() || (in_array($variantProduct->getTypeId(), $this->productMultiple) && $product->isInStock());
219
+ $this->xml->element('is_available', (int)$isAvailable);
220
+
221
+ $qty = Mage::getModel('cataloginventory/stock_item')->loadByProduct($variantProduct)->getQty();
222
+ $this->xml->element('stock', (int)$qty);
223
+
224
+ $this->xml->element('url', $this->xml->encloseCData($variantProduct->getProductUrl()));
225
+ $this->xml->pop();
226
+ }
227
+ $this->xml->pop();
228
+ }
229
+
230
+ /**
231
+ * Get catalog/product model
232
+ *
233
+ * @return Model
234
+ */
235
+ protected function getCatalogProduct()
236
+ {
237
+ if(!$this->catalogProduct) {
238
+ $this->catalogProduct = Mage::getModel('catalog/product');
239
+ }
240
+
241
+ return $this->catalogProduct;
242
+ }
243
+
244
+ /**
245
+ * Get product stores
246
+ *
247
+ * @param Product $product
248
+ * @param array $context
249
+ */
250
+ protected function getProductStores($product, $context)
251
+ {
252
+ $stores = array();
253
+
254
+ $storeIds = array_intersect($product->getStoreIds(), $context['store_id']);
255
+ foreach($storeIds as $storeId) {
256
+ $stores[] = $context['stores'][$storeId];
257
+ }
258
+
259
+ return $stores;
260
+ }
261
+
262
+ /**
263
+ * Write the product descriptions
264
+ *
265
+ * @param Product $product
266
+ */
267
+ protected function writeDescriptions($product)
268
+ {
269
+ if(!empty($this->fields['description'])) {
270
+ $this->xml->push('descriptions');
271
+ foreach($this->fields['description'] as $description) {
272
+ if ($value = $this->getField($product, $description)) {
273
+ $this->xml->element('description', $this->xml->encloseCData(substr($value, 0, 20000)), array('type' => $description));
274
+ }
275
+ }
276
+ $this->xml->pop();
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Write the product identifiers
282
+ *
283
+ * @param Product $product
284
+ */
285
+ protected function writeIdentifiers($product)
286
+ {
287
+ if($gtin = $this->getField($product, 'gtin')) {
288
+ if(!preg_match('/^[0-9]{12,14}$/', $gtin)) {
289
+ $gtin = false;
290
+ }
291
+ }
292
+
293
+ $identifiers = array();
294
+ if(!empty($this->fields['identifier'])) {
295
+ foreach($this->fields['identifier'] as $identifier) {
296
+ if ($value = $this->getField($product, $identifier)) {
297
+ $identifiers[$identifier] = mb_substr($value, 0, 40, 'UTF-8');
298
+ }
299
+ }
300
+ }
301
+
302
+ if($gtin ||!empty($identifiers)) {
303
+ $this->xml->push('identifiers');
304
+ if($gtin) {
305
+ $this->xml->element('gtin', $gtin);
306
+ }
307
+
308
+ if(!empty($identifiers)) {
309
+ foreach($identifiers as $identifier => $value) {
310
+ $this->xml->element('identifier', $value, array('type' => $identifier));
311
+ }
312
+ }
313
+
314
+ $this->xml->pop();
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Write the product identifiers
320
+ *
321
+ * @param Product $product
322
+ */
323
+ protected function writeBrand($product)
324
+ {
325
+ if ($manufacturer = $this->getField($product, 'manufacturer')) {
326
+ if(!empty($manufacturer)) {
327
+ $field = empty($this->fields['manufacturer']) ? 'manufacturer' : $this->fields['manufacturer'];
328
+ $brand = mb_substr($product->getAttributeText($field), 0, 40, 'UTF-8');
329
+ $brandUrl = Mage::helper('catalogsearch')->getResultUrl($brand);
330
+ $brandUrl = parse_url($brandUrl, PHP_URL_PATH).'?'.parse_url($brandUrl, PHP_URL_QUERY);
331
+ if(!empty($brand)) {
332
+ $this->xml->element('brand', $this->xml->encloseCData($brand), array('id' => $manufacturer, 'url' => $brandUrl));
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Write the product urls
340
+ *
341
+ * @param Product $product
342
+ * @param string $urlImg
343
+ */
344
+ protected function writeImageUrl($product, $urlImg = true)
345
+ {
346
+ try {
347
+ if ($product->getThumbnail()) {
348
+ $this->xml->element('url_thumbnail', $this->xml->encloseCData(Mage::getModel('catalog/product_media_config')->getMediaUrl($product->getThumbnail())));
349
+ }
350
+ } catch(Exception $e) {}
351
+
352
+ try {
353
+ if ($urlImg && $product->getImage()) {
354
+ $this->xml->element('url_image', $this->xml->encloseCData(Mage::getModel('catalog/product_media_config')->getMediaUrl($product->getImage())));
355
+ }
356
+ } catch(Exception $e) {}
357
+ }
358
+
359
+ /**
360
+ * Write the product classification
361
+ *
362
+ * @param Product $product
363
+ */
364
+ protected function writeClassification($product)
365
+ {
366
+ $categories = $this->getProductCategories($product);
367
+ if(count($categories) > 0) {
368
+ $this->xml->push('classification');
369
+ foreach($categories as $category) {
370
+ $this->writeCategory($category);
371
+ }
372
+ $this->xml->pop();
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Write category node with their parents
378
+ *
379
+ * @param $category
380
+ * @param bool $first
381
+ * @param int $level
382
+ */
383
+ protected function writeCategory($category, $first = true, &$level = 0)
384
+ {
385
+ if($category->getParentId() !== 1 && $category->getParentId() !== 0) {
386
+ $level++;
387
+ if(!$this->writeCategory($this->getCategoryById($category->getParentId()), false, $level)) {
388
+ $level--;
389
+ }
390
+ }
391
+
392
+ $attributes = array('id' => $category->getId(), 'label' => $category->getName(), 'url' => $this->getUri($category->getUrl()));
393
+ if ($category->getImage()) {
394
+ $attributes['img'] = $category->getImage();
395
+ }
396
+ $this->xml->push('category', $attributes);
397
+
398
+ if($first) {
399
+ // close xml elements
400
+ for($i = 0; $i <= $level; $i++) {
401
+ $this->xml->pop();
402
+ }
403
+ }
404
+
405
+ return true;
406
+ }
407
+
408
+ /**
409
+ * Get category by id
410
+ *
411
+ * @param int $categoryId
412
+ * @return Category
413
+ */
414
+ protected function getCategoryById($categoryId)
415
+ {
416
+ if(!isset($this->categories[$categoryId])) {
417
+ $this->categories[$categoryId] = Mage::getModel('catalog/category')->load($categoryId);
418
+ }
419
+
420
+ return $this->categories[$categoryId];
421
+ }
422
+
423
+ /**
424
+ * Return the top level category
425
+ *
426
+ * @param Product $product
427
+ * @return array
428
+ */
429
+ protected function getProductCategories($product)
430
+ {
431
+ $categories = $product->getCategoryCollection()->setStoreId($product->getStoreId())->addAttributeToSelect('name')->addAttributeToSelect('image')->addAttributeToSelect('url_key');
432
+ $productCategories = array();
433
+ foreach($categories as $category) {
434
+ $productCategories[$category->getId()] = $category;
435
+ $parentCategory[] = $category->getParentId();
436
+ }
437
+
438
+ foreach($productCategories as $category) {
439
+ if(in_array($category->getParentId(), $parentCategory)) {
440
+ unset($productCategories[$category->getParentId()]);
441
+ }
442
+ }
443
+
444
+ return $productCategories;
445
+ }
446
+
447
+ /**
448
+ * Write the product materials
449
+ *
450
+ * @param Product $product
451
+ */
452
+ protected function writeMaterials($product)
453
+ {
454
+ if(!empty($this->fields['materials']) && $materials = $this->getField($product, $this->fields['materials'])) {
455
+ $this->xml->push('materials');
456
+ $this->xml->element('material', $this->xml->encloseCData($materials));
457
+ $this->xml->pop();
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Write the product colors
463
+ *
464
+ * @param Product $product
465
+ */
466
+ protected function writeColors($product)
467
+ {
468
+ if(!empty($this->fields['colors']) && $color = $product->getAttributeText($this->fields['colors'])) {
469
+ $this->xml->push('colors');
470
+ $this->xml->element('color', $this->xml->encloseCData($color));
471
+ $this->xml->pop();
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Write the product models
477
+ *
478
+ * @param Product $product
479
+ */
480
+ protected function writeModels($product)
481
+ {
482
+ if(!empty($this->fields['models']) && $models = $this->getField($product, $this->fields['models'])) {
483
+ $this->xml->push('models', array('autocomplete' => 'off'));
484
+ $this->xml->element('model', $this->xml->encloseCData(substr($models, 0, 40)));
485
+ $this->xml->pop();
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Write the product sizes
491
+ *
492
+ * @param Product $product
493
+ */
494
+ protected function writeSizes($product)
495
+ {
496
+ if(!empty($this->fields['sizes']) && $size = $product->getAttributeText($this->fields['sizes'])) {
497
+ $this->xml->push('sizes');
498
+ $this->xml->element('size', $this->xml->encloseCData($size));
499
+ $this->xml->pop();
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Write the product genders
505
+ *
506
+ * @param Product $product
507
+ */
508
+ protected function writeGenders($product)
509
+ {
510
+ if(!empty($this->fields['gender']) && $gender = $product->getAttributeText($this->fields['gender'])) {
511
+ $this->xml->push('audience');
512
+ $this->xml->push('genders');
513
+ $this->xml->element('gender', $this->xml->encloseCData($gender));
514
+ $this->xml->pop();
515
+ $this->xml->pop();
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Write the product properties
521
+ *
522
+ * @param Product $product
523
+ * @param array $stores List product store
524
+ */
525
+ protected function writeProperties($product, $stores)
526
+ {
527
+ $properties = array();
528
+ if(!empty($this->fields['properties'])) {
529
+ foreach($this->fields['properties'] as $property) {
530
+ $id = $this->getField($product, $property['value']);
531
+ if($id !== null) {
532
+ $value = $product->getResource()->getAttribute($property['value'])->getFrontend()->getValue($product);
533
+ $label = $product->getResource()->getAttribute($property['value'])->getStoreLabel();
534
+ $optionName = $value;
535
+ if(!empty($this->propertyLabel[$property['value']]['options'][$id]['per_store'][current($stores)->getId()])) {
536
+ $optionName = $this->propertyLabel[$property['value']]['options'][$id]['per_store'][current($stores)->getId()];
537
+ }
538
+
539
+ $value = is_bool($value) ? (int)$value : $value;
540
+ $properties[] = array(
541
+ 'name' => $property['value'],
542
+ 'display_name' => substr($label, 0, 79),
543
+ 'label' => substr($optionName, 0, 79),
544
+ 'autocomplete' => ($property['autocomplete'] == 1 ? 'on' : 'off')
545
+ );
546
+ }
547
+ }
548
+ }
549
+
550
+ if(!empty($properties)) {
551
+ $this->xml->push('properties');
552
+ foreach($properties as $property) {
553
+ $this->xml->emptyelement('property', $property);
554
+ }
555
+ $this->xml->pop();
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Write the product prices
561
+ *
562
+ * @param Product $product
563
+ */
564
+ protected function writePrices($product, $context, $store, $operations)
565
+ {
566
+ $prices = current($this->getPrices($product->getId(), $store->getWebsiteId()));
567
+
568
+ if($product->getTypeID() === Mage_Catalog_Model_Product_Type::TYPE_BUNDLE) {
569
+ $prices['price'] = $prices['min_price'];
570
+ }
571
+
572
+ $price = Mage::helper('tax')->getPrice($product, $prices['price'], true);
573
+ if($operations) {
574
+ $operation = current($operations);
575
+ if($operation['action_operator'] === 'by_percent') {
576
+ $amount = $prices['price'] * $operation['action_amount'] / 100;
577
+ } else {
578
+ $amount = $operation['action_amount'];
579
+ }
580
+
581
+ $priceCut = $price;
582
+ $price = Mage::helper('tax')->getPrice($product, $prices['price'] - $amount, true);
583
+ $finalPrice = Mage::helper('tax')->getPrice($product, $prices['final_price'], true);
584
+ if($finalPrice < $price) {
585
+ $price = $finalPrice;
586
+ }
587
+
588
+ $priceCut = Mage::helper('directory')->currencyConvert($priceCut, Mage::app()->getStore()->getCurrentCurrencyCode(), $store->getCurrentCurrencyCode());
589
+ }
590
+ $price = Mage::helper('directory')->currencyConvert($price, Mage::app()->getStore()->getCurrentCurrencyCode(), $store->getCurrentCurrencyCode());
591
+
592
+ $this->xml->push('prices');
593
+ $this->xml->element(
594
+ 'price',
595
+ round($price, 2),
596
+ array('currency' => $context['currency'], 'type' => 'PRICE_FINAL', 'vat_included' => 'true', 'country' => strtoupper($context['country']))
597
+ );
598
+
599
+
600
+ if(isset($priceCut)) {
601
+ $this->xml->element(
602
+ 'price',
603
+ round($priceCut, 2),
604
+ array('currency' => $context['currency'], 'type' => 'PRICE_CUT', 'vat_included' => 'true', 'country' => strtoupper($context['country']))
605
+ );
606
+
607
+ }
608
+
609
+ $this->xml->pop();
610
+ }
611
+
612
+ /**
613
+ * Get product's prices
614
+ *
615
+ * @param int $productId
616
+ * @param int $websiteId
617
+ * @return array
618
+ */
619
+ protected function getPrices($productId, $websiteId)
620
+ {
621
+ $query = "SELECT price, final_price, min_price "
622
+ . "FROM catalog_product_index_price "
623
+ . "WHERE entity_id = ".(int)$productId." "
624
+ . "AND website_id = ".(int)$websiteId." "
625
+ . "AND customer_group_id = 0 "
626
+ ;
627
+
628
+ return Mage::getSingleton('core/resource')->getConnection('core_read')->fetchAll($query);
629
+ }
630
+
631
+ /**
632
+ * Write the marketing elements
633
+ *
634
+ * @param Product $product
635
+ * @param array $operations
636
+ */
637
+ protected function writeMarketing($product, $operations)
638
+ {
639
+ $this->xml->push('marketing');
640
+ $this->xml->element('is_new', ($this->getField($product, 'is_new') ? 1 : 0));
641
+ $this->xml->element('is_best_sale', ($this->getField($product, 'is_best_sale') ? 1 : 0));
642
+ $this->xml->element('is_featured', ($this->getField($product, 'is_featured') ? 1 : 0));
643
+
644
+ $isPromotional = false;
645
+ foreach($operations as $operation) {
646
+ $isPromotional = true;
647
+ $this->xml->element('operation', 1, array('display_name' => $operation['name'], 'name' => 'OPERATION_'.$operation['rule_id']));
648
+ }
649
+ $this->xml->element('is_promotional', (int)$isPromotional);
650
+
651
+ $this->xml->pop();
652
+ }
653
+
654
+ /**
655
+ * Get operations from $product
656
+ *
657
+ * @param Product $product
658
+ * @param Store $store
659
+ * @return array
660
+ */
661
+ protected function getOperations($product, $store)
662
+ {
663
+ $date = date('Y-m-d');
664
+ $query = "SELECT catalogrule.name, catalogrule.rule_id, action_operator, action_amount "
665
+ . "FROM catalogrule_product "
666
+ . "JOIN catalogrule ON catalogrule_product.rule_id = catalogrule.rule_id "
667
+ . "WHERE product_id = ".(int)$product->getId()." "
668
+ . "AND website_id = ".$store->getWebSiteId()." "
669
+ . "AND from_date < '".$date."' "
670
+ . "AND to_date > '".$date."' "
671
+ . "AND customer_group_id = 0 "
672
+ ;
673
+
674
+ return Mage::getSingleton('core/resource')->getConnection('core_read')->fetchAll($query);
675
+ }
676
+
677
+ /**
678
+ * Write the dynamic elements
679
+ *
680
+ * @param Product $product
681
+ */
682
+ protected function writeMisc($product)
683
+ {
684
+ $this->xml->push('misc');
685
+ $this->xml->element('product_type', $this->xml->encloseCData($product->getTypeID()));
686
+ if(!empty($this->fields['misc'])) {
687
+ foreach($this->fields['misc'] as $misc) {
688
+ $this->xml->element($misc, $this->xml->encloseCData($this->getField($product, $misc)));
689
+ }
690
+ }
691
+ $this->xml->pop();
692
+ }
693
+
694
+ /**
695
+ * Write variants produt
696
+ *
697
+ * @param Product $product
698
+ * @param array $stores
699
+ */
700
+ protected function writeVariants($product, $stores)
701
+ {
702
+ $this->xml->push('variants');
703
+
704
+ $this->xml->push('variant', array('id' => 'fake'));
705
+ $this->writeVariant($product, $product, $stores);
706
+ $this->xml->pop();
707
+
708
+ if($product->getTypeID() === Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE) {
709
+ $variantProducts = $product->getTypeInstance(true)->getUsedProducts(null, $product);
710
+ if(count($variantProducts) > 0) {
711
+ foreach($variantProducts as $variantProduct) {
712
+ $this->xml->push('variant', array('id' => $variantProduct->getId()));
713
+ $this->writeVariant($variantProduct, $product, $stores);
714
+ $this->xml->pop();
715
+ }
716
+ }
717
+ }
718
+ $this->xml->pop();
719
+ }
720
+
721
+ /**
722
+ * Write variant
723
+ *
724
+ * @param Product $variantProduct
725
+ * @param Product $product
726
+ * @param array $stores
727
+ */
728
+ protected function writeVariant($variantProduct, $product, $stores)
729
+ {
730
+ $this->writeStore($product, $stores, $variantProduct);
731
+ $this->writeIdentifiers($variantProduct);
732
+ $this->writeProperties($variantProduct, $stores);
733
+ $this->writeMaterials($variantProduct);
734
+ $this->writeColors($variantProduct);
735
+ $this->writeModels($variantProduct);
736
+ $this->writeSizes($variantProduct);
737
+ $this->writeGenders($product);
738
+ $this->writeImageUrl($variantProduct);
739
+ $this->writeMisc($variantProduct);
740
+ }
741
+
742
+ /**
743
+ * Write a part xml to file
744
+ *
745
+ * @param string $xml
746
+ * @param boolean $close
747
+ */
748
+ protected function writePart($xml, $close = false)
749
+ {
750
+ $filename = $this->getFilename();
751
+ if ($this->file === null) {
752
+ $this->file = fopen($filename, 'a+');
753
+ }
754
+
755
+ fwrite($this->file, $xml);
756
+ if ($close) {
757
+ fclose($this->file);
758
+ $this->file = null;
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Set the filename
764
+ *
765
+ * @param string $filename
766
+ */
767
+ protected function setFilename($filename)
768
+ {
769
+ if(file_exists($filename)) {
770
+ unlink($filename);
771
+ }
772
+ $this->filename = $filename;
773
+ }
774
+
775
+ /**
776
+ * Return the filename
777
+ *
778
+ * @return string Return the filename
779
+ */
780
+ protected function getFilename()
781
+ {
782
+ return $this->filename;
783
+ }
784
+
785
+ /**
786
+ * Get products to generate
787
+ *
788
+ * @param array $storeIds
789
+ * @param int $page
790
+ * @param int $limit
791
+ * @param string $type
792
+ * @return array
793
+ */
794
+ protected function getProductIds($storeIds, $type)
795
+ {
796
+ $productsInStock = $this->onlyProductsWithStock ? ' AND is_in_stock = 1' : '';
797
+ $collection = Mage::getModel('catalog/product')
798
+ ->getCollection()
799
+ ->setStoreId($storeIds)
800
+ ->addAttributeToFilter('visibility', $this->productVisible)
801
+ ->addAttributeToFilter('status', 1)
802
+ ->joinField('qty',
803
+ 'cataloginventory/stock_item',
804
+ 'qty',
805
+ 'product_id = entity_id',
806
+ '{{table}}.stock_id = 1'.$productsInStock)
807
+ ;
808
+
809
+ if ($type === MDN_Antidot_Model_Observer::GENERATE_INC) {
810
+ if($this->lastGeneration === null) {
811
+ $this->lastGeneration = Mage::helper('Antidot/LogExport')->getLastGeneration(self::TYPE);
812
+ }
813
+ $collection->addAttributeToFilter('updated_at', array('gteq' => $this->lastGeneration));
814
+ }
815
+
816
+ return $collection->getAllIds();
817
+ }
818
+ }
app/code/community/MDN/Antidot/Model/Observer.php ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Observer extends Mage_Core_Model_Abstract
17
+ {
18
+ const GENERATE_FULL = 'FULL';
19
+ const GENERATE_INC = 'INC';
20
+
21
+ /**
22
+ * @var string @tmpDirectory
23
+ */
24
+ private $tmpDirectory;
25
+ private $tmpErrorDirectory;
26
+
27
+ /**
28
+ * @var string Uniq request Id
29
+ */
30
+ private $request;
31
+
32
+ /**
33
+ * @var int Begin timestamp
34
+ */
35
+ private $begin;
36
+
37
+ /**
38
+ * @var string Current generation type
39
+ */
40
+ private $type;
41
+
42
+ /**
43
+ * Init the controller
44
+ */
45
+ protected function _construct()
46
+ {
47
+ $this->request = uniqid();
48
+ $this->begin = microtime(true);
49
+ $this->initTmpDirectory();
50
+
51
+ ini_set('memory_limit', '1024M');
52
+ }
53
+
54
+ /**
55
+ * Init the tmp directory
56
+ */
57
+ protected function initTmpDirectory()
58
+ {
59
+ $this->tmpDirectory = sys_get_temp_dir().'/antidot/';
60
+ $this->tmpErrorDirectory = $this->tmpDirectory.'error/';
61
+ if(!is_dir($this->tmpDirectory)) {
62
+ mkdir($this->tmpDirectory, 0775);
63
+ mkdir($this->tmpErrorDirectory, 0775);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Generate the full catalog file
69
+ */
70
+ public function catalogFullExport()
71
+ {
72
+ $this->log('FULL EXPORT');
73
+ $this->generate(Mage::getModel('Antidot/Export_Product'), self::GENERATE_FULL);
74
+ }
75
+
76
+ /**
77
+ * Generate the inc catalog file
78
+ */
79
+ public function catalogIncExport()
80
+ {
81
+ $this->generate(Mage::getModel('Antidot/Export_Product'), self::GENERATE_INC);
82
+ }
83
+
84
+ /**
85
+ * Generate the category file
86
+ */
87
+ public function categoriesFullExport()
88
+ {
89
+ $this->generate(Mage::getModel('Antidot/Export_Category'), self::GENERATE_FULL);
90
+ }
91
+
92
+ /**
93
+ * Generate files
94
+ *
95
+ * @param Antidot/Export_* $exportModel
96
+ * @param string $type
97
+ * @throws Exception
98
+ */
99
+ protected function generate($exportModel, $type)
100
+ {
101
+ $this->type = $exportModel::TYPE;
102
+ $this->log('start');
103
+ $log['begin'] = time();
104
+ $log['items'] = 0;
105
+
106
+ $files = array();
107
+ foreach($this->getDefaultContext() as $context) {
108
+ $this->log('generate '.$exportModel::TYPE.' '.$context['owner']);
109
+ $context['store_id'] = array_keys($context['stores']);
110
+
111
+ $filename = $this->tmpDirectory.sprintf($exportModel::FILENAME_XML, $context['lang']);
112
+ $items = $exportModel->writeXml($context, $filename, $type);
113
+ if($items === 0) {
114
+ continue;
115
+ }
116
+
117
+ $log['items']+= $items;
118
+ if ($this->schemaValidate($filename, $exportModel::XSD)) {
119
+ $files[] = $filename;
120
+ } else {
121
+ $this->fileError($filename);
122
+
123
+ $errors = Mage::helper('Antidot/XmlWriter')->getErrors();
124
+ $this->log('xml schema not valid '.print_r($errors, true));
125
+ Mage::helper('Antidot')->sendMail('Export failed', print_r($errors, true));
126
+ continue;
127
+ }
128
+ }
129
+
130
+ if($log['items'] === 0) {
131
+ return;
132
+ }
133
+
134
+ $log['reference'] = 'unknown';
135
+ if(!empty($files)) {
136
+ $filenameZip = $type === self::GENERATE_INC ? $exportModel::FILENAME_ZIP_INC : $exportModel::FILENAME_ZIP;
137
+ $filename = $this->compress($files, $filenameZip);
138
+ $log['reference'] = md5($filename);
139
+ $this->send($filename);
140
+
141
+ $log['status'] = 'SUCCESS';
142
+ $log['error'] = '';
143
+ } else {
144
+ $log['status'] = 'FAILED';
145
+ $log['error'] = current(Mage::helper('Antidot/XmlWriter')->getErrors());
146
+ }
147
+ if(file_exists($filename)) {
148
+ unlink($filename);
149
+ }
150
+
151
+ $log['end'] = time();
152
+ $this->log('generate '.$exportModel::TYPE.' '.$context['owner']);
153
+ $this->log('end');
154
+
155
+ Mage::helper('Antidot/LogExport')->add($log['reference'], $type, $exportModel::TYPE, $log['begin'], $log['end'], $log['items'], $log['status'], $log['error']);
156
+ }
157
+
158
+ /**
159
+ * Move file with error to another directory $tmp/antidot/error
160
+ *
161
+ * @param string $file
162
+ */
163
+ protected function fileError($file)
164
+ {
165
+ $files = array();
166
+ if ($handle = opendir($this->tmpErrorDirectory)) {
167
+ while ($fileError = readdir($handle)) {
168
+ if ($fileError != "." && $fileError != "..") {
169
+ $files[] = $fileError;
170
+ }
171
+ }
172
+ closedir($handle);
173
+
174
+ if(count($files) >= 5) {
175
+ sort($files);
176
+ unlink($this->tmpErrorDirectory.current($files));
177
+ }
178
+
179
+ $fileError = $this->tmpErrorDirectory.time().'-'.basename($file);
180
+ rename($file, $fileError);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Compress xml file
186
+ *
187
+ * @param array $files
188
+ * @param string $compressFile filename
189
+ * @return path to file compressed
190
+ */
191
+ protected function compress($files, $compressFile)
192
+ {
193
+ $this->log('compress the file');
194
+
195
+ $compressFile = dirname(current($files)).'/'.sprintf($compressFile, date('YmdHis'));
196
+ Mage::helper('Antidot/Compress')->zip($files, $compressFile);
197
+
198
+ return $compressFile;
199
+ }
200
+
201
+ /**
202
+ * Send the file to antidot
203
+ *
204
+ * @param string $filename
205
+ * @return boolean
206
+ */
207
+ protected function send($filename)
208
+ {
209
+ $this->log('send the file');
210
+
211
+ $transport = Mage::getModel('Antidot/Transport');
212
+
213
+ return $transport->send($filename, $transport::TRANS_FTP);
214
+ }
215
+
216
+ /**
217
+ * Check if the xml file is valid
218
+ *
219
+ * @param string $filename xml file
220
+ * @param string $xsd xsd file
221
+ * @return boolean
222
+ */
223
+ protected function schemaValidate($filename, $xsd)
224
+ {
225
+ libxml_use_internal_errors(true);
226
+ $this->log('schema validate');
227
+
228
+ $xml = new DOMDocument();
229
+ $xml->load($filename);
230
+
231
+ return $xml->schemaValidate($xsd);
232
+ }
233
+
234
+ /**
235
+ * Return the context default values
236
+ *
237
+ * @todo retrieve these data from the db
238
+ * @return array
239
+ */
240
+ private function getDefaultContext()
241
+ {
242
+ $listStore = array();
243
+ foreach (Mage::app()->getStores() as $store) {
244
+ list($lang) = explode('_', Mage::getStoreConfig('general/locale/code', $store->getId()));
245
+ $listStore[$lang][$store->getId()] = $store;
246
+ }
247
+
248
+ $listContext = array();
249
+ foreach($listStore as $lang => $stores) {
250
+ $defaultOwner = 'AFS@Store for Magento v'.Mage::getConfig()->getNode()->modules->MDN_Antidot->version;
251
+ $context['owner'] = Mage::getStoreConfig('antidot/general/owner') === '' ? $defaultOwner : Mage::getStoreConfig('antidot/general/owner');
252
+ $context['lang'] = $lang;
253
+ $context['stores'] = $stores;
254
+ $context['langs'] = count($listStore);
255
+
256
+ $listContext[] = $context;
257
+ }
258
+
259
+ return $listContext;
260
+ }
261
+
262
+ /**
263
+ * Write message to log
264
+ *
265
+ * @param string $action
266
+ */
267
+ private function log($action)
268
+ {
269
+ $message = '[antidot] ['.$this->type.'] ['.$this->request.'] '
270
+ . memory_get_usage(true).' '
271
+ . $action.' ('.round(microtime(true)-$this->begin, 2)."sec)";
272
+
273
+ Mage::log($message, null, 'antidot.log');
274
+ }
275
+ }
app/code/community/MDN/Antidot/Model/Resource/Advanced.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Resource_Advanced extends Mage_CatalogSearch_Model_Resource_Advanced
17
+ {
18
+
19
+ }
app/code/community/MDN/Antidot/Model/Resource/Catalog/Product/Collection.php ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Resource_Catalog_Product_Collection extends Mage_Catalog_Model_Resource_Product_Collection
17
+ {
18
+ /**
19
+ * @var MDN_Antidot_Model_Resource_Engine_Abstract Search engine.
20
+ */
21
+ protected $_engine;
22
+
23
+ /**
24
+ * @var array Faceted data.
25
+ */
26
+ protected $_facetedData = false;
27
+
28
+ /**
29
+ * @var array Categories ids
30
+ */
31
+ protected $_categoryIds = false;
32
+
33
+ /**
34
+ * @var array Facets conditions.
35
+ */
36
+ protected $_facetsConditions = array();
37
+
38
+ /**
39
+ * @var string Search query text.
40
+ */
41
+ protected $_searchQueryText = '';
42
+
43
+ /**
44
+ * @var array Search query filters.
45
+ */
46
+ protected $_searchQueryFilters = array();
47
+
48
+ /**
49
+ * @var array Search entity ids.
50
+ */
51
+ protected $_searchedEntityIds = array();
52
+
53
+ /**
54
+ * @var array Sort by definition.
55
+ */
56
+ protected $_sortBy = array();
57
+
58
+ /**
59
+ * @var array Request params
60
+ */
61
+ protected $_params = null;
62
+
63
+ /**
64
+ * @var array Query result
65
+ */
66
+ protected $queryResult = null;
67
+
68
+ /**
69
+ * Adds facet condition to current collection.
70
+ *
71
+ * @param string $field
72
+ * @param mixed $condition
73
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
74
+ */
75
+ public function addFacetCondition($field, $condition = null)
76
+ {
77
+ if (array_key_exists($field, $this->_facetsConditions)) {
78
+ if (!empty($this->_facetsConditions[$field])){
79
+ $this->_facetsConditions[$field] = array($this->_facetsConditions[$field]);
80
+ }
81
+ $this->_facetsConditions[$field][] = $condition;
82
+ } else {
83
+ $this->_facetsConditions[$field] = $condition;
84
+ }
85
+
86
+ return $this;
87
+ }
88
+
89
+ /**
90
+ * Add some fields to filter.
91
+ *
92
+ * @param $fields
93
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
94
+ */
95
+ public function addFieldsToFilter($fields)
96
+ {
97
+ return $this;
98
+ }
99
+
100
+ /**
101
+ * Stores filter query.
102
+ *
103
+ * @param array $params
104
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
105
+ */
106
+ public function addFqFilter($params)
107
+ {
108
+ if (is_array($params)) {
109
+ foreach ($params as $field => $value) {
110
+ $this->_searchQueryFilters[$field] = $value;
111
+ }
112
+ }
113
+
114
+ return $this;
115
+ }
116
+
117
+ /**
118
+ * Stores query text filter.
119
+ *
120
+ * @param $query
121
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
122
+ */
123
+ public function addSearchFilter($query)
124
+ {
125
+ $this->_searchQueryText = $query;
126
+
127
+ return $this;
128
+ }
129
+
130
+ /**
131
+ * Stores search query filter.
132
+ *
133
+ * @param mixed $param
134
+ * @param null $value
135
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
136
+ */
137
+ public function addSearchQfFilter($param, $value = null)
138
+ {
139
+ if (is_array($param)) {
140
+ foreach ($param as $field => $value) {
141
+ $this->addSearchQfFilter($field, $value);
142
+ }
143
+ } elseif (isset($value)) {
144
+ if (isset($this->_searchQueryFilters[$param]) && !is_array($this->_searchQueryFilters[$param])) {
145
+ $this->_searchQueryFilters[$param] = array($this->_searchQueryFilters[$param]);
146
+ $this->_searchQueryFilters[$param][] = $value;
147
+ } else {
148
+ $this->_searchQueryFilters[$param] = $value;
149
+ }
150
+ }
151
+
152
+ return $this;
153
+ }
154
+
155
+ /**
156
+ * Aggregates search query filters.
157
+ *
158
+ * @return array
159
+ */
160
+ public function getExtendedSearchParams()
161
+ {
162
+ $result = $this->_searchQueryFilters;
163
+ $result['query_text'] = $this->_searchQueryText;
164
+
165
+ return $result;
166
+ }
167
+
168
+ /**
169
+ * Returns facet data
170
+ *
171
+ * @return array
172
+ */
173
+ public function getFacets()
174
+ {
175
+ if($this->_facetedData === false) {
176
+ $this->getSize();
177
+ }
178
+
179
+ return $this->_facetedData;
180
+ }
181
+
182
+ /**
183
+ * Returns faceted data.
184
+ *
185
+ * @param string $field
186
+ * @return array
187
+ */
188
+ public function getFacetedData($field)
189
+ {
190
+ $this->initQueryResult($this->_getQuery(), $this->_getParams());
191
+
192
+ if (array_key_exists($field, $this->_facetedData)) {
193
+ return $this->_facetedData[$field];
194
+ }
195
+
196
+ return array();
197
+ }
198
+
199
+ /**
200
+ * Returh the category ids
201
+ *
202
+ * @return array
203
+ */
204
+ public function getCategoryIds()
205
+ {
206
+ return $this->_categoryIds;
207
+ }
208
+
209
+ /**
210
+ * Returns collection size
211
+ *
212
+ * @return int
213
+ */
214
+ public function getSize()
215
+ {
216
+ $this->initQueryResult($this->_getQuery(), $this->_getParams());
217
+
218
+ return $this->_engine->getLastNumFound();
219
+ }
220
+
221
+ /**
222
+ * Defines current search engine.
223
+ *
224
+ * @param MDN_Antidot_Model_Resource_Engine_Abstract $engine
225
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
226
+ */
227
+ public function setEngine(MDN_Antidot_Model_Resource_Engine_Abstract $engine)
228
+ {
229
+ $this->_engine = $engine;
230
+
231
+ return $this;
232
+ }
233
+
234
+ /**
235
+ * Stores sort order.
236
+ *
237
+ * @param string $attribute
238
+ * @param string $dir
239
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
240
+ */
241
+ public function setOrder($attribute, $dir = self::SORT_ORDER_DESC)
242
+ {
243
+ $this->_sortBy[] = array($attribute => $dir);
244
+
245
+ return $this;
246
+ }
247
+
248
+ /**
249
+ * Reorder collection according to current sort order.
250
+ *
251
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
252
+ */
253
+ protected function _afterLoad()
254
+ {
255
+ parent::_afterLoad();
256
+
257
+ if (!empty($this->_searchedEntityIds)) {
258
+ $sortedItems = array();
259
+ foreach ($this->_searchedEntityIds as $id) {
260
+ if (isset($this->_items[$id])) {
261
+ $sortedItems[$id] = $this->_items[$id];
262
+ }
263
+ }
264
+ $this->_items = &$sortedItems;
265
+ }
266
+
267
+ return $this;
268
+ }
269
+
270
+ /**
271
+ * Handles collection filtering by ids retrieves from search engine.
272
+ * Will also stores faceted data and total records.
273
+ *
274
+ * @return Mage_Catalog_Model_Resource_Product_Collection
275
+ */
276
+ protected function _beforeLoad()
277
+ {
278
+ $ids = array();
279
+ if ($this->_engine) {
280
+ $this->initQueryResult($this->_getQuery(), $this->_getParams());
281
+ if(count($this->_searchedEntityIds) === 1) {
282
+ header('location: '. Mage::getModel('catalog/product')->load(current($this->_searchedEntityIds))->getProductUrl());
283
+ exit(0);
284
+ }
285
+ }
286
+
287
+ $this->addIdFilter($this->_searchedEntityIds);
288
+ $this->_pageSize = false;
289
+
290
+ return parent::_beforeLoad();
291
+ }
292
+
293
+ /**
294
+ * Retrieve the query result
295
+ *
296
+ * @param string $query
297
+ * @param array $params
298
+ * @return array
299
+ */
300
+ protected function initQueryResult($query, $params)
301
+ {
302
+ if($this->queryResult === null) {
303
+ $this->queryResult = $this->_engine->getIdsByQuery($query, $params);
304
+
305
+ $this->_totalRecords = $this->_engine->getLastNumFound();
306
+ $this->_facetedData = isset($this->queryResult['faceted_data']) ? $this->queryResult['faceted_data'] : array();
307
+ $this->_searchedEntityIds = isset($this->queryResult['ids']) ? $this->queryResult['ids'] : array();
308
+ $this->_categoryIds = isset($this->queryResult['category_ids']) ? $this->queryResult['category_ids'] : array();
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Retrieves parameters.
314
+ *
315
+ * @return array
316
+ */
317
+ protected function _getParams()
318
+ {
319
+ if($this->_params === null) {
320
+ $params = array();
321
+
322
+ $blockList = Mage::getBlockSingleton('catalog/product_list_toolbar');
323
+
324
+ $params['limit'] = $blockList->getLimit();
325
+ $params['p'] = $blockList->getCurrentPage();
326
+
327
+ if($order = Mage::app()->getRequest()->getParam('order', false)) {
328
+ if(!$dir = Mage::app()->getRequest()->getParam('dir', false)) {
329
+ $dir = 'asc';
330
+ }
331
+ $params['sort_by'] = array(array($order => $dir));
332
+ }
333
+
334
+ $params['filters'] = $this->_searchQueryFilters;
335
+
336
+ if (!empty($this->_facetsConditions)) {
337
+ $params['facets'] = $this->_facetsConditions;
338
+ }
339
+
340
+ $this->_params = $params;
341
+ }
342
+
343
+ return $this->_params;
344
+ }
345
+
346
+ /**
347
+ * Load entities records into items
348
+ *
349
+ * @throws Exception
350
+ * @return Mage_Eav_Model_Entity_Collection_Abstract
351
+ */
352
+ public function _loadEntities($printQuery = false, $logQuery = false)
353
+ {
354
+ $this->printLogQuery($printQuery, $logQuery);
355
+ try {
356
+ $query = $this->_prepareSelect($this->getSelect());
357
+ $query = str_replace('INNER JOIN `catalog_category_product_index`', 'LEFT JOIN `catalog_category_product_index`', $query);
358
+ $rows = $this->_fetchAll($query);
359
+ } catch (Exception $e) {
360
+ Mage::printException($e, $query);
361
+ $this->printLogQuery(true, true, $query);
362
+ throw $e;
363
+ }
364
+
365
+ foreach ($rows as $v) {
366
+ $object = $this->getNewEmptyItem()->setData($v);
367
+ $this->addItem($object);
368
+ if (isset($this->_itemsById[$object->getId()])) {
369
+ $this->_itemsById[$object->getId()][] = $object;
370
+ } else {
371
+ $this->_itemsById[$object->getId()] = array($object);
372
+ }
373
+ }
374
+
375
+ return $this;
376
+ }
377
+
378
+ /**
379
+ * Returns stored text query
380
+ *
381
+ * @return string
382
+ */
383
+ protected function _getQuery()
384
+ {
385
+ return $this->_searchQueryText;
386
+ }
387
+ }
app/code/community/MDN/Antidot/Model/Resource/Engine/Abstract.php ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ abstract class MDN_Antidot_Model_Resource_Engine_Abstract
17
+ {
18
+
19
+ /**
20
+ * @var object Search engine client.
21
+ */
22
+ protected $_client;
23
+
24
+ /**
25
+ * @var array List of default query parameters.
26
+ */
27
+ protected $_defaultQueryParams = array(
28
+ 'p' => 1,
29
+ 'limit' => 10,
30
+ 'store_id' => null,
31
+ 'fields' => array(),
32
+ 'params' => array(),
33
+ 'ignore_handler' => false,
34
+ 'filters' => array(),
35
+ );
36
+
37
+ /**
38
+ * @var int Last number of results found.
39
+ */
40
+ protected $_lastNumFound;
41
+
42
+ /**
43
+ * Returns advanced search results.
44
+ *
45
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
46
+ */
47
+ public function getAdvancedResultCollection()
48
+ {
49
+ return $this->getResultCollection();
50
+ }
51
+
52
+ /**
53
+ * Checks if advanced index is allowed for current search engine.
54
+ *
55
+ * @return bool
56
+ */
57
+ public function allowAdvancedIndex()
58
+ {
59
+ return true;
60
+ }
61
+
62
+ /**
63
+ * Returns product visibility ids for search.
64
+ *
65
+ * @see Mage_Catalog_Model_Product_Visibility
66
+ * @return mixed
67
+ */
68
+ public function getAllowedVisibility()
69
+ {
70
+ return Mage::getSingleton('catalog/product_visibility')->getVisibleInSearchIds();
71
+ }
72
+
73
+ /**
74
+ * Retrieves product ids for specified query.
75
+ *
76
+ * @param string $query
77
+ * @param array $params
78
+ * @param string $type
79
+ * @return array
80
+ */
81
+ public function getIdsByQuery($query, $params = array(), $type = 'product')
82
+ {
83
+ $ids = array();
84
+ $resultTmp = $this->search($query, $params, $type);
85
+ if (!empty($resultTmp['ids'])) {
86
+ foreach ($resultTmp['ids'] as $id) {
87
+ $ids[] = $id['id'];
88
+ }
89
+ }
90
+
91
+ $result = array(
92
+ 'ids' => $ids,
93
+ 'total_count' => (isset($resultTmp['total_count'])) ? $resultTmp['total_count'] : null,
94
+ 'faceted_data' => (isset($resultTmp['facets'])) ? $resultTmp['facets'] : array(),
95
+ 'category_ids' => (isset($resultTmp['category_ids'])) ? $resultTmp['category_ids'] : array(),
96
+ );
97
+
98
+ return $result;
99
+ }
100
+
101
+ /**
102
+ * Returns resource name.
103
+ *
104
+ * @return string
105
+ */
106
+ public function getResourceName()
107
+ {
108
+ return 'Antidot/advanced';
109
+ }
110
+
111
+ /**
112
+ * Returns last number of results found.
113
+ *
114
+ * @return int
115
+ */
116
+ public function getLastNumFound()
117
+ {
118
+ return $this->_lastNumFound;
119
+ }
120
+
121
+ /**
122
+ * Returns catalog product collection with current search engine set.
123
+ *
124
+ * @return MDN_Antidot_Model_Resource_Catalog_Product_Collection
125
+ */
126
+ public function getResultCollection()
127
+ {
128
+ return Mage::getResourceModel('Antidot/catalog_product_collection')->setEngine($this);
129
+ }
130
+
131
+ /**
132
+ * Retrieves stats for specified query.
133
+ *
134
+ * @param string $query
135
+ * @param array $params
136
+ * @param string $type
137
+ * @return array
138
+ */
139
+ public function getStats($query, $params = array(), $type = 'product')
140
+ {
141
+ return $this->_search($query, $params, $type);
142
+ }
143
+
144
+ /**
145
+ * Alias of isLayeredNavigationAllowed.
146
+ *
147
+ * @return bool
148
+ */
149
+ public function isLeyeredNavigationAllowed()
150
+ {
151
+ return $this->isLayeredNavigationAllowed();
152
+ }
153
+
154
+ /**
155
+ * Checks if layered navigation is available for current search engine.
156
+ *
157
+ * @return bool
158
+ */
159
+ public function isLayeredNavigationAllowed()
160
+ {
161
+ return true;
162
+ }
163
+
164
+ /**
165
+ * Performs search query and facetting.
166
+ *
167
+ * @param string $query
168
+ * @param array $params
169
+ * @param string $type
170
+ * @return array
171
+ */
172
+ public function search($query, $params = array(), $type = 'product')
173
+ {
174
+ $result = array();
175
+ try {
176
+ Varien_Profiler::start('Antidot');
177
+ $result = $this->_search($query, $params, $type);
178
+ Varien_Profiler::stop('Antidot');
179
+
180
+ } catch (Exception $e) {
181
+ Mage::logException($e, null, 'antidot.log');
182
+ }
183
+
184
+ return $result;
185
+ }
186
+
187
+ /**
188
+ * Checks search engine availability.
189
+ * Should be overriden by child classes.
190
+ *
191
+ * @return bool
192
+ */
193
+ public function test()
194
+ {
195
+ return true;
196
+ }
197
+
198
+ /**
199
+ * Returns search helper.
200
+ *
201
+ * @return MDN_Antidot_Helper_Data
202
+ */
203
+ protected function _getHelper()
204
+ {
205
+ return Mage::helper('Antidot');
206
+ }
207
+
208
+ /**
209
+ * Transforms specified object to an array.
210
+ *
211
+ * @param $object
212
+ * @return array
213
+ */
214
+ protected function _objectToArray($object)
215
+ {
216
+ if (!is_object($object) && !is_array($object)){
217
+ return $object;
218
+ }
219
+ if (is_object($object)){
220
+ $object = get_object_vars($object);
221
+ }
222
+
223
+ return array_map(array($this, '_objectToArray'), $object);
224
+ }
225
+
226
+ /**
227
+ * Prepares query before search.
228
+ *
229
+ * @param mixed $query
230
+ * @return string
231
+ */
232
+ protected function prepareSearchConditions($query)
233
+ {
234
+ return $query;
235
+ }
236
+
237
+ /**
238
+ * Prepares facets query response.
239
+ *
240
+ * @abstract
241
+ * @param mixed $response
242
+ * @return mixed
243
+ */
244
+ abstract protected function prepareFacetsQueryResponse($response);
245
+
246
+ /**
247
+ * Prepares query response.
248
+ *
249
+ * @abstract
250
+ * @param mixed $response
251
+ * @return mixed
252
+ */
253
+ abstract protected function prepareQueryResponse($response);
254
+
255
+ /**
256
+ * Performs search and facetting for specified query and parameters.
257
+ *
258
+ * @abstract
259
+ * @param string $query
260
+ * @param array $params
261
+ * @return mixed
262
+ */
263
+ abstract protected function _search($query, $params = array());
264
+ }
app/code/community/MDN/Antidot/Model/Resource/Engine/Antidot.php ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Resource_Engine_Antidot extends MDN_Antidot_Model_Resource_Engine_Abstract
17
+ {
18
+ /**
19
+ * @var booleen Check if the note is already added
20
+ */
21
+ protected $addedNote = false;
22
+
23
+ /**
24
+ * Initializes search engine.
25
+ *
26
+ * @see MDN_Antidot_Model_Resource_Engine_Antidotclient
27
+ */
28
+ public function __construct()
29
+ {
30
+ $this->client = Mage::getModel('Antidot/Search_Search');
31
+ }
32
+
33
+ /**
34
+ * Returns search helper.
35
+ *
36
+ * @return MDN_Antidot_Helper_Antidot
37
+ */
38
+ protected function _getHelper()
39
+ {
40
+ return Mage::helper('Antidot/Antidot');
41
+ }
42
+
43
+ /**
44
+ * Prepares facets conditions.
45
+ *
46
+ * @param array $facetsFields
47
+ * @return array
48
+ */
49
+ protected function prepareFacetsConditions($facetsFields)
50
+ {
51
+ $result = array();
52
+ if (is_array($facetsFields)) {
53
+ foreach ($facetsFields as $facetField => $facetFieldConditions) {
54
+ if (empty($facetFieldConditions)) {
55
+ $result[] = $facetField;
56
+ } else {
57
+ foreach ($facetFieldConditions as $facetCondition) {
58
+ $result['queries'][] = array($facetField => $facetCondition);
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ return $result;
65
+ }
66
+
67
+ /**
68
+ * Prepares facets query response.
69
+ *
70
+ * @param mixed $response
71
+ * @return array
72
+ */
73
+ protected function prepareFacetsQueryResponse($response)
74
+ {
75
+ $result = array();
76
+ foreach ($response as $facet) {
77
+ foreach($facet->get_elements() as $element) {
78
+ if ($facet->get_layout() === 'TREE') {
79
+ if(!isset($result[$facet->get_id()])) {
80
+ $result[$facet->get_id()] = array();
81
+ }
82
+ if(Mage::helper('Antidot')->hasFacetMultiple($facet->get_id())) {
83
+ $result[$facet->get_id()] = array_merge($result[$facet->get_id()], $this->getListFacet($element));
84
+ } else {
85
+ $result[$facet->get_id()] = array_merge($result[$facet->get_id()], $this->getTreeFacet($element));
86
+ }
87
+ } elseif ($facet->get_layout() === 'INTERVAL') {
88
+ $key = str_replace(array('[', ']'), array('', ''), $element->key);
89
+ list($from, $to) = explode(' .. ', $key);
90
+
91
+ $tplPrice = Mage::getStoreConfig('antidot/engine/price_facet');
92
+ $label = str_replace(array('{min}', '{max}'), array($from, $to), $tplPrice);
93
+ $result[$facet->get_id()][$from.'-'.$to] = array('count' => $element->count, 'label' => $label);
94
+ }
95
+ }
96
+ }
97
+
98
+ $maxOptions = (int)Mage::getStoreConfig('antidot/engine/facet_options');
99
+ foreach($result as &$facets) {
100
+ $facets = array_slice($facets, 0, $maxOptions);
101
+ }
102
+
103
+ return $result;
104
+ }
105
+
106
+ /**
107
+ * Retrieve elements from tree facet
108
+ *
109
+ * @param StdClass $element
110
+ * @param array $sortElements
111
+ * @return array
112
+ */
113
+ protected function getListFacet($element)
114
+ {
115
+ $result[$element->key] = array('count' => $element->count, 'label' => $element->label);
116
+ if(!empty($element->values)) {
117
+ foreach($element->values as $childElement) {
118
+ $result = array_merge($result, $this->getListFacet($childElement));
119
+ }
120
+ }
121
+
122
+ return $result;
123
+ }
124
+
125
+ /**
126
+ * Retrieve elements from tree facet
127
+ *
128
+ * @param StdClass $element
129
+ * @param array $sortElements
130
+ * @return array
131
+ */
132
+ protected function getTreeFacet($element)
133
+ {
134
+ $result[$element->key] = array('count' => $element->count, 'label' => $element->label);
135
+ if(!empty($element->values)) {
136
+ $result[$element->key]['child'] = array();
137
+ foreach($element->values as $childElement) {
138
+ $result[$element->key]['child'] = array_merge($result[$element->key]['child'], $this->getTreeFacet($childElement));
139
+ }
140
+ }
141
+
142
+ return $result;
143
+ }
144
+
145
+ /**
146
+ * Prepares filters.
147
+ *
148
+ * @param array $filters
149
+ * @return array
150
+ */
151
+ protected function prepareFilters($filters)
152
+ {
153
+ $result = array();
154
+ if (is_array($filters) && !empty($filters)) {
155
+ foreach ($filters as $field => $value) {
156
+ if (is_array($value)) {
157
+ if (substr($field, 0, 5) === 'price') {
158
+ list($from, $to) = explode('-', $value[0]);
159
+ $fieldCondition = array($field => "[$from .. $to]");
160
+ } else {
161
+ $fieldCondition = array();
162
+ foreach ($value as $part) {
163
+ $fieldCondition = array($field => explode(',', $part));
164
+ break;
165
+ }
166
+ }
167
+ } else {
168
+ $fieldCondition = array($field => $value);
169
+ }
170
+
171
+ $result[] = $fieldCondition;
172
+ }
173
+ }
174
+
175
+ return $result;
176
+ }
177
+
178
+ /**
179
+ * Prepares query response.
180
+ *
181
+ * @param StdClass $response
182
+ * @return array
183
+ */
184
+ protected function prepareQueryResponse($response, $type = 'Catalog')
185
+ {
186
+ if($type === 'Catalog') {
187
+ $this->_lastNumFound = (int)$response->get_meta()->get_total_replies();
188
+ $result = array();
189
+ foreach ($response->get_replies() as $reply) {
190
+ $result[] = $this->_objectToArray($this->getDataFromReply($reply));
191
+ }
192
+
193
+ return $result;
194
+ }
195
+
196
+ foreach ($response->get_replies() as $reply) {
197
+ $reply = $this->getDataFromReply($reply);
198
+ $result[] = $reply['id'];
199
+ }
200
+
201
+ return $result;
202
+ }
203
+
204
+ /**
205
+ * Retrieve client data
206
+ *
207
+ * @param ReplyHelper $reply
208
+ * @return array
209
+ */
210
+ protected function getDataFromReply($reply)
211
+ {
212
+ $data = array();
213
+ $sxe = simplexml_load_string(str_replace('&', '&amp;', $reply->get_clientdata()->get_value()));
214
+
215
+ $data['id'] = (string)$sxe['id'];
216
+ foreach($sxe->children() as $field) {
217
+ if($field->children()) {
218
+ foreach($field->children() as $child) {
219
+ $data[$field->getName()][] = (string)$child;
220
+ }
221
+ } else {
222
+ $data[$field->getName()] = (string)$field;
223
+ }
224
+ }
225
+
226
+ return $data;
227
+ }
228
+
229
+ /**
230
+ * Prepares sort fields.
231
+ *
232
+ * @param array $sortBy
233
+ * @return array
234
+ */
235
+ protected function prepareSortFields($sortBy)
236
+ {
237
+ $result = array();
238
+ foreach ($sortBy as $sort) {
239
+ $_sort = each($sort);
240
+ $sortField = $_sort['key'];
241
+ $sortType = $_sort['value'];
242
+
243
+ $result[] = $sortField.','.trim(strtoupper($sortType));
244
+ }
245
+
246
+ return $result;
247
+ }
248
+
249
+ /**
250
+ * Performs search and facetting.
251
+ *
252
+ * @param string $query
253
+ * @param array $params
254
+ * @param string $type
255
+ * @return array
256
+ */
257
+ protected function _search($query, $params = array())
258
+ {
259
+ $_params = $this->_defaultQueryParams;
260
+ if (is_array($params) && !empty($params)) {
261
+ $_params = array_merge($_params, $params);
262
+ }
263
+
264
+ $searchParams = array();
265
+ $searchParams['page'] = isset($_params['p']) ? (int) $_params['p'] : 1;
266
+ $searchParams['limit'] = (int)$_params['limit'];
267
+
268
+ if (!is_array($_params['params'])) {
269
+ $_params['params'] = array($_params['params']);
270
+ }
271
+
272
+ if(!empty($_params['sort_by'])) {
273
+ $searchParams['sort'] = $this->prepareSortFields($_params['sort_by']);
274
+ } elseif($configSort = Mage::getStoreConfig('antidot/engine/default_sort')) {
275
+ $listDefaultSort = unserialize($configSort);
276
+ foreach($listDefaultSort as $defaultSort) {
277
+ list($defaultSort['field']) = explode('|', $defaultSort['field']);
278
+ $_params['sort_by'][] = array($defaultSort['field'] => $defaultSort['dir']);
279
+ }
280
+
281
+ $searchParams['sort'] = $this->prepareSortFields($_params['sort_by']);
282
+ }
283
+
284
+ if (isset($params['facets']) && !empty($params['facets'])) {
285
+ $searchParams['facets'] = $this->prepareFacetsConditions($params['facets']);
286
+ }
287
+
288
+ if (!empty($_params['params'])) {
289
+ foreach ($_params['params'] as $name => $value) {
290
+ $searchParams[$name] = $value;
291
+ }
292
+ }
293
+
294
+ $searchParams['filters'] = $this->prepareFilters($_params['filters']);
295
+
296
+ Varien_Profiler::start('ANTIDOT_SEARCH');
297
+ $resultAntidot = $this->client->search($query, $searchParams);
298
+ Varien_Profiler::stop('ANTIDOT_SEARCH');
299
+
300
+ return $this->formatResult($resultAntidot);
301
+ }
302
+
303
+ /**
304
+ * Format the response from antidot
305
+ *
306
+ * @param StdClass $resultAntidot
307
+ * @return array
308
+ */
309
+ protected function formatResult($resultAntidot)
310
+ {
311
+ $result = array('ids' => array(), 'total_count' => 0);
312
+ if(isset($resultAntidot->replyset) && $resultAntidot->replyset !== null) {
313
+ $result = array(
314
+ 'ids' => $this->prepareQueryResponse($resultAntidot->replyset),
315
+ 'total_count' => $resultAntidot->replyset->get_meta()->get_total_replies()
316
+ );
317
+ }
318
+
319
+ if ($resultAntidot->replyset !== null && $resultAntidot->replyset->has_facet()) {
320
+ $result['facets'] = $this->prepareFacetsQueryResponse($resultAntidot->replyset->get_facets());
321
+ }
322
+
323
+ if(isset($resultAntidot->replysetCategories) && $resultAntidot->replysetCategories !== null) {
324
+ $result['category_ids'] = $this->prepareQueryResponse($resultAntidot->replysetCategories, 'Categories');
325
+ }
326
+
327
+ if($promote = $resultAntidot->promote && $replies = $resultAntidot->promote->get_replies()) {
328
+ if((Mage::getStoreConfig('antidot/promote/redirect') === 'no_result' && $result['total_count'] == 0) || Mage::getStoreConfig('antidot/promote/redirect') === 'always') {
329
+ $promote = current($replies);
330
+ if($promote->uri !== 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']) {
331
+ header('Location: '.$promote->uri);
332
+ exit(0);
333
+ }
334
+ }
335
+ }
336
+
337
+ if (!$this->addedNote && $result['total_count'] == 0) {
338
+ if($spellcheck = $resultAntidot->spellcheck) {
339
+ $link = '<a href="'.Mage::helper('catalogsearch')->getResultUrl($spellcheck).'">'.$spellcheck.'</a>';
340
+ $spellcheck = str_replace('{spellcheck}', $link, Mage::getStoreConfig('antidot/engine/spellcheck'));
341
+
342
+ Mage::helper('catalogsearch')->addNoteMessage($spellcheck);
343
+ $this->addedNote = true;
344
+ }
345
+ }
346
+
347
+ return $result;
348
+ }
349
+
350
+ /**
351
+ * Default method call by Magento
352
+ */
353
+ public function cleanIndex()
354
+ {
355
+ return $this;
356
+ }
357
+
358
+ /**
359
+ * Default method call by Magento
360
+ */
361
+ public function prepareEntityIndex()
362
+ {
363
+ return $this;
364
+ }
365
+
366
+ /**
367
+ * Default method call by Magento
368
+ */
369
+ public function saveEntityIndexes()
370
+ {
371
+ return $this;
372
+ }
373
+ }
app/code/community/MDN/Antidot/Model/Search/Abstract.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Search_Abstract extends Mage_Core_Model_Abstract
17
+ {
18
+ protected $afsService;
19
+ protected $afsHost;
20
+ protected $afsStatus;
21
+
22
+ protected $isConfigured = false;
23
+
24
+ /**
25
+ * Init Antidot API
26
+ */
27
+ public function _construct()
28
+ {
29
+ set_include_path(get_include_path().':'.MAGENTO_ROOT.'/lib/antidot');
30
+ require_once "afs_lib.php";
31
+
32
+ if($config = Mage::getStoreConfig('antidot/web_service')) {
33
+ $this->afsHost = $config['host'];
34
+ $this->afsService = (int)$config['service'];
35
+ $this->afsStatus = $config['status'];
36
+
37
+ $this->isConfigured = true;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Return the user session
43
+ *
44
+ * @return string
45
+ */
46
+ protected function getSession()
47
+ {
48
+ $session = Mage::getSingleton('core/session');
49
+ if(!$antidotSession = $session->getData('antidot_session')) {
50
+ $antidotSession = uniqid();
51
+ $session->setData('antidot_session', $antidotSession);
52
+ }
53
+
54
+ return $antidotSession;
55
+ }
56
+ }
app/code/community/MDN/Antidot/Model/Search/Search.php ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Search_Search extends MDN_Antidot_Model_Search_Abstract
17
+ {
18
+ /**
19
+ * @var string Language used
20
+ */
21
+ protected $lang;
22
+
23
+ /**
24
+ * @var array list feed
25
+ */
26
+ protected $feeds = array('Catalog', 'Promote');
27
+
28
+ /**
29
+ * @var AfsSearch
30
+ */
31
+ protected $afsSearch;
32
+
33
+ /**
34
+ * {@inherit}
35
+ */
36
+ public function _construct()
37
+ {
38
+ parent::_construct();
39
+
40
+ list($lang) = explode('_', Mage::getStoreConfig('general/locale/code', Mage::app()->getStore()->getId()));
41
+ $this->lang = $lang;
42
+
43
+ foreach (Mage::getStoreConfig('antidot/engine') as $field => $value) {
44
+ if (substr($field, 0, 4) === 'feed' && $value === '1') {
45
+ $this->feeds[] = ucfirst(substr($field, 5));
46
+ }
47
+ }
48
+ $this->feeds = array_unique($this->feeds);
49
+
50
+ if ($this->isConfigured) {
51
+ $this->afsSearch = new AfsSearch($this->afsHost, $this->afsService, $this->afsStatus);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get the suggest list
57
+ *
58
+ * @param string $query
59
+ */
60
+ public function search($search = null, $params = array(), $facetOnly = false)
61
+ {
62
+ if (!$this->isConfigured) {
63
+ return;
64
+ }
65
+
66
+ if (!$facetOnly) {
67
+ $params['filters'][] = array(
68
+ 'store' => '"' . Mage::app()->getStore()->getId() . '"',
69
+ 'website' => '"' . Mage::app()->getStore()->getWebsiteId() . '"',
70
+ );
71
+
72
+ if (!isset($params['lang'])) {
73
+ $params['lang'] = $this->lang;
74
+ }
75
+ }
76
+
77
+
78
+ $this->afsSearch->set_query($this->getQuery($search, $params));
79
+ $results = $this->afsSearch->execute(AfsHelperFormat::HELPERS);
80
+ Mage::log(urldecode($this->afsSearch->get_generated_url()), null, 'antidot.log');
81
+
82
+ $resultAntidot = new stdClass();
83
+ if ($results->in_error()) {
84
+ return $resultAntidot;
85
+ }
86
+
87
+ $resultAntidot->spellcheck = $this->getSpellcheckFromResult($results);
88
+ $resultAntidot->promote = $this->getPromoteFromResult($results);
89
+ $resultAntidot->replyset = $this->getReplySetFromResult($results);
90
+ $resultAntidot->replysetCategories = $this->getReplySetFromResult($results, 'Categories');
91
+
92
+ return $resultAntidot;
93
+ }
94
+
95
+ /**
96
+ * Get spellcheck from result
97
+ *
98
+ * @param StdClass $results
99
+ * @return string
100
+ */
101
+ protected function getSpellcheckFromResult($results)
102
+ {
103
+ $spellcheck = null;
104
+ try {
105
+ $spellcheck = $results->get_spellchecks();
106
+ if($results->has_spellcheck() && !empty($spellcheck['Catalog'][0])) {
107
+ $spellcheck = $spellcheck['Catalog'][0]->get_raw_text();
108
+ }
109
+ } catch (Exception $e) {
110
+ Mage::log($e->getMessage(), null, 'antidot.log');
111
+ }
112
+
113
+ return $spellcheck;
114
+ }
115
+
116
+ /**
117
+ * Get promote from result
118
+ *
119
+ * @param StdClass $results
120
+ * @return string
121
+ */
122
+ protected function getPromoteFromResult($results)
123
+ {
124
+ $promote = null;
125
+ try {
126
+ $promote = $results->get_promote();
127
+ } catch (Exception $e) {
128
+ Mage::log($e->getMessage(), null, 'antidot.log');
129
+ }
130
+
131
+ return $promote;
132
+ }
133
+
134
+ /**
135
+ * Get Replyset from results
136
+ *
137
+ * @param StdClass $results
138
+ * @param string $type Catalog|Product
139
+ * @return ReplySetHelper|null
140
+ */
141
+ protected function getReplySetFromResult($results, $type = 'Catalog')
142
+ {
143
+ try {
144
+ $replyset = $results->get_replyset($type);
145
+ } catch (Exception $e) {
146
+ Mage::log($e->getMessage(), null, 'antidot.log');
147
+ $replyset = null;
148
+ }
149
+
150
+ return $replyset;
151
+ }
152
+
153
+ /**
154
+ * Return facets list
155
+ *
156
+ * @return array
157
+ */
158
+ public function getFacets()
159
+ {
160
+ $facets = array();
161
+
162
+ $resultAntidot = $this->search(null, array('limit' => 1), true);
163
+ if (isset($resultAntidot->replyset) && $resultAntidot->replyset) {
164
+ foreach ($resultAntidot->replyset->facets as $facet) {
165
+ $facets[$facet->id] = $facet;
166
+ }
167
+ }
168
+
169
+ return $facets;
170
+ }
171
+
172
+ /**
173
+ * Prepare the Antidot query
174
+ *
175
+ * @param string $search
176
+ * @param array $params
177
+ * @return AfsQuery
178
+ */
179
+ protected function getQuery($search, $params)
180
+ {
181
+ $query = new AfsQuery();
182
+ $query = $query->set_query($search);
183
+ $query = $query->set_session_id($this->getSession());
184
+
185
+ foreach ($this->feeds as $feed) {
186
+ $query = $query->add_feed($feed);
187
+ }
188
+
189
+ if (isset($params['lang'])) {
190
+ $query = $query->set_lang($params['lang']);
191
+ }
192
+
193
+ if (isset($params['filters']) && is_array($params['filters'])) {
194
+ foreach ($params['filters'] as $filter) {
195
+ if (is_array($filter)) {
196
+ foreach ($filter as $key => $values) {
197
+ $query = $query->add_filter($key, $values);
198
+ }
199
+ } else {
200
+ $query = $query->add_filter($key, $value);
201
+ }
202
+ }
203
+ }
204
+
205
+ if (isset($params['sort']) && is_array($params['sort'])) {
206
+ foreach($params['sort'] as $sort) {
207
+ list($field, $dir) = explode(',', $sort);
208
+ $dir = $dir === 'ASC' && $field !== 'afs:relevance' ? AfsSortOrder::ASC : AfsSortOrder::DESC;
209
+ $query = $query->add_sort($field, $dir);
210
+ }
211
+ }
212
+
213
+ if (isset($params['limit']) && is_numeric($params['limit'])) {
214
+ $query = $query->set_replies((int)$params['limit']);
215
+ }
216
+
217
+ if (isset($params['page']) && is_numeric($params['page'])) {
218
+ $query = $query->set_page((int)$params['page']);
219
+ }
220
+
221
+ $query = $query->add_log('AFS@Store for Magento v'.Mage::getConfig()->getNode()->modules->MDN_Antidot->version);
222
+ $query = $query->add_log('Magento '.Mage::getEdition().' '.Mage::getVersion());
223
+
224
+ $query = $this->setSelectionFacets($query);
225
+
226
+ $query = $query->set_facets_values_sort_order(AfsFacetValuesSortMode::ITEMS, AfsSortOrder::DESC);
227
+
228
+ return $query;
229
+ }
230
+
231
+ /**
232
+ * Set selection facets
233
+ *
234
+ * @param AFSQuery $query
235
+ * return AFSQuery
236
+ */
237
+ protected function setSelectionFacets($query)
238
+ {
239
+ $facets = Mage::helper('Antidot')->getFacetsFilter();
240
+
241
+ $multiSelectionFacets = array();
242
+ $monoSelectionFacets = array();
243
+ foreach($facets as $facetId => $facet) {
244
+ if($facet['multiple'] === '1') {
245
+ $multiSelectionFacets[] = $facetId;
246
+ } else {
247
+ $monoSelectionFacets[] = $facetId;
248
+ }
249
+ }
250
+
251
+
252
+ if(!empty($multiSelectionFacets)) {
253
+ $query = call_user_func_array(array($query, 'set_multi_selection_facets'), $multiSelectionFacets);
254
+ }
255
+
256
+ if(!empty($monoSelectionFacets)) {
257
+ $query = call_user_func_array(array($query, 'set_mono_selection_facets'), $monoSelectionFacets);
258
+ }
259
+
260
+ return $query;
261
+ }
262
+ }
app/code/community/MDN/Antidot/Model/Search/Suggest.php ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Search_Suggest extends MDN_Antidot_Model_Search_Abstract
17
+ {
18
+
19
+ const URI = 'http://%s/acp?afs:service=%s&afs:status=%s&afs:feed=%s&afs:query=%s&afs:sessionId=%s';
20
+
21
+ /**
22
+ * List feeds to use for the query sprintf($feed, website_id, lang)
23
+ *
24
+ * @var array
25
+ */
26
+ private $feed = array(
27
+ 'products' => array(
28
+ 'prefix' => 'featured_products_',
29
+ 'tpl' => 'featured_products_%d_%s',
30
+ 'number' => 5,
31
+ ),
32
+ 'categories' => array(
33
+ 'prefix' => 'categories_',
34
+ 'tpl' => 'categories_%d_%s',
35
+ 'number' => 5,
36
+ ),
37
+ 'brands' => array(
38
+ 'prefix' => 'brands_',
39
+ 'tpl' => 'brands_%d_%s',
40
+ 'number' => 5,
41
+ ),
42
+ );
43
+
44
+ /**
45
+ * @var array Types sorted
46
+ */
47
+ protected $typeOrder = array();
48
+
49
+ /**
50
+ * Xslt Template
51
+ *
52
+ * @var string
53
+ */
54
+ protected $template;
55
+
56
+ /**
57
+ * {@inherit}
58
+ */
59
+ public function _construct()
60
+ {
61
+ parent::_construct();
62
+
63
+ $config = Mage::getStoreConfig('antidot/suggest');
64
+ $this->template = trim($config['template']);
65
+ foreach($config as $field => $value) {
66
+ if(isset($this->feed[$field]) && $value === '0') {
67
+ unset($this->feed[$field]);
68
+ } elseif(preg_match('/([a-z]+)_displayed/', $field, $matches)) {
69
+ $field = $matches[1];
70
+ if(isset($this->feed[$field])) {
71
+ $this->feed[$field]['number'] = (int)$value;
72
+ }
73
+ } elseif(preg_match('/order_([0-4])/', $field, $matches)) {
74
+ $order = $matches[1];
75
+ $this->typeOrder[$value] = $order;
76
+ }
77
+ }
78
+ $this->loadFacetAutocomplete();
79
+ }
80
+
81
+ /**
82
+ * @return array
83
+ */
84
+ protected function loadFacetAutocomplete()
85
+ {
86
+ $facets = @unserialize(Mage::getStoreConfig('antidot/fields_product/properties'));
87
+ foreach($facets as $facet) {
88
+ if($facet['autocomplete'] === '1') {
89
+ $this->feed['property_'.$facet['value']] = array(
90
+ 'prefix' => 'property_'.$facet['value'].'_',
91
+ 'tpl' => 'property_'.$facet['value'].'_%d_%s',
92
+ 'number' => 5,
93
+ );
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Get the suggest list
100
+ *
101
+ * @param string $query
102
+ * @param string $format
103
+ */
104
+ public function get($query, $format = 'html')
105
+ {
106
+ $url = $this->buildUrl($query);
107
+ Mage::log($url, null, 'antidot.log');
108
+ if(!$content = file_get_contents($url)) {
109
+ $response = array();
110
+ } elseif(!$response = json_decode($content, true)) {
111
+ $response = array();
112
+ }
113
+
114
+ $xml = $this->getXmlSuggest($response, $query);
115
+ if($format === 'xml') {
116
+ $this->displayXml($xml);
117
+ }
118
+
119
+ return $this->transformToXml($xml);
120
+ }
121
+
122
+ /**
123
+ * Display xml
124
+ *
125
+ * @param string $xml
126
+ */
127
+ private function displayXml($xml)
128
+ {
129
+ header ("Content-Type:text/xml");
130
+ echo $xml;
131
+ exit(0);
132
+ }
133
+
134
+ /**
135
+ * Build url to request AFS
136
+ *
137
+ * @param string $query
138
+ * @return string
139
+ */
140
+ protected function buildUrl($query)
141
+ {
142
+ return sprintf(
143
+ static::URI,
144
+ $this->afsHost,
145
+ $this->afsService,
146
+ $this->afsStatus,
147
+ $this->getFeeds(),
148
+ urlencode($query),
149
+ $this->getSession());
150
+
151
+ }
152
+
153
+ /**
154
+ * Extract the items from response
155
+ *
156
+ * @param array $response
157
+ * @return array
158
+ */
159
+ protected function getXmlSuggest($response, $query)
160
+ {
161
+ $xml = Mage::helper('Antidot/XmlWriter');
162
+ $xml->init();
163
+
164
+ $ns = array(
165
+ 'xmlns:afs' => 'http://ref.antidot.net/v7/afs#',
166
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
167
+ 'xsi:schemaLocation' => 'http://ref.antidot.net/v7/afs# http://ref.antidot.net/v7.7/acp-reply.xsd'
168
+ );
169
+ $xml->push('afs:replies', $ns);
170
+
171
+ $xml->push('afs:header');
172
+ $xml->emptyElement('afs:query', array('textQuery' => htmlentities($query)));
173
+ $xml->pop();
174
+
175
+ if(!is_numeric(key($response))) {
176
+ $response = $this->setOrders($response);
177
+ foreach($response as &$responseOrder) {
178
+ $type = key($responseOrder);
179
+ $data = current($responseOrder);
180
+ $feed = $this->getFeed($type);
181
+
182
+ $currentItems = 0;
183
+ $xml->push('afs:replySet', array('name' => $type));
184
+ $xml->emptyElement('afs:meta', array('uri' => $type, 'producer' => 'acp', 'totalItems' => count($data[2])));
185
+ foreach($data[2] as $key => &$item) {
186
+ $xml->push('afs:reply', array('label' => $data[1][$key]));
187
+ $this->writeOptions($xml, $item);
188
+ $xml->pop();
189
+ if(++$currentItems >= $feed['number']) {
190
+ break;
191
+ }
192
+ }
193
+ $xml->pop();
194
+ }
195
+ }
196
+ $xml->pop();
197
+
198
+ return $xml->getXml();
199
+ }
200
+
201
+ /**
202
+ * Write suggest options
203
+ *
204
+ * @param XmlWriter $xml
205
+ * @param array $items
206
+ */
207
+ protected function writeOptions($xml, $item)
208
+ {
209
+ foreach($item as $field => $value) {
210
+ if(is_array($value)) {
211
+ $this->writeOptions($xml, $value);
212
+ continue;
213
+ }
214
+
215
+ $attributes = array(
216
+ 'key' => $field,
217
+ 'value' => str_replace('&', '&amp;', html_entity_decode($value)),
218
+ );
219
+ $xml->emptyelement('afs:option', $attributes);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Set response order
225
+ *
226
+ * @param array $response
227
+ * @return array
228
+ */
229
+ protected function setOrders($response)
230
+ {
231
+ // set orders
232
+ $types = array();
233
+ foreach($response as $type => $data) {
234
+ $feed = $this->getFeedKey($type);
235
+ if(array_key_exists($feed, $this->typeOrder)) {
236
+ $types[$this->typeOrder[$feed]][$type] = $data;
237
+ unset($response[$type]);
238
+ }
239
+ }
240
+
241
+ foreach($response as $type => $data) {
242
+ $types[][$type] = $data;
243
+ }
244
+
245
+ ksort($types);
246
+ return $types;
247
+ }
248
+
249
+
250
+ /**
251
+ * Build the feed param
252
+ *
253
+ * @return string
254
+ */
255
+ protected function getFeeds()
256
+ {
257
+ list($lang) = explode('_', Mage::getStoreConfig('general/locale/code', Mage::app()->getStore()->getId()));
258
+
259
+ $feeds = '';
260
+ foreach($this->feed as $feed) {
261
+ $id = substr($feed['prefix'], 0, 18) !== 'featured_products_' ? Mage::app()->getStore()->getWebsiteId() : Mage::app()->getStore()->getId();
262
+ $feeds.= empty($feeds) ? '' : '-';
263
+ $feeds.= sprintf($feed['tpl'], $id, $lang);
264
+ }
265
+
266
+ return $feeds;
267
+ }
268
+
269
+ /**
270
+ * Get feed by type
271
+ *
272
+ * @param string $type
273
+ * @return array
274
+ */
275
+ protected function getFeed($type)
276
+ {
277
+ foreach($this->feed as $feed) {
278
+ if(strpos($type, $feed['prefix']) !== false) {
279
+ return $feed;
280
+ }
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Get the key feed
286
+ *
287
+ * @param string $type
288
+ * @return string
289
+ */
290
+ protected function getFeedKey($type)
291
+ {
292
+ foreach($this->feed as $key => $feed) {
293
+ if(strpos($type, $feed['prefix']) !== false) {
294
+ return $key;
295
+ }
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Format the response to html format
301
+ *
302
+ * @param string $suggestXml Response from AFS formated
303
+ * @return string
304
+ */
305
+ protected function transformToXml($suggestXml)
306
+ {
307
+ libxml_use_internal_errors(true);
308
+ $xml = simplexml_load_string($suggestXml);
309
+ $xsl = simplexml_load_string($this->template);
310
+
311
+ $xslt = new XSLTProcessor();
312
+ $xslt->importStylesheet($xsl);
313
+
314
+ if(!$xml = $xslt->transformToXml($xml)) {
315
+ Mage::log(print_r(libxml_get_errors(), true), null, 'antidot.log');
316
+ return '';
317
+ }
318
+
319
+ return str_replace('<?xml version="1.0"?>', '', $xml);
320
+ }
321
+ }
app/code/community/MDN/Antidot/Model/System/Config/ArticleAttribute.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_ArticleAttribute extends Mage_Eav_Model_Entity_Attribute_Source_Abstract
17
+ {
18
+ protected $options = false;
19
+
20
+ /**
21
+ * {@inherit}
22
+ */
23
+ public function getAllOptions()
24
+ {
25
+ if (!$this->_options) {
26
+ $pageCollection = Mage::getModel('cms/page')->getCollection();
27
+ foreach($pageCollection as $page) {
28
+ $attributes = array_keys($page->get());
29
+ break;
30
+ }
31
+
32
+ $options = array();
33
+ foreach ($attributes as $code) {
34
+ $options[] = array('value' => $code, 'label' => $code);
35
+ }
36
+
37
+ $this->_options = $options;
38
+ }
39
+ return $this->_options;
40
+ }
41
+
42
+ /**
43
+ * {@inherit}
44
+ */
45
+ public function toOptionArray()
46
+ {
47
+ return $this->getAllOptions();
48
+ }
49
+ }
app/code/community/MDN/Antidot/Model/System/Config/Backend/Engine.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Backend_Engine extends Mage_Core_Model_Config_Data
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ protected function _afterSave()
22
+ {
23
+ Mage::getModel('core/config')->saveConfig(
24
+ 'catalog/search/engine',
25
+ $this->getData('groups/engine/fields/engine/value')
26
+ );
27
+
28
+ return $this;
29
+ }
30
+ }
app/code/community/MDN/Antidot/Model/System/Config/CategoryAttribute.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_CategoryAttribute extends Mage_Eav_Model_Entity_Attribute_Source_Abstract
17
+ {
18
+ protected $options = false;
19
+
20
+ /**
21
+ * @var array Attribute type
22
+ */
23
+ protected $attributesType = array(
24
+ 'name' => 'text',
25
+ 'description' => array('text', 'textarea'),
26
+ 'keywords' => array('text', 'textarea'),
27
+ );
28
+
29
+ public function getAllOptions() {}
30
+
31
+ /**
32
+ * {@inherit}
33
+ */
34
+ public function _getAllOptions($type)
35
+ {
36
+ if (!$this->_options) {
37
+ $this->_options = array();
38
+ foreach($this->getActiveCategories() as $category) {
39
+ foreach($category->getAttributes() as $attribute) {
40
+ if(in_array($attribute->getFrontendInput(), (array)$type)) {
41
+ $this->_options[$attribute->getAttributeCode()] = array('value' => $attribute->getAttributeCode(), 'label' => $attribute->getName());
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ return $this->_options;
48
+ }
49
+
50
+ /**
51
+ * Return the active categories
52
+ *
53
+ * @return Collection
54
+ */
55
+ protected function getActiveCategories()
56
+ {
57
+ return Mage::getModel('catalog/category')
58
+ ->getCollection()
59
+ ->addAttributeToSelect('*')
60
+ ->addAttributeToFilter('is_active', 1);
61
+ }
62
+
63
+ /**
64
+ * {@inherit}
65
+ */
66
+ public function toOptionArray($elementName)
67
+ {
68
+ $type = null;
69
+ if(preg_match('/groups\[fields_category\]\[fields\]\[([a-zA-Z0-9_]+)\]\[value\]/', $elementName, $matches)) {
70
+ $field = $matches[1];
71
+ if(isset($this->attributesType[$field])) {
72
+ $type = $this->attributesType[$field];
73
+ }
74
+ }
75
+
76
+ return $this->_getAllOptions($type);
77
+ }
78
+ }
app/code/community/MDN/Antidot/Model/System/Config/Dir.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Dir
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function toOptionArray()
22
+ {
23
+ return array(
24
+ array('value' => 'asc', 'label' => Mage::helper('Antidot')->__('Ascending')),
25
+ array('value' => 'desc', 'label' => Mage::helper('Antidot')->__('Descending'))
26
+ );
27
+ }
28
+ }
app/code/community/MDN/Antidot/Model/System/Config/DisableEnable.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_DisableEnable extends Mage_Adminhtml_Model_System_Config_Source_Enabledisable
17
+ {
18
+ public function toOptionArray()
19
+ {
20
+ return array(
21
+ array('value' => 0, 'label' => Mage::helper('adminhtml')->__('Disable')),
22
+ array('value' => 1, 'label' => Mage::helper('adminhtml')->__('Enable')),
23
+ );
24
+ }
25
+ }
app/code/community/MDN/Antidot/Model/System/Config/Engine.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Engine
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function toOptionArray()
22
+ {
23
+ return array(
24
+ array(
25
+ 'value' => 'Antidot/engine_antidot',
26
+ 'label' => Mage::helper('adminhtml')->__('AFS@Store')),
27
+ array(
28
+ 'value' => 'catalogsearch/fulltext_engine',
29
+ 'label' => Mage::helper('adminhtml')->__('Magento'))
30
+ );
31
+ }
32
+ }
app/code/community/MDN/Antidot/Model/System/Config/Facet.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Facet
17
+ {
18
+
19
+ protected $options = false;
20
+
21
+ /**
22
+ * {@inherit}
23
+ */
24
+ public function toOptionArray($typeExclude = null)
25
+ {
26
+ if (!$this->options) {
27
+ try {
28
+ $search = Mage::getModel('Antidot/Search_Search');
29
+
30
+ $this->options = array();
31
+ foreach($search->getFacets() as $facetId => $facet) {
32
+ if($typeExclude === null || $facet->get_type() !== $typeExclude) {
33
+ $this->options[] = array('value' => $facetId.'|'.$facet->label, 'label' => $facet->label.' ('.$facet->get_type().')');
34
+ }
35
+ }
36
+
37
+ return $this->options;
38
+ } catch(Exception $e) {
39
+ $this->options = array();
40
+ }
41
+ }
42
+
43
+ return $this->options;
44
+ }
45
+ }
app/code/community/MDN/Antidot/Model/System/Config/Number.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Number
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function toOptionArray($nb)
22
+ {
23
+ $nb = !empty($nb) ? (int)$nb : 10;
24
+ $options = array();
25
+ for($i = 1; $i < $nb+1; $i++) {
26
+ $options[] = array('value' => $i, 'label' => $i);
27
+ }
28
+
29
+ return $options;
30
+ }
31
+ }
app/code/community/MDN/Antidot/Model/System/Config/Options.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Options
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function toOptionArray($nb)
22
+ {
23
+ $nb = !empty($nb) ? (int)$nb : 20;
24
+ $options = array();
25
+ $options[] = array('value' => 0, 'label' => Mage::helper('Antidot')->__('All'));
26
+ for($i = 5; $i < $nb+1; $i+= 5) {
27
+ $options[] = array('value' => $i, 'label' => $i);
28
+ }
29
+
30
+ return $options;
31
+ }
32
+ }
app/code/community/MDN/Antidot/Model/System/Config/ProductAttribute.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_ProductAttribute extends Mage_Eav_Model_Entity_Attribute_Source_Abstract
17
+ {
18
+ protected $options = false;
19
+
20
+ /**
21
+ * @var array Attribute type
22
+ */
23
+ protected $attributesType = array(
24
+ 'name' => 'text',
25
+ 'short_name' => 'text',
26
+ 'description' => array('text', 'textarea'),
27
+ 'keywords' => array('text', 'textarea'),
28
+ 'is_promotional' => 'boolean',
29
+ 'is_new' => 'boolean',
30
+ 'is_best_sale' => 'boolean',
31
+ 'is_featured' => 'boolean',
32
+ 'materials' => array('text', 'select'),
33
+ 'colors' => array('text', 'select'),
34
+ 'models' => array('text', 'select'),
35
+ 'sizes' => array('text', 'select'),
36
+ 'manufacturer' => array('text', 'select'),
37
+ 'reseller' => 'text',
38
+ 'supplier' => 'text',
39
+ 'gtin' => 'text',
40
+ 'identifier' => 'text',
41
+ 'misc' => 'text',
42
+ 'properties' => 'text',
43
+ );
44
+
45
+ public function getAllOptions() {}
46
+
47
+ /**
48
+ * {@inherit}
49
+ */
50
+ public function _getAllOptions($type)
51
+ {
52
+ $key = md5(serialize((array)$type));
53
+ if (!$this->_options[$key]) {
54
+ $options[] = array('value' => '', 'label' => '');
55
+ if($type === null || in_array('text', (array)$type)) {
56
+ $options[] = array('value' => 'sku', 'label' => 'sku');
57
+ }
58
+
59
+ foreach ($this->getAttributes($type) as $attribute) {
60
+ $options[] = array(
61
+ 'value' => $attribute->getAttributeCode(),
62
+ 'label' => $attribute->getName(),
63
+ );
64
+ }
65
+
66
+ $this->_options[$key] = $options;
67
+ }
68
+ return $this->_options[$key];
69
+ }
70
+
71
+ /**
72
+ * {@inherit}
73
+ */
74
+ protected function getAttributes($type)
75
+ {
76
+ $entityTypeId = Mage::getModel('eav/entity_type')->loadByCode('catalog_product')->getId();
77
+ $attributes = Mage::getResourceModel('eav/entity_attribute_collection')
78
+ ->setEntityTypeFilter($entityTypeId)
79
+ ->addFieldToFilter('backend_type', array('neq' => 'static'));
80
+
81
+ if($type !== null) {
82
+ $attributes->addFieldToFilter('frontend_input', array('in' => (array)$type));
83
+ }
84
+
85
+ return $attributes;
86
+ }
87
+
88
+ /**
89
+ * {@inherit}
90
+ */
91
+ public function toOptionArray($elementName)
92
+ {
93
+ $type = null;
94
+ if(preg_match('/groups\[fields_product\]\[fields\]\[([a-zA-Z0-9_]+)\]\[value\]/', $elementName, $matches)) {
95
+ $field = $matches[1];
96
+ if(isset($this->attributesType[$field])) {
97
+ $type = $this->attributesType[$field];
98
+ }
99
+ }
100
+
101
+ return $this->_getAllOptions($type);
102
+ }
103
+ }
app/code/community/MDN/Antidot/Model/System/Config/PromoteRedirect.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_PromoteRedirect
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function toOptionArray()
22
+ {
23
+ return array(
24
+ array(
25
+ 'value' => 'no_result',
26
+ 'label' => Mage::helper('adminhtml')->__('Only when no result')),
27
+ array(
28
+ 'value' => 'always',
29
+ 'label' => Mage::helper('adminhtml')->__('Always'))
30
+ );
31
+ }
32
+ }
app/code/community/MDN/Antidot/Model/System/Config/Sort.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Sort
17
+ {
18
+
19
+ /**
20
+ * Cache options
21
+ */
22
+ protected static $options;
23
+
24
+ /**
25
+ * @var array Marketing fields
26
+ */
27
+ protected $marketingFields = array();
28
+
29
+
30
+ /**
31
+ * {@inherit}
32
+ */
33
+ public function toOptionArray()
34
+ {
35
+ if (!self::$options) {
36
+ $this->initMarketingFields();
37
+ $options = array();
38
+ $options[] = array('value' => 'afs:relevance|Relevance', 'label' => Mage::helper('Antidot')->__('Relevance'));
39
+ $options[] = array('value' => 'position|Position', 'label' => Mage::helper('Antidot')->__('Position'));
40
+ $options[] = array('value' => 'name|Name', 'label' => Mage::helper('Antidot')->__('Name'));
41
+
42
+ foreach($this->marketingFields as $field => $label) {
43
+ if(Mage::getStoreConfig('antidot/fields_product/'.$field) !== '') {
44
+ $options[] = array('value' => $field.'|'.$label, 'label' => $label);
45
+ }
46
+ }
47
+
48
+
49
+ self::$options = array_merge($options, Mage::getModel("Antidot/System_Config_Facet")->toOptionArray('STRING'));
50
+
51
+ foreach(self::$options as &$option) {
52
+ if(preg_match('/^price_/', $option['value'])) {
53
+ $option['label'] = Mage::helper('Antidot')->__('Price');
54
+ $option['value'] = 'price|'.Mage::helper('Antidot')->__('Price');
55
+ }
56
+ }
57
+
58
+ }
59
+
60
+ return self::$options;
61
+ }
62
+
63
+ /**
64
+ * Init the marketing fields
65
+ */
66
+ public function initMarketingFields()
67
+ {
68
+ $this->marketingFields = array(
69
+ 'is_promotional' => Mage::helper('Antidot')->__('Is promotional'),
70
+ 'is_new' => Mage::helper('Antidot')->__('Is new'),
71
+ 'is_best_sale' => Mage::helper('Antidot')->__('Is top sale'),
72
+ 'is_featured' => Mage::helper('Antidot')->__('Is featured'),
73
+ );
74
+ }
75
+ }
app/code/community/MDN/Antidot/Model/System/Config/Suggest/Type.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_Suggest_Type
17
+ {
18
+ protected $defaultTypes = array('categories', 'products', 'brands');
19
+ protected $types = array();
20
+
21
+ /**
22
+ * Return the types list
23
+ *
24
+ * @return array
25
+ */
26
+ public function toOptionArray()
27
+ {
28
+ $options = array();
29
+ foreach ($this->getTypes() as $v) {
30
+ $options[] = array(
31
+ 'value' => $v,
32
+ 'label' => Mage::helper('Antidot')->__($v)
33
+ );
34
+ }
35
+
36
+ return $options;
37
+ }
38
+
39
+ /**
40
+ * Return available types, and move first element to the end
41
+ *
42
+ * @return array
43
+ */
44
+ protected function getTypes()
45
+ {
46
+ if(empty($this->types)) {
47
+ $this->types = $this->defaultTypes;
48
+ } else {
49
+ $tmpType = array_shift($this->types);
50
+ $this->types[] = $tmpType;
51
+ }
52
+
53
+ return $this->types;
54
+ }
55
+ }
app/code/community/MDN/Antidot/Model/System/Config/WSStatus.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_System_Config_WSStatus
17
+ {
18
+ /**
19
+ * {@inherit}
20
+ */
21
+ public function toOptionArray()
22
+ {
23
+ return array(
24
+ array('value' => 'sandbox', 'label' => 'sandbox'),
25
+ array('value' => 'alpha', 'label' => 'alpha'),
26
+ array('value' => 'beta', 'label' => 'beta'),
27
+ array('value' => 'rc', 'label' => 'rc'),
28
+ array('value' => 'stable', 'label' => 'stable')
29
+ );
30
+ }
31
+ }
app/code/community/MDN/Antidot/Model/Transport.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Transport extends Mage_Core_Model_Abstract
17
+ {
18
+
19
+ const TRANS_FILE = 'file';
20
+ const TRANS_FTP = 'ftp';
21
+ const TRANS_HTTP = 'http';
22
+
23
+ /**
24
+ * Send files to Antidot
25
+ *
26
+ * @param string $file File to send
27
+ * @param string $type The transport type used to send the file
28
+ */
29
+ public function send($file, $type = self::TRANS_FILE)
30
+ {
31
+ if($transport = Mage::getModel('Antidot/Transport_'.ucfirst($type))) {
32
+ return $transport->send($file);
33
+ }
34
+
35
+ throw new Exception('The type transport "'.$type.'" does not exist');
36
+ }
37
+ }
app/code/community/MDN/Antidot/Model/Transport/Abstract.php ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Transport_Abstract extends Mage_Core_Model_Abstract
17
+ {
18
+
19
+ }
app/code/community/MDN/Antidot/Model/Transport/File.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Transport_File extends MDN_Antidot_Model_Transport_Abstract implements MDN_Antidot_Model_Transport_Interface
17
+ {
18
+
19
+ const DIRECTORY = '/home/bmsoliv2/www/magento/';
20
+
21
+ /**
22
+ * {@inherit}
23
+ */
24
+ public function send($file)
25
+ {
26
+ return rename($file, self::DIRECTORY.'/'.basename($file));
27
+ }
28
+ }
app/code/community/MDN/Antidot/Model/Transport/Ftp.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Transport_Ftp extends MDN_Antidot_Model_Transport_Abstract implements MDN_Antidot_Model_Transport_Interface
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ public function send($file)
23
+ {
24
+ $ftpConfig = Mage::getStoreConfig('antidot/ftp');
25
+ foreach($ftpConfig as $key => $config) {
26
+ if(empty($config) && $key !== 'port') {
27
+ throw new Exception("The ftp $key is required");
28
+ }
29
+ }
30
+
31
+ if(!$fHandle = fopen($file, 'r')) {
32
+ throw new Exception("Can't read file ".$file);
33
+ }
34
+
35
+ $url = 'sftp://'
36
+ .$ftpConfig['login'].':'.$ftpConfig['password']
37
+ .'@upload.antidot.net/'.$ftpConfig['directory'].'/'.basename($file);
38
+
39
+ $curl = curl_init();
40
+ curl_setopt($curl, CURLOPT_URL, $url);
41
+ curl_setopt($curl, CURLOPT_UPLOAD, 1);
42
+ curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_SFTP);
43
+ curl_setopt($curl, CURLOPT_INFILE, $fHandle);
44
+ curl_setopt($curl, CURLOPT_INFILESIZE, filesize($file));
45
+ curl_exec($curl);
46
+
47
+ if (curl_errno($curl) != 0) {
48
+ throw new Exception("Can't send the file to ftp");
49
+ }
50
+ }
51
+ }
app/code/community/MDN/Antidot/Model/Transport/Http.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Model_Transport_Http extends MDN_Antidot_Model_Transport_Abstract implements MDN_Antidot_Model_Transport_Interface
17
+ {
18
+
19
+ /**
20
+ * {@inherit}
21
+ */
22
+ public function send($file)
23
+ {
24
+
25
+ }
26
+ }
app/code/community/MDN/Antidot/Model/Transport/Interface.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ interface MDN_Antidot_Model_Transport_Interface
17
+ {
18
+ /**
19
+ * Send file to Antidot
20
+ *
21
+ * @param string $file Files to send
22
+ */
23
+ public function send($files);
24
+ }
app/code/community/MDN/Antidot/controllers/Admin/PushController.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Admin_PushController extends Mage_Adminhtml_Controller_Action
17
+ {
18
+
19
+ /**
20
+ * Generate the category file, call from back office
21
+ */
22
+ public function CategoryAction()
23
+ {
24
+ Mage::getModel('Antidot/Observer')->categoriesFullExport();
25
+ $this->_redirectReferer();
26
+ }
27
+
28
+ /**
29
+ * Generate the catalog file, call from back office
30
+ */
31
+ public function ProductAction()
32
+ {
33
+ Mage::getModel('Antidot/Observer')->catalogFullExport();
34
+ $this->_redirectReferer();
35
+ }
36
+ }
app/code/community/MDN/Antidot/controllers/Front/SearchController.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Magento
5
+ *
6
+ * NOTICE OF LICENSE
7
+ *
8
+ * This source file is subject to the Open Software License (OSL 3.0)
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/osl-3.0.php
11
+ *
12
+ * @copyright Copyright (c) 2009 Maison du Logiciel (http://www.maisondulogiciel.com)
13
+ * @author : Olivier ZIMMERMANN
14
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
15
+ */
16
+ class MDN_Antidot_Front_SearchController extends Mage_Core_Controller_Front_Action
17
+ {
18
+
19
+ /**
20
+ * Method call by autocomplete
21
+ */
22
+ public function SuggestAction()
23
+ {
24
+ if (!$query = $this->getRequest()->getParam('q', false)) {
25
+ $this->getResponse()->setRedirect(Mage::getSingleton('core/url')->getBaseUrl());
26
+ }
27
+
28
+ $format = 'html';
29
+ if ($formatParam = $this->getRequest()->getParam('format', false)) {
30
+ $format = $formatParam;
31
+ }
32
+
33
+ $this->getResponse()->setBody(Mage::getModel('Antidot/Search_Suggest')->get($query, $format));
34
+ }
35
+ }
app/code/community/MDN/Antidot/etc/config.xml ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <crontab>
4
+ <jobs>
5
+ <antidot_catalog_full_export>
6
+ <schedule><cron_expr>00 2 * * *</cron_expr></schedule>
7
+ <run><model>Antidot/Observer::catalogFullExport</model></run>
8
+ </antidot_catalog_full_export>
9
+ <antidot_catalog_inc_export>
10
+ <schedule><cron_expr>45 7-20 * * *</cron_expr></schedule>
11
+ <run><model>Antidot/Observer::catalogIncExport</model></run>
12
+ </antidot_catalog_inc_export>
13
+ <antidot_categories_full_export>
14
+ <schedule><cron_expr>00 21 * * *</cron_expr></schedule>
15
+ <run><model>Antidot/Observer::categoriesFullExport</model></run>
16
+ </antidot_categories_full_export>
17
+ </jobs>
18
+ </crontab>
19
+ <modules>
20
+ <MDN_Antidot>
21
+ <version>1.0.2</version>
22
+ </MDN_Antidot>
23
+ </modules>
24
+ <global>
25
+ <helpers>
26
+ <Antidot>
27
+ <class>MDN_Antidot_Helper</class>
28
+ </Antidot>
29
+ <catalogsearch>
30
+ <rewrite>
31
+ <data>MDN_Antidot_Helper_CatalogSearch_Data</data>
32
+ </rewrite>
33
+ </catalogsearch>
34
+ </helpers>
35
+ <blocks>
36
+ <Antidot>
37
+ <class>MDN_Antidot_Block</class>
38
+ </Antidot>
39
+ <catalogsearch>
40
+ <rewrite>
41
+ <layer>MDN_Antidot_Block_Catalogsearch_Layer</layer>
42
+ <result>MDN_Antidot_Block_Catalogsearch_Result</result>
43
+ </rewrite>
44
+ </catalogsearch>
45
+ </blocks>
46
+ <models>
47
+
48
+ <Antidot>
49
+ <class>MDN_Antidot_Model</class>
50
+ <resourceModel>Antidot_engine</resourceModel>
51
+ </Antidot>
52
+
53
+ <Antidot_engine>
54
+ <class>MDN_Antidot_Model_Resource</class>
55
+ <entities></entities>
56
+ </Antidot_engine>
57
+
58
+ </models>
59
+ <resources>
60
+ <Antidot_setup>
61
+ <setup>
62
+ <module>MDN_Antidot</module>
63
+ <class>Mage_Eav_Model_Entity_Setup</class>
64
+ </setup>
65
+ <connection>
66
+ <use>core_setup</use>
67
+ </connection>
68
+ </Antidot_setup>
69
+ <Antidot_write>
70
+ <connection>
71
+ <use>core_write</use>
72
+ </connection>
73
+ </Antidot_write>
74
+ <Antidot_read>
75
+ <connection>
76
+ <use>core_read</use>
77
+ </connection>
78
+ </Antidot_read>
79
+ </resources>
80
+
81
+ </global>
82
+ <adminhtml>
83
+ <translate>
84
+ <modules>
85
+ <MDN_Antidot>
86
+ <files>
87
+ <default>MDN_Antidot.csv</default>
88
+ </files>
89
+ </MDN_Antidot>
90
+ </modules>
91
+ </translate>
92
+
93
+ <layout>
94
+ <updates>
95
+ <Antidot>
96
+ <file>antidot.xml</file>
97
+ </Antidot>
98
+ </updates>
99
+ </layout>
100
+ <acl>
101
+ <resources>
102
+ <admin>
103
+ <children>
104
+ <system>
105
+ <children>
106
+ <config>
107
+ <children>
108
+ <antidot module="Antidot">
109
+ <title>Antidot</title>
110
+ </antidot>
111
+ </children>
112
+ </config>
113
+ </children>
114
+ </system>
115
+ </children>
116
+ </admin>
117
+ </resources>
118
+ </acl>
119
+ </adminhtml>
120
+
121
+ <frontend>
122
+ <routers>
123
+ <Antidot>
124
+ <use>standard</use>
125
+ <args>
126
+ <module>MDN_Antidot</module>
127
+ <frontName>Antidot</frontName>
128
+ </args>
129
+ </Antidot>
130
+ </routers>
131
+ <layout>
132
+ <updates>
133
+ <Antidot>
134
+ <file>antidot.xml</file>
135
+ </Antidot>
136
+ </updates>
137
+ </layout>
138
+ </frontend>
139
+
140
+ <admin>
141
+ <routers>
142
+ <Antidot>
143
+ <use>admin</use>
144
+ <args>
145
+ <module>MDN_Antidot</module>
146
+ <frontName>Antidot</frontName>
147
+ </args>
148
+ </Antidot>
149
+ </routers>
150
+ </admin>
151
+
152
+ <default>
153
+ <antidot>
154
+ <web_service>
155
+ <status>rc</status>
156
+ </web_service>
157
+ <engine>
158
+ <spellcheck>Did you mean {spellcheck} ?</spellcheck>
159
+ <price_facet>From {min}€ to {max}€</price_facet>
160
+ <feed_catalog>1</feed_catalog>
161
+ <facet_multiple>0</facet_multiple>
162
+ <facet_options>100</facet_options>
163
+ <default_sort>a:1:{s:18:"_1395736930373_373";a:2:{s:5:"field";s:23:"afs:relevance|Relevance";s:3:"dir";s:4:"desc";}}</default_sort>
164
+ </engine>
165
+ <suggest>
166
+ <enable>1</enable>
167
+ <products>1</products>
168
+ <brands>1</brands>
169
+ <categories>1</categories>
170
+ <products_displayed>10</products_displayed>
171
+ <brands_displayed>10</brands_displayed>
172
+ <categories_displayed>10</categories_displayed>
173
+ <template>
174
+ <![CDATA[
175
+ <?xml version="1.0" encoding="UTF-8"?>
176
+ <xsl:stylesheet xmlns:afs="http://ref.antidot.net/v7/afs#"
177
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
178
+ <!-- Titles to translate in localized storeviews -->
179
+ <xsl:variable name="results-title">Our suggestions...</xsl:variable>
180
+ <xsl:variable name="no-result-title">No suggestions.</xsl:variable>
181
+ <xsl:variable name="see-more-products">See more products</xsl:variable>
182
+ <!-- Column titles -->
183
+ <xsl:variable name="products-title">Products</xsl:variable>
184
+ <xsl:variable name="categories-title">Categories</xsl:variable>
185
+ <xsl:variable name="brands-title">Brands</xsl:variable>
186
+
187
+ <!-- Main HTML template -->
188
+ <xsl:template match="afs:replies">
189
+ <ul class="ajax-search">
190
+ <li class="search-container-top"/>
191
+ <xsl:choose>
192
+ <xsl:when test="afs:replySet">
193
+ <li class="header-text">
194
+ <xsl:value-of select="$results-title"/>
195
+ </li>
196
+ <xsl:apply-templates select="afs:replySet"/>
197
+ <a>
198
+ <xsl:attribute name="href">/index.php/catalogsearch/result/?q=<xsl:value-of
199
+ select="afs:header/afs:query/@textQuery"/>*</xsl:attribute>
200
+ <xsl:value-of select="$see-more-products"/>
201
+ </a>
202
+ </xsl:when>
203
+ <xsl:otherwise>
204
+ <li class="header-text">
205
+ <xsl:value-of select="$no-result-title"/>
206
+ </li>
207
+ </xsl:otherwise>
208
+ </xsl:choose>
209
+ </ul>
210
+ </xsl:template>
211
+
212
+ <!-- Suggestions column main template -->
213
+ <xsl:template match="afs:replySet">
214
+ <xsl:variable name="title">
215
+ <!-- Use of 'starts-with/substring-before' is needed to be compliant with multiple storeviews/languages which give suffixes to replySet name -->
216
+ <xsl:choose>
217
+ <xsl:when test="starts-with(@name,'categories')">
218
+ <xsl:value-of select="$categories-title"/>
219
+ </xsl:when>
220
+ <xsl:when test="starts-with(@name,'featured_products')">
221
+ <xsl:value-of select="$products-title"/>
222
+ </xsl:when>
223
+ <xsl:when test="starts-with(@name,'brands')">
224
+ <xsl:value-of select="$brands-title"/>
225
+ </xsl:when>
226
+ <xsl:otherwise>
227
+ <xsl:value-of select="substring-before(@name,'_')"/>
228
+ </xsl:otherwise>
229
+ </xsl:choose>
230
+ </xsl:variable>
231
+ <li class="title">
232
+ <span>
233
+ <xsl:value-of select="$title"/> (<xsl:value-of select="afs:meta/@totalItems"/>) :
234
+ </span>
235
+ </li>
236
+ <xsl:apply-templates select="afs:reply"/>
237
+ </xsl:template>
238
+
239
+ <!-- Template common to basic and products replies -->
240
+ <xsl:template match="afs:reply">
241
+ <li>
242
+ <!-- Show thumbnail if any -->
243
+ <xsl:apply-templates select="afs:option[@key='url_thumbnail']" mode="url_thumbnail"/>
244
+ <a>
245
+ <xsl:apply-templates select="afs:option[@key='url']" mode="href"/>
246
+ <xsl:attribute name="title">
247
+ <xsl:value-of select="@label"/>
248
+ </xsl:attribute>
249
+ <!-- Apply basic or product template -->
250
+ <xsl:apply-templates
251
+ select="self::node()[afs:option[@key='url'] and not(afs:option[@key='price'])]"
252
+ mode="basic"/>
253
+ <xsl:apply-templates select="self::node()[afs:option[@key='price']]" mode="product"
254
+ />
255
+ </a>
256
+ </li>
257
+ </xsl:template>
258
+
259
+ <!-- Thumbnail display -->
260
+ <xsl:template match="afs:option" mode="url_thumbnail">
261
+ <div class="image" style="float: left; margin-right:5px;">
262
+ <img width="35">
263
+ <xsl:attribute name="src">
264
+ <xsl:value-of select="@value"/>
265
+ </xsl:attribute>
266
+ </img>
267
+ </div>
268
+ </xsl:template>
269
+
270
+ <!-- Creates href link -->
271
+ <xsl:template match="afs:option" mode="href">
272
+ <xsl:attribute name="href">
273
+ <xsl:value-of select="@value"/>
274
+ </xsl:attribute>
275
+ </xsl:template>
276
+
277
+ <!-- Basic suggestion template -->
278
+ <xsl:template match="afs:reply" mode="basic">
279
+ <div class="name">
280
+ <xsl:value-of select="@label"/>
281
+ <span style="padding-left: 5px;"/>
282
+ </div>
283
+ </xsl:template>
284
+
285
+ <!-- Product rich template: uses custom metadata like price -->
286
+ <xsl:template match="afs:reply" mode="product">
287
+ <xsl:variable name="currency">
288
+ <xsl:value-of select="afs:option[@key='currency_sign']/@value"/>
289
+ </xsl:variable>
290
+ <xsl:variable name="price_display">
291
+ <xsl:value-of select="afs:option[@key='price']/@value"/>
292
+ <xsl:value-of select="$currency"/>
293
+ </xsl:variable>
294
+ <div class="name">
295
+ <xsl:value-of select="@label"/>
296
+ <span style="padding-left: 5px;">
297
+ <xsl:choose>
298
+ <xsl:when test="afs:option[@key='price_cut']">
299
+ <s>
300
+ <xsl:value-of select="afs:option[@key='price_cut']/@value"/>
301
+ <xsl:value-of select="$currency"/>
302
+ </s>&#160; <xsl:value-of select="$price_display"/>
303
+ </xsl:when>
304
+ <xsl:otherwise>
305
+ <xsl:value-of select="$price_display"/>
306
+ </xsl:otherwise>
307
+ </xsl:choose>
308
+ </span>
309
+ </div>
310
+ </xsl:template>
311
+
312
+ </xsl:stylesheet>
313
+ ]]>
314
+ </template>
315
+ <suggest_xml>
316
+ <![CDATA[
317
+ &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;<br />
318
+ &lt;feeds&gt;<br />
319
+ &lt;feed name=&quot;catalog_fr&quot;&gt;<br />
320
+ &lt;AFS&gt;<br />
321
+ &lt;Suggestions&gt;<br />
322
+ &lt;Suggestion label=&quot;T-Shirt manches courtes&quot;&gt;<br />
323
+ &lt;Option key=&quot;category&quot; value=&quot;T-shirt&quot;/&gt;<br />
324
+ &lt;Option key=&quot;category&quot; value=&quot;T-shirts b&eacute;b&eacute;&quot;/&gt;<br />
325
+ &lt;Option key=&quot;category&quot; value=&quot;Quechua&quot;/&gt;<br />
326
+ &lt;Option key=&quot;category&quot; value=&quot;Randonn&eacute;e&quot;/&gt;<br />
327
+ &lt;/Suggestion&gt;<br />
328
+ &lt;/Suggestions&gt;<br />
329
+ &lt;/AFS&gt;<br />
330
+ &lt;/feed&gt;<br />
331
+ &lt;feed name=&quot;brands_fr&quot;&gt;<br />
332
+ &lt;AFS&gt;<br />
333
+ &lt;Suggestions&gt;<br />
334
+ &lt;Suggestion label=&quot;QUECHUA&quot;/&gt;<br />
335
+ &lt;/Suggestions&gt;<br />
336
+ &lt;/AFS&gt;<br />
337
+ &lt;/feed&gt;<br />
338
+ &lt;feed name=&quot;categories_fr&quot;&gt;<br />
339
+ &lt;AFS&gt;<br />
340
+ &lt;Suggestions&gt;<br />
341
+ &lt;Suggestion label=&quot;T-shirt&quot;&gt;<br />
342
+ &lt;Option key=&quot;id&quot; value=&quot;50657&quot;/&gt;<br />
343
+ &lt;Option key=&quot;url&quot; value=&quot;%2fF-50657-t_shirt&quot;/&gt;<br />
344
+ &lt;Option key=&quot;level&quot; value=&quot;1&quot;/&gt;<br />
345
+ &lt;/Suggestion&gt;<br />
346
+ &lt;Suggestion label=&quot;T-shirts b&eacute;b&eacute;&quot;&gt;<br />
347
+ &lt;Option key=&quot;id&quot; value=&quot;308392&quot;/&gt;<br />
348
+ &lt;Option key=&quot;url&quot; value=&quot;%2fC-308392-t_shirts&quot;/&gt;<br />
349
+ &lt;Option key=&quot;level&quot; value=&quot;1&quot;/&gt;<br />
350
+ &lt;/Suggestion&gt;<br />
351
+ &lt;Suggestion label=&quot;Quechua&quot;&gt;<br />
352
+ &lt;Option key=&quot;id&quot; value=&quot;309970&quot;/&gt;<br />
353
+ &lt;Option key=&quot;url&quot; value=&quot;%2fC-309970-quechua&quot;/&gt;<br />
354
+ &lt;Option key=&quot;level&quot; value=&quot;1&quot;/&gt;<br />
355
+ &lt;/Suggestion&gt;<br />
356
+ &lt;Suggestion label=&quot;Randonn&eacute;e&quot;&gt;<br />
357
+ &lt;Option key=&quot;id&quot; value=&quot;313441&quot;/&gt;<br />
358
+ &lt;Option key=&quot;url&quot; value=&quot;%2fC-313441-randonnee&quot;/&gt;<br />
359
+ &lt;Option key=&quot;level&quot; value=&quot;1&quot;/&gt;<br />
360
+ &lt;/Suggestion&gt;<br />
361
+ &lt;/Suggestions&gt;<br />
362
+ &lt;/AFS&gt;<br />
363
+ &lt;/feed&gt;<br />
364
+ &lt;/feeds&gt;
365
+ ]]>
366
+ </suggest_xml>
367
+ <order_1>categories</order_1>
368
+ <order_2>products</order_2>
369
+ <order_3>brands</order_3>
370
+ </suggest>
371
+ <fields_category>
372
+ <name>name</name>
373
+ <keywords>meta_keywords</keywords>
374
+ <description>description</description>
375
+ </fields_category>
376
+ <fields_product>
377
+ <in_stock_only>0</in_stock_only>
378
+ <name>name</name>
379
+ <short_name>short_name</short_name>
380
+ <colors>color</colors>
381
+ <models>model</models>
382
+ <manufacturer>manufacturer</manufacturer>
383
+ <gender>gender</gender>
384
+ <description>a:1:{s:18:"_1390211203389_389";a:1:{s:5:"value";s:11:"description";}}</description>
385
+ <keywords>meta_keyword</keywords>
386
+ <identifier>a:1:{s:18:"_1389092688608_608";a:1:{s:5:"value";s:3:"sku";}}</identifier>
387
+ </fields_product>
388
+ <promote>
389
+ <redirect>no_result</redirect>
390
+ </promote>
391
+ </antidot>
392
+ </default>
393
+ </config>
app/code/community/MDN/Antidot/etc/system.xml ADDED
@@ -0,0 +1,636 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <sections>
4
+ <antidot translate="label" module="Antidot">
5
+ <label>AFS@Store</label>
6
+ <tab>catalog</tab>
7
+ <frontend_type>text</frontend_type>
8
+ <sort_order>500</sort_order>
9
+ <show_in_default>1</show_in_default>
10
+ <show_in_website>1</show_in_website>
11
+ <show_in_store>1</show_in_store>
12
+ <groups>
13
+ <general>
14
+ <label>General</label>
15
+ <comment><![CDATA[<a href="https://bo.afs-antidot.net/" target="_blank" style="font-size:16px">Antidot Back office (Analytics, Synonyms, Promote)</a>]]></comment>
16
+ <frontend_type>text</frontend_type>
17
+ <sort_order>1</sort_order>
18
+ <show_in_default>1</show_in_default>
19
+ <show_in_website>1</show_in_website>
20
+ <show_in_store>1</show_in_store>
21
+ <fields>
22
+ <version translate="label">
23
+ <label>AFS@Store Extension Version</label>
24
+ <frontend_model>Antidot/System_Config_Html_Version</frontend_model>
25
+ <sort_order>1</sort_order>
26
+ <show_in_default>1</show_in_default>
27
+ <show_in_website>1</show_in_website>
28
+ <show_in_store>1</show_in_store>
29
+ <comment>Communicate this version to the support team if you need help</comment>
30
+ </version>
31
+ <owner translate="label">
32
+ <label>Organization name</label>
33
+ <frontend_type>text</frontend_type>
34
+ <sort_order>10</sort_order>
35
+ <show_in_default>1</show_in_default>
36
+ <show_in_website>1</show_in_website>
37
+ <show_in_store>1</show_in_store>
38
+ <comment>Useful for support team</comment>
39
+ </owner>
40
+ <email translate="label">
41
+ <label>E-mail</label>
42
+ <frontend_type>text</frontend_type>
43
+ <sort_order>12</sort_order>
44
+ <show_in_default>1</show_in_default>
45
+ <show_in_website>0</show_in_website>
46
+ <show_in_store>0</show_in_store>
47
+ <comment>Used to send alert when an error occured</comment>
48
+ </email>
49
+ </fields>
50
+
51
+ </general>
52
+
53
+ <ftp>
54
+ <label>Data upload parameters</label>
55
+ <frontend_type>text</frontend_type>
56
+ <sort_order>2</sort_order>
57
+ <show_in_default>1</show_in_default>
58
+ <show_in_website>0</show_in_website>
59
+ <show_in_store>0</show_in_store>
60
+ <fields>
61
+ <host translate="label">
62
+ <label>Host</label>
63
+ <frontend_type>text</frontend_type>
64
+ <sort_order>1</sort_order>
65
+ <show_in_default>1</show_in_default>
66
+ <comment>Host to connect to Antidot to push files</comment>
67
+ </host>
68
+ <directory translate="label">
69
+ <label>Upload Directory</label>
70
+ <frontend_type>text</frontend_type>
71
+ <sort_order>2</sort_order>
72
+ <show_in_default>1</show_in_default>
73
+ <comment>Directory to upload files</comment>
74
+ </directory>
75
+ <login translate="label">
76
+ <label>Login</label>
77
+ <frontend_type>text</frontend_type>
78
+ <sort_order>3</sort_order>
79
+ <show_in_default>1</show_in_default>
80
+ <comment>Your Antidot FTP Login</comment>
81
+ </login>
82
+ <password translate="label">
83
+ <label>Password</label>
84
+ <frontend_type>password</frontend_type>
85
+ <sort_order>4</sort_order>
86
+ <show_in_default>1</show_in_default>
87
+ <comment>Your Antidot FTP Password</comment>
88
+ </password>
89
+ </fields>
90
+ </ftp>
91
+
92
+ <web_service>
93
+ <label>Web services</label>
94
+ <frontend_type>text</frontend_type>
95
+ <sort_order>3</sort_order>
96
+ <show_in_default>1</show_in_default>
97
+ <show_in_website>0</show_in_website>
98
+ <show_in_store>0</show_in_store>
99
+ <fields>
100
+ <host translate="label">
101
+ <label>Host</label>
102
+ <frontend_type>text</frontend_type>
103
+ <sort_order>1</sort_order>
104
+ <show_in_default>1</show_in_default>
105
+ <comment>Web service host to communicate with Antidot</comment>
106
+ </host>
107
+ <service translate="label">
108
+ <label>Service ID</label>
109
+ <frontend_type>text</frontend_type>
110
+ <sort_order>2</sort_order>
111
+ <show_in_default>1</show_in_default>
112
+ <comment>Antidot service ID</comment>
113
+ </service>
114
+ <status translate="label">
115
+ <label>Status</label>
116
+ <frontend_type>select</frontend_type>
117
+ <source_model>Antidot/System_Config_WSStatus</source_model>
118
+ <sort_order>3</sort_order>
119
+ <show_in_default>1</show_in_default>
120
+ <comment>Web service status. Go live only with 'stable' status on your production site</comment>
121
+ </status>
122
+ </fields>
123
+ </web_service>
124
+
125
+ <fields_product>
126
+ <label>Products fields mapping</label>
127
+ <frontend_type>text</frontend_type>
128
+ <sort_order>4</sort_order>
129
+ <show_in_default>1</show_in_default>
130
+ <show_in_website>1</show_in_website>
131
+ <show_in_store>1</show_in_store>
132
+ <comment>These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model</comment>
133
+ <fields>
134
+ <in_stock_only translate="label">
135
+ <label>Include out of stock products</label>
136
+ <frontend_type>select</frontend_type>
137
+ <source_model>adminhtml/system_config_source_yesno</source_model>
138
+ <sort_order>5</sort_order>
139
+ <show_in_default>1</show_in_default>
140
+ <show_in_website>1</show_in_website>
141
+ <show_in_store>1</show_in_store>
142
+ <comment>Change this setting if you want to include out of stock products</comment>
143
+ </in_stock_only>
144
+ <name translate="label">
145
+ <label>Name</label>
146
+ <frontend_type>select</frontend_type>
147
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
148
+ <sort_order>10</sort_order>
149
+ <show_in_default>1</show_in_default>
150
+ <show_in_website>1</show_in_website>
151
+ <show_in_store>1</show_in_store>
152
+ </name>
153
+ <short_name translate="label">
154
+ <label>Short name</label>
155
+ <frontend_type>select</frontend_type>
156
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
157
+ <sort_order>11</sort_order>
158
+ <show_in_default>1</show_in_default>
159
+ <show_in_website>1</show_in_website>
160
+ <show_in_store>1</show_in_store>
161
+ </short_name>
162
+ <manufacturer translate="label">
163
+ <label>Brand</label>
164
+ <frontend_type>select</frontend_type>
165
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
166
+ <sort_order>13</sort_order>
167
+ <show_in_default>1</show_in_default>
168
+ <show_in_website>1</show_in_website>
169
+ <show_in_store>1</show_in_store>
170
+ </manufacturer>
171
+ <description translate="label">
172
+ <label>Description</label>
173
+ <frontend_type>select</frontend_type>
174
+ <frontend_model>Antidot/System_Config_Form_Field_Array_ProductIdentifier</frontend_model>
175
+ <backend_model>Adminhtml/System_Config_Backend_Serialized_Array</backend_model>
176
+ <sort_order>20</sort_order>
177
+ <show_in_default>1</show_in_default>
178
+ <show_in_website>1</show_in_website>
179
+ <show_in_store>1</show_in_store>
180
+ <comment>Additional fields for plain text indexation</comment>
181
+ </description>
182
+ <keywords translate="label">
183
+ <label>Keywords</label>
184
+ <frontend_type>select</frontend_type>
185
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
186
+ <sort_order>30</sort_order>
187
+ <show_in_default>1</show_in_default>
188
+ <show_in_website>1</show_in_website>
189
+ <show_in_store>1</show_in_store>
190
+ </keywords>
191
+ <is_new translate="label">
192
+ <label>Is new</label>
193
+ <frontend_type>select</frontend_type>
194
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
195
+ <sort_order>50</sort_order>
196
+ <show_in_default>1</show_in_default>
197
+ <show_in_website>1</show_in_website>
198
+ <show_in_store>1</show_in_store>
199
+ <comment>This attribute may be used as searchandizing criterium to manage Product search/autocomplete order</comment>
200
+ </is_new>
201
+ <is_best_sale translate="label">
202
+ <label>Is top sale</label>
203
+ <frontend_type>select</frontend_type>
204
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
205
+ <sort_order>60</sort_order>
206
+ <show_in_default>1</show_in_default>
207
+ <show_in_website>1</show_in_website>
208
+ <show_in_store>1</show_in_store>
209
+ <comment>This attribute may be used as searchandizing criterium to manage Product search/autocomplete order</comment>
210
+ </is_best_sale>
211
+ <is_featured translate="label">
212
+ <label>Is featured</label>
213
+ <frontend_type>select</frontend_type>
214
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
215
+ <sort_order>70</sort_order>
216
+ <show_in_default>1</show_in_default>
217
+ <show_in_website>1</show_in_website>
218
+ <show_in_store>1</show_in_store>
219
+ <comment>This attribute may be used as searchandizing criterium to manage Product search/autocomplete order</comment>
220
+ </is_featured>
221
+ <materials translate="label">
222
+ <label>Material</label>
223
+ <frontend_type>select</frontend_type>
224
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
225
+ <sort_order>90</sort_order>
226
+ <show_in_default>1</show_in_default>
227
+ <show_in_website>1</show_in_website>
228
+ <show_in_store>1</show_in_store>
229
+ </materials>
230
+ <colors translate="label">
231
+ <label>Color</label>
232
+ <frontend_type>select</frontend_type>
233
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
234
+ <sort_order>100</sort_order>
235
+ <show_in_default>1</show_in_default>
236
+ <show_in_website>1</show_in_website>
237
+ <show_in_store>1</show_in_store>
238
+ </colors>
239
+ <models translate="label">
240
+ <label>Model</label>
241
+ <frontend_type>select</frontend_type>
242
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
243
+ <sort_order>110</sort_order>
244
+ <show_in_default>1</show_in_default>
245
+ <show_in_website>1</show_in_website>
246
+ <show_in_store>1</show_in_store>
247
+ </models>
248
+ <sizes translate="label">
249
+ <label>Size</label>
250
+ <frontend_type>select</frontend_type>
251
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
252
+ <sort_order>120</sort_order>
253
+ <show_in_default>1</show_in_default>
254
+ <show_in_website>1</show_in_website>
255
+ <show_in_store>1</show_in_store>
256
+ </sizes>
257
+ <gender translate="label">
258
+ <label>Gender</label>
259
+ <frontend_type>select</frontend_type>
260
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
261
+ <sort_order>125</sort_order>
262
+ <show_in_default>1</show_in_default>
263
+ <show_in_website>1</show_in_website>
264
+ <show_in_store>1</show_in_store>
265
+ </gender>
266
+ <gtin translate="label">
267
+ <label>GTIN / EAN</label>
268
+ <frontend_type>select</frontend_type>
269
+ <frontend_model>Antidot/System_Config_Form_Field_ProductAttribute</frontend_model>
270
+ <sort_order>125</sort_order>
271
+ <show_in_default>1</show_in_default>
272
+ <show_in_website>1</show_in_website>
273
+ <show_in_store>1</show_in_store>
274
+ </gtin>
275
+ <identifier translate="label">
276
+ <label>Identifier</label>
277
+ <frontend_model>Antidot/System_Config_Form_Field_Array_ProductIdentifier</frontend_model>
278
+ <backend_model>Adminhtml/System_Config_Backend_Serialized_Array</backend_model>
279
+ <sort_order>160</sort_order>
280
+ <show_in_default>1</show_in_default>
281
+ <show_in_website>1</show_in_website>
282
+ <show_in_store>1</show_in_store>
283
+ <comment>Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)</comment>
284
+ </identifier>
285
+ <properties translate="label">
286
+ <label>Additional facets</label>
287
+ <frontend_model>Antidot/System_Config_Form_Field_Array_ProductAdditionalFacet</frontend_model>
288
+ <backend_model>Adminhtml/System_Config_Backend_Serialized_Array</backend_model>
289
+ <sort_order>180</sort_order>
290
+ <show_in_default>1</show_in_default>
291
+ <show_in_website>1</show_in_website>
292
+ <show_in_store>1</show_in_store>
293
+ <comment>Additional attributes to filter results and optionally appear as autocomplete suggestions</comment>
294
+ </properties>
295
+ </fields>
296
+ </fields_product>
297
+
298
+ <!--<fields_category>
299
+ <label>Categories fields mapping</label>
300
+ <frontend_type>text</frontend_type>
301
+ <sort_order>5</sort_order>
302
+ <show_in_default>1</show_in_default>
303
+ <show_in_website>1</show_in_website>
304
+ <show_in_store>1</show_in_store>
305
+ <comment>These settings are related to the category file generation, used for AFS@Store indexing :</comment>
306
+ <fields>
307
+ <name translate="label">
308
+ <label>Name</label>
309
+ <frontend_type>select</frontend_type>
310
+ <frontend_model>Antidot/System_Config_Form_Field_CategoryAttribute</frontend_model>
311
+ <sort_order>1</sort_order>
312
+ <show_in_default>1</show_in_default>
313
+ <show_in_website>1</show_in_website>
314
+ <show_in_store>1</show_in_store>
315
+ <comment>Category attribute to use as category name</comment>
316
+ </name>
317
+ <description translate="label">
318
+ <label>Description</label>
319
+ <frontend_type>select</frontend_type>
320
+ <frontend_model>Antidot/System_Config_Form_Field_CategoryAttribute</frontend_model>
321
+ <sort_order>2</sort_order>
322
+ <show_in_default>1</show_in_default>
323
+ <show_in_website>1</show_in_website>
324
+ <show_in_store>1</show_in_store>
325
+ <comment>Category attribute to use as category description</comment>
326
+ </description>
327
+ <keywords translate="label">
328
+ <label>Keywords</label>
329
+ <frontend_type>select</frontend_type>
330
+ <frontend_model>Antidot/System_Config_Form_Field_CategoryAttribute</frontend_model>
331
+ <sort_order>3</sort_order>
332
+ <show_in_default>1</show_in_default>
333
+ <show_in_website>1</show_in_website>
334
+ <show_in_store>1</show_in_store>
335
+ <comment>Category attribute to use as category keywords</comment>
336
+ </keywords>
337
+ </fields>
338
+ </fields_category>-->
339
+
340
+ <suggest>
341
+ <label>Autocomplete</label>
342
+ <frontend_type>text</frontend_type>
343
+ <sort_order>6</sort_order>
344
+ <show_in_default>1</show_in_default>
345
+ <show_in_website>1</show_in_website>
346
+ <show_in_store>1</show_in_store>
347
+ <fields>
348
+ <enable translate="label">
349
+ <label>Enable</label>
350
+ <frontend_type>select</frontend_type>
351
+ <source_model>Antidot/System_Config_Engine</source_model>
352
+ <sort_order>1</sort_order>
353
+ <show_in_default>1</show_in_default>
354
+ <show_in_website>1</show_in_website>
355
+ <show_in_store>1</show_in_store>
356
+ <comment>Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues</comment>
357
+ </enable>
358
+ <products translate="label">
359
+ <label>Display products ?</label>
360
+ <frontend_type>select</frontend_type>
361
+ <source_model>adminhtml/system_config_source_enabledisable</source_model>
362
+ <sort_order>20</sort_order>
363
+ <show_in_default>1</show_in_default>
364
+ <show_in_website>1</show_in_website>
365
+ <show_in_store>1</show_in_store>
366
+ <comment>Display products in auto complete results</comment>
367
+ </products>
368
+ <products_displayed translate="label">
369
+ <label>Number of products displayed</label>
370
+ <frontend_type>select</frontend_type>
371
+ <source_model>Antidot/System_Config_Number</source_model>
372
+ <sort_order>21</sort_order>
373
+ <show_in_default>1</show_in_default>
374
+ <show_in_website>1</show_in_website>
375
+ <show_in_store>1</show_in_store>
376
+ <comment>Select the maximum number of products to display</comment>
377
+ </products_displayed>
378
+ <brands translate="label">
379
+ <label>Display brands</label>
380
+ <frontend_type>select</frontend_type>
381
+ <source_model>adminhtml/system_config_source_enabledisable</source_model>
382
+ <sort_order>30</sort_order>
383
+ <show_in_default>1</show_in_default>
384
+ <show_in_website>1</show_in_website>
385
+ <show_in_store>1</show_in_store>
386
+ <comment>Display brands in auto complete results</comment>
387
+ </brands>
388
+ <brands_displayed translate="label">
389
+ <label>Number of brands displayed</label>
390
+ <frontend_type>select</frontend_type>
391
+ <source_model>Antidot/System_Config_Number</source_model>
392
+ <sort_order>31</sort_order>
393
+ <show_in_default>1</show_in_default>
394
+ <show_in_website>1</show_in_website>
395
+ <show_in_store>1</show_in_store>
396
+ <comment>Select the maximum number of brands to display</comment>
397
+ </brands_displayed>
398
+ <categories translate="label">
399
+ <label>Categories</label>
400
+ <frontend_type>select</frontend_type>
401
+ <source_model>adminhtml/system_config_source_enabledisable</source_model>
402
+ <sort_order>40</sort_order>
403
+ <show_in_default>1</show_in_default>
404
+ <show_in_website>1</show_in_website>
405
+ <show_in_store>1</show_in_store>
406
+ <comment>Display categories in auto complete results</comment>
407
+ </categories>
408
+ <categories_displayed translate="label">
409
+ <label>Number of categories displayed</label>
410
+ <frontend_type>select</frontend_type>
411
+ <source_model>Antidot/System_Config_Number</source_model>
412
+ <sort_order>41</sort_order>
413
+ <show_in_default>1</show_in_default>
414
+ <show_in_website>1</show_in_website>
415
+ <show_in_store>1</show_in_store>
416
+ <comment>Select the maximum number of categories to display</comment>
417
+ </categories_displayed>
418
+ <template translate="label">
419
+ <label>Template</label>
420
+ <frontend_type>textarea</frontend_type>
421
+ <sort_order>51</sort_order>
422
+ <show_in_default>1</show_in_default>
423
+ <show_in_website>1</show_in_website>
424
+ <show_in_store>1</show_in_store>
425
+ <comment>Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore</comment>
426
+ </template>
427
+ <show_xml>
428
+ <label>Example</label>
429
+ <frontend_model>Antidot/System_Config_Html_ShowXml</frontend_model>
430
+ <sort_order>50</sort_order>
431
+ <show_in_default>1</show_in_default>
432
+ <show_in_website>0</show_in_website>
433
+ <show_in_store>0</show_in_store>
434
+ <comment>Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.</comment>
435
+ </show_xml>
436
+ <restore_template>
437
+ <label></label>
438
+ <frontend_model>Antidot/System_Config_Button_RestoreTemplate</frontend_model>
439
+ <sort_order>52</sort_order>
440
+ <show_in_default>1</show_in_default>
441
+ <show_in_website>0</show_in_website>
442
+ <show_in_store>0</show_in_store>
443
+ <comment>Restore the default XSLT template (usefull if you broke it !)</comment>
444
+ </restore_template>
445
+ <order_1 translate="label">
446
+ <label>Sort order</label>
447
+ <frontend_type>select</frontend_type>
448
+ <source_model>Antidot/System_Config_Suggest_Type</source_model>
449
+ <sort_order>42</sort_order>
450
+ <show_in_default>1</show_in_default>
451
+ <show_in_website>1</show_in_website>
452
+ <show_in_store>1</show_in_store>
453
+ <comment>First collection to display in suggest</comment>
454
+ </order_1>
455
+ <order_2 translate="label">
456
+ <frontend_type>select</frontend_type>
457
+ <source_model>Antidot/System_Config_Suggest_Type</source_model>
458
+ <sort_order>43</sort_order>
459
+ <show_in_default>1</show_in_default>
460
+ <show_in_website>1</show_in_website>
461
+ <show_in_store>1</show_in_store>
462
+ <comment>Second collection to display in suggest</comment>
463
+ </order_2>
464
+ <order_3 translate="label">
465
+ <frontend_type>select</frontend_type>
466
+ <source_model>Antidot/System_Config_Suggest_Type</source_model>
467
+ <sort_order>44</sort_order>
468
+ <show_in_default>1</show_in_default>
469
+ <show_in_website>1</show_in_website>
470
+ <show_in_store>1</show_in_store>
471
+ <comment>Third collection to display in suggest</comment>
472
+ </order_3>
473
+ </fields>
474
+ </suggest>
475
+
476
+ <engine>
477
+ <label>Search engine</label>
478
+ <frontend_type>text</frontend_type>
479
+ <sort_order>7</sort_order>
480
+ <show_in_default>1</show_in_default>
481
+ <show_in_website>1</show_in_website>
482
+ <show_in_store>1</show_in_store>
483
+ <fields>
484
+ <engine translate="label">
485
+ <label>Search Engine</label>
486
+ <frontend_type>select</frontend_type>
487
+ <source_model>Antidot/System_Config_Engine</source_model>
488
+ <backend_model>Antidot/System_Config_backend_engine</backend_model>
489
+ <sort_order>19</sort_order>
490
+ <show_in_default>1</show_in_default>
491
+ <show_in_website>1</show_in_website>
492
+ <show_in_store>1</show_in_store>
493
+ <comment>Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues</comment>
494
+ </engine>
495
+ <feed_categories translate="label">
496
+ <label>Display categories</label>
497
+ <frontend_type>select</frontend_type>
498
+ <source_model>adminhtml/system_config_source_enabledisable</source_model>
499
+ <sort_order>40</sort_order>
500
+ <show_in_default>1</show_in_default>
501
+ <show_in_website>1</show_in_website>
502
+ <show_in_store>1</show_in_store>
503
+ <comment>Display categories matching to search query (require template customization)</comment>
504
+ </feed_categories>
505
+ <spellcheck translate="label">
506
+ <label>Spellcheck sentence</label>
507
+ <frontend_type>text</frontend_type>
508
+ <sort_order>45</sort_order>
509
+ <show_in_default>1</show_in_default>
510
+ <show_in_website>1</show_in_website>
511
+ <show_in_store>1</show_in_store>
512
+ <comment>Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text</comment>
513
+ </spellcheck>
514
+ <facets translate="label">
515
+ <label>Facets</label>
516
+ <frontend_model>Antidot/System_Config_Form_Field_Array_Facet</frontend_model>
517
+ <backend_model>Adminhtml/System_Config_Backend_Serialized_Array</backend_model>
518
+ <sort_order>50</sort_order>
519
+ <show_in_default>1</show_in_default>
520
+ <show_in_website>1</show_in_website>
521
+ <show_in_store>1</show_in_store>
522
+ <comment>Select the facets (and the order) you want to enable on the search page</comment>
523
+ </facets>
524
+ <facet_options translate="label">
525
+ <label>Max number of facet values</label>
526
+ <frontend_type>text</frontend_type>
527
+ <sort_order>60</sort_order>
528
+ <show_in_default>1</show_in_default>
529
+ <show_in_website>1</show_in_website>
530
+ <show_in_store>1</show_in_store>
531
+ <comment>Max number of options to display for a facet (additional results will be hidden)</comment>
532
+ </facet_options>
533
+ <price_facet translate="label">
534
+ <label>Price facet label</label>
535
+ <frontend_type>text</frontend_type>
536
+ <sort_order>62</sort_order>
537
+ <show_in_default>1</show_in_default>
538
+ <show_in_website>1</show_in_website>
539
+ <show_in_store>1</show_in_store>
540
+ <comment>Text to display for each price ranges : use {min} and {max} codes to include values in your text</comment>
541
+ </price_facet>
542
+ <sortable translate="label">
543
+ <label>Sort options</label>
544
+ <frontend_model>Antidot/System_Config_Form_Field_Array_Sort</frontend_model>
545
+ <backend_model>Adminhtml/System_Config_Backend_Serialized_Array</backend_model>
546
+ <sort_order>180</sort_order>
547
+ <show_in_default>1</show_in_default>
548
+ <show_in_website>1</show_in_website>
549
+ <show_in_store>1</show_in_store>
550
+ <comment>Select sort options available in sort drop down menu</comment>
551
+ </sortable>
552
+ <default_sort translate="label">
553
+ <label>Default sort</label>
554
+ <frontend_model>Antidot/System_Config_Form_Field_Array_DefaultSort</frontend_model>
555
+ <backend_model>Adminhtml/System_Config_Backend_Serialized_Array</backend_model>
556
+ <sort_order>190</sort_order>
557
+ <show_in_default>1</show_in_default>
558
+ <show_in_website>1</show_in_website>
559
+ <show_in_store>1</show_in_store>
560
+ <comment>Default sort to apply</comment>
561
+ </default_sort>
562
+ </fields>
563
+ </engine>
564
+
565
+ <promote>
566
+ <label>Merchandising</label>
567
+ <frontend_type>text</frontend_type>
568
+ <sort_order>8</sort_order>
569
+ <show_in_default>1</show_in_default>
570
+ <show_in_website>1</show_in_website>
571
+ <show_in_store>1</show_in_store>
572
+ <fields>
573
+ <redirect translate="label">
574
+ <label>Promote redirect</label>
575
+ <frontend_type>select</frontend_type>
576
+ <source_model>Antidot/System_Config_PromoteRedirect</source_model>
577
+ <sort_order>19</sort_order>
578
+ <show_in_default>1</show_in_default>
579
+ <show_in_website>1</show_in_website>
580
+ <show_in_store>1</show_in_store>
581
+ <comment>Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls</comment>
582
+ </redirect>
583
+ </fields>
584
+ </promote>
585
+
586
+ <action>
587
+ <label>On-demand Data Upload</label>
588
+ <frontend_type>text</frontend_type>
589
+ <sort_order>10</sort_order>
590
+ <show_in_default>1</show_in_default>
591
+ <show_in_website>0</show_in_website>
592
+ <show_in_store>0</show_in_store>
593
+ <fields>
594
+ <!--<push_categories>
595
+ <label>Push categories</label>
596
+ <frontend_model>Antidot/System_Config_Button_PushCategories</frontend_model>
597
+ <sort_order>1</sort_order>
598
+ <show_in_default>1</show_in_default>
599
+ <show_in_website>0</show_in_website>
600
+ <show_in_store>0</show_in_store>
601
+ <comment>Manually push category index to AFS@Store (usefull when you change configuration above)</comment>
602
+ </push_categories>-->
603
+ <push_products>
604
+ <label>Push products</label>
605
+ <frontend_model>Antidot/System_Config_Button_PushProducts</frontend_model>
606
+ <sort_order>2</sort_order>
607
+ <show_in_default>1</show_in_default>
608
+ <show_in_website>0</show_in_website>
609
+ <show_in_store>0</show_in_store>
610
+ <comment>Manually push products index to AFS@Store (usefull when you change configuration above)</comment>
611
+ </push_products>
612
+ </fields>
613
+ </action>
614
+
615
+ <generation>
616
+ <label>Data upload report</label>
617
+ <frontend_type>text</frontend_type>
618
+ <sort_order>15</sort_order>
619
+ <show_in_default>1</show_in_default>
620
+ <show_in_website>0</show_in_website>
621
+ <show_in_store>0</show_in_store>
622
+ <comment>Last index push history logs</comment>
623
+ <fields>
624
+ <log>
625
+ <frontend_model>Antidot/System_Config_Html_Export</frontend_model>
626
+ <sort_order>1</sort_order>
627
+ <show_in_default>1</show_in_default>
628
+ <show_in_website>0</show_in_website>
629
+ <show_in_store>0</show_in_store>
630
+ </log>
631
+ </fields>
632
+ </generation>
633
+ </groups>
634
+ </antidot>
635
+ </sections>
636
+ </config>
app/code/community/MDN/Antidot/sql/Antidot_setup/mysql4-install-0.9.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $installer = $this;
4
+
5
+ $installer->startSetup();
6
+
7
+ /**
8
+ * Create table 'antidot/export'
9
+ */
10
+ $installer->run("
11
+ CREATE TABLE `antidot_export` (
12
+ `id` INT unsigned NOT NULL AUTO_INCREMENT,
13
+ `reference` VARCHAR(64) DEFAULT '',
14
+ `type` ENUM('FULL', 'INC') NOT NULL DEFAULT 'FULL',
15
+ `element` ENUM('CATALOG', 'CATEGORY') NOT NULL,
16
+ `begin_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
+ `end_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00',
18
+ `items_processed` INT NOT NULL DEFAULT 0,
19
+ `status` ENUM('SUCCESS', 'FAILED') NOT NULL,
20
+ `error` VARCHAR(255) DEFAULT '',
21
+ PRIMARY KEY (`id`)
22
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
23
+ ");
24
+
25
+ $installer->endSetup();
app/design/frontend/base/default/template/antidot/catalog/layer/category.phtml ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Academic Free License (AFL 3.0)
8
+ * that is bundled with this package in the file LICENSE_AFL.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/afl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category design
22
+ * @package base_default
23
+ * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
24
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
25
+ */
26
+ ?>
27
+ <?php
28
+ /**
29
+ * Template for filter items block
30
+ *
31
+ * @see Mage_Catalog_Block_Layer_Filter
32
+ */
33
+
34
+
35
+ if(!function_exists('getItems')) {
36
+ function getItems($items)
37
+ {
38
+ $listItems = array();
39
+ foreach($items as $item) {
40
+ $listItems[] = $item;
41
+ if($item->getChild()) {
42
+ $listItems = array_merge($listItems, getItems($item->getChild()));
43
+ }
44
+ }
45
+
46
+ return $listItems;
47
+ }
48
+
49
+ function writeItems($items, $isChild = false, $selectedNode = array())
50
+ {
51
+ $tree = '<ul '.(!$isChild ? 'class="collapsibleList"' : '').'>%s</ul>';
52
+ $node = '<li class="%s"><a %s href="%s"> %s (%d)%s</a>';
53
+
54
+ $nodes = '';
55
+ foreach ($items as $item) {
56
+ if ($item->getCount() > 0) {
57
+
58
+ $nodesValue = explode('|', str_replace('"', '', $item->getValue()));
59
+ $nodeId = array_pop($nodesValue);
60
+
61
+ $selected = $item->isSelected() || in_array($nodeId, $selectedNode) ? 'checked="checked"': '';
62
+ $class = $selected !== '' ? ' collapsibleListClosed' : '';
63
+ $child = $item->getChild() ? writeItems($item->getChild(), true, $selectedNode) : '';
64
+
65
+ $nodes.= sprintf($node, $class, $selected, $item->getUrl(), $item->getLabel(), $item->getCount(), $child);
66
+ }
67
+ }
68
+
69
+ return sprintf($tree, $nodes);
70
+ }
71
+ }
72
+
73
+ $selectedNode = array();
74
+ foreach(getItems($this->getItems()) as $item) {
75
+ if($item->isSelected()) {
76
+ $selectedNode = array_merge($selectedNode, explode('|', str_replace('"', '', $item->getValue())));
77
+ }
78
+ }
79
+
80
+ ?>
81
+
82
+ <script type="text/javascript" src="<?php echo Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_JS, true); ?>/mdn/antidot/CollapsibleLists.js"></script>
83
+ <link rel="stylesheet" type="text/css" href="<?php echo $this->getSkinUrl('css/mdn/antidot/CollapsibleLists.css'); ?>" />
84
+ <ul class="treeView"><?php echo writeItems($this->getItems(), false, $selectedNode); ?></ul>
85
+ <script type="text/javascript">
86
+ CollapsibleLists.apply();
87
+ </script>
app/design/frontend/base/default/template/antidot/catalog/layer/filter.phtml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Academic Free License (AFL 3.0)
8
+ * that is bundled with this package in the file LICENSE_AFL.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/afl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category design
22
+ * @package base_default
23
+ * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
24
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
25
+ */
26
+ ?>
27
+ <?php
28
+ /**
29
+ * Template for filter items block
30
+ *
31
+ * @see Mage_Catalog_Block_Layer_Filter
32
+ */
33
+ ?>
34
+ <ol>
35
+ <?php foreach ($this->getItems() as $_item): ?>
36
+ <li>
37
+ <?php if ($_item->getCount() > 0): ?>
38
+ <?php $selected = $_item->isSelected() ? 'checked="checked"': ''; ?>
39
+ <input type="checkbox" <?php echo $selected; ?> value="1" name="" id="" onclick="document.location.href='<?php echo $this->urlEscape($_item->getUrl()) ?>'"> <?php echo $_item->getLabel() ?> (<?php echo $_item->getCount() ?>)
40
+ <?php else: ?>
41
+ <?php echo $_item->getLabel() ?> (<?php echo $_item->getCount() ?>)
42
+ <?php endif; ?>
43
+ </li>
44
+ <?php endforeach ?>
45
+ </ol>
app/design/frontend/base/default/template/antidot/catalogsearch/result.phtml ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Magento
4
+ *
5
+ * NOTICE OF LICENSE
6
+ *
7
+ * This source file is subject to the Academic Free License (AFL 3.0)
8
+ * that is bundled with this package in the file LICENSE_AFL.txt.
9
+ * It is also available through the world-wide-web at this URL:
10
+ * http://opensource.org/licenses/afl-3.0.php
11
+ * If you did not receive a copy of the license and are unable to
12
+ * obtain it through the world-wide-web, please send an email
13
+ * to license@magentocommerce.com so we can send you a copy immediately.
14
+ *
15
+ * DISCLAIMER
16
+ *
17
+ * Do not edit or add to this file if you wish to upgrade Magento to newer
18
+ * versions in the future. If you wish to customize Magento for your
19
+ * needs please refer to http://www.magentocommerce.com for more information.
20
+ *
21
+ * @category design
22
+ * @package base_default
23
+ * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
24
+ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
25
+ */
26
+ ?>
27
+ <?php if($this->getResultCount()): ?>
28
+ <?php echo $this->getMessagesBlock()->getGroupedHtml() ?>
29
+ <div class="page-title">
30
+ <?php if ($this->helper('rss/catalog')->getTagFeedUrl()): ?>
31
+ <a href="<?php echo $this->helper('rss/catalog')->getTagFeedUrl() ?>" class="nobr link-rss"><?php echo $this->__('Subscribe to Feed') ?></a>
32
+ <?php endif; ?>
33
+ <h1><?php echo ($this->getHeaderText() || $this->getHeaderText() === false) ? $this->getHeaderText() : $this->__("Search results for '%s'", $this->helper('catalogsearch')->getEscapedQueryText()) ?></h1>
34
+ </div>
35
+ <?php if ($messages = $this->getNoteMessages()):?>
36
+ <p class="note-msg">
37
+ <?php foreach ($messages as $message):?>
38
+ <?php echo $message?><br />
39
+ <?php endforeach;?>
40
+ </p>
41
+ <?php endif; ?>
42
+ <?php $productHtml = $this->getProductListHtml(); ?>
43
+ <?php if($this->helper('Antidot')->isActiveEngine()): ?>
44
+ <?php echo $this->getChildHtml('search_result_category'); ?>
45
+ <?php endif; ?>
46
+ <?php echo $productHtml; ?>
47
+ <?php else: ?>
48
+ <div class="page-title">
49
+ <h1><?php echo ($this->getHeaderText() || $this->getHeaderText() === false) ? $this->getHeaderText() : $this->__("Search results for '%s'", $this->helper('catalogsearch')->getEscapedQueryText()) ?></h1>
50
+ </div>
51
+ <p class="note-msg">
52
+ <?php echo ($this->getNoResultText()) ? $this->getNoResultText() : $this->__('Your search returns no results.') ?>
53
+ <?php if ($messages = $this->getNoteMessages()):?>
54
+ <?php foreach ($messages as $message):?>
55
+ <br /><?php echo $message?>
56
+ <?php endforeach;?>
57
+ <?php endif; ?>
58
+ </p>
59
+ <?php endif; ?>
app/design/frontend/default/default/layout/antidot.xml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+
3
+ <layout version="0.1.0">
4
+
5
+ <catalogsearch_result_index>
6
+ <reference name="search.result">
7
+ <block type="Antidot/Catalogsearch_Category" name="search_result_category" as="search_result_category" template="antidot/catalogsearch/result/category.phtml" />
8
+ </reference>
9
+ </catalogsearch_result_index>
10
+
11
+ </layout>
app/design/frontend/default/default/template/antidot/catalogsearch/result/category.phtml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php if ($this->getCategories()->getSize() > 0): ?>
2
+
3
+ <h2><?php echo $this->__('Categories matching to your search :'); ?></h2>
4
+
5
+ <ul>
6
+ <?php foreach($this->getCategories() as $cat): ?>
7
+ <li><a href="<?php echo $this->getCategoryUrl($cat); ?>"><?php echo $cat->getName(); ?></a></li>
8
+ <?php endforeach; ?>
9
+ </ul>
10
+ <?php endif; ?>
app/etc/modules/MDN_Antidot.xml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <config>
3
+ <modules>
4
+ <MDN_Antidot>
5
+ <active>true</active>
6
+ <codePool>community</codePool>
7
+ </MDN_Antidot>
8
+ </modules>
9
+ </config>
app/locale/de_AT/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Senden
2
+ "Restore the default template","Standardschablone wiederherstellen"
3
+ "Display XML","XML zeigen"
4
+ Attribute,Attribut
5
+ "Add a field","Ein Feld hinzufügen"
6
+ Sortable,Sortierbar
7
+ Direction,Richtung
8
+ Facet,Facette
9
+ Sort,Sortierung
10
+ "Multiple selections",Mehrfachauswahl
11
+ "Auto Complete",Autovervollständigung
12
+ Date,Datum
13
+ Reference,Referenz
14
+ Type,Typ
15
+ Element,Element
16
+ Products,Produkte
17
+ Status,Status
18
+ Store,Laden
19
+ Term,Term
20
+ "Did you mean {spellcheck} ?","Meinten Sie {spellcheck} ?"
21
+ "From {min}€ to {max}€","Ab {min}�?� bis {max}�?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back office (Analytics, Synonyme, Promote)"
23
+ "AFS@Store Extension Version","AFS@Store Erweiterungsversion"
24
+ "Communicate this version to the support team if you need help","Bei Bedarf teilen Sie diese Version dem Support-Team mit"
25
+ "Organization name",Unternehmensname
26
+ "Useful for support team","Nützlich für das Support-Team"
27
+ E-mail,E-mail
28
+ "Used to send alert when an error occured","Im Fehlerfall verwendet, um eine Benachrichtigung zu senden"
29
+ "Data upload parameters","Einstellungen für Datenpload"
30
+ Host,Server
31
+ "Host to connect to Antidot to push files","Antidots Server mit dem Sie verbinden um die Daten zu übertragen"
32
+ "Upload Directory","Upload Verzeichnis"
33
+ "Directory to upload files","Verzeichnis für Datenupload"
34
+ Login,Login
35
+ "Your Antidot FTP Login","Ihr Antidot FTP Login"
36
+ Password,Passwort
37
+ "Your Antidot FTP Password","Ihr Antidot FTP Passwort"
38
+ "Web services","Web Services"
39
+ "Web service host to communicate with Antidot","Adresse der Web Services AFS@Store"
40
+ "Service ID","Service ID"
41
+ "Antidot service ID","Antidot service ID"
42
+ Status,Status
43
+ "Web service status. Go live only with 'stable' status on your production site","Web Service status. Inbetriebnahme auf Ihrer Webseite nur mit 'stable' status"
44
+ "Products fields mapping","Mapping der Produktfelder"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Diese Einstellungen sind im Zusammenhang mit der Erzeugung des Produktkatalogs durch AFS@Store Indexierung. Sie können vorhanden Magento Attribute benutzen und Ihre eigene Eigenschaften hinzufügen. Standard Magento Attribute wie Urls, Kategorien, Preise, Mengen, Förderungen sind automatisch in diesem Mapping ererbt. Benutzen Sie dieser Formulär um die spezifische Felder von Ihrem Datenmodell hinzufügen."
46
+ "Include out of stock products","Ausverkaufte Produkte umfassen"
47
+ "Change this setting if you want to include out of stock products","Veränderen Sie diese Einstellung wenn Sie ausverkäufte Produkte umfassen wollen"
48
+ Name,Name
49
+ "Short name",Kurzname
50
+ Brand,Marke
51
+ Description,Beschreibung
52
+ Keywords,Stichworte
53
+ "Is new",Neuigkeit
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Dieses Attribut kann als searchandising Kriterium zur Verwaltung der Produkt Suche/Autovervollständigung benutzt werden."
55
+ "Is top sale",Spitzenverkauf
56
+ "Is featured",Hervorgehoben
57
+ Material,Werkstoff
58
+ Color,Farbe
59
+ Model,Modell
60
+ Size,Grö�?e
61
+ Gender,Geschlecht
62
+ "GTIN / EAN","GTIN / EAN"
63
+ Identifier,"Andere Produktkennzeichnungen"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Konfigurieren Sie hier zusätzliche Produktkennzeichnungen (Ihre Referenz, Herstellersreferenz, Lieferantsreferenz, OEM ...)"
65
+ "Additional facets","Zusätzliche Facetten"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Zusätzliche Attribute, um die Ergebnisse zu filtern und als Autovervollständigung Vorschläge zu erscheinen"
67
+ Autocomplete,Autovervollständigung
68
+ Enable,Ermöglichen
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Wählen Sie Magento oder AFS@Store (empfohlen) Autovervollständigung. Magento wiederherstellen nur wenn Sie Probleme auftretten."
70
+ "Display products ?","Produkte zeigen ?"
71
+ "Display products in auto complete results","Produkte in Autovervollständigung Ergebnisse zeigen"
72
+ "Number of products displayed","Anzahl der angezeigten Produkten"
73
+ "Select the maximum number of products to display","Wählen Sie die maximale Anzahl der angezeigten Produkten"
74
+ "Display brands","Marke zeigen"
75
+ "Display brands in auto complete results","Marke in Autovervollständigungsergebnisse zeigen"
76
+ "Number of brands displayed","Anzahl der angezeigten Marken"
77
+ "Select the maximum number of brands to display","Wählen Sie die maximale Anzahl der angezeigten Marken"
78
+ Categories,Kategorien
79
+ "Display categories in auto complete results","Kategorien in Autovervollständigungsergebnisse zeigen"
80
+ "Number of categories displayed","Anzahl der angezeigten Kategorien"
81
+ "Select the maximum number of categories to display","Wählen Sie die maximale Anzahl der angezeigten Kategorien"
82
+ Template,Schablone
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Hier können Sie eine XSLT Schablone für ACP Ergebnisse Anzeige erstellen. Achtung: wenn Ihre Schablone seine eigene Sortierreihenfolge einstellt, gilt die obige Einstellung 'Sortierreihenfolge' nie mehr."
84
+ Example,Beispiel
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Zeigt ein Pop-up mit einer Stichprobe vom XML Code der mit der XSLT Schablone transformiert wird."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Standard XSLT Schablone wiederherstellen (nützlich wenn die Schablone nie mehr funktioniert!)"
87
+ "Sort order",Sortierreihenfolge
88
+ "First collection to display in suggest","Erste angezeigte Spalte in Autovervollständigung"
89
+ "Second collection to display in suggest","Zweite angezeigte Spalte in Autovervollständigung"
90
+ "Third collection to display in suggest","Dritte angezeigte Spalte in Autovervollständigung"
91
+ "Search engine",Suchmachine
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Wählen Sie Magento oder AFS@Store (empfohlen) Suchmachine. Magento wiederherstellen nur wenn Sie Probleme auftretten."
93
+ "Display categories","Kategorien zeigen"
94
+ "Display categories matching to search query (require template customization)","Suchkriterien entsprechende Kategorien anzeigen (benötige Schablonen Anpassung)"
95
+ "Spellcheck sentence","Rechtschreibprüfung Ausdruck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Angezeigter Text wenn ein Rechtschreibprüfung Vorschlag verfügbar ist. Benutzen Sie {spellcheck} Stichwort um AFS@Store Vorschlag in Ihrem Text umzufassen."
97
+ Facets,Facetten
98
+ "Select the facets (and the order) you want to enable on the search page","Wählen Sie die Facetten (und ihre Reihenfolge) die Sie auf Ihrer Webseite ermöglichen wollen."
99
+ "Max number of facet values","Maximale Anzahl der Werten der Facette"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Maximale Anzahl von der angezeigten Optionen für eine Facette (zusätzliche Ergebnisse werden versteckt)"
101
+ "Price facet label","Etikett von Preisfacette"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Angezeigter Text für jede Preissklasse: benutzen Sie {min} und {max} Codes um die Werte in Ihrem Text umzufassen"
103
+ "Sort options","Sortierung Optionen"
104
+ "Select sort options available in sort drop down menu","Wählen Sie Sortierung Optionen verfügbar in der Dropdown-Liste"
105
+ "Default sort","Standard Sortierung"
106
+ "Default sort to apply","Standard Sortierung zu verwenden"
107
+ Merchandising,Merchandising
108
+ "Promote redirect","Promote Umleitung"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Hiermit können Sie wählen wie die im Antidot Back-Office erstellten Promote Kampagnen verwandet sind, um den Besucher zu den Zielseiten Ihrer Website umzuleiten."
110
+ "On-demand Data Upload","Datenupload auf Anfrage"
111
+ "Push products","Katalog senden"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Den Katalog manuell für die Indexierung AFS@Store senden (nützlich bei Konfigurationsänderungen)"
113
+ "Data upload report","Datenupload Bericht"
114
+ "Last index push history logs","Journal der letzten Datenübertragungen"
115
+ Price,Preis
116
+ categories,kategorien
117
+ products,produkte
118
+ brands,marke
119
+ Ascending,Aufsteigend
120
+ Descending,Absteigend
121
+ Disable,Deaktivieren
122
+ Enable,Ermöglichen
123
+ All,Alle
124
+ "Only when no result","Nur wenn keine Ergebnisse"
125
+ Always,Immer
126
+ Relevance,Relevanz
127
+ Position,Position
128
+ Name,Name
129
+ Price,Preis
130
+ "Is promotional",Förgerung
131
+ "Is new",Neuigkeiten
132
+ "Is top sale",Spitzenverkaufs
133
+ "Is featured",Hervorgehoben
134
+ "Additional fields for plain text indexation","Zusätzliche Felder für Volltextindexierung"
app/locale/de_CH/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Senden
2
+ "Restore the default template","Standardschablone wiederherstellen"
3
+ "Display XML","XML zeigen"
4
+ Attribute,Attribut
5
+ "Add a field","Ein Feld hinzufügen"
6
+ Sortable,Sortierbar
7
+ Direction,Richtung
8
+ Facet,Facette
9
+ Sort,Sortierung
10
+ "Multiple selections",Mehrfachauswahl
11
+ "Auto Complete",Autovervollständigung
12
+ Date,Datum
13
+ Reference,Referenz
14
+ Type,Typ
15
+ Element,Element
16
+ Products,Produkte
17
+ Status,Status
18
+ Store,Laden
19
+ Term,Term
20
+ "Did you mean {spellcheck} ?","Meinten Sie {spellcheck} ?"
21
+ "From {min}€ to {max}€","Ab {min}�?� bis {max}�?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back office (Analytics, Synonyme, Promote)"
23
+ "AFS@Store Extension Version","AFS@Store Erweiterungsversion"
24
+ "Communicate this version to the support team if you need help","Bei Bedarf teilen Sie diese Version dem Support-Team mit"
25
+ "Organization name",Unternehmensname
26
+ "Useful for support team","Nützlich für das Support-Team"
27
+ E-mail,E-mail
28
+ "Used to send alert when an error occured","Im Fehlerfall verwendet, um eine Benachrichtigung zu senden"
29
+ "Data upload parameters","Einstellungen für Datenpload"
30
+ Host,Server
31
+ "Host to connect to Antidot to push files","Antidots Server mit dem Sie verbinden um die Daten zu übertragen"
32
+ "Upload Directory","Upload Verzeichnis"
33
+ "Directory to upload files","Verzeichnis für Datenupload"
34
+ Login,Login
35
+ "Your Antidot FTP Login","Ihr Antidot FTP Login"
36
+ Password,Passwort
37
+ "Your Antidot FTP Password","Ihr Antidot FTP Passwort"
38
+ "Web services","Web Services"
39
+ "Web service host to communicate with Antidot","Adresse der Web Services AFS@Store"
40
+ "Service ID","Service ID"
41
+ "Antidot service ID","Antidot service ID"
42
+ Status,Status
43
+ "Web service status. Go live only with 'stable' status on your production site","Web Service status. Inbetriebnahme auf Ihrer Webseite nur mit 'stable' status"
44
+ "Products fields mapping","Mapping der Produktfelder"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Diese Einstellungen sind im Zusammenhang mit der Erzeugung des Produktkatalogs durch AFS@Store Indexierung. Sie können vorhanden Magento Attribute benutzen und Ihre eigene Eigenschaften hinzufügen. Standard Magento Attribute wie Urls, Kategorien, Preise, Mengen, Förderungen sind automatisch in diesem Mapping ererbt. Benutzen Sie dieser Formulär um die spezifische Felder von Ihrem Datenmodell hinzufügen."
46
+ "Include out of stock products","Ausverkaufte Produkte umfassen"
47
+ "Change this setting if you want to include out of stock products","Veränderen Sie diese Einstellung wenn Sie ausverkäufte Produkte umfassen wollen"
48
+ Name,Name
49
+ "Short name",Kurzname
50
+ Brand,Marke
51
+ Description,Beschreibung
52
+ Keywords,Stichworte
53
+ "Is new",Neuigkeit
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Dieses Attribut kann als searchandising Kriterium zur Verwaltung der Produkt Suche/Autovervollständigung benutzt werden."
55
+ "Is top sale",Spitzenverkauf
56
+ "Is featured",Hervorgehoben
57
+ Material,Werkstoff
58
+ Color,Farbe
59
+ Model,Modell
60
+ Size,Grö�?e
61
+ Gender,Geschlecht
62
+ "GTIN / EAN","GTIN / EAN"
63
+ Identifier,"Andere Produktkennzeichnungen"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Konfigurieren Sie hier zusätzliche Produktkennzeichnungen (Ihre Referenz, Herstellersreferenz, Lieferantsreferenz, OEM ...)"
65
+ "Additional facets","Zusätzliche Facetten"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Zusätzliche Attribute, um die Ergebnisse zu filtern und als Autovervollständigung Vorschläge zu erscheinen"
67
+ Autocomplete,Autovervollständigung
68
+ Enable,Ermöglichen
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Wählen Sie Magento oder AFS@Store (empfohlen) Autovervollständigung. Magento wiederherstellen nur wenn Sie Probleme auftretten."
70
+ "Display products ?","Produkte zeigen ?"
71
+ "Display products in auto complete results","Produkte in Autovervollständigung Ergebnisse zeigen"
72
+ "Number of products displayed","Anzahl der angezeigten Produkten"
73
+ "Select the maximum number of products to display","Wählen Sie die maximale Anzahl der angezeigten Produkten"
74
+ "Display brands","Marke zeigen"
75
+ "Display brands in auto complete results","Marke in Autovervollständigungsergebnisse zeigen"
76
+ "Number of brands displayed","Anzahl der angezeigten Marken"
77
+ "Select the maximum number of brands to display","Wählen Sie die maximale Anzahl der angezeigten Marken"
78
+ Categories,Kategorien
79
+ "Display categories in auto complete results","Kategorien in Autovervollständigungsergebnisse zeigen"
80
+ "Number of categories displayed","Anzahl der angezeigten Kategorien"
81
+ "Select the maximum number of categories to display","Wählen Sie die maximale Anzahl der angezeigten Kategorien"
82
+ Template,Schablone
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Hier können Sie eine XSLT Schablone für ACP Ergebnisse Anzeige erstellen. Achtung: wenn Ihre Schablone seine eigene Sortierreihenfolge einstellt, gilt die obige Einstellung 'Sortierreihenfolge' nie mehr."
84
+ Example,Beispiel
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Zeigt ein Pop-up mit einer Stichprobe vom XML Code der mit der XSLT Schablone transformiert wird."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Standard XSLT Schablone wiederherstellen (nützlich wenn die Schablone nie mehr funktioniert!)"
87
+ "Sort order",Sortierreihenfolge
88
+ "First collection to display in suggest","Erste angezeigte Spalte in Autovervollständigung"
89
+ "Second collection to display in suggest","Zweite angezeigte Spalte in Autovervollständigung"
90
+ "Third collection to display in suggest","Dritte angezeigte Spalte in Autovervollständigung"
91
+ "Search engine",Suchmachine
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Wählen Sie Magento oder AFS@Store (empfohlen) Suchmachine. Magento wiederherstellen nur wenn Sie Probleme auftretten."
93
+ "Display categories","Kategorien zeigen"
94
+ "Display categories matching to search query (require template customization)","Suchkriterien entsprechende Kategorien anzeigen (benötige Schablonen Anpassung)"
95
+ "Spellcheck sentence","Rechtschreibprüfung Ausdruck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Angezeigter Text wenn ein Rechtschreibprüfung Vorschlag verfügbar ist. Benutzen Sie {spellcheck} Stichwort um AFS@Store Vorschlag in Ihrem Text umzufassen."
97
+ Facets,Facetten
98
+ "Select the facets (and the order) you want to enable on the search page","Wählen Sie die Facetten (und ihre Reihenfolge) die Sie auf Ihrer Webseite ermöglichen wollen."
99
+ "Max number of facet values","Maximale Anzahl der Werten der Facette"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Maximale Anzahl von der angezeigten Optionen für eine Facette (zusätzliche Ergebnisse werden versteckt)"
101
+ "Price facet label","Etikett von Preisfacette"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Angezeigter Text für jede Preissklasse: benutzen Sie {min} und {max} Codes um die Werte in Ihrem Text umzufassen"
103
+ "Sort options","Sortierung Optionen"
104
+ "Select sort options available in sort drop down menu","Wählen Sie Sortierung Optionen verfügbar in der Dropdown-Liste"
105
+ "Default sort","Standard Sortierung"
106
+ "Default sort to apply","Standard Sortierung zu verwenden"
107
+ Merchandising,Merchandising
108
+ "Promote redirect","Promote Umleitung"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Hiermit können Sie wählen wie die im Antidot Back-Office erstellten Promote Kampagnen verwandet sind, um den Besucher zu den Zielseiten Ihrer Website umzuleiten."
110
+ "On-demand Data Upload","Datenupload auf Anfrage"
111
+ "Push products","Katalog senden"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Den Katalog manuell für die Indexierung AFS@Store senden (nützlich bei Konfigurationsänderungen)"
113
+ "Data upload report","Datenupload Bericht"
114
+ "Last index push history logs","Journal der letzten Datenübertragungen"
115
+ Price,Preis
116
+ categories,kategorien
117
+ products,produkte
118
+ brands,marke
119
+ Ascending,Aufsteigend
120
+ Descending,Absteigend
121
+ Disable,Deaktivieren
122
+ Enable,Ermöglichen
123
+ All,Alle
124
+ "Only when no result","Nur wenn keine Ergebnisse"
125
+ Always,Immer
126
+ Relevance,Relevanz
127
+ Position,Position
128
+ Name,Name
129
+ Price,Preis
130
+ "Is promotional",Förgerung
131
+ "Is new",Neuigkeiten
132
+ "Is top sale",Spitzenverkaufs
133
+ "Is featured",Hervorgehoben
134
+ "Additional fields for plain text indexation","Zusätzliche Felder für Volltextindexierung"
app/locale/de_DE/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Senden
2
+ "Restore the default template","Standardschablone wiederherstellen"
3
+ "Display XML","XML zeigen"
4
+ Attribute,Attribut
5
+ "Add a field","Ein Feld hinzufügen"
6
+ Sortable,Sortierbar
7
+ Direction,Richtung
8
+ Facet,Facette
9
+ Sort,Sortierung
10
+ "Multiple selections",Mehrfachauswahl
11
+ "Auto Complete",Autovervollständigung
12
+ Date,Datum
13
+ Reference,Referenz
14
+ Type,Typ
15
+ Element,Element
16
+ Products,Produkte
17
+ Status,Status
18
+ Store,Laden
19
+ Term,Term
20
+ "Did you mean {spellcheck} ?","Meinten Sie {spellcheck} ?"
21
+ "From {min}€ to {max}€","Ab {min}�?� bis {max}�?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back office (Analytics, Synonyme, Promote)"
23
+ "AFS@Store Extension Version","AFS@Store Erweiterungsversion"
24
+ "Communicate this version to the support team if you need help","Bei Bedarf teilen Sie diese Version dem Support-Team mit"
25
+ "Organization name",Unternehmensname
26
+ "Useful for support team","Nützlich für das Support-Team"
27
+ E-mail,E-mail
28
+ "Used to send alert when an error occured","Im Fehlerfall verwendet, um eine Benachrichtigung zu senden"
29
+ "Data upload parameters","Einstellungen für Datenpload"
30
+ Host,Server
31
+ "Host to connect to Antidot to push files","Antidots Server mit dem Sie verbinden um die Daten zu übertragen"
32
+ "Upload Directory","Upload Verzeichnis"
33
+ "Directory to upload files","Verzeichnis für Datenupload"
34
+ Login,Login
35
+ "Your Antidot FTP Login","Ihr Antidot FTP Login"
36
+ Password,Passwort
37
+ "Your Antidot FTP Password","Ihr Antidot FTP Passwort"
38
+ "Web services","Web Services"
39
+ "Web service host to communicate with Antidot","Adresse der Web Services AFS@Store"
40
+ "Service ID","Service ID"
41
+ "Antidot service ID","Antidot service ID"
42
+ Status,Status
43
+ "Web service status. Go live only with 'stable' status on your production site","Web Service status. Inbetriebnahme auf Ihrer Webseite nur mit 'stable' status"
44
+ "Products fields mapping","Mapping der Produktfelder"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Diese Einstellungen sind im Zusammenhang mit der Erzeugung des Produktkatalogs durch AFS@Store Indexierung. Sie können vorhanden Magento Attribute benutzen und Ihre eigene Eigenschaften hinzufügen. Standard Magento Attribute wie Urls, Kategorien, Preise, Mengen, Förderungen sind automatisch in diesem Mapping ererbt. Benutzen Sie dieser Formulär um die spezifische Felder von Ihrem Datenmodell hinzufügen."
46
+ "Include out of stock products","Ausverkaufte Produkte umfassen"
47
+ "Change this setting if you want to include out of stock products","Veränderen Sie diese Einstellung wenn Sie ausverkäufte Produkte umfassen wollen"
48
+ Name,Name
49
+ "Short name",Kurzname
50
+ Brand,Marke
51
+ Description,Beschreibung
52
+ Keywords,Stichworte
53
+ "Is new",Neuigkeit
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Dieses Attribut kann als searchandising Kriterium zur Verwaltung der Produkt Suche/Autovervollständigung benutzt werden."
55
+ "Is top sale",Spitzenverkauf
56
+ "Is featured",Hervorgehoben
57
+ Material,Werkstoff
58
+ Color,Farbe
59
+ Model,Modell
60
+ Size,Grö�?e
61
+ Gender,Geschlecht
62
+ "GTIN / EAN","GTIN / EAN"
63
+ Identifier,"Andere Produktkennzeichnungen"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Konfigurieren Sie hier zusätzliche Produktkennzeichnungen (Ihre Referenz, Herstellersreferenz, Lieferantsreferenz, OEM ...)"
65
+ "Additional facets","Zusätzliche Facetten"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Zusätzliche Attribute, um die Ergebnisse zu filtern und als Autovervollständigung Vorschläge zu erscheinen"
67
+ Autocomplete,Autovervollständigung
68
+ Enable,Ermöglichen
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Wählen Sie Magento oder AFS@Store (empfohlen) Autovervollständigung. Magento wiederherstellen nur wenn Sie Probleme auftretten."
70
+ "Display products ?","Produkte zeigen ?"
71
+ "Display products in auto complete results","Produkte in Autovervollständigung Ergebnisse zeigen"
72
+ "Number of products displayed","Anzahl der angezeigten Produkten"
73
+ "Select the maximum number of products to display","Wählen Sie die maximale Anzahl der angezeigten Produkten"
74
+ "Display brands","Marke zeigen"
75
+ "Display brands in auto complete results","Marke in Autovervollständigungsergebnisse zeigen"
76
+ "Number of brands displayed","Anzahl der angezeigten Marken"
77
+ "Select the maximum number of brands to display","Wählen Sie die maximale Anzahl der angezeigten Marken"
78
+ Categories,Kategorien
79
+ "Display categories in auto complete results","Kategorien in Autovervollständigungsergebnisse zeigen"
80
+ "Number of categories displayed","Anzahl der angezeigten Kategorien"
81
+ "Select the maximum number of categories to display","Wählen Sie die maximale Anzahl der angezeigten Kategorien"
82
+ Template,Schablone
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Hier können Sie eine XSLT Schablone für ACP Ergebnisse Anzeige erstellen. Achtung: wenn Ihre Schablone seine eigene Sortierreihenfolge einstellt, gilt die obige Einstellung 'Sortierreihenfolge' nie mehr."
84
+ Example,Beispiel
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Zeigt ein Pop-up mit einer Stichprobe vom XML Code der mit der XSLT Schablone transformiert wird."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Standard XSLT Schablone wiederherstellen (nützlich wenn die Schablone nie mehr funktioniert!)"
87
+ "Sort order",Sortierreihenfolge
88
+ "First collection to display in suggest","Erste angezeigte Spalte in Autovervollständigung"
89
+ "Second collection to display in suggest","Zweite angezeigte Spalte in Autovervollständigung"
90
+ "Third collection to display in suggest","Dritte angezeigte Spalte in Autovervollständigung"
91
+ "Search engine",Suchmachine
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Wählen Sie Magento oder AFS@Store (empfohlen) Suchmachine. Magento wiederherstellen nur wenn Sie Probleme auftretten."
93
+ "Display categories","Kategorien zeigen"
94
+ "Display categories matching to search query (require template customization)","Suchkriterien entsprechende Kategorien anzeigen (benötige Schablonen Anpassung)"
95
+ "Spellcheck sentence","Rechtschreibprüfung Ausdruck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Angezeigter Text wenn ein Rechtschreibprüfung Vorschlag verfügbar ist. Benutzen Sie {spellcheck} Stichwort um AFS@Store Vorschlag in Ihrem Text umzufassen."
97
+ Facets,Facetten
98
+ "Select the facets (and the order) you want to enable on the search page","Wählen Sie die Facetten (und ihre Reihenfolge) die Sie auf Ihrer Webseite ermöglichen wollen."
99
+ "Max number of facet values","Maximale Anzahl der Werten der Facette"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Maximale Anzahl von der angezeigten Optionen für eine Facette (zusätzliche Ergebnisse werden versteckt)"
101
+ "Price facet label","Etikett von Preisfacette"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Angezeigter Text für jede Preissklasse: benutzen Sie {min} und {max} Codes um die Werte in Ihrem Text umzufassen"
103
+ "Sort options","Sortierung Optionen"
104
+ "Select sort options available in sort drop down menu","Wählen Sie Sortierung Optionen verfügbar in der Dropdown-Liste"
105
+ "Default sort","Standard Sortierung"
106
+ "Default sort to apply","Standard Sortierung zu verwenden"
107
+ Merchandising,Merchandising
108
+ "Promote redirect","Promote Umleitung"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Hiermit können Sie wählen wie die im Antidot Back-Office erstellten Promote Kampagnen verwandet sind, um den Besucher zu den Zielseiten Ihrer Website umzuleiten."
110
+ "On-demand Data Upload","Datenupload auf Anfrage"
111
+ "Push products","Katalog senden"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Den Katalog manuell für die Indexierung AFS@Store senden (nützlich bei Konfigurationsänderungen)"
113
+ "Data upload report","Datenupload Bericht"
114
+ "Last index push history logs","Journal der letzten Datenübertragungen"
115
+ Price,Preis
116
+ categories,kategorien
117
+ products,produkte
118
+ brands,marke
119
+ Ascending,Aufsteigend
120
+ Descending,Absteigend
121
+ Disable,Deaktivieren
122
+ Enable,Ermöglichen
123
+ All,Alle
124
+ "Only when no result","Nur wenn keine Ergebnisse"
125
+ Always,Immer
126
+ Relevance,Relevanz
127
+ Position,Position
128
+ Name,Name
129
+ Price,Preis
130
+ "Is promotional",Förgerung
131
+ "Is new",Neuigkeiten
132
+ "Is top sale",Spitzenverkaufs
133
+ "Is featured",Hervorgehoben
134
+ "Additional fields for plain text indexation","Zusätzliche Felder für Volltextindexierung"
app/locale/es_AR/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_CL/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_CO/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_CR/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_ES/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_MX/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_PA/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_PE/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/es_VE/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Enviar
2
+ "Restore the default template","Restaurar la plantilla predeterminada"
3
+ "Display XML","Fijar en XML"
4
+ Attribute,Atributo
5
+ "Add a field","Agregar un campo"
6
+ Sortable,Ordenar
7
+ Direction,Sentido
8
+ Facet,Faceta
9
+ Sort,Ordenar
10
+ "Multiple selections","Selecciones múltiples"
11
+ "Auto Complete","Auto compleción"
12
+ Date,Fecha
13
+ Reference,Referencia
14
+ Type,Tipo
15
+ Element,Elemento
16
+ Products,Productos
17
+ Status,Estado
18
+ Store,Tienda
19
+ Term,"Palabra clave"
20
+ "Did you mean {spellcheck} ?","Quiere decir { spellcheck } ?"
21
+ "From {min}€ to {max}€","Desde { min } �?� a { max } �?�"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Antidot Back Office ( Analytics, sinónimos, promover )"
23
+ "AFS@Store Extension Version","Versión de la extensión AFS@Store"
24
+ "Communicate this version to the support team if you need help","Si es necesario, comunique esta versión al equipo del soporte"
25
+ "Organization name","Nombre de la organización"
26
+ "Useful for support team","�?til para el equipo del soporte"
27
+ E-mail,"Correo electrónico"
28
+ "Used to send alert when an error occured","Utilizado para enviar una alerta cuando ocurre un error"
29
+ "Data upload parameters","Parámetros de carga de datos"
30
+ Host,Servidor
31
+ "Host to connect to Antidot to push files","Servidor para conectarse a Antidot y enviar los dados"
32
+ "Upload Directory","Subir Directorio"
33
+ "Directory to upload files","Directorio para subir archivos"
34
+ Login,Contraseña
35
+ "Your Antidot FTP Login","Su contraseña FTP Antidot"
36
+ Password,Contraseña
37
+ "Your Antidot FTP Password","Su Antidot FTP Contraseña"
38
+ "Web services","Servicios web"
39
+ "Web service host to communicate with Antidot","Servidor de servicios Web para comunicarse con Antidot"
40
+ "Service ID","ID de servicio"
41
+ "Antidot service ID","Antidot ID de servicio"
42
+ Status,Estado
43
+ "Web service status. Go live only with 'stable' status on your production site","Estado del servicio Web. Pasar a producción únicamente con el estatuto « estable »"
44
+ "Products fields mapping","Mapa de campos de productos"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Esta sección permite configurar el exporte del catalogo indexado por Afs@store. Los atributos del modelo estándar de Magento como urls , categorías, precios, cantidades y las promociones son utilizadas automáticamente para el exporte. Utilice este formulario para asignar los campos específicos de su modelo de datos"
46
+ "Include out of stock products","Incluir los productos sin stock"
47
+ "Change this setting if you want to include out of stock products","Cambie esta configuración si desea incluir los productos sin stock"
48
+ Name,Designación
49
+ "Short name","Designación corta"
50
+ Brand,Marca
51
+ Description,Descripción
52
+ Keywords,"Palabras clave"
53
+ "Is new","Es nuevo"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Este atributo se puede usar como un criterio de searchandizing para gestionar la búsqueda de productos y la auto compleción de los productos"
55
+ "Is top sale","Es el tope de las ventas"
56
+ "Is featured","Es un producto para destacar"
57
+ Material,Material
58
+ Color,Color
59
+ Model,Modelo
60
+ Size,Tamaño
61
+ Gender,Género
62
+ "GTIN / EAN","Código EAN"
63
+ Identifier,"Otros códigos productos"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurar identificadores adicionales aquí ( su referencia , referencia del fabricante , referencia , proveedor OEM ... )"
65
+ "Additional facets","Facetas adicionales"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Atributos adicionales para filtrar los resultados y, opcionalmente, aparecerán sugerencias de auto auto-compleción"
67
+ Autocomplete,Auto-compleción
68
+ Enable,"Motor de auto-compleción"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS@Store (recomendado) motor de búsqueda auto-completado . Volver a Magento sólo si tiene problemas"
70
+ "Display products ?","Enseñar los productos ?"
71
+ "Display products in auto complete results","Enseñar los productos en las sugestiones"
72
+ "Number of products displayed","Número de productos para ensebar"
73
+ "Select the maximum number of products to display","Seleccione el número máximo de marcas para enseñar"
74
+ "Display brands","Marcas de pantalla"
75
+ "Display brands in auto complete results","Mostrar marcas en los resultados auto-completados"
76
+ "Number of brands displayed","Numero de marcas que se ensenan"
77
+ "Select the maximum number of brands to display","Seleccione el número máximo de marcas para mostrar"
78
+ Categories,Categorías
79
+ "Display categories in auto complete results","Mostrar categorías en los resultados auto-completados"
80
+ "Number of categories displayed","Número de categorías mostradas"
81
+ "Select the maximum number of categories to display","Seleccione el número máximo de categorías para enseñar"
82
+ Template,Plantilla
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configure aquí la plantilla XSLT para mostrar los resultados de ACP. Precaución: si puse en la plantilla su propio criterio de ordenación de columnas y en seguida tecla « Sort order » por encima de la configuración jamás funcionará"
84
+ Example,Ejemplo
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Mostrar una ventana emergente con una muestra de código XML que se transforma con la hoja de estilo XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaurar la plantilla XSLT predeterminado ( muy útil si lo rompió !)"
87
+ "Sort order","Ordenar la clasificación"
88
+ "First collection to display in suggest","Primera columna para enseñar en la auto-compleción"
89
+ "Second collection to display in suggest","Segunda columna para enseñar en la auto-compleción"
90
+ "Third collection to display in suggest","Tercera columna para enseñar en la auto-compleción"
91
+ "Search engine","Motor de búsqueda"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Seleccione Magento o AFS @ Store (recomendado) motor de búsqueda. Volver a Magento sólo si tiene problemas"
93
+ "Display categories","Mostrar categorías"
94
+ "Display categories matching to search query (require template customization)","Mostrar categorías que coincidan con la consulta de la búsqueda (requiere personalización de las plantillas )"
95
+ "Spellcheck sentence","Frase de spellcheck"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texto que se mostrará cuando una sugerencia de corrección ortográfica está disponible. Utilice { } spellcheck palabra clave para Incluir la sugerencia AFS@tienda en su texto"
97
+ Facets,Facetas
98
+ "Select the facets (and the order) you want to enable on the search page","Seleccione las facetas (y el orden) en que desea habilitar en la página de búsqueda"
99
+ "Max number of facet values","Número máximo de valores de faceta"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Número máximo de opciones para mostrar una faceta ( resultados adicionales serán ocultados )"
101
+ "Price facet label","Etiqueta de precio faceta"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Selección del texto que se muestra para cada intervalo de precio : utilizar { min } y {max} códigos para incluir valores en el texto"
103
+ "Sort options","Criterios de ordenación"
104
+ "Select sort options available in sort drop down menu","Seleccione el menú opciones de clasificación disponible en Sort Drop Down"
105
+ "Default sort","Clasificación predeterminada"
106
+ "Default sort to apply","Clasificación predeterminada para aplicar"
107
+ Merchandising,Comercialización
108
+ "Promote redirect","Promover la función « Promote »"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Te permite elegir cómo se utilizan « Promover campañas » creadas en Antidot Back-Office para re-dirigir la búsqueda hacia urls internos"
110
+ "On-demand Data Upload","Envié de la dados según la demanda"
111
+ "Push products","Envié el catalogo"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envié manualmente productos de AFS@store para la indexación (útil cuando se cambia la configuración anterior)"
113
+ "Data upload report","Informe de envió de datos"
114
+ "Last index push history logs","Historial de los últimos envíos de dados"
115
+ Price,Precio
116
+ categories,Categorías
117
+ products,Productos
118
+ brands,Marcas
119
+ Ascending,Ascendente
120
+ Descending,Descendiente
121
+ Disable,NO
122
+ Enable,Si
123
+ All,Siempre
124
+ "Only when no result","Sólo cuando no hay ningún resultado"
125
+ Always,Siempre
126
+ Relevance,Pertinencia
127
+ Position,Posición
128
+ Name,Nombre
129
+ Price,Precio
130
+ "Is promotional",Promociones
131
+ "Is new","Productos nuevos"
132
+ "Is top sale","Top de las ventas"
133
+ "Is featured",Destacar
134
+ "Additional fields for plain text indexation","Campos aditionales para indexacion en texto pleno"
app/locale/fr_CA/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Envoyer
2
+ "Restore the default template","Restaurer le modèle par défaut"
3
+ "Display XML","Voir le XML"
4
+ Attribute,Attribut
5
+ "Add a field","Ajouter un champ"
6
+ Sortable,Critère
7
+ Direction,Sens
8
+ Facet,Facette
9
+ Sort,Rang
10
+ "Multiple selections","Sélection multiple"
11
+ "Auto Complete","Auto Complétion"
12
+ Date,Date
13
+ Reference,Clé
14
+ Type,Type
15
+ Element,Flux
16
+ Products,Elements
17
+ Status,Status
18
+ Store,Magasin
19
+ Term,Mot-clé
20
+ "Did you mean {spellcheck} ?","Vouliez vous dire {spellcheck} ?"
21
+ "From {min}€ to {max}€","De {min}€ à {max}€"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Back office Antidot (Analytics, Synonymes, Promote)"
23
+ "AFS@Store Extension Version","Version de l'extension AFS@Store"
24
+ "Communicate this version to the support team if you need help","Au besoin, communiquez cette version à l'équipe support"
25
+ "Organization name","Nom de la société"
26
+ "Useful for support team","Utile à l'équipe support"
27
+ E-mail,"Adresse mèl"
28
+ "Used to send alert when an error occured","Utilisée pour envoyer une alerte en cas d'erreur"
29
+ "Data upload parameters","Paramètres d'envoi des données"
30
+ Host,Serveur
31
+ "Host to connect to Antidot to push files","Serveur Antidot auquel se connecter pour envoyer les données"
32
+ "Upload Directory","Dossier de dépôt"
33
+ "Directory to upload files","Dossier de dépôt des fichiers"
34
+ Login,Identifiant
35
+ "Your Antidot FTP Login","Votre identifiant FTP Antidot"
36
+ Password,"Mot de passe"
37
+ "Your Antidot FTP Password","Votre mot de passe FTP Antidot"
38
+ "Web services","Services Web"
39
+ "Web service host to communicate with Antidot","Adresse du service web AFS@Store"
40
+ "Service ID","ID du service"
41
+ "Antidot service ID","ID du service chez Antidot"
42
+ Status,Statut
43
+ "Web service status. Go live only with 'stable' status on your production site","Statut du service Web. Ne mettre en production qu'avec le statut 'stable'"
44
+ "Products fields mapping","Mapping des champs produits"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Cette section permet de paramétrer l'export du catalogue indexé par AFS@Store. Les attributs standards de Magento comme les urls, les catégories, les prix, les quantités et les promotions sont automatiquement découverts. Utilisez ce formulaire pour faire correspondre les champs spécifiques de votre modèle de donnée"
46
+ "Include out of stock products","Inclure les produits hors stock"
47
+ "Change this setting if you want to include out of stock products","Changez ce paramètre si vous souhaitez inclure les produits hors stock"
48
+ Name,Libellé
49
+ "Short name","Libellé court"
50
+ Brand,Marque
51
+ Description,Description
52
+ Keywords,Mots-clés
53
+ "Is new","Est une nouveauté"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Cet attribut pourra être utilisé comme critère de searchandizing par la recherche et l'autocomplétion des produits"
55
+ "Is top sale","Est un top des vente"
56
+ "Is featured","Est un produit mis en avant"
57
+ Material,Matière
58
+ Color,Couleur
59
+ Model,Modèle
60
+ Size,Taille
61
+ Gender,Genre
62
+ "GTIN / EAN","Code EAN"
63
+ Identifier,"Autres codes produits"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurez des codes additionnels ici (votre référence, celle du fabricant, du fournisseur...)"
65
+ "Additional facets","Facettes additionnelles"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Attributs additionnels pour filtrer les résultats (également utilisables en autocomplétion)"
67
+ Autocomplete,Autocomplétion
68
+ Enable,"Moteur d'autocomplétion"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Choisissez le moteur d'autocomplétion de Magento ou d'AFS@Store (recommandé). Ne revenir à celui de Magento qu'en cas de problème"
70
+ "Display products ?","Afficher les produits ?"
71
+ "Display products in auto complete results","Affiche les produits dans les suggestions"
72
+ "Number of products displayed","Nombre de produits à afficher"
73
+ "Select the maximum number of products to display","Choisissez le nombre maximal de produits à afficher"
74
+ "Display brands","Afficher les marques ?"
75
+ "Display brands in auto complete results","Affiche les marques dans les suggestions"
76
+ "Number of brands displayed","Nombre de marques à afficher"
77
+ "Select the maximum number of brands to display","Choisissez le nombre maximal de marques à afficher"
78
+ Categories,"Afficher les catégories ?"
79
+ "Display categories in auto complete results","Affiche les catégories dans les suggestions"
80
+ "Number of categories displayed","Nombre de catégories à afficher"
81
+ "Select the maximum number of categories to display","Choisissez le nombre maximal de catégories à afficher"
82
+ Template,Modèle
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configurez ici votre modèle de transformation XSLT pour afficher les résultats de l'ACP. Attention: si votre modèle fixe son propre ordre d'affichage des colonnes, alors le paramètre 'Ordre' ci-dessus sera sans effet"
84
+ Example,Exemple
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Affiche une popup avec un exemple de code XML qui sera transformé par la feuille de style XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaure le modèle XSLT par défaut (utile si vous l'avez cassé !)"
87
+ "Sort order",Ordre
88
+ "First collection to display in suggest","Première colonne à afficher dans l'autocomplétion"
89
+ "Second collection to display in suggest","Seconde colonne à afficher dans l'autocomplétion"
90
+ "Third collection to display in suggest","Troisième colonne à afficher dans l'autocomplétion"
91
+ "Search engine","Moteur de recherche"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Choisissez le moteur de recherche de Magento ou d'AFS@Store (recommandé). Ne revenir à celui de Magento qu'en cas de problème"
93
+ "Display categories","Afficher les rayons"
94
+ "Display categories matching to search query (require template customization)","Affiche des liens directs vers les pages de catégories correspondant à la recherche (requiert de la personnalisation de modèle Magento, voir documentation de l'extension)"
95
+ "Spellcheck sentence","Phrase de suggestion orthographique"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texte à afficher quand une suggestion orthographique est disponible. Utilisez {spellcheck} pour inclure la suggestion dans votre texte"
97
+ Facets,Facettes
98
+ "Select the facets (and the order) you want to enable on the search page","Choisissez les facettes (et leur ordre d'affichage) que vous souhaitez dans la page de résultats de recherche"
99
+ "Max number of facet values","Nombre max de valeurs de facettes affichées"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Nombre max de valeurs affichées pour une facette (les autres valeurs seront cachées)"
101
+ "Price facet label","Libellé pour la facette prix"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Texte à afficher pour chaque intervalle de prix : utilisez {min} et {max} pour inclure les bornes dans votre texte"
103
+ "Sort options","Critères de tri"
104
+ "Select sort options available in sort drop down menu","Choisissez les critères de tri disponibles dans la liste déroulante de la page résultat"
105
+ "Default sort","Tri par défaut"
106
+ "Default sort to apply","Tri par défaut à appliquer aux résultats"
107
+ Merchandising,Merchandising
108
+ "Promote redirect","Redirection Promote"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Vous permet de choisir la façon dont les campagnes Promote, créées dans le Back-Office Antidot, sont utilisés pour rediriger les visiteurs vers des pages d'atterrissage de votre site"
110
+ "On-demand Data Upload","Envoi de données à la demande"
111
+ "Push products","Envoyez le catalogue"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envoi manuel des produits pour l'indexation AFS@Store (utile après une modification de paramétrage dans le mapping)"
113
+ "Data upload report","Rapport d'envoi des données"
114
+ "Last index push history logs","Journal des derniers envois de données"
115
+ Price,Prix
116
+ categories,Catégories
117
+ products,Produits
118
+ brands,Marques
119
+ Ascending,Ascendant
120
+ Descending,Descendant
121
+ Disable,Non
122
+ Enable,Oui
123
+ All,Toujours
124
+ "Only when no result","Seulement si aucun résultat"
125
+ Always,Toujours
126
+ Relevance,Pertinence
127
+ Position,Position
128
+ Name,Désignation
129
+ Price,Prix
130
+ "Is promotional",Promotions
131
+ "Is new",Nouveautés
132
+ "Is top sale","Top des ventes"
133
+ "Is featured","Mis en avant"
134
+ "Additional fields for plain text indexation","Champs textuels supplémentaires pour l'indexation"
app/locale/fr_FR/MDN_Antidot.csv ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Push,Envoyer
2
+ "Restore the default template","Restaurer le modèle par défaut"
3
+ "Display XML","Voir le XML"
4
+ Attribute,Attribut
5
+ "Add a field","Ajouter un champ"
6
+ Sortable,Critère
7
+ Direction,Sens
8
+ Facet,Facette
9
+ Sort,Rang
10
+ "Multiple selections","Sélection multiple"
11
+ "Auto Complete","Auto Complétion"
12
+ Date,Date
13
+ Reference,Clé
14
+ Type,Type
15
+ Element,Flux
16
+ Products,Elements
17
+ Status,Status
18
+ Store,Magasin
19
+ Term,Mot-clé
20
+ "Did you mean {spellcheck} ?","Vouliez vous dire {spellcheck} ?"
21
+ "From {min}€ to {max}€","De {min}€ à {max}€"
22
+ "Antidot Back office (Analytics, Synonyms, Promote)","Back office Antidot (Analytics, Synonymes, Promote)"
23
+ "AFS@Store Extension Version","Version de l'extension AFS@Store"
24
+ "Communicate this version to the support team if you need help","Au besoin, communiquez cette version à l'équipe support"
25
+ "Organization name","Nom de la société"
26
+ "Useful for support team","Utile à l'équipe support"
27
+ E-mail,"Adresse mèl"
28
+ "Used to send alert when an error occured","Utilisée pour envoyer une alerte en cas d'erreur"
29
+ "Data upload parameters","Paramètres d'envoi des données"
30
+ Host,Serveur
31
+ "Host to connect to Antidot to push files","Serveur Antidot auquel se connecter pour envoyer les données"
32
+ "Upload Directory","Dossier de dépôt"
33
+ "Directory to upload files","Dossier de dépôt des fichiers"
34
+ Login,Identifiant
35
+ "Your Antidot FTP Login","Votre identifiant FTP Antidot"
36
+ Password,"Mot de passe"
37
+ "Your Antidot FTP Password","Votre mot de passe FTP Antidot"
38
+ "Web services","Services Web"
39
+ "Web service host to communicate with Antidot","Adresse du service web AFS@Store"
40
+ "Service ID","ID du service"
41
+ "Antidot service ID","ID du service chez Antidot"
42
+ Status,Statut
43
+ "Web service status. Go live only with 'stable' status on your production site","Statut du service Web. Ne mettre en production qu'avec le statut 'stable'"
44
+ "Products fields mapping","Mapping des champs produits"
45
+ "These settings are related to the product file generation, used for AFS@Store indexing. You can match to existing attributes in Magento and add your own properties. Standard Magento attributes such as urls, categories, prices, quantities, promotions are automatically inherited by this mapping. Use this form to map specific fields from your data model","Cette section permet de paramétrer l'export du catalogue indexé par AFS@Store. Les attributs standards de Magento comme les urls, les catégories, les prix, les quantités et les promotions sont automatiquement découverts. Utilisez ce formulaire pour faire correspondre les champs spécifiques de votre modèle de donnée"
46
+ "Include out of stock products","Inclure les produits hors stock"
47
+ "Change this setting if you want to include out of stock products","Changez ce paramètre si vous souhaitez inclure les produits hors stock"
48
+ Name,Libellé
49
+ "Short name","Libellé court"
50
+ Brand,Marque
51
+ Description,Description
52
+ Keywords,Mots-clés
53
+ "Is new","Est une nouveauté"
54
+ "This attribute may be used as searchandizing criterium to manage Product search/autocomplete order","Cet attribut pourra être utilisé comme critère de searchandizing par la recherche et l'autocomplétion des produits"
55
+ "Is top sale","Est un top des vente"
56
+ "Is featured","Est un produit mis en avant"
57
+ Material,Matière
58
+ Color,Couleur
59
+ Model,Modèle
60
+ Size,Taille
61
+ Gender,Genre
62
+ "GTIN / EAN","Code EAN"
63
+ Identifier,"Autres codes produits"
64
+ "Configure additional identifiers here (your reference, manufacturer reference, supplier reference, OEM ...)","Configurez des codes additionnels ici (votre référence, celle du fabricant, du fournisseur...)"
65
+ "Additional facets","Facettes additionnelles"
66
+ "Additional attributes to filter results and optionally appear as autocomplete suggestions","Attributs additionnels pour filtrer les résultats (également utilisables en autocomplétion)"
67
+ Autocomplete,Autocomplétion
68
+ Enable,"Moteur d'autocomplétion"
69
+ "Select Magento or AFS@Store (recommended) autocomplete engine. Rollback to Magento only if you experience issues","Choisissez le moteur d'autocomplétion de Magento ou d'AFS@Store (recommandé). Ne revenir à celui de Magento qu'en cas de problème"
70
+ "Display products ?","Afficher les produits ?"
71
+ "Display products in auto complete results","Affiche les produits dans les suggestions"
72
+ "Number of products displayed","Nombre de produits à afficher"
73
+ "Select the maximum number of products to display","Choisissez le nombre maximal de produits à afficher"
74
+ "Display brands","Afficher les marques ?"
75
+ "Display brands in auto complete results","Affiche les marques dans les suggestions"
76
+ "Number of brands displayed","Nombre de marques à afficher"
77
+ "Select the maximum number of brands to display","Choisissez le nombre maximal de marques à afficher"
78
+ Categories,"Afficher les catégories ?"
79
+ "Display categories in auto complete results","Affiche les catégories dans les suggestions"
80
+ "Number of categories displayed","Nombre de catégories à afficher"
81
+ "Select the maximum number of categories to display","Choisissez le nombre maximal de catégories à afficher"
82
+ Template,Modèle
83
+ "Configure here the XSLT template to display ACP results. Caution: if your template set his own sort order for columns, then 'Sort order' above settings will not work anymore","Configurez ici votre modèle de transformation XSLT pour afficher les résultats de l'ACP. Attention: si votre modèle fixe son propre ordre d'affichage des colonnes, alors le paramètre 'Ordre' ci-dessus sera sans effet"
84
+ Example,Exemple
85
+ "Display a popup with a sample of XML code that will be transformed with the XSLT stylesheet.","Affiche une popup avec un exemple de code XML qui sera transformé par la feuille de style XSLT."
86
+ "Restore the default XSLT template (usefull if you broke it !)","Restaure le modèle XSLT par défaut (utile si vous l'avez cassé !)"
87
+ "Sort order",Ordre
88
+ "First collection to display in suggest","Première colonne à afficher dans l'autocomplétion"
89
+ "Second collection to display in suggest","Seconde colonne à afficher dans l'autocomplétion"
90
+ "Third collection to display in suggest","Troisième colonne à afficher dans l'autocomplétion"
91
+ "Search engine","Moteur de recherche"
92
+ "Select Magento or AFS@Store (recommended) search engine. Rollback to Magento only if you experience issues","Choisissez le moteur de recherche de Magento ou d'AFS@Store (recommandé). Ne revenir à celui de Magento qu'en cas de problème"
93
+ "Display categories","Afficher les rayons"
94
+ "Display categories matching to search query (require template customization)","Affiche des liens directs vers les pages de catégories correspondant à la recherche (requiert de la personnalisation de modèle Magento, voir documentation de l'extension)"
95
+ "Spellcheck sentence","Phrase de suggestion orthographique"
96
+ "Text to display when a spellcheck suggestion is available. Use {spellcheck} keyword to include AFS@Store suggestion in your text","Texte à afficher quand une suggestion orthographique est disponible. Utilisez {spellcheck} pour inclure la suggestion dans votre texte"
97
+ Facets,Facettes
98
+ "Select the facets (and the order) you want to enable on the search page","Choisissez les facettes (et leur ordre d'affichage) que vous souhaitez dans la page de résultats de recherche"
99
+ "Max number of facet values","Nombre max de valeurs de facettes affichées"
100
+ "Max number of options to display for a facet (additional results will be hidden)","Nombre max de valeurs affichées pour une facette (les autres valeurs seront cachées)"
101
+ "Price facet label","Libellé pour la facette prix"
102
+ "Text to display for each price ranges : use {min} and {max} codes to include values in your text","Texte à afficher pour chaque intervalle de prix : utilisez {min} et {max} pour inclure les bornes dans votre texte"
103
+ "Sort options","Critères de tri"
104
+ "Select sort options available in sort drop down menu","Choisissez les critères de tri disponibles dans la liste déroulante de la page résultat"
105
+ "Default sort","Tri par défaut"
106
+ "Default sort to apply","Tri par défaut à appliquer aux résultats"
107
+ Merchandising,Merchandising
108
+ "Promote redirect","Redirection Promote"
109
+ "Allows you to choose how Promote campaigns created in Antidot Back-Office are used to redirect search towards internal urls","Vous permet de choisir la façon dont les campagnes Promote, créées dans le Back-Office Antidot, sont utilisés pour rediriger les visiteurs vers des pages d'atterrissage de votre site"
110
+ "On-demand Data Upload","Envoi de données à la demande"
111
+ "Push products","Envoyez le catalogue"
112
+ "Manually push products index to AFS@Store (usefull when you change configuration above)","Envoi manuel des produits pour l'indexation AFS@Store (utile après une modification de paramétrage dans le mapping)"
113
+ "Data upload report","Rapport d'envoi des données"
114
+ "Last index push history logs","Journal des derniers envois de données"
115
+ Price,Prix
116
+ categories,Catégories
117
+ products,Produits
118
+ brands,Marques
119
+ Ascending,Ascendant
120
+ Descending,Descendant
121
+ Disable,Non
122
+ Enable,Oui
123
+ All,Toujours
124
+ "Only when no result","Seulement si aucun résultat"
125
+ Always,Toujours
126
+ Relevance,Pertinence
127
+ Position,Position
128
+ Name,Désignation
129
+ Price,Prix
130
+ "Is promotional",Promotions
131
+ "Is new",Nouveautés
132
+ "Is top sale","Top des ventes"
133
+ "Is featured","Mis en avant"
134
+ "Additional fields for plain text indexation","Champs textuels supplémentaires pour l'indexation"
js/mdn/antidot/CollapsibleLists.js ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+
3
+ CollapsibleLists.js
4
+
5
+ An object allowing lists to dynamically expand and collapse
6
+
7
+ Created by Stephen Morley - http://code.stephenmorley.org/ - and released under
8
+ the terms of the CC0 1.0 Universal legal code:
9
+
10
+ http://creativecommons.org/publicdomain/zero/1.0/legalcode
11
+
12
+ */
13
+
14
+ // create the CollapsibleLists object
15
+ var CollapsibleLists =
16
+ new function(){
17
+
18
+ /* Makes all lists with the class 'collapsibleList' collapsible. The
19
+ * parameter is:
20
+ *
21
+ * doNotRecurse - true if sub-lists should not be made collapsible
22
+ */
23
+ this.apply = function(doNotRecurse){
24
+
25
+ // loop over the unordered lists
26
+ var uls = document.getElementsByTagName('ul');
27
+ for (var index = 0; index < uls.length; index ++){
28
+
29
+ // check whether this list should be made collapsible
30
+ if (uls[index].className.match(/(^| )collapsibleList( |$)/)){
31
+
32
+ // make this list collapsible
33
+ this.applyTo(uls[index], true);
34
+
35
+ // check whether sub-lists should also be made collapsible
36
+ if (!doNotRecurse){
37
+
38
+ // add the collapsibleList class to the sub-lists
39
+ var subUls = uls[index].getElementsByTagName('ul');
40
+ for (var subIndex = 0; subIndex < subUls.length; subIndex ++){
41
+ subUls[subIndex].className += ' collapsibleList';
42
+ }
43
+
44
+ }
45
+
46
+ }
47
+
48
+ }
49
+
50
+ };
51
+
52
+ /* Makes the specified list collapsible. The parameters are:
53
+ *
54
+ * node - the list element
55
+ * doNotRecurse - true if sub-lists should not be made collapsible
56
+ */
57
+ this.applyTo = function(node, doNotRecurse){
58
+
59
+ // loop over the list items within this node
60
+ var lis = node.getElementsByTagName('li');
61
+ for (var index = 0; index < lis.length; index ++){
62
+
63
+ // check whether this list item should be collapsible
64
+ if (!doNotRecurse || node == lis[index].parentNode){
65
+
66
+ // prevent text from being selected unintentionally
67
+ if (lis[index].addEventListener){
68
+ lis[index].addEventListener(
69
+ 'mousedown', function (e){ e.preventDefault(); }, false);
70
+ }else{
71
+ lis[index].attachEvent(
72
+ 'onselectstart', function(){ event.returnValue = false; });
73
+ }
74
+
75
+ // add the click listener
76
+ if (lis[index].addEventListener){
77
+ lis[index].addEventListener(
78
+ 'click', createClickListener(lis[index]), false);
79
+ }else{
80
+ lis[index].attachEvent(
81
+ 'onclick', createClickListener(lis[index]));
82
+ }
83
+
84
+ // close the unordered lists within this list item
85
+ toggle(lis[index]);
86
+
87
+ }
88
+
89
+ }
90
+
91
+ };
92
+
93
+ /* Returns a function that toggles the display status of any unordered
94
+ * list elements within the specified node. The parameter is:
95
+ *
96
+ * node - the node containing the unordered list elements
97
+ */
98
+ function createClickListener(node){
99
+
100
+ // return the function
101
+ return function(e){
102
+
103
+ // ensure the event object is defined
104
+ if (!e) e = window.event;
105
+
106
+ // find the list item containing the target of the event
107
+ var li = (e.target ? e.target : e.srcElement);
108
+ while (li.nodeName != 'LI') li = li.parentNode;
109
+
110
+ // toggle the state of the node if it was the target of the event
111
+ if (li == node) toggle(node);
112
+
113
+ };
114
+
115
+ }
116
+
117
+ /* Opens or closes the unordered list elements directly within the
118
+ * specified node. The parameter is:
119
+ *
120
+ * node - the node containing the unordered list elements
121
+ */
122
+ function toggle(node){
123
+
124
+ // determine whether to open or close the unordered lists
125
+ var open = node.className.match(/(^| )collapsibleListClosed( |$)/);
126
+
127
+ // loop over the unordered list elements with the node
128
+ var uls = node.getElementsByTagName('ul');
129
+ for (var index = 0; index < uls.length; index ++){
130
+
131
+ // find the parent list item of this unordered list
132
+ var li = uls[index];
133
+ while (li.nodeName != 'LI') li = li.parentNode;
134
+
135
+ // style the unordered list if it is directly within this node
136
+ if (li == node) uls[index].style.display = (open ? 'block' : 'none');
137
+
138
+ }
139
+
140
+ // remove the current class from the node
141
+ node.className =
142
+ node.className.replace(
143
+ /(^| )collapsibleList(Open|Closed)( |$)/, '');
144
+
145
+ // if the node contains unordered lists, set its class
146
+ if (uls.length > 0){
147
+ node.className += ' collapsibleList' + (open ? 'Open' : 'Closed');
148
+ }
149
+
150
+ }
151
+
152
+ }();
lib/antidot/.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ doc/html
2
+ *~
3
+ *.bak
4
+ *.swp
5
+ *#*
lib/antidot/AFS/ACP/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2014
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/../..
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/ACP/TEST/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2013
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/../../../
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/ACP/TEST/acpConnectorTest.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/ACP/afs_acp_connector.php";
3
+ require_once "AFS/ACP/afs_acp_query.php";
4
+
5
+
6
+
7
+ class Connector extends AfsAcpConnector
8
+ {
9
+ public function __construct($host, $service)
10
+ {
11
+ parent::__construct($host, $service);
12
+ }
13
+
14
+ public function get_url()
15
+ {
16
+ return $this->scheme . '://' . $this->host . '/acp';
17
+ }
18
+ public function get_id()
19
+ {
20
+ return $this->service->id;
21
+ }
22
+ public function get_status()
23
+ {
24
+ return $this->service->status;
25
+ }
26
+ public function format_parameters(array $parameters)
27
+ {
28
+ return parent::format_parameters($parameters);
29
+ }
30
+ public function build_url($web_service, array $parameters)
31
+ {
32
+ return parent::build_url('acp', $parameters);
33
+ }
34
+ }
35
+
36
+ class AcpConnectorTest extends PHPUnit_Framework_TestCase
37
+ {
38
+ public function testConstructDefaultParameters()
39
+ {
40
+ $connector = new Connector('url', new AfsService(42));
41
+ $this->assertEquals('http://url/acp', $connector->get_url());
42
+ $this->assertEquals(42, $connector->get_id());
43
+ $this->assertEquals('stable', $connector->get_status());
44
+ }
45
+ public function testConstructParameters()
46
+ {
47
+ $connector = new Connector('url', new AfsService(42, 'rc'));
48
+ $this->assertEquals('http://url/acp', $connector->get_url());
49
+ $this->assertEquals(42, $connector->get_id());
50
+ $this->assertEquals('rc', $connector->get_status());
51
+ }
52
+
53
+ public function testNoParameter()
54
+ {
55
+ $connector = new Connector('url', new AfsService(42));
56
+ $this->assertEquals('', $connector->format_parameters(array()));
57
+ }
58
+ public function testParameters()
59
+ {
60
+ $connector = new Connector('url', new AfsService(42));
61
+ $this->assertEquals('foo=bar&fooz=baz&fooz=bat',
62
+ $connector->format_parameters(array(
63
+ 'foo' => 'bar',
64
+ 'fooz' => array('baz', 'bat'))));
65
+ }
66
+
67
+ public function testFailOnInvalidUrl()
68
+ {
69
+ $connector = new AfsAcpConnector('foo', new AfsService(42));
70
+ try {
71
+ $connector->send(array());
72
+ $this->fail('Send query with bad URL should have failed!');
73
+ } catch (Exception $e) { }
74
+ }
75
+
76
+ public function testAPIVersion()
77
+ {
78
+ $connector = new Connector('foo', new AfsService(42));
79
+ $query = new AfsAcpQuery();
80
+ $url = $connector->build_url(null, $query->get_parameters());
81
+ $this->assertFalse(strpos($url, urlencode(get_api_version())) === False,
82
+ '"'.urlencode(get_api_version()).'" should be in: '.$url);
83
+ }
84
+
85
+ public function testNoUserAgent()
86
+ {
87
+ $connector = new Connector('foo', new AfsService(42));
88
+ $query = new AfsAcpQuery();
89
+ $url = $connector->build_url(null, $query->get_parameters());
90
+ $this->assertTrue(strpos($url, urlencode('afs:userAgent')) === False);
91
+ }
92
+
93
+ public function testUserAgent()
94
+ {
95
+ global $_SERVER;
96
+ $_SERVER = array('HTTP_USER_AGENT' => 'foo');
97
+ $connector = new Connector('foo', new AfsService(42));
98
+ $query = new AfsAcpQuery();
99
+ $url = $connector->build_url(null, $query->get_parameters());
100
+ $this->assertFalse(strpos($url, urlencode('afs:userAgent')) === False);
101
+ }
102
+
103
+ public function testNoIp()
104
+ {
105
+ $connector = new Connector('foo', new AfsService(42));
106
+ $query = new AfsAcpQuery();
107
+ $url = $connector->build_url(null, $query->get_parameters());
108
+ $this->assertTrue(strpos($url, urlencode('afs:ip')) === False);
109
+ }
110
+
111
+ public function testIp()
112
+ {
113
+ global $_SERVER;
114
+ $_SERVER = array('REMOTE_ADDR' => '127.0.0.1');
115
+ $connector = new Connector('foo', new AfsService(42));
116
+ $query = new AfsAcpQuery();
117
+ $url = $connector->build_url(null, $query->get_parameters());
118
+ $this->assertFalse(strpos($url, urlencode('afs:ip')) === False);
119
+ }
120
+ }
121
+
122
+
lib/antidot/AFS/ACP/TEST/acpQueryTest.php ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/ACP/afs_acp_query.php";
3
+
4
+
5
+ class AcpQueryTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testSetQuery()
8
+ {
9
+ $query = new AfsAcpQuery();
10
+ $query = $query->set_query('foo');
11
+ $this->assertTrue($query->get_query() == 'foo');
12
+ }
13
+ public function testSetNewQueryValue()
14
+ {
15
+ $query = new AfsAcpQuery();
16
+ $query = $query->set_query('foo');
17
+ $query = $query->set_query('bar');
18
+ $this->assertFalse($query->get_query() == 'foo');
19
+ $this->assertTrue($query->get_query() == 'bar');
20
+ }
21
+
22
+ public function testHasNoQuery()
23
+ {
24
+ $query = new AfsAcpQuery();
25
+ $this->assertFalse($query->has_query());
26
+ }
27
+ public function testHasQuery()
28
+ {
29
+ $query = new AfsAcpQuery();
30
+ $query = $query->set_query('foo');
31
+ $this->assertTrue($query->has_query());
32
+ }
33
+
34
+ public function testHasNoFeedSet()
35
+ {
36
+ $query = new AfsAcpQuery();
37
+ $this->assertFalse($query->has_feed());
38
+ }
39
+ public function testHasFeedName()
40
+ {
41
+ $query = new AfsAcpQuery();
42
+ $query = $query->set_feed('foo');
43
+ $this->assertTrue($query->has_feed());
44
+ $this->assertTrue(in_array('foo', $query->get_feeds()));
45
+ }
46
+ public function testHasFeedNames()
47
+ {
48
+ $query = new AfsAcpQuery();
49
+ $query = $query->add_feed('foo');
50
+ $query = $query->add_feed('bar');
51
+ $this->assertTrue($query->has_feed());
52
+ $this->assertTrue(in_array('foo', $query->get_feeds()));
53
+ $this->assertTrue(in_array('bar', $query->get_feeds()));
54
+ }
55
+ public function testResetFeedName()
56
+ {
57
+ $query = new AfsAcpQuery();
58
+ $query = $query->add_feed('foo');
59
+ $query = $query->add_feed('bar');
60
+ $query = $query->set_feed('baz');
61
+ $this->assertTrue($query->has_feed());
62
+ $this->assertFalse(in_array('foo', $query->get_feeds()));
63
+ $this->assertFalse(in_array('bar', $query->get_feeds()));
64
+ $this->assertTrue(in_array('baz', $query->get_feeds()));
65
+ }
66
+
67
+ public function testDefaultNumberOfReplies()
68
+ {
69
+ $query = new AfsAcpQuery();
70
+ $this->assertTrue($query->get_replies() == 10);
71
+ }
72
+ public function testSetNumberOfReplies()
73
+ {
74
+ $query = new AfsAcpQuery();
75
+ $query = $query->set_replies(42);
76
+ $this->assertTrue($query->get_replies() == 42);
77
+ }
78
+
79
+ public function testNoUserId()
80
+ {
81
+ $query = new AfsAcpQuery();
82
+ $id = $query->get_user_id();
83
+ $this->assertFalse(empty($id));
84
+ }
85
+ public function testUserId()
86
+ {
87
+ $query = new AfsAcpQuery();
88
+ $query = $query->set_user_id('foo');
89
+ $this->assertEquals('foo', $query->get_user_id());
90
+ }
91
+
92
+ public function testNoSessionId()
93
+ {
94
+ $query = new AfsAcpQuery();
95
+ $id = $query->get_session_id();
96
+ $this->assertFalse(empty($id));
97
+ }
98
+ public function testSessionId()
99
+ {
100
+ $query = new AfsAcpQuery();
101
+ $query = $query->set_session_id('foo');
102
+ $this->assertEquals('foo', $query->get_session_id());
103
+ }
104
+
105
+ public function testUserIdInitFromManager()
106
+ {
107
+ $name = 'MyUserCookie';
108
+ $_COOKIE[$name] = 'foo';
109
+ $mgr = new AfsUserSessionManager($name);
110
+ $query = new AfsAcpQuery();
111
+ $user_id = $query->get_user_id();
112
+ $session_id = $query->get_session_id();
113
+ $query = $query->initialize_user_and_session_id($mgr);
114
+ $this->assertFalse($user_id == $query->get_user_id());
115
+ $this->assertEquals($session_id, $query->get_session_id());
116
+ }
117
+ public function testSessionIdInitFromManager()
118
+ {
119
+ $name = 'MySessionCookie';
120
+ $_COOKIE[$name] = 'bar';
121
+ $mgr = new AfsUserSessionManager('blabla', $name);
122
+ $query = new AfsAcpQuery();
123
+ $user_id = $query->get_user_id();
124
+ $session_id = $query->get_session_id();
125
+ $query = $query->initialize_user_and_session_id($mgr);
126
+ $this->assertEquals($user_id, $query->get_user_id());
127
+ $this->assertFalse($session_id == $query->get_session_id());
128
+ }
129
+
130
+ public function testNoLog()
131
+ {
132
+ $query = new AfsAcpQuery();
133
+ $this->assertEquals(0, count($query->get_logs()));
134
+ }
135
+
136
+ public function testSomeLogs()
137
+ {
138
+ $query = new AfsAcpQuery();
139
+ $query->add_log('foo');
140
+ $query->add_log('bar');
141
+ $logs = $query->get_logs();
142
+ $this->assertEquals(2, count($logs));
143
+ $this->assertEquals('foo', $logs[0]);
144
+ $this->assertEquals('bar', $logs[1]);
145
+ }
146
+
147
+ public function testNoKey()
148
+ {
149
+ $query = new AfsAcpQuery();
150
+ $this->assertFalse($query->has_key());
151
+ $this->assertEquals(null, $query->get_key());
152
+ }
153
+
154
+ public function testKey()
155
+ {
156
+ $query = new AfsAcpQuery();
157
+ $query->set_key('test');
158
+ $this->assertTrue($query->has_key());
159
+ $this->assertEquals($query->get_key(), 'test');
160
+ }
161
+
162
+ public function testCloneQuery()
163
+ {
164
+ $query = new AfsAcpQuery();
165
+ $query = $query->set_query('query')
166
+ ->add_feed('feed')
167
+ ->add_feed('food')
168
+ ->set_replies(666)
169
+ ->add_log('loggy');
170
+ $clone = new AfsAcpQuery($query);
171
+ $this->assertTrue($clone->get_query('query') == 'query');
172
+ $this->assertTrue(in_array('food', $clone->get_feeds()));
173
+ $this->assertTrue($clone->get_replies() == 666);
174
+
175
+ $logs = $clone->get_logs();
176
+ $this->assertEquals(1, count($logs));
177
+ $this->assertEquals('loggy', $logs[0]);
178
+ }
179
+
180
+ public function testRetrieveParametersArray()
181
+ {
182
+ $query = new AfsAcpQuery();
183
+ $query = $query->set_query('query');
184
+
185
+ $query = $query->add_feed('feed');
186
+ $query = $query->add_feed('food');
187
+
188
+ $query = $query->set_replies(666);
189
+
190
+ $query = $query->add_log('loggy');
191
+ $query = $query->add_log('loggo');
192
+
193
+ $result = $query->get_parameters();
194
+ $this->assertTrue(array_key_exists('query', $result));
195
+ $this->assertTrue($result['query'] == 'query');
196
+
197
+ $this->assertTrue(array_key_exists('feed', $result));
198
+ $this->assertTrue(in_array('feed', $result['feed']));
199
+ $this->assertTrue(in_array('food', $result['feed']));
200
+
201
+ $this->assertTrue(array_key_exists('replies', $result));
202
+ $this->assertTrue($result['replies'] == 666);
203
+
204
+ $this->assertTrue(array_key_exists('log', $result));
205
+ $this->assertEquals('loggy', $result['log'][0]);
206
+ $this->assertEquals('loggo', $result['log'][1]);
207
+ }
208
+ }
209
+
lib/antidot/AFS/ACP/TEST/acpReplysetHelperTest.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/ACP/afs_acp_replyset_helper.php';
3
+ require_once 'AFS/SEARCH/afs_query.php';
4
+
5
+
6
+ class AcpReplysetHelperTest extends PHPUnit_Framework_TestCase
7
+ {
8
+ public function testReplyRetrieveSearchQuery()
9
+ {
10
+ $input = json_decode('[
11
+ "search",
12
+ [ "suggest" ]
13
+ ]', true);
14
+
15
+ $replyset = new AfsAcpReplysetHelper('', $input);
16
+ $this->assertTrue($replyset->has_reply());
17
+ $this->assertEquals(1, $replyset->get_nb_replies());
18
+
19
+ $replies = $replyset->get_replies();
20
+ $reply = reset($replies);
21
+ $query = $reply->get_search_query();
22
+ $this->assertEquals('suggest', $query->get_query());
23
+ $this->assertEquals(AfsOrigin::ACP, $query->get_from());
24
+ }
25
+
26
+ public function testReplyHasNoOption()
27
+ {
28
+ $input = json_decode('[
29
+ "search",
30
+ [ "suggest" ]
31
+ ]', true);
32
+
33
+ $replyset = new AfsAcpReplysetHelper('NoName', $input);
34
+ $this->assertEquals('NoName', $replyset->get_feed());
35
+ $this->assertEquals(1, $replyset->get_nb_replies());
36
+
37
+ $replies = $replyset->get_replies();
38
+ $reply = reset($replies);
39
+ $this->assertFalse($reply->has_option());
40
+ }
41
+ public function testReplyHasNamedOption()
42
+ {
43
+ $input = json_decode('[
44
+ "search",
45
+ [
46
+ "foo",
47
+ "bar"
48
+ ],
49
+ [
50
+ {
51
+ "k": "v",
52
+ "l": "b"
53
+ },
54
+ { }
55
+ ]
56
+ ]', true);
57
+
58
+ $replyset = new AfsAcpReplysetHelper('', $input);
59
+ $this->assertEquals('', $replyset->get_feed());
60
+ $this->assertEquals(2, $replyset->get_nb_replies());
61
+
62
+ $replies = $replyset->get_replies();
63
+ $reply = reset($replies);
64
+ $this->assertEquals('foo', $reply->get_value());
65
+ $this->assertTrue($reply->has_option());
66
+ $this->assertTrue($reply->has_option('k'));
67
+ $this->assertFalse($reply->has_option('kk'));
68
+ $this->assertEquals('v', $reply->get_option('k'));
69
+ $this->assertEquals('b', $reply->get_option('l'));
70
+
71
+ $reply = next($replies);
72
+ $this->assertFalse($reply->has_option());
73
+ }
74
+
75
+ public function testReplyUnknownNamedOption()
76
+ {
77
+ $input = json_decode('[
78
+ "search",
79
+ [
80
+ "foo",
81
+ "bar"
82
+ ],
83
+ [
84
+ {
85
+ "k": "v",
86
+ "l": "b"
87
+ },
88
+ { }
89
+ ]
90
+ ]', true);
91
+
92
+ $replyset = new AfsAcpReplysetHelper('', $input);
93
+ $replies = $replyset->get_replies();
94
+
95
+ $reply = reset($replies);
96
+ $this->assertTrue($reply->has_option());
97
+ try {
98
+ $reply->get_option('blabla');
99
+ $this->fail('Retrieving unknown option should have raised exception!');
100
+ } catch (OutOfBoundsException $e) { }
101
+
102
+ $reply = next($replies);
103
+ $this->assertFalse($reply->has_option());
104
+ try {
105
+ $reply->get_option('blabla');
106
+ $this->fail('Retrieving unknown option should have raised exception!');
107
+ } catch (OutOfBoundsException $e) { }
108
+ }
109
+
110
+ public function testReplysetHasNoReply()
111
+ {
112
+ $input = json_decode('[
113
+ "search",
114
+ [ ]
115
+ ]', true);
116
+
117
+ try {
118
+ $replyset = new AfsAcpReplysetHelper('', $input);
119
+ $this->fail('No suggestion should not allow to create replyset helper!');
120
+ } catch (AfsAcpEmptyReplysetException $e) { }
121
+ }
122
+ }
lib/antidot/AFS/ACP/TEST/acpResponseHelperTest.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/ACP/afs_acp_response_helper.php';
3
+
4
+
5
+ class AcpResponseHelperTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testNoSuggestion()
8
+ {
9
+ $input = json_decode('[
10
+ "foo",
11
+ []
12
+ ]', true);
13
+
14
+ $response = new AfsAcpResponseHelper($input);
15
+ $this->assertFalse($response->has_replyset());
16
+ }
17
+
18
+ public function testMonoFeed()
19
+ {
20
+ $input = json_decode('[
21
+ "search",
22
+ [ "suggest" ]
23
+ ]', true);
24
+
25
+ $response = new AfsAcpResponseHelper($input);
26
+ $this->assertTrue($response->has_replyset());
27
+
28
+ $replysets = $response->get_replysets();
29
+ $this->assertEquals(1, count($replysets));
30
+
31
+ $replyset = $response->get_replyset();
32
+ $this->assertEquals('', $replyset->get_feed());
33
+ }
34
+
35
+ public function testMultiFeed()
36
+ {
37
+ $input = json_decode('{
38
+ "Foo": [
39
+ "search",
40
+ [ "foo" ]
41
+ ],
42
+ "Bar": [
43
+ "search",
44
+ [
45
+ "bar",
46
+ "baz"
47
+ ]
48
+ ]
49
+ }', true);
50
+
51
+ $response = new AfsAcpResponseHelper($input);
52
+ $this->assertTrue($response->has_replyset());
53
+
54
+ $replysets = $response->get_replysets();
55
+ $this->assertEquals(2, count($replysets));
56
+
57
+ $replyset = $response->get_replyset('Foo');
58
+ $this->assertEquals('Foo', $replyset->get_feed());
59
+ $replyset = reset($replysets);
60
+ $this->assertEquals('Foo', $replyset->get_feed());
61
+ $replies = $replyset->get_replies();
62
+ $this->assertEquals(1, count($replies));
63
+ $reply = reset($replies);
64
+ $this->assertEquals('foo', $reply->get_value());
65
+
66
+ $replyset = next($replysets);
67
+ $this->assertEquals('Bar', $replyset->get_feed());
68
+ $replies = $replyset->get_replies();
69
+ $this->assertEquals(2, count($replies));
70
+ $reply = reset($replies);
71
+ $this->assertEquals('bar', $reply->get_value());
72
+ $reply = next($replies);
73
+ $this->assertEquals('baz', $reply->get_value());
74
+ }
75
+
76
+ public function testUnknownFeed()
77
+ {
78
+ $input = json_decode('{
79
+ "Foo": [
80
+ "search",
81
+ [ "foo" ]
82
+ ],
83
+ "Bar": [
84
+ "search",
85
+ [
86
+ "bar",
87
+ "baz"
88
+ ]
89
+ ]
90
+ }', true);
91
+
92
+ $response = new AfsAcpResponseHelper($input);
93
+ $this->assertTrue($response->has_replyset());
94
+
95
+ try {
96
+ $response->get_replyset('BAZ');
97
+ $this->fail('Retrieving replyset from unknown feed should have raised exception!');
98
+ } catch (OutOfBoundsException $e) { }
99
+ }
100
+
101
+ public function testSingleFeedAsArray()
102
+ {
103
+ $input = json_decode('[
104
+ "search",
105
+ [ "suggest" ],
106
+ [ { "key": "value"} ]
107
+ ]', true);
108
+
109
+ $response = new AfsAcpResponseHelper($input);
110
+ $this->assertTrue($response->has_replyset());
111
+ $result = $response->format();
112
+
113
+ $this->assertTrue(array_key_exists('', $result));
114
+ $replyset = $result[''];
115
+ $this->assertEquals('', $replyset['feed']);
116
+ $replies = $replyset['replies'];
117
+ $reply = reset($replies);
118
+ $this->assertEquals('suggest', $reply['value']);
119
+ $this->assertFalse(empty($reply['options']));
120
+ $option = each($reply['options']);
121
+ $this->assertEquals('key', $option[0]);
122
+ $this->assertEquals('value', $option[1]);
123
+ }
124
+
125
+ public function testMultiFeedsAsArray()
126
+ {
127
+ $input = json_decode('{
128
+ "foo": [
129
+ "search",
130
+ [ "suggest" ],
131
+ [ { "key": "value"} ]
132
+ ],
133
+ "bar": [
134
+ "search",
135
+ [ "sugg" ]
136
+ ]
137
+ }', true);
138
+
139
+ $response = new AfsAcpResponseHelper($input);
140
+ $this->assertTrue($response->has_replyset());
141
+ $result = $response->format();
142
+
143
+ $this->assertEquals('search', $result['query_string']);
144
+
145
+ $this->assertTrue(array_key_exists('foo', $result));
146
+ $replyset = $result['foo'];
147
+ $this->assertEquals('foo', $replyset['feed']);
148
+ $replies = $replyset['replies'];
149
+ $reply = reset($replies);
150
+ $this->assertEquals('suggest', $reply['value']);
151
+ $option = each($reply['options']);
152
+ $this->assertEquals('key', $option[0]);
153
+ $this->assertEquals('value', $option[1]);
154
+
155
+ $this->assertTrue(array_key_exists('bar', $result));
156
+ $replyset = $result['bar'];
157
+ $this->assertEquals('bar', $replyset['feed']);
158
+ $replies = $replyset['replies'];
159
+ $reply = reset($replies);
160
+ $this->assertEquals('sugg', $reply['value']);
161
+ $this->assertTrue(empty($reply['options']));
162
+ }
163
+ }
lib/antidot/AFS/ACP/TEST/acpTest.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/ACP/afs_acp.php';
3
+
4
+ class AcpTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testRetrieveDefaultParameters()
7
+ {
8
+ $acp = new AfsAcp('127.0.0.1', 666);
9
+
10
+ $service = $acp->get_service();
11
+ $this->assertEquals(666, $service->id);
12
+ $this->assertEquals(AfsServiceStatus::STABLE, $service->status);
13
+
14
+ $acp->execute();
15
+ $url = $acp->get_generated_url();
16
+ $this->assertTrue(strpos($url, '127.0.0.1') !== False, 'URL does not contain right host');
17
+ $this->assertTrue(strpos($url, 'service=666') !== False, 'URL does not contain right sesrvice id');
18
+ $this->assertTrue(strpos($url, 'status=stable') !== False, 'URL does not contain right sesrvice status');
19
+
20
+ $config = $acp->get_helpers_configuration();
21
+ $this->assertEquals(AfsHelperFormat::ARRAYS, $config->get_helper_format());
22
+ }
23
+
24
+ public function testQueryStringOnly()
25
+ {
26
+ $acp = new AfsAcp('127.0.0.1', 666);
27
+ $acp->query('foo');
28
+
29
+ $query = $acp->get_query();
30
+ $this->assertEquals('foo', $query->get_query());
31
+ $this->assertFalse($query->has_feed());
32
+ }
33
+ public function testQueryStringWithFeeds()
34
+ {
35
+ $acp = new AfsAcp('127.0.0.1', 666);
36
+ $acp->query('foo', array('a', 'b'));
37
+
38
+ $query = $acp->get_query();
39
+ $this->assertEquals('foo', $query->get_query());
40
+ $this->assertTrue($query->has_feed());
41
+
42
+ $feeds = $query->get_feeds();
43
+ $feed = reset($feeds);
44
+ $this->assertEquals('a', $feed);
45
+
46
+ $feed = next($feeds);
47
+ $this->assertEquals('b', $feed);
48
+ }
49
+ public function testQueryStringWithFeedsWhereasFeedsHasAlreadyBeenSet()
50
+ {
51
+ $acp = new AfsAcp('127.0.0.1', 666);
52
+ $query = $acp->get_query();
53
+ $query = $query->set_query('bar')->set_feed('bat')->add_feed('baz');
54
+ $acp->query('foo', array('a', 'b'));
55
+
56
+ $query = $acp->get_query();
57
+ $this->assertEquals('foo', $query->get_query());
58
+ $this->assertTrue($query->has_feed());
59
+
60
+ $feeds = $query->get_feeds();
61
+ $feed = reset($feeds);
62
+ $this->assertEquals('a', $feed);
63
+
64
+ $feed = next($feeds);
65
+ $this->assertEquals('b', $feed);
66
+ }
67
+ }
lib/antidot/AFS/ACP/afs_acp.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_service.php';
3
+ require_once 'AFS/ACP/afs_acp_query.php';
4
+ require_once 'AFS/ACP/afs_acp_query_manager.php';
5
+ require_once 'AFS/ACP/afs_acp_connector.php';
6
+ require_once 'AFS/ACP/afs_acp_configuration.php';
7
+ require_once 'AFS/ACP/afs_acp_response_helper.php';
8
+
9
+ /** @brief Facade for AFS ACP engine query. */
10
+ class AfsAcp
11
+ {
12
+ private $service = null;
13
+ private $connector = null;
14
+ private $config = null;
15
+ private $query = null;
16
+
17
+
18
+ /** @brief Constructs AFS ACP facade.
19
+ *
20
+ * @param $host [in] server hosting the required service.
21
+ * @param $id [in] identifier of the desired service.
22
+ * @param $status [in] status of the desired service (see @ref AfsServiceStatus).
23
+ */
24
+ public function __construct($host, $id, $status=AfsServiceStatus::STABLE)
25
+ {
26
+ $this->service = new AfsService($id, $status);
27
+ $this->connector = new AfsAcpConnector($host, $this->service);
28
+ $this->config = new AfsAcpConfiguration();
29
+ $this->query = new AfsAcpQuery();
30
+ }
31
+
32
+ /** @name Query management
33
+ *
34
+ * Remember that AfsAcpQuery objects are immutable.
35
+ * @{ */
36
+
37
+ /** @brief Defines query string for ACP engine.
38
+ *
39
+ * This is a shortcut to:
40
+ * @code $query = $acp->get_query();
41
+ $query = $query->set_query($value)->set_feed('value1')->add_feed('value2');
42
+ $acp->set_query($query); @endcode
43
+ * This can be writtent in onle line as following:
44
+ * @code $acp->query($value, array('value1', 'value2')); @endcode
45
+ *
46
+ * @param $value [in] New value to submit to ACP engine.
47
+ * @param $feeds [in] List of feeds to filter on. By default, there is no
48
+ * filter on feeds (empty array).
49
+ */
50
+ public function query($value, $feeds=array())
51
+ {
52
+ $this->query = $this->query->set_query($value);
53
+ if (! empty($feeds)) {
54
+ $this->query = $this->query->set_feed(array_shift($feeds));
55
+ foreach ($feeds as $feed)
56
+ $this->query = $this->query->add_feed($feed);
57
+ }
58
+ }
59
+ /** @brief Retrieves current query.
60
+ * @return AFS ACP query.
61
+ */
62
+ public function get_query()
63
+ {
64
+ return $this->query;
65
+ }
66
+ /** @brief Defines new query.
67
+ * @param $query [in] New query to set.
68
+ */
69
+ public function set_query(AfsAcpQuery $query)
70
+ {
71
+ $this->query = $query;
72
+ }
73
+
74
+ /** @brief Executes query.
75
+ * @param $format [in] prefered result format.
76
+ * @return Helper or array depending on chosen $format.
77
+ */
78
+ public function execute($format=AfsHelperFormat::ARRAYS)
79
+ {
80
+ $this->config->set_helper_format($format);
81
+ $query_mgr = new AfsAcpQueryManager($this->connector, $this->config);
82
+ $reply = $query_mgr->send($this->query);
83
+ $helper = new AfsAcpResponseHelper($reply, $this->config);
84
+ if (AfsHelperFormat::ARRAYS == $format)
85
+ return $helper->format();
86
+ else
87
+ return $helper;
88
+ }
89
+
90
+ /** @brief Retrieves URL used to query AFS ACP engine.
91
+ *
92
+ * Useful for debug purpose only. It should be called after
93
+ * AfsAcp::execute has been called.
94
+ * @return generated URL for AFS ACP engine.
95
+ */
96
+ public function get_generated_url()
97
+ {
98
+ return $this->connector->get_generated_url();
99
+ }
100
+ /** @} */
101
+
102
+ /** @name Miscellaneous accessors
103
+ * @{ */
104
+
105
+ /** @brief Retrieves current AFS service.
106
+ * @return AFS service.
107
+ */
108
+ public function get_service()
109
+ {
110
+ return $this->service;
111
+ }
112
+
113
+ /** @brief Retrieves helper configuration.
114
+ * @return AFS Helper configuration.
115
+ */
116
+ public function get_helpers_configuration()
117
+ {
118
+ return $this->config;
119
+ }
120
+
121
+ /** @brief Retrieves ACP engine connector.
122
+ * @return AFS ACP connector.
123
+ */
124
+ public function get_connector()
125
+ {
126
+ return $this->connector;
127
+ }
128
+ /** @} */
129
+ }
lib/antidot/AFS/ACP/afs_acp_configuration.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/afs_configuration_base.php';
3
+
4
+
5
+ /** @brief Configuration object for AFS ACP queries.
6
+ */
7
+ class AfsAcpConfiguration extends AfsConfigurationBase
8
+ {
9
+ }
lib/antidot/AFS/ACP/afs_acp_connector.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once 'AFS/afs_connector.php';
4
+
5
+
6
+ /** @brief AFS ACP connector.
7
+ *
8
+ * AFS auto complete connnector. */
9
+ class AfsAcpConnector extends AfsConnector
10
+ {
11
+ public function __construct($host, AfsService $service, $scheme=AFS_SCHEME_HTTP)
12
+ {
13
+ parent::__construct($host, $service, $scheme);
14
+ if ($scheme != AFS_SCHEME_HTTP)
15
+ throw InvalidArgumentException('ACP connector support only HTTP connection');
16
+ $this->build_reply_as_associative_array();
17
+ }
18
+
19
+ /** @brief Retrieves web service name.
20
+ * @return always return 'acp';
21
+ */
22
+ protected function get_web_service_name()
23
+ {
24
+ return 'acp';
25
+ }
26
+
27
+ /** @internal
28
+ * @brief Overload default implemantation with something easiest to handle
29
+ * for ACP.
30
+ *
31
+ * @param $message [in] Error message.
32
+ * @param $details [in] Error details.
33
+ *
34
+ * @return Associated array with error and details.
35
+ */
36
+ protected function build_error($message, $details)
37
+ {
38
+ return array('error' => $message, 'details' => $details);
39
+ }
40
+ }
lib/antidot/AFS/ACP/afs_acp_exception.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_exception.php';
3
+
4
+
5
+ /** @brief Base class for all ACP exceptions. */
6
+ abstract class AfsAcpException extends AfsBaseException
7
+ { }
8
+
9
+ /** @brief Exception raised when input data can't be used to initialize ACP suggestion. */
10
+ class AfsAcpUnmanagedSuggestionFormatException extends AfsAcpException
11
+ { }
12
+
13
+ /** @brief Exception raised when input data are incoherent. */
14
+ class AfsAcpInvalidSuggestionFormatException extends AfsAcpException
15
+ { }
16
+
17
+ /** @brief Exception raised when trying to initialize replyset from empty ACP suggestion. */
18
+ class AfsAcpEmptyReplysetException extends AfsAcpException
19
+ {
20
+ private $query_string = null; ///> search word (or partial word)
21
+
22
+ public function __construct($query_string, $message="")
23
+ {
24
+ parent::__construct($message);
25
+ $this->query_string = $query_string;
26
+ }
27
+
28
+ /** @brief Retrieves query word for which there is no suggestion.
29
+ * @return query word or partial word.
30
+ */
31
+ public function get_query_string()
32
+ {
33
+ return $this->query_string;
34
+ }
35
+ }
lib/antidot/AFS/ACP/afs_acp_query.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once 'AFS/afs_query_base.php';
4
+
5
+
6
+ /** @brief Represents an AFS ACP query.
7
+ *
8
+ * All instances of this class are immutable: most of all call involves
9
+ * creation of new instance copied from current one. Newly created instance
10
+ * is modified according to called method and then returned. So, <b>don't
11
+ * forget</b> to store returned object!
12
+ */
13
+ class AfsAcpQuery extends AfsQueryBase
14
+ {
15
+ /** @internal
16
+ * @brief Copy current instance.
17
+ * @return New copied instance.
18
+ */
19
+ protected function copy()
20
+ {
21
+ return new AfsAcpQuery($this);
22
+ }
23
+ }
lib/antidot/AFS/ACP/afs_acp_query_manager.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/ACP/afs_acp_configuration.php';
3
+
4
+ /** @brief Manages ACP queries.
5
+ */
6
+ class AfsAcpQueryManager
7
+ {
8
+ private $connector = null;
9
+ private $config = null;
10
+
11
+
12
+ /** @brief Constructs new ACP query manager.
13
+ *
14
+ * @param $connector [in] Connector used to submit a query to AFS ACP engine.
15
+ * @param $config [in] ACP main configuration (see AfsAcpConfiguration for
16
+ * more details). Default instance is constructed when no
17
+ * configuration is provided.
18
+ */
19
+ public function __construct(AfsConnectorInterface $connector,
20
+ AfsAcpConfiguration $config=null)
21
+ {
22
+ $this->connector = $connector;
23
+ $this->config = (is_null($config) ? new AfsAcpConfiguration() : $config);
24
+ }
25
+
26
+ /** @brief Send query to AFS ACP engine.
27
+ * @param $query [in] @a AfsAcpQuery object to use in order to generate
28
+ * appropriate ACP query.
29
+ * @return reply of AFS ACP engine.
30
+ */
31
+ public function send(AfsAcpQuery $query)
32
+ {
33
+ $query->initialize_user_and_session_id($this->config->get_user_session_manager());
34
+ $params = $this->convert_to_param($query);
35
+ return $this->connector->send($params);
36
+ }
37
+
38
+ /** @internal
39
+ * @brief Converts @a AfsAcpQuery to arrays of parameters.
40
+ *
41
+ * Retrieves parameters from query and builds appropriate parameters to be
42
+ * sent to AFS ACP engine.
43
+ *
44
+ * @param $query [in] AfsAcpQuery to transform.
45
+ *
46
+ * @return array of key-value pairs where key is a valid AFS ACP query
47
+ * parameter name and the associated value is a valid string for this
48
+ * specific parameter name.
49
+ */
50
+ private function convert_to_param(AfsAcpQuery $query)
51
+ {
52
+ $params = array();
53
+ foreach ($query->get_parameters() as $param => $values) {
54
+ $params['afs:' . $param] = $values;
55
+ }
56
+ return $params;
57
+ }
58
+ }
lib/antidot/AFS/ACP/afs_acp_reply_helper.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/afs_origin.php';
3
+ require_once 'COMMON/afs_helper_format.php';
4
+
5
+ /** @brief Helper representing one suggestion reply.
6
+ *
7
+ * This class is not intended to be instanciated manually.
8
+ */
9
+ class AfsAcpReplyHelper extends AfsHelperBase
10
+ {
11
+ private $value = null;
12
+ private $meta = null;
13
+
14
+
15
+ /** @brief Constructs new ACP suggestion helper.
16
+ *
17
+ * @param $suggestion [in] Suggestion value.
18
+ * @param $meta [in] Meta data associated to the suggestion value (may be null).
19
+ * @param $config [in] ACP configuration.
20
+ */
21
+ public function __construct($suggestion, $meta, AfsAcpConfiguration $config=null)
22
+ {
23
+ $this->value = $suggestion;
24
+ $this->meta = (is_null($meta) ? array() : $meta);
25
+ }
26
+
27
+ /** @brief Retrieves suggestion value.
28
+ * @return value of the suggestion.
29
+ */
30
+ public function get_value()
31
+ {
32
+ return $this->value;
33
+ }
34
+
35
+ /** @brief Gets AFS search query associated to this suggestion value.
36
+ *
37
+ * AFS search query is initialized with appropriate search word and @c from
38
+ * parameter is set to AfsOrigin::ACP.
39
+ *
40
+ * @return AFS search query.
41
+ */
42
+ public function get_search_query()
43
+ {
44
+ $query = new AfsQuery();
45
+ $query = $query->set_query($this->value)->set_from(AfsOrigin::ACP);
46
+ return $query;
47
+ }
48
+
49
+ /** @name Meta data
50
+ * @{ */
51
+
52
+ /** @brief Checks whether mete data (options) are available.
53
+ *
54
+ * @param $name [in] name of meta data to check. Default is null to test
55
+ * whether at least one meta data is available.
56
+ * @return @c true when required meta data is present or least one meta data
57
+ * is available, @c false otherwise.
58
+ */
59
+ public function has_option($name=null)
60
+ {
61
+ if (is_null($name))
62
+ return ! empty($this->meta);
63
+ elseif (array_key_exists($name, $this->meta))
64
+ return true;
65
+ else
66
+ return false;
67
+ }
68
+ /** @brief Retrieves specified meta data.
69
+ * @param $name [in] name of the requested meta data.
70
+ * @return meta data.
71
+ * @exception OutOfBoundsException when requested meta data is unavailable.
72
+ */
73
+ public function get_option($name)
74
+ {
75
+ if (array_key_exists($name, $this->meta))
76
+ return $this->meta[$name];
77
+ else
78
+ throw new OutOfBoundsException('No meta data available for suggestion: ' .$this->value);
79
+ }
80
+ /** @brief Retrieves all meta data associated to current suggestion value.
81
+ * @return meta data as key-value pairs.
82
+ */
83
+ public function get_options()
84
+ {
85
+ return $this->meta;
86
+ }
87
+ /** @} */
88
+
89
+ /** @name Miscellaneous
90
+ * @{ */
91
+
92
+ /** @brief Retrieves suggestions as array.
93
+ *
94
+ * This method is intended for internal use only.
95
+ *
96
+ * All data are stored in <tt>key => value</tt> format:
97
+ * @li @c value: suggestion value,
98
+ * @li @c options: map of meta data key-value pairs.
99
+ *
100
+ * @return array filled with key and values.
101
+ */
102
+ public function format()
103
+ {
104
+ return array('value' => $this->value,
105
+ 'options' => $this->meta);
106
+ }
107
+ }
lib/antidot/AFS/ACP/afs_acp_replyset_helper.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_helper_base.php';
3
+ require_once 'AFS/ACP/afs_acp_reply_helper.php';
4
+ require_once 'AFS/ACP/afs_acp_exception.php';
5
+
6
+ /** @brief AFS ACP Replyset helper.
7
+ *
8
+ * Each replyset corresponds to suggestions of each feed.
9
+ */
10
+ class AfsAcpReplysetHelper extends AfsHelperBase
11
+ {
12
+ private $name = null; ///> Name of the feed which has produced these suggestions.
13
+ private $query_string = null;
14
+ private $replies = array();
15
+
16
+ /** @brief Constructs new ACP replyset helper.
17
+ *
18
+ * @param $name [in] Feed name source of generated suggestions.
19
+ * @param $reply_set [in] Json decoded suggestions of specific replyset.
20
+ * @param $config ACP configuration.
21
+ *
22
+ * @exception AfsAcpEmptyReplysetException when feed does not generate any
23
+ * suggestion.
24
+ * @exception AfsAcpUnmanagedSuggestionFormatException when provided
25
+ * $reply_set format is unknown.
26
+ * @exception AfsAcpInvalidSuggestionFormatException when provided
27
+ * $reply_set is invalid.
28
+ *
29
+ */
30
+ public function __construct($name, $reply_set, AfsAcpConfiguration $config=null)
31
+ {
32
+ $size = count($reply_set);
33
+ if ($size < 2 || $size > 3)
34
+ throw new AfsAcpUnmanagedSuggestionFormatException();
35
+
36
+ $this->query_string = reset($reply_set);
37
+ $suggestions = next($reply_set);
38
+ if (empty($suggestions))
39
+ throw new AfsAcpEmptyReplysetException($this->query_string);
40
+
41
+ if ($size == 3) {
42
+ $metas = next($reply_set);
43
+ if (count($metas) != count($suggestions))
44
+ throw new AfsAcpInvalidSuggestionFormatException();
45
+ } else {
46
+ $metas = array_fill(0, count($suggestions), null);
47
+ }
48
+
49
+ $this->name = $name;
50
+ for ($i = 0; $i < count($suggestions); $i++)
51
+ $this->replies[] = new AfsAcpReplyHelper($suggestions[$i], $metas[$i], $config);
52
+ }
53
+
54
+ /** @brief Retrieves name of the feed which has produced these suggestions.
55
+ * @return feed name.
56
+ */
57
+ public function get_feed()
58
+ {
59
+ return $this->name;
60
+ }
61
+
62
+ /** @brief Retrieves query string.
63
+ * @return query string.
64
+ */
65
+ public function get_query_string()
66
+ {
67
+ return $this->query_string;
68
+ }
69
+
70
+ /** @name Suggestions
71
+ * @{ */
72
+
73
+ /** @brief Checks wether at least one reply is available.
74
+ * @return @c true when one or more replies are available, @c false
75
+ * otherwise.
76
+ */
77
+ public function has_reply()
78
+ {
79
+ return (empty($this->replies) ? false : true);
80
+ }
81
+ /** @brief Retrieves number suggestions.
82
+ * @return Number of suggestions for current feed.
83
+ */
84
+ public function get_nb_replies()
85
+ {
86
+ return count($this->replies);
87
+ }
88
+
89
+ /** @brief Retrieves suggestions.
90
+ * @return List of suggestion helpers.
91
+ */
92
+ public function get_replies()
93
+ {
94
+ return $this->replies;
95
+ }
96
+ /** @} */
97
+
98
+ /** @name Miscellaneous
99
+ * @{ */
100
+
101
+ /** @brief Retrieves suggestions as array.
102
+ *
103
+ * This method is intended for internal use only.
104
+ *
105
+ * All suggestions are stored in list format.
106
+ *
107
+ * @return array filled with key and values.
108
+ */
109
+ public function format()
110
+ {
111
+ $replies = array();
112
+ foreach ($this->replies as $reply)
113
+ $replies[] = $reply->format();
114
+ return array('feed' => $this->name,
115
+ 'replies' => $replies);
116
+ }
117
+ /** @} */
118
+ }
lib/antidot/AFS/ACP/afs_acp_response_helper.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/ACP/afs_acp_exception.php';
3
+ require_once 'AFS/ACP/afs_acp_replyset_helper.php';
4
+ require_once 'AFS/afs_response_helper_base.php';
5
+
6
+
7
+ /** @brief Main helper for AFS ACP reply.
8
+ *
9
+ * This helper is intended to be initialized with reply from @a
10
+ * AfsAcpQueryManager::send. It allows to manage suggestions from one or
11
+ * more feeds.
12
+ */
13
+ class AfsAcpResponseHelper extends AfsResponseHelperBase
14
+ {
15
+ private $replysets = array();
16
+ private $query_string = null;
17
+
18
+
19
+ /** @brief Constructs new ACP response helper.
20
+ *
21
+ * @param $response [in] Json decoded reply.
22
+ * @param $config [in] ACP configuration.
23
+ */
24
+ public function __construct($response, AfsAcpConfiguration $config=null)
25
+ {
26
+ if (array_key_exists('error', $response)) {
27
+ $this->set_error_msg($response['error']);
28
+ return;
29
+ }
30
+
31
+ if (array_values($response) === $response)
32
+ $response = array('' => $response);
33
+ foreach ($response as $feed => $replies) {
34
+ try {
35
+ $replyset = new AfsAcpReplysetHelper($feed, $replies, $config);
36
+ $this->replysets[$feed] = $replyset;
37
+ } catch (AfsAcpEmptyReplysetException $e) {
38
+ $this->query_string = $e->get_query_string();
39
+ }
40
+ }
41
+ if (is_null($this->query_string) && ! empty($this->replysets)) {
42
+ $replyset = reset($this->replysets);
43
+ $this->query_string = $replyset->get_query_string();
44
+ }
45
+ }
46
+
47
+
48
+ /** @brief Retrieves query string.
49
+ * @return query string.
50
+ */
51
+ public function get_query_string()
52
+ {
53
+ return $this->query_string;
54
+ }
55
+
56
+ /** @name Replies
57
+ * @{ */
58
+
59
+ /** @brief Checks whether there is suggestion.
60
+ * @return @c true when at least one suggestion is available, @c false
61
+ * otherwise.
62
+ */
63
+ public function has_replyset()
64
+ {
65
+ return (! $this->in_error()) && (! empty($this->replysets));
66
+ }
67
+ /** @brief Retrieves all suggestions from all feeds.
68
+ *
69
+ * Map of suggestions where the key corresponds to the name of feed which
70
+ * has generated suggestions and the value corresponds to suggestion helper.
71
+ *
72
+ * @return all defined suggestions per feed.
73
+ */
74
+ public function get_replysets()
75
+ {
76
+ return $this->replysets;
77
+ }
78
+ /** @brief Retrieves suggestions from specific feed.
79
+ *
80
+ * @param $feed [in] name of the feed to filter on
81
+ * (default: empty string -> retrieves generic spellcheck if any,
82
+ * otherwise raise exception)
83
+ * @return @a AfsReplysetHelper or formatted replyset depending on @a format
84
+ * parameter.
85
+ * @exception OutOfBoundsException when required feed does not exist. This
86
+ * also happen when no feed name is provided whereas all
87
+ * suggestions are associated to named feeds.
88
+ */
89
+ public function get_replyset($feed='')
90
+ {
91
+ if (array_key_exists($feed, $this->replysets))
92
+ return $this->replysets[$feed];
93
+ else
94
+ throw new OutOfBoundsException('No spellcheck available'
95
+ . (empty($feed) ? '' : ' for feed named: ' . $feed));
96
+ }
97
+ /** @} */
98
+
99
+ /** @name Miscellaneous
100
+ * @{ */
101
+
102
+ /** @brief Retrieves suggestions as array.
103
+ *
104
+ * This method is intended for internal use only.
105
+ *
106
+ * All data are stored in <tt>key => value</tt> format:
107
+ * @li <tt>feed name</tt>: suggestions per feed,
108
+ * Or, in case of error:
109
+ * @li @c error: error message.
110
+ *
111
+ * @return array filled with key and values.
112
+ */
113
+ public function format()
114
+ {
115
+ if ($this->in_error()) {
116
+ return array('error' => $this->get_error_msg());
117
+ } else {
118
+ $result = array('query_string' => $this->query_string);
119
+ foreach ($this->replysets as $feed => $replyset)
120
+ $result[$feed] = $replyset->format();
121
+ return $result;
122
+ }
123
+ }
124
+ /** @} */
125
+
126
+ }
lib/antidot/AFS/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2014
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/..
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/SEARCH/FILTER/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2014
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/../../..
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/SEARCH/FILTER/TEST/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2013
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/../../../..
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/SEARCH/FILTER/TEST/filterBuilderTest.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/FILTER/afs_combinator_filter.php';
3
+
4
+
5
+ function myBuilder($id, array $values)
6
+ {
7
+ if (empty($values))
8
+ return null;
9
+
10
+ $value = array_shift($values);
11
+ $result = filter($id)->equal->value($value);
12
+ while (! empty($values))
13
+ $result = $result->or->filter($id)->equal->value(array_shift($values));
14
+
15
+ return $result;
16
+ }
17
+
18
+
19
+ class FilterBuilderTest extends PHPUnit_Framework_TestCase
20
+ {
21
+ public function testFilterBuilderNoValue()
22
+ {
23
+ $this->assertEquals(null, myBuilder('ID', array()));
24
+ }
25
+
26
+ public function testFilterBuilderOneValue()
27
+ {
28
+ $this->assertEquals('ID=v', myBuilder('ID', array('v'))->to_string());
29
+ }
30
+
31
+ public function testFilterBuilderMultipleValues()
32
+ {
33
+ $this->assertEquals('ID=v1 or ID=v2 or ID=v3', myBuilder('ID', array('v1', 'v2', 'v3'))->to_string());
34
+ }
35
+ }
lib/antidot/AFS/SEARCH/FILTER/TEST/filterCombinationTest.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/FILTER/afs_combinator_filter.php';
3
+
4
+
5
+ class FilterCombinationTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testAndCombinator()
8
+ {
9
+ $result = filter('ID')->equal->value('value')->and->filter('FOO')->not_equal->value('bar');
10
+ $this->assertEquals('ID=value and FOO!=bar', $result->to_string());
11
+ }
12
+
13
+ public function testOrCombinator()
14
+ {
15
+ $result = filter('ID')->equal->value('value')->or->filter('FOO')->not_equal->value('bar');
16
+ $this->assertEquals('ID=value or FOO!=bar', $result->to_string());
17
+ }
18
+
19
+ public function testMultipleCombinators()
20
+ {
21
+ $result = filter('ID')->equal->value('value')->and->filter('FOO')->not_equal->value('bar')->or
22
+ ->filter('ID')->equal->value('value')->or->filter('FOO')->not_equal->value('bar');
23
+ $this->assertEquals('ID=value and FOO!=bar or ID=value or FOO!=bar', $result->to_string());
24
+ }
25
+
26
+ public function testBadCombinator()
27
+ {
28
+ try
29
+ {
30
+ filter('ID')->equal->value('value')->foo->filter('FOO')->not_equal->value('bar');
31
+ $this->fail('Bad combinator should have raised exception!');
32
+ } catch (AfsUnknownCombinatorException $e) {}
33
+ }
34
+ }
lib/antidot/AFS/SEARCH/FILTER/TEST/filterTest.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+
3
+ require_once 'AFS/SEARCH/FILTER/afs_filter.php';
4
+
5
+
6
+ class FilterTest extends PHPUnit_Framework_TestCase
7
+ {
8
+ public function testFilterEqual()
9
+ {
10
+ $filter = filter('ID')->equal;
11
+ $this->assertEquals('ID=', $filter->to_string());
12
+ }
13
+ public function testFilterNotEqual()
14
+ {
15
+ $filter = filter('ID')->not_equal;
16
+ $this->assertEquals('ID!=', $filter->to_string());
17
+ }
18
+ public function testFilterLess()
19
+ {
20
+ $filter = filter('ID')->less;
21
+ $this->assertEquals('ID<', $filter->to_string());
22
+ }
23
+ public function testFilterLessEqual()
24
+ {
25
+ $filter = filter('ID')->less_equal;
26
+ $this->assertEquals('ID<=', $filter->to_string());
27
+ }
28
+ public function testFilterGreater()
29
+ {
30
+ $filter = filter('ID')->greater;
31
+ $this->assertEquals('ID>', $filter->to_string());
32
+ }
33
+ public function testFilterGreaterEqual()
34
+ {
35
+ $filter = filter('ID')->greater_equal;
36
+ $this->assertEquals('ID>=', $filter->to_string());
37
+ }
38
+
39
+ public function testFilterBadOperator()
40
+ {
41
+ try
42
+ {
43
+ filter('ID')->less_than_or_equal_to;
44
+ $this->fail('Should have raised an exception for invalid operator');
45
+ } catch (AfsUnknownOperatorException $e) { }
46
+ }
47
+
48
+ public function testFilterEqualValue()
49
+ {
50
+ $filter = filter('ID')->equal->value('42');
51
+ $this->assertEquals('ID=42', $filter->to_string());
52
+ }
53
+ public function testFilterNotEqualValue()
54
+ {
55
+ $filter = filter('ID')->not_equal->value('666');
56
+ $this->assertEquals('ID!=666', $filter->to_string());
57
+ }
58
+ public function testFilterLessValue()
59
+ {
60
+ $filter = filter('ID')->less->value('666');
61
+ $this->assertEquals('ID<666', $filter->to_string());
62
+ }
63
+ public function testFilterLessEqualValue()
64
+ {
65
+ $filter = filter('ID')->less_equal->value('42');
66
+ $this->assertEquals('ID<=42', $filter->to_string());
67
+ }
68
+ public function testFilterGreaterValue()
69
+ {
70
+ $filter = filter('ID')->greater->value('42');
71
+ $this->assertEquals('ID>42', $filter->to_string());
72
+ }
73
+ public function testFilterGreaterEqualValue()
74
+ {
75
+ $filter = filter('ID')->greater_equal->value('666');
76
+ $this->assertEquals('ID>=666', $filter->to_string());
77
+ }
78
+
79
+ public function testIntegerValue()
80
+ {
81
+ $filter = filter('ID')->equal->value(42);
82
+ $this->assertEquals('ID=42', $filter->to_string());
83
+ }
84
+ public function testFloatValue()
85
+ {
86
+ $filter = filter('ID')->equal->value(66.6);
87
+ $this->assertEquals('ID=66.6', $filter->to_string());
88
+ }
89
+ public function testTrueBoolValue()
90
+ {
91
+ $filter = filter('ID')->equal->value(true);
92
+ $this->assertEquals('ID=true', $filter->to_string());
93
+ }
94
+ public function testFalseBoolValue()
95
+ {
96
+ $filter = filter('ID')->equal->value(FALSE);
97
+ $this->assertEquals('ID=false', $filter->to_string());
98
+ }
99
+
100
+ }
lib/antidot/AFS/SEARCH/FILTER/TEST/groupFilterTest.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/FILTER/afs_group_filter.php';
3
+
4
+
5
+ class GroupFilterTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testGroupOneFilter()
8
+ {
9
+ $result = group(filter('ID')->equal->value('value'));
10
+ $this->assertEquals('(ID=value)', $result->to_string());
11
+ }
12
+
13
+ public function testGroupOfCombinedFilters()
14
+ {
15
+ $result = group(filter('ID')->equal->value('value')->and->filter('FOO')->less->value('val'));
16
+ $this->assertEquals('(ID=value and FOO<val)', $result->to_string());
17
+ }
18
+
19
+ public function testCombineGroupAndFilter()
20
+ {
21
+ $result = group(filter('ID')->equal->value('value'))->and->filter('FOO')->less->value('val');
22
+ $this->assertEquals('(ID=value) and FOO<val', $result->to_string());
23
+ }
24
+
25
+ public function testCombineFilterAndGroup()
26
+ {
27
+ $result = filter('ID')->equal->value('value')->and->group(filter('FOO')->less->value('val'));
28
+ $this->assertEquals('ID=value and (FOO<val)', $result->to_string());
29
+ }
30
+
31
+ public function testCombineGroups()
32
+ {
33
+ $result = group(filter('ID')->equal->value('value'))->and->group(filter('FOO')->less->value('val'));
34
+ $this->assertEquals('(ID=value) and (FOO<val)', $result->to_string());
35
+ }
36
+
37
+ public function testGroupOfGroupsAndCombinedFilters()
38
+ {
39
+ $result = group(filter('ID')->equal->value('value')->and->filter('FOO')->equal->value('bar'))
40
+ ->or->group(filter('ID')->equal->value('val')->and->filter('FOO')->equal->value('baz'))
41
+ ->or->filter('YOP')->less->value('bla');
42
+ $this->assertEquals('(ID=value and FOO=bar) or (ID=val and FOO=baz) or YOP<bla', $result->to_string());
43
+ }
44
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_combinable_filter.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/FILTER/afs_combinator_filter.php';
3
+
4
+
5
+ /** @brief Base class for combinable elements.
6
+ *
7
+ * Combinable elements are element which can be combined with specific
8
+ * combinator filter object such as: AfsAndCombinatorFilter and
9
+ * AfsOrCombinatorFilter.
10
+ */
11
+ abstract class AfsCombinableFilter
12
+ {
13
+ /** @brief Creates new combinator filter object initialized with current instance.
14
+ *
15
+ * @param $name [in] Combinator name. Available values are:
16
+ * - @c and: to and-combine elements,
17
+ * - @c or: to or-combine elements.
18
+ *
19
+ * @return Newly created instance of combinator filter type.
20
+ *
21
+ * @exception AfsUnknownCombinatorException required combinator does not exist.
22
+ */
23
+ public function __get($name)
24
+ {
25
+ return AfsCombinatorFactory::create($name, $this);
26
+ }
27
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_combinator_filter.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/FILTER/afs_filter.php';
3
+ require_once 'AFS/SEARCH/FILTER/afs_filter_wrapper.php';
4
+
5
+
6
+ /** @brief Factory class to create combinator filter objects.
7
+ */
8
+ class AfsCombinatorFactory
9
+ {
10
+ /** @brief Creates new combinator filter objects.
11
+ *
12
+ * @param $name [in] Name of the combinator. Allowed names are:
13
+ * - @c and: to and-combine elements,
14
+ * - @c or: to or-combine elements.
15
+ * @param $left [in] AfsFilter or AfsGroupFilter object.
16
+ *
17
+ * @return newly created instance of combinator filter object.
18
+ *
19
+ * @exception AfsUnknownCombinatorException when required combinator does not exist.
20
+ */
21
+ public static function create($name, $left)
22
+ {
23
+ if ('and' == $name)
24
+ return new AfsAndCombinatorFilter($left);
25
+ elseif ('or' == $name)
26
+ return new AfsOrCombinatorFilter($left);
27
+ else
28
+ throw new AfsUnknownCombinatorException($name);
29
+ }
30
+ }
31
+
32
+
33
+ /** @brief Base class for combinator filter objects.
34
+ */
35
+ class AfsBaseCombinatorFilter
36
+ {
37
+ private $left = null;
38
+ private $comb_str = null;
39
+ private $right = null;
40
+
41
+
42
+ /** @brief Constructs new instance of AfsBaseCombinatorFilter.
43
+ *
44
+ * @param $left [in] AfsFilter or AfsGroupFilter object to combine with provided operator.
45
+ * @param $comb_str [in] String representation of the operator (recognized by
46
+ * AFS search engine).
47
+ */
48
+ public function __construct($left, $comb_str)
49
+ {
50
+ $this->left = $left;
51
+ $this->comb_str = $comb_str;
52
+ }
53
+
54
+ /** @brief Creates new filter as the right operand of current combinator.
55
+ * @param $id [in] Filter identifier.
56
+ * @return newly created filter.
57
+ */
58
+ public function filter($id)
59
+ {
60
+ $this->right = new AfsFilterWrapper($this, new AfsFilter($id));
61
+ return $this->right;
62
+ }
63
+
64
+ /** @brief Creates new group as the right operand of current combinator.
65
+ * @param $element [in] Element initialization of the group.
66
+ * @return newly created group.
67
+ */
68
+ public function group($element)
69
+ {
70
+ $this->right = new AfsFilterWrapper($this, new AfsGroupFilter($element));
71
+ return $this->right;
72
+ }
73
+
74
+ /** @brief Retrieves string representation of current instance.
75
+ * @return string representation.
76
+ */
77
+ public function to_string()
78
+ {
79
+ return $this->left->to_string() . ' ' . $this->comb_str . ' ' . $this->right->to_string(true);
80
+ }
81
+ }
82
+
83
+
84
+ /** @brief Combinator class for and combination.
85
+ */
86
+ class AfsAndCombinatorFilter extends AfsBaseCombinatorFilter
87
+ {
88
+ /** @brief Constructs new instance of AfsOrCombinatorFilter.
89
+ * @param $left [in] AfsFilter or AfsGroupFilter used for the combination.
90
+ */
91
+ public function __construct($left)
92
+ {
93
+ parent::__construct($left, 'and');
94
+ }
95
+ }
96
+
97
+ /** @brief Combinator class for or combination.
98
+ */
99
+ class AfsOrCombinatorFilter extends AfsBaseCombinatorFilter
100
+ {
101
+ /** @brief Constructs new instance of AfsOrCombinatorFilter.
102
+ * @param $left [in] AfsFilter or AfsGroupFilter used for the combination.
103
+ */
104
+ public function __construct($left)
105
+ {
106
+ parent::__construct($left, 'or');
107
+ }
108
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_filter.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /** @file afs_filter.php */
3
+
4
+ require_once 'AFS/SEARCH/FILTER/afs_operator_filter.php';
5
+
6
+ /** @brief Helper function to create new AfsFilter instance.
7
+ * @param $id [in] Filter identifier.
8
+ * @return newly created AfsFilter instance.
9
+ */
10
+ function filter($id)
11
+ {
12
+ return new AfsFilterWrapper(null, new AfsFilter($id));
13
+ }
14
+
15
+
16
+ /** @brief Base class used to represent a filter.
17
+ *
18
+ * This class should never be instanced directly. Use filter function instead.
19
+ */
20
+ class AfsFilter
21
+ {
22
+ private $id = null;
23
+
24
+
25
+ /** @brief Constructs new filter instance.
26
+ * @param $id [in] Filter identifier (should be a string).
27
+ */
28
+ public function __construct($id)
29
+ {
30
+ $this->id = $id;
31
+ }
32
+
33
+ /** @brief Create new filter operator object.
34
+ *
35
+ * Valid operators are:
36
+ * - @c equal: equal comparison,
37
+ * - @c not_equal: not equal comparison,
38
+ * - @c less: less than comparison,
39
+ * - @c less_equal: less than or equal comparison,
40
+ * - @c greater: greater than comparison,
41
+ * - @c greater_equal: greater than or equal comparison.
42
+ *
43
+ * @param $name [in] Should be one of the valid operators.
44
+ *
45
+ * @return newly created instance depending on the provided parameter.
46
+ *
47
+ * @exception AfsUnknownOperatorException when invalid operator has been
48
+ * provided.
49
+ */
50
+ public function __get($name)
51
+ {
52
+ return AfsOperatorFactory::create($name, $this);
53
+ }
54
+
55
+ /** @brief Transforms this instance in its string representation.
56
+ * @return string representation of the instance.
57
+ */
58
+ public function to_string()
59
+ {
60
+ return $this->id;
61
+ }
62
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_filter_exception.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @brief Base class for all filter expression construction errors. */
4
+ class AfsFilterException extends Exception
5
+ { }
6
+
7
+ /** @brief Exception raised when invalid operator has been used. */
8
+ class AfsUnknownOperatorException extends AfsFilterException
9
+ {
10
+ /** @brief Constructs new instance with appropriated error message.
11
+ * @param $name [in] Name of the invalid requested operator.
12
+ */
13
+ public function __construct($name)
14
+ {
15
+ parent::__construct('Unknown filter operator: ' . $name);
16
+ }
17
+ }
18
+
19
+ /** @brief Exception raised when invalid combination has been used. */
20
+ class AfsUnknownCombinatorException extends AfsFilterException
21
+ {
22
+ /** @brief Constructs new instance with appropriated error message.
23
+ * @param $name [in] Name of the invalid requested combination.
24
+ */
25
+ public function __construct($name)
26
+ {
27
+ parent::__construct('Unknown filter combinator: ' . $name);
28
+ }
29
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_filter_wrapper.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ /** @brief Wrapper to access right underlying object when building filter expression. */
5
+ class AfsFilterWrapper
6
+ {
7
+ private $parent = null;
8
+ private $obj = null;
9
+
10
+ /** @brief Constructs new wrapped object.
11
+ *
12
+ * @param $parent [in] Parent of the wrapper.
13
+ * @param $obj [in] The wrapped object.
14
+ */
15
+ public function __construct($parent, $obj)
16
+ {
17
+ $this->parent = $parent;
18
+ $this->obj = $obj;
19
+ }
20
+
21
+ /** @brief Wraps all getter methods and properties calls.
22
+ * @param $name [in] Name of the getter/property.
23
+ * @return This wrapper with updated wrapped object.
24
+ * @exception AfsFilterException when invalid getter/property has been requested
25
+ */
26
+ public function __get($name)
27
+ {
28
+ $this->obj = $this->obj->__get($name);
29
+ return $this;
30
+ }
31
+
32
+ /** @brief Wraps all setter methods and properties calls.
33
+ *
34
+ * @param $name [in] Name of the setter/property.
35
+ * @param $params [in] Parameter forwarded to the setter/property/
36
+ *
37
+ * @return This wrapper with updated wrapped object.
38
+ *
39
+ * @exception AfsFilterException when invalid setter/property has been requested
40
+ */
41
+ public function __set($name, $params)
42
+ {
43
+ $this->obj = $this->obj->__set($name, $params);
44
+ return $this;
45
+ }
46
+
47
+ /** @brief Wraps all methods calls.
48
+ *
49
+ * Only first method parameter is forwarded to corresponding method of the
50
+ * wrapped object.
51
+ *
52
+ * @param $name [in] Name of the method.
53
+ * @param $params [in] Array of parameters (only the first one is used!).
54
+ *
55
+ * @return This wrapper with updated wrapped object.
56
+ *
57
+ * @exception AfsFilterException when invalid method has been requested.
58
+ */
59
+ public function __call($name, $params)
60
+ {
61
+ // Fortunatelly, the methods accept single parameter!
62
+ $this->obj = $this->obj->$name($params[0]);
63
+ return $this;
64
+ }
65
+
66
+ /** @brief Retrieves string representation.
67
+ *
68
+ * @param $serialize_wrapped_object [in] When set to @c true, output
69
+ * corresponds to the string representation of the wrapped object.
70
+ * Otherwise, @c false (default), output corresponds to the string
71
+ * representation of the parent element.
72
+ *
73
+ * @return string representation of the parent or the wrapped object.
74
+ */
75
+ public function to_string($serialize_wrapped_object=false)
76
+ {
77
+ if ($serialize_wrapped_object || is_null($this->parent))
78
+ return $this->obj->to_string();
79
+ else
80
+ return $this->parent->to_string();
81
+ }
82
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_group_filter.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /** @file afs_group_filter.php */
3
+ require_once 'AFS/SEARCH/FILTER/afs_combinable_filter.php';
4
+
5
+
6
+ /** @brief Helper function to create new AfsGroup instance.
7
+ * @param $filter_expr [in] One filter or combined and/or grouped filters.
8
+ * @return newly created AfsGroup instance.
9
+ */
10
+ function group($filter_expr)
11
+ {
12
+ return new AfsGroupFilter($filter_expr);
13
+ }
14
+
15
+
16
+ /** @brief Class used to group filter expressions.
17
+ *
18
+ * Example:
19
+ * @code GROUP(filter_1 AND filter_2) OR filter_1 @endcode
20
+ */
21
+ class AfsGroupFilter extends AfsCombinableFilter
22
+ {
23
+ private $filter_expr = null;
24
+
25
+
26
+ /** @brief Constructs new group instance.
27
+ * @param $filter_expr [in] Valid filter expression.
28
+ */
29
+ public function __construct($filter_expr)
30
+ {
31
+ $this->filter_expr = $filter_expr;
32
+ }
33
+
34
+ /** @brief Transforms this instance in its string representation.
35
+ * @return string representation of the instance.
36
+ */
37
+ public function to_string()
38
+ {
39
+ return '(' . $this->filter_expr->to_string() . ')';
40
+ }
41
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_operator_filter.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/FILTER/afs_valued_filter.php';
3
+ require_once 'AFS/SEARCH/FILTER/afs_filter_exception.php';
4
+
5
+
6
+ /** @brief Factory class to create operator filter objects.
7
+ */
8
+ class AfsOperatorFactory
9
+ {
10
+ /** @brief Creates new operator filter objects
11
+ *
12
+ * @param $name [in] Name of the operator. Allowed names are:
13
+ * - @c equal: equal comparison,
14
+ * - @c not_equal: not equal comparison,
15
+ * - @c less: less than comparison,
16
+ * - @c less_equal: less than or equal comparison,
17
+ * - @c greater: greater than comparison,
18
+ * - @c greater_equal: greater than or equal comparison.
19
+ * @param $filter [in] AfsFilter object.
20
+ *
21
+ * @return newly created instance of operator filter object.
22
+ *
23
+ * @exception AfsUnknownOperatorException when required operator does not exist.
24
+ */
25
+ public static function create($name, AfsFilter $filter)
26
+ {
27
+ if ('equal' == $name)
28
+ return new AfsEqualOperatorFilter($filter);
29
+ elseif ('not_equal' == $name)
30
+ return new AfsNotEqualOperatorFilter($filter);
31
+ elseif ('less' == $name)
32
+ return new AfsLessOperatorFilter($filter);
33
+ elseif ('less_equal' == $name)
34
+ return new AfsLessEqualOperatorFilter($filter);
35
+ elseif ('greater' == $name)
36
+ return new AfsGreaterOperatorFilter($filter);
37
+ elseif ('greater_equal' == $name)
38
+ return new AfsGreaterEqualOperatorFilter($filter);
39
+ else
40
+ throw new AfsUnknownOperatorException($name);
41
+ }
42
+ }
43
+
44
+
45
+ /** @brief Base class for operator filter objects.
46
+ */
47
+ abstract class AfsBaseOperatorFilter
48
+ {
49
+ private $filter = null;
50
+ private $op_str = null;
51
+
52
+
53
+ /** @brief Constructs new instance of AfsBaseOperatorFilter
54
+ *
55
+ * @param $filter [in] AfsFilter object to combine with provided operator.
56
+ * @param $op_str [in] String representation of the operator (recognized by
57
+ * AFS search engine).
58
+ */
59
+ public function __construct(AfsFilter $filter, $op_str)
60
+ {
61
+ $this->filter = $filter;
62
+ $this->op_str = $op_str;
63
+ }
64
+
65
+ /** @brief Associates a value to current instance and create an AfsValuedFilter.
66
+ * @param $value [in] Value to assign to AfsValuedFilter.
67
+ * @return newly created AfsValuedFilter instance.
68
+ */
69
+ public function value($value)
70
+ {
71
+ if (is_bool($value))
72
+ $value = ($value) ? 'true' : 'false';
73
+ return new AfsValuedFilter($this, $value);
74
+ }
75
+
76
+ /** @brief Transforms this instance in its string representation.
77
+ * @return string representation of the instance.
78
+ */
79
+ public function to_string()
80
+ {
81
+ return $this->filter->to_string() . $this->op_str;
82
+ }
83
+ }
84
+
85
+
86
+ /** @brief Operator class for equality comparison.
87
+ */
88
+ class AfsEqualOperatorFilter extends AfsBaseOperatorFilter
89
+ {
90
+ /** @brief Constructs new instance of AfsEqualOperatorFilter.
91
+ * @param $filter [in] AfsFilter used for equal comparison.
92
+ */
93
+ public function __construct(AfsFilter $filter)
94
+ {
95
+ parent::__construct($filter, '=');
96
+ }
97
+ }
98
+
99
+ /** @brief Operator class for inequality comparison.
100
+ */
101
+ class AfsNotEqualOperatorFilter extends AfsBaseOperatorFilter
102
+ {
103
+ /** @brief Constructs new instance of AfsNotEqualOperatorFilter.
104
+ * @param $filter [in] AfsFilter used for not equal comparison.
105
+ */
106
+ public function __construct(AfsFilter $filter)
107
+ {
108
+ parent::__construct($filter, '!=');
109
+ }
110
+ }
111
+
112
+ /** @brief Operator class for less than comparison.
113
+ */
114
+ class AfsLessOperatorFilter extends AfsBaseOperatorFilter
115
+ {
116
+ /** @brief Constructs new instance of AfsLessOperatorFilter.
117
+ * @param $filter [in] AfsFilter used for less than comparison.
118
+ */
119
+ public function __construct(AfsFilter $filter)
120
+ {
121
+ parent::__construct($filter, '<');
122
+ }
123
+ }
124
+
125
+ /** @brief Operator class for less than or equal to comparison.
126
+ */
127
+ class AfsLessEqualOperatorFilter extends AfsBaseOperatorFilter
128
+ {
129
+ /** @brief Constructs new instance of AfsLessEqualOperatorFilter.
130
+ * @param $filter [in] AfsFilter used for less than or equal to comparison.
131
+ */
132
+ public function __construct(AfsFilter $filter)
133
+ {
134
+ parent::__construct($filter, '<=');
135
+ }
136
+ }
137
+
138
+ /** @brief Operator class for greater than comparison.
139
+ */
140
+ class AfsGreaterOperatorFilter extends AfsBaseOperatorFilter
141
+ {
142
+ /** @brief Constructs new instance of AfsGreaterOperatorFilter.
143
+ * @param $filter [in] AfsFilter used for greater than comparison.
144
+ */
145
+ public function __construct(AfsFilter $filter)
146
+ {
147
+ parent::__construct($filter, '>');
148
+ }
149
+ }
150
+
151
+ /** @brief Operator class for greater than or equal to comparison.
152
+ */
153
+ class AfsGreaterEqualOperatorFilter extends AfsBaseOperatorFilter
154
+ {
155
+ /** @brief Constructs new instance of AfsGreaterEqualOperatorFilter.
156
+ * @param $filter [in] AfsFilter used for greater than or equal to comparison.
157
+ */
158
+ public function __construct(AfsFilter $filter)
159
+ {
160
+ parent::__construct($filter, '>=');
161
+ }
162
+ }
lib/antidot/AFS/SEARCH/FILTER/afs_valued_filter.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/FILTER/afs_combinable_filter.php';
3
+
4
+
5
+ /** @brief Filter with filter value.
6
+ *
7
+ * This object is made of facet identifier, filter operator and a value.
8
+ */
9
+ class AfsValuedFilter extends AfsCombinableFilter
10
+ {
11
+ private $op_filter = null;
12
+ private $value = null;
13
+
14
+
15
+ /** @brief Constructs new valued filter object.
16
+ *
17
+ * @param $op_filter [in] Filter operator object (see AfsOperatorFactory).
18
+ * @param $value [in] The value to consider (to filter on when operator is
19
+ * set to equal).
20
+ */
21
+ public function __construct(AfsBaseOperatorFilter $op_filter, $value)
22
+ {
23
+ $this->op_filter = $op_filter;
24
+ $this->value = $value;
25
+ }
26
+
27
+ /** @brief Transforms this instance in its string representation.
28
+ * @return string representation of the instance.
29
+ */
30
+ public function to_string()
31
+ {
32
+ return $this->op_filter->to_string() . $this->value;
33
+ }
34
+ }
lib/antidot/AFS/SEARCH/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2014
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/../..
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/SEARCH/TEST/Makefile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ #*******************************************************************************
2
+ #
3
+ # AFS © Antidot 2013
4
+ #
5
+ #*******************************************************************************
6
+ ROOT_PATH=$(CURDIR)/../../../
7
+
8
+ -include $(ROOT_PATH)/rules.mk
lib/antidot/AFS/SEARCH/TEST/clientDataHelperTest.php ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_client_data_helper.php";
3
+
4
+ class ClientDataHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testRetrieveXMLClientDataAsText()
7
+ {
8
+ $input = json_decode('{
9
+ "clientData": [
10
+ {
11
+ "contents": "<clientdata><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>",
12
+ "id": "main",
13
+ "mimeType": "text/xml"
14
+ }
15
+ ]
16
+ }');
17
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
18
+ $this->assertEquals($helper->value, '<clientdata><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>');
19
+ $this->assertEquals($helper->mime_type, 'application/xml');
20
+ }
21
+
22
+ public function testRetrieveSpecificDataFromXMLClientData()
23
+ {
24
+ $input = json_decode('{
25
+ "clientData": [
26
+ {
27
+ "contents": "<clientdata><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>",
28
+ "id": "main",
29
+ "mimeType": "text/xml"
30
+ }
31
+ ]
32
+ }');
33
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
34
+ $this->assertEquals($helper->get_value('/clientdata/data/data1'), 'data 0');
35
+ $this->assertEquals($helper->get_value('/clientdata/data/data1[2]'), 'data 1');
36
+ $this->assertEquals(array('data 0', 'data 1'), $helper->get_values('/clientdata/data/data1'));
37
+ }
38
+
39
+ public function testRetrieveSpecificDataFromXMLClientDataWithNamedNamespace()
40
+ {
41
+ $input = json_decode('{
42
+ "clientData": [
43
+ {
44
+ "contents": "<clientdata xmlns:foo=\"http://bar\"><foo:data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></foo:data></clientdata>",
45
+ "id": "main",
46
+ "mimeType": "text/xml"
47
+ }
48
+ ]
49
+ }');
50
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
51
+ $this->assertEquals('data 0', $helper->get_value('/clientdata/boo:data/data1', array('boo' => 'http://bar')));
52
+ $this->assertEquals('data 1', $helper->get_value('/clientdata/boo:data/data1[2]', array('boo' => 'http://bar')));
53
+ $this->assertEquals(array('data 0', 'data 1'), $helper->get_values('/clientdata/boo:data/data1', array('boo' => 'http://bar')));
54
+ }
55
+
56
+ public function testRetrieveSpecificDataFromXMLClientDataWithDefaultNamespace()
57
+ {
58
+ $input = json_decode('{
59
+ "clientData": [
60
+ {
61
+ "contents": "<clientdata xmlns=\"http://bar\"><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>",
62
+ "id": "main",
63
+ "mimeType": "text/xml"
64
+ }
65
+ ]
66
+ }');
67
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
68
+ $this->assertEquals('data 0', $helper->get_value('/boo:clientdata/boo:data/boo:data1', array('boo' => 'http://bar')));
69
+ $this->assertEquals('data 1', $helper->get_value('/boo:clientdata/boo:data/boo:data1[2]', array('boo' => 'http://bar')));
70
+ $this->assertEquals(array('data 0', 'data 1'), $helper->get_values('/boo:clientdata/boo:data/boo:data1', array('boo' => 'http://bar')));
71
+ }
72
+
73
+ public function testRetrieveSpecificDataFromXMLClientDataWithAfsNamespace()
74
+ {
75
+ $input = json_decode('{
76
+ "clientData": [
77
+ {
78
+ "contents": "<clientdata xmlns=\"http://ref.antidot.net/v7/afs#\"><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>",
79
+ "id": "main",
80
+ "mimeType": "text/xml"
81
+ }
82
+ ]
83
+ }');
84
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
85
+ $this->assertEquals('data 0', $helper->get_value('/afs:clientdata/afs:data/afs:data1'));
86
+ }
87
+
88
+ public function testInvalidXpathForXmlClientDataRetrieval()
89
+ {
90
+ $input = json_decode('{
91
+ "clientData": [
92
+ {
93
+ "contents": "<clientdata><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>",
94
+ "id": "main",
95
+ "mimeType": "text/xml"
96
+ }
97
+ ]
98
+ }');
99
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
100
+ try {
101
+ $helper->get_value('/clientdata/data/foo');
102
+ $this->fail('XPath with no reply should have raised exception');
103
+ } catch (AfsClientDataException $e) { }
104
+ }
105
+
106
+ public function testEmptyResultXpathForXmlClientDataRetrieval()
107
+ {
108
+ $input = json_decode('{
109
+ "clientData": [
110
+ {
111
+ "contents": "<clientdata><data></data></clientdata>",
112
+ "id": "main",
113
+ "mimeType": "text/xml"
114
+ }
115
+ ]
116
+ }');
117
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
118
+ $this->assertEquals('', $helper->get_value('/clientdata/data'));
119
+ }
120
+
121
+ public function testRetrieveXmlClientDataWithHighlight()
122
+ {
123
+ $input = json_decode('{
124
+ "clientData": [
125
+ {
126
+ "contents": "<clientdata><data><data1>data <afs:match>0</afs:match></data1><data1>data <afs:match>1</afs:match> foo</data1></data></clientdata>",
127
+ "id": "foo",
128
+ "mimeType": "text/xml"
129
+ }
130
+ ]
131
+ }');
132
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
133
+ $this->assertEquals($helper->get_value('/clientdata/data/data1[1]'), 'data <b>0</b>');
134
+ $this->assertEquals($helper->get_value('/clientdata/data/data1[2]'), 'data <b>1</b> foo');
135
+ $this->assertEquals(array('data <b>0</b>', 'data <b>1</b> foo'), $helper->get_values('/clientdata/data/data1'));
136
+ }
137
+
138
+ public function testRetrieveXmlClientDataWithTruncatedText()
139
+ {
140
+ $input = json_decode('{
141
+ "clientData": [
142
+ {
143
+ "contents": "<clientdata><data><data1>data 0<afs:trunc/></data1><data1>data 1 foo<afs:trunc/></data1></data></clientdata>",
144
+ "id": "foo",
145
+ "mimeType": "text/xml"
146
+ }
147
+ ]
148
+ }');
149
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
150
+ $this->assertEquals($helper->get_value('/clientdata/data/data1[1]'), 'data 0...');
151
+ $this->assertEquals($helper->get_value('/clientdata/data/data1[2]'), 'data 1 foo...');
152
+ $this->assertEquals(array('data 0...', 'data 1 foo...'), $helper->get_values('/clientdata/data/data1'));
153
+ }
154
+
155
+ public function testRetrieveJSONDataAsText()
156
+ {
157
+ $input = json_decode('{
158
+ "clientData": [
159
+ {
160
+ "contents": { "data": [ { "data1": [ { "afs:t": "KwicString", "text": "data 0" } ] }, { "data1": [ { "afs:t": "KwicString", "text": "data " }, { "afs:t": "KwicMatch", "match": "1" } ] } ] },
161
+ "id": "id1",
162
+ "mimeType": "application/json"
163
+ }
164
+ ]
165
+ }');
166
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
167
+ $this->assertEquals($helper->value, '{"data":[{"data1":[{"afs:t":"KwicString","text":"data 0"}]},{"data1":[{"afs:t":"KwicString","text":"data "},{"afs:t":"KwicMatch","match":"1"}]}]}');
168
+ $this->assertEquals($helper->mime_type, 'application/json');
169
+ }
170
+
171
+ public function testRetrieveSimpleJSONDataAsText()
172
+ {
173
+ $input = json_decode('{
174
+ "clientData": [
175
+ {
176
+ "contents": [ { "afs:t": "KwicString", "text": "data 1" } ],
177
+ "id": "id1",
178
+ "mimeType": "application/json"
179
+ }
180
+ ]
181
+ }');
182
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
183
+ $this->assertEquals($helper->get_value(''), 'data 1');
184
+ }
185
+
186
+ public function testRetrieveJSONDataAsTextWithHighlight()
187
+ {
188
+ $input = json_decode('{
189
+ "clientData": [
190
+ {
191
+ "contents": [ { "afs:t": "KwicString", "text": "data " }, { "afs:t": "KwicMatch", "match": "1" } ],
192
+ "id": "id1",
193
+ "mimeType": "application/json"
194
+ }
195
+ ]
196
+ }');
197
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
198
+ $this->assertEquals($helper->get_value(''), 'data <b>1</b>');
199
+ }
200
+
201
+ public function testRetrieveSpecificJSONDataAsTextWithHighlight()
202
+ {
203
+ $input = json_decode('{
204
+ "clientData": [
205
+ {
206
+ "contents": { "foo": [ { "afs:t": "KwicString", "text": "data " },
207
+ { "afs:t": "KwicMatch", "match": "1" } ],
208
+ "bar": [ { "afs:t": "KwicString", "text": "baz " },
209
+ { "afs:t": "KwicMatch", "match": "42" },
210
+ { "afs:t": "KwicString", "text": " bat" } ] },
211
+ "id": "id1",
212
+ "mimeType": "application/json"
213
+ }
214
+ ]
215
+ }');
216
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
217
+ $this->assertEquals($helper->get_value('bar'), 'baz <b>42</b> bat');
218
+ }
219
+
220
+ public function testRetrieveUnknownSpecificJSONDataAsTextWithHighlight()
221
+ {
222
+ $input = json_decode('{
223
+ "clientData": [
224
+ {
225
+ "contents": { "foo": [ { "afs:t": "KwicString", "text": "data " },
226
+ { "afs:t": "KwicMatch", "match": "1" } ] },
227
+ "id": "id1",
228
+ "mimeType": "application/json"
229
+ }
230
+ ]
231
+ }');
232
+ $helper = AfsClientDataHelperFactory::create($input->clientData[0]);
233
+ try {
234
+ $helper->get_value('bar');
235
+ $this->fail('Unknown JSON element should have rosen exception');
236
+ } catch (AfsClientDataException $e) { }
237
+ }
238
+
239
+
240
+ public function testClientDataManagerDirectClientDataAccess()
241
+ {
242
+ $input = json_decode('[
243
+ {
244
+ "contents": { "foo": [ { "afs:t": "KwicString", "text": "data " },
245
+ { "afs:t": "KwicMatch", "match": "1" } ],
246
+ "bar": [ { "afs:t": "KwicString", "text": "baz " },
247
+ { "afs:t": "KwicMatch", "match": "42" },
248
+ { "afs:t": "KwicString", "text": " bat" } ] },
249
+ "id": "id1",
250
+ "mimeType": "application/json"
251
+ },
252
+ {
253
+ "contents": "<clientdata><data><data1>data <afs:match>0</afs:match></data1><data1>data <afs:match>1</afs:match> foo</data1></data></clientdata>",
254
+ "id": "foo",
255
+ "mimeType": "text/xml"
256
+ }
257
+ ]');
258
+ $mgr = new AfsClientDataManager($input);
259
+ $this->assertEquals($mgr->get_value('id1', 'bar'), 'baz <b>42</b> bat');
260
+ $this->assertEquals($mgr->get_value('foo', '/clientdata/data/data1[1]'), 'data <b>0</b>');
261
+ $this->assertEquals($mgr->get_value('foo', '/clientdata/data/data1[2]'), 'data <b>1</b> foo');
262
+ $this->assertEquals(array('data <b>0</b>', 'data <b>1</b> foo'), $mgr->get_values('foo', '/clientdata/data/data1'));
263
+ }
264
+
265
+ public function testClientDataManagerFirstRetrieveClientDataHelpers()
266
+ {
267
+ $input = json_decode('[
268
+ {
269
+ "contents": { "foo": [ { "afs:t": "KwicString", "text": "data " },
270
+ { "afs:t": "KwicMatch", "match": "1" } ],
271
+ "bar": [ { "afs:t": "KwicString", "text": "baz " },
272
+ { "afs:t": "KwicMatch", "match": "42" },
273
+ { "afs:t": "KwicString", "text": " bat" } ] },
274
+ "id": "id1",
275
+ "mimeType": "application/json"
276
+ },
277
+ {
278
+ "contents": "<clientdata><data><data1>data <afs:match>0</afs:match></data1><data1>data <afs:match>1</afs:match> foo</data1></data></clientdata>",
279
+ "id": "foo",
280
+ "mimeType": "text/xml"
281
+ }
282
+ ]');
283
+ $mgr = new AfsClientDataManager($input);
284
+ $data1 = $mgr->get_clientdata('id1');
285
+ $this->assertEquals('data <b>1</b>', $data1->get_value('foo'));
286
+
287
+ $data2 = $mgr->get_clientdata('foo');
288
+ $this->assertEquals('data <b>0</b>', $data2->get_value('/clientdata/data/data1[1]'));
289
+ }
290
+
291
+ public function testRawXmlClientDataWithNamespace()
292
+ {
293
+ $input = json_decode('[
294
+ {
295
+ "contents": "<clientdata><data><data1>data <afs:match>0</afs:match></data1><data1>data <afs:match>1</afs:match> foo</data1></data></clientdata>",
296
+ "id": "foo",
297
+ "mimeType": "text/xml"
298
+ }
299
+ ]');
300
+ $mgr = new AfsClientDataManager($input);
301
+ $data = $mgr->get_clientdata('foo')->get_value();
302
+ $doc = new DOMDocument();
303
+ $doc->loadXML($data);
304
+ }
305
+ }
lib/antidot/AFS/SEARCH/TEST/clusterHelperTest.php ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+
3
+ require_once 'AFS/SEARCH/afs_cluster_helper.php';
4
+ require_once 'AFS/SEARCH/afs_facet_helper.php';
5
+ require_once 'AFS/SEARCH/afs_meta_helper.php';
6
+ require_once 'AFS/SEARCH/afs_query.php';
7
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
8
+
9
+ class ClusterHelperTest extends PHPUnit_Framework_TestCase
10
+ {
11
+ public function testCluster()
12
+ {
13
+ $facet_id = 'marketing';
14
+ $facet_label = 'Facet label';
15
+ $value_id = 'OP';
16
+ $value_label = 'Youhou';
17
+ $query = new AfsQuery();
18
+
19
+ $input = json_decode('{
20
+ "afs:t": "FacetTree",
21
+ "node": [ {
22
+ "key": "' . $value_id .'",
23
+ "labels": [ { "label": "' . $value_label .'" } ],
24
+ "items": 67
25
+ } ],
26
+ "layout": "TREE",
27
+ "type": "BOOL",
28
+ "id": "' . $facet_id .'",
29
+ "labels": [ { "label": "' . $facet_label .'" } ]
30
+ }');
31
+
32
+ $config = new AfsHelperConfiguration();
33
+ $facet_helper = new AfsFacetHelper($input, $query, $config);
34
+
35
+ $input = json_decode('{
36
+ "uri": "Catalog",
37
+ "totalItems": 61,
38
+ "totalItemsIsExact": true,
39
+ "pageItems": 20,
40
+ "firstPageItem": 1,
41
+ "lastPageItem": 20,
42
+ "durationMs": 6,
43
+ "cluster": "' . $facet_id . '",
44
+ "firstPaFId": 1,
45
+ "lastPaFId": 1,
46
+ "producer": "SEARCH",
47
+ "totalItemsInClusters": 2,
48
+ "nbClusters": 2
49
+ }');
50
+ $meta = new AfsMetaHelper($input);
51
+
52
+ $input = json_decode('{
53
+ "id": "' . $value_id .'",
54
+ "totalItems": 6,
55
+ "totalItemsIsExact": true,
56
+ "pageItems": 1,
57
+ "firstPageItem": 1,
58
+ "lastPageItem": 1,
59
+ "reply": [
60
+ {
61
+ "docId": 64,
62
+ "uri": "166_en",
63
+ "title": [
64
+ {
65
+ "afs:t": "KwicString",
66
+ "text": "HTC Touch Diamond"
67
+ }
68
+ ],
69
+ "relevance": { "rank": 1 }
70
+ }
71
+ ]
72
+ }');
73
+
74
+ $helper = new AfsClusterHelper($input, $meta, $facet_helper, $query, $config);
75
+ $this->assertEquals($value_id, $helper->get_id());
76
+ $this->assertEquals($value_label, $helper->get_label());
77
+ $this->assertEquals(6, $helper->get_total_replies());
78
+
79
+ $this->assertTrue($helper->has_reply());
80
+ $this->assertEquals(1, $helper->get_nb_replies());
81
+
82
+ $query = $helper->get_query();
83
+ $this->assertTrue($query->has_filter($facet_id, $value_id));
84
+ }
85
+
86
+ public function testClusterWithoutFacet()
87
+ {
88
+ $facet_id = 'marketing';
89
+ $value_id = 'OP';
90
+ $query = new AfsQuery();
91
+
92
+ $input = json_decode('{
93
+ "uri": "Catalog",
94
+ "totalItems": 61,
95
+ "totalItemsIsExact": true,
96
+ "pageItems": 20,
97
+ "firstPageItem": 1,
98
+ "lastPageItem": 20,
99
+ "durationMs": 6,
100
+ "cluster": "marketing",
101
+ "firstPaFId": 1,
102
+ "lastPaFId": 1,
103
+ "producer": "SEARCH",
104
+ "totalItemsInClusters": 2,
105
+ "nbClusters": 2
106
+ }');
107
+ $meta = new AfsMetaHelper($input);
108
+
109
+ $input = json_decode('{
110
+ "id": "' . $value_id .'",
111
+ "totalItems": 6,
112
+ "totalItemsIsExact": true,
113
+ "pageItems": 1,
114
+ "firstPageItem": 1,
115
+ "lastPageItem": 1,
116
+ "reply": [
117
+ {
118
+ "docId": 64,
119
+ "uri": "166_en",
120
+ "title": [
121
+ {
122
+ "afs:t": "KwicString",
123
+ "text": "HTC Touch Diamond"
124
+ }
125
+ ],
126
+ "relevance": { "rank": 1 }
127
+ }
128
+ ]
129
+ }');
130
+
131
+ $config = new AfsHelperConfiguration();
132
+ $helper = new AfsClusterHelper($input, $meta, null, $query, $config);
133
+ $this->assertEquals($value_id, $helper->get_id());
134
+ $this->assertEquals($value_id, $helper->get_label());
135
+ $this->assertEquals(6, $helper->get_total_replies());
136
+
137
+ $this->assertTrue($helper->has_reply());
138
+ $this->assertEquals(1, $helper->get_nb_replies());
139
+ $replies = $helper->get_replies();
140
+ $this->assertEquals(1, count($replies));
141
+ $reply = $replies[0];
142
+ $this->assertEquals('166_en', $reply->get_uri());
143
+
144
+ $query = $helper->get_query();
145
+ $this->assertTrue($query->has_filter($facet_id, $value_id));
146
+ }
147
+
148
+ public function testClusterAsArray()
149
+ {
150
+ $facet_id = 'marketing';
151
+ $facet_label = 'Facet label';
152
+ $value_id = 'OP';
153
+ $value_label = 'Youhou';
154
+ $query = new AfsQuery();
155
+
156
+ $input = json_decode('{
157
+ "afs:t": "FacetTree",
158
+ "node": [ {
159
+ "key": "' . $value_id .'",
160
+ "labels": [ { "label": "' . $value_label .'" } ],
161
+ "items": 67
162
+ } ],
163
+ "layout": "TREE",
164
+ "type": "BOOL",
165
+ "id": "' . $facet_id .'",
166
+ "labels": [ { "label": "' . $facet_label .'" } ]
167
+ }');
168
+
169
+ $config = new AfsHelperConfiguration();
170
+ $facet_helper = new AfsFacetHelper($input, $query, $config);
171
+
172
+ $input = json_decode('{
173
+ "uri": "Catalog",
174
+ "totalItems": 61,
175
+ "totalItemsIsExact": true,
176
+ "pageItems": 20,
177
+ "firstPageItem": 1,
178
+ "lastPageItem": 20,
179
+ "durationMs": 6,
180
+ "cluster": "' . $facet_id . '",
181
+ "firstPaFId": 1,
182
+ "lastPaFId": 1,
183
+ "producer": "SEARCH",
184
+ "totalItemsInClusters": 2,
185
+ "nbClusters": 2
186
+ }');
187
+ $meta = new AfsMetaHelper($input);
188
+
189
+ $input = json_decode('{
190
+ "id": "' . $value_id .'",
191
+ "totalItems": 6,
192
+ "totalItemsIsExact": true,
193
+ "pageItems": 1,
194
+ "firstPageItem": 1,
195
+ "lastPageItem": 1,
196
+ "reply": [
197
+ {
198
+ "docId": 64,
199
+ "uri": "166_en",
200
+ "title": [
201
+ {
202
+ "afs:t": "KwicString",
203
+ "text": "HTC Touch Diamond"
204
+ }
205
+ ],
206
+ "relevance": { "rank": 1 }
207
+ }
208
+ ]
209
+ }');
210
+
211
+ $helper = new AfsClusterHelper($input, $meta, $facet_helper, $query, $config);
212
+ $result = $helper->format();
213
+
214
+ $this->assertEquals($value_id, $result['id']);
215
+ $this->assertEquals($value_label, $result['label']);
216
+ $this->assertEquals(6, $result['total_replies']);
217
+ # No query coder provided --> no link
218
+ $this->assertEquals('', $result['link']);
219
+
220
+ $replies = $result['replies'];
221
+ $this->assertFalse(empty($replies));
222
+ $this->assertEquals(1, count($replies));
223
+ $this->assertEquals('166_en', $replies[0]['uri']);
224
+ }
225
+ }
lib/antidot/AFS/SEARCH/TEST/conceptHelperTest.php ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_concept_helper.php";
3
+
4
+ class ConceptHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testWithDefaultConceptName()
7
+ {
8
+ $input = json_decode('{
9
+ "meta": {
10
+ "uri": "concept",
11
+ "totalItems": 1,
12
+ "totalItemsIsExact": true,
13
+ "pageItems": 1,
14
+ "firstPageItem": 1,
15
+ "lastPageItem": 1,
16
+ "durationMs": 0,
17
+ "firstPaFId": 1,
18
+ "lastPaFId": 1,
19
+ "producer": "CONCEPT"
20
+ },
21
+ "content": {
22
+ "reply": [
23
+ {
24
+ "docId": 1,
25
+ "uri": "concept",
26
+ "concept": {
27
+ "query": {
28
+ "items": [
29
+ {
30
+ "afs:t": "QueryMatch",
31
+ "text": "mariage",
32
+ "uri": [ "lnf:taxo#QI-thm2009862" ]
33
+ },
34
+ {
35
+ "afs:t": "QueryText",
36
+ "text": " et "
37
+ },
38
+ {
39
+ "afs:t": "QueryMatch",
40
+ "text": "divorce",
41
+ "uri": [ "lnf:taxo#QI-thm1344998" ]
42
+ }
43
+ ]
44
+ },
45
+ "concepts": {
46
+ "concept": [
47
+ {
48
+ "uri": "lnf:taxo#QI-thm2009862",
49
+ "contents": "foo"
50
+ },
51
+ {
52
+ "uri": "lnf:taxo#QI-thm1344998",
53
+ "contents": "bar"
54
+ }
55
+ ]
56
+ }
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ }');
62
+ $mgr = new AfsConceptManager();
63
+ $mgr->add_concept($input);
64
+
65
+ $helper = $mgr->get_concept();
66
+ // is equivalent to
67
+ $helper = $mgr->get_concept('concept');
68
+ try {
69
+ $mgr->get_concept('Unknown');
70
+ $this->fail('Should have raised exception on unknown feed');
71
+ } catch (OutOfBoundsException $e) { }
72
+ $items = $helper->get_items();
73
+ $this->assertEquals(3, count($items));
74
+
75
+ $key_value = each($items);
76
+ $this->assertEquals('mariage', $key_value['value']->get_text());
77
+ $this->assertTrue($key_value['value']->has_concept());
78
+ $data = $key_value['value']->get_data();
79
+ $item = each($data);
80
+ $this->assertEquals('foo', $item['value']);
81
+ $this->assertEquals('lnf:taxo#QI-thm2009862', $item['key']);
82
+
83
+ $key_value = each($items);
84
+ $this->assertEquals(' et ', $key_value['value']->get_text());
85
+ $this->assertFalse($key_value['value']->has_concept());
86
+
87
+ $key_value = each($items);
88
+ $this->assertEquals('divorce', $key_value['value']->get_text());
89
+ $this->assertTrue($key_value['value']->has_concept());
90
+ $data = $key_value['value']->get_data();
91
+ $item = each($data);
92
+ $this->assertEquals('bar', $item['value']);
93
+ $this->assertEquals('lnf:taxo#QI-thm1344998', $item['key']);
94
+ }
95
+
96
+ public function testWithSpecificConceptName()
97
+ {
98
+ $input = json_decode('{
99
+ "meta": {
100
+ "uri": "Specific",
101
+ "totalItems": 1,
102
+ "totalItemsIsExact": true,
103
+ "pageItems": 1,
104
+ "firstPageItem": 1,
105
+ "lastPageItem": 1,
106
+ "durationMs": 0,
107
+ "firstPaFId": 1,
108
+ "lastPaFId": 1,
109
+ "producer": "CONCEPT"
110
+ },
111
+ "content": {
112
+ "reply": [
113
+ {
114
+ "docId": 1,
115
+ "uri": "concept",
116
+ "concept": {
117
+ "query": {
118
+ "items": [
119
+ {
120
+ "afs:t": "QueryMatch",
121
+ "text": "mariage",
122
+ "uri": [ "lnf:taxo#QI-thm2009862" ]
123
+ }
124
+ ]
125
+ },
126
+ "concepts": {
127
+ "concept": [
128
+ {
129
+ "uri": "lnf:taxo#QI-thm2009862",
130
+ "contents": "foo"
131
+ }
132
+ ]
133
+ }
134
+ }
135
+ }
136
+ ]
137
+ }
138
+ }');
139
+ $mgr = new AfsConceptManager();
140
+ $mgr->add_concept($input);
141
+
142
+ $helper = $mgr->get_concept();
143
+ // is equivalent to
144
+ $helper = $mgr->get_concept('Specific');
145
+ try {
146
+ $mgr->get_concept(AFS_DEFAULT_CONCEPT);
147
+ $this->fail('Should have raised exception on default concept name');
148
+ } catch (OutOfBoundsException $e) { }
149
+ $items = $helper->get_items();
150
+ $this->assertEquals(1, count($items));
151
+
152
+ $key_value = each($items);
153
+ $this->assertEquals('mariage', $key_value['value']->get_text());
154
+ $this->assertTrue($key_value['value']->has_concept());
155
+ $data = $key_value['value']->get_data();
156
+ $item = each($data);
157
+ $this->assertEquals('foo', $item['value']);
158
+ $this->assertEquals('lnf:taxo#QI-thm2009862', $item['key']);
159
+ }
160
+
161
+ public function testWithMultipleConcepts()
162
+ {
163
+ $input1 = json_decode('{
164
+ "meta": {
165
+ "uri": "Specific",
166
+ "totalItems": 1,
167
+ "totalItemsIsExact": true,
168
+ "pageItems": 1,
169
+ "firstPageItem": 1,
170
+ "lastPageItem": 1,
171
+ "durationMs": 0,
172
+ "firstPaFId": 1,
173
+ "lastPaFId": 1,
174
+ "producer": "CONCEPT"
175
+ },
176
+ "content": {
177
+ "reply": [
178
+ {
179
+ "docId": 1,
180
+ "uri": "concept",
181
+ "concept": {
182
+ "query": {
183
+ "items": [
184
+ {
185
+ "afs:t": "QueryMatch",
186
+ "text": "mariage",
187
+ "uri": [ "lnf:taxo#QI-thm2009862" ]
188
+ }
189
+ ]
190
+ },
191
+ "concepts": {
192
+ "concept": [
193
+ {
194
+ "uri": "lnf:taxo#QI-thm2009862",
195
+ "contents": "foo"
196
+ }
197
+ ]
198
+ }
199
+ }
200
+ }
201
+ ]
202
+ }
203
+ }');
204
+ $input2 = json_decode('{
205
+ "meta": {
206
+ "uri": "Default",
207
+ "totalItems": 1,
208
+ "totalItemsIsExact": true,
209
+ "pageItems": 1,
210
+ "firstPageItem": 1,
211
+ "lastPageItem": 1,
212
+ "durationMs": 0,
213
+ "firstPaFId": 1,
214
+ "lastPaFId": 1,
215
+ "producer": "CONCEPT"
216
+ },
217
+ "content": {
218
+ "reply": [
219
+ {
220
+ "docId": 1,
221
+ "uri": "concept",
222
+ "concept": {
223
+ "query": {
224
+ "items": [
225
+ {
226
+ "afs:t": "QueryMatch",
227
+ "text": "mariage",
228
+ "uri": [ "lnf:taxo#QI-thm2009862" ]
229
+ }
230
+ ]
231
+ },
232
+ "concepts": {
233
+ "concept": [
234
+ {
235
+ "uri": "lnf:taxo#QI-thm2009862",
236
+ "contents": "foo"
237
+ }
238
+ ]
239
+ }
240
+ }
241
+ }
242
+ ]
243
+ }
244
+ }');
245
+ $mgr = new AfsConceptManager();
246
+ $mgr->add_concept($input1);
247
+ $mgr->add_concept($input2);
248
+
249
+ try {
250
+ $mgr->get_concept();
251
+ $this->fail('Should have raised exception on default concept name');
252
+ } catch (OutOfBoundsException $e) { }
253
+ $helper = $mgr->get_concept('Specific');
254
+ }
255
+
256
+ public function testWithNoConcept()
257
+ {
258
+ $input = json_decode('{
259
+ "meta": {
260
+ "uri": "fooConcept",
261
+ "totalItems": 1,
262
+ "totalItemsIsExact": true,
263
+ "pageItems": 1,
264
+ "firstPageItem": 1,
265
+ "lastPageItem": 1,
266
+ "durationMs": 0,
267
+ "firstPaFId": 1,
268
+ "lastPaFId": 1,
269
+ "producer": "CONCEPT"
270
+ },
271
+ "content": {
272
+ "reply": [
273
+ {
274
+ "docId": 1,
275
+ "uri": "concept",
276
+ "concept": {
277
+ "query": {
278
+ "items": [
279
+ {
280
+ "afs:t": "QueryText",
281
+ "text": "toto"
282
+ }
283
+ ]
284
+ },
285
+ "concepts": { }
286
+ }
287
+ }
288
+ ]
289
+ }
290
+ }');
291
+ $mgr = new AfsConceptManager();
292
+ $mgr->add_concept($input);
293
+ $this->assertEquals(0, count($mgr->get_concepts()));
294
+ }
295
+ }
lib/antidot/AFS/SEARCH/TEST/facetDefaultTest.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once 'AFS/SEARCH/afs_facet_default.php';
4
+
5
+ class FacetDefaultTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testDefaultParameters()
8
+ {
9
+ $default = new AfsFacetDefault();
10
+ $this->assertEquals(1000, $default->get_nb_replies());
11
+ $this->assertNull($default->get_sort_order());
12
+ }
13
+
14
+ public function testNbReplies()
15
+ {
16
+ $default = new AfsFacetDefault();
17
+ $default->set_nb_replies(42);
18
+ $this->assertEquals(42, $default->get_nb_replies());
19
+ }
20
+
21
+ public function testSortOrder()
22
+ {
23
+ $default = new AfsFacetDefault();
24
+ $default->set_sort_order(AfsFacetValuesSortMode::ITEMS, AfsSortOrder::DESC);
25
+ $this->assertFalse(is_null($default->get_sort_order()));
26
+ $this->assertEquals(AfsFacetValuesSortMode::ITEMS, $default->get_sort_order()->mode);
27
+ $this->assertEquals(AfsSortOrder::DESC, $default->get_sort_order()->order);
28
+ }
29
+
30
+ public function testFormatWithDefaultValues()
31
+ {
32
+ $default = new AfsFacetDefault();
33
+ $default->set_nb_replies(42);
34
+ $this->assertEquals(array('replies=42'), $default->format());
35
+ }
36
+ public function testFormatWithSortOrder()
37
+ {
38
+ $default = new AfsFacetDefault();
39
+ $default->set_nb_replies(42);
40
+ $default->set_sort_order(AfsFacetValuesSortMode::ITEMS, AfsSortOrder::DESC);
41
+ $this->assertEquals(array('replies=42', 'sort=items', 'order=DESC'), $default->format());
42
+ }
43
+ }
lib/antidot/AFS/SEARCH/TEST/facetHelperTest.php ADDED
@@ -0,0 +1,614 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_facet_helper.php";
3
+ require_once "AFS/SEARCH/afs_query.php";
4
+ require_once "AFS/SEARCH/afs_response_helper.php";
5
+
6
+ class FacetHelperTest extends PHPUnit_Framework_TestCase
7
+ {
8
+ public function testRetrieveFacetLabel()
9
+ {
10
+ $input = json_decode('{
11
+ "afs:t": "FacetTree",
12
+ "node": [
13
+ {
14
+ "key": "false",
15
+ "labels": [
16
+ {
17
+ "label": "BAD"
18
+ }
19
+ ],
20
+ "items": 67
21
+ },
22
+ {
23
+ "key": "true",
24
+ "labels": [
25
+ {
26
+ "label": "GOOD"
27
+ }
28
+ ],
29
+ "items": 133
30
+ }
31
+ ],
32
+ "layout": "TREE",
33
+ "type": "BOOL",
34
+ "id": "BOOL",
35
+ "labels": [
36
+ {
37
+ "lang": "ES",
38
+ "region": "ES",
39
+ "label": "Faceta booleana"
40
+ },
41
+ {
42
+ "lang": "FR",
43
+ "label": "Facette booléenne"
44
+ },
45
+ {
46
+ "label": "Boolean facet"
47
+ }
48
+ ] }');
49
+
50
+ $config = new AfsHelperConfiguration();
51
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
52
+ $this->assertEquals($helper->get_label(), "Faceta booleana");
53
+ $this->assertEquals('BOOL', $helper->get_id());
54
+ $this->assertEquals(AfsFacetType::BOOL_TYPE, $helper->get_type());
55
+ $this->assertEquals(AfsFacetLayout::TREE, $helper->get_layout());
56
+ $this->assertEquals(false, $helper->is_sticky());
57
+ }
58
+
59
+ public function testRetrieveStickyness()
60
+ {
61
+ $input = json_decode('{
62
+ "afs:t": "FacetTree",
63
+ "node": [
64
+ {
65
+ "key": "false",
66
+ "labels": [
67
+ {
68
+ "label": "BAD"
69
+ }
70
+ ],
71
+ "items": 67
72
+ }
73
+ ],
74
+ "layout": "TREE",
75
+ "type": "BOOL",
76
+ "id": "FOO",
77
+ "labels": [
78
+ {
79
+ "label": "String facet"
80
+ }
81
+ ],
82
+ "sticky": "true" }');
83
+
84
+ $config = new AfsHelperConfiguration();
85
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
86
+ $this->assertEquals($helper->get_label(), "String facet");
87
+ $this->assertEquals('FOO', $helper->get_id());
88
+ $this->assertEquals(AfsFacetType::BOOL_TYPE, $helper->get_type());
89
+ $this->assertEquals(AfsFacetLayout::TREE, $helper->get_layout());
90
+ $this->assertEquals(true, $helper->is_sticky());
91
+ }
92
+
93
+ public function testFacetValueNoMetaAvailable()
94
+ {
95
+ $input = json_decode('{
96
+ "afs:t": "FacetTree",
97
+ "node": [ {
98
+ "key": "false",
99
+ "labels": [ { "label": "BAD" } ],
100
+ "items": 67
101
+ } ],
102
+ "layout": "TREE",
103
+ "type": "BOOL",
104
+ "id": "BOOL",
105
+ "labels": [ { "label": "Boolean facet" } ] }');
106
+
107
+ $config = new AfsHelperConfiguration();
108
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
109
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
110
+ $elems = $helper->get_elements();
111
+ $this->assertEquals(1, count($elems));
112
+ $this->assertEquals(0, count($elems[0]->get_meta()));
113
+ }
114
+
115
+ public function testFacetValueOneMetaAvailable()
116
+ {
117
+ $input = json_decode('{
118
+ "afs:t": "FacetTree",
119
+ "node": [ {
120
+ "key": "false",
121
+ "labels": [ { "label": "BAD" } ],
122
+ "items": 67,
123
+ "meta": [ {
124
+ "key": "meta_id",
125
+ "value": "meta_value"
126
+ } ]
127
+ } ],
128
+ "layout": "TREE",
129
+ "type": "BOOL",
130
+ "id": "BOOL",
131
+ "labels": [ { "label": "Boolean facet" } ]
132
+ }');
133
+
134
+ $config = new AfsHelperConfiguration();
135
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
136
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
137
+ $elems = $helper->get_elements();
138
+
139
+ $this->assertEquals(1, count($elems));
140
+ $metas = $elems[0]->get_meta();
141
+ $this->assertEquals(1, count($metas));
142
+ foreach ($metas as $meta_key => $meta_value) {
143
+ $this->assertEquals('meta_id', $meta_key);
144
+ $this->assertEquals('meta_value', $meta_value);
145
+ }
146
+ $this->assertEquals('meta_value', $elems[0]->get_meta('meta_id'));
147
+ }
148
+
149
+ public function testFacetValueMultipleMetaAvailable()
150
+ {
151
+ $input = json_decode('{
152
+ "afs:t": "FacetTree",
153
+ "node": [ {
154
+ "key": "false",
155
+ "labels": [ { "label": "BAD" } ],
156
+ "items": 67,
157
+ "meta": [
158
+ {
159
+ "key": "meta_id_1",
160
+ "value": "meta_value_1"
161
+ },
162
+ {
163
+ "key": "meta_id_2",
164
+ "value": "meta_value_2"
165
+ } ]
166
+ } ],
167
+ "layout": "TREE",
168
+ "type": "BOOL",
169
+ "id": "BOOL",
170
+ "labels": [ { "label": "Boolean facet" } ]
171
+ }');
172
+
173
+ $config = new AfsHelperConfiguration();
174
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
175
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
176
+ $elems = $helper->get_elements();
177
+
178
+ $this->assertEquals(1, count($elems));
179
+ $metas = $elems[0]->get_meta();
180
+ $this->assertEquals(2, count($metas));
181
+ for ($i = 1; $i < 2; $i++) {
182
+ $res = each($metas);
183
+ $this->assertEquals('meta_id_' . $i, $res['key']);
184
+ $this->assertEquals('meta_value_' . $i, $res['value']);
185
+ }
186
+ $this->assertEquals('meta_value_1', $elems[0]->get_meta('meta_id_1'));
187
+ $this->assertEquals('meta_value_2', $elems[0]->get_meta('meta_id_2'));
188
+ }
189
+
190
+ public function testFacetValueWrongMetaRequested()
191
+ {
192
+ $input = json_decode('{
193
+ "afs:t": "FacetTree",
194
+ "node": [ {
195
+ "key": "false",
196
+ "labels": [ { "label": "BAD" } ],
197
+ "items": 67,
198
+ "meta": [
199
+ {
200
+ "key": "meta_id_1",
201
+ "value": "meta_value_1"
202
+ },
203
+ {
204
+ "key": "meta_id_2",
205
+ "value": "meta_value_2"
206
+ } ]
207
+ } ],
208
+ "layout": "TREE",
209
+ "type": "BOOL",
210
+ "id": "BOOL",
211
+ "labels": [ { "label": "Boolean facet" } ]
212
+ }');
213
+
214
+ $config = new AfsHelperConfiguration();
215
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
216
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
217
+ $elems = $helper->get_elements();
218
+
219
+ $this->assertEquals(1, count($elems));
220
+ $metas = $elems[0]->get_meta();
221
+ $this->assertEquals(2, count($metas));
222
+ try {
223
+ $elems[0]->get_meta('unknown_meta_id');
224
+ $this->fail('Should have raised an exception on unknown meta id');
225
+ } catch (OutOfBoundsException $e) { }
226
+ }
227
+
228
+ public function testFacetValueMultipleMetaAvailableInArrayFormat()
229
+ {
230
+ $input = json_decode('{
231
+ "afs:t": "FacetTree",
232
+ "node": [ {
233
+ "key": "false",
234
+ "labels": [ { "label": "BAD" } ],
235
+ "items": 67,
236
+ "meta": [
237
+ {
238
+ "key": "meta_id_1",
239
+ "value": "meta_value_1"
240
+ },
241
+ {
242
+ "key": "meta_id_2",
243
+ "value": "meta_value_2"
244
+ } ]
245
+ } ],
246
+ "layout": "TREE",
247
+ "type": "BOOL",
248
+ "id": "BOOL",
249
+ "labels": [ { "label": "Boolean facet" } ]
250
+ }');
251
+
252
+ $config = new AfsHelperConfiguration();
253
+ $helper = new AfsFacetHelper($input, new AfsQuery(), $config);
254
+ $elems = $helper->get_elements();
255
+
256
+ $this->assertEquals(1, count($elems));
257
+ $metas = $elems[0]->meta;
258
+ $this->assertEquals(2, count($metas));
259
+ for ($i = 1; $i < 2; $i++) {
260
+ $res = each($metas);
261
+ $this->assertEquals('meta_id_' . $i, $res['key']);
262
+ $this->assertEquals('meta_value_' . $i, $res['value']);
263
+ }
264
+ }
265
+
266
+
267
+ public function testFacetElementBuilderOnInterval()
268
+ {
269
+ $input = json_decode('{
270
+ "afs:t": "FacetInterval",
271
+ "interval": [
272
+ {
273
+ "key": "[\"2009-10-02\" .. \"2013-10-01\"[",
274
+ "items": 109
275
+ },
276
+ {
277
+ "key": "[\"2010-10-02\" .. \"2013-10-01\"[",
278
+ "items": 97
279
+ }
280
+ ],
281
+ "layout": "INTERVAL",
282
+ "type": "DATE",
283
+ "id": "ADVANCED_INTERVAL_DATE",
284
+ "labels": [
285
+ {
286
+ "label": "Advanced date interval"
287
+ }
288
+ ]
289
+ }');
290
+
291
+ $query = new AfsQuery();
292
+ $config = new AfsHelperConfiguration();
293
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
294
+ $facet_mgr = $query->get_facet_manager();
295
+ $facet_mgr->add_facet(new AfsFacet('ADVANCED_INTERVAL_DATE', AfsFacetType::DATE_TYPE, AfsFacetLayout::INTERVAL));
296
+ $builder = new AfsFacetElementBuilder($facet_mgr, $query);
297
+ $elems = $builder->create_elements('ADVANCED_INTERVAL_DATE', $input, $config);
298
+
299
+ $this->assertEquals(count($elems), 2);
300
+ $elem = reset($elems);
301
+ $this->assertEquals($elem->label, '["2009-10-02" .. "2013-10-01"[');
302
+ $this->assertEquals('["2009-10-02" .. "2013-10-01"[', $elem->key);
303
+ $this->assertEquals($elem->count, 109);
304
+ $this->assertFalse($elem->active);
305
+ $this->assertTrue($elem->query->has_filter('ADVANCED_INTERVAL_DATE', '["2009-10-02" .. "2013-10-01"['));
306
+ $this->assertEquals(count($elem->values), 0);
307
+ next($elems);
308
+ $elem = current($elems);
309
+ $this->assertEquals($elem->label, '["2010-10-02" .. "2013-10-01"[');
310
+ $this->assertEquals('["2010-10-02" .. "2013-10-01"[', $elem->key);
311
+ $this->assertEquals($elem->count, 97);
312
+ $this->assertFalse($elem->active);
313
+ $this->assertTrue($elem->query->has_filter('ADVANCED_INTERVAL_DATE', '["2010-10-02" .. "2013-10-01"['));
314
+ $this->assertEquals(count($elem->values), 0);
315
+ }
316
+
317
+ public function testFacetElementBuilderOnNode()
318
+ {
319
+ $input = json_decode('{
320
+ "afs:t": "FacetTree",
321
+ "node": [
322
+ {
323
+ "key": "false",
324
+ "labels": [
325
+ {
326
+ "label": "BAD"
327
+ }
328
+ ],
329
+ "items": 67
330
+ },
331
+ {
332
+ "key": "true",
333
+ "labels": [
334
+ {
335
+ "label": "GOOD"
336
+ }
337
+ ],
338
+ "items": 133
339
+ }
340
+ ],
341
+ "layout": "TREE",
342
+ "type": "BOOL",
343
+ "id": "BOOL",
344
+ "labels": [
345
+ {
346
+ "label": "Boolean facet"
347
+ }
348
+ ]
349
+ }');
350
+
351
+ $query = new AfsQuery();
352
+ $config = new AfsHelperConfiguration();
353
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
354
+ $facet_mgr = $query->get_facet_manager();
355
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE));
356
+ $builder = new AfsFacetElementBuilder($facet_mgr, $query);
357
+ $elems = $builder->create_elements('BOOL', $input, $config);
358
+
359
+ $this->assertEquals(count($elems), 2);
360
+ $elem = reset($elems);
361
+ $this->assertEquals($elem->label, 'BAD');
362
+ $this->assertEquals($elem->count, 67);
363
+ $this->assertFalse($elem->active);
364
+ $this->assertTrue($elem->query->has_filter('BOOL', 'false'));
365
+ $this->assertEquals(count($elem->values), 0);
366
+ next($elems);
367
+ $elem = current($elems);
368
+ $this->assertEquals($elem->label, 'GOOD');
369
+ $this->assertEquals($elem->count, 133);
370
+ $this->assertFalse($elem->active);
371
+ $this->assertTrue($elem->query->has_filter('BOOL', 'true'));
372
+ $this->assertEquals(count($elem->values), 0);
373
+ }
374
+
375
+ public function testFacetElementBuilderOnTreeNode()
376
+ {
377
+ $input = json_decode('{
378
+ "afs:t": "FacetTree",
379
+ "node": [
380
+ {
381
+ "key": "2010",
382
+ "labels": [
383
+ {
384
+ "lang": "FR",
385
+ "region": "FR",
386
+ "label": "2010"
387
+ }
388
+ ],
389
+ "items": 24,
390
+ "node": [
391
+ {
392
+ "key": "2010-03",
393
+ "labels": [
394
+ {
395
+ "lang": "FR",
396
+ "region": "FR",
397
+ "label": "03"
398
+ }
399
+ ],
400
+ "items": 14,
401
+ "node": [
402
+ {
403
+ "key": "2010-03-07",
404
+ "labels": [
405
+ {
406
+ "lang": "FR",
407
+ "region": "FR",
408
+ "label": "07"
409
+ }
410
+ ],
411
+ "items": 4
412
+ }
413
+ ]
414
+ }
415
+ ]
416
+ }
417
+ ],
418
+ "layout": "TREE",
419
+ "type": "DATE",
420
+ "id": "TREE_DATE",
421
+ "labels": [
422
+ {
423
+ "label": "Tree date"
424
+ }
425
+ ]
426
+ }');
427
+
428
+ $query = new AfsQuery();
429
+ $config = new AfsHelperConfiguration();
430
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
431
+ $facet_mgr = $query->get_facet_manager();
432
+ $facet_mgr->add_facet(new AfsFacet('TREE_DATE', AfsFacetType::DATE_TYPE));
433
+ $builder = new AfsFacetElementBuilder($facet_mgr, $query);
434
+ $elems = $builder->create_elements('TREE_DATE', $input, $config);
435
+
436
+ $this->assertEquals(count($elems), 1);
437
+ $elem = reset($elems);
438
+ $this->assertEquals($elem->label, '2010');
439
+ $this->assertEquals($elem->count, 24);
440
+ $this->assertFalse($elem->active);
441
+ $this->assertTrue($elem->query->has_filter('TREE_DATE', '"2010"'));
442
+ $this->assertEquals(count($elem->values), 1);
443
+ $elem = $elem->values[0];
444
+ $this->assertEquals($elem->label, '03');
445
+ $this->assertEquals($elem->count, 14);
446
+ $this->assertFalse($elem->active);
447
+ $this->assertTrue($elem->query->has_filter('TREE_DATE', '"2010-03"'));
448
+ $this->assertEquals(count($elem->values), 1);
449
+ $elem = $elem->values[0];
450
+ $this->assertEquals($elem->label, '07');
451
+ $this->assertEquals($elem->count, 4);
452
+ $this->assertFalse($elem->active);
453
+ $this->assertTrue($elem->query->has_filter('TREE_DATE', '"2010-03-07"'));
454
+ $this->assertEquals(count($elem->values), 0);
455
+ }
456
+
457
+ public function testFacetElementBuilderReplaceFilter()
458
+ {
459
+ $input = json_decode('{
460
+ "afs:t": "FacetTree",
461
+ "node": [
462
+ {
463
+ "key": "false",
464
+ "labels": [
465
+ {
466
+ "label": "BAD"
467
+ }
468
+ ],
469
+ "items": 67
470
+ },
471
+ {
472
+ "key": "true",
473
+ "labels": [
474
+ {
475
+ "label": "GOOD"
476
+ }
477
+ ],
478
+ "items": 133
479
+ }
480
+ ],
481
+ "layout": "TREE",
482
+ "type": "BOOL",
483
+ "id": "BOOL",
484
+ "labels": [
485
+ {
486
+ "label": "Boolean facet"
487
+ }
488
+ ]
489
+ }');
490
+
491
+ $query = new AfsQuery();
492
+ $query = $query->add_filter('BOOL', 'false');
493
+ $config = new AfsHelperConfiguration();
494
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
495
+ $facet_mgr = $query->get_facet_manager();
496
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE, AfsFacetLayout::TREE, AfsFacetMode::SINGLE_MODE));
497
+ $builder = new AfsFacetElementBuilder($facet_mgr, $query);
498
+ $elems = $builder->create_elements('BOOL', $input, $config);
499
+
500
+ $this->assertEquals(count($elems), 2);
501
+ $elem = reset($elems);
502
+ $this->assertEquals($elem->label, 'BAD');
503
+ $this->assertEquals($elem->count, 67);
504
+ $this->assertTrue($elem->active);
505
+ $this->assertFalse($elem->query->has_filter('BOOL', 'false'));
506
+ $this->assertEquals(count($elem->values), 0);
507
+ next($elems);
508
+ $elem = current($elems);
509
+ $this->assertEquals($elem->label, 'GOOD');
510
+ $this->assertEquals($elem->count, 133);
511
+ $this->assertFalse($elem->active);
512
+ $this->assertTrue($elem->query->has_filter('BOOL', 'true'));
513
+ $this->assertFalse($elem->query->has_filter('BOOL', 'false'));
514
+ $this->assertEquals(count($elem->values), 0);
515
+ }
516
+
517
+ public function testFacetElementBuilderAddFilter()
518
+ {
519
+ $input = json_decode('{
520
+ "afs:t": "FacetTree",
521
+ "node": [
522
+ {
523
+ "key": "false",
524
+ "labels": [
525
+ {
526
+ "label": "BAD"
527
+ }
528
+ ],
529
+ "items": 67
530
+ },
531
+ {
532
+ "key": "true",
533
+ "labels": [
534
+ {
535
+ "label": "GOOD"
536
+ }
537
+ ],
538
+ "items": 133
539
+ }
540
+ ],
541
+ "layout": "TREE",
542
+ "type": "BOOL",
543
+ "id": "BOOL",
544
+ "labels": [
545
+ {
546
+ "label": "Boolean facet"
547
+ }
548
+ ]
549
+ }');
550
+
551
+ $query = new AfsQuery();
552
+ $query = $query->add_filter('BOOL', 'false');
553
+
554
+ $config = new AfsHelperConfiguration();
555
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
556
+ $facet_mgr = $query->get_facet_manager();
557
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE));
558
+ $builder = new AfsFacetElementBuilder($facet_mgr, $query);
559
+ $elems = $builder->create_elements('BOOL', $input, $config);
560
+
561
+ $this->assertEquals(count($elems), 2);
562
+ $elem = reset($elems);
563
+ $this->assertEquals($elem->label, 'BAD');
564
+ $this->assertEquals($elem->count, 67);
565
+ $this->assertTrue($elem->active);
566
+ $this->assertFalse($elem->query->has_filter('BOOL', 'false'));
567
+ $this->assertEquals(count($elem->values), 0);
568
+ next($elems);
569
+ $elem = current($elems);
570
+ $this->assertEquals($elem->label, 'GOOD');
571
+ $this->assertEquals($elem->count, 133);
572
+ $this->assertFalse($elem->active);
573
+ $this->assertTrue($elem->query->has_filter('BOOL', 'true'));
574
+ $this->assertTrue($elem->query->has_filter('BOOL', 'false'));
575
+ $this->assertEquals(count($elem->values), 0);
576
+ }
577
+
578
+ public function testFacetWithoutLabel()
579
+ {
580
+ $input = json_decode('{
581
+ "afs:t": "FacetTree",
582
+ "node": [
583
+ {
584
+ "key": "false",
585
+ "labels": [
586
+ {
587
+ "label": "BAD"
588
+ }
589
+ ],
590
+ "items": 67
591
+ },
592
+ {
593
+ "key": "true",
594
+ "labels": [
595
+ {
596
+ "label": "GOOD"
597
+ }
598
+ ],
599
+ "items": 133
600
+ }
601
+ ],
602
+ "layout": "TREE",
603
+ "type": "BOOL",
604
+ "id": "BOOOOL"
605
+ }');
606
+
607
+ $config = new AfsHelperConfiguration();
608
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
609
+ $query = new AfsQuery();
610
+ $facet = new AfsFacetHelper($input, $query, $config);
611
+
612
+ $this->assertEquals('BOOOOL', $facet->get_label());
613
+ }
614
+ }
lib/antidot/AFS/SEARCH/TEST/facetManagerTest.php ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_facet_manager.php";
3
+
4
+ class FacetManagerTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testNoFacetDefinedGetEmptyFacetList()
7
+ {
8
+ $mgr = new AfsFacetManager();
9
+ $this->assertEquals(count($mgr->get_facets()), 0);
10
+ }
11
+
12
+ public function testNoFacetDefinedFailOnGetSpecificFacetName()
13
+ {
14
+ $mgr = new AfsFacetManager();
15
+ try {
16
+ $mgr->get_facet('foo');
17
+ $this->fail('Query of unknown facet should have rosen an exception!');
18
+ } catch (OutOfBoundsException $e) {}
19
+ }
20
+
21
+ public function testGetDefinedFacet()
22
+ {
23
+ $mgr = new AfsFacetManager();
24
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::STRING_TYPE));
25
+ $this->assertEquals($mgr->get_facet('foo')->get_id(), 'foo');
26
+ }
27
+
28
+ public function testGetAllFacets()
29
+ {
30
+ $mgr = new AfsFacetManager();
31
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::STRING_TYPE));
32
+ $this->assertTrue(array_key_exists('foo', $mgr->get_facets()));
33
+ }
34
+
35
+ public function testAddFacetWithSameName()
36
+ {
37
+ $mgr = new AfsFacetManager();
38
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::STRING_TYPE));
39
+ try {
40
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::DATE_TYPE));
41
+ } catch (InvalidArgumentException $e) { }
42
+ }
43
+
44
+ public function testHasDefinedFacet()
45
+ {
46
+ $mgr = new AfsFacetManager();
47
+ $this->assertFalse($mgr->has_facets());
48
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::STRING_TYPE));
49
+ $this->assertTrue($mgr->has_facets());
50
+ $this->assertTrue($mgr->has_facet('foo'));
51
+ }
52
+ public function testHasNotFacet()
53
+ {
54
+ $mgr = new AfsFacetManager();
55
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::STRING_TYPE));
56
+ $this->assertFalse($mgr->has_facet('bar'));
57
+ }
58
+
59
+ public function testCheckExistingFacet()
60
+ {
61
+ $mgr = new AfsFacetManager();
62
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
63
+ $mgr->add_facet($facet);
64
+ try {
65
+ $mgr->check_facet($facet);
66
+ } catch (Exception $e) {
67
+ $this->fail('Check of existing facet with right parameters should not have raised any exception! '
68
+ . $e);
69
+ }
70
+ }
71
+ public function testCheckUnexistingFacet()
72
+ {
73
+ $mgr = new AfsFacetManager();
74
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
75
+ try {
76
+ $mgr->check_facet($facet);
77
+ $this->fail('Check of unknown facet should have raise exception');
78
+ } catch (AfsUndefinedFacetException $e) { }
79
+ }
80
+ public function testCheckFacetWithImproperParameters()
81
+ {
82
+ $mgr = new AfsFacetManager();
83
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
84
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::INTEGER_TYPE));
85
+ try {
86
+ $mgr->check_facet($facet);
87
+ $this->fail('Check of invalid facet parameters should have raise exception');
88
+ } catch (AfsInvalidFacetParameterException $e) { }
89
+ }
90
+
91
+ public function testCheckOrAddNewFacet()
92
+ {
93
+ $mgr = new AfsFacetManager();
94
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
95
+ try {
96
+ $mgr->check_or_add_facet($facet);
97
+ } catch (Exception $e) {
98
+ $this->fail('New facet should have been added!');
99
+ }
100
+ $this->assertTrue($mgr->has_facet('foo'));
101
+ }
102
+ public function testCheckOrAddExistingFacet()
103
+ {
104
+ $mgr = new AfsFacetManager();
105
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
106
+ $mgr->add_facet($facet);
107
+ try {
108
+ $mgr->check_or_add_facet($facet);
109
+ } catch (Exception $e) {
110
+ $this->fail('New facet should have been added!');
111
+ }
112
+ $this->assertTrue($mgr->has_facet('foo'));
113
+ }
114
+ public function testCheckOrAddExistingFacetWithDifferentParameter()
115
+ {
116
+ $mgr = new AfsFacetManager();
117
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
118
+ $mgr->add_facet(new AfsFacet('foo', AfsFacetType::STRING_TYPE, AfsFacetLayout::INTERVAL));
119
+ try {
120
+ $mgr->check_or_add_facet($facet);
121
+ $this->fail('Existing facet with different parameters should have raise exception!');
122
+ } catch (AfsInvalidFacetParameterException $e) { }
123
+ }
124
+
125
+ public function testFacetStrictSortOrder()
126
+ {
127
+ $mgr = new AfsFacetManager();
128
+ $mgr->set_facet_order(array('foo', 'bar'), AfsFacetOrder::STRICT);
129
+ $this->assertEquals(true, $mgr->is_facet_order_strict());
130
+ }
131
+ public function testFacetLaxSortOrder()
132
+ {
133
+ $mgr = new AfsFacetManager();
134
+ $mgr->set_facet_order(array('foo', 'bar'), AfsFacetOrder::LAX);
135
+ $this->assertEquals(false, $mgr->is_facet_order_strict());
136
+ }
137
+ public function testFacetInvalidSortOrder()
138
+ {
139
+ $mgr = new AfsFacetManager();
140
+ try {
141
+ $mgr->set_facet_order(array('foo', 'bar'), 'FOO');
142
+ $this->fail('Invalid sort order should have raised exception');
143
+ } catch (InvalidArgumentException $e) { }
144
+ }
145
+
146
+ public function testValidFacetValuesSort()
147
+ {
148
+ $mgr = new AfsFacetManager();
149
+ $mgr->set_facets_values_sort_order(AfsFacetValuesSortMode::ALPHA, AfsSortOrder::DESC);
150
+ $this->assertTrue($mgr->has_facets_values_sort_order());
151
+ $this->assertEquals(AfsFacetValuesSortMode::ALPHA, $mgr->get_facets_values_sort_order()->mode);
152
+ $this->assertEquals(AfsSortOrder::DESC, $mgr->get_facets_values_sort_order()->order);
153
+ }
154
+ }
155
+
156
+
lib/antidot/AFS/SEARCH/TEST/facetTest.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_facet.php";
3
+
4
+ class FacetTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testDefaultValues()
7
+ {
8
+ $facet = new AfsFacet('foo');
9
+ $this->assertTrue($facet->get_id() == 'foo');
10
+ $this->assertEquals(AfsFacetType::UNKNOWN_TYPE, $facet->get_type());
11
+ $this->assertEquals(AfsFacetLayout::TREE, $facet->get_layout());
12
+ $this->assertEquals(AfsFacetMode::UNSPECIFIED_MODE, $facet->get_mode());
13
+ $this->assertFalse($facet->has_or_mode());
14
+ $this->assertFalse($facet->has_and_mode());
15
+ $this->assertFalse($facet->has_single_mode());
16
+ }
17
+
18
+ public function testFailOnBadTypeValue()
19
+ {
20
+ try {
21
+ $facet = new AfsFacet('foo', 'foo');
22
+ $this->fail('Should have failed on invalid type value');
23
+ } catch (Exception $e) { }
24
+ }
25
+ public function testFailOnBadLayoutValue()
26
+ {
27
+ try {
28
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE, 'foo');
29
+ $this->fail('Should have failed on invalid sticky value');
30
+ } catch (Exception $e) { }
31
+ }
32
+ public function testFailOnBadModeValue()
33
+ {
34
+ try {
35
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE, AfsFacetLayout::TREE, 'bar');
36
+ $this->fail('Should have failed on invalid mode value');
37
+ } catch (Exception $e) { }
38
+ }
39
+
40
+ public function testJoinOneStringValue()
41
+ {
42
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE);
43
+ $this->assertEquals('foo="bar"', $facet->join_values(array('"bar"')));
44
+ }
45
+ public function testJoinStringValues()
46
+ {
47
+ $facet = new AfsFacet('foo', AfsFacetType::STRING_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE);
48
+ $this->assertEquals('foo="bar" or foo="baz"', $facet->join_values(array('"bar"', '"baz"')));
49
+ }
50
+
51
+ public function testJoinOneValueOtherThanString()
52
+ {
53
+ $facet = new AfsFacet('foo', AfsFacetType::INTEGER_TYPE);
54
+ $this->assertEquals('foo=bar', $facet->join_values(array('bar')));
55
+ }
56
+ public function testJoinValuesOtherThanString()
57
+ {
58
+ $facet = new AfsFacet('foo', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::AND_MODE);
59
+ $this->assertEquals('foo=bar and foo=baz', $facet->join_values(array('bar', 'baz')));
60
+ }
61
+
62
+ public function testFacetAreSimilar()
63
+ {
64
+ $facet = new AfsFacet('foo', AfsFacetType::BOOL_TYPE);
65
+ $other = new AfsFacet('foo', AfsFacetType::BOOL_TYPE, AfsFacetLayout::TREE);
66
+ $this->assertTrue($facet->is_similar_to($facet));
67
+ $this->assertTrue($facet->is_similar_to($other));
68
+ }
69
+ public function testFacetOfDifferentName()
70
+ {
71
+ $facet = new AfsFacet('foo', AfsFacetType::BOOL_TYPE);
72
+ $other = new AfsFacet('FOO', AfsFacetType::BOOL_TYPE, AfsFacetLayout::TREE);
73
+ $this->assertFalse($facet->is_similar_to($other));
74
+ }
75
+ public function testFacetOfDifferentType()
76
+ {
77
+ $facet = new AfsFacet('foo', AfsFacetType::BOOL_TYPE);
78
+ $other = new AfsFacet('foo', AfsFacetType::DATE_TYPE, AfsFacetLayout::TREE);
79
+ $this->assertFalse($facet->is_similar_to($other));
80
+ }
81
+ public function testFacetOfDifferentLayout()
82
+ {
83
+ $facet = new AfsFacet('foo', AfsFacetType::BOOL_TYPE);
84
+ $other = new AfsFacet('foo', AfsFacetType::BOOL_TYPE, AfsFacetLayout::INTERVAL);
85
+ $this->assertFalse($facet->is_similar_to($other));
86
+ }
87
+ }
88
+
lib/antidot/AFS/SEARCH/TEST/facetValuesSortOrderTest.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_facet_values_sort_order.php';
3
+
4
+
5
+ class FacetValuesSortOrderTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testValidSortModeAndSortOrder()
8
+ {
9
+ $s = new AfsFacetValuesSortOrder(AfsFacetValuesSortMode::ALPHA, AfsSortOrder::ASC);
10
+ $this->assertEquals(AfsFacetValuesSortMode::ALPHA, $s->mode);
11
+ $this->assertEquals(AfsSortOrder::ASC, $s->order);
12
+ $this->assertEquals(array('alpha', 'ASC'), $s->format());
13
+ }
14
+ public function testInvalidSortModeAndValidSortOrder()
15
+ {
16
+ try {
17
+ $s = new AfsFacetValuesSortOrder('::ALPHA', AfsSortOrder::ASC);
18
+ $this->fail('Invalid sort mode should have raised exception!');
19
+ } catch (InvalidArgumentException $e) { }
20
+ }
21
+ public function testValidSortModeAndInvalidSortOrder()
22
+ {
23
+ try {
24
+ $s = new AfsFacetValuesSortOrder(AfsFacetValuesSortMode::ALPHA, '::ASC');
25
+ $this->fail('Invalid sort order should have raised exception!');
26
+ } catch (InvalidArgumentException $e) { }
27
+ }
28
+ public function testCopy()
29
+ {
30
+ $f = new AfsFacetValuesSortOrder(AfsFacetValuesSortMode::ALPHA, AfsSortOrder::ASC);
31
+ $s = $f->copy();
32
+ $f->mode = AfsFacetValuesSortMode::ITEMS;
33
+ $f->order = AfsSortOrder::ASC;
34
+ $this->assertEquals(AfsFacetValuesSortMode::ALPHA, $s->mode);
35
+ $this->assertEquals(AfsSortOrder::ASC, $s->order);
36
+ $this->assertEquals(array('alpha', 'ASC'), $s->format());
37
+ }
38
+ }
lib/antidot/AFS/SEARCH/TEST/feedCoderTest.php ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_feed_coder.php";
3
+
4
+ class FeedCoderTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testEncodeOneFeed()
7
+ {
8
+ $coder = new AfsFeedCoder();
9
+ $feeds = array('feed');
10
+ $this->assertTrue($coder->encode($feeds) == 'feed');
11
+ }
12
+ public function testEncodeOneFeedCollisionWithSeparator()
13
+ {
14
+ $coder = new AfsFeedCoder();
15
+ $feeds = array('feed_feed');
16
+ $this->assertTrue($coder->encode($feeds) == 'feed|_feed');
17
+ }
18
+ public function testEncodeOneFeedCollisionWithEscapeChar()
19
+ {
20
+ $coder = new AfsFeedCoder();
21
+ $feeds = array('feed|feed');
22
+ $this->assertTrue($coder->encode($feeds) == 'feed||feed');
23
+ }
24
+ public function testEncodeOneFeedCollisionWithRegexDelimiter()
25
+ {
26
+ $coder = new AfsFeedCoder();
27
+ $feeds = array('feed~feed');
28
+ $this->assertTrue($coder->encode($feeds) == 'feed~feed');
29
+ }
30
+
31
+ public function testEncodeMultipleFeeds()
32
+ {
33
+ $coder = new AfsFeedCoder();
34
+ $feeds = array('feed', 'feed');
35
+ $this->assertTrue($coder->encode($feeds) == 'feed_feed');
36
+ }
37
+ public function testEncodeMultipleFeedsCollisionWithSeparator()
38
+ {
39
+ $coder = new AfsFeedCoder();
40
+ $feeds = array('feed_feed', 'food_food');
41
+ $this->assertTrue($coder->encode($feeds) == 'feed|_feed_food|_food');
42
+ }
43
+ public function testEncodeMultipleFeedsCollisionWithEscapeChar()
44
+ {
45
+ $coder = new AfsFeedCoder();
46
+ $feeds = array('feed|feed', 'food|food');
47
+ $this->assertTrue($coder->encode($feeds) == 'feed||feed_food||food');
48
+ }
49
+ public function testEncodeMultipleFeedsCollisionWithRegexDelimiter()
50
+ {
51
+ $coder = new AfsFeedCoder();
52
+ $feeds = array('feed~feed', 'food~food');
53
+ $this->assertTrue($coder->encode($feeds) == 'feed~feed_food~food');
54
+ }
55
+
56
+ public function testDecodeOneFeed()
57
+ {
58
+ $coder = new AfsFeedCoder();
59
+ $this->assertTrue(in_array('feed', $coder->decode('feed')));
60
+ }
61
+ public function testDecodeOneFeedCollisionWithSeparator()
62
+ {
63
+ $coder = new AfsFeedCoder();
64
+ $this->assertTrue(in_array('feed_feed', $coder->decode('feed|_feed')));
65
+ }
66
+ public function testDecodeOneFeedCollisionWithEscapeChar()
67
+ {
68
+ $coder = new AfsFeedCoder();
69
+ $this->assertTrue(in_array('feed|feed', $coder->decode('feed||feed')));
70
+ }
71
+ public function testDecodeOneFeedCollisionWithRegexDelimiter()
72
+ {
73
+ $coder = new AfsFeedCoder();
74
+ $feeds = array('feed~feed');
75
+ $this->assertTrue(in_array('feed~feed', $coder->decode('feed~feed')));
76
+ }
77
+
78
+ public function testDecodeMultipleFeeds()
79
+ {
80
+ $coder = new AfsFeedCoder();
81
+ $feeds = $coder->decode('feed_food');
82
+ $this->assertTrue(in_array('feed', $feeds));
83
+ $this->assertTrue(in_array('food', $feeds));
84
+ }
85
+ public function testDecodeMultipleFeedsCollisionWithSeparator()
86
+ {
87
+ $coder = new AfsFeedCoder();
88
+ $feeds = $coder->decode('feed|_feed_food|_food');
89
+ $this->assertTrue(in_array('feed_feed', $feeds));
90
+ $this->assertTrue(in_array('food_food', $feeds));
91
+ }
92
+ public function testDecodeMultipleFeedsCollisionWithEscapeChar()
93
+ {
94
+ $coder = new AfsFeedCoder();
95
+ $feeds = $coder->decode('feed||feed_food||food');
96
+ $this->assertTrue(in_array('feed|feed', $feeds));
97
+ $this->assertTrue(in_array('food|food', $feeds));
98
+ }
99
+ public function testDecodeMultipleFeedsCollisionWithRegexDelimiter()
100
+ {
101
+ $coder = new AfsFeedCoder();
102
+ $feeds = $coder->decode('feed~feed_food~food');
103
+ $this->assertTrue(in_array('feed~feed', $feeds));
104
+ $this->assertTrue(in_array('food~food', $feeds));
105
+ }
106
+
107
+ public function testEncodeDecode()
108
+ {
109
+ $coder = new AfsFeedCoder();
110
+ $feeds = array('foo_foo', 'bar|_bar', '~|baz|baz~|~|_||__|||');
111
+ $encode = $coder->encode($feeds);
112
+ $decode = $coder->decode($encode);
113
+ for ($i = 0; $i < count($feeds); $i++) {
114
+ $this->assertTrue($feeds[$i] == $decode[$i]);
115
+ }
116
+ }
117
+
118
+ public function testSpecificValueSeparator()
119
+ {
120
+ $coder = new AfsFeedCoder('o');
121
+ $feeds = array('foo_foo', 'bar|_bar', '~|baz|baz~|~|_||__|||');
122
+ $encode = $coder->encode($feeds);
123
+ $this->assertTrue('f|o|o_f|o|oobar||_baro~||baz||baz~||~||_||||__||||||'
124
+ == $encode);
125
+
126
+ $decode = $coder->decode($encode);
127
+ for ($i = 0; $i < count($feeds); $i++) {
128
+ $this->assertTrue($feeds[$i] == $decode[$i]);
129
+ }
130
+ }
131
+
132
+ public function testSpecificEscapeCharacter()
133
+ {
134
+ $coder = new AfsFeedCoder('_', 'a');
135
+ $feeds = array('foo_foo', 'bar|_bar', '~|baz|baz~|~|_||__|||');
136
+ $encode = $coder->encode($feeds);
137
+ $this->assertTrue('fooa_foo_baar|a_baar_~|baaz|baaz~|~|a_||a_a_|||'
138
+ == $encode);
139
+
140
+ $decode = $coder->decode($encode);
141
+ for ($i = 0; $i < count($feeds); $i++) {
142
+ $this->assertTrue($feeds[$i] == $decode[$i]);
143
+ }
144
+ }
145
+ }
146
+
147
+
lib/antidot/AFS/SEARCH/TEST/filterCoderTest.php ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_filter_coder.php";
3
+
4
+ class FilterCoderTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testEncodeOneFilterValue()
7
+ {
8
+ $coder = new AfsFilterCoder();
9
+ $filters = array('filter' => array('value'));
10
+ $this->assertTrue($coder->encode($filters) == 'filter_value');
11
+ }
12
+ public function testEncodeOneFilterValueCollisionWithValueSeparator()
13
+ {
14
+ $coder = new AfsFilterCoder();
15
+ $filters = array('filter' => array('value_value'));
16
+ $this->assertTrue($coder->encode($filters) == 'filter_value|_value');
17
+ }
18
+ public function testEncodeOneFilterValueCollisionWithEscapeChar()
19
+ {
20
+ $coder = new AfsFilterCoder();
21
+ $filters = array('filter' => array('value|value'));
22
+ $this->assertTrue($coder->encode($filters) == 'filter_value||value');
23
+ }
24
+ public function testEncodeOneFilterValueCollisionWithRegexDelimiter()
25
+ {
26
+ $coder = new AfsFilterCoder();
27
+ $filters = array('filter' => array('value~value'));
28
+ $this->assertTrue($coder->encode($filters) == 'filter_value~value');
29
+ }
30
+
31
+ public function testEncodeMultipleFilterValues()
32
+ {
33
+ $coder = new AfsFilterCoder();
34
+ $filters = array('filter' => array('value', 'value'));
35
+ $this->assertTrue($coder->encode($filters) == 'filter_value_value');
36
+ }
37
+ public function testEncodeMultipleFilterValuesCollisionWithValueSeparator()
38
+ {
39
+ $coder = new AfsFilterCoder();
40
+ $filters = array('filter' => array('foo_foo', 'bar_bar'));
41
+ $this->assertTrue($coder->encode($filters) == 'filter_foo|_foo_bar|_bar');
42
+ }
43
+ public function testEncodeMultipleFilterValuesCollisionWithEscapeChar()
44
+ {
45
+ $coder = new AfsFilterCoder();
46
+ $filters = array('filter' => array('foo|foo', 'bar|bar'));
47
+ $this->assertTrue($coder->encode($filters) == 'filter_foo||foo_bar||bar');
48
+ }
49
+ public function testEncodeMultipleFilterValuesCollisionWithRegexDelimiter()
50
+ {
51
+ $coder = new AfsFilterCoder();
52
+ $filters = array('filter' => array('foo~foo', 'bar~bar'));
53
+ $this->assertTrue($coder->encode($filters) == 'filter_foo~foo_bar~bar');
54
+ }
55
+ public function testEncodeMultipleFiltersMultipleValuesCollisionFilterSeparator()
56
+ {
57
+ $coder = new AfsFilterCoder();
58
+ $filters = array('filter' => array('foo-foo', 'bar-bar'),
59
+ 'filt' => array('fot-fot', 'baz-baz'));
60
+ $this->assertTrue($coder->encode($filters) == 'filter_foo|-foo_bar|-bar-filt_fot|-fot_baz|-baz');
61
+ }
62
+
63
+ public function testDecodeOneFilterValue()
64
+ {
65
+ $coder = new AfsFilterCoder();
66
+ $decoded = $coder->decode('filter_value');
67
+ $this->assertArrayHasKey('filter', $decoded);
68
+ $this->assertTrue(in_array('value', $decoded['filter']));
69
+ }
70
+ public function testDecodeOneFilterValueCollisionWithValueSeparator()
71
+ {
72
+ $coder = new AfsFilterCoder();
73
+ $decoded = $coder->decode('filter_value|_value');
74
+ $this->assertArrayHasKey('filter', $decoded);
75
+ $this->assertTrue(in_array('value_value', $decoded['filter']));
76
+ }
77
+ public function testDecodeOneFilterValueCollisionWithEscapeChar()
78
+ {
79
+ $coder = new AfsFilterCoder();
80
+ $decoded = $coder->decode('filter_value||value');
81
+ $this->assertArrayHasKey('filter', $decoded);
82
+ $this->assertTrue(in_array('value|value', $decoded['filter']));
83
+ }
84
+ public function testDecodeOneFilterValueCollisionWithRegexDelimiter()
85
+ {
86
+ $coder = new AfsFilterCoder();
87
+ $decoded = $coder->decode('filter_value~value');
88
+ $this->assertArrayHasKey('filter', $decoded);
89
+ $this->assertTrue(in_array('value~value', $decoded['filter']));
90
+ }
91
+
92
+ public function testDecodeMultipleFilterValues()
93
+ {
94
+ $coder = new AfsFilterCoder();
95
+ $decoded = $coder->decode('filter_value_val');
96
+ $this->assertArrayHasKey('filter', $decoded);
97
+ $this->assertTrue(in_array('value', $decoded['filter']));
98
+ $this->assertTrue(in_array('val', $decoded['filter']));
99
+ }
100
+ public function testDecodeMultipleFilterValuesCollisionWithValueSeparator()
101
+ {
102
+ $coder = new AfsFilterCoder();
103
+ $decoded = $coder->decode('filter_value|_value_val|_val');
104
+ $this->assertArrayHasKey('filter', $decoded);
105
+ $this->assertTrue(in_array('value_value', $decoded['filter']));
106
+ $this->assertTrue(in_array('val_val', $decoded['filter']));
107
+ }
108
+ public function testDecodeMultipleFilterValuesCollisionWithEscapeChar()
109
+ {
110
+ $coder = new AfsFilterCoder();
111
+ $decoded = $coder->decode('filter_value||value_val||val');
112
+ $this->assertArrayHasKey('filter', $decoded);
113
+ $this->assertTrue(in_array('value|value', $decoded['filter']));
114
+ $this->assertTrue(in_array('val|val', $decoded['filter']));
115
+ }
116
+ public function testDecodeMultipleFilterValuesCollisionWithRegexDelimiter()
117
+ {
118
+ $coder = new AfsFilterCoder();
119
+ $decoded = $coder->decode('filter_value~value_val~val');
120
+ $this->assertArrayHasKey('filter', $decoded);
121
+ $this->assertTrue(in_array('value~value', $decoded['filter']));
122
+ $this->assertTrue(in_array('val~val', $decoded['filter']));
123
+ }
124
+ public function testDecodeMultipleFiltersMultipleValuesCollisionWithFilterSeparator()
125
+ {
126
+ $coder = new AfsFilterCoder();
127
+ $decoded = $coder->decode('filter_value|-value_val|-val-filt_va|-va_v|-v');
128
+ $this->assertArrayHasKey('filter', $decoded);
129
+ $this->assertTrue(in_array('value-value', $decoded['filter']));
130
+ $this->assertTrue(in_array('val-val', $decoded['filter']));
131
+ $this->assertArrayHasKey('filt', $decoded);
132
+ $this->assertTrue(in_array('va-va', $decoded['filt']));
133
+ $this->assertTrue(in_array('v-v', $decoded['filt']));
134
+ }
135
+
136
+ public function testEncodeDecode()
137
+ {
138
+ $coder = new AfsFilterCoder();
139
+ $filters = array('filter' => array('foo_foo', 'bar|_bar', '~|baz|baz~|~|_||__|||'),
140
+ 'filt' => array('bla~|||---_b___t'));
141
+ $encode = $coder->encode($filters);
142
+ $decode = $coder->decode($encode);
143
+ foreach ($filters as $filter => $values) {
144
+ $this->assertArrayHasKey($filter, $decode);
145
+ foreach ($values as $value) {
146
+ $this->assertTrue(in_array($value, $decode[$filter]));
147
+ }
148
+ }
149
+ }
150
+
151
+ public function testSpecificValueSeparator()
152
+ {
153
+ $coder = new AfsFilterCoder('r');
154
+ $filters = array('filter' => array('foo_foo', 'bar|_bar', '~|baz|baz~|~|_||__|||'),
155
+ 'filt' => array('bla~|||---_b___t'));
156
+ $encode = $coder->encode($filters);
157
+ $this->assertEquals('filte|rrfoo_foorba|r||_ba|rr~||baz||baz~||~||_||||__||||||-filtrbla~|||||||-|-|-_b___t', $encode);
158
+
159
+ $decode = $coder->decode($encode);
160
+ foreach ($filters as $filter => $values) {
161
+ $this->assertArrayHasKey($filter, $decode);
162
+ foreach ($values as $value) {
163
+ $this->assertTrue(in_array($value, $decode[$filter]));
164
+ }
165
+ }
166
+ }
167
+
168
+ public function testSpecificFilterSeparator()
169
+ {
170
+ $coder = new AfsFilterCoder('_', 'a');
171
+ $filters = array('filter' => array('foo_foo', 'bar|_bar', '~|baz|baz~|~|_||__|||'),
172
+ 'filt' => array('bla~|||---_b___t'));
173
+ $encode = $coder->encode($filters);
174
+ $this->assertEquals('filter_foo|_foo_b|ar|||_b|ar_~||b|az||b|az~||~|||_|||||_|_||||||afilt_bl|a~||||||---|_b|_|_|_t', $encode);
175
+
176
+ $decode = $coder->decode($encode);
177
+ foreach ($filters as $filter => $values) {
178
+ $this->assertArrayHasKey($filter, $decode);
179
+ foreach ($values as $value) {
180
+ $this->assertTrue(in_array($value, $decode[$filter]));
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+
187
+
lib/antidot/AFS/SEARCH/TEST/headerHelperTest.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_header_helper.php";
3
+
4
+ class HeaderHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testError()
7
+ {
8
+ $input = json_decode('{
9
+ "header": {
10
+ "query": {
11
+ "userId": "?",
12
+ "sessionId": "?",
13
+ "date": "2014-01-10T16:04:32+0000",
14
+ "queryParam": [ ],
15
+ "mainCtx": {
16
+ "textQuery": ""
17
+ },
18
+ "textQuery": ""
19
+ },
20
+ "user": {
21
+ "requestMethod": "GET",
22
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0 Iceweasel/24.0",
23
+ "address": "10.61.8.236",
24
+ "output": {
25
+ "format": "JSON",
26
+ "encoding": "gzip",
27
+ "charset": "UTF-8"
28
+ }
29
+ },
30
+ "info": { },
31
+ "error": {
32
+ "message": [
33
+ "errorMsg"
34
+ ]
35
+ }
36
+ }
37
+ }');
38
+ $header = new AfsHeaderHelper($input->header);
39
+ $this->assertTrue($header->in_error());
40
+ $this->assertEquals('errorMsg', $header->get_error());
41
+ }
42
+
43
+ public function testNotInError()
44
+ {
45
+ $input = json_decode('{
46
+ "header": {
47
+ "query": {
48
+ "userId": "e3eddaff-5a3d-4807-8fb2-09e13baf78e1",
49
+ "sessionId": "4c0f28d1-bb67-469b-86bf-54f83432914e",
50
+ "date": "2014-01-10T16:17:44+0000",
51
+ "queryParam": [ ],
52
+ "mainCtx": {
53
+ "textQuery": ""
54
+ },
55
+ "textQuery": ""
56
+ },
57
+ "user": {
58
+ "requestMethod": "GET",
59
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0 Iceweasel/24.0",
60
+ "address": "10.61.8.236",
61
+ "output": {
62
+ "format": "JSON",
63
+ "encoding": "gzip",
64
+ "charset": "UTF-8"
65
+ }
66
+ },
67
+ "performance": {
68
+ "durationMs": 204
69
+ },
70
+ "info": { }
71
+ }
72
+ }');
73
+ $header = new AfsHeaderHelper($input->header);
74
+ $this->assertFalse($header->in_error());
75
+ $this->assertEquals('e3eddaff-5a3d-4807-8fb2-09e13baf78e1', $header->get_user_id());
76
+ $this->assertEquals('4c0f28d1-bb67-469b-86bf-54f83432914e', $header->get_session_id());
77
+ $this->assertEquals(204, $header->get_duration());
78
+ }
79
+ }
80
+
81
+
lib/antidot/AFS/SEARCH/TEST/helperConfigurationTest.php ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
3
+
4
+ // Testing purpose
5
+ class TextVisitorMock implements AfsTextVisitorInterface
6
+ {
7
+ public function visit_AfsStringText(AfsStringText $afs_text)
8
+ {
9
+ return null;
10
+ }
11
+
12
+ public function visit_AfsMatchText(AfsMatchText $afs_text)
13
+ {
14
+ return '<BBB>' . $afs_text->get_text() . '</BBB>';
15
+ }
16
+
17
+ public function visit_AfsTruncateText(AfsTruncateText $afs_text)
18
+ {
19
+ return '';
20
+ }
21
+ }
22
+
23
+ class SpellcheckTextVisitorMock implements AfsSpellcheckTextVisitorInterface
24
+ {
25
+ public function visit_AfsSpellcheckText(AfsSpellcheckText $spellcheck_text)
26
+ {
27
+ return new AfsRawAndFormattedText('ARG'.$spellcheck_text->get_text().'ARG');
28
+ }
29
+
30
+ public function visit_AfsSpellcheckMatch(AfsSpellcheckMatch $spellcheck_text)
31
+ {
32
+ return new AfsRawAndFormattedText($spellcheck_text->get_text());
33
+ }
34
+ }
35
+
36
+
37
+ // Tests
38
+ class HelperConfigurationTest extends PHPUnit_Framework_TestCase
39
+ {
40
+ public function testRetrieveDefaultFormat()
41
+ {
42
+ $config = new AfsHelperConfiguration();
43
+ $this->assertEquals(AfsHelperFormat::HELPERS, $config->get_helper_format());
44
+ $this->assertFalse($config->is_array_format());
45
+ $this->assertTrue($config->is_helper_format());
46
+ }
47
+
48
+ public function testSetAndRetrieveFormat()
49
+ {
50
+ $config = new AfsHelperConfiguration();
51
+ $config->set_helper_format(AfsHelperFormat::ARRAYS);
52
+ $this->assertEquals(AfsHelperFormat::ARRAYS, $config->get_helper_format());
53
+ $this->assertTrue($config->is_array_format());
54
+ $this->assertFalse($config->is_helper_format());
55
+ }
56
+
57
+ public function testSetInvalidFormat()
58
+ {
59
+ $config = new AfsHelperConfiguration();
60
+ try {
61
+ $config->set_helper_format('foo');
62
+ $this->fail('Invalid helper format should have raised exception');
63
+ } catch (InvalidArgumentException $e) { }
64
+ }
65
+
66
+ public function testHasQueryCoder()
67
+ {
68
+ $config = new AfsHelperConfiguration();
69
+ $this->assertFalse($config->has_query_coder());
70
+ }
71
+
72
+ public function testRetrieveQueryCoder()
73
+ {
74
+ $config = new AfsHelperConfiguration();
75
+ $query_coder = $config->get_query_coder();
76
+ $this->assertTrue(is_null($query_coder));
77
+ }
78
+
79
+ public function testSetAndRetrieveQueryCoder()
80
+ {
81
+ $config = new AfsHelperConfiguration();
82
+ $config->set_query_coder(new AfsQueryCoder('foofoo'));
83
+ $query = new AfsQuery();
84
+ $coder = $config->get_query_coder();
85
+ $link = $coder->generate_link($query);
86
+ $this->assertTrue(strstr($link, 'foofoo') !== null);
87
+ }
88
+
89
+ public function testRetrieveReplyTextVisitor()
90
+ {
91
+ $config = new AfsHelperConfiguration();
92
+ $visitor = $config->get_reply_text_visitor();
93
+ $this->assertNotNull($visitor);
94
+ $text = new AfsMatchText(json_decode('{"match": "foo"}'));
95
+ $this->assertEquals('<b>foo</b>', $visitor->visit_AfsMatchText($text));
96
+ }
97
+
98
+ public function testSetAndRetrieveReplyTextVisitor()
99
+ {
100
+ $config = new AfsHelperConfiguration();
101
+ $visitor = new TextVisitorMock();
102
+ $config->set_reply_text_visitor($visitor);
103
+ $visitor = $config->get_reply_text_visitor();
104
+ $this->assertNotNull($visitor);
105
+ $text = new AfsMatchText(json_decode('{"match": "bar"}'));
106
+ $this->assertEquals('<BBB>bar</BBB>', $visitor->visit_AfsMatchText($text));
107
+ }
108
+
109
+ public function testRetrieveSpellcheckTextVisitor()
110
+ {
111
+ $config = new AfsHelperConfiguration();
112
+ $visitor = $config->get_spellcheck_text_visitor();
113
+ $this->assertNotNull($visitor);
114
+ $text = new AfsSpellcheckText(json_decode('{"text": "foo"}'));
115
+ $this->assertEquals('foo', $visitor->visit_AfsSpellcheckText($text)->raw);
116
+ }
117
+
118
+ public function testSetAndRetrieveSpellcheckTextVisitor()
119
+ {
120
+ $config = new AfsHelperConfiguration();
121
+ $visitor = new SpellcheckTextVisitorMock();
122
+ $config->set_spellcheck_text_visitor($visitor);
123
+ $visitor = $config->get_spellcheck_text_visitor();
124
+ $this->assertNotNull($visitor);
125
+ $text = new AfsSpellcheckText(json_decode('{"text": "bar"}'));
126
+ $this->assertEquals('ARGbarARG', $visitor->visit_AfsSpellcheckText($text)->raw);
127
+ }
128
+
129
+ }
130
+
131
+
lib/antidot/AFS/SEARCH/TEST/intervalTest.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/afs_interval.php';
3
+
4
+
5
+ class AfsIntervalTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testBuildIntervalWithBothBoundaries()
8
+ {
9
+ $interval = AfsInterval::create(10, 20);
10
+ $this->assertEquals(10, $interval->get_lower_bound());
11
+ $this->assertEquals(20, $interval->get_upper_bound());
12
+ $this->assertFalse($interval->is_lower_bound_excluded());
13
+ $this->assertFalse($interval->is_upper_bound_excluded());
14
+ }
15
+ public function testBuildIntervalWithLowerBound()
16
+ {
17
+ $interval = AfsInterval::create(10);
18
+ $this->assertEquals(10, $interval->get_lower_bound());
19
+ $this->assertEquals(null, $interval->get_upper_bound());
20
+ }
21
+ public function testBuildIntervalWithUpperBound()
22
+ {
23
+ $interval = AfsInterval::create(null, 20);
24
+ $this->assertEquals(null, $interval->get_lower_bound());
25
+ $this->assertEquals(20, $interval->get_upper_bound());
26
+ }
27
+ public function testBuildIntervalWithoutBound()
28
+ {
29
+ try {
30
+ AfsInterval::create();
31
+ $this->fail('Creating interval without bound should have raised error!');
32
+ } catch (AfsIntervalBoundException $e) { }
33
+ }
34
+
35
+ public function testBuildIntervalExcludingLowerBound()
36
+ {
37
+ $interval = AfsInterval::create(10, 20)->exclude_lower_bound();
38
+ $this->assertEquals(']10 .. 20]', (string)$interval);
39
+ $this->assertTrue($interval->is_lower_bound_excluded());
40
+ $this->assertFalse($interval->is_upper_bound_excluded());
41
+ }
42
+ public function testBuildIntervalExcludingUpperBound()
43
+ {
44
+ $interval = AfsInterval::create(10, 20)->exclude_upper_bound();
45
+ $this->assertEquals('[10 .. 20[', (string)$interval);
46
+ $this->assertFalse($interval->is_lower_bound_excluded());
47
+ $this->assertTrue($interval->is_upper_bound_excluded());
48
+ }
49
+ public function testBuildIntervalExcludingBothBounds()
50
+ {
51
+ $interval = AfsInterval::create(10, 20)->exclude_upper_bound()
52
+ ->exclude_lower_bound();
53
+ $this->assertEquals(']10 .. 20[', (string)$interval);
54
+ $this->assertTrue($interval->is_lower_bound_excluded());
55
+ $this->assertTrue($interval->is_upper_bound_excluded());
56
+ }
57
+
58
+ public function testSerializeIntervalWithBothBoundaries()
59
+ {
60
+ $interval = AfsInterval::create(10, 20);
61
+ $this->assertEquals('[10 .. 20]', (string)$interval);
62
+ }
63
+ public function testSerializeIntervalWithLowerBound()
64
+ {
65
+ $interval = AfsInterval::create(10);
66
+ $this->assertEquals('[10 .. ' . PHP_INT_MAX . ']', (string)$interval);
67
+ }
68
+ public function testSerializeIntervalWithUpperhBound()
69
+ {
70
+ $interval = AfsInterval::create(null, 20);
71
+ $this->assertEquals('[' . PHP_INT_MIN . ' .. 20]', (string)$interval);
72
+ }
73
+
74
+ public function testBuildIntervalFromStringValues()
75
+ {
76
+ $interval = AfsInterval::parse('[10.3 .. 20]');
77
+ $this->assertEquals(10.3, $interval->get_lower_bound());
78
+ $this->assertEquals(20, $interval->get_upper_bound());
79
+ $this->assertFalse($interval->is_lower_bound_excluded());
80
+ $this->assertFalse($interval->is_upper_bound_excluded());
81
+ }
82
+ public function testBuildIntervalFromStringValueAndMinInf()
83
+ {
84
+ $interval = AfsInterval::parse('[' . PHP_INT_MIN . ' .. 20]');
85
+ $this->assertEquals(null, $interval->get_lower_bound());
86
+ $this->assertEquals(20, $interval->get_upper_bound());
87
+ }
88
+ public function testBuildIntervalFromStringValueAndMaxInf()
89
+ {
90
+ $interval = AfsInterval::parse('[10 .. ' . PHP_INT_MAX . ']');
91
+ $this->assertEquals(10, $interval->get_lower_bound());
92
+ $this->assertEquals(null, $interval->get_upper_bound());
93
+ }
94
+ public function testBuildIntervalFromStringExcludingLowerBound()
95
+ {
96
+ $interval = AfsInterval::parse(']10 .. 20]');
97
+ $this->assertEquals(10, $interval->get_lower_bound());
98
+ $this->assertEquals(20, $interval->get_upper_bound());
99
+ $this->assertTrue($interval->is_lower_bound_excluded());
100
+ $this->assertFalse($interval->is_upper_bound_excluded());
101
+ }
102
+ public function testBuildIntervalFromStringExcludingUpperBound()
103
+ {
104
+ $interval = AfsInterval::parse('[10 .. 20[');
105
+ $this->assertEquals(10, $interval->get_lower_bound());
106
+ $this->assertEquals(20, $interval->get_upper_bound());
107
+ $this->assertFalse($interval->is_lower_bound_excluded());
108
+ $this->assertTrue($interval->is_upper_bound_excluded());
109
+ }
110
+ public function testBuildIntervalFromStringExcludingBothBounds()
111
+ {
112
+ $interval = AfsInterval::parse(']10 .. 20[');
113
+ $this->assertEquals(10, $interval->get_lower_bound());
114
+ $this->assertEquals(20, $interval->get_upper_bound());
115
+ $this->assertTrue($interval->is_lower_bound_excluded());
116
+ $this->assertTrue($interval->is_upper_bound_excluded());
117
+ }
118
+ public function testBuildIntervalFromInvalidStringValue()
119
+ {
120
+ try {
121
+ AfsInterval::parse('[10..20]');
122
+ $this->fail('Invalid interval string should have not been parsed!');
123
+ } catch (AfsIntervalInitializerException $e) { }
124
+ try {
125
+ AfsInterval::parse('10 .. 20]');
126
+ $this->fail('Invalid interval string should have not been parsed!');
127
+ } catch (AfsIntervalInitializerException $e) { }
128
+ try {
129
+ AfsInterval::parse('[10 .. 20');
130
+ $this->fail('Invalid interval string should have not been parsed!');
131
+ } catch (AfsIntervalInitializerException $e) { }
132
+ try {
133
+ AfsInterval::parse('[10 . 20]');
134
+ $this->fail('Invalid interval string should have not been parsed!');
135
+ } catch (AfsIntervalInitializerException $e) { }
136
+ }
137
+ }
lib/antidot/AFS/SEARCH/TEST/metaHelperTest.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_meta_helper.php";
3
+
4
+ class MetaHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testValues()
7
+ {
8
+ $input = json_decode('{ "meta": {
9
+ "uri": "TOTO",
10
+ "totalItems": 200,
11
+ "totalItemsIsExact": true,
12
+ "pageItems": 2,
13
+ "firstPageItem": 21,
14
+ "lastPageItem": 22,
15
+ "durationMs": 66,
16
+ "firstPaFId": 1,
17
+ "lastPaFId": 1,
18
+ "producer": "SEARCH" } }');
19
+ $meta = new AfsMetaHelper($input->meta);
20
+ $this->assertEquals($meta->get_feed(), 'TOTO');
21
+ $this->assertEquals($meta->get_total_replies(), 200);
22
+ $this->assertEquals($meta->get_replies_per_page(), 2);
23
+ $this->assertEquals($meta->get_duration(), 66);
24
+ $this->assertEquals($meta->get_producer(), 'SEARCH');
25
+ $this->assertFalse($meta->has_cluster());
26
+ }
27
+ public function testValuesWithCluster()
28
+ {
29
+ $input = json_decode('{
30
+ "uri": "Catalog",
31
+ "totalItems": 61,
32
+ "totalItemsIsExact": true,
33
+ "pageItems": 20,
34
+ "firstPageItem": 1,
35
+ "lastPageItem": 20,
36
+ "durationMs": 6,
37
+ "cluster": "marketing",
38
+ "firstPaFId": 1,
39
+ "lastPaFId": 1,
40
+ "producer": "SEARCH",
41
+ "totalItemsInClusters": 2,
42
+ "nbClusters": 2
43
+ }');
44
+ $meta = new AfsMetaHelper($input);
45
+ $this->assertEquals('Catalog', $meta->get_feed());
46
+ $this->assertEquals(61, $meta->get_total_replies());
47
+ $this->assertEquals($meta->get_replies_per_page(), 20);
48
+ $this->assertEquals(6, $meta->get_duration());
49
+ $this->assertEquals('SEARCH', $meta->get_producer());
50
+ $this->assertTrue($meta->has_cluster());
51
+ $this->assertEquals('marketing', $meta->get_cluster_id());
52
+ $this->assertEquals('marketing', $meta->get_cluster_label());
53
+ $meta->set_cluster_label('My label');
54
+ $this->assertEquals('My label', $meta->get_cluster_label());
55
+ }
56
+
57
+ public function testValuesAsArray()
58
+ {
59
+ $input = json_decode('{ "meta": {
60
+ "uri": "TOTO",
61
+ "totalItems": 200,
62
+ "totalItemsIsExact": true,
63
+ "pageItems": 2,
64
+ "firstPageItem": 21,
65
+ "lastPageItem": 22,
66
+ "durationMs": 66,
67
+ "firstPaFId": 1,
68
+ "lastPaFId": 1,
69
+ "producer": "SEARCH" } }');
70
+ $meta = new AfsMetaHelper($input->meta);
71
+ $meta = $meta->format();
72
+ $this->assertTrue(array_key_exists('feed', $meta));
73
+ $this->assertEquals($meta['feed'], 'TOTO');
74
+ $this->assertTrue(array_key_exists('total_replies', $meta));
75
+ $this->assertEquals($meta['total_replies'], 200);
76
+ $this->assertTrue(array_key_exists('replies_per_page', $meta));
77
+ $this->assertEquals($meta['replies_per_page'], 2);
78
+ $this->assertTrue(array_key_exists('duration', $meta));
79
+ $this->assertEquals($meta['duration'], 66);
80
+ $this->assertTrue(array_key_exists('producer', $meta));
81
+ $this->assertEquals($meta['producer'], 'SEARCH');
82
+ $this->assertFalse(array_key_exists('cluster', $meta));
83
+ $this->assertFalse(array_key_exists('cluster_label', $meta));
84
+ }
85
+ public function testValuesWithClusterAsArray()
86
+ {
87
+ $input = json_decode('{
88
+ "uri": "Catalog",
89
+ "totalItems": 61,
90
+ "totalItemsIsExact": true,
91
+ "pageItems": 20,
92
+ "firstPageItem": 1,
93
+ "lastPageItem": 20,
94
+ "durationMs": 6,
95
+ "cluster": "marketing",
96
+ "firstPaFId": 1,
97
+ "lastPaFId": 1,
98
+ "producer": "SEARCH",
99
+ "totalItemsInClusters": 2,
100
+ "nbClusters": 2
101
+ }');
102
+ $meta = new AfsMetaHelper($input);
103
+ $meta->set_cluster_label('My label');
104
+ $meta = $meta->format();
105
+ $this->assertTrue(array_key_exists('feed', $meta));
106
+ $this->assertEquals($meta['feed'], 'Catalog');
107
+ $this->assertTrue(array_key_exists('total_replies', $meta));
108
+ $this->assertEquals($meta['total_replies'], 61);
109
+ $this->assertTrue(array_key_exists('replies_per_page', $meta));
110
+ $this->assertEquals($meta['replies_per_page'], 20);
111
+ $this->assertTrue(array_key_exists('duration', $meta));
112
+ $this->assertEquals($meta['duration'], 6);
113
+ $this->assertTrue(array_key_exists('producer', $meta));
114
+ $this->assertEquals($meta['producer'], 'SEARCH');
115
+ $this->assertTrue(array_key_exists('cluster', $meta));
116
+ $this->assertEquals($meta['cluster'], 'marketing');
117
+ $this->assertTrue(array_key_exists('cluster_label', $meta));
118
+ $this->assertEquals($meta['cluster_label'], 'My label');
119
+ }
120
+ }
121
+
122
+
lib/antidot/AFS/SEARCH/TEST/pagerHelperTest.php ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/afs_pager_helper.php';
3
+ require_once 'AFS/SEARCH/afs_query.php';
4
+ require_once 'AFS/SEARCH/afs_query_coder.php';
5
+ require_once 'AFS/SEARCH/afs_meta_helper.php';
6
+
7
+ class PagerHelperTest extends PHPUnit_Framework_TestCase
8
+ {
9
+ protected function setUp()
10
+ {
11
+ $input = json_decode('{
12
+ "uri": "Catalog",
13
+ "totalItems": 62,
14
+ "totalItemsIsExact": true,
15
+ "pageItems": 10,
16
+ "firstPageItem": 1,
17
+ "lastPageItem": 20,
18
+ "durationMs": 6,
19
+ "firstPaFId": 1,
20
+ "lastPaFId": 1,
21
+ "producer": "SEARCH"
22
+ }');
23
+ $this->meta = new AfsMetaHelper($input);
24
+ }
25
+
26
+ public function testDefaultPage()
27
+ {
28
+ $input = json_decode('{
29
+ "pager": {
30
+ "nextPage": 2,
31
+ "currentPage": 1,
32
+ "page": [
33
+ 1,
34
+ 2
35
+ ]
36
+ }
37
+ }');
38
+
39
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(),
40
+ new AfsHelperConfiguration());
41
+ try {
42
+ $helper->get_previous();
43
+ $this->fail('No previous page available should have rosen exception!');
44
+ } catch (OutOfBoundsException $e) { }
45
+ $this->assertEquals($helper->get_next()->get_page(), 2);
46
+
47
+ $pages = $helper->get_pages();
48
+ $this->assertEquals(count($pages), 2);
49
+ $this->assertTrue(array_key_exists(1, $pages));
50
+ $this->assertEquals($pages[1]->get_page(), '1');
51
+ $this->assertTrue(array_key_exists(2, $pages));
52
+ $this->assertEquals($pages[2]->get_page(), '2');
53
+ $this->assertEquals($helper->get_current_no(), 1);
54
+ }
55
+
56
+ public function testLastPage()
57
+ {
58
+ $input = json_decode('{
59
+ "pager": {
60
+ "previousPage": 2,
61
+ "currentPage": 3,
62
+ "page": [
63
+ 1,
64
+ 2,
65
+ 3
66
+ ]
67
+ }
68
+ }');
69
+
70
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(),
71
+ new AfsHelperConfiguration());
72
+ try {
73
+ $helper->get_next();
74
+ $this->fail('No next page available should have rosen exception!');
75
+ } catch (OutOfBoundsException $e) { }
76
+ $this->assertEquals($helper->get_previous()->get_page(), 2);
77
+
78
+ $pages = $helper->get_pages();
79
+ $this->assertEquals(count($pages), 3);
80
+ $this->assertTrue(array_key_exists(1, $pages));
81
+ $this->assertEquals($pages[1]->get_page(), '1');
82
+ $this->assertTrue(array_key_exists(2, $pages));
83
+ $this->assertEquals($pages[2]->get_page(), '2');
84
+ $this->assertTrue(array_key_exists(3, $pages));
85
+ $this->assertEquals($pages[3]->get_page(), '3');
86
+ $this->assertEquals($helper->get_current_no(), 3);
87
+ }
88
+
89
+ public function testFormatOnLastPage()
90
+ {
91
+ $input = json_decode('{
92
+ "pager": {
93
+ "previousPage": 2,
94
+ "currentPage": 3,
95
+ "page": [
96
+ 1,
97
+ 2,
98
+ 3
99
+ ]
100
+ }
101
+ }');
102
+
103
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(),
104
+ new AfsHelperConfiguration());
105
+ $format = $helper->format();
106
+ $this->assertFalse(array_key_exists('next', $format['pages']));
107
+ $this->assertEquals($format['pages']['previous']->get_page(), 2);
108
+ $this->assertEquals($format['pages'][1]->get_page(), '1');
109
+ $this->assertEquals($format['pages'][2]->get_page(), '2');
110
+ $this->assertEquals($format['pages'][3]->get_page(), '3');
111
+ $this->assertEquals($format['current'], 3);
112
+ }
113
+
114
+ public function testSomePagesWithCoder()
115
+ {
116
+ $input = json_decode('{
117
+ "pager": {
118
+ "previousPage": 1,
119
+ "nextPage": 3,
120
+ "currentPage": 2,
121
+ "page": [
122
+ 1,
123
+ 2,
124
+ 3
125
+ ]
126
+ }
127
+ }');
128
+
129
+ $config = new AfsHelperConfiguration();
130
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
131
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(), $config);
132
+ $this->assertEquals($helper->get_previous(), 'foo.php?replies=10');
133
+ $this->assertEquals('foo.php?replies=10&page=3', $helper->get_next());
134
+
135
+ $pages = $helper->get_pages();
136
+ $this->assertEquals(count($pages), 3);
137
+ $this->assertTrue(array_key_exists(1, $pages));
138
+ $this->assertEquals($pages[1], 'foo.php?replies=10');
139
+ $this->assertTrue(array_key_exists(2, $pages));
140
+ $this->assertEquals('foo.php?replies=10&page=2', $pages[2]);
141
+ $this->assertTrue(array_key_exists(3, $pages));
142
+ $this->assertEquals('foo.php?replies=10&page=3', $pages[3]);
143
+ $this->assertEquals($helper->get_current_no(), 2);
144
+ }
145
+
146
+ public function testFormatSomePagesWithCoder()
147
+ {
148
+ $input = json_decode('{
149
+ "pager": {
150
+ "previousPage": 1,
151
+ "nextPage": 3,
152
+ "currentPage": 2,
153
+ "page": [
154
+ 1,
155
+ 2,
156
+ 3
157
+ ]
158
+ }
159
+ }');
160
+
161
+ $config = new AfsHelperConfiguration();
162
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
163
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(), $config);
164
+ $format = $helper->format();
165
+
166
+ $this->assertEquals('foo.php?replies=10', $format['pages']['previous']);
167
+ $this->assertEquals('foo.php?replies=10&page=3', $format['pages']['next']);
168
+ $this->assertEquals('foo.php?replies=10', $format['pages'][1]);
169
+ $this->assertEquals('foo.php?replies=10&page=2', $format['pages'][2]);
170
+ $this->assertEquals('foo.php?replies=10&page=3', $format['pages'][3]);
171
+ $this->assertEquals($format['current'], 2);
172
+ }
173
+
174
+ public function testRetrieveAllPagesWithoutPreviousAndNext()
175
+ {
176
+ $input = json_decode('{
177
+ "pager": {
178
+ "currentPage": 111,
179
+ "page": [
180
+ 1,
181
+ 2
182
+ ]
183
+ }
184
+ }');
185
+
186
+ $config = new AfsHelperConfiguration();
187
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
188
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(), $config);
189
+ $this->assertEquals(111, $helper->get_current_no());
190
+
191
+ $pages = $helper->get_all_pages();
192
+ $this->assertEquals(2, count($pages));
193
+ $key_value = each($pages);
194
+ $this->assertEquals('1', $key_value['key']);
195
+ $this->assertEquals('foo.php?replies=10', $key_value['value']);
196
+ $key_value = each($pages);
197
+ $this->assertEquals('2', $key_value['key']);
198
+ $this->assertEquals('foo.php?replies=10&page=2', $key_value['value']);
199
+ }
200
+
201
+ public function testRetrieveAllPagesWithPreviousWithoutNext()
202
+ {
203
+ $input = json_decode('{
204
+ "pager": {
205
+ "previousPage": 42,
206
+ "currentPage": 111,
207
+ "page": [
208
+ 1,
209
+ 2
210
+ ]
211
+ }
212
+ }');
213
+
214
+ $config = new AfsHelperConfiguration();
215
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
216
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(), $config);
217
+ $this->assertEquals(111, $helper->get_current_no());
218
+
219
+ $pages = $helper->get_all_pages();
220
+ $this->assertEquals(3, count($pages));
221
+ $key_value = each($pages);
222
+ $this->assertEquals('previous', $key_value['key']);
223
+ $this->assertEquals('foo.php?replies=10&page=42', $key_value['value']);
224
+ $key_value = each($pages);
225
+ $this->assertEquals('1', $key_value['key']);
226
+ $this->assertEquals('foo.php?replies=10', $key_value['value']);
227
+ $key_value = each($pages);
228
+ $this->assertEquals('2', $key_value['key']);
229
+ $this->assertEquals('foo.php?replies=10&page=2', $key_value['value']);
230
+ }
231
+
232
+ public function testRetrieveAllPagesWithoutPreviousWithNext()
233
+ {
234
+ $input = json_decode('{
235
+ "pager": {
236
+ "nextPage": 666,
237
+ "currentPage": 111,
238
+ "page": [
239
+ 1,
240
+ 2
241
+ ]
242
+ }
243
+ }');
244
+
245
+ $config = new AfsHelperConfiguration();
246
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
247
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(), $config);
248
+ $this->assertEquals(111, $helper->get_current_no());
249
+
250
+ $pages = $helper->get_all_pages();
251
+ $this->assertEquals(3, count($pages));
252
+ $key_value = each($pages);
253
+ $this->assertEquals('1', $key_value['key']);
254
+ $this->assertEquals('foo.php?replies=10', $key_value['value']);
255
+ $key_value = each($pages);
256
+ $this->assertEquals('2', $key_value['key']);
257
+ $this->assertEquals('foo.php?replies=10&page=2', $key_value['value']);
258
+ $key_value = each($pages);
259
+ $this->assertEquals('next', $key_value['key']);
260
+ $this->assertEquals('foo.php?replies=10&page=666', $key_value['value']);
261
+ }
262
+
263
+ public function testRetrieveAllPagesWithPreviousAndNext()
264
+ {
265
+ $input = json_decode('{
266
+ "pager": {
267
+ "previousPage": 42,
268
+ "nextPage": 666,
269
+ "currentPage": 111,
270
+ "page": [
271
+ 1,
272
+ 2
273
+ ]
274
+ }
275
+ }');
276
+
277
+ $config = new AfsHelperConfiguration();
278
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
279
+ $helper = new AfsPagerHelper($input->pager, $this->meta, new AfsQuery(), $config);
280
+ $this->assertEquals(111, $helper->get_current_no());
281
+
282
+ $pages = $helper->get_all_pages();
283
+ $this->assertEquals(4, count($pages));
284
+ $key_value = each($pages);
285
+ $this->assertEquals('previous', $key_value['key']);
286
+ $this->assertEquals('foo.php?replies=10&page=42', $key_value['value']);
287
+ $key_value = each($pages);
288
+ $this->assertEquals('1', $key_value['key']);
289
+ $this->assertEquals('foo.php?replies=10', $key_value['value']);
290
+ $key_value = each($pages);
291
+ $this->assertEquals('2', $key_value['key']);
292
+ $this->assertEquals('foo.php?replies=10&page=2', $key_value['value']);
293
+ $key_value = each($pages);
294
+ $this->assertEquals('next', $key_value['key']);
295
+ $this->assertEquals('foo.php?replies=10&page=666', $key_value['value']);
296
+ }
297
+
298
+ public function testComputedLastPage1()
299
+ {
300
+ $input = json_decode('{
301
+ "uri": "Catalog",
302
+ "totalItems": 62,
303
+ "totalItemsIsExact": true,
304
+ "pageItems": 10,
305
+ "firstPageItem": 1,
306
+ "lastPageItem": 20,
307
+ "durationMs": 6,
308
+ "firstPaFId": 1,
309
+ "lastPaFId": 1,
310
+ "producer": "SEARCH"
311
+ }');
312
+ $meta = new AfsMetaHelper($input);
313
+
314
+ $input = json_decode('{
315
+ "pager": {
316
+ "previousPage": 42,
317
+ "nextPage": 666,
318
+ "currentPage": 111,
319
+ "page": [ 1 ]
320
+ }
321
+ }');
322
+
323
+ $config = new AfsHelperConfiguration();
324
+ $helper = new AfsPagerHelper($input->pager, $meta, new AfsQuery(), $config);
325
+
326
+ $this->assertEquals(7, $helper->get_last_page_no());
327
+ $page_info = $helper->get_last_page();
328
+ $this->assertEquals(7, $page_info[0]);
329
+ $this->assertEquals(7, $page_info[1]->get_page());
330
+ }
331
+ public function testComputedLastPage2()
332
+ {
333
+ $input = json_decode('{
334
+ "uri": "Catalog",
335
+ "totalItems": 59,
336
+ "totalItemsIsExact": true,
337
+ "pageItems": 10,
338
+ "firstPageItem": 1,
339
+ "lastPageItem": 20,
340
+ "durationMs": 6,
341
+ "firstPaFId": 1,
342
+ "lastPaFId": 1,
343
+ "producer": "SEARCH"
344
+ }');
345
+ $meta = new AfsMetaHelper($input);
346
+
347
+ $input = json_decode('{
348
+ "pager": {
349
+ "previousPage": 42,
350
+ "nextPage": 666,
351
+ "currentPage": 111,
352
+ "page": [ 1 ]
353
+ }
354
+ }');
355
+
356
+ $config = new AfsHelperConfiguration();
357
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
358
+ $helper = new AfsPagerHelper($input->pager, $meta, new AfsQuery(), $config);
359
+
360
+ $this->assertEquals(6, $helper->get_last_page_no());
361
+ }
362
+ public function testComputedLastPage3()
363
+ {
364
+ $input = json_decode('{
365
+ "uri": "Catalog",
366
+ "totalItems": 60,
367
+ "totalItemsIsExact": true,
368
+ "pageItems": 10,
369
+ "firstPageItem": 1,
370
+ "lastPageItem": 20,
371
+ "durationMs": 6,
372
+ "firstPaFId": 1,
373
+ "lastPaFId": 1,
374
+ "producer": "SEARCH"
375
+ }');
376
+ $meta = new AfsMetaHelper($input);
377
+
378
+ $input = json_decode('{
379
+ "pager": {
380
+ "previousPage": 42,
381
+ "nextPage": 666,
382
+ "currentPage": 111,
383
+ "page": [ 1 ]
384
+ }
385
+ }');
386
+
387
+ $config = new AfsHelperConfiguration();
388
+ $config->set_query_coder(new AfsQueryCoder('foo.php'));
389
+ $helper = new AfsPagerHelper($input->pager, $meta, new AfsQuery(), $config);
390
+
391
+ $this->assertEquals(6, $helper->get_last_page_no());
392
+ }
393
+ }
394
+
395
+
lib/antidot/AFS/SEARCH/TEST/promoteReplyHelperTest.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_promote_reply_helper.php";
3
+
4
+ class PromoteReplyHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testPromoteWithHighlightedText()
7
+ {
8
+ $reply = json_decode('{
9
+ "docId": 1,
10
+ "uri": "http://www.wanimo.com/marques/tresor",
11
+ "title": [
12
+ {
13
+ "afs:t": "KwicMatch",
14
+ "match": "Croquettes"
15
+ },
16
+ {
17
+ "afs:t": "KwicString",
18
+ "text": " Trésor pour chien et chat"
19
+ }
20
+ ],
21
+ "abstract": [
22
+ {
23
+ "afs:t": "KwicString",
24
+ "text": "Sur Wanimo, achetez vos "
25
+ },
26
+ {
27
+ "afs:t": "KwicMatch",
28
+ "match": "croquettes"
29
+ },
30
+ {
31
+ "afs:t": "KwicString",
32
+ "text": " a moitie prix"
33
+ }
34
+ ],
35
+ "relevance": {
36
+ "rank": 1
37
+ },
38
+ "clientData": [
39
+ {
40
+ "contents": "<afs:customData xmlns:afs=\"http://ref.antidot.net/7.3/bo.xsd\"><afs:banniere>http://www.wanimo.com/images_media/Image/antidot/banniere_tresor.jpg</afs:banniere></afs:customData>",
41
+ "id": "main",
42
+ "mimeType": "text/xml"
43
+ }
44
+ ]
45
+ }');
46
+ $helper = new AfsPromoteReplyHelper($reply);
47
+ $this->assertEquals('Croquettes Trésor pour chien et chat', $helper->get_title());
48
+ $this->assertEquals('Sur Wanimo, achetez vos croquettes a moitie prix', $helper->get_abstract());
49
+ $this->assertEquals('http://www.wanimo.com/marques/tresor', $helper->get_uri());
50
+ $this->assertEquals('http://www.wanimo.com/images_media/Image/antidot/banniere_tresor.jpg', $helper->get_custom_data('banniere'));
51
+ }
52
+
53
+ public function testRequiredCustomDataNotAvailable()
54
+ {
55
+ $reply = json_decode('{
56
+ "docId": 1,
57
+ "uri": "http://www.wanimo.com/marques/tresor",
58
+ "relevance": {
59
+ "rank": 1
60
+ },
61
+ "clientData": [
62
+ {
63
+ "contents": "<afs:customData xmlns:afs=\"http://ref.antidot.net/7.3/bo.xsd\"><afs:banniere>http://www.wanimo.com/images_media/Image/antidot/banniere_tresor.jpg</afs:banniere></afs:customData>",
64
+ "id": "main",
65
+ "mimeType": "text/xml"
66
+ }
67
+ ]
68
+ }');
69
+ $helper = new AfsPromoteReplyHelper($reply);
70
+ try {
71
+ $helper->get_custom_data('unknown');
72
+ } catch (Exception $e) {
73
+ return;
74
+ }
75
+ $this->fail('Exception should have been raised for unknown key');
76
+ }
77
+
78
+ public function testPromoteRetrieveCustomDataAsArray()
79
+ {
80
+ $reply = json_decode('{
81
+ "docId": 1,
82
+ "uri": "http://www.wanimo.com/marques/tresor",
83
+ "title": [
84
+ {
85
+ "afs:t": "KwicString",
86
+ "text": "Foo"
87
+ }
88
+ ],
89
+ "abstract": [
90
+ {
91
+ "afs:t": "KwicString",
92
+ "text": "Bar"
93
+ }
94
+ ],
95
+ "relevance": {
96
+ "rank": 1
97
+ },
98
+ "clientData": [
99
+ {
100
+ "contents": "<afs:customData xmlns:afs=\"http://ref.antidot.net/7.3/bo.xsd\"><afs:banniere>BAN1</afs:banniere><afs:foo>FOO</afs:foo></afs:customData>",
101
+ "id": "main",
102
+ "mimeType": "text/xml"
103
+ }
104
+ ]
105
+ }');
106
+ $helper = new AfsPromoteReplyHelper($reply);
107
+ $this->assertEquals(array('banniere' => 'BAN1', 'foo' => 'FOO'),
108
+ $helper->get_custom_data());
109
+ }
110
+ }
111
+
112
+
lib/antidot/AFS/SEARCH/TEST/promoteReplysetHelperTest.php ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_promote_replyset_helper.php";
3
+
4
+ class PromoteReplysetHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testBlabla()
7
+ {
8
+ $input = json_decode('{
9
+ "header": {
10
+ "query": {
11
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
12
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
13
+ "date": "2013-10-02T15:48:41+0200",
14
+ "queryParam": [
15
+ {
16
+ "name": "afs:service",
17
+ "value": "42"
18
+ }
19
+ ],
20
+ "mainCtx": { "textQuery": "title" },
21
+ "textQuery": "title"
22
+ },
23
+ "user": {
24
+ "requestMethod": "GET",
25
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0 Iceweasel/23.0",
26
+ "address": "127.0.0.1",
27
+ "output": {
28
+ "format": "JSON",
29
+ "encoding": "gzip",
30
+ "charset": "UTF-8"
31
+ }
32
+ },
33
+ "performance": { "durationMs": 666 },
34
+ "info": { }
35
+ },
36
+ "replySet": [
37
+ {
38
+ "meta": {
39
+ "uri": "Promote",
40
+ "totalItems": 200,
41
+ "totalItemsIsExact": true,
42
+ "pageItems": 2,
43
+ "firstPageItem": 3,
44
+ "lastPageItem": 4,
45
+ "durationMs": 42,
46
+ "firstPaFId": 1,
47
+ "lastPaFId": 1,
48
+ "producer": "SEARCH"
49
+ },
50
+ "facets": {
51
+ "facet": [
52
+ {
53
+ "afs:t": "FacetTree",
54
+ "node": [
55
+ {
56
+ "key": "false",
57
+ "labels": [ { "label": "BAD" } ],
58
+ "items": 67
59
+ },
60
+ {
61
+ "key": "true",
62
+ "labels": [ { "label": "GOOD" } ],
63
+ "items": 133
64
+ }
65
+ ],
66
+ "layout": "TREE",
67
+ "type": "BOOL",
68
+ "id": "BOOL",
69
+ "labels": [ { "label": "Boolean facet" } ]
70
+ }
71
+ ]
72
+ },
73
+ "content": {
74
+ "reply": [
75
+ {
76
+ "docId": 198,
77
+ "uri": "http://foo.bar.baz/116",
78
+ "title": [
79
+ {
80
+ "afs:t": "KwicString",
81
+ "text": "The "
82
+ },
83
+ {
84
+ "afs:t": "KwicMatch",
85
+ "match": "title"
86
+ },
87
+ {
88
+ "afs:t": "KwicString",
89
+ "text": " 116"
90
+ }
91
+ ],
92
+ "abstract": [
93
+ {
94
+ "afs:t": "KwicString",
95
+ "text": "viens de tomber a monté d\'un nouveau cran dans l\'étrangeté. Jamais dans l\'Histoire de l\'humanité il n\'a existé de civilisation sans enfants. Je tente d\'en imaginer les conséquences. George, qui m\'a deviné, énumère: - Comme nous ne nous reproduisons pas, la moitié féminine de l\'humanité"
96
+ },
97
+ { "afs:t": "KwicTruncate" }
98
+ ],
99
+ "relevance": {
100
+ "rank": 3
101
+ },
102
+ "clientData": [
103
+ {
104
+ "contents": "<clientdata>{&quot;data&quot;: [{&quot;data1&quot;: &quot;data 0&quot;}, {&quot;data1&quot;: &quot;data 1&quot;}, {&quot;m1&quot;: &quot;m 1&quot;, &quot;m0&quot;: &quot;m 0&quot;, &quot;m3&quot;: &quot;m 3&quot;, &quot;m2&quot;: &quot;m 2&quot;}]}</clientdata>",
105
+ "id": "main",
106
+ "mimeType": "text/xml"
107
+ }
108
+ ]
109
+ },
110
+ {
111
+ "docId": 197,
112
+ "uri": "http://foo.bar.baz/81",
113
+ "title": [
114
+ {
115
+ "afs:t": "KwicString",
116
+ "text": "The "
117
+ },
118
+ {
119
+ "afs:t": "KwicMatch",
120
+ "match": "title"
121
+ },
122
+ {
123
+ "afs:t": "KwicString",
124
+ "text": " 81"
125
+ }
126
+ ],
127
+ "abstract": [
128
+ {
129
+ "afs:t": "KwicString",
130
+ "text": "morose... il n\'y a pas de quoi en être fier. J\'émets des doutes, certes; mais au fond de moi, j\'ai confiance. Ne vous en étonnez-vous pas? Il y aurait pourtant bien de quoi! Voici que va surgir de nulle part une collectivité cachée comme aucune autre. Rien de pareil, jamais, n\'est arrivé dans"
131
+ },
132
+ {
133
+ "afs:t": "KwicTruncate"
134
+ }
135
+ ],
136
+ "relevance": {
137
+ "rank": 4
138
+ },
139
+ "clientData": [
140
+ {
141
+ "contents": "<clientdata>&lt;data&gt;&lt;data1&gt;data 0&lt;/data1&gt;&lt;data1&gt;data 1&lt;/data1&gt;&lt;multi&gt;&lt;m0&gt;m 0&lt;/m0&gt;&lt;m1&gt;m 1&lt;/m1&gt;&lt;m2&gt;m 2&lt;/m2&gt;&lt;m3&gt;m 3&lt;/m3&gt;&lt;/multi&gt;&lt;/data&gt;</clientdata>",
142
+ "id": "main",
143
+ "mimeType": "text/xml"
144
+ }
145
+ ]
146
+ }
147
+ ]
148
+ },
149
+ "pager": {
150
+ "previousPage": 1,
151
+ "nextPage": 3,
152
+ "currentPage": 2,
153
+ "page": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
154
+ }
155
+ }
156
+ ]
157
+ }');
158
+
159
+ $config = new AfsHelperConfiguration();
160
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
161
+ $helper = new AfsPromoteReplysetHelper($input->replySet[0], $config);
162
+
163
+ $meta = $helper->get_meta();
164
+ $this->assertEquals('Promote', $meta->get_feed());
165
+ $this->assertEquals('200', $meta->get_total_replies());
166
+ $this->assertEquals('42', $meta->get_duration());
167
+ $this->assertEquals('SEARCH', $meta->get_producer());
168
+
169
+ $this->assertEquals(2, $helper->get_nb_replies());
170
+ $replies = $helper->get_replies();
171
+ $this->assertEquals('The title 116', $replies[0]->title);
172
+ $this->assertEquals('The title 81', $replies[1]->title);
173
+ // and so on...
174
+ }
175
+ }
176
+
177
+
lib/antidot/AFS/SEARCH/TEST/queryCoderTest.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/afs_query_coder.php';
3
+ require_once 'AFS/SEARCH/afs_search_connector.php';
4
+
5
+ class QueryCoderTest extends PHPUnit_Framework_TestCase
6
+ {
7
+ public function testBuildQueryFromEmptyParameter()
8
+ {
9
+ $coder = new AfsQueryCoder();
10
+ $query = $coder->build_query(array());
11
+ $this->assertEquals(0, count($query->get_filters()));
12
+ $this->assertFalse($query->has_query());
13
+ // and so on
14
+ }
15
+
16
+ public function testBuildQueryFromQueryParameter()
17
+ {
18
+ $coder = new AfsQueryCoder();
19
+ $query = $coder->build_query(array('query' => 'FOO'));
20
+ $this->assertEquals(0, count($query->get_filters()));
21
+ $this->assertTrue($query->has_query());
22
+ $this->assertEquals('FOO', $query->get_query());
23
+ // and so on
24
+ }
25
+
26
+ public function testBuildQueryFromFilterParameter()
27
+ {
28
+ $coder = new AfsQueryCoder();
29
+ $query = $coder->build_query(array('filter' => 'FOO_bar_baz'));
30
+ $this->assertFalse($query->has_query());
31
+ $this->assertEquals(1, count($query->get_filters()));
32
+ $this->assertTrue($query->has_filter('FOO', 'bar'));
33
+ $this->assertTrue($query->has_filter('FOO', 'baz'));
34
+ $this->assertFalse($query->has_filter('FOO', 'bat'));
35
+ // and so on
36
+ }
37
+
38
+ public function testBuildQueryFromUnknownParameter()
39
+ {
40
+ $coder = new AfsQueryCoder();
41
+ $query = $coder->build_query(array('X' => '42', 'Y' => '666'));
42
+ $this->assertEquals(0, count($query->get_filters()));
43
+ $this->assertFalse($query->has_query());
44
+ // and so on
45
+ }
46
+ }
lib/antidot/AFS/SEARCH/TEST/queryTest.php ADDED
@@ -0,0 +1,1027 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/afs_query.php';
3
+ require_once 'AFS/SEARCH/afs_interval.php';
4
+ require_once 'AFS/SEARCH/FILTER/afs_filter.php';
5
+
6
+
7
+ class QueryTest extends PHPUnit_Framework_TestCase
8
+ {
9
+ public function testSetQuery()
10
+ {
11
+ $query = new AfsQuery();
12
+ $query = $query->set_query('foo');
13
+ $this->assertTrue($query->get_query() == 'foo');
14
+ }
15
+ public function testSetNewQueryValue()
16
+ {
17
+ $query = new AfsQuery();
18
+ $query = $query->set_query('foo');
19
+ $query = $query->set_query('bar');
20
+ $this->assertFalse($query->get_query() == 'foo');
21
+ $this->assertTrue($query->get_query() == 'bar');
22
+ }
23
+
24
+ public function testHasNoQuery()
25
+ {
26
+ $query = new AfsQuery();
27
+ $this->assertFalse($query->has_query());
28
+ }
29
+ public function testHasQuery()
30
+ {
31
+ $query = new AfsQuery();
32
+ $query = $query->set_query('foo');
33
+ $this->assertTrue($query->has_query());
34
+ }
35
+
36
+ public function testAddFilterValue()
37
+ {
38
+ $query = new AfsQuery();
39
+ $query = $query->add_filter('foo', 'bar');
40
+ $this->assertTrue($query->has_filter('foo', 'bar'));
41
+ }
42
+ public function testAddSameFilterValue()
43
+ {
44
+ $query = new AfsQuery();
45
+ $query = $query->add_filter('foo', 'bar');
46
+ try {
47
+ $query = $query->add_filter('foo', 'bar');
48
+ $this->assertTrue($query->has_filter('foo', 'bar'));
49
+ } catch (Exception $e) {
50
+ $this->fail('Cannot set same filter value twice!');
51
+ }
52
+ }
53
+ public function testAddFilterValues()
54
+ {
55
+ $query = new AfsQuery();
56
+ $query = $query->add_filter('foo', 'bar');
57
+ $query = $query->add_filter('foo', 'baz');
58
+ $this->assertTrue($query->has_filter('foo', 'bar'));
59
+ $this->assertTrue($query->has_filter('foo', 'baz'));
60
+ }
61
+ public function testAddFilterArrayValues()
62
+ {
63
+ $query = new AfsQuery();
64
+ $query = $query->add_filter('foo', array('bar', 'baz'));
65
+ $this->assertTrue($query->has_filter('foo', 'bar'));
66
+ $this->assertTrue($query->has_filter('foo', 'baz'));
67
+ }
68
+ public function testAddValuesToFilters()
69
+ {
70
+ $query = new AfsQuery();
71
+ $query = $query->add_filter('foo', 'bar');
72
+ $query = $query->add_filter('foo', 'baz');
73
+ $query = $query->add_filter('fox', 'bat');
74
+ $query = $query->add_filter('fox', 'bas');
75
+ $this->assertTrue($query->has_filter('foo', 'bar'));
76
+ $this->assertTrue($query->has_filter('foo', 'baz'));
77
+ $this->assertTrue($query->has_filter('fox', 'bat'));
78
+ $this->assertTrue($query->has_filter('fox', 'bas'));
79
+ }
80
+
81
+ public function testSetValueToFilter()
82
+ {
83
+ $query = new AfsQuery();
84
+ $query = $query->set_filter('foo', 'bar');
85
+ $this->assertTrue($query->has_filter('foo', 'bar'));
86
+ }
87
+ public function testOverwriteValueToFilter()
88
+ {
89
+ $query = new AfsQuery();
90
+ $query = $query->add_filter('foo', 'bar');
91
+ $query = $query->add_filter('foo', 'baz');
92
+ $query = $query->set_filter('foo', 'foz');
93
+ $this->assertFalse($query->has_filter('foo', 'bar'));
94
+ $this->assertFalse($query->has_filter('foo', 'baz'));
95
+ $this->assertTrue($query->has_filter('foo', 'foz'));
96
+ }
97
+ public function testOverwriteMultipleValuesForFilter()
98
+ {
99
+ $query = new AfsQuery();
100
+ $query = $query->add_filter('foo', 'bar');
101
+ $query = $query->add_filter('foo', 'baz');
102
+ $query = $query->set_filter('foo', array('for', 'foz'));
103
+ $this->assertFalse($query->has_filter('foo', 'bar'));
104
+ $this->assertFalse($query->has_filter('foo', 'baz'));
105
+ $this->assertTrue($query->has_filter('foo', 'foz'));
106
+ $this->assertTrue($query->has_filter('foo', 'foz'));
107
+ }
108
+
109
+ public function testSetIntervalValueToFilter()
110
+ {
111
+ $query = new AfsQuery();
112
+ $query = $query->set_filter('foo', AfsInterval::create(42, 666));
113
+ $this->assertTrue($query->has_filter('foo', '[42 .. 666]'));
114
+ }
115
+
116
+ public function testHasNoFilter()
117
+ {
118
+ $query = new AfsQuery();
119
+ $this->assertFalse($query->has_filter('foo', 'bar'));
120
+ }
121
+ public function testHasFilterWithWrongValue()
122
+ {
123
+ $query = new AfsQuery();
124
+ $query = $query->add_filter('foo', 'baz');
125
+ $this->assertFalse($query->has_filter('foo', 'bar'));
126
+ }
127
+ public function testHasFilterWithRightValue()
128
+ {
129
+ $query = new AfsQuery();
130
+ $query = $query->add_filter('foo', 'bar');
131
+ $this->assertTrue($query->has_filter('foo', 'bar'));
132
+ }
133
+ public function testHasFilterWithValueEqualToZero()
134
+ {
135
+ $query = new AfsQuery();
136
+ $query = $query->add_filter('foo', '4');
137
+ $this->assertTrue($query->has_filter('foo', '4'));
138
+ $this->assertFalse($query->has_filter('foo', '0'));
139
+ }
140
+
141
+ public function testRemoveValueFromUnexistingFilter()
142
+ {
143
+ $query = new AfsQuery();
144
+ try {
145
+ $query->remove_filter('foo', 'bar');
146
+ } catch (Exception $e) {
147
+ $this->fail('Exception raised: ' . $e);
148
+ }
149
+ }
150
+ public function testRemoveUnexistingFilterValue()
151
+ {
152
+ $query = new AfsQuery();
153
+ $query = $query->add_filter('foo', 'baz');
154
+ try {
155
+ $query->remove_filter('foo', 'bar');
156
+ } catch (Exception $e) {
157
+ $this->fail('Exception raised: ' . $e);
158
+ }
159
+ }
160
+ public function testRemoveExistingFilterValue()
161
+ {
162
+ $query = new AfsQuery();
163
+ $query = $query->add_filter('foo', 'bar');
164
+ $query = $query->remove_filter('foo', 'bar');
165
+ $this->assertFalse($query->has_filter('foo', 'bar'));
166
+ }
167
+
168
+ public function testGetListOfValuesForUnexistingFilter()
169
+ {
170
+ $query = new AfsQuery();
171
+ try {
172
+ $values = $query->get_filter_values('foo');
173
+ } catch (Exception $e) {
174
+ return;
175
+ }
176
+ $this->fail('Getting values from unexisting filter should raise exception!');
177
+ }
178
+ public function testGetListOfFilterValues()
179
+ {
180
+ $query = new AfsQuery();
181
+ $query = $query->add_filter('foo', 'bar');
182
+ $query = $query->add_filter('foo', 'baz');
183
+ $values = $query->get_filter_values('foo');
184
+ $this->assertTrue(in_array('bar', $values));
185
+ $this->assertTrue(in_array('baz', $values));
186
+ }
187
+
188
+ public function testGetEmptyListOfFilters()
189
+ {
190
+ $query = new AfsQuery();
191
+ $filters = $query->get_filters();
192
+ $this->assertTrue(empty($filters));
193
+ }
194
+ public function testGetListOfFilters()
195
+ {
196
+ $query = new AfsQuery();
197
+ $query = $query->add_filter('foo', 'bar');
198
+ $query = $query->add_filter('foz', 'baz');
199
+ $filters = $query->get_filters();
200
+ $this->assertFalse(empty($filters));
201
+ $this->assertTrue(in_array('foo', $query->get_filters()));
202
+ $this->assertTrue(in_array('foz', $query->get_filters()));
203
+ }
204
+
205
+ public function testNoAdvancedFilter()
206
+ {
207
+ $query = new AfsQuery();
208
+ $this->assertFalse($query->has_advanced_filter());
209
+ }
210
+ public function testOneAdvancedFilter()
211
+ {
212
+ $query = new AfsQuery();
213
+ $query = $query->set_advanced_filter(filter('FOO')->less->value('bar'));
214
+ $this->assertTrue($query->has_advanced_filter());
215
+ $advanced_filters = $query->get_advanced_filters();
216
+ $this->assertEquals(1, count($advanced_filters));
217
+ $this->assertEquals('FOO<bar', $advanced_filters[0]);
218
+ }
219
+ public function testMultipleAdvancedFilter()
220
+ {
221
+ $query = new AfsQuery();
222
+ $query = $query->add_advanced_filter(filter('FOO')->less->value('bar'))
223
+ ->add_advanced_filter(filter('FOZ')->greater->value('baz'));
224
+ $this->assertTrue($query->has_advanced_filter());
225
+ $advanced_filters = $query->get_advanced_filters();
226
+ $this->assertEquals(2, count($advanced_filters));
227
+ $this->assertEquals('FOO<bar', $advanced_filters[0]);
228
+ $this->assertEquals('FOZ>baz', $advanced_filters[1]);
229
+ }
230
+ public function testOverrideAdvancedFilter()
231
+ {
232
+ $query = new AfsQuery();
233
+ $query = $query->set_advanced_filter(filter('FOO')->less->value('bar'));
234
+ $query = $query->set_advanced_filter(filter('FOZ')->less->value('baz'));
235
+ $this->assertTrue($query->has_advanced_filter());
236
+ $advanced_filters = $query->get_advanced_filters();
237
+ $this->assertEquals(1, count($advanced_filters));
238
+ $this->assertEquals('FOZ<baz', $advanced_filters[0]);
239
+ }
240
+ public function testResetAdvancedFilter()
241
+ {
242
+ $query = new AfsQuery();
243
+ $query = $query->set_advanced_filter(filter('FOO')->less->value('bar'));
244
+ $query = $query->reset_advanced_filter();
245
+ $this->assertFalse($query->has_advanced_filter());
246
+ }
247
+
248
+ public function testHasNoFeedSet()
249
+ {
250
+ $query = new AfsQuery();
251
+ $this->assertFalse($query->has_feed());
252
+ }
253
+ public function testHasFeedName()
254
+ {
255
+ $query = new AfsQuery();
256
+ $query = $query->set_feed('foo');
257
+ $this->assertTrue($query->has_feed());
258
+ $this->assertTrue(in_array('foo', $query->get_feeds()));
259
+ }
260
+ public function testHasFeedNames()
261
+ {
262
+ $query = new AfsQuery();
263
+ $query = $query->add_feed('foo');
264
+ $query = $query->add_feed('bar');
265
+ $this->assertTrue($query->has_feed());
266
+ $this->assertTrue(in_array('foo', $query->get_feeds()));
267
+ $this->assertTrue(in_array('bar', $query->get_feeds()));
268
+ }
269
+ public function testResetFeedName()
270
+ {
271
+ $query = new AfsQuery();
272
+ $query = $query->add_feed('foo');
273
+ $query = $query->add_feed('bar');
274
+ $query = $query->set_feed('baz');
275
+ $this->assertTrue($query->has_feed());
276
+ $this->assertFalse(in_array('foo', $query->get_feeds()));
277
+ $this->assertFalse(in_array('bar', $query->get_feeds()));
278
+ $this->assertTrue(in_array('baz', $query->get_feeds()));
279
+ }
280
+
281
+ public function testDefaultPage()
282
+ {
283
+ $query = new AfsQuery();
284
+ $this->assertTrue($query->get_page() == 1);
285
+ }
286
+ public function testSetPage()
287
+ {
288
+ $query = new AfsQuery();
289
+ $query = $query->set_page(42);
290
+ $this->assertTrue($query->get_page() == 42);
291
+ }
292
+ public function testResetPageOnNewQuery()
293
+ {
294
+ $query = new AfsQuery();
295
+ $query = $query->set_page(42);
296
+ $query = $query->set_query('foo');
297
+ $this->assertTrue($query->get_page() == 1);
298
+ }
299
+ public function testResetPageOnNewFeed()
300
+ {
301
+ $query = new AfsQuery();
302
+ $query = $query->set_page(42);
303
+ $query = $query->set_feed('foo');
304
+ $this->assertTrue($query->get_page() == 1);
305
+
306
+ $query = $query->set_page(42);
307
+ $this->assertTrue($query->get_page() == 42);
308
+ $query = $query->add_feed('foz');
309
+ $this->assertTrue($query->get_page() == 1);
310
+ }
311
+ public function testResetPageOnNewFacet()
312
+ {
313
+ $query = new AfsQuery();
314
+ $query = $query->set_page(42);
315
+ $query = $query->set_filter('foo', 'bar');
316
+ $this->assertTrue($query->get_page() == 1);
317
+
318
+ $query = $query->set_page(42);
319
+ $this->assertTrue($query->get_page() == 42);
320
+ $query = $query->add_filter('foo', 'baz');
321
+ $this->assertTrue($query->get_page() == 1);
322
+ }
323
+ public function testResetPageOnNewRepliesPerPage()
324
+ {
325
+ $query = new AfsQuery();
326
+ $query = $query->set_page(42);
327
+ $query = $query->set_replies(5);
328
+ $this->assertTrue($query->get_page() == 1);
329
+ }
330
+ public function testResetPageOnNewLanguage()
331
+ {
332
+ $query = new AfsQuery();
333
+ $query = $query->set_page(42);
334
+ $query = $query->set_lang('fr');
335
+ $this->assertEquals(42, $query->get_page());
336
+
337
+ $query = $query->reset_lang();
338
+ $this->assertEquals(42, $query->get_page());
339
+ }
340
+
341
+ public function testDefaultRepliesPerPage()
342
+ {
343
+ $query = new AfsQuery();
344
+ $this->assertTrue($query->get_replies() == 10);
345
+ }
346
+ public function testSetRepliesPerPage()
347
+ {
348
+ $query = new AfsQuery();
349
+ $query = $query->set_replies(42);
350
+ $this->assertTrue($query->get_replies() == 42);
351
+ }
352
+
353
+ public function testSetLanguage()
354
+ {
355
+ $query = new AfsQuery();
356
+ $this->assertFalse($query->has_lang());
357
+ $query = $query->set_lang('en');
358
+ $this->assertTrue($query->get_lang() == 'en');
359
+ }
360
+ public function testSetLanguageWithRegionCode()
361
+ {
362
+ $query = new AfsQuery();
363
+ foreach (array('en-US', 'en_US', 'EN-us') as $lang)
364
+ {
365
+ $query = $query->set_lang($lang);
366
+ $lang = strtolower(strtr($lang, '_', '-'));
367
+ $this->assertTrue($query->get_lang() == $lang);
368
+ }
369
+ }
370
+ public function testResetLanguage()
371
+ {
372
+ $query = new AfsQuery();
373
+ $query = $query->set_lang('en');
374
+ $this->assertTrue($query->get_lang() == 'en');
375
+ $query = $query->reset_lang();
376
+ $this->assertTrue($query->get_lang()->lang == null);
377
+ }
378
+ public function testSetInvalidLanguage()
379
+ {
380
+ $query = new AfsQuery();
381
+ foreach (array('eng', 'en-', 'en_', 'en-U', 'en-USA') as $lang)
382
+ {
383
+ try {
384
+ $query = $query->set_lang($lang);
385
+ } catch (Exception $e) {
386
+ continue;
387
+ }
388
+ $this->fail('Should have failed for invalid language: '. $lang);
389
+ }
390
+ }
391
+
392
+ public function testSetSortOrder()
393
+ {
394
+ $query = new AfsQuery();
395
+ $this->assertFalse($query->has_sort());
396
+ $this->assertFalse($query->has_sort('foo'));
397
+ $query = $query->set_sort('afs:relevance');
398
+ $this->assertTrue($query->has_sort());
399
+ $this->assertTrue($query->has_sort('afs:relevance'));
400
+ $this->assertEquals(AfsSortOrder::DESC, $query->get_sort_order('afs:relevance'));
401
+ }
402
+ public function testResetSortOrder()
403
+ {
404
+ $query = new AfsQuery();
405
+ $query = $query->set_sort('afs:relevance', AfsSortOrder::DESC)
406
+ ->add_sort('afs:words', AfsSortOrder::ASC)
407
+ ->add_sort('foo');
408
+ $this->assertTrue($query->has_sort('afs:relevance'));
409
+ $this->assertEquals(AfsSortOrder::DESC, $query->get_sort_order('afs:relevance'));
410
+ $this->assertTrue($query->has_sort('afs:words'));
411
+ $this->assertEquals(AfsSortOrder::ASC, $query->get_sort_order('afs:words'));
412
+ $this->assertTrue($query->has_sort('foo'));
413
+ $this->assertEquals(AfsSortOrder::DESC, $query->get_sort_order('foo'));
414
+ $query = $query->reset_sort();
415
+ $this->assertFalse($query->has_sort());
416
+ }
417
+ public function testCustomSortOrderFacet()
418
+ {
419
+ $query = new AfsQuery();
420
+ $query = $query->set_sort('relevance');
421
+ $this->assertTrue($query->has_sort('relevance'));
422
+ $this->assertEquals(AfsSortOrder::DESC, $query->get_sort_order('relevance'));
423
+ }
424
+ public function testInvalidSortOrderOrder()
425
+ {
426
+ $query = new AfsQuery();
427
+ try {
428
+ $query = $query->set_sort('afs:relevance', 'DES');
429
+ } catch (Exception $e) {
430
+ return;
431
+ }
432
+ $this->fail('Invalid sort order parameter should have raised an exception!');
433
+ }
434
+
435
+ public function testNoCluster()
436
+ {
437
+ $query = new AfsQuery();
438
+ $this->assertFalse($query->has_cluster());
439
+ $this->assertFalse($query->has_max_clusters());
440
+ $this->assertFalse($query->has_overspill());
441
+ $this->assertEquals(null, $query->get_count_mode());
442
+ }
443
+ public function testSimpleCluster()
444
+ {
445
+ $query = new AfsQuery();
446
+ $query = $query->set_cluster('Foo', 3);
447
+ $this->assertTrue($query->has_cluster());
448
+ $this->assertEquals('Foo', $query->get_cluster_id());
449
+ $this->assertEquals(3, $query->get_nb_replies_per_cluster());
450
+ $this->assertFalse($query->has_max_clusters());
451
+ $this->assertFalse($query->has_overspill());
452
+ $this->assertEquals(null, $query->get_count_mode());
453
+ }
454
+ public function testClusterLimit()
455
+ {
456
+ $query = new AfsQuery();
457
+ $query = $query->set_cluster('Foo', 3)->set_max_clusters(42);
458
+ $this->assertTrue($query->has_cluster());
459
+ $this->assertTrue($query->has_max_clusters());
460
+ $this->assertEquals(42, $query->get_max_clusters());
461
+ $this->assertFalse($query->has_overspill());
462
+ $this->assertEquals(null, $query->get_count_mode());
463
+ }
464
+ public function testClusterOverspill()
465
+ {
466
+ $query = new AfsQuery();
467
+ $query = $query->set_cluster('Foo', 3)->set_overspill();
468
+ $this->assertTrue($query->has_cluster());
469
+ $this->assertFalse($query->has_max_clusters());
470
+ $this->assertTrue($query->has_overspill());
471
+ $this->assertEquals(null, $query->get_count_mode());
472
+
473
+ $query = $query->set_overspill(false);
474
+ $this->assertFalse($query->has_overspill());
475
+ }
476
+ public function testClusterCountMode()
477
+ {
478
+ $query = new AfsQuery();
479
+ $query = $query->set_cluster('Foo', 3)->set_count(AfsCount::CLUSTERS);
480
+ $this->assertTrue($query->has_cluster());
481
+ $this->assertFalse($query->has_max_clusters());
482
+ $this->assertFalse($query->has_overspill());
483
+ $this->assertEquals(AfsCount::CLUSTERS, $query->get_count_mode());
484
+ }
485
+ public function testClusterDocumentsCountMode()
486
+ {
487
+ $query = new AfsQuery();
488
+ $query = $query->set_cluster('Foo', 3)->set_count(AfsCount::DOCUMENTS);
489
+ $this->assertTrue($query->has_cluster());
490
+ $this->assertFalse($query->has_max_clusters());
491
+ $this->assertFalse($query->has_overspill());
492
+ $this->assertEquals(AfsCount::DOCUMENTS, $query->get_count_mode());
493
+ }
494
+ public function testUnsetCluster()
495
+ {
496
+ $query = new AfsQuery();
497
+ $query = $query->set_cluster('Foo', 3)
498
+ ->set_overspill()
499
+ ->set_max_clusters(42)
500
+ ->set_count(AfsCount::CLUSTERS);
501
+ $this->assertTrue($query->has_cluster());
502
+ $this->assertTrue($query->has_max_clusters());
503
+ $this->assertTrue($query->has_overspill());
504
+ $this->assertEquals(AfsCount::CLUSTERS, $query->get_count_mode());
505
+
506
+ $query = $query->unset_cluster();
507
+ $this->assertFalse($query->has_cluster());
508
+ $this->assertFalse($query->has_max_clusters());
509
+ $this->assertFalse($query->has_overspill());
510
+ $this->assertEquals(null, $query->get_count_mode());
511
+ }
512
+ public function testUninitializedClusterFailsOnMaxClusters()
513
+ {
514
+ $query = new AfsQuery();
515
+ try {
516
+ $query->set_max_clusters(42);
517
+ $this->fail('Setting Max Clusters on uninitialized cluster should have failed!');
518
+ } catch (AfsClusterException $e) {}
519
+ }
520
+ public function testUninitializedClusterFailsOnCount()
521
+ {
522
+ $query = new AfsQuery();
523
+ try {
524
+ $query->set_count(AfsCount::CLUSTERS);
525
+ $this->fail('Setting cluster count mode on uninitialized cluster should have failed!');
526
+ } catch (AfsClusterException $e) {}
527
+ }
528
+ public function testUninitializedClusterFailsOnOverspill()
529
+ {
530
+ $query = new AfsQuery();
531
+ try {
532
+ $query->set_overspill();
533
+ $this->fail('Setting overspill on uninitialized cluster should have failed!');
534
+ } catch (AfsClusterException $e) {}
535
+ }
536
+
537
+
538
+ public function testOriginDefaultValue()
539
+ {
540
+ $query = new AfsQuery();
541
+ $this->assertNull($query->get_from());
542
+ }
543
+ public function testOriginKnownValue()
544
+ {
545
+ $query = new AfsQuery();
546
+ $query = $query->set_from(AfsOrigin::RTE);
547
+ $this->assertEquals(AfsOrigin::RTE, $query->get_from());
548
+ }
549
+ public function testOriginUnknownValue()
550
+ {
551
+ $query = new AfsQuery();
552
+ try {
553
+ $query = $query->set_from('UnknownValue');
554
+ } catch (Exception $e) {
555
+ return;
556
+ }
557
+ $this->fail('Unknown query origin value should have raised exception!');
558
+ }
559
+ public function testOriginAutoSetForQuery()
560
+ {
561
+ $query = new AfsQuery();
562
+ $query = $query->auto_set_from()->set_query('foo');
563
+ $this->assertEquals(AfsOrigin::SEARCHBOX, $query->get_from());
564
+ }
565
+ public function testOriginAutoSetForFilters()
566
+ {
567
+ $query = new AfsQuery();
568
+ $query = $query->auto_set_from()->set_filter('foo', 'bar');
569
+ $this->assertEquals(AfsOrigin::FACET, $query->get_from());
570
+ }
571
+ public function testOriginAutoSetForPager()
572
+ {
573
+ $query = new AfsQuery();
574
+ $query = $query->auto_set_from()->set_page(42);
575
+ $this->assertEquals(AfsOrigin::PAGER, $query->get_from());
576
+ }
577
+ public function testOriginNotAutoSet()
578
+ {
579
+ $query = new AfsQuery();
580
+ $query = $query->set_query('query');
581
+ $this->assertTrue(is_null($query->get_from()));
582
+ }
583
+
584
+ public function testNoUserId()
585
+ {
586
+ $query = new AfsQuery();
587
+ $id = $query->get_user_id();
588
+ $this->assertFalse(empty($id));
589
+ }
590
+ public function testUserId()
591
+ {
592
+ $query = new AfsQuery();
593
+ $query = $query->set_user_id('foo');
594
+ $this->assertEquals('foo', $query->get_user_id());
595
+ }
596
+
597
+ public function testNoSessionId()
598
+ {
599
+ $query = new AfsQuery();
600
+ $id = $query->get_session_id();
601
+ $this->assertFalse(empty($id));
602
+ }
603
+ public function testSessionId()
604
+ {
605
+ $query = new AfsQuery();
606
+ $query = $query->set_session_id('foo');
607
+ $this->assertEquals('foo', $query->get_session_id());
608
+ }
609
+
610
+ public function testUserIdInitFromManager()
611
+ {
612
+ $name = 'MyUserCookie';
613
+ $_COOKIE[$name] = 'foo';
614
+ $mgr = new AfsUserSessionManager($name);
615
+ $query = new AfsQuery();
616
+ $user_id = $query->get_user_id();
617
+ $session_id = $query->get_session_id();
618
+ $query = $query->initialize_user_and_session_id($mgr);
619
+ $this->assertFalse($user_id == $query->get_user_id());
620
+ $this->assertEquals($session_id, $query->get_session_id());
621
+ }
622
+ public function testSessionIdInitFromManager()
623
+ {
624
+ $name = 'MySessionCookie';
625
+ $_COOKIE[$name] = 'bar';
626
+ $mgr = new AfsUserSessionManager('blabla', $name);
627
+ $query = new AfsQuery();
628
+ $user_id = $query->get_user_id();
629
+ $session_id = $query->get_session_id();
630
+ $query = $query->initialize_user_and_session_id($mgr);
631
+ $this->assertEquals($user_id, $query->get_user_id());
632
+ $this->assertFalse($session_id == $query->get_session_id());
633
+ }
634
+
635
+ public function testNoLog()
636
+ {
637
+ $query = new AfsQuery();
638
+ $this->assertEquals(0, count($query->get_logs()));
639
+ }
640
+
641
+ public function testSomeLogs()
642
+ {
643
+ $query = new AfsQuery();
644
+ $query->add_log('foo');
645
+ $query->add_log('bar');
646
+ $logs = $query->get_logs();
647
+ $this->assertEquals(2, count($logs));
648
+ $this->assertEquals('foo', $logs[0]);
649
+ $this->assertEquals('bar', $logs[1]);
650
+ }
651
+
652
+ public function testNoKey()
653
+ {
654
+ $query = new AfsQuery();
655
+ $this->assertFalse($query->has_key());
656
+ $this->assertEquals(null, $query->get_key());
657
+ }
658
+
659
+ public function testKey()
660
+ {
661
+ $query = new AfsQuery();
662
+ $query->set_key('test');
663
+ $this->assertTrue($query->has_key());
664
+ $this->assertEquals($query->get_key(), 'test');
665
+ }
666
+
667
+ public function testCloneQuery()
668
+ {
669
+ $query = new AfsQuery();
670
+ $query = $query->set_query('query')
671
+ ->add_filter('foo', 'bar')
672
+ ->add_filter('foo', 'baz')
673
+ ->add_filter('fox', 'bat')
674
+ ->add_filter('fox', 'bas')
675
+ ->add_feed('feed')
676
+ ->add_feed('food')
677
+ ->set_replies(666)
678
+ ->set_lang('en')
679
+ ->set_sort(AfsSortBuiltins::WEIGHT, AfsSortOrder::ASC)
680
+ ->add_sort('foo')
681
+ ->add_sort('BAR')
682
+ ->set_cluster('CLUSTER', 42666)
683
+ ->set_max_clusters(66642)
684
+ ->set_overspill()
685
+ ->set_count(AfsCount::CLUSTERS)
686
+ ->set_page(42)
687
+ ->set_from(AfsOrigin::SEARCHBOX)
688
+ ->add_log('loggy')
689
+ ->add_advanced_filter(filter('foo')->greater->value(666))
690
+ ->auto_set_from();
691
+ $clone = new AfsQuery($query);
692
+ $this->assertTrue($clone->get_query('query') == 'query');
693
+ $this->assertTrue($clone->has_filter('foo', 'bar'));
694
+ $this->assertTrue($clone->has_filter('foo', 'baz'));
695
+ $this->assertTrue($clone->has_filter('fox', 'bat'));
696
+ $this->assertTrue($clone->has_filter('fox', 'bas'));
697
+ $this->assertTrue(in_array('feed', $clone->get_feeds()));
698
+ $this->assertTrue(in_array('food', $clone->get_feeds()));
699
+ $this->assertTrue($clone->get_page() == 42);
700
+ $this->assertTrue($clone->get_replies() == 666);
701
+ $this->assertTrue($clone->get_lang() == 'en');
702
+ $this->assertEquals(AfsSortOrder::ASC, $clone->get_sort_order('afs:weight'));
703
+ $this->assertTrue($clone->has_sort('foo'));
704
+ $this->assertEquals(AfsSortOrder::DESC, $clone->get_sort_order('foo'));
705
+ $this->assertTrue($clone->has_sort('BAR'));
706
+ $this->assertEquals(AfsSortOrder::DESC, $clone->get_sort_order('BAR'));
707
+
708
+ $this->assertTrue($clone->has_cluster());
709
+ $this->assertEquals('CLUSTER', $clone->get_cluster_id());
710
+ $this->assertEquals(42666, $clone->get_nb_replies_per_cluster());
711
+ $this->assertTrue($clone->has_max_clusters());
712
+ $this->assertEquals(66642, $clone->get_max_clusters());
713
+ $this->assertTrue($clone->has_overspill());
714
+ $this->assertEquals(AfsCount::CLUSTERS, $clone->get_count_mode());
715
+
716
+ $this->assertEquals(AfsOrigin::SEARCHBOX, $clone->get_from());
717
+ $logs = $clone->get_logs();
718
+ $this->assertEquals(1, count($logs));
719
+ $this->assertEquals('loggy', $logs[0]);
720
+
721
+ $this->assertTrue($clone->has_advanced_filter());
722
+ $adv_filters = $clone->get_advanced_filters();
723
+ $this->assertEquals('foo>666', $adv_filters[0]);
724
+
725
+ // Need to call specific method to check that auto set from is active
726
+ $clone = $clone->add_filter('youhou', 'bloublou');
727
+ $this->assertEquals(AfsOrigin::FACET, $clone->get_from());
728
+ }
729
+
730
+ public function testRetrieveParametersArray()
731
+ {
732
+ $query = new AfsQuery();
733
+ $query = $query->set_query('query');
734
+
735
+ $query = $query->add_filter('foo', 'bar');
736
+ $query = $query->add_filter('foo', 'baz');
737
+ $query = $query->add_filter('fox', 'bat');
738
+ $query = $query->add_filter('fox', 'bas');
739
+
740
+ $query = $query->add_feed('feed');
741
+ $query = $query->add_feed('food');
742
+
743
+ $query = $query->set_replies(666);
744
+
745
+ $query = $query->set_lang('en');
746
+
747
+ $query = $query->set_sort(AfsSortBuiltins::WEIGHT, AfsSortOrder::ASC)
748
+ ->add_sort('foo')
749
+ ->add_sort('BAR');
750
+
751
+ $query = $query->set_cluster('CLUSTER', 666)
752
+ ->set_max_clusters(3)
753
+ ->set_overspill()
754
+ ->set_count(AfsCount::CLUSTERS);
755
+
756
+ $query = $query->set_advanced_filter(filter('FOO')->less_equal->value(42));
757
+
758
+ $query = $query->set_page(42);
759
+
760
+ $query = $query->set_from(AfsOrigin::CONCEPT);
761
+
762
+ $query = $query->add_log('loggy');
763
+ $query = $query->add_log('loggo');
764
+
765
+ $result = $query->get_parameters();
766
+ $this->assertTrue(array_key_exists('query', $result));
767
+ $this->assertTrue($result['query'] == 'query');
768
+
769
+ $this->assertTrue(array_key_exists('filter', $result));
770
+ $this->assertTrue(array_key_exists('foo', $result['filter']));
771
+ $this->assertTrue(in_array('bar', $result['filter']['foo']));
772
+ $this->assertTrue(in_array('baz', $result['filter']['foo']));
773
+ $this->assertTrue(array_key_exists('fox', $result['filter']));
774
+ $this->assertTrue(in_array('bat', $result['filter']['fox']));
775
+ $this->assertTrue(in_array('bas', $result['filter']['fox']));
776
+
777
+ $this->assertTrue(array_key_exists('feed', $result));
778
+ $this->assertTrue(in_array('feed', $result['feed']));
779
+ $this->assertTrue(in_array('food', $result['feed']));
780
+
781
+ $this->assertTrue(array_key_exists('replies', $result));
782
+ $this->assertTrue($result['replies'] == 666);
783
+
784
+ $this->assertTrue(array_key_exists('lang', $result));
785
+ $this->assertTrue($result['lang'] == 'en');
786
+
787
+ $this->assertTrue(array_key_exists('sort', $result));
788
+ $kv = each($result['sort']);
789
+ $this->assertEquals('afs:weight', $kv[0]);
790
+ $this->assertEquals('ASC', $kv[1]);
791
+ $kv = each($result['sort']);
792
+ $this->assertEquals('foo', $kv[0]);
793
+ $this->assertEquals('DESC', $kv[1]);
794
+ $kv = each($result['sort']);
795
+ $this->assertEquals('BAR', $kv[0]);
796
+ $this->assertEquals('DESC', $kv[1]);
797
+
798
+ $this->assertTrue(array_key_exists('cluster', $result));
799
+ $this->assertEquals('CLUSTER,666', $result['cluster']);
800
+ $this->assertTrue(array_key_exists('maxClusters', $result));
801
+ $this->assertEquals(3, $result['maxClusters']);
802
+ $this->assertTrue(array_key_exists('overspill', $result));
803
+ $this->assertEquals('true', $result['overspill']);
804
+ $this->assertTrue(array_key_exists('count', $result));
805
+ $this->assertEquals('clusters', $result['count']);
806
+
807
+ $this->assertTrue(array_key_exists('advancedFilter', $result));
808
+ $this->assertEquals('FOO<=42', $result['advancedFilter'][0]);
809
+
810
+ $this->assertTrue(array_key_exists('page', $result));
811
+ $this->assertTrue($result['page'] == 42);
812
+
813
+ $this->assertTrue(array_key_exists('from', $result));
814
+ $this->assertEquals(AfsOrigin::CONCEPT, $result['from']);
815
+
816
+ $this->assertTrue(array_key_exists('log', $result));
817
+ $this->assertEquals('loggy', $result['log'][0]);
818
+ $this->assertEquals('loggo', $result['log'][1]);
819
+ }
820
+
821
+ public function testInitializeWithArray()
822
+ {
823
+ $query = AfsQuery::create_from_parameters(array(
824
+ 'page' => 42,
825
+ 'query' => 'query',
826
+ 'filter' => array('foo' => array('bar', 'baz'),
827
+ 'fox' => array('bat', 'bas')),
828
+ 'feed' => array('feed', 'food'),
829
+ 'replies' => 666,
830
+ 'lang' => 'en',
831
+ 'sort' => array('afs:weight' => 'ASC',
832
+ 'foo' => 'DESC',
833
+ 'BAR' => 'DESC'),
834
+ 'cluster' => 'CLUSTER,6',
835
+ 'maxClusters' => '666',
836
+ 'overspill' => 'true',
837
+ 'count' => 'clusters',
838
+ 'from' => 'PAGER',
839
+ 'log' => array('loggy', 'loggo')));
840
+
841
+ $this->assertTrue($query->has_query());
842
+ $this->assertTrue($query->get_query() == 'query');
843
+
844
+ $this->assertTrue(in_array('foo', $query->get_filters()));
845
+ $this->assertTrue(in_array('bar', $query->get_filter_values('foo')));
846
+ $this->assertTrue(in_array('baz', $query->get_filter_values('foo')));
847
+ $this->assertTrue(in_array('fox', $query->get_filters()));
848
+ $this->assertTrue(in_array('bat', $query->get_filter_values('fox')));
849
+ $this->assertTrue(in_array('bas', $query->get_filter_values('fox')));
850
+
851
+ $this->assertTrue($query->has_feed());
852
+ $this->assertTrue(in_array('feed', $query->get_feeds()));
853
+ $this->assertTrue(in_array('food', $query->get_feeds()));
854
+
855
+ $this->assertTrue($query->has_replies());
856
+ $this->assertTrue($query->get_replies() == 666);
857
+
858
+ $this->assertTrue($query->has_lang());
859
+ $this->assertTrue($query->get_lang() == 'en');
860
+
861
+ $this->assertTrue($query->has_sort());
862
+ $this->assertTrue($query->has_sort('afs:weight'));
863
+ $this->assertEquals(AfsSortOrder::ASC, $query->get_sort_order('afs:weight'));
864
+ $this->assertTrue($query->has_sort('foo'));
865
+ $this->assertEquals(AfsSortOrder::DESC, $query->get_sort_order('foo'));
866
+ $this->assertTrue($query->has_sort('BAR'));
867
+ $this->assertEquals(AfsSortOrder::DESC, $query->get_sort_order('BAR'));
868
+
869
+ $this->assertEquals('CLUSTER', $query->get_cluster_id());
870
+ $this->assertEquals(6, $query->get_nb_replies_per_cluster());
871
+ $this->assertEquals(666, $query->get_max_clusters());
872
+ $this->assertTrue($query->has_overspill());
873
+ $this->assertEquals(AfsCount::CLUSTERS, $query->get_count_mode());
874
+
875
+ $this->assertTrue($query->has_page());
876
+ $this->assertTrue($query->get_page() == 42);
877
+
878
+ $this->assertEquals(AfsOrigin::PAGER, $query->get_from());
879
+
880
+ $logs = $query->get_logs();
881
+ $this->assertEquals(2, count($logs));
882
+ $this->assertEquals('loggy', $logs[0]);
883
+ $this->assertEquals('loggo', $logs[1]);
884
+ }
885
+
886
+ public function testInitializeWithArrayWithoutOverspill()
887
+ {
888
+ $query = AfsQuery::create_from_parameters(array(
889
+ 'cluster' => 'CLUSTER,6',
890
+ 'maxClusters' => '666',
891
+ 'count' => 'documents'));
892
+
893
+ $this->assertEquals('CLUSTER', $query->get_cluster_id());
894
+ $this->assertEquals(6, $query->get_nb_replies_per_cluster());
895
+ $this->assertEquals(666, $query->get_max_clusters());
896
+ $this->assertFalse($query->has_overspill());
897
+ $this->assertEquals(AfsCount::DOCUMENTS, $query->get_count_mode());
898
+ }
899
+
900
+ public function testInitializeWithUnknownValueInArray()
901
+ {
902
+ $query = AfsQuery::create_from_parameters(array(
903
+ 'X' => '42',
904
+ 'Y' => '666'));
905
+ $this->assertEquals(0, count($query->get_filters()));
906
+ $this->assertFalse($query->has_query());
907
+ }
908
+
909
+ public function testRetrieveFacetManager()
910
+ {
911
+ $query = new AfsQuery();
912
+ $mgr = $query->get_facet_manager();
913
+ $this->assertNotNull($mgr);
914
+ $facets = $mgr->get_facets();
915
+ $this->assertTrue(empty($facets));
916
+ }
917
+
918
+ public function testRetrieveFacetManagerAndUpdate()
919
+ {
920
+ $query = new AfsQuery();
921
+ $mgr = $query->get_facet_manager();
922
+ $this->assertNotNull($mgr);
923
+ $facets = $mgr->get_facets();
924
+ $this->assertTrue(empty($facets));
925
+ $mgr->add_facet(new AfsFacet('FOO', AfsFacetType::STRING_TYPE));
926
+
927
+ $mgr = $query->get_facet_manager();
928
+ $this->assertNotNull($mgr);
929
+ $facets = $mgr->get_facets();
930
+ $this->assertFalse(empty($facets));
931
+ $this->assertTrue(array_key_exists('FOO', $facets));
932
+ }
933
+
934
+ public function testDefaultFacetOptionMultiValuedMode()
935
+ {
936
+ $query = new AfsQuery();
937
+ $query = $query->set_default_multi_selection_facets();
938
+ $facet_mgr = $query->get_facet_manager();
939
+ $this->assertEquals(AfsFacetMode::OR_MODE, $facet_mgr->get_default_facets_mode());
940
+ }
941
+ public function testDefaultFacetOptionSingleValuedMode()
942
+ {
943
+ $query = new AfsQuery();
944
+ $query = $query->set_default_mono_selection_facets();
945
+ $facet_mgr = $query->get_facet_manager();
946
+ $this->assertEquals(AfsFacetMode::SINGLE_MODE, $facet_mgr->get_default_facets_mode());
947
+ }
948
+ public function testFacetMultiValued()
949
+ {
950
+ $query = new AfsQuery();
951
+ $query = $query->set_multi_selection_facets('FOO');
952
+ $facet_mgr = $query->get_facet_manager();
953
+ $this->assertTrue($facet_mgr->has_facet('FOO'));
954
+ $facet = $facet_mgr->get_facet('FOO');
955
+ $this->assertTrue($facet->has_or_mode());
956
+ }
957
+ public function testFacetsMultiValuedAsArray()
958
+ {
959
+ $query = new AfsQuery();
960
+ $facets = array('FOO', 'BAR');
961
+ $query = $query->set_multi_selection_facets($facets);
962
+ $facet_mgr = $query->get_facet_manager();
963
+ foreach ($facets as $facet) {
964
+ $this->assertTrue($facet_mgr->has_facet($facet));
965
+ $facet = $facet_mgr->get_facet($facet);
966
+ $this->assertTrue($facet->has_or_mode());
967
+ }
968
+ }
969
+ public function testFacetsMultiValuedAsList()
970
+ {
971
+ $query = new AfsQuery();
972
+ $query = $query->set_multi_selection_facets('FOO', 'BAR');
973
+ $facet_mgr = $query->get_facet_manager();
974
+ foreach (array('FOO', 'BAR') as $facet) {
975
+ $this->assertTrue($facet_mgr->has_facet($facet));
976
+ $facet = $facet_mgr->get_facet($facet);
977
+ $this->assertTrue($facet->has_or_mode());
978
+ }
979
+ }
980
+ public function testFacetSingleValuedAsArray()
981
+ {
982
+ $query = new AfsQuery();
983
+ $query = $query->set_mono_selection_facets('FOO');
984
+ $facet_mgr = $query->get_facet_manager();
985
+ $this->assertTrue($facet_mgr->has_facet('FOO'));
986
+ $facet = $facet_mgr->get_facet('FOO');
987
+ $this->assertTrue($facet->has_single_mode());
988
+ }
989
+ public function testFacetsSingleValuedAsList()
990
+ {
991
+ $query = new AfsQuery();
992
+ $facets = array('FOO', 'BAR');
993
+ $query = $query->set_mono_selection_facets($facets);
994
+ $facet_mgr = $query->get_facet_manager();
995
+ foreach ($facets as $facet) {
996
+ $this->assertTrue($facet_mgr->has_facet($facet));
997
+ $facet = $facet_mgr->get_facet($facet);
998
+ $this->assertTrue($facet->has_single_mode());
999
+ }
1000
+ }
1001
+ public function testFacetsSingleValued()
1002
+ {
1003
+ $query = new AfsQuery();
1004
+
1005
+ $query = $query->set_mono_selection_facets('FOO', 'BAR');
1006
+ $facet_mgr = $query->get_facet_manager();
1007
+ foreach (array('FOO', 'BAR') as $facet) {
1008
+ $this->assertTrue($facet_mgr->has_facet($facet));
1009
+ $facet = $facet_mgr->get_facet($facet);
1010
+ $this->assertTrue($facet->has_single_mode());
1011
+ }
1012
+ }
1013
+
1014
+ public function testSmoothFacetSortOrder()
1015
+ {
1016
+ $query = new AfsQuery();
1017
+ $this->assertFalse($query->get_facet_manager()->is_facet_order_strict());
1018
+ }
1019
+ public function testStrictFacetSortOrder()
1020
+ {
1021
+ $query = new AfsQuery();
1022
+ $query = $query->set_facet_order(array('foo', 'bar'), AfsFacetOrder::STRICT);
1023
+ $this->assertTrue($query->get_facet_manager()->is_facet_order_strict());
1024
+ }
1025
+ }
1026
+
1027
+
lib/antidot/AFS/SEARCH/TEST/replyHelperTest.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_reply_helper.php";
3
+
4
+ class ReplyHelperTest extends PHPUnit_Framework_TestCase
5
+ {
6
+ public function testReply()
7
+ {
8
+ $reply = json_decode('{
9
+ "docId": 180,
10
+ "uri": "http://foo.bar.baz/14",
11
+ "title": [
12
+ {
13
+ "afs:t": "KwicString",
14
+ "text": "The title 14"
15
+ }
16
+ ],
17
+ "abstract": [
18
+ {
19
+ "afs:t": "KwicString",
20
+ "text": "some content "
21
+ },
22
+ {
23
+ "afs:t": "KwicMatch",
24
+ "match": "match content"
25
+ },
26
+ {
27
+ "afs:t": "KwicString",
28
+ "text": " other content"
29
+ },
30
+ {
31
+ "afs:t": "KwicTruncate"
32
+ }
33
+ ],
34
+ "relevance": {
35
+ "rank": 21
36
+ },
37
+ "clientData": [
38
+ {
39
+ "contents": "<clientdata><data><data1>data 0</data1><data1>data 1</data1><multi><m0>m 0</m0><m1>m 1</m1><m2>m 2</m2><m3>m 3</m3></multi></data></clientdata>",
40
+ "id": "main",
41
+ "mimeType": "text/xml"
42
+ }
43
+ ]
44
+ }');
45
+
46
+ $helper = new AfsReplyHelper($reply);
47
+ $this->assertEquals($helper->get_title(), 'The title 14');
48
+ $this->assertEquals($helper->get_abstract(), 'some content <b>match content</b> other content...');
49
+ // Magics
50
+ $this->assertEquals($helper->title, 'The title 14');
51
+ $this->assertEquals($helper->abstract, 'some content <b>match content</b> other content...');
52
+ $this->assertEquals($helper->uri, 'http://foo.bar.baz/14');
53
+
54
+ $this->assertEquals('data 1', $helper->get_clientdata()->get_value('/clientdata/data/data1[2]'));
55
+ }
56
+ }
lib/antidot/AFS/SEARCH/TEST/replysetHelperTest.php ADDED
@@ -0,0 +1,1544 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_response_helper.php";
3
+ require_once "AFS/SEARCH/afs_replyset_helper.php";
4
+ require_once "AFS/SEARCH/afs_query.php";
5
+ require_once "AFS/SEARCH/afs_query_coder.php";
6
+
7
+ class ReplysetHelperTest extends PHPUnit_Framework_TestCase
8
+ {
9
+ public function testSimpleReply()
10
+ {
11
+ $input = json_decode('{
12
+ "header": {
13
+ "query": {
14
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
15
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
16
+ "date": "2013-10-02T15:48:41+0200",
17
+ "queryParam": [
18
+ {
19
+ "name": "afs:service",
20
+ "value": "42"
21
+ }
22
+ ],
23
+ "mainCtx": {
24
+ "textQuery": "title"
25
+ },
26
+ "textQuery": "title"
27
+ },
28
+ "user": {
29
+ "requestMethod": "GET",
30
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0 Iceweasel/23.0",
31
+ "address": "127.0.0.1",
32
+ "output": {
33
+ "format": "JSON",
34
+ "encoding": "gzip",
35
+ "charset": "UTF-8"
36
+ }
37
+ },
38
+ "performance": {
39
+ "durationMs": 666
40
+ },
41
+ "info": { }
42
+ },
43
+ "replySet": [
44
+ {
45
+ "meta": {
46
+ "uri": "Test",
47
+ "totalItems": 200,
48
+ "totalItemsIsExact": true,
49
+ "pageItems": 2,
50
+ "firstPageItem": 3,
51
+ "lastPageItem": 4,
52
+ "durationMs": 42,
53
+ "firstPaFId": 1,
54
+ "lastPaFId": 1,
55
+ "producer": "SEARCH"
56
+ },
57
+ "facets": {
58
+ "facet": [
59
+ {
60
+ "afs:t": "FacetTree",
61
+ "node": [
62
+ {
63
+ "key": "false",
64
+ "labels": [
65
+ {
66
+ "label": "BAD"
67
+ }
68
+ ],
69
+ "items": 67
70
+ },
71
+ {
72
+ "key": "true",
73
+ "labels": [
74
+ {
75
+ "label": "GOOD"
76
+ }
77
+ ],
78
+ "items": 133
79
+ }
80
+ ],
81
+ "layout": "TREE",
82
+ "type": "BOOL",
83
+ "id": "BOOL",
84
+ "labels": [
85
+ {
86
+ "label": "Boolean facet"
87
+ }
88
+ ]
89
+ }
90
+ ]
91
+ },
92
+ "content": {
93
+ "reply": [
94
+ {
95
+ "docId": 198,
96
+ "uri": "http://foo.bar.baz/116",
97
+ "title": [
98
+ {
99
+ "afs:t": "KwicString",
100
+ "text": "The "
101
+ },
102
+ {
103
+ "afs:t": "KwicMatch",
104
+ "match": "title"
105
+ },
106
+ {
107
+ "afs:t": "KwicString",
108
+ "text": " 116"
109
+ }
110
+ ],
111
+ "abstract": [
112
+ {
113
+ "afs:t": "KwicString",
114
+ "text": "viens de tomber a monté d\'un nouveau cran dans l\'étrangeté. Jamais dans l\'Histoire de l\'humanité il n\'a existé de civilisation sans enfants. Je tente d\'en imaginer les conséquences. George, qui m\'a deviné, énumère: - Comme nous ne nous reproduisons pas, la moitié féminine de l\'humanité"
115
+ },
116
+ {
117
+ "afs:t": "KwicTruncate"
118
+ }
119
+ ],
120
+ "relevance": {
121
+ "rank": 3
122
+ },
123
+ "clientData": [
124
+ {
125
+ "contents": "<clientdata>{&quot;data&quot;: [{&quot;data1&quot;: &quot;data 0&quot;}, {&quot;data1&quot;: &quot;data 1&quot;}, {&quot;m1&quot;: &quot;m 1&quot;, &quot;m0&quot;: &quot;m 0&quot;, &quot;m3&quot;: &quot;m 3&quot;, &quot;m2&quot;: &quot;m 2&quot;}]}</clientdata>",
126
+ "id": "main",
127
+ "mimeType": "text/xml"
128
+ }
129
+ ]
130
+ },
131
+ {
132
+ "docId": 197,
133
+ "uri": "http://foo.bar.baz/81",
134
+ "title": [
135
+ {
136
+ "afs:t": "KwicString",
137
+ "text": "The "
138
+ },
139
+ {
140
+ "afs:t": "KwicMatch",
141
+ "match": "title"
142
+ },
143
+ {
144
+ "afs:t": "KwicString",
145
+ "text": " 81"
146
+ }
147
+ ],
148
+ "abstract": [
149
+ {
150
+ "afs:t": "KwicString",
151
+ "text": "morose... il n\'y a pas de quoi en être fier. J\'émets des doutes, certes; mais au fond de moi, j\'ai confiance. Ne vous en étonnez-vous pas? Il y aurait pourtant bien de quoi! Voici que va surgir de nulle part une collectivité cachée comme aucune autre. Rien de pareil, jamais, n\'est arrivé dans"
152
+ },
153
+ {
154
+ "afs:t": "KwicTruncate"
155
+ }
156
+ ],
157
+ "relevance": {
158
+ "rank": 4
159
+ },
160
+ "clientData": [
161
+ {
162
+ "contents": "<clientdata>&lt;data&gt;&lt;data1&gt;data 0&lt;/data1&gt;&lt;data1&gt;data 1&lt;/data1&gt;&lt;multi&gt;&lt;m0&gt;m 0&lt;/m0&gt;&lt;m1&gt;m 1&lt;/m1&gt;&lt;m2&gt;m 2&lt;/m2&gt;&lt;m3&gt;m 3&lt;/m3&gt;&lt;/multi&gt;&lt;/data&gt;</clientdata>",
163
+ "id": "main",
164
+ "mimeType": "text/xml"
165
+ }
166
+ ]
167
+ }
168
+ ]
169
+ },
170
+ "pager": {
171
+ "previousPage": 1,
172
+ "nextPage": 3,
173
+ "currentPage": 2,
174
+ "page": [
175
+ 1,
176
+ 2,
177
+ 3,
178
+ 4,
179
+ 5,
180
+ 6,
181
+ 7,
182
+ 8,
183
+ 9,
184
+ 10
185
+ ]
186
+ }
187
+ }
188
+ ]
189
+ }');
190
+
191
+ $query = new AfsQuery();
192
+ $query = $query->set_query('title');
193
+ $query = $query->set_replies(2);
194
+ $query = $query->set_page(3);
195
+
196
+ $config = new AfsHelperConfiguration();
197
+ $facet_mgr = $query->get_facet_manager();
198
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE));
199
+
200
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
201
+
202
+ $meta = $helper->get_meta();
203
+ $this->assertEquals('Test', $meta->get_feed());
204
+ $this->assertEquals('200', $meta->get_total_replies());
205
+ $this->assertEquals('42', $meta->get_duration());
206
+ $this->assertEquals('SEARCH', $meta->get_producer());
207
+
208
+ $this->assertTrue($helper->has_facet());
209
+ $facets = $helper->get_facets();
210
+ $this->assertEquals(1, count($facets));
211
+ $this->assertEquals('Boolean facet', $facets[0]->get_label());
212
+ $elems = $facets[0]->get_elements();
213
+ $this->assertEquals(2, count($elems));
214
+ // You can continue here if you want but unit tests already exists for
215
+ // facets.
216
+
217
+ $this->assertEquals(2, $helper->get_nb_replies());
218
+ $replies = $helper->get_replies();
219
+ $this->assertEquals('The <b>title</b> 116', $replies[0]->title);
220
+ $this->assertEquals('The <b>title</b> 81', $replies[1]->title);
221
+ // and so on...
222
+
223
+ $this->assertTrue($helper->has_pager());
224
+ $pager = $helper->get_pager();
225
+ $this->assertEquals($pager->get_next()->get_page(), '3');
226
+ $this->assertEquals($pager->get_previous()->get_page(), '1');
227
+ // and so on...
228
+ }
229
+
230
+ public function testSimpleReplyWithLinks()
231
+ {
232
+ $input = json_decode('{
233
+ "header": {
234
+ "query": {
235
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
236
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
237
+ "date": "2013-10-02T15:48:41+0200",
238
+ "queryParam": [
239
+ {
240
+ "name": "afs:service",
241
+ "value": "42"
242
+ }
243
+ ],
244
+ "mainCtx": {
245
+ "textQuery": "title"
246
+ },
247
+ "textQuery": "title"
248
+ },
249
+ "user": {
250
+ "requestMethod": "GET",
251
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0 Iceweasel/23.0",
252
+ "address": "127.0.0.1",
253
+ "output": {
254
+ "format": "JSON",
255
+ "encoding": "gzip",
256
+ "charset": "UTF-8"
257
+ }
258
+ },
259
+ "performance": {
260
+ "durationMs": 666
261
+ },
262
+ "info": { }
263
+ },
264
+ "replySet": [
265
+ {
266
+ "meta": {
267
+ "uri": "Test",
268
+ "totalItems": 200,
269
+ "totalItemsIsExact": true,
270
+ "pageItems": 2,
271
+ "firstPageItem": 3,
272
+ "lastPageItem": 4,
273
+ "durationMs": 42,
274
+ "firstPaFId": 1,
275
+ "lastPaFId": 1,
276
+ "producer": "SEARCH"
277
+ },
278
+ "facets": {
279
+ "facet": [
280
+ {
281
+ "afs:t": "FacetTree",
282
+ "node": [
283
+ {
284
+ "key": "false",
285
+ "labels": [
286
+ {
287
+ "label": "BAD"
288
+ }
289
+ ],
290
+ "items": 67
291
+ },
292
+ {
293
+ "key": "true",
294
+ "labels": [
295
+ {
296
+ "label": "GOOD"
297
+ }
298
+ ],
299
+ "items": 133
300
+ }
301
+ ],
302
+ "layout": "TREE",
303
+ "type": "BOOL",
304
+ "id": "BOOL",
305
+ "labels": [
306
+ {
307
+ "label": "Boolean facet"
308
+ }
309
+ ]
310
+ }
311
+ ]
312
+ },
313
+ "content": {
314
+ "reply": [
315
+ {
316
+ "docId": 198,
317
+ "uri": "http://foo.bar.baz/116",
318
+ "title": [
319
+ {
320
+ "afs:t": "KwicString",
321
+ "text": "The "
322
+ },
323
+ {
324
+ "afs:t": "KwicMatch",
325
+ "match": "title"
326
+ },
327
+ {
328
+ "afs:t": "KwicString",
329
+ "text": " 116"
330
+ }
331
+ ],
332
+ "abstract": [
333
+ {
334
+ "afs:t": "KwicString",
335
+ "text": "viens de tomber a monté d\'un nouveau cran dans l\'étrangeté. Jamais dans l\'Histoire de l\'humanité il n\'a existé de civilisation sans enfants. Je tente d\'en imaginer les conséquences. George, qui m\'a deviné, énumère: - Comme nous ne nous reproduisons pas, la moitié féminine de l\'humanité"
336
+ },
337
+ {
338
+ "afs:t": "KwicTruncate"
339
+ }
340
+ ],
341
+ "relevance": {
342
+ "rank": 3
343
+ },
344
+ "clientData": [
345
+ {
346
+ "contents": "<clientdata>{&quot;data&quot;: [{&quot;data1&quot;: &quot;data 0&quot;}, {&quot;data1&quot;: &quot;data 1&quot;}, {&quot;m1&quot;: &quot;m 1&quot;, &quot;m0&quot;: &quot;m 0&quot;, &quot;m3&quot;: &quot;m 3&quot;, &quot;m2&quot;: &quot;m 2&quot;}]}</clientdata>",
347
+ "id": "main",
348
+ "mimeType": "text/xml"
349
+ }
350
+ ]
351
+ },
352
+ {
353
+ "docId": 197,
354
+ "uri": "http://foo.bar.baz/81",
355
+ "title": [
356
+ {
357
+ "afs:t": "KwicString",
358
+ "text": "The "
359
+ },
360
+ {
361
+ "afs:t": "KwicMatch",
362
+ "match": "title"
363
+ },
364
+ {
365
+ "afs:t": "KwicString",
366
+ "text": " 81"
367
+ }
368
+ ],
369
+ "abstract": [
370
+ {
371
+ "afs:t": "KwicString",
372
+ "text": "morose... il n\'y a pas de quoi en être fier. J\'émets des doutes, certes; mais au fond de moi, j\'ai confiance. Ne vous en étonnez-vous pas? Il y aurait pourtant bien de quoi! Voici que va surgir de nulle part une collectivité cachée comme aucune autre. Rien de pareil, jamais, n\'est arrivé dans"
373
+ },
374
+ {
375
+ "afs:t": "KwicTruncate"
376
+ }
377
+ ],
378
+ "relevance": {
379
+ "rank": 4
380
+ },
381
+ "clientData": [
382
+ {
383
+ "contents": "<clientdata>&lt;data&gt;&lt;data1&gt;data 0&lt;/data1&gt;&lt;data1&gt;data 1&lt;/data1&gt;&lt;multi&gt;&lt;m0&gt;m 0&lt;/m0&gt;&lt;m1&gt;m 1&lt;/m1&gt;&lt;m2&gt;m 2&lt;/m2&gt;&lt;m3&gt;m 3&lt;/m3&gt;&lt;/multi&gt;&lt;/data&gt;</clientdata>",
384
+ "id": "main",
385
+ "mimeType": "text/xml"
386
+ }
387
+ ]
388
+ }
389
+ ]
390
+ },
391
+ "pager": {
392
+ "previousPage": 1,
393
+ "nextPage": 3,
394
+ "currentPage": 2,
395
+ "page": [
396
+ 1,
397
+ 2,
398
+ 3,
399
+ 4,
400
+ 5,
401
+ 6,
402
+ 7,
403
+ 8,
404
+ 9,
405
+ 10
406
+ ]
407
+ }
408
+ }
409
+ ]
410
+ }');
411
+
412
+ $query = new AfsQuery();
413
+ $query = $query->set_query('title');
414
+ $query = $query->set_replies(2);
415
+ $query = $query->set_page(3);
416
+
417
+ $config = new AfsHelperConfiguration();
418
+ $coder = new AfsQueryCoder('foo.php');
419
+ $config->set_query_coder($coder);
420
+ $facet_mgr = $query->get_facet_manager();
421
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE));
422
+
423
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
424
+
425
+ $meta = $helper->get_meta();
426
+ $this->assertEquals('Test', $meta->get_feed());
427
+ $this->assertEquals('200', $meta->get_total_replies());
428
+ $this->assertEquals('42', $meta->get_duration());
429
+ $this->assertEquals('SEARCH', $meta->get_producer());
430
+
431
+ $this->assertTrue($helper->has_facet());
432
+ $facets = $helper->get_facets();
433
+ $this->assertEquals(1, count($facets));
434
+ $this->assertEquals('Boolean facet', $facets[0]->get_label());
435
+ $elems = $facets[0]->get_elements();
436
+ $this->assertEquals(2, count($elems));
437
+ $this->assertEquals('BAD', $elems[0]->label);
438
+ $this->assertTrue($elems[0]->link != null);
439
+ $this->assertEquals($elems[0]->link, 'foo.php?replies=2&query=title&filter=BOOL_false');
440
+ // You can continue here if you want but unit tests already exists for
441
+ // facets.
442
+
443
+ $this->assertEquals(2, $helper->get_nb_replies());
444
+ $replies = $helper->get_replies();
445
+ $this->assertEquals('The <b>title</b> 116', $replies[0]->title);
446
+ $this->assertEquals('The <b>title</b> 81', $replies[1]->title);
447
+ // and so on...
448
+
449
+ $this->assertTrue($helper->has_pager());
450
+ $pager = $helper->get_pager();
451
+ $this->assertEquals('foo.php?replies=2&query=title&page=3', $pager->get_next());
452
+ $this->assertEquals('foo.php?replies=2&query=title', $pager->get_previous());
453
+ // and so on...
454
+ }
455
+
456
+ public function testFormattedReplyWithLinks()
457
+ {
458
+ $input = json_decode('{
459
+ "header": {
460
+ "query": {
461
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
462
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
463
+ "date": "2013-10-02T15:48:41+0200",
464
+ "queryParam": [
465
+ {
466
+ "name": "afs:service",
467
+ "value": "42"
468
+ }
469
+ ],
470
+ "mainCtx": {
471
+ "textQuery": "title"
472
+ },
473
+ "textQuery": "title"
474
+ },
475
+ "user": {
476
+ "requestMethod": "GET",
477
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0 Iceweasel/23.0",
478
+ "address": "127.0.0.1",
479
+ "output": {
480
+ "format": "JSON",
481
+ "encoding": "gzip",
482
+ "charset": "UTF-8"
483
+ }
484
+ },
485
+ "performance": {
486
+ "durationMs": 666
487
+ },
488
+ "info": { }
489
+ },
490
+ "replySet": [
491
+ {
492
+ "meta": {
493
+ "uri": "Test",
494
+ "totalItems": 200,
495
+ "totalItemsIsExact": true,
496
+ "pageItems": 2,
497
+ "firstPageItem": 3,
498
+ "lastPageItem": 4,
499
+ "durationMs": 42,
500
+ "firstPaFId": 1,
501
+ "lastPaFId": 1,
502
+ "producer": "SEARCH"
503
+ },
504
+ "facets": {
505
+ "facet": [
506
+ {
507
+ "afs:t": "FacetTree",
508
+ "node": [
509
+ {
510
+ "key": "false",
511
+ "labels": [
512
+ {
513
+ "label": "BAD"
514
+ }
515
+ ],
516
+ "items": 67
517
+ },
518
+ {
519
+ "key": "true",
520
+ "labels": [
521
+ {
522
+ "label": "GOOD"
523
+ }
524
+ ],
525
+ "items": 133
526
+ }
527
+ ],
528
+ "layout": "TREE",
529
+ "type": "BOOL",
530
+ "id": "BOOL",
531
+ "labels": [
532
+ {
533
+ "label": "Boolean facet"
534
+ }
535
+ ]
536
+ }
537
+ ]
538
+ },
539
+ "content": {
540
+ "reply": [
541
+ {
542
+ "docId": 198,
543
+ "uri": "http://foo.bar.baz/116",
544
+ "title": [
545
+ {
546
+ "afs:t": "KwicString",
547
+ "text": "The "
548
+ },
549
+ {
550
+ "afs:t": "KwicMatch",
551
+ "match": "title"
552
+ },
553
+ {
554
+ "afs:t": "KwicString",
555
+ "text": " 116"
556
+ }
557
+ ],
558
+ "abstract": [
559
+ {
560
+ "afs:t": "KwicString",
561
+ "text": "viens de tomber a monté d\'un nouveau cran dans l\'étrangeté. Jamais dans l\'Histoire de l\'humanité il n\'a existé de civilisation sans enfants. Je tente d\'en imaginer les conséquences. George, qui m\'a deviné, énumère: - Comme nous ne nous reproduisons pas, la moitié féminine de l\'humanité"
562
+ },
563
+ {
564
+ "afs:t": "KwicTruncate"
565
+ }
566
+ ],
567
+ "relevance": {
568
+ "rank": 3
569
+ },
570
+ "clientData": [
571
+ {
572
+ "contents": "<clientdata>{&quot;data&quot;: [{&quot;data1&quot;: &quot;data 0&quot;}, {&quot;data1&quot;: &quot;data 1&quot;}, {&quot;m1&quot;: &quot;m 1&quot;, &quot;m0&quot;: &quot;m 0&quot;, &quot;m3&quot;: &quot;m 3&quot;, &quot;m2&quot;: &quot;m 2&quot;}]}</clientdata>",
573
+ "id": "main",
574
+ "mimeType": "text/xml"
575
+ }
576
+ ]
577
+ },
578
+ {
579
+ "docId": 197,
580
+ "uri": "http://foo.bar.baz/81",
581
+ "title": [
582
+ {
583
+ "afs:t": "KwicString",
584
+ "text": "The "
585
+ },
586
+ {
587
+ "afs:t": "KwicMatch",
588
+ "match": "title"
589
+ },
590
+ {
591
+ "afs:t": "KwicString",
592
+ "text": " 81"
593
+ }
594
+ ],
595
+ "abstract": [
596
+ {
597
+ "afs:t": "KwicString",
598
+ "text": "morose... il n\'y a pas de quoi en être fier. J\'émets des doutes, certes; mais au fond de moi, j\'ai confiance. Ne vous en étonnez-vous pas? Il y aurait pourtant bien de quoi! Voici que va surgir de nulle part une collectivité cachée comme aucune autre. Rien de pareil, jamais, n\'est arrivé dans"
599
+ },
600
+ {
601
+ "afs:t": "KwicTruncate"
602
+ }
603
+ ],
604
+ "relevance": {
605
+ "rank": 4
606
+ },
607
+ "clientData": [
608
+ {
609
+ "contents": "<clientdata>&lt;data&gt;&lt;data1&gt;data 0&lt;/data1&gt;&lt;data1&gt;data 1&lt;/data1&gt;&lt;multi&gt;&lt;m0&gt;m 0&lt;/m0&gt;&lt;m1&gt;m 1&lt;/m1&gt;&lt;m2&gt;m 2&lt;/m2&gt;&lt;m3&gt;m 3&lt;/m3&gt;&lt;/multi&gt;&lt;/data&gt;</clientdata>",
610
+ "id": "main",
611
+ "mimeType": "text/xml"
612
+ }
613
+ ]
614
+ }
615
+ ]
616
+ },
617
+ "pager": {
618
+ "previousPage": 1,
619
+ "nextPage": 3,
620
+ "currentPage": 2,
621
+ "page": [
622
+ 1,
623
+ 2,
624
+ 3,
625
+ 4,
626
+ 5,
627
+ 6,
628
+ 7,
629
+ 8,
630
+ 9,
631
+ 10
632
+ ]
633
+ }
634
+ }
635
+ ]
636
+ }');
637
+
638
+ $query = new AfsQuery();
639
+ $query = $query->set_query('title');
640
+ $query = $query->set_replies(2);
641
+ $query = $query->set_page(3);
642
+
643
+ $config = new AfsHelperConfiguration();
644
+
645
+ $coder = new AfsQueryCoder('foo.php');
646
+ $config->set_query_coder($coder);
647
+ $facet_mgr = $query->get_facet_manager();
648
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE));
649
+
650
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
651
+
652
+ $meta = $helper->get_meta()->format();
653
+ $this->assertEquals('Test', $meta['feed']);
654
+ $this->assertEquals('200', $meta['total_replies']);
655
+ $this->assertEquals('42', $meta['duration']);
656
+ $this->assertEquals('SEARCH', $meta['producer']);
657
+
658
+ $this->assertTrue($helper->has_facet());
659
+ $facets = $helper->get_facets();
660
+ $this->assertEquals(1, count($facets));
661
+ $this->assertEquals('Boolean facet', $facets[0]->get_label());
662
+ $elems = $facets[0]->get_elements();
663
+ $this->assertEquals(2, count($elems));
664
+ $this->assertEquals('BAD', $elems[0]->label);
665
+ $this->assertTrue($elems[0]->link != null);
666
+ $this->assertEquals($elems[0]->link, 'foo.php?replies=2&query=title&filter=BOOL_false');
667
+ // You can continue here if you want but unit tests already exists for
668
+ // facets.
669
+
670
+ $this->assertEquals(2, $helper->get_nb_replies());
671
+ $replies = $helper->get_replies();
672
+ $this->assertEquals('The <b>title</b> 116', $replies[0]->get_title());
673
+ $this->assertEquals('The <b>title</b> 81', $replies[1]->get_title());
674
+ // and so on...
675
+
676
+ $this->assertTrue($helper->has_pager());
677
+ $pager = $helper->get_pager()->format();
678
+ $this->assertEquals('foo.php?replies=2&query=title&page=3', $pager['pages']['next']);
679
+ $this->assertEquals('foo.php?replies=2&query=title', $pager['pages']['previous']);
680
+ // and so on...
681
+ }
682
+
683
+ public function testSimpleReplyWithoutPagerWithoutFacet()
684
+ {
685
+ $input = json_decode('{
686
+ "header": {
687
+ "query": {
688
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
689
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
690
+ "date": "2013-10-02T15:48:41+0200",
691
+ "queryParam": [
692
+ {
693
+ "name": "afs:service",
694
+ "value": "42"
695
+ }
696
+ ],
697
+ "mainCtx": {
698
+ "textQuery": "title"
699
+ },
700
+ "textQuery": "title"
701
+ },
702
+ "user": {
703
+ "requestMethod": "GET",
704
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0 Iceweasel/23.0",
705
+ "address": "127.0.0.1",
706
+ "output": {
707
+ "format": "JSON",
708
+ "encoding": "gzip",
709
+ "charset": "UTF-8"
710
+ }
711
+ },
712
+ "performance": {
713
+ "durationMs": 666
714
+ },
715
+ "info": { }
716
+ },
717
+ "replySet": [
718
+ {
719
+ "meta": {
720
+ "uri": "Test",
721
+ "totalItems": 200,
722
+ "totalItemsIsExact": true,
723
+ "pageItems": 2,
724
+ "firstPageItem": 3,
725
+ "lastPageItem": 4,
726
+ "durationMs": 42,
727
+ "firstPaFId": 1,
728
+ "lastPaFId": 1,
729
+ "producer": "SEARCH"
730
+ },
731
+ "content": {
732
+ "reply": [
733
+ {
734
+ "docId": 198,
735
+ "uri": "http://foo.bar.baz/116",
736
+ "title": [
737
+ {
738
+ "afs:t": "KwicString",
739
+ "text": "The "
740
+ },
741
+ {
742
+ "afs:t": "KwicMatch",
743
+ "match": "title"
744
+ },
745
+ {
746
+ "afs:t": "KwicString",
747
+ "text": " 116"
748
+ }
749
+ ],
750
+ "abstract": [
751
+ {
752
+ "afs:t": "KwicString",
753
+ "text": "viens de tomber a monté d\'un nouveau cran dans l\'étrangeté. Jamais dans l\'Histoire de l\'humanité il n\'a existé de civilisation sans enfants. Je tente d\'en imaginer les conséquences. George, qui m\'a deviné, énumère: - Comme nous ne nous reproduisons pas, la moitié féminine de l\'humanité"
754
+ },
755
+ {
756
+ "afs:t": "KwicTruncate"
757
+ }
758
+ ],
759
+ "relevance": {
760
+ "rank": 3
761
+ },
762
+ "clientData": [
763
+ {
764
+ "contents": "<clientdata>{&quot;data&quot;: [{&quot;data1&quot;: &quot;data 0&quot;}, {&quot;data1&quot;: &quot;data 1&quot;}, {&quot;m1&quot;: &quot;m 1&quot;, &quot;m0&quot;: &quot;m 0&quot;, &quot;m3&quot;: &quot;m 3&quot;, &quot;m2&quot;: &quot;m 2&quot;}]}</clientdata>",
765
+ "id": "main",
766
+ "mimeType": "text/xml"
767
+ }
768
+ ]
769
+ }
770
+ ]
771
+ }
772
+ }
773
+ ]
774
+ }');
775
+
776
+ $query = new AfsQuery();
777
+ $query = $query->set_query('title');
778
+ $query = $query->set_replies(1);
779
+ $query = $query->set_page(3);
780
+
781
+ $config = new AfsHelperConfiguration();
782
+ $facet_mgr = $query->get_facet_manager();
783
+ $facet_mgr->add_facet(new AfsFacet('BOOL', AfsFacetType::BOOL_TYPE));
784
+
785
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
786
+
787
+ $meta = $helper->get_meta();
788
+ $this->assertEquals('Test', $meta->get_feed());
789
+ $this->assertEquals('200', $meta->get_total_replies());
790
+ $this->assertEquals('42', $meta->get_duration());
791
+ $this->assertEquals('SEARCH', $meta->get_producer());
792
+
793
+ $this->assertFalse($helper->has_facet());
794
+
795
+ $this->assertEquals(1, $helper->get_nb_replies());
796
+ $replies = $helper->get_replies();
797
+ $this->assertEquals('The <b>title</b> 116', $replies[0]->title);
798
+ // and so on...
799
+
800
+ $this->assertFalse($helper->has_pager());
801
+ }
802
+
803
+ public function testRetrieveFacetFromReplyWhereasItAsNotBeenFullyDeclared()
804
+ {
805
+ $input = json_decode('{
806
+ "header": {
807
+ "query": {
808
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
809
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
810
+ "date": "2013-10-02T15:48:41+0200",
811
+ "queryParam": [],
812
+ "mainCtx": { },
813
+ "textQuery": "title"
814
+ },
815
+ "user": { },
816
+ "performance": {
817
+ "durationMs": 666
818
+ },
819
+ "info": { }
820
+ },
821
+ "replySet": [
822
+ {
823
+ "meta": {
824
+ "uri": "Test",
825
+ "totalItems": 200,
826
+ "totalItemsIsExact": true,
827
+ "pageItems": 2,
828
+ "firstPageItem": 3,
829
+ "lastPageItem": 4,
830
+ "durationMs": 42,
831
+ "firstPaFId": 1,
832
+ "lastPaFId": 1,
833
+ "producer": "SEARCH"
834
+ },
835
+ "facets": {
836
+ "facet": [
837
+ {
838
+ "afs:t": "FacetTree",
839
+ "node": [
840
+ {
841
+ "key": "false",
842
+ "labels": [
843
+ {
844
+ "label": "BAD"
845
+ }
846
+ ],
847
+ "items": 67
848
+ },
849
+ {
850
+ "key": "true",
851
+ "labels": [
852
+ {
853
+ "label": "GOOD"
854
+ }
855
+ ],
856
+ "items": 133
857
+ }
858
+ ],
859
+ "layout": "TREE",
860
+ "type": "BOOL",
861
+ "id": "BOOL",
862
+ "labels": [
863
+ {
864
+ "label": "Boolean facet"
865
+ }
866
+ ]
867
+ }
868
+ ]
869
+ },
870
+ "content": {
871
+ "reply": [
872
+ {
873
+ "docId": 198,
874
+ "uri": "http://foo.bar.baz/116",
875
+ "title": [
876
+ {
877
+ "afs:t": "KwicString",
878
+ "text": "Foo"
879
+ }
880
+ ],
881
+ "abstract": [
882
+ {
883
+ "afs:t": "KwicString",
884
+ "text": "Bar"
885
+ }
886
+ ],
887
+ "relevance": {
888
+ "rank": 3
889
+ }
890
+ }
891
+ ]
892
+ }
893
+ }
894
+ ]
895
+ }');
896
+
897
+ $config = new AfsHelperConfiguration();
898
+ $query = new AfsQuery();
899
+ $facet_mgr = $query->get_facet_manager();
900
+ $facet_mgr->set_facet_order(array('BOOL'), AfsFacetOrder::STRICT);
901
+ $this->assertTrue($facet_mgr->has_facet('BOOL'));
902
+ $this->assertEquals(AfsFacetType::UNKNOWN_TYPE, $facet_mgr->get_facet('BOOL')->get_type());
903
+
904
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
905
+ $this->assertEquals(AfsFacetType::BOOL_TYPE, $facet_mgr->get_facet('BOOL')->get_type());
906
+ }
907
+
908
+ public function testRetrieveIntervalFacetFromReplyWhereasItAsNotBeenFullyDeclared()
909
+ {
910
+ $input = json_decode('{
911
+ "header": {
912
+ "query": {
913
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
914
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d",
915
+ "date": "2013-10-02T15:48:41+0200",
916
+ "queryParam": [],
917
+ "mainCtx": { },
918
+ "textQuery": "title"
919
+ },
920
+ "user": { },
921
+ "performance": {
922
+ "durationMs": 666
923
+ },
924
+ "info": { }
925
+ },
926
+ "replySet": [
927
+ {
928
+ "meta": {
929
+ "uri": "Test",
930
+ "totalItems": 200,
931
+ "totalItemsIsExact": true,
932
+ "pageItems": 2,
933
+ "firstPageItem": 3,
934
+ "lastPageItem": 4,
935
+ "durationMs": 42,
936
+ "firstPaFId": 1,
937
+ "lastPaFId": 1,
938
+ "producer": "SEARCH"
939
+ },
940
+ "facets": {
941
+ "facet": [
942
+ {
943
+ "afs:t": "FacetInterval",
944
+ "interval": [
945
+ {
946
+ "key": "[0 .. 3]",
947
+ "items": 1
948
+ },
949
+ {
950
+ "key": "[3 .. 6]",
951
+ "items": 1
952
+ }
953
+ ],
954
+ "layout": "INTERVAL",
955
+ "type": "REAL",
956
+ "id": "Foo",
957
+ "labels": [
958
+ {
959
+ "label": "Real facet"
960
+ }
961
+ ]
962
+ }
963
+ ]
964
+ },
965
+ "content": {
966
+ "reply": [
967
+ {
968
+ "docId": 198,
969
+ "uri": "http://foo.bar.baz/116",
970
+ "title": [
971
+ {
972
+ "afs:t": "KwicString",
973
+ "text": "Foo"
974
+ }
975
+ ],
976
+ "abstract": [
977
+ {
978
+ "afs:t": "KwicString",
979
+ "text": "Bar"
980
+ }
981
+ ],
982
+ "relevance": {
983
+ "rank": 3
984
+ }
985
+ }
986
+ ]
987
+ }
988
+ }
989
+ ]
990
+ }');
991
+
992
+ $config = new AfsHelperConfiguration();
993
+ $query = new AfsQuery();
994
+ $facet_mgr = $query->get_facet_manager();
995
+ $facet_mgr->set_facet_order(array('Foo'), AfsFacetOrder::STRICT);
996
+ $this->assertTrue($facet_mgr->has_facet('Foo'));
997
+ $this->assertEquals(AfsFacetType::UNKNOWN_TYPE, $facet_mgr->get_facet('Foo')->get_type());
998
+
999
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
1000
+ $this->assertEquals(AfsFacetType::REAL_TYPE, $facet_mgr->get_facet('Foo')->get_type());
1001
+ }
1002
+
1003
+ public function testLazyFacetOrderingAllFacetsDeclared()
1004
+ {
1005
+ $input = json_decode('{
1006
+ "header": {
1007
+ "query": {
1008
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
1009
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d"
1010
+ },
1011
+ "performance": {
1012
+ "durationMs": 666
1013
+ }
1014
+ },
1015
+ "replySet": [
1016
+ {
1017
+ "meta": {
1018
+ "uri": "Test",
1019
+ "totalItems": 200,
1020
+ "totalItemsIsExact": true,
1021
+ "pageItems": 2,
1022
+ "firstPageItem": 3,
1023
+ "lastPageItem": 4,
1024
+ "durationMs": 42,
1025
+ "firstPaFId": 1,
1026
+ "lastPaFId": 1,
1027
+ "producer": "SEARCH"
1028
+ },
1029
+ "facets": {
1030
+ "facet": [
1031
+ {
1032
+ "afs:t": "FacetInterval",
1033
+ "interval": [
1034
+ {
1035
+ "key": "[3 .. 6]",
1036
+ "items": 1
1037
+ }
1038
+ ],
1039
+ "layout": "INTERVAL",
1040
+ "type": "INTEGER",
1041
+ "id": "Bar"
1042
+ },
1043
+ {
1044
+ "afs:t": "FacetInterval",
1045
+ "interval": [
1046
+ {
1047
+ "key": "[0 .. 3]",
1048
+ "items": 1
1049
+ }
1050
+ ],
1051
+ "layout": "INTERVAL",
1052
+ "type": "REAL",
1053
+ "id": "Foo"
1054
+ }
1055
+ ]
1056
+ }
1057
+ }
1058
+ ]
1059
+ }');
1060
+
1061
+ $config = new AfsHelperConfiguration();
1062
+ $query = new AfsQuery();
1063
+ $facet_mgr = $query->get_facet_manager();
1064
+ $facet_mgr->set_facet_order(array('Foo', 'Bar'), AfsFacetOrder::LAX);
1065
+ $this->assertTrue($facet_mgr->has_facet('Foo'));
1066
+ $this->assertTrue($facet_mgr->has_facet('Bar'));
1067
+
1068
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
1069
+ $this->assertEquals(AfsFacetType::REAL_TYPE, $facet_mgr->get_facet('Foo')->get_type());
1070
+ $this->assertEquals(AfsFacetType::INTEGER_TYPE, $facet_mgr->get_facet('Bar')->get_type());
1071
+
1072
+ $facets = $helper->get_facets();
1073
+ $this->assertEquals(2, count($facets));
1074
+ $this->assertEquals('Foo', $facets[0]->get_id());
1075
+ $this->assertEquals('Bar', $facets[1]->get_id());
1076
+ }
1077
+
1078
+ public function testLazyFacetOrderingSomeFacetsDeclared()
1079
+ {
1080
+ $input = json_decode('{
1081
+ "header": {
1082
+ "query": {
1083
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
1084
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d"
1085
+ },
1086
+ "performance": {
1087
+ "durationMs": 666
1088
+ }
1089
+ },
1090
+ "replySet": [
1091
+ {
1092
+ "meta": {
1093
+ "uri": "Test",
1094
+ "totalItems": 200,
1095
+ "totalItemsIsExact": true,
1096
+ "pageItems": 2,
1097
+ "firstPageItem": 3,
1098
+ "lastPageItem": 4,
1099
+ "durationMs": 42,
1100
+ "firstPaFId": 1,
1101
+ "lastPaFId": 1,
1102
+ "producer": "SEARCH"
1103
+ },
1104
+ "facets": {
1105
+ "facet": [
1106
+ {
1107
+ "afs:t": "FacetInterval",
1108
+ "interval": [
1109
+ {
1110
+ "key": "[\"3\" .. \"6\"]",
1111
+ "items": 1
1112
+ }
1113
+ ],
1114
+ "layout": "INTERVAL",
1115
+ "type": "STRING",
1116
+ "id": "Bat"
1117
+ },
1118
+ {
1119
+ "afs:t": "FacetInterval",
1120
+ "interval": [
1121
+ {
1122
+ "key": "[3 .. 6]",
1123
+ "items": 1
1124
+ }
1125
+ ],
1126
+ "layout": "INTERVAL",
1127
+ "type": "DATE",
1128
+ "id": "Baz"
1129
+ },
1130
+ {
1131
+ "afs:t": "FacetInterval",
1132
+ "interval": [
1133
+ {
1134
+ "key": "[3 .. 6]",
1135
+ "items": 1
1136
+ }
1137
+ ],
1138
+ "layout": "INTERVAL",
1139
+ "type": "INTEGER",
1140
+ "id": "Bar"
1141
+ },
1142
+ {
1143
+ "afs:t": "FacetInterval",
1144
+ "interval": [
1145
+ {
1146
+ "key": "[0 .. 3]",
1147
+ "items": 1
1148
+ }
1149
+ ],
1150
+ "layout": "INTERVAL",
1151
+ "type": "REAL",
1152
+ "id": "Foo"
1153
+ }
1154
+ ]
1155
+ }
1156
+ }
1157
+ ]
1158
+ }');
1159
+
1160
+ $config = new AfsHelperConfiguration();
1161
+ $query = new AfsQuery();
1162
+ $facet_mgr = $query->get_facet_manager();
1163
+ $facet_mgr->set_facet_order(array('Foo', 'Bar'), AfsFacetOrder::LAX);
1164
+ $this->assertTrue($facet_mgr->has_facet('Foo'));
1165
+ $this->assertTrue($facet_mgr->has_facet('Bar'));
1166
+ $this->assertFalse($facet_mgr->has_facet('Baz'));
1167
+ $this->assertFalse($facet_mgr->has_facet('Bat'));
1168
+
1169
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
1170
+ $this->assertEquals(AfsFacetType::REAL_TYPE, $facet_mgr->get_facet('Foo')->get_type());
1171
+ $this->assertEquals(AfsFacetType::INTEGER_TYPE, $facet_mgr->get_facet('Bar')->get_type());
1172
+
1173
+ $facets = $helper->get_facets();
1174
+ $this->assertEquals(4, count($facets));
1175
+ $this->assertEquals('Foo', $facets[0]->get_id());
1176
+ $this->assertEquals('Bar', $facets[1]->get_id());
1177
+ $this->assertEquals('Bat', $facets[2]->get_id());
1178
+ $this->assertEquals('Baz', $facets[3]->get_id());
1179
+ }
1180
+
1181
+ public function testLazyFacetOrderingMoreFacetsDeclaredThanPresentInReply()
1182
+ {
1183
+ $input = json_decode('{
1184
+ "header": {
1185
+ "query": {
1186
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
1187
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d"
1188
+ },
1189
+ "performance": {
1190
+ "durationMs": 666
1191
+ }
1192
+ },
1193
+ "replySet": [
1194
+ {
1195
+ "meta": {
1196
+ "uri": "Test",
1197
+ "totalItems": 200,
1198
+ "totalItemsIsExact": true,
1199
+ "pageItems": 2,
1200
+ "firstPageItem": 3,
1201
+ "lastPageItem": 4,
1202
+ "durationMs": 42,
1203
+ "firstPaFId": 1,
1204
+ "lastPaFId": 1,
1205
+ "producer": "SEARCH"
1206
+ },
1207
+ "facets": {
1208
+ "facet": [
1209
+ {
1210
+ "afs:t": "FacetInterval",
1211
+ "interval": [
1212
+ {
1213
+ "key": "[3 .. 6]",
1214
+ "items": 1
1215
+ }
1216
+ ],
1217
+ "layout": "INTERVAL",
1218
+ "type": "DATE",
1219
+ "id": "Baz"
1220
+ },
1221
+ {
1222
+ "afs:t": "FacetInterval",
1223
+ "interval": [
1224
+ {
1225
+ "key": "[3 .. 6]",
1226
+ "items": 1
1227
+ }
1228
+ ],
1229
+ "layout": "INTERVAL",
1230
+ "type": "INTEGER",
1231
+ "id": "Bar"
1232
+ },
1233
+ {
1234
+ "afs:t": "FacetInterval",
1235
+ "interval": [
1236
+ {
1237
+ "key": "[0 .. 3]",
1238
+ "items": 1
1239
+ }
1240
+ ],
1241
+ "layout": "INTERVAL",
1242
+ "type": "REAL",
1243
+ "id": "Foo"
1244
+ }
1245
+ ]
1246
+ }
1247
+ }
1248
+ ]
1249
+ }');
1250
+
1251
+ $config = new AfsHelperConfiguration();
1252
+ $query = new AfsQuery();
1253
+ $facet_mgr = $query->get_facet_manager();
1254
+ $facet_mgr->set_facet_order(array('Foo', 'Bal', 'Bar'), AfsFacetOrder::LAX);
1255
+ $this->assertTrue($facet_mgr->has_facet('Foo'));
1256
+ $this->assertTrue($facet_mgr->has_facet('Bal'));
1257
+ $this->assertTrue($facet_mgr->has_facet('Bar'));
1258
+ $this->assertFalse($facet_mgr->has_facet('Baz'));
1259
+
1260
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
1261
+ $this->assertEquals(AfsFacetType::REAL_TYPE, $facet_mgr->get_facet('Foo')->get_type());
1262
+ $this->assertEquals(AfsFacetType::INTEGER_TYPE, $facet_mgr->get_facet('Bar')->get_type());
1263
+
1264
+ $facets = $helper->get_facets();
1265
+ $this->assertEquals(3, count($facets));
1266
+ $this->assertEquals('Foo', $facets[0]->get_id());
1267
+ $this->assertEquals('Bar', $facets[1]->get_id());
1268
+ $this->assertEquals('Baz', $facets[2]->get_id());
1269
+ }
1270
+
1271
+ private function get_cluster_input_data()
1272
+ {
1273
+ return json_decode('{
1274
+ "header": {
1275
+ "query": {
1276
+ "userId": "user_52efac67e5abc",
1277
+ "sessionId": "3127b3a7-3de0-4cc9-b152-ef33d0c28a1a",
1278
+ "date": "2014-02-28T13:22:56+0000",
1279
+ "queryParam": [],
1280
+ "mainCtx": { "textQuery": "" },
1281
+ "textQuery": ""
1282
+ },
1283
+ "user": {
1284
+ "requestMethod": "GET",
1285
+ "agent": "Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0 Iceweasel/24.0",
1286
+ "address": "10.61.8.236",
1287
+ "output": {
1288
+ "format": "JSON",
1289
+ "encoding": "gzip",
1290
+ "charset": "UTF-8"
1291
+ }
1292
+ },
1293
+ "performance": {
1294
+ "durationMs": 13
1295
+ },
1296
+ "info": { }
1297
+ },
1298
+ "replySet": [
1299
+ {
1300
+ "meta": {
1301
+ "uri": "Catalog",
1302
+ "totalItems": 61,
1303
+ "totalItemsIsExact": true,
1304
+ "pageItems": 20,
1305
+ "firstPageItem": 1,
1306
+ "lastPageItem": 20,
1307
+ "durationMs": 6,
1308
+ "cluster": "marketing",
1309
+ "firstPaFId": 1,
1310
+ "lastPaFId": 1,
1311
+ "producer": "SEARCH",
1312
+ "totalItemsInClusters": 2,
1313
+ "nbClusters": 2
1314
+ },
1315
+ "facets": {
1316
+ "facet": [
1317
+ {
1318
+ "afs:t": "FacetTree",
1319
+ "node": [
1320
+ {
1321
+ "key": "OPERATION_9",
1322
+ "labels": [
1323
+ {
1324
+ "lang": "FR",
1325
+ "label": "5% sur les produtis textiles de plus de 100 euros"
1326
+ }
1327
+ ],
1328
+ "items": 9
1329
+ },
1330
+ {
1331
+ "key": "OPERATION_8",
1332
+ "items": 6
1333
+ }
1334
+ ],
1335
+ "layout": "TREE",
1336
+ "type": "STRING",
1337
+ "id": "marketing",
1338
+ "labels": [
1339
+ {
1340
+ "lang": "FR",
1341
+ "label": "Marketing"
1342
+ }
1343
+ ]
1344
+ }
1345
+ ]
1346
+ },
1347
+ "content": {
1348
+ "cluster": [
1349
+ {
1350
+ "id": "OPERATION_8",
1351
+ "totalItems": 6,
1352
+ "totalItemsIsExact": true,
1353
+ "pageItems": 1,
1354
+ "firstPageItem": 1,
1355
+ "lastPageItem": 1,
1356
+ "reply": [
1357
+ {
1358
+ "docId": 64,
1359
+ "uri": "166_en",
1360
+ "title": [
1361
+ {
1362
+ "afs:t": "KwicString",
1363
+ "text": "HTC Touch Diamond"
1364
+ }
1365
+ ],
1366
+ "relevance": { "rank": 1 },
1367
+ "clientData": [
1368
+ {
1369
+ "contents": "<product/>",
1370
+ "id": "main",
1371
+ "mimeType": "text/xml"
1372
+ }
1373
+ ]
1374
+ }
1375
+ ]
1376
+ },
1377
+ {
1378
+ "id": "OPERATION_9",
1379
+ "totalItems": 9,
1380
+ "totalItemsIsExact": true,
1381
+ "pageItems": 1,
1382
+ "firstPageItem": 1,
1383
+ "lastPageItem": 1,
1384
+ "reply": [
1385
+ {
1386
+ "docId": 16,
1387
+ "uri": "112_fr",
1388
+ "title": [
1389
+ {
1390
+ "afs:t": "KwicString",
1391
+ "text": "ECCO Womens Golf Flexor Chaussures de golf"
1392
+ }
1393
+ ],
1394
+ "relevance": { "rank": 2 },
1395
+ "clientData": [
1396
+ {
1397
+ "contents": "<product/>",
1398
+ "id": "main",
1399
+ "mimeType": "text/xml"
1400
+ }
1401
+ ]
1402
+ }
1403
+ ]
1404
+ }
1405
+ ],
1406
+ "reply": [
1407
+ {
1408
+ "docId": 63,
1409
+ "uri": "165_en",
1410
+ "title": [
1411
+ {
1412
+ "afs:t": "KwicString",
1413
+ "text": "My Computer"
1414
+ }
1415
+ ],
1416
+ "abstract": [
1417
+ {
1418
+ "afs:t": "KwicString",
1419
+ "text": "test description"
1420
+ }
1421
+ ],
1422
+ "relevance": { "rank": 3 },
1423
+ "clientData": [
1424
+ {
1425
+ "contents": "<product/>",
1426
+ "id": "main",
1427
+ "mimeType": "text/xml"
1428
+ }
1429
+ ]
1430
+ }
1431
+ ]
1432
+ },
1433
+ "pager": {
1434
+ "nextPage": 2,
1435
+ "currentPage": 1,
1436
+ "page": [ 1, 2, 3, 4 ]
1437
+ }
1438
+ }
1439
+ ]
1440
+ }');
1441
+ }
1442
+
1443
+ public function testClustering()
1444
+ {
1445
+ $input = $this->get_cluster_input_data();
1446
+
1447
+ $config = new AfsHelperConfiguration();
1448
+ $query = new AfsQuery();
1449
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
1450
+
1451
+ $this->assertEquals('marketing', $helper->get_meta()->get_cluster_id());
1452
+ $this->assertEquals('Marketing', $helper->get_meta()->get_cluster_label());
1453
+
1454
+ $this->assertTrue($helper->has_cluster());
1455
+
1456
+ # First cluster from list of clusters
1457
+ $clusters = $helper->get_clusters();
1458
+ $this->assertEquals(2, count($clusters));
1459
+ $cluster = reset($clusters);
1460
+ $this->assertEquals('"OPERATION_8"', $cluster->get_id());
1461
+ $this->assertEquals('OPERATION_8', $cluster->get_label());
1462
+ $this->assertTrue($cluster->has_reply());
1463
+ $replies = $cluster->get_replies();
1464
+ $this->assertEquals(1, count($replies));
1465
+ $this->assertEquals('166_en', $replies[0]->get_uri());
1466
+
1467
+ # Second cluster from list of clusters
1468
+ $cluster = next($clusters);
1469
+ $this->assertEquals('"OPERATION_9"', $cluster->get_id());
1470
+ $this->assertEquals('5% sur les produtis textiles de plus de 100 euros', $cluster->get_label());
1471
+ $this->assertTrue($cluster->has_reply());
1472
+ $replies = $cluster->get_replies();
1473
+ $this->assertEquals(1, count($replies));
1474
+ $this->assertEquals('112_fr', $replies[0]->get_uri());
1475
+
1476
+ # Overspill
1477
+ $this->assertTrue($helper->has_reply());
1478
+ $replies = $helper->get_replies();
1479
+ $this->assertEquals(1, count($replies));
1480
+ $reply = reset($replies);
1481
+ $this->assertEquals('165_en', $reply->get_uri());
1482
+
1483
+ # Merge replies from all cluster replies
1484
+ $replies = $helper->get_cluster_replies();
1485
+ $this->assertEquals(2, count($replies));
1486
+ $this->assertEquals('166_en', $replies[0]->get_uri());
1487
+ $this->assertEquals('112_fr', $replies[1]->get_uri());
1488
+
1489
+ # Merge all replies: cluster replies and overspill
1490
+ $replies = $helper->get_all_replies();
1491
+ $this->assertEquals(3, count($replies));
1492
+ $this->assertEquals('166_en', $replies[0]->get_uri());
1493
+ $this->assertEquals('112_fr', $replies[1]->get_uri());
1494
+ $this->assertEquals('165_en', $replies[2]->get_uri());
1495
+ }
1496
+
1497
+ public function testClusteringAsArray()
1498
+ {
1499
+ $input = $this->get_cluster_input_data();
1500
+
1501
+ $config = new AfsHelperConfiguration();
1502
+ $query = new AfsQuery();
1503
+ $helper = new AfsReplysetHelper($input->replySet[0], $query, $config);
1504
+ $result = $helper->format();
1505
+
1506
+ $this->assertEquals('marketing', $result['meta']['cluster']);
1507
+ $this->assertEquals('Marketing', $result['meta']['cluster_label']);
1508
+
1509
+ $clusters = $result['clusters'];
1510
+ $this->assertFalse(empty($clusters));
1511
+ $this->assertEquals(2, count($clusters));
1512
+
1513
+ # First cluster from list of clusters
1514
+ $cluster = each($clusters);
1515
+ $this->assertEquals('"OPERATION_8"', $cluster['key']);
1516
+ $cluster = $cluster['value'];
1517
+ $this->assertEquals('"OPERATION_8"', $cluster['id']);
1518
+ $this->assertEquals('OPERATION_8', $cluster['label']);
1519
+ $replies = $cluster['replies'];
1520
+ $this->assertFalse(empty($replies));
1521
+ $this->assertEquals(1, count($replies));
1522
+ $this->assertEquals('166_en', $replies[0]['uri']);
1523
+
1524
+ # Second cluster from list of clusters
1525
+ $cluster = each($clusters);
1526
+ $this->assertEquals('"OPERATION_9"', $cluster['key']);
1527
+ $cluster = $cluster['value'];
1528
+ $this->assertEquals('"OPERATION_9"', $cluster['id']);
1529
+ $this->assertEquals('5% sur les produtis textiles de plus de 100 euros', $cluster['label']);
1530
+ $replies = $cluster['replies'];
1531
+ $this->assertFalse(empty($replies));
1532
+ $this->assertEquals(1, count($replies));
1533
+ $this->assertEquals('112_fr', $replies[0]['uri']);
1534
+
1535
+ # Overspill
1536
+ $replies = $result['replies'];
1537
+ $this->assertFalse(empty($replies));
1538
+ $this->assertEquals(1, count($replies));
1539
+ $reply = $replies[0];
1540
+ $this->assertEquals('165_en', $reply['uri']);
1541
+ }
1542
+ }
1543
+
1544
+
lib/antidot/AFS/SEARCH/TEST/responseHelperTest.php ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_response_helper.php";
3
+ require_once "AFS/SEARCH/afs_query.php";
4
+ require_once "AFS/SEARCH/afs_producer.php";
5
+
6
+ class ResponseHelperTest extends PHPUnit_Framework_TestCase
7
+ {
8
+ public function testNoReplySet()
9
+ {
10
+ $input = json_decode('{
11
+ "header": {
12
+ "query": {
13
+ "userId": "foo",
14
+ "sessionId": "bar"
15
+ },
16
+ "user": { },
17
+ "performance": {
18
+ "durationMs": 215
19
+ },
20
+ "info": { }
21
+ }
22
+ }');
23
+
24
+ $config = new AfsHelperConfiguration();
25
+ $query = new AfsQuery();
26
+ $response = new AfsResponseHelper($input, $query, $config);
27
+ $this->assertFalse($response->has_replyset());
28
+ $this->assertFalse($response->in_error());
29
+ $this->assertFalse($response->has_spellcheck());
30
+ $this->assertFalse($response->has_promote());
31
+ $this->assertFalse($response->has_concept());
32
+
33
+ try {
34
+ $response->get_replysets();
35
+ $this->fail('Should have raised exception');
36
+ } catch (AfsNoReplyException $e) { }
37
+ try {
38
+ $response->get_replyset('FOO');
39
+ $this->fail('Should have raised exception');
40
+ } catch (AfsNoReplyException $e) { }
41
+ try {
42
+ $response->get_spellchecks();
43
+ $this->fail('Should have raised exception');
44
+ } catch (AfsNoReplyException $e) { }
45
+ try {
46
+ $response->get_spellchecks('FOO');
47
+ $this->fail('Should have raised exception');
48
+ } catch (AfsNoReplyException $e) { }
49
+ try {
50
+ $response->get_promotes();
51
+ $this->fail('Should have raised exception');
52
+ } catch (AfsNoReplyException $e) { }
53
+ try {
54
+ $response->get_concepts();
55
+ $this->fail('Should have raised exception');
56
+ } catch (AfsNoReplyException $e) { }
57
+ try {
58
+ $response->get_concept('FOO');
59
+ $this->fail('Should have raised exception');
60
+ } catch (AfsNoReplyException $e) { }
61
+ }
62
+
63
+ public function testReplysetWith()
64
+ {
65
+ $input = json_decode('{
66
+ "header": {
67
+ "query": {
68
+ "userId": "foo",
69
+ "sessionId": "bar"
70
+ },
71
+ "user": { },
72
+ "performance": {
73
+ "durationMs": 211
74
+ },
75
+ "info": { }
76
+ },
77
+ "replySet": [
78
+ {
79
+ "meta": {
80
+ "uri": "Catalog",
81
+ "totalItems": 5,
82
+ "totalItemsIsExact": true,
83
+ "pageItems": 5,
84
+ "firstPageItem": 1,
85
+ "lastPageItem": 5,
86
+ "durationMs": 4,
87
+ "firstPaFId": 1,
88
+ "lastPaFId": 1,
89
+ "producer": "SEARCH"
90
+ }
91
+ }
92
+ ]
93
+ }');
94
+
95
+ $config = new AfsHelperConfiguration();
96
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
97
+ $query = new AfsQuery();
98
+ $response = new AfsResponseHelper($input, $query, $config);
99
+ $this->assertEquals(211, $response->get_duration());
100
+ $this->assertTrue($response->has_replyset());
101
+ $this->assertEquals('Catalog', $response->get_replyset()->get_meta()->get_feed());
102
+ $this->assertFalse($response->get_replyset()->has_reply());
103
+ }
104
+
105
+ public function testRetrieveAppropriateReplyset()
106
+ {
107
+ $input = json_decode('{
108
+ "header": {
109
+ "query": {
110
+ "userId": "foo",
111
+ "sessionId": "bar"
112
+ },
113
+ "user": { },
114
+ "performance": {
115
+ "durationMs": 211
116
+ },
117
+ "info": { }
118
+ },
119
+ "replySet": [
120
+ {
121
+ "meta": {
122
+ "uri": "Country",
123
+ "totalItems": 5,
124
+ "totalItemsIsExact": true,
125
+ "pageItems": 5,
126
+ "firstPageItem": 1,
127
+ "lastPageItem": 5,
128
+ "durationMs": 4,
129
+ "firstPaFId": 1,
130
+ "lastPaFId": 1,
131
+ "producer": "SEARCH"
132
+ }
133
+ },
134
+ {
135
+ "meta": {
136
+ "uri": "Catalog",
137
+ "totalItems": 666,
138
+ "totalItemsIsExact": true,
139
+ "pageItems": 5,
140
+ "firstPageItem": 1,
141
+ "lastPageItem": 5,
142
+ "durationMs": 4,
143
+ "firstPaFId": 1,
144
+ "lastPaFId": 1,
145
+ "producer": "SPELLCHECK"
146
+ },
147
+ "content": {
148
+ "reply": [
149
+ {
150
+ "uri": "Catalog",
151
+ "suggestion": [
152
+ {
153
+ "items": [
154
+ {
155
+ "match": {
156
+ "text": "FOO"
157
+ }
158
+ }
159
+ ]
160
+ }
161
+ ]
162
+ }
163
+ ]
164
+ }
165
+ },
166
+ {
167
+ "meta": {
168
+ "uri": "Catalog",
169
+ "totalItems": 42,
170
+ "totalItemsIsExact": true,
171
+ "pageItems": 5,
172
+ "firstPageItem": 1,
173
+ "lastPageItem": 5,
174
+ "durationMs": 4,
175
+ "firstPaFId": 1,
176
+ "lastPaFId": 1,
177
+ "producer": "SEARCH"
178
+ }
179
+ }
180
+ ]
181
+ }');
182
+
183
+ $config = new AfsHelperConfiguration();
184
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
185
+ $query = new AfsQuery();
186
+ $response = new AfsResponseHelper($input, $query, $config);
187
+ $this->assertTrue($response->has_replyset());
188
+ $replyset = $response->get_replyset('Catalog');
189
+ $this->assertEquals('Catalog', $replyset->get_meta()->get_feed());
190
+ $this->assertEquals(AfsProducer::SEARCH, $replyset->get_meta()->get_producer());
191
+ $this->assertEquals(42, $replyset->get_meta()->get_total_replies());
192
+
193
+ $this->assertTrue($response->has_spellcheck());
194
+ }
195
+
196
+ public function testRetrieveUnreachableReplyset()
197
+ {
198
+ $input = json_decode('{
199
+ "header": {
200
+ "query": {
201
+ "userId": "foo",
202
+ "sessionId": "bar"
203
+ },
204
+ "user": { },
205
+ "performance": {
206
+ "durationMs": 211
207
+ },
208
+ "info": { }
209
+ },
210
+ "replySet": [
211
+ {
212
+ "meta": {
213
+ "uri": "Country",
214
+ "totalItems": 5,
215
+ "totalItemsIsExact": true,
216
+ "pageItems": 5,
217
+ "firstPageItem": 1,
218
+ "lastPageItem": 5,
219
+ "durationMs": 4,
220
+ "firstPaFId": 1,
221
+ "lastPaFId": 1,
222
+ "producer": "SEARCH"
223
+ }
224
+ },
225
+ {
226
+ "meta": {
227
+ "uri": "Catalog",
228
+ "totalItems": 666,
229
+ "totalItemsIsExact": true,
230
+ "pageItems": 5,
231
+ "firstPageItem": 1,
232
+ "lastPageItem": 5,
233
+ "durationMs": 4,
234
+ "firstPaFId": 1,
235
+ "lastPaFId": 1,
236
+ "producer": "SPELLCHECK"
237
+ },
238
+ "content": {
239
+ "reply": [
240
+ {
241
+ "uri": "Catalog",
242
+ "suggestion": [
243
+ {
244
+ "items": [
245
+ {
246
+ "match": {
247
+ "text": "FOO"
248
+ }
249
+ }
250
+ ]
251
+ }
252
+ ]
253
+ }
254
+ ]
255
+ }
256
+ }
257
+ ]
258
+ }');
259
+
260
+ $config = new AfsHelperConfiguration();
261
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
262
+ $query = new AfsQuery();
263
+ $response = new AfsResponseHelper($input, $query, $config);
264
+ $this->assertTrue($response->has_replyset());
265
+ try {
266
+ $replyset = $response->get_replyset('Catalog');
267
+ $this->fail('Should not have found any appropriate reply set');
268
+ } catch (OutOfBoundsException $e) { }
269
+ }
270
+
271
+ public function testRetrieveConcept()
272
+ {
273
+ $input = json_decode('{
274
+ "header": {
275
+ "query": {
276
+ "userId": "foo",
277
+ "sessionId": "bar"
278
+ },
279
+ "user": { },
280
+ "performance": {
281
+ "durationMs": 211
282
+ },
283
+ "info": { }
284
+ },
285
+ "replySet": [
286
+ {
287
+ "meta": {
288
+ "uri": "Default",
289
+ "totalItems": 1,
290
+ "totalItemsIsExact": true,
291
+ "pageItems": 1,
292
+ "firstPageItem": 1,
293
+ "lastPageItem": 1,
294
+ "durationMs": 0,
295
+ "firstPaFId": 1,
296
+ "lastPaFId": 1,
297
+ "producer": "CONCEPT"
298
+ },
299
+ "content": {
300
+ "reply": [
301
+ {
302
+ "docId": 1,
303
+ "uri": "concept",
304
+ "concept": {
305
+ "query": {
306
+ "items": [
307
+ {
308
+ "afs:t": "QueryMatch",
309
+ "text": "mariage",
310
+ "uri": [ "lnf:taxo#QI-thm2009862" ]
311
+ }
312
+ ]
313
+ },
314
+ "concepts": {
315
+ "concept": [
316
+ {
317
+ "uri": "lnf:taxo#QI-thm2009862",
318
+ "contents": "foo"
319
+ }
320
+ ]
321
+ }
322
+ }
323
+ }
324
+ ]
325
+ }
326
+ }
327
+ ]
328
+ }');
329
+
330
+ $config = new AfsHelperConfiguration();
331
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
332
+ $query = new AfsQuery();
333
+ $response = new AfsResponseHelper($input, $query, $config);
334
+
335
+ $this->assertTrue($response->has_concept());
336
+ $concept_helper = $response->get_concept('Default');
337
+ $concept_items = $concept_helper->get_items();
338
+ $this->assertEquals(1, count($concept_items));
339
+ $concept_item = each($concept_items);
340
+ $concept_item = $concept_item['value'];
341
+ $this->assertEquals('mariage', $concept_item->get_text());
342
+ $data = $concept_item->get_data();
343
+ $item = each($data);
344
+ $this->assertEquals('foo', $item['value']);
345
+ $this->assertEquals('lnf:taxo#QI-thm2009862', $item['key']);
346
+ }
347
+
348
+ public function testArraysFormat()
349
+ {
350
+ $input = json_decode('{
351
+ "header": {
352
+ "query": {
353
+ "userId": "foo",
354
+ "sessionId": "bar"
355
+ },
356
+ "user": { },
357
+ "performance": {
358
+ "durationMs": 211
359
+ },
360
+ "info": { }
361
+ },
362
+ "replySet": [
363
+ {
364
+ "meta": {
365
+ "uri": "Country",
366
+ "totalItems": 5,
367
+ "totalItemsIsExact": true,
368
+ "pageItems": 5,
369
+ "firstPageItem": 1,
370
+ "lastPageItem": 5,
371
+ "durationMs": 4,
372
+ "firstPaFId": 1,
373
+ "lastPaFId": 1,
374
+ "producer": "SEARCH"
375
+ }
376
+ },
377
+ {
378
+ "meta": {
379
+ "uri": "Catalog",
380
+ "totalItems": 666,
381
+ "totalItemsIsExact": true,
382
+ "pageItems": 5,
383
+ "firstPageItem": 1,
384
+ "lastPageItem": 5,
385
+ "durationMs": 4,
386
+ "firstPaFId": 1,
387
+ "lastPaFId": 1,
388
+ "producer": "SPELLCHECK"
389
+ },
390
+ "content": {
391
+ "reply": [
392
+ {
393
+ "uri": "Catalog",
394
+ "suggestion": [ { "items": [ { "match": { "text": "FOO" } } ] } ]
395
+ }
396
+ ]
397
+ }
398
+ },
399
+ {
400
+ "meta": {
401
+ "uri": "Catalog",
402
+ "totalItems": 42,
403
+ "totalItemsIsExact": true,
404
+ "pageItems": 5,
405
+ "firstPageItem": 1,
406
+ "lastPageItem": 5,
407
+ "durationMs": 4,
408
+ "firstPaFId": 1,
409
+ "lastPaFId": 1,
410
+ "producer": "SEARCH"
411
+ }
412
+ }
413
+ ]
414
+ }');
415
+
416
+ $config = new AfsHelperConfiguration();
417
+ $config->set_helper_format(AfsHelperFormat::ARRAYS);
418
+ $query = new AfsQuery();
419
+ $response = new AfsResponseHelper($input, $query, $config);
420
+ $response = $response->format();
421
+
422
+ $this->assertTrue(array_key_exists('replysets', $response));
423
+ $replyset = $response['replysets']['Catalog'];
424
+ $this->assertEquals('Catalog', $replyset['meta']['feed']);
425
+
426
+ $this->assertTrue(array_key_exists('spellchecks', $response));
427
+ $spellcheck = $response['spellchecks'];
428
+ $this->assertTrue(array_key_exists('Catalog', $spellcheck));
429
+ $res = $spellcheck['Catalog'];
430
+ $this->assertEquals(1, count($res));
431
+ $this->assertEquals('FOO', $res[0]['raw']);
432
+ }
433
+
434
+ public function testFromParameterIsSetForHelpers()
435
+ {
436
+ $input = json_decode('{
437
+ "header": {
438
+ "query": {
439
+ "userId": "afd070b6-4315-40cc-975d-747e28bf132a",
440
+ "sessionId": "5bf5642d-a262-4608-9901-45aa6e87325d"
441
+ },
442
+ "performance": {
443
+ "durationMs": 666
444
+ }
445
+ },
446
+ "replySet": [
447
+ {
448
+ "meta": {
449
+ "uri": "Test",
450
+ "totalItems": 200,
451
+ "totalItemsIsExact": true,
452
+ "pageItems": 2,
453
+ "firstPageItem": 3,
454
+ "lastPageItem": 4,
455
+ "durationMs": 42,
456
+ "firstPaFId": 1,
457
+ "lastPaFId": 1,
458
+ "producer": "SEARCH"
459
+ },
460
+ "facets": {
461
+ "facet": [
462
+ {
463
+ "afs:t": "FacetInterval",
464
+ "interval": [
465
+ {
466
+ "key": "[0 .. 3]",
467
+ "items": 1
468
+ }
469
+ ],
470
+ "layout": "INTERVAL",
471
+ "type": "REAL",
472
+ "id": "Foo"
473
+ }
474
+ ]
475
+ }
476
+ },
477
+ {
478
+ "meta": {
479
+ "uri": "Catalog",
480
+ "totalItems": 2,
481
+ "totalItemsIsExact": true,
482
+ "pageItems": 2,
483
+ "firstPageItem": 1,
484
+ "lastPageItem": 1,
485
+ "durationMs": 4,
486
+ "firstPaFId": 1,
487
+ "lastPaFId": 1,
488
+ "producer": "SPELLCHECK"
489
+ },
490
+ "content": {
491
+ "reply": [
492
+ {
493
+ "docId": 1,
494
+ "uri": "Catalog",
495
+ "title": [
496
+ {
497
+ "afs:t": "KwicMatch",
498
+ "match": "LIGNE"
499
+ }
500
+ ],
501
+ "abstract": [
502
+ {
503
+ "afs:t": "KwicString",
504
+ "text": "LIGNE"
505
+ }
506
+ ],
507
+ "suggestion": [ { "items": [ { "match": { "text": "LIGNE" } } ] } ]
508
+ }
509
+ ]
510
+ }
511
+ }
512
+ ]
513
+ }');
514
+
515
+ $config = new AfsHelperConfiguration();
516
+ $config->set_helper_format(AfsHelperFormat::HELPERS);
517
+ $query = new AfsQuery();
518
+ $response = new AfsResponseHelper($input, $query, $config);
519
+
520
+ $this->assertTrue($response->has_spellcheck());
521
+ $spellchecks = $response->get_spellchecks();
522
+ $this->assertFalse(empty($spellchecks));
523
+ $spellcheck_helper = $spellchecks['Catalog'][0];
524
+ $query = $spellcheck_helper->get_query();
525
+ $this->assertEquals(AfsOrigin::SPELLCHECK, $query->get_from());
526
+
527
+ $this->assertTrue($response->has_replyset());
528
+ $replysets = $response->get_replysets();
529
+ $replyset_helper = $replysets['Test'];
530
+ $this->assertTrue($replyset_helper->has_facet());
531
+ $facets = $replyset_helper->get_facets();
532
+ $this->assertFalse(empty($facets));
533
+ $elements = $facets[0]->get_elements();
534
+ $this->assertFalse(empty($elements));
535
+ $this->assertEquals(AfsOrigin::FACET, $elements[0]->query->get_from());
536
+
537
+ }
538
+ }
539
+
540
+
lib/antidot/AFS/SEARCH/TEST/searchConnectorTest.php ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_search_connector.php";
3
+ require_once "AFS/SEARCH/afs_query.php";
4
+
5
+
6
+
7
+ class SearchConnector extends AfsSearchConnector
8
+ {
9
+ public function __construct($host, $service)
10
+ {
11
+ parent::__construct($host, $service);
12
+ }
13
+
14
+ public function get_url()
15
+ {
16
+ return $this->scheme . '://' . $this->host . '/search';
17
+ }
18
+ public function get_id()
19
+ {
20
+ return $this->service->id;
21
+ }
22
+ public function get_status()
23
+ {
24
+ return $this->service->status;
25
+ }
26
+ public function format_parameters(array $parameters)
27
+ {
28
+ return parent::format_parameters($parameters);
29
+ }
30
+ public function build_url($web_service, array $parameters)
31
+ {
32
+ return parent::build_url('search', $parameters);
33
+ }
34
+ }
35
+
36
+ class SearchConnectorTest extends PHPUnit_Framework_TestCase
37
+ {
38
+ public function testConstructDefaultParameters()
39
+ {
40
+ $connector = new SearchConnector('url', new AfsService(42));
41
+ $this->assertEquals($connector->get_url(), 'http://url/search');
42
+ $this->assertTrue($connector->get_id() == 42);
43
+ $this->assertTrue($connector->get_status() == 'stable');
44
+ }
45
+ public function testConstructParameters()
46
+ {
47
+ $connector = new SearchConnector('url', new AfsService(42, 'rc'));
48
+ $this->assertEquals($connector->get_url(), 'http://url/search');
49
+ $this->assertTrue($connector->get_id() == 42);
50
+ $this->assertTrue($connector->get_status() == 'rc');
51
+ }
52
+
53
+ public function testNoParameter()
54
+ {
55
+ $connector = new SearchConnector('url', new AfsService(42));
56
+ $this->assertEquals($connector->format_parameters(array()), '');
57
+ }
58
+ public function testParameters()
59
+ {
60
+ $connector = new SearchConnector('url', new AfsService(42));
61
+ $this->assertEquals($connector->format_parameters(array(
62
+ 'foo' => 'bar',
63
+ 'fooz' => array('baz', 'bat'))),
64
+ 'foo=bar&fooz=baz&fooz=bat');
65
+ }
66
+
67
+ public function testFailOnInvalidUrl()
68
+ {
69
+ $connector = new AfsSearchConnector('foo', new AfsService(42));
70
+ try {
71
+ $connector->send(array());
72
+ $this->fail('Send query with bad URL should have failed!');
73
+ } catch (Exception $e) { }
74
+ }
75
+
76
+ public function testAPIVersion()
77
+ {
78
+ $connector = new SearchConnector('foo', new AfsService(42));
79
+ $query = new AfsQuery();
80
+ $url = $connector->build_url(null, $query->get_parameters());
81
+ $this->assertFalse(strpos($url, urlencode(get_api_version())) === False,
82
+ '"'.urlencode(get_api_version()).'" should be in: '.$url);
83
+ }
84
+
85
+ public function testNoUserAgent()
86
+ {
87
+ $connector = new SearchConnector('foo', new AfsService(42));
88
+ $query = new AfsQuery();
89
+ $url = $connector->build_url(null, $query->get_parameters());
90
+ $this->assertTrue(strpos($url, urlencode('afs:userAgent')) === False);
91
+ }
92
+
93
+ public function testUserAgent()
94
+ {
95
+ global $_SERVER;
96
+ $_SERVER = array('HTTP_USER_AGENT' => 'foo');
97
+ $connector = new SearchConnector('foo', new AfsService(42));
98
+ $query = new AfsQuery();
99
+ $url = $connector->build_url(null, $query->get_parameters());
100
+ $this->assertFalse(strpos($url, urlencode('afs:userAgent')) === False);
101
+ }
102
+
103
+ public function testNoIp()
104
+ {
105
+ $connector = new SearchConnector('foo', new AfsService(42));
106
+ $query = new AfsQuery();
107
+ $url = $connector->build_url(null, $query->get_parameters());
108
+ $this->assertTrue(strpos($url, urlencode('afs:ip')) === False);
109
+ }
110
+
111
+ public function testIp()
112
+ {
113
+ global $_SERVER;
114
+ $_SERVER = array('REMOTE_ADDR' => '127.0.0.1');
115
+ $connector = new SearchConnector('foo', new AfsService(42));
116
+ $query = new AfsQuery();
117
+ $url = $connector->build_url(null, $query->get_parameters());
118
+ $this->assertFalse(strpos($url, urlencode('afs:ip')) === False);
119
+ }
120
+ }
121
+
122
+
lib/antidot/AFS/SEARCH/TEST/searchQueryManagerTest.php ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'COMMON/afs_connector_interface.php';
3
+ require_once 'AFS/SEARCH/afs_search_query_manager.php';
4
+ require_once 'AFS/SEARCH/afs_search_connector.php';
5
+ require_once 'AFS/SEARCH/afs_query.php';
6
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
7
+ require_once 'AFS/SEARCH/afs_interval.php';
8
+ require_once 'AFS/SEARCH/FILTER/afs_filter.php';
9
+
10
+ class ConnectorMock implements AfsConnectorInterface
11
+ {
12
+ private $parameters = null;
13
+
14
+ public function send(array $parameters)
15
+ {
16
+ $this->parameters = $parameters;
17
+ }
18
+
19
+ public function get_parameters()
20
+ {
21
+ return $this->parameters;
22
+ }
23
+ }
24
+
25
+
26
+ class SearchQueryManagerTest extends PHPUnit_Framework_TestCase
27
+ {
28
+ protected function setUp()
29
+ {
30
+ $this->connector = new ConnectorMock();
31
+ $this->config = new AfsHelperConfiguration();
32
+ $this->qm = new AfsSearchQueryManager($this->connector, $this->config);
33
+ }
34
+
35
+ private function checkOneFacetValue($facet_id, $facet_value, $operator='=')
36
+ {
37
+ $params = $this->connector->get_parameters();
38
+ $filter_str = explode($operator, $params['afs:filter'][0], 2);
39
+ $filter[$filter_str[0]] = $filter_str[1];
40
+ $facet = $filter[$facet_id];
41
+ $this->assertEquals($facet_value, $facet);
42
+ }
43
+
44
+ private function checkFacetValues($facet_id, $facet_values, $split)
45
+ {
46
+ $params = $this->connector->get_parameters();
47
+ if (! array_key_exists('afs:filter', $params))
48
+ throw new Exception('No filter defined!');
49
+ $filters = explode(' ' . $split . ' ', $params['afs:filter'][0]);
50
+ $facets = array();
51
+ foreach ($filters as $filter)
52
+ {
53
+ $facet_str = explode('=', $filter, 2);
54
+ if (empty($facets[$facet_str[0]]))
55
+ {
56
+ $facets[$facet_str[0]] = array();
57
+ }
58
+ $facets[$facet_str[0]][] = $facet_str[1];
59
+ }
60
+
61
+ foreach ($facet_values as $value)
62
+ {
63
+ $this->assertTrue(in_array($value, $facets[$facet_id]));
64
+ }
65
+ }
66
+
67
+ private function checkFromValue($origin)
68
+ {
69
+ $params = $this->connector->get_parameters();
70
+ $this->assertTrue(array_key_exists('afs:from', $params));
71
+ $this->assertEquals($origin, $params['afs:from']);
72
+ }
73
+
74
+ private function checkFacetDefaultValues($values, $exists=true)
75
+ {
76
+ $params = $this->connector->get_parameters();
77
+ $this->assertTrue(array_key_exists('afs:facetDefault', $params));
78
+ foreach ($values as $value) {
79
+ if ($exists)
80
+ $this->assertTrue(in_array($value, $params['afs:facetDefault']),
81
+ $value.' NOT FOUND in: '.serialize($params));
82
+ else
83
+ $this->assertFalse(in_array($value, $params['afs:facetDefault']),
84
+ $value.' FOUND in: '.serialize($params));
85
+ }
86
+ }
87
+
88
+ private function checkFacetOptions($facet_id, $option, $exists=true)
89
+ {
90
+ $params = $this->connector->get_parameters();
91
+ $this->assertTrue(array_key_exists('afs:facet', $params), 'No facet option available');
92
+ $facet_options = array();
93
+ foreach ($params['afs:facet'] as $facet_option) {
94
+ $res = explode(',', $facet_option);
95
+ $facet_options[$res[0]] = $res[1];
96
+ }
97
+ if ($exists) {
98
+ $this->assertTrue(array_key_exists($facet_id, $facet_options), 'No facet option available for facet: ' . $facet_id);
99
+ $this->assertEquals($option, $facet_options[$facet_id]);
100
+ } else {
101
+ $this->assertFalse(array_key_exists($facet_id, $facet_options), '!! Facet option available for facet: ' . $facet_id);
102
+ }
103
+ }
104
+
105
+
106
+ public function testNoParameterProvided()
107
+ {
108
+ $query = new AfsQuery();
109
+ $this->qm->send($query);
110
+ $this->checkFacetDefaultValues(array('replies=1000'));
111
+ }
112
+
113
+ public function testUnregisteredFacet()
114
+ {
115
+ $query = new AfsQuery();
116
+ $query = $query->add_filter('foo', 'bar');
117
+ $this->qm->send($query);
118
+ }
119
+
120
+ public function testOneFacetOneValue()
121
+ {
122
+ $query = new AfsQuery();
123
+ $query = $query->add_filter('foo', '"bar"');
124
+ $this->qm->send($query);
125
+ $this->checkOneFacetValue('foo', '"bar"');
126
+ }
127
+
128
+ public function testOneFacetOneIntervalValue()
129
+ {
130
+ $query = new AfsQuery();
131
+ $query = $query->add_filter('foo', AfsInterval::create(42, 666));
132
+ $this->qm->send($query);
133
+ $this->checkOneFacetValue('foo', '[42 .. 666]');
134
+ }
135
+
136
+ public function testFailOneFacetOneValue()
137
+ {
138
+ $query = new AfsQuery();
139
+ $query = $query->add_filter('foo', 'bar');
140
+ $this->qm->send($query);
141
+ try {
142
+ $this->checkOneFacetValue('foo', '"bar"');
143
+ } catch (Exception $e) {
144
+ return;
145
+ }
146
+ $this->fail('Should have failed due to value type/reference provided!');
147
+ }
148
+
149
+ public function testOneFacetMultipleValues()
150
+ {
151
+ $facet = new AfsFacet('foo', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE);
152
+ $query = new AfsQuery();
153
+ $query = $query->add_filter('foo', '4');
154
+ $query = $query->add_filter('foo', '2');
155
+ $query->get_facet_manager()->add_facet($facet);
156
+ $this->qm->send($query);
157
+ $this->checkFacetValues('foo', array('4', '2'), 'or');
158
+ }
159
+
160
+ public function testFailOnValueOneFacetMultipleValues()
161
+ {
162
+ $facet = new AfsFacet('foo', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE);
163
+ $query = new AfsQuery();
164
+ $query = $query->add_filter('foo', '4');
165
+ $query = $query->add_filter('foo', '2');
166
+ $query->get_facet_manager()->add_facet($facet);
167
+ $this->qm->send($query);
168
+ try
169
+ {
170
+ $this->checkFacetValues('foo', array('4', '3'), 'or');
171
+ }
172
+ catch (Exception $e)
173
+ {
174
+ return;
175
+ }
176
+ $this->fail('Should have failed due to invalid value provided!');
177
+ }
178
+
179
+ public function testFailOnModeValueOneFacetMultipleValues()
180
+ {
181
+ $facet = new AfsFacet('foo', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE);
182
+ $query = new AfsQuery();
183
+ $query = $query->add_filter('foo', '4');
184
+ $query = $query->add_filter('foo', '2');
185
+ $query->get_facet_manager()->add_facet($facet);
186
+ $this->qm->send($query);
187
+ try {
188
+ $this->checkFacetValues('foo', array('4', '2'), 'and');
189
+ } catch (Exception $e) {
190
+ return;
191
+ }
192
+ $this->fail('Should have failed due to invalid mode provided!');
193
+ }
194
+
195
+ public function testFromParameter()
196
+ {
197
+ $facet = new AfsFacet('foo', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE);
198
+ $query = new AfsQuery();
199
+ $query = $query->auto_set_from()
200
+ ->add_filter('foo', '4')
201
+ ->add_filter('foo', '2');
202
+ $query->get_facet_manager()->add_facet($facet);
203
+ $this->qm->send($query);
204
+ $this->checkFromValue(AfsOrigin::FACET);
205
+ }
206
+
207
+ public function testFacetDefaultNonSticky()
208
+ {
209
+ $query = new AfsQuery();
210
+ $this->qm->send($query);
211
+ $this->checkFacetDefaultValues(array('sticky=false'), false);
212
+ $this->checkFacetDefaultValues(array('sticky=true'), false);
213
+ }
214
+ public function testFacetDefaultSticky()
215
+ {
216
+ $query = new AfsQuery();
217
+ $query->get_facet_manager()->set_default_facets_mode(AfsFacetMode::OR_MODE);
218
+ $this->qm->send($query);
219
+ $this->checkFacetDefaultValues(array('sticky=true'));
220
+ }
221
+ public function testFacetNonStickyWithDefaultNonSticky()
222
+ {
223
+ $query = new AfsQuery();
224
+ $facet_mgr = $query->get_facet_manager();
225
+ $this->assertFalse($facet_mgr->get_default_stickyness());
226
+ $facet_mgr->set_facet_order(array('FOO'), AfsFacetOrder::STRICT);
227
+ $this->qm->send($query);
228
+ $this->checkFacetOptions('FOO', 'sticky=false', false);
229
+ }
230
+ public function testFacetNonStickyWithDefaultSticky()
231
+ {
232
+ $query = new AfsQuery();
233
+ $facet_mgr = $query->get_facet_manager();
234
+ $facet_mgr->set_default_facets_mode(AfsFacetMode::OR_MODE);
235
+ $this->assertTrue($facet_mgr->get_default_stickyness());
236
+ $facet_mgr->add_facet(new AfsFacet('FOO', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::AND_MODE));
237
+ $this->qm->send($query);
238
+ $this->checkFacetOptions('FOO', 'sticky=false');
239
+ }
240
+ public function testFacetStickyWithDefaultNonSticky()
241
+ {
242
+ $query = new AfsQuery();
243
+ $facet_mgr = $query->get_facet_manager();
244
+ $facet_mgr->set_default_facets_mode(AfsFacetMode::AND_MODE);
245
+ $this->assertFalse($facet_mgr->get_default_stickyness());
246
+ $facet_mgr->add_facet(new AfsFacet('FOO', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE));
247
+ $this->qm->send($query);
248
+ $this->checkFacetOptions('FOO', 'sticky=true');
249
+ }
250
+ public function testFacetStickyWithDefaultSticky()
251
+ {
252
+ $query = new AfsQuery();
253
+ $facet_mgr = $query->get_facet_manager();
254
+ $facet_mgr->set_default_facets_mode(AfsFacetMode::OR_MODE);
255
+ $this->assertTrue($facet_mgr->get_default_stickyness());
256
+ $facet_mgr->add_facet(new AfsFacet('FOO', AfsFacetType::INTEGER_TYPE, AfsFacetLayout::TREE, AfsFacetMode::OR_MODE));
257
+ $this->qm->send($query);
258
+ $this->checkFacetOptions('FOO', 'sticky=true', false);
259
+ }
260
+
261
+ public function testFacetNonStrictOrder()
262
+ {
263
+ $query = new AfsQuery();
264
+ $facet_mgr = $query->get_facet_manager();
265
+ $facet_mgr->set_facet_order(array('FOO', 'BAR'), AfsFacetOrder::LAX);
266
+ $this->qm->send($query);
267
+ $params = $this->connector->get_parameters();
268
+ $this->assertFalse(array_key_exists('afs:facetOrder', $params));
269
+ }
270
+ public function testFacetStrictOrder()
271
+ {
272
+ $query = new AfsQuery();
273
+ $facet_mgr = $query->get_facet_manager();
274
+ $sort = array('FOO', 'BAR');
275
+ $facet_mgr->set_facet_order($sort, AfsFacetOrder::STRICT);
276
+ $this->qm->send($query);
277
+ $params = $this->connector->get_parameters();
278
+ $this->assertTrue(array_key_exists('afs:facetOrder', $params));
279
+ $this->assertEquals(implode(',', $sort), $params['afs:facetOrder']);
280
+ }
281
+
282
+ public function testFilterParameterForUnconfiguredFacet()
283
+ {
284
+ $query = new AfsQuery();
285
+ $query = $query->add_filter('FOO', 'value1')
286
+ ->add_filter('FOO', 'value2');
287
+ $this->qm->send($query);
288
+ $this->checkFacetValues('FOO', array('value1', 'value2'), 'and');
289
+ }
290
+
291
+ public function testAdvancedFilter()
292
+ {
293
+ $query = new AfsQuery();
294
+ $query = $query->set_advanced_filter(filter('FOO')->greater_equal->value(666));
295
+ $this->qm->send($query);
296
+ $this->checkOneFacetValue('FOO', 666, '>=');
297
+ }
298
+
299
+ public function testDefaultFacetValuesSortOrder()
300
+ {
301
+ $query = new AfsQuery();
302
+ $query = $query->set_facets_values_sort_order(AfsFacetValuesSortMode::ITEMS, AfsSortOrder::DESC);
303
+ $this->qm->send($query);
304
+ $this->checkFacetDefaultValues(array('sort=items', 'order=DESC'));
305
+ }
306
+ public function testDefaultFacetValuesReplies()
307
+ {
308
+ $query = new AfsQuery();
309
+ $query = $query->set_facets_values_nb_replies(42);
310
+ $this->qm->send($query);
311
+ $this->checkFacetDefaultValues(array('replies=42'));
312
+ }
313
+
314
+
315
+ public function testMultiLogs()
316
+ {
317
+ $connector = new AfsSearchConnector('foo', new AfsService(42));
318
+ $qm = new AfsSearchQueryManager($connector, $this->config);
319
+
320
+ $query = new AfsQuery();
321
+ $query = $query->add_log('LOG')->add_log('LOGGG');
322
+ try {
323
+ $qm->send($query);
324
+ } catch (Exception $e) { }
325
+
326
+ $url = $connector->get_generated_url();
327
+ $afs_log = urlencode('afs:log').'=';
328
+ $this->assertFalse(strpos($url, $afs_log.'LOG') === False,
329
+ 'No afs:log LOG in URL: '.$url);
330
+ $this->assertFalse(strpos($url, $afs_log.'LOGGG') === False,
331
+ 'No afs:log LOGGG in URL: '.$url);
332
+ }
333
+ }
lib/antidot/AFS/SEARCH/TEST/searchTest.php ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once 'AFS/SEARCH/afs_search.php';
3
+ require_once 'COMMON/php-SAI/lib/CurlStub.php';
4
+ require_once 'AIF/afs_user_authentication.php';
5
+
6
+ class SearchTest extends PHPUnit_Framework_TestCase
7
+ {
8
+ public function testRetrieveDefaultParameters()
9
+ {
10
+ $search = new AfsSearch('127.0.0.1', 666);
11
+
12
+ $service = $search->get_service();
13
+ $this->assertEquals(666, $service->id);
14
+ $this->assertEquals(AfsServiceStatus::STABLE, $service->status);
15
+
16
+ $search->execute();
17
+ $url = $search->get_generated_url();
18
+ $this->assertTrue(strpos($url, '127.0.0.1') !== False, 'URL does not contain right host');
19
+ $this->assertTrue(strpos($url, 'service=666') !== False, 'URL does not contain right sesrvice id');
20
+ $this->assertTrue(strpos($url, 'status=stable') !== False, 'URL does not contain right sesrvice status');
21
+
22
+ $config = $search->get_helpers_configuration();
23
+ $this->assertEquals(AfsHelperFormat::HELPERS, $config->get_helper_format());
24
+ }
25
+
26
+ public function testRetrieveSpecificParameters()
27
+ {
28
+ $search = new AfsSearch('127.0.0.2', 42, AfsServiceStatus::RC);
29
+
30
+ $service = $search->get_service();
31
+ $this->assertEquals(42, $service->id);
32
+ $this->assertEquals(AfsServiceStatus::RC, $service->status);
33
+
34
+ $search->execute(AfsHelperFormat::ARRAYS);
35
+ $url = $search->get_generated_url();
36
+ $this->assertTrue(strpos($url, '127.0.0.2') !== False, 'URL does not contain right host');
37
+ $this->assertTrue(strpos($url, 'service=42') !== False, 'URL does not contain right sesrvice id');
38
+ $this->assertTrue(strpos($url, 'status=rc') !== False, 'URL does not contain right sesrvice status');
39
+
40
+ $config = $search->get_helpers_configuration();
41
+ $this->assertEquals(AfsHelperFormat::ARRAYS, $config->get_helper_format());
42
+ }
43
+
44
+ public function testSetQuery()
45
+ {
46
+ $search = new AfsSearch('127.0.0.1', 666);
47
+ $query = new AfsQuery();
48
+ $query = $query->set_query('foo');
49
+ $search->set_query($query);
50
+
51
+ $this->assertEquals('foo', $search->get_query()->get_query());
52
+
53
+ $search->execute();
54
+ $this->assertTrue(strpos($search->get_generated_url(), 'query=foo') !== False, 'URL does not contain query!');
55
+ }
56
+
57
+ //Ensure custom parameters are kept
58
+ public function testKeepCustomParameters()
59
+ {
60
+ $query = AfsQuery::create_from_parameters(array("query" => "topic", "mycustomparameter" => "mycustomvalue"));
61
+ $curlConnector = new SAI_CurlStub();
62
+ $mockBaseUrl = "localhost";
63
+ $aboutRequestOpts = array(CURLOPT_URL => "http://$mockBaseUrl/bo-ws/about");
64
+ $aboutResponse = <<<JSON
65
+ {
66
+ "x:type":"ws.response",
67
+ "query":{
68
+ "x:type":"ws.response.query",
69
+ "parameters":{
70
+ "x:type":"collection",
71
+ "x:values":[
72
+
73
+ ]
74
+ },
75
+ "properties":{
76
+ "x:type":"x:dynamic"
77
+ }
78
+ },
79
+ "result":{
80
+ "x:type":"bows.about",
81
+ "boWsVersion":{
82
+ "x:type":"AfsVersion",
83
+ "build":"3eaebfd1f1fe261780347cbc35bfbd65d613575e",
84
+ "gen":"7.6",
85
+ "major":"4",
86
+ "minor":"0",
87
+ "motto":"Pink Dolphin"
88
+ },
89
+ "copyright":"Copyright (C) 1999-2013 Antidot"
90
+ }
91
+ }
92
+ JSON;
93
+ $response = <<<JSON
94
+ {"header":{"query":{"userId":"user_5354ec142aa12","sessionId":"session_5354ec142aa4b","date":"2014-04-21T13:41:10+0200",
95
+ "queryParam":[{"name":"afs:service","value":"71003"},{"name":"afs:status","value":"beta"},{"name":"afs:query","value":"topic"},
96
+ {"name":"afs:query@Book","value":"topic"},{"name":"afs:query@Topic","value":"topic"},
97
+ {"name":"afs:query@spellcheck","value":"topic"},{"name":"afs:output","value":"json,2"},{"name":"afs:output@Book","value":"json,2"},
98
+ {"name":"afs:output@Topic","value":"json,2"},{"name":"afs:output@spellcheck","value":"json,2"},{"name":"mycustomparameter","value":"mycustomvalue"},
99
+ {"name":"mycustomparameter@Book","value":"mycustomvalue"},{"name":"mycustomparameter@Topic","value":"mycustomvalue"},
100
+ {"name":"mycustomparameter@spellcheck","value":"mycustomvalue"},{"name":"afs:replies","value":"10"},
101
+ {"name":"afs:replies@Book","value":"10"},{"name":"afs:replies@Topic","value":"10"},{"name":"afs:replies@spellcheck",
102
+ "value":"10"}],"mainCtx":{"textQuery":"topic"},"textQuery":"topic"},"user":{"requestMethod":"GET","agent":"Mozilla 5.0",
103
+ "address":"127.0.0.1","output":{"format":"JSON","encoding":"gzip","charset":"UTF-8"}},"performance":{"durationMs":11},"info":{}},
104
+ "replySet":[{"meta":{"uri":"Book","totalItems":1,"totalItemsIsExact":true,"pageItems":1,"firstPageItem":1,"lastPageItem":1,
105
+ "durationMs":1,"firstPaFId":147,"lastPaFId":147,"producer":"SEARCH"},
106
+ "facets":{"facet":[{"afs:t":"FacetTree","node":[{"key":"urn:dita:single_topic.ditamap",
107
+ "labels":[{"lang":"EN","label":"urn:dita:single_topic.ditamap"},{"lang":"FR","label":"urn:dita:single_topic.ditamap"}],
108
+ "items":1}],"layout":"TREE","type":"STRING","id":"BaseUri","labels":[{"lang":"EN","label":"BaseUri"},
109
+ {"lang":"FR","label":"BaseUri"}]},{"afs:t":"FacetTree","node":[{"key":"noditaval","labels":[{"lang":"EN","label":"noditaval"},
110
+ {"lang":"FR","label":"noditaval"}],"items":1}],"layout":"TREE","type":"STRING","id":"Ditaval",
111
+ "labels":[{"lang":"EN","label":"Ditaval"},{"lang":"FR","label":"Ditaval"}]},{"afs:t":"FacetTree",
112
+ "node":[{"key":"en","labels":[{"label":"English"}],"items":1}],"layout":"TREE","type":"STRING","id":"afs:lang",
113
+ "labels":[{"label":"Language"}]}]},"content":{"reply":[{"docId":3,"uri":"urn:dita:single_topic.ditamap",
114
+ "title":[{"afs:t":"KwicString","text":"Single "},{"afs:t":"KwicMatch","match":"Topic"},
115
+ {"afs:t":"KwicString","text":" Map"}],"relevance":{"rank":1},"layerReplies":{"reply":[{"layer":"USER_1",
116
+ "reply":{"docId":3,"uri":"","clientData":[{"contents":{"meta":[],"label":"Single Topic Map","ditaval":"noditaval",
117
+ "uri":"urn:dita:single_topic.ditamap"},"id":"ditaval","mimeType":"application\/json"}]}}]}}]}},
118
+ {"meta":{"uri":"Topic","totalItems":1,"totalItemsIsExact":true,"pageItems":1,"firstPageItem":1,"lastPageItem":1,"durationMs":2,
119
+ "firstPaFId":147,"lastPaFId":147,"producer":"SEARCH"},"facets":{"facet":[{"afs:t":"FacetTree",
120
+ "node":[{"key":"others","labels":[{"lang":"EN","label":"others"},{"lang":"FR","label":"others"}],"items":1}],
121
+ "layout":"TREE","type":"STRING","id":"Audience","labels":[{"lang":"EN","label":"Audience"},{"lang":"FR","label":"Audience"}]},
122
+ {"afs:t":"FacetTree","node":[{"key":"Single Topic Map","labels":[{"lang":"EN","label":"Single Topic Map"},
123
+ {"lang":"FR","label":"Single Topic Map"}],"items":1}],"layout":"TREE","type":"STRING","id":"Filter_By_Docs",
124
+ "labels":[{"lang":"EN","label":"Filter by Documents"},{"lang":"FR","label":"Filtrer par Documents"}]},
125
+ {"afs:t":"FacetTree","node":[{"key":"en","labels":[{"label":"English"}],"items":1}],"layout":"TREE","type":"STRING",
126
+ "id":"afs:lang","labels":[{"label":"Language"}]}]},"content":{"reply":[{"docId":2,"uri":"urn:dita:single_topic.dita",
127
+ "title":[{"afs:t":"KwicString","text":"Titre du "},{"afs:t":"KwicMatch","match":"topic"},
128
+ {"afs:t":"KwicString","text":" unique"}],"abstract":[{"afs:t":"KwicString","text":"Ce "},
129
+ {"afs:t":"KwicMatch","match":"topic"},{"afs:t":"KwicString","text":" est seul dans sa ditamap et ceci devrait apparaitre dans le résumé."}],
130
+ "relevance":{"rank":1},"layerReplies":{"reply":[{"layer":"USER_2","reply":{"docId":2,"uri":"",
131
+ "clientData":[{"contents":{"book":{"uri":"urn:dita:single_topic.ditamap","label":"Single Topic Map"},
132
+ "topics":[{"uri":"urn:dita:single_topic.dita","label":"Titre du topic unique"}],"ditaval":"noditaval"},
133
+ "id":"breadcrumb","mimeType":"application\/json"}]}}]}}]}}]}
134
+ JSON;
135
+ //Set BO response for AboutConnector
136
+ $curlConnector->setResponse($aboutResponse, $aboutRequestOpts);
137
+ //Set response for query
138
+ $curlConnector->setResponse($response);
139
+ $search = new AfsSearch($mockBaseUrl, '71003', AfsServiceStatus::STABLE, $curlConnector);
140
+ $search->set_query($query);
141
+ $coder = new AfsQueryCoder();
142
+ $search->set_query_coder($coder);
143
+ $helper = $search->execute($query);
144
+ $replysetHelper = $helper->get_replyset("Book");
145
+ $facetHelpers = $replysetHelper->get_facets();
146
+ //Make sure each link of facets contains custom parameter
147
+ foreach($facetHelpers as $facetHelper) {
148
+ foreach($facetHelper->get_elements() as $facetValueHelper) {
149
+ $this->assertEquals(1, preg_match("/[&\?]mycustomparameter=mycustomvalue[&$]/", $facetValueHelper->link));
150
+ }
151
+ }
152
+ $this->assertEquals("mycustomvalue", $helper->get_query_parameter("mycustomparameter"));
153
+ }
154
+ }
lib/antidot/AFS/SEARCH/TEST/spellcheckHelperTest.php ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_spellcheck_helper.php";
3
+ require_once "AFS/SEARCH/afs_query.php";
4
+ require_once "afs_version.php" ;
5
+
6
+ class spellcheckHelperTest extends PHPUnit_Framework_TestCase
7
+ {
8
+ public function testSpellcheckHelperWithoutQueryCoder()
9
+ {
10
+ $input = json_decode('{
11
+ "meta": {
12
+ "uri": "Catalog",
13
+ "totalItems": 2,
14
+ "totalItemsIsExact": true,
15
+ "pageItems": 2,
16
+ "firstPageItem": 1,
17
+ "lastPageItem": 1,
18
+ "durationMs": 4,
19
+ "firstPaFId": 1,
20
+ "lastPaFId": 1,
21
+ "producer": "SPELLCHECK"
22
+ },
23
+ "content": {
24
+ "reply": [
25
+ {
26
+ "docId": 1,
27
+ "uri": "Catalog",
28
+ "title": [
29
+ {
30
+ "afs:t": "KwicMatch",
31
+ "match": "LIGNE"
32
+ }
33
+ ],
34
+ "abstract": [
35
+ {
36
+ "afs:t": "KwicString",
37
+ "text": "LIGNE"
38
+ }
39
+ ],
40
+ "suggestion": [
41
+ {
42
+ "items": [
43
+ {
44
+ "match": {
45
+ "text": "LIGNE"
46
+ }
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ "docId": 2,
54
+ "uri": "Catalog",
55
+ "suggestion": [
56
+ {
57
+ "items": [
58
+ {
59
+ "match": {
60
+ "text": "LIGNE"
61
+ }
62
+ },
63
+ {
64
+ "text": {
65
+ "text": "ET",
66
+ "pre": " "
67
+ }
68
+ },
69
+ {
70
+ "match": {
71
+ "text": "PLUME",
72
+ "pre": " "
73
+ }
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ }
79
+ ]
80
+ }
81
+ }');
82
+ $query = new AfsQuery();
83
+ $query = $query->set_query('lige ET plum');
84
+ $this->assertTrue($query->get_from() != AfsOrigin::SPELLCHECK);
85
+ $mgr = new AfsSpellcheckManager($query, new AfsHelperConfiguration());
86
+ $mgr->add_spellcheck($input);
87
+
88
+ try {
89
+ $mgr->get_spellcheck('foobar');
90
+ $this->fail('Should have raised an exception');
91
+ } catch (OutOfBoundsException $e) { }
92
+
93
+ $spellcheck = $mgr->get_spellcheck('Catalog');
94
+ $this->assertEquals(2, count($spellcheck));
95
+
96
+ // This is also good because there is only one spellcheck result
97
+ $spellcheck = $mgr->get_spellcheck();
98
+ $this->assertEquals(2, count($spellcheck));
99
+
100
+ $first = $spellcheck[0];
101
+ $this->assertEquals('LIGNE', $first->get_raw_text());
102
+ $this->assertEquals('<b>LIGNE</b>', $first->get_formatted_text());
103
+ $this->assertEquals('LIGNE', $first->get_query()->get_query());
104
+ $this->assertEquals(AfsOrigin::SPELLCHECK, $first->get_query()->get_from());
105
+
106
+ $second = $spellcheck[1];
107
+ $this->assertEquals('LIGNE ET PLUME', $second->get_raw_text());
108
+ $this->assertEquals('<b>LIGNE</b> ET <b>PLUME</b>', $second->get_formatted_text());
109
+ $this->assertEquals(AfsOrigin::SPELLCHECK, $second->get_query()->get_from());
110
+ }
111
+
112
+ public function testMultiSpellcheck()
113
+ {
114
+ $input1 = $this->get_spellcheck_input('Cot', 'FOO');
115
+ $input2 = $this->get_spellcheck_input('Cat', 'BAR');
116
+
117
+ $query = new AfsQuery();
118
+ $query = $query->set_query('lige ET plum');
119
+ $mgr = new AfsSpellcheckManager($query, new AfsHelperConfiguration());
120
+ $mgr->add_spellcheck($input1);
121
+ $mgr->add_spellcheck($input2);
122
+
123
+ try {
124
+ $mgr->get_spellcheck(); // invalid: 2 spellcheck replies are available
125
+ $this->fail('Should have raised an exception');
126
+ } catch (OutOfBoundsException $e) { }
127
+
128
+ $spellcheck = $mgr->get_spellcheck('Cot');
129
+ $first = $spellcheck[0];
130
+ $this->assertEquals('FOO', $first->get_raw_text());
131
+ $this->assertEquals('<b>FOO</b>', $first->get_formatted_text());
132
+ $this->assertEquals('FOO', $first->get_query()->get_query());
133
+
134
+ $spellcheck = $mgr->get_spellcheck('Cat');
135
+ $second = $spellcheck[0];
136
+ $this->assertEquals('BAR', $second->get_raw_text());
137
+ $this->assertEquals('<b>BAR</b>', $second->get_formatted_text());
138
+ $this->assertEquals('BAR', $second->get_query()->get_query());
139
+
140
+ $spellchecks = $mgr->get_spellchecks();
141
+ $this->assertEquals(2, count($spellchecks));
142
+ foreach ($spellchecks as $feed => $spellcheck) {
143
+ if ('Cot' == $feed) {
144
+ $this->assertEquals('FOO', $spellcheck[0]->get_raw_text());
145
+ $this->assertEquals('<b>FOO</b>', $spellcheck[0]->get_formatted_text());
146
+ } elseif ('Cat' == $feed) {
147
+ $this->assertEquals('BAR', $spellcheck[0]->get_raw_text());
148
+ $this->assertEquals('<b>BAR</b>', $spellcheck[0]->get_formatted_text());
149
+ } else {
150
+ $this->fail('Unknown feed: ' . $feed);
151
+ }
152
+ }
153
+ }
154
+
155
+ private function get_spellcheck_input($feed, $match)
156
+ {
157
+ return json_decode('{
158
+ "meta": {
159
+ "uri": "Catalog",
160
+ "totalItems": 2,
161
+ "totalItemsIsExact": true,
162
+ "pageItems": 2,
163
+ "firstPageItem": 1,
164
+ "lastPageItem": 1,
165
+ "durationMs": 4,
166
+ "firstPaFId": 1,
167
+ "lastPaFId": 1,
168
+ "producer": "SPELLCHECK"
169
+ },
170
+ "content": {
171
+ "reply": [
172
+ {
173
+ "docId": 1,
174
+ "uri": "' . $feed . '",
175
+ "title": [
176
+ {
177
+ "afs:t": "KwicMatch",
178
+ "match": "' . $match . '"
179
+ }
180
+ ],
181
+ "abstract": [
182
+ {
183
+ "afs:t": "KwicString",
184
+ "text": "' . $match . '"
185
+ }
186
+ ],
187
+ "suggestion": [
188
+ {
189
+ "items": [
190
+ {
191
+ "match": {
192
+ "text": "' . $match . '"
193
+ }
194
+ }
195
+ ]
196
+ }
197
+ ]
198
+ }
199
+ ]
200
+ }
201
+ }');
202
+ }
203
+
204
+ public function testSpellcheckWithSep() // bug #2531
205
+ {
206
+ $input = json_decode('{
207
+ "meta": {
208
+ "uri": "Catalog",
209
+ "totalItems": 2,
210
+ "totalItemsIsExact": true,
211
+ "pageItems": 2,
212
+ "firstPageItem": 1,
213
+ "lastPageItem": 1,
214
+ "durationMs": 4,
215
+ "firstPaFId": 1,
216
+ "lastPaFId": 1,
217
+ "producer": "SPELLCHECK"
218
+ },
219
+ "content": {
220
+ "reply": [
221
+ {
222
+ "docId": 1,
223
+ "uri": "Catalog",
224
+ "title": [
225
+ {
226
+ "afs:t": "KwicMatch",
227
+ "match": "Bar"
228
+ }
229
+ ],
230
+ "abstract": [
231
+ {
232
+ "afs:t": "KwicString",
233
+ "text": "Bar"
234
+ }
235
+ ],
236
+ "suggestion": [
237
+ {
238
+ "items": [
239
+ { "sep": { "text": "(" } },
240
+ { "match": { "text": "FOO" } }
241
+ ]
242
+ }
243
+ ]
244
+ }
245
+ ]
246
+ }
247
+ }');
248
+ $query = new AfsQuery();
249
+ $mgr = new AfsSpellcheckManager($query, new AfsHelperConfiguration());
250
+ $mgr->add_spellcheck($input);
251
+
252
+ $spellcheck = $mgr->get_spellcheck('Catalog');
253
+ $this->assertEquals(1, count($spellcheck));
254
+
255
+ $first = $spellcheck[0];
256
+ $this->assertEquals('(FOO', $first->get_raw_text());
257
+ $this->assertEquals('(<b>FOO</b>', $first->get_formatted_text());
258
+ $this->assertEquals('(FOO', $first->get_query()->get_query());
259
+ $this->assertEquals(AfsOrigin::SPELLCHECK, $first->get_query()->get_from());
260
+ }
261
+ }
262
+
263
+
lib/antidot/AFS/SEARCH/TEST/spellcheckTextVisitorTest.php ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_spellcheck_text_visitor.php";
3
+
4
+ class SpellcheckVisitor implements AfsSpellcheckTextVisitorInterface
5
+ {
6
+ public $text = null;
7
+ public $text_pre = null;
8
+ public $match = null;
9
+ public $match_pre = null;
10
+
11
+ public function visit_AfsSpellcheckText(AfsSpellcheckText $text)
12
+ {
13
+ $this->text = $text->get_text();
14
+ $this->text_pre = $text->get_pre();
15
+ return new AfsRawAndFormattedText($this->text, '<s>' . $this->text . '</s>');
16
+ }
17
+
18
+ public function visit_AfsSpellcheckMatch(AfsSpellcheckMatch $text)
19
+ {
20
+ $this->match = $text->get_text();
21
+ $this->match_pre = $text->get_pre();
22
+ return new AfsRawAndFormattedText($this->match_pre . $this->match,
23
+ $this->match_pre . '<b>' . $this->match . '</b>');
24
+ }
25
+ }
26
+
27
+
28
+ class spellcheckTextVisitorTest extends PHPUnit_Framework_TestCase
29
+ {
30
+ public function testVisitTextClasses()
31
+ {
32
+ $element = json_decode('{
33
+ "text": { "text": "foo",
34
+ "pre": "pret" },
35
+ "match": { "text": "bar",
36
+ "pre": "prem" }
37
+ }');
38
+ $texts = array(new AfsSpellcheckText($element->text),
39
+ new AfsSpellcheckMatch($element->match));
40
+ $visitor = new SpellcheckVisitor();
41
+ foreach ($texts as $text) {
42
+ $text->accept($visitor);
43
+ }
44
+
45
+ $this->assertEquals('pretfoo', $visitor->text);
46
+ $this->assertEquals('', $visitor->text_pre);
47
+ $this->assertEquals('bar' , $visitor->match);
48
+ $this->assertEquals('prem', $visitor->match_pre);
49
+ }
50
+
51
+ public function testTextManager()
52
+ {
53
+ $texts = json_decode('[
54
+ { "text": { "text": "foo",
55
+ "pre": "pret" } },
56
+ { "match": { "text": "bar",
57
+ "pre": "prem" } },
58
+ { "text": { "text": "baz" } },
59
+ { "match": { "text": "bat" } }
60
+ ]');
61
+ $mgr = new AfsSpellcheckTextManager($texts);
62
+ $visitor = new SpellcheckVisitor();
63
+ $text = $mgr->visit_text($visitor);
64
+ $this->assertEquals('<s>pretfoo</s>prem<b>bar</b><s>baz</s><b>bat</b>', $text->formatted);
65
+ $this->assertEquals('pretfooprembarbazbat', $text->raw);
66
+ }
67
+ }
68
+
69
+
lib/antidot/AFS/SEARCH/TEST/textVisitorTest.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php ob_start();
2
+ require_once "AFS/SEARCH/afs_text_helper.php";
3
+
4
+ class Visitor implements AfsTextVisitorInterface
5
+ {
6
+ public $string_text = null;
7
+ public $match_text = null;
8
+ public $trunc_text = null;
9
+
10
+ public function visit_AfsStringText(AfsStringText $afs_text)
11
+ {
12
+ $this->string_text = $afs_text->get_text();
13
+ return '<s>' . $this->string_text . '</s>';
14
+ }
15
+
16
+ public function visit_AfsMatchText(AfsMatchText $afs_text)
17
+ {
18
+ $this->match_text = $afs_text->get_text();
19
+ return '<m>' . $this->match_text . '</m>';
20
+ }
21
+
22
+ public function visit_AfsTruncateText(AfsTruncateText $afs_text)
23
+ {
24
+ $this->trunc_text = $afs_text->get_text();
25
+ return '<t>' . $this->trunc_text . '</t>';
26
+ }
27
+ }
28
+
29
+ class Element
30
+ {
31
+ public $text = null;
32
+ public $match = null;
33
+
34
+ public function __construct($text, $match)
35
+ {
36
+ $this->text = $text;
37
+ $this->match = $match;
38
+ }
39
+ }
40
+
41
+
42
+ class textVisitorTest extends PHPUnit_Framework_TestCase
43
+ {
44
+ public function testVisitTextClasses()
45
+ {
46
+ $element = new Element('string', 'match');
47
+ $texts = array(new AfsStringText($element),
48
+ new AfsMatchText($element),
49
+ new AfsTruncateText($element));
50
+ $visitor = new Visitor();
51
+ foreach ($texts as $text) {
52
+ $text->accept($visitor);
53
+ }
54
+
55
+ $this->assertTrue($visitor->string_text == 'string');
56
+ $this->assertTrue($visitor->match_text == 'match');
57
+ $this->assertTrue($visitor->trunc_text == '...');
58
+ }
59
+
60
+ public function testTextManager()
61
+ {
62
+ $texts = json_decode('[
63
+ { "afs:t": "KwicString",
64
+ "text" : "string" },
65
+ { "afs:t": "KwicMatch",
66
+ "match": "match" },
67
+ { "afs:t": "KwicString",
68
+ "text" : "str" },
69
+ { "afs:t": "KwicMatch",
70
+ "match": "mat" },
71
+ { "afs:t": "KwicString",
72
+ "text" : "s" },
73
+ { "afs:t": "KwicTruncate" }
74
+ ]');
75
+ $mgr = new AfsTextManager($texts);
76
+ $visitor = new Visitor();
77
+ $this->assertTrue($mgr->visit_text($visitor)
78
+ == '<s>string</s><m>match</m><s>str</s><m>mat</m><s>s</s><t>...</t>');
79
+ }
80
+ }
81
+
82
+
lib/antidot/AFS/SEARCH/afs_base_reply_helper.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "COMMON/afs_helper_base.php";
3
+ require_once "AFS/SEARCH/afs_text_helper.php";
4
+
5
+ /** @brief Base class for reply helpers. */
6
+ class AfsBaseReplyHelper extends AfsHelperBase
7
+ {
8
+ protected $reply = null;
9
+ protected $visitor = null;
10
+
11
+ /** @brief Constructs new instance.
12
+ * @param $reply [in] should correspond to one reply.
13
+ * @param $visitor [in] visitor used to traverse title and abstract reply
14
+ * texts.
15
+ */
16
+ public function __construct($reply, AfsTextVisitorInterface $visitor)
17
+ {
18
+ $this->reply = $reply;
19
+ $this->visitor = $visitor;
20
+ }
21
+
22
+ /** @brief Retrieves formatted title reply.
23
+ * @return title reply or empty string if not defined.
24
+ */
25
+ public function get_title()
26
+ {
27
+ if (property_exists($this->reply, 'title')) {
28
+ return $this->get_text($this->reply->title);
29
+ } else {
30
+ return '';
31
+ }
32
+ }
33
+
34
+ /** @brief Retrieves formatted abstract reply.
35
+ * @return abstract reply or empty string if not defined.
36
+ */
37
+ public function get_abstract()
38
+ {
39
+ if (property_exists($this->reply, 'abstract')) {
40
+ return $this->get_text($this->reply->abstract);
41
+ } else {
42
+ return '';
43
+ }
44
+ }
45
+
46
+ /** @brief Retrieves URI of the document.
47
+ * @return document URI or empty string if not set.
48
+ */
49
+ public function get_uri()
50
+ {
51
+ if (property_exists($this->reply, 'uri')) {
52
+ return $this->reply->uri;
53
+ } else {
54
+ return '';
55
+ }
56
+ }
57
+
58
+ /** @brief Retrieves reply data as array.
59
+ *
60
+ * All data are store in <tt>key => value</tt> format:
61
+ * @li @c title: title of the reply,
62
+ * @li @c abstract: abstract of the reply,
63
+ * @li @c uri: URI of the reply.
64
+ *
65
+ * @return array filled with key and values.
66
+ */
67
+ public function format()
68
+ {
69
+ return array('title' => $this->get_title(),
70
+ 'abstract' => $this->get_abstract(),
71
+ 'uri' => $this->get_uri());
72
+ }
73
+
74
+ /** @internal
75
+ * @brief Common acces for title and abstract reply formatter.
76
+ * @param $json_text [in] appropriate text block (title/abstract) in JSON
77
+ * format.
78
+ * @return formatted text.
79
+ */
80
+ protected function get_text($json_text)
81
+ {
82
+ $text_mgr = new AfsTextManager($json_text);
83
+ return $text_mgr->visit_text($this->visitor);
84
+ }
85
+
86
+ }
87
+
88
+
lib/antidot/AFS/SEARCH/afs_base_replyset_helper.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_meta_helper.php';
3
+ require_once 'AFS/SEARCH/afs_producer.php';
4
+ require_once 'COMMON/afs_helper_base.php';
5
+ require_once 'COMMON/afs_helper_format.php';
6
+
7
+ /** @brief Base class for replyset helpers. */
8
+ class AfsBaseReplysetHelper extends AfsHelperBase
9
+ {
10
+ protected $meta = null;
11
+ protected $replies = array();
12
+
13
+ /** @brief Construct new replyset helper instance.
14
+ *
15
+ * @param $reply_set [in] one reply from decoded json reply.
16
+ * @param $config [in] helper configuration object (see AfsHelperConfiguration).
17
+ * @param $factory [in] used to create appropriate reply helper.
18
+ */
19
+ public function __construct($reply_set, AfsHelperConfiguration $config,
20
+ AfsReplyHelperFactory $factory)
21
+ {
22
+ $this->initialize_meta($reply_set, $config);
23
+ $this->initialize_content($reply_set, $config, $factory);
24
+ }
25
+
26
+ protected function initialize_meta($reply_set, AfsHelperConfiguration $config)
27
+ {
28
+ $this->meta = new AfsMetaHelper($reply_set->meta);
29
+ }
30
+
31
+ protected function initialize_content($reply_set, AfsHelperConfiguration $config, $factory)
32
+ {
33
+ if (property_exists($reply_set, 'content')) {
34
+ $feed = $this->meta->get_feed();
35
+ $this->replies = $factory->create_replies($feed, $reply_set->content);
36
+ }
37
+ }
38
+
39
+ /** @brief Retrieves meta data object.
40
+ * @return instance of @a AfsMetaHelper.
41
+ */
42
+ public function get_meta()
43
+ {
44
+ return $this->meta;
45
+ }
46
+
47
+ /** @brief Checks whether reply set contains at least one reply.
48
+ * @return true when one or more reply is defined, false otherwise.
49
+ */
50
+ public function has_reply()
51
+ {
52
+ return ! empty($this->replies);
53
+ }
54
+ /** @brief Retrieve number of replies for current page.
55
+ *
56
+ * you can retrieve total number of replies through
57
+ * <tt>get_meta()->get_total_items()</tt>.
58
+ *
59
+ * @return number of replies for current page.
60
+ */
61
+ public function get_nb_replies()
62
+ {
63
+ return count($this->replies);
64
+ }
65
+ /** @brief Retrieves all replies of current page.
66
+ *
67
+ * You can loop on each reply:
68
+ * @code
69
+ * foreach ($replies->get_replies() as $reply) {
70
+ * // Work on reply
71
+ * }
72
+ * @endcode
73
+ *
74
+ * @return All replies of current page.
75
+ */
76
+ public function get_replies()
77
+ {
78
+ return $this->replies;
79
+ }
80
+
81
+ /** @brief Retrieves replyset as array.
82
+ *
83
+ * All data are store in <tt>key => value</tt> format:
84
+ * @li @c meta: array of meta data (@a AfsMetaHelper::format),
85
+ * @li @c nb_replies: number of replies on the current page.
86
+ * @li @c replies: standard or Promote reply.
87
+ *
88
+ * @return array filled with key and values.
89
+ */
90
+ public function format()
91
+ {
92
+ $result = array('meta' => $this->get_meta()->format(),
93
+ 'nb_replies' => $this->get_nb_replies());
94
+
95
+ if ($this->has_reply()) {
96
+ $formatted_replies = array();
97
+ foreach ($this->replies as $reply)
98
+ $formatted_replies[] = $reply->format();
99
+ $result['replies'] = $formatted_replies;
100
+ }
101
+ return $result;
102
+ }
103
+ }
104
+
105
+
lib/antidot/AFS/SEARCH/afs_client_data_exception.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_exception.php';
3
+
4
+ /** @brief Base exception class for all client data access errors. */
5
+ abstract class AfsClientDataException extends AfsBaseException
6
+ { }
7
+
8
+ /** @brief Exception raised on malformed expression or invalid context.
9
+ *
10
+ * See DOMXPath::query official documentation for more details. */
11
+ class AfsInvalidQueryException extends AfsClientDataException
12
+ { }
13
+
14
+ /** @brief Exception raised when provided path query has no result. */
15
+ class AfsNoResultException extends AfsClientDataException
16
+ { }
17
+
18
+
lib/antidot/AFS/SEARCH/afs_client_data_helper.php ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_text_visitor.php";
3
+ require_once "AFS/SEARCH/afs_client_data_exception.php";
4
+ require_once "COMMON/afs_helper_base.php";
5
+ require_once "COMMON/afs_tools.php";
6
+
7
+
8
+ /** @brief Manage client data.
9
+ *
10
+ * Instances of this class allow to manage one or more XML and JSON client
11
+ * data.*/
12
+ class AfsClientDataManager
13
+ {
14
+ private $client_data = array();
15
+
16
+ /** @brief Construct new manager with all necessary client data helpers.
17
+ *
18
+ * One or more client data helper can be created and managed.
19
+ * @param $client_datas [in] root of client data element.
20
+ */
21
+ public function __construct($client_datas)
22
+ {
23
+ foreach ($client_datas as $data) {
24
+ $helper = AfsClientDataHelperFactory::create($data);
25
+ $this->client_data[$helper->id] = $helper;
26
+ }
27
+ }
28
+
29
+ /** @brief Retrieves client data helper.
30
+ *
31
+ * @param $id [in] Id of the client data to retrieve (default='main').
32
+ * @return client data helper.
33
+ *
34
+ * @exception OutOfBoundsException when required client data is not found.
35
+ */
36
+ public function get_clientdata($id='main')
37
+ {
38
+ if (array_key_exists($id, $this->client_data)) {
39
+ return $this->client_data[$id];
40
+ } else {
41
+ throw new OutOfBoundsException('No client data with id \'' . $id . '\' found.');
42
+ }
43
+ }
44
+
45
+ /** @brief Retrieves value from the appropriate client data.
46
+ *
47
+ * @param $id [in] client data id.
48
+ * @param $name [in] name or XPath of the required element for JSON
49
+ * respectively XML client data.
50
+ * @param $context [in] context used to look for text with specified name.
51
+ * @param $formatter [in] used for highlighted content (default=null,
52
+ * appropriate formatter is instanced for JSON and XML).
53
+ *
54
+ * @return client data as text.
55
+ */
56
+ public function get_value($id, $name=null, $context=array(), $formatter=null)
57
+ {
58
+ return $this->get_clientdata($id)->get_value($name, $context, $formatter);
59
+ }
60
+
61
+ /** @brief Retrieves value(s) from the appropriate client data.
62
+ *
63
+ * @param $id [in] client data id.
64
+ * @param $name [in] name or XPath of the required element for JSON
65
+ * respectively XML client data.
66
+ * @param $context [in] context used to look for text with specified name.
67
+ * @param $formatter [in] used for highlighted content (default=null,
68
+ * appropriate formatter is instanced for JSON and XML).
69
+ *
70
+ * @return client data as text.
71
+ */
72
+ public function get_values($id, $name=null, $context=array(), $formatter=null)
73
+ {
74
+ return $this->get_clientdata($id)->get_values($name, $context, $formatter);
75
+ }
76
+ }
77
+
78
+
79
+ /** @brief Client data interface. */
80
+ interface AfsClientDataHelperInterface
81
+ {
82
+ /** @brief Retrieves client data as text.
83
+ *
84
+ * All client data or sub-tree can be retrieved depending on @a name
85
+ * parameter.
86
+ * @param $name [in] data name to be extracted (default=null, retrieve
87
+ * all client data).
88
+ * @param $context [in] context used for looking for text with specified name.
89
+ * @param $formatter [in] format output string. It is used when highlight in
90
+ * client data is activated. See implementation to provide
91
+ * appropriate formatter (default=null, default formatter is used).
92
+ * @return first matching client data with specified name as text.
93
+ */
94
+ public function get_value($name=null, $context=array(), $formatter=null);
95
+ /** @brief Retrieves client data as array of texts.
96
+ *
97
+ * All client data or sub-tree can be retrieved depending on @a name
98
+ * parameter.
99
+ * @param $name [in] data name to be extracted (default=null, retrieve
100
+ * all client data).
101
+ * @param $context [in] context used for looking for text with specified name.
102
+ * @param $formatter [in] format output string. It is used when highlight in
103
+ * client data is activated. See implementation to provide
104
+ * appropriate formatter (default=null, default formatter is used).
105
+ * @return matching client data as array of texts.
106
+ */
107
+ public function get_values($name=null, $context=array(), $formatter=null);
108
+
109
+ /** @brief Retrieve client data's mime type.
110
+ *
111
+ * @return mime type of the client data.
112
+ */
113
+ public function get_mime_type();
114
+ }
115
+
116
+ /** @brief Base class for client data helpers. */
117
+ abstract class AfsClientDataHelperBase extends AfsHelperBase
118
+ {
119
+ private $id = null;
120
+
121
+ /** @brief Construct base class instance.
122
+ * @param $client_data [in] client data used to retrieve the right id.
123
+ */
124
+ public function __construct($client_data)
125
+ {
126
+ $this->id = $client_data->id;
127
+ }
128
+
129
+ /** @brief Retrieve client data id.
130
+ * @return id associated to client data.
131
+ */
132
+ public function get_id()
133
+ {
134
+ return $this->id;
135
+ }
136
+ }
137
+
138
+
139
+ /** @brief Factory for client data helper. */
140
+ class AfsClientDataHelperFactory
141
+ {
142
+ /** @brief Create appropriate client data helper.
143
+ * @param $client_data [in] client data entry point.
144
+ * @return appropriate client data helper.
145
+ * @exception Exception invalid @a client_data parameter provided
146
+ */
147
+ public static function create($client_data)
148
+ {
149
+ if (! property_exists($client_data, 'mimeType')) {
150
+ throw new Exception('No mime-type available for provided client data.');
151
+ } elseif (! property_exists($client_data, 'contents')) {
152
+ throw new Exception('No content available for provided client data.');
153
+ } elseif ($client_data->mimeType == 'text/xml'
154
+ || $client_data->mimeType == 'application/xml') {
155
+ return new AfsXmlClientDataHelper($client_data);
156
+ } elseif ($client_data->mimeType == 'text/json'
157
+ || $client_data->mimeType == 'application/json') {
158
+ return new AfsJsonClientDataHelper($client_data);
159
+ } else {
160
+ throw new Exception('Unmanaged client data type: ' . $client_data->mimeType);
161
+ }
162
+ }
163
+ }
164
+
165
+
166
+ /** @brief XML client data helper. */
167
+ class AfsXmlClientDataHelper extends AfsClientDataHelperBase implements AfsClientDataHelperInterface
168
+ {
169
+ private $contents = null;
170
+ private $doc = null;
171
+ private $callbacks = array(); // callbacks activated on specific node name
172
+ private static $afs_ns = 'http://ref.antidot.net/v7/afs#';
173
+
174
+ /** @brief Construct new instance of XML helper.
175
+ * @param $client_data [in] input data used to initialize the instance.
176
+ */
177
+ public function __construct($client_data)
178
+ {
179
+ parent::__construct($client_data);
180
+
181
+ // Client data content is not XML valid when highlight is activated for
182
+ // client data and a match occurs: no afs namespace prefix is defined!
183
+ // No namespace declared for truncated client data...
184
+ // So check whether afs prefix namespace is used
185
+ $has_trunc = false;
186
+ if (strpos($client_data->contents, '<afs:match>') !== false
187
+ || strpos($client_data->contents, '<afs:trunc/>') !== false) {
188
+ $this->contents = str_replace_first('>',
189
+ ' xmlns:afs="' . AfsXmlClientDataHelper::$afs_ns . '">',
190
+ $client_data->contents);
191
+ $this->init_callbacks();
192
+ } else {
193
+ $this->contents = $client_data->contents;
194
+ }
195
+ $this->doc = new DOMDocument();
196
+ $this->doc->loadXML($this->contents);
197
+ }
198
+
199
+ /** @brief Retrieves text from XML node.
200
+ *
201
+ * @remark @c afs prefix should never be used in provided XPath.
202
+ *
203
+ * @param $path [in] XPath to apply (default=null, retrieve all content as
204
+ * text).
205
+ * @param $nsmap [in] prefix/uri mapping to use along with provided XPath.
206
+ * @param $callbacks [in] list of callbacks to emphase text when highlight
207
+ * of client data is activated or when client data text is truncated.
208
+ * It should be list of @a FilterNode type
209
+ * (default=null, default instances of @a FilterNode are used).
210
+ *
211
+ * @return text of first specific node(s) depending on parameters.
212
+ *
213
+ * @exception AfsInvalidQueryException when provided XPath is invalid.
214
+ * @exception AfsNoResultException when provided XPath returns no value/node.
215
+ */
216
+ public function get_value($path=null, $nsmap=array(), $callbacks=null)
217
+ {
218
+ if (is_null($path)) {
219
+ return $this->contents;
220
+ } else {
221
+ $items = $this->apply_xpath($path, $nsmap);
222
+ $named_callbacks = $this->update_callbacks(is_null($callbacks) ? array() : $callbacks);
223
+ return DOMNodeHelper::get_text($items->item(0), $named_callbacks);
224
+ }
225
+ }
226
+
227
+ /** @brief Retrieves array of texts from XML node.
228
+ *
229
+ * @remark @c afs prefix should never be used in provided XPath.
230
+ *
231
+ * @param $path [in] XPath to apply (default=null, retrieve all content as
232
+ * text).
233
+ * @param $nsmap [in] prefix/uri mapping to use along with provided XPath.
234
+ * @param $callbacks [in] list of callbacks to emphase text when highlight
235
+ * of client data is activated or when client data text is truncated.
236
+ * It should be list of @a FilterNode type
237
+ * (default=null, default instances of @a FilterNode are used).
238
+ *
239
+ * @return text of first specific node(s) depending on parameters.
240
+ *
241
+ * @exception AfsInvalidQueryException when provided XPath is invalid.
242
+ * @exception AfsNoResultException when provided XPath returns no value/node.
243
+ */
244
+ public function get_values($path=null, $nsmap=array(), $callbacks=null)
245
+ {
246
+ if (is_null($path)) {
247
+ return array($this->contents);
248
+ } else {
249
+ $items = $this->apply_xpath($path, $nsmap);
250
+ $named_callbacks = $this->update_callbacks(is_null($callbacks) ? array() : $callbacks);
251
+ $result = array();
252
+ foreach ($items as $item) {
253
+ $result[] = DOMNodeHelper::get_text($item, $named_callbacks);
254
+ }
255
+ return $result;
256
+ }
257
+ }
258
+
259
+ /** @internal
260
+ * @brief Retrieves values at given XPath or fails.
261
+ *
262
+ * @param $path [in] XPath to apply to the document.
263
+ * @param $nsmap [in] Namespace mapping used along with XPath.
264
+ *
265
+ * @return List of matching results.
266
+ *
267
+ * @exception AfsInvalidQueryException provided XPath is invalid.
268
+ * @exception AfsNoResultException provided XPath does not return any result.
269
+ */
270
+ private function apply_xpath($path, $nsmap)
271
+ {
272
+ $xpath = new DOMXPath($this->doc);
273
+ if (! array_key_exists('afs', $nsmap)) {
274
+ $nsmap['afs'] = AfsXmlClientDataHelper::$afs_ns;
275
+ }
276
+ foreach ($nsmap as $prefix => $namespace) {
277
+ $xpath->registerNamespace($prefix, $namespace);
278
+ }
279
+ $result = $xpath->query($path);
280
+ if (false === $result) {
281
+ throw new AfsInvalidQueryException('Invalid XPath: ' . $path);
282
+ } elseif ($result->length == 0) {
283
+ throw new AfsNoResultException('No result available for: ' . $path);
284
+ }
285
+ return $result;
286
+ }
287
+
288
+ /** @brief Retrieve client data's mime type.
289
+ *
290
+ * @return application/xml mime type.
291
+ */
292
+ public function get_mime_type()
293
+ {
294
+ return 'application/xml';
295
+ }
296
+
297
+ private function init_callbacks()
298
+ {
299
+ $this->callbacks[] = new BoldFilterNode('match', AfsXmlClientDataHelper::$afs_ns);
300
+ $this->callbacks[] = new TruncatedFilterNode('trunc', AfsXmlClientDataHelper::$afs_ns);
301
+ }
302
+ private function update_callbacks($callbacks)
303
+ {
304
+ if (empty($callbacks))
305
+ $callbacks = $this->callbacks;
306
+ return array(XML_ELEMENT_NODE => $callbacks);
307
+ }
308
+ }
309
+
310
+
311
+ /** @brief Helper for client data in JSON format. */
312
+ class AfsJsonClientDataHelper extends AfsClientDataHelperBase implements AfsClientDataHelperInterface
313
+ {
314
+ private $client_data;
315
+
316
+ /** @brief Construct new instance of JSON helper.
317
+ * @param $client_data [in] input data used to initialize the instance.
318
+ */
319
+ public function __construct($client_data)
320
+ {
321
+ parent::__construct($client_data);
322
+
323
+ $this->client_data = $client_data;
324
+ }
325
+
326
+ /** @brief Retrieves text from JSON content.
327
+ *
328
+ * @param $name [in] name of the first element to retrieve (default=null,
329
+ * all JSON content is returned as text). Empty string allows to
330
+ * retrieve text content correctly formatted when highlight is
331
+ * activated.
332
+ * @param $unused Hum...
333
+ * @param $visitor [in] instance of @a AfsTextVisitorInterface used to format
334
+ * appropriately text content when highlight has been activated
335
+ * (default=null, @a AfsTextVisitor is used).
336
+ *
337
+ * @return formatted text.
338
+ *
339
+ * @exception AfsNoResultException when required JSON element is not defined.
340
+ *
341
+ * @par Example with name=null:
342
+ * Input JSON client data:
343
+ * @verbatim
344
+ {
345
+ "clientData": [
346
+ {
347
+ "contents": { "data": [ "afs:t": "KwicString", "text": "some text" ] },
348
+ "id": "data1",
349
+ "mimeType": "application/json"
350
+ }
351
+ ]
352
+ }
353
+ @endverbatim
354
+ * Call to <tt>get_text(null)</tt> will return
355
+ * @verbatim {"data":["afs:t":"KwicString","text":"some text"]}@endverbatim.
356
+ *
357
+ * @par Example with name='data':
358
+ * Same input JSON as previous example:
359
+ * @verbatim
360
+ {
361
+ "clientData": [
362
+ {
363
+ "contents": { "data": [ "afs:t": "KwicString", "text": "some text" ] },
364
+ "id": "data1",
365
+ "mimeType": "application/json"
366
+ }
367
+ ]
368
+ }
369
+ @endverbatim
370
+ * Call to <tt>get_text('data')</tt> will return
371
+ * @verbatim some text @endverbatim.
372
+ *
373
+ * @par Example with name='':
374
+ * Client data is a @em simple text:
375
+ * @verbatim
376
+ {
377
+ "clientData": [
378
+ {
379
+ "contents": [ { "afs:t": "KwicString", "text": "some text" } ],
380
+ "id": "data1",
381
+ "mimeType": "application/json"
382
+ }
383
+ ]
384
+ }
385
+ @endverbatim
386
+ * Call to <tt>get_text('')</tt> will return
387
+ * @verbatim some text @endverbatim.
388
+ */
389
+ public function get_value($name=null, $unused=array(), $visitor=null)
390
+ {
391
+ return $this->get_values($name, $unused, $visitor);
392
+ }
393
+
394
+ /** @brief Same result as @a get_value method
395
+ *
396
+ * @param $name [in] name of the first element to retrieve (default=null,
397
+ * all JSON content is returned as text). Empty string allows to
398
+ * retrieve text content correctly formatted when highlight is
399
+ * activated.
400
+ * @param $unused Hum...
401
+ * @param $visitor [in] instance of @a AfsTextVisitorInterface used to format
402
+ * appropriately text content when highlight has been activated
403
+ * (default=null, @a AfsTextVisitor is used).
404
+ *
405
+ * @return formatted text.
406
+ *
407
+ * @exception AfsNoResultException when required JSON element is not defined.
408
+ */
409
+ public function get_values($name=null, $unused=array(), $visitor=null)
410
+ {
411
+ if (is_null($visitor)) {
412
+ $visitor = new AfsTextVisitor();
413
+ }
414
+ $contents = $this->client_data->contents;
415
+ if (is_null($name)) {
416
+ return json_encode($contents);
417
+ } else {
418
+ if (! is_array($contents)) {
419
+ if (property_exists($contents, $name)) {
420
+ $text_mgr = new AfsTextManager($contents->$name);
421
+ } else {
422
+ throw new AfsNoResultException('No client data content named: ' . $name);
423
+ }
424
+ } else {
425
+ $text_mgr = new AfsTextManager($contents);
426
+ }
427
+ return $text_mgr->visit_text($visitor);
428
+ }
429
+ }
430
+
431
+ /** @brief Retrieve client data's mime type.
432
+ *
433
+ * @return application/json mime type.
434
+ */
435
+ public function get_mime_type()
436
+ {
437
+ return 'application/json';
438
+ }
439
+ }
440
+
441
+
lib/antidot/AFS/SEARCH/afs_cluster_exception.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_exception.php';
3
+
4
+ /** @brief Base class for all exceptions related to cluster errors. */
5
+ abstract class AfsClusterException extends AfsBaseException
6
+ { }
7
+
8
+ /** @brief Requested cluster identifier is unknown. */
9
+ class AfsUninitializedClusterException extends AfsClusterException
10
+ {
11
+ /** @brief Constructs new exception instance. */
12
+ public function __construct()
13
+ {
14
+ parent::__construct('Trying to use or set cluster parameters whereas no cluster has been defined!');
15
+ }
16
+ }
17
+
lib/antidot/AFS/SEARCH/afs_cluster_helper.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_reply_helper.php';
3
+ require_once 'AFS/SEARCH/afs_reply_helper_factory.php';
4
+
5
+ /** @brief Helper for one cluster reply.
6
+ */
7
+ class AfsClusterHelper extends AfsHelperBase
8
+ {
9
+ private $id = null;
10
+ private $label = null;
11
+ private $total_replies = null;
12
+ private $replies = array();
13
+ private $query = null;
14
+ private $config = null;
15
+
16
+ /** @brief Constructs new cluster helper instance.
17
+ *
18
+ * @param $cluster [in] One cluster reply.
19
+ * @param $meta [in] Reply set meta data.
20
+ * @param $facet_helper [in] Facet helper (used to retrieve value label).
21
+ * @param $query [in] AfsQuery object previously initialized. It is used to
22
+ * generate new query to filter on this cluster reply.
23
+ * @param $config [in] Helper configuration.
24
+ */
25
+ public function __construct($cluster, AfsMetaHelper $meta, $facet_helper,
26
+ AfsQuery $query, AfsHelperConfiguration $config)
27
+ {
28
+ $formatter = AfsFacetHelperRetriever::get_formatter($meta->get_cluster_id(), $query);
29
+ $this->id = $formatter->format($cluster->id);
30
+ $this->initialize_label($facet_helper);
31
+ $this->total_replies = $cluster->totalItems;
32
+ $factory = new AfsReplyHelperFactory($config->get_reply_text_visitor());
33
+ $this->replies = $factory->create_replies($meta->get_feed(), $cluster);
34
+ $this->query = $query->auto_set_from()->unset_cluster()->add_filter($meta->get_cluster_id(), $this->id);
35
+ $this->config = $config;
36
+ }
37
+
38
+ private function initialize_label($facet_helper)
39
+ {
40
+ if (! is_null($facet_helper)) {
41
+ foreach ($facet_helper->get_elements() as $element) {
42
+ if ($this->id == $element->key) {
43
+ $this->label = $element->label;
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ if (is_null($this->label))
49
+ $this->label = $this->id;
50
+ }
51
+
52
+ /** @brief Retrieves identifier of the facet value defined for current cluster.
53
+ * @return facet value identifier.
54
+ */
55
+ public function get_id()
56
+ {
57
+ return $this->id;
58
+ }
59
+
60
+ /** @brief Retrieves label of the facet value defined for current cluster.
61
+ * @return facet value label.
62
+ */
63
+ public function get_label()
64
+ {
65
+ return $this->label;
66
+ }
67
+
68
+ /** @brief Retrieves number of replies in the cluster.
69
+ * @return number of replies.
70
+ */
71
+ public function get_total_replies()
72
+ {
73
+ return $this->total_replies;
74
+ }
75
+
76
+ /** @brief Checks whether current cluster has replies.
77
+ * @return @c True when at least one reply is present, @c false otherwise.
78
+ */
79
+ public function has_reply()
80
+ {
81
+ return !empty($this->replies);
82
+ }
83
+
84
+ /** @brief Retrieves number of replies in current cluster.
85
+ * @return replies in current cluster.
86
+ */
87
+ public function get_nb_replies()
88
+ {
89
+ return count($this->replies);
90
+ }
91
+
92
+ /** @brief Retrieves all clustered replies.
93
+ * @return clustered replies.
94
+ */
95
+ public function get_replies()
96
+ {
97
+ return $this->replies;
98
+ }
99
+
100
+ /** @brief Retrieves query to filter on current cluster.
101
+ *
102
+ * Using this query allows to
103
+ * @return
104
+ */
105
+ public function get_query()
106
+ {
107
+ return $this->query;
108
+ }
109
+
110
+ /** @brief Retrieves cluster as array.
111
+ *
112
+ * All data are store in <tt>key => value</tt> format:
113
+ * @li @c id: cluster id (ie identifier of facet value used to make current cluster),
114
+ * @li @c label: cluster label (ie label of the facet value, fallback to id when no label is available),
115
+ * @li @c total_replies: number of replies in current cluster,
116
+ * @li @c replies: list of replies for current cluster (see AfsReplyHelper::format),
117
+ * @li @c link: link which can be used to filter on this specific cluster.
118
+ * You MUST have provided query coder to helper configuration otherwise
119
+ * link is set to empty string.
120
+ *
121
+ * @return array filled with key and values.
122
+ */
123
+ public function format()
124
+ {
125
+ $formatted_replies = array();
126
+ foreach ($this->replies as $reply)
127
+ $formatted_replies[] = $reply->format();
128
+ if ($this->config->has_query_coder())
129
+ $link = $this->config->get_query_coder()->generate_link($this->query);
130
+ else
131
+ $link = '';
132
+
133
+ return array('id' => $this->id,
134
+ 'label' => $this->label,
135
+ 'total_replies' => $this->total_replies,
136
+ 'replies' => $formatted_replies,
137
+ 'link' => $link);
138
+ }
139
+ }
lib/antidot/AFS/SEARCH/afs_coder_base.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @brief Base class for coders.
4
+ *
5
+ * Implements usefull methods to simplify encode/decode methods.
6
+ */
7
+ abstract class AfsCoderBase
8
+ {
9
+ protected $escape;
10
+ protected $regex_delim;
11
+
12
+ /** @brief Construct instance with appropriate escape and regex delimiter
13
+ * characters.
14
+ *
15
+ * @param $escape_character [in] character used when values contain
16
+ * characters with specific meaning (eg: separator)
17
+ * @param $regex_delimiter [in] character used as delimiters in regular
18
+ * expression.
19
+ */
20
+ public function __construct($escape_character, $regex_delimiter='~')
21
+ {
22
+ $this->escape = $escape_character;
23
+ $this->regex_delim = $regex_delimiter;
24
+ }
25
+
26
+ /**
27
+ * @brief Check unicity of provided @a params.
28
+ * @param $params [in] list of parameters to be checked.
29
+ * @exception InvalidArgumentException when @a params are not unique.
30
+ */
31
+ protected function check_unicity(array $params)
32
+ {
33
+ if (count($params) != count(array_unique($params))) {
34
+ throw new InvalidArgumentException('All parameter values must be '
35
+ . 'differents!');
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @brief Split provided @a value.
41
+ *
42
+ * Input @a value is splitted on each character separator @a sep except when
43
+ * it is preceded by escape character.
44
+ * @remark escape characters which have been escaped should be unescaped by
45
+ * appropriate function call.
46
+ *
47
+ * @param $sep [in] separator used to split input @a value.
48
+ * @param $value [in] value to be splitted.
49
+ *
50
+ * @return splitted value.
51
+ */
52
+ protected function explode($sep, $value)
53
+ {
54
+ $result = array();
55
+ $escapes = preg_split($this->regex_delim . '[' . $this->escape . ']{2}'
56
+ . $this->regex_delim, $value);
57
+ foreach ($escapes as $escape) {
58
+ $split_on_sep = preg_split($this->regex_delim . '(?<!['
59
+ . $this->escape . "])$sep" . $this->regex_delim,
60
+ $escape);
61
+ $last = array_pop($result);
62
+ if ($last != null) {
63
+ $result[] = $last . $this->escape . $this->escape
64
+ . array_shift($split_on_sep);
65
+ }
66
+ $result = array_merge($result, $split_on_sep);
67
+ }
68
+ return $result;
69
+ }
70
+
71
+ /**
72
+ * @brief Simple wrapper which add surrounding regex delimiters.
73
+ * @param $pattern [in] regex pattern to use (already quoted).
74
+ * @param $replacement [in] replacement.
75
+ * @param $value [in] value to modify.
76
+ * @return updated @a value.
77
+ */
78
+ protected function replace($pattern, $replacement, $value)
79
+ {
80
+ return preg_replace($this->regex_delim
81
+ . str_replace($this->regex_delim, '\\'. $this->regex_delim,
82
+ $pattern)
83
+ . $this->regex_delim, $replacement, $value);
84
+ }
85
+ }
86
+
87
+
lib/antidot/AFS/SEARCH/afs_coder_interface.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @brief Interface for encoding/decoding parameters.
4
+ *
5
+ * Encoding parameters results in a string which should be decoded in order to
6
+ * retrieve original data.
7
+ *
8
+ * Example for filters:
9
+ * @code
10
+ * $filters = $query->get_parameters()['filter'];
11
+ * $coder = new FilterCoder(); // implements AfsCoderInterface
12
+ * $coded_filters = $coder->encode($filters);
13
+ * $decoded_filters = $coder->decode($filters);
14
+ * assert($filters == $decoded_filters);
15
+ * @endcode
16
+ */
17
+ interface AfsCoderInterface
18
+ {
19
+ /** @brief Encode parameters.
20
+ * @param $parameters [in] array of parameters.
21
+ * @return encoded string.
22
+ */
23
+ public function encode(array $parameters);
24
+ /** @brief Decode previously encoded parameters.
25
+ * @param $parameters [in] encoded string representing parameters.
26
+ * @return decoded parameters.
27
+ */
28
+ public function decode($parameters);
29
+ }
30
+
31
+
lib/antidot/AFS/SEARCH/afs_concept_helper.php ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "COMMON/afs_helper_base.php";
3
+ require_once "AFS/SEARCH/afs_producer.php";
4
+
5
+ /** @defgroup default_concept Concept names.
6
+ *
7
+ * Defines names of the concept.
8
+ * @{ */
9
+ /** @brief Default concept name when concept is not bind to specific feed. */
10
+ define('AFS_DEFAULT_CONCEPT', 'concept');
11
+ /** @} */
12
+
13
+
14
+ /** @brief Manager for concept helpers.
15
+ *
16
+ * Each concept result is retrieved as a concept concept helper (see
17
+ * AfsConceptItem).
18
+ */
19
+ class AfsConceptManager extends AfsHelperBase
20
+ {
21
+ private $concepts = array();
22
+
23
+
24
+ /** @brief Adds new concept reply to concept manager.
25
+ * @param $replyset [in] JSON reply corresponding to a concept reply.
26
+ * @exception Exception when invalid replyset has been provided.
27
+ */
28
+ public function add_concept($replyset)
29
+ {
30
+ if (AfsProducer::CONCEPT != $replyset->meta->producer) {
31
+ throw new Exception('Invalid replyset provided for concept initialization');
32
+ }
33
+ $feed = $replyset->meta->uri;
34
+ foreach ($replyset->content->reply as $reply) {
35
+ if (property_exists($reply->concept->concepts,'concept')) {
36
+ $this->add_one_concept($reply, $feed);
37
+ }
38
+ }
39
+ }
40
+
41
+ /** @brief Checks whether at least one concept is available.
42
+ * @return @c True when one or more concepts is available, @c false
43
+ * otherwise.
44
+ */
45
+ public function has_concept()
46
+ {
47
+ return count($this->concepts) > 0;
48
+ }
49
+
50
+ /** @brief Retrieves all concept helpers.
51
+ * @return concept replies.
52
+ */
53
+ public function get_concepts()
54
+ {
55
+ return $this->concepts;
56
+ }
57
+
58
+ /** @brief Retrieves default, available or specified concept.
59
+ *
60
+ * @param $feed [in] Feed for which concept should be retrieved. Default value
61
+ * is @c null; two cases can occur:
62
+ * - there is only one concept reply, this reply is returned,
63
+ * - there is multiple concept replies and one corresponds to default
64
+ * concept reply (AFS_DEFAULT_CONCEPT), this one is returned.
65
+ *
66
+ * @return appropriate concept helper (see @a AfsConceptHelper).
67
+ * @exception OutOfBoundsException when required feed has not produced any
68
+ * concept reply.
69
+ */
70
+ public function get_concept($feed=null)
71
+ {
72
+ if (is_null($feed)) {
73
+ if (1 == count($this->concepts)) {
74
+ return reset($this->concepts);
75
+ } else {
76
+ $feed = AFS_DEFAULT_CONCEPT;
77
+ }
78
+ }
79
+ if (! array_key_exists($feed, $this->concepts)) {
80
+ throw new OutOfBoundsException('No spellcheck available for feed: ' . $feed);
81
+ }
82
+ return $this->concepts[$feed];
83
+ }
84
+
85
+ private function add_one_concept($reply, $feed)
86
+ {
87
+ $buffer = array();
88
+ if (! array_key_exists($feed, $this->concepts)) {
89
+ $this->concepts[$feed] = new AfsConceptHelper($feed);
90
+ }
91
+
92
+ foreach ($reply->concept->concepts->concept as $concept) {
93
+ $buffer[$concept->uri] = $concept->contents;
94
+ }
95
+ foreach ($reply->concept->query->items as $item) {
96
+ if (property_exists($item, 'afs:t')) {
97
+ $this->concepts[$feed]->add_item($item, $buffer);
98
+ } else {
99
+ throw new Exception('No type specified for concept value!');
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+
106
+ /** @brief Simple access to one concepts of one agent.
107
+ */
108
+ class AfsConceptHelper extends AfsHelperBase
109
+ {
110
+ private $feed = null;
111
+ private $items = array();
112
+
113
+ /** @brief Constructs new concept instance.
114
+ * @param $feed [in] name of the feed this concept is binded to.
115
+ */
116
+ public function __construct($feed)
117
+ {
118
+ $this->feed = $feed;
119
+ }
120
+
121
+ /** @brief Adds new concept item to current helper.
122
+ *
123
+ * @param $item [in] values to initialize new concept item with.
124
+ * @param $buffer [in] buffer of concept data.
125
+ */
126
+ public function add_item($item, $buffer)
127
+ {
128
+ $this->items[] = new AfsConceptItem($item, $buffer);
129
+ }
130
+
131
+ /** @brief Retrieves all concept items of current concept.
132
+ * @return concept items (see @a AfsConceptItem).
133
+ */
134
+ public function get_items()
135
+ {
136
+ return $this->items;
137
+ }
138
+
139
+ /** @brief Retrieves feed name which has produced this concept.
140
+ * @return feed name.
141
+ */
142
+ public function get_feed()
143
+ {
144
+ return $this->feed;
145
+ }
146
+ }
147
+
148
+
149
+ /** @brief Concept item.
150
+ *
151
+ * Concept results are made of one or more concept items. Concept items can have
152
+ * no data, one data or even multiple data attached to them.
153
+ */
154
+ class AfsConceptItem
155
+ {
156
+ private $text = null;
157
+ private $data = array();
158
+
159
+ /** @brief Constructs new concept item.
160
+ *
161
+ * @param $item [in] new values to initialize the instance with.
162
+ * @param $concepts [in] buffer of concept data (read only).
163
+ *
164
+ * @exception Exception when invalid/unrecognized data has been provided.
165
+ */
166
+ public function __construct($item, $concepts)
167
+ {
168
+ $this->text = $item->text;
169
+ if ('QueryMatch' == $item->{'afs:t'}) {
170
+ foreach ($item->uri as $uri) {
171
+ $this->data[$uri] = $concepts[$uri];
172
+ }
173
+ } elseif ('QueryText' != $item->{'afs:t'}) {
174
+ throw new Exception('Unmanaged concept type: ' . $item->{'afs:t'});
175
+ }
176
+ }
177
+
178
+ /** @brief Retrieves text of the item.
179
+ * @return text of the item.
180
+ */
181
+ public function get_text()
182
+ {
183
+ return $this->text;
184
+ }
185
+
186
+ /** @brief Check whether current item has a concept.
187
+ *
188
+ * For multi-words queries, no, one or more words can match one or more
189
+ * concepts. So, this method allow to check whether current item has binded
190
+ * concept(s) or not.
191
+ *
192
+ * @return @c True when current item has at least one binded concept,
193
+ * @c false otherwise.
194
+ */
195
+ public function has_concept()
196
+ {
197
+ return ! empty($this->data);
198
+ }
199
+
200
+ /** @brief Retrieves concept data associated to current item.
201
+ *
202
+ * Data are store as key/value pairs. Keys correspond to the URI of the
203
+ * concept. Values correspond to XML data of the concept.
204
+ *
205
+ * @remark to avoid getting empty data, check whether concept is available
206
+ * for current item by calling @a has_concept.
207
+ *
208
+ * @return key/value map of URI/concept or @c null when no concept matches
209
+ * the item text.
210
+ */
211
+ public function get_data()
212
+ {
213
+ return $this->data;
214
+ }
215
+ }
216
+
217
+
lib/antidot/AFS/SEARCH/afs_count.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Count mode when cluster mode is active.
5
+ *
6
+ * Specify whether reply count should consider documents or clusters.
7
+ */
8
+ class AfsCount extends BasicEnum
9
+ {
10
+ private static $instance = null;
11
+
12
+ static public function check_value($value, $msg=null)
13
+ {
14
+ if (is_null(self::$instance))
15
+ self::$instance = new self();
16
+ BasicEnum::check_val(self::$instance, $value, $msg);
17
+ }
18
+
19
+ /** @brief Count number of documents. */
20
+ const DOCUMENTS = 'documents';
21
+ /** @brief Count number of replies. */
22
+ const CLUSTERS = 'clusters';
23
+ }
24
+
lib/antidot/AFS/SEARCH/afs_facet.php ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_facet_type.php';
3
+ require_once 'AFS/SEARCH/afs_facet_layout.php';
4
+ require_once 'AFS/SEARCH/afs_facet_mode.php';
5
+ require_once 'AFS/SEARCH/afs_facet_combination.php';
6
+
7
+
8
+ /** @brief Configuration class for AFS facets.
9
+ */
10
+ class AfsFacet
11
+ {
12
+ private $id = null;
13
+ private $type = null;
14
+ private $layout = null;
15
+ private $mode = null;
16
+ private $combination = null;
17
+
18
+ /** @brief Construct new facet with specified parameters.
19
+ *
20
+ * @param $id [in] facet id defined in feed.xml on indexation side.
21
+ * @param $type [in] facet type defined in feed.xml on indexation side.
22
+ * Default value is AfsFacetType::UNKNOWN_TYPE.
23
+ * (see @ref AfsFacetType for available types).
24
+ * @param $layout [in] facet layout defined in feed.xml on indexation side.
25
+ * Default value is AfsFacetLayout::TREE.
26
+ * (see @ref AfsFacetLayout for availanle layouts).
27
+ * @param $mode [in] facet mode, see @ref AfsFacetMode for more details.
28
+ * (default: UNSPECIFIED_MODE).
29
+ *
30
+ * @exception InvalidArgumentException invalid parameter value provided for
31
+ * @a type, @a layout or @a mode parameter.
32
+ */
33
+ public function __construct($id, $type=AfsFacetType::UNKNOWN_TYPE,
34
+ $layout=AfsFacetLayout::TREE, $mode=AfsFacetMode::UNSPECIFIED_MODE)
35
+ {
36
+ $this->set_type($type);
37
+ $this->id = $id;
38
+ $this->layout = $layout;
39
+ $this->set_mode($mode);
40
+ }
41
+
42
+ /** @brief Retrieves facet id.
43
+ * @return facet id.
44
+ */
45
+ public function get_id()
46
+ {
47
+ return $this->id;
48
+ }
49
+ /** @brief Redefines facet type.
50
+ * @param $type [in] new type of the facet to set.
51
+ * @exception InvalidArgumentException invalid type provided.
52
+ */
53
+ public function set_type($type)
54
+ {
55
+ AfsFacetType::check_value($type, 'Invalid facet type parameter: ');
56
+ $this->type = $type;
57
+ }
58
+ /** @brief Retrieves facet type.
59
+ * @return type of the facet.
60
+ */
61
+ public function get_type()
62
+ {
63
+ return $this->type;
64
+ }
65
+ /** @brief Retrieves facet layout.
66
+ * @return layout of the facet.
67
+ */
68
+ public function get_layout()
69
+ {
70
+ return $this->layout;
71
+ }
72
+
73
+ /** @brief Defines new facet mode.
74
+ * @param $mode [in] New mode to set.
75
+ * @exception InvalidArgumentException invalid mode provided.
76
+ */
77
+ public function set_mode($mode)
78
+ {
79
+ AfsFacetMode::check_value($mode, 'Invalid facet mode: ');
80
+ $this->mode = $mode;
81
+ if (AfsFacetMode::SINGLE_MODE == $mode
82
+ || AfsFacetMode::OR_MODE == $mode) {
83
+ $this->combination = 'or';
84
+ } elseif (AfsFacetMode::AND_MODE == $mode) {
85
+ $this->combination = 'and';
86
+ }
87
+ }
88
+ /** @brief Retrieve facet mode.
89
+ * @return facet mode (@c replace, @c or, @c add or @c unspecified).
90
+ */
91
+ public function get_mode()
92
+ {
93
+ return $this->mode;
94
+ }
95
+ /** @brief Check whether mode is set to <tt>single</tt>.
96
+ * @return true when mode is <tt>single</tt>, false otherwise.
97
+ */
98
+ public function has_single_mode()
99
+ {
100
+ return $this->get_mode() == AfsFacetMode::SINGLE_MODE;
101
+ }
102
+ /** @brief Check whether mode is set to <tt>or</tt>.
103
+ * @return true when mode is <tt>or</tt>, false otherwise.
104
+ */
105
+ public function has_or_mode()
106
+ {
107
+ return $this->get_mode() == AfsFacetMode::OR_MODE;
108
+ }
109
+ /** @brief Check whether mode is set to <tt>or</tt>.
110
+ * @return true when mode is <tt>or</tt>, false otherwise.
111
+ */
112
+ public function has_and_mode()
113
+ {
114
+ return $this->get_mode() == AfsFacetMode::AND_MODE;
115
+ }
116
+
117
+ /** @brief Checks whether provided facet is similar to current instance.
118
+ *
119
+ * Two instances are considered similar when following values are equals:
120
+ * - facet identifier,
121
+ * - facet type (or one is of unknown type),
122
+ * - facet layout (or one is of unknown layout).
123
+ * Other facet parameters are not taken into account.
124
+ *
125
+ * @param $other [in] instance to compare with.
126
+ * @return @c True when both instances are similar, @c false otherwise.
127
+ */
128
+ public function is_similar_to(AfsFacet $other)
129
+ {
130
+ if ($this->id == $other->get_id()
131
+ && ($this->type == $other->get_type()
132
+ || $this->type == AfsFacetType::UNKNOWN_TYPE
133
+ || $other->get_type() == AfsFacetType::UNKNOWN_TYPE)
134
+ && ($this->layout == $other->get_layout()
135
+ || $this->type == AfsFacetLayout::UNKNOWN
136
+ || $other->get_layout() == AfsFacetLayout::UNKNOWN)) {
137
+ return true;
138
+ } else {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /** @brief Updates current instance with parameters from other instance.
144
+ *
145
+ * First, both instance are compared then when they are enough similar
146
+ * current instance is updated.<br/>
147
+ * This can be usefull internally when facets are partially declared.
148
+ *
149
+ * @param $other [in] other instance to compare with and use as source to
150
+ * update.
151
+ *
152
+ * @return @c true when everything goes right, @c false otherwise (instances
153
+ * are not similar).
154
+ */
155
+ public function update(AfsFacet $other)
156
+ {
157
+ if (! $this->is_similar_to($other))
158
+ return false;
159
+
160
+ if ($this->type == AfsFacetType::UNKNOWN_TYPE)
161
+ $this->type = $other->get_type();
162
+ if ($this->layout == AfsFacetLayout::UNKNOWN)
163
+ $this->layout = $other->get_layout();
164
+ return true;
165
+ }
166
+
167
+ /** @internal
168
+ * @brief Join all provided @a values and format them appropriately.
169
+ * @param $values [in] array of values to be joined.
170
+ * @return string of joined values.
171
+ */
172
+ public function join_values($values)
173
+ {
174
+ // In single mode we should have only one value!
175
+ if ($this->has_single_mode())
176
+ $values = array(array_pop($values));
177
+
178
+ $formatted = array();
179
+ foreach ($values as $value) {
180
+ $formatted[] = $this->id . '=' . $value;
181
+ }
182
+ return implode(' ' . $this->combination . ' ', $formatted);
183
+ }
184
+
185
+ /** @brief Printable facet.
186
+ *
187
+ * This method should be used for debug purpose only.
188
+ * @return string representation of the facet.
189
+ */
190
+ public function __toString()
191
+ {
192
+ return '<' . $this->id . ': ' . $this->type . ' - ' . $this->layout
193
+ . ', ' . $this->mode . ', ' . $this->combination . '>';
194
+ }
195
+ }
196
+
197
+
lib/antidot/AFS/SEARCH/afs_facet_combination.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Combination of the facets' values.
5
+ *
6
+ * Specify whether results of each filter should be summed up or instersected to
7
+ * build final result.
8
+ */
9
+ class AfsFacetCombination extends BasicEnum
10
+ {
11
+ private static $instance = null;
12
+
13
+ static public function check_value($value, $msg=null)
14
+ {
15
+ if (is_null(self::$instance))
16
+ self::$instance = new self();
17
+ BasicEnum::check_val(self::$instance, $value, $msg);
18
+ }
19
+
20
+ /** @brief Values of the facets are OR-combined. */
21
+ const OR_MODE = 'or';
22
+ /** @brief Values of the facets are AND-combined. */
23
+ const AND_MODE = 'and';
24
+ }
25
+
26
+
lib/antidot/AFS/SEARCH/afs_facet_default.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ require_once 'AFS/SEARCH/afs_query_object_interface.php';
4
+ require_once 'AFS/SEARCH/afs_facet_values_sort_order.php';
5
+
6
+ /** @brief Manage parameter for all facets.
7
+ */
8
+ class AfsFacetDefault implements AfsQueryObjectInterface
9
+ {
10
+ private $replies = 1000;
11
+ private $values_sort_order = null;
12
+
13
+
14
+ /** @brief Constructs new AfsFacetDefault instance.
15
+ *
16
+ * @param $other [in] Other instance used to initialize newly created one
17
+ * (default: @c null, creates new instance from scratch).
18
+ */
19
+ public function __construct(AfsFacetDefault $other=null)
20
+ {
21
+ if (! is_null($other)) {
22
+ $this->replies = $other->replies;
23
+ if (! is_null($other->values_sort_order))
24
+ $this->values_sort_order = $other->values_sort_order->copy();
25
+ }
26
+ }
27
+
28
+ /** @name Facet values replies
29
+ * @{ */
30
+
31
+ /** @brief Defines number of facet values in facet replies.
32
+ *
33
+ * Default number of facet values per facet is 1000. This value overrides
34
+ * default AFS search engine value which is 10.
35
+ *
36
+ * @param $nb_replies [in] Maximum number of facet values.
37
+ */
38
+ public function set_nb_replies($nb_replies)
39
+ {
40
+ $this->replies = $nb_replies;
41
+ }
42
+ /** @brief Retrieves maximum number of facet values in facet replies.
43
+ * @return maximum number of facet values.
44
+ */
45
+ public function get_nb_replies()
46
+ {
47
+ return $this->replies;
48
+ }
49
+ /** @} */
50
+
51
+ /** @name Facet values sort order
52
+ * @{ */
53
+
54
+ /** @brief Defines sort order for all facet values.
55
+ *
56
+ * AFS search default sort for facet values is alphanumeric. This method
57
+ * allows to change this behaviour.
58
+ *
59
+ * @param $mode [in] Sort mode (see AfsFacetValuesSortMode).
60
+ * @param $order [in] Sort order (see AfsSortOrder).
61
+ *
62
+ * @exception InvalidArgumentException when $mode or $order is invalid.
63
+ */
64
+ public function set_sort_order($mode, $order)
65
+ {
66
+ $this->values_sort_order = new AfsFacetValuesSortOrder($mode, $order);
67
+ }
68
+ /** @brief Retrieves sort order defined on facet values.
69
+ * @return sort order or @c null when no sort sorder has been set.
70
+ */
71
+ public function get_sort_order()
72
+ {
73
+ return $this->values_sort_order;
74
+ }
75
+ /** @} */
76
+
77
+ /** @name Interface implementation
78
+ * @{ */
79
+
80
+ /** @brief Produces new instance copied from current one.
81
+ * @return copy of the current instance.
82
+ */
83
+ public function copy()
84
+ {
85
+ return new AfsFacetDefault($this);
86
+ }
87
+ /** @brief Format object to appropriate string form.
88
+ * @return array of strings.
89
+ */
90
+ public function format()
91
+ {
92
+ $result = array('replies=' . $this->replies);
93
+ if (! is_null($this->values_sort_order)) {
94
+ $result[] = 'sort=' . $this->values_sort_order->mode;
95
+ $result[] = 'order=' . $this->values_sort_order->order;
96
+ }
97
+ return $result;
98
+ }
99
+ /** @} */
100
+ }
lib/antidot/AFS/SEARCH/afs_facet_exception.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_exception.php';
3
+
4
+ /** @brief Base class for all exceptions related to facet errors. */
5
+ abstract class AfsFacetException extends AfsBaseException
6
+ { }
7
+
8
+ /** @brief Requested facet identifier is unknown. */
9
+ class AfsUndefinedFacetException extends AfsFacetException
10
+ { }
11
+
12
+ /** @brief Configured facet parameter and detected facet parameter are not coherent. */
13
+ class AfsInvalidFacetParameterException extends AfsFacetException
14
+ { }
15
+
16
+
lib/antidot/AFS/SEARCH/afs_facet_helper.php ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_facet_manager.php';
3
+ require_once 'COMMON/afs_helper_base.php';
4
+ require_once 'AFS/SEARCH/afs_facet_helper_retriever.php';
5
+
6
+ function is_not_null($value) {
7
+ return ! is_null($value);
8
+ }
9
+
10
+ /** @brief Helper to manage facets. */
11
+ class AfsFacetHelper extends AfsHelperBase
12
+ {
13
+ private $id = null;
14
+ private $label = null;
15
+ private $layout = null;
16
+ private $type = null;
17
+ private $sticky = null;
18
+ private $elements = null;
19
+
20
+ /** @brief Constructs new instance of facet helper.
21
+ *
22
+ * @param $facet [in] root facet element.
23
+ * @param $query [in] @a AfsQuery which has produced current reply.
24
+ * @param $config [in] helper configuration object.
25
+ */
26
+ public function __construct($facet, AfsQuery $query, AfsHelperConfiguration $config)
27
+ {
28
+ $this->id = $facet->id;
29
+ if (property_exists($facet, 'labels') && ! empty($facet->labels)
30
+ && property_exists($facet->labels[0], 'label')) {
31
+ $this->label = $facet->labels[0]->label;
32
+ } else {
33
+ $this->label = $this->id;
34
+ }
35
+ $this->layout = $facet->layout;
36
+ $this->type = $facet->type;
37
+ if (property_exists($facet, 'sticky')
38
+ && 0 == strcmp('true', $facet->sticky)) {
39
+ $this->sticky = true;
40
+ } else {
41
+ $this->sticky = false;
42
+ }
43
+ $facet_manager = $query->get_facet_manager();
44
+ $facet_manager->check_or_add_facet(new AfsFacet($this->id, $this->type, $this->layout));
45
+ $builder = new AfsFacetElementBuilder($facet_manager, $query);
46
+ $this->elements = $builder->create_elements($this->id, $facet, $config);
47
+ }
48
+
49
+ /** @brief Retrieve facet label.
50
+ *
51
+ * First label found is retrieved. Label in right language is retrieved as
52
+ * soon as filter on required language has been set in @a AfsQuery.
53
+ *
54
+ * @return facet label.
55
+ */
56
+ public function get_label()
57
+ {
58
+ return $this->label;
59
+ }
60
+
61
+ /** @brief Retrieves facet id.
62
+ *
63
+ * This value is not necessary unless specific query should be created
64
+ * instead of the ones provided by @a AfsFacetValueHelper.
65
+ *
66
+ * @return the id of the facet.
67
+ */
68
+ public function get_id()
69
+ {
70
+ return $this->id;
71
+ }
72
+
73
+ /** @brief Retrieves facet layout.
74
+ * @return layout of the facet (should be AFS_FACET_INTERVAL or
75
+ * AFS_FACET_TREE).
76
+ */
77
+ public function get_layout()
78
+ {
79
+ return $this->layout;
80
+ }
81
+
82
+ /** @brief Retrieves facet type.
83
+ * @return type of the facet (should be one of AFS_FACET_INTEGER,
84
+ * AFS_FACET_REAL...)
85
+ */
86
+ public function get_type()
87
+ {
88
+ return $this->type;
89
+ }
90
+
91
+ /** @brief Retrieves stickyness of the facet.
92
+ * @return return true when the facet is sticky, false otherwise.
93
+ * @return return true when the facet is sticky, false otherwise.
94
+ */
95
+ public function is_sticky()
96
+ {
97
+ return $this->sticky;
98
+ }
99
+
100
+ /** @brief Retrieve all facet elements of this facet.
101
+ * @return facet elements.
102
+ */
103
+ public function get_elements()
104
+ {
105
+ return $this->elements;
106
+ }
107
+
108
+ /** @brief Retrieve facet as array.
109
+ *
110
+ * All data are store in <tt>key => value</tt> format:
111
+ * @li @c label: facet label,
112
+ * @li @c values: array of facet elements (see @a AfsFacetValueHelper).
113
+ *
114
+ * @return array filled with key and values.
115
+ */
116
+ public function format()
117
+ {
118
+ return array('label' => $this->get_label(),
119
+ 'values' => $this->get_elements());
120
+ }
121
+
122
+ }
123
+
124
+
125
+ /** @brief Simple AFS facet value representation. */
126
+ class AfsFacetValueHelper extends AfsHelperBase
127
+ {
128
+ /** @brief Label of facet value. */
129
+ public $label = null;
130
+ /** @brief Key of facet value. */
131
+ public $key = null;
132
+ /** @brief Number of elements of facet value. */
133
+ public $count = null;
134
+ /** @brief Boolean state of facet value.
135
+ *
136
+ * Set to true when current query filters on this facet value. */
137
+ public $active = null;
138
+ /** @brief Generated query associated to this facet value.
139
+ *
140
+ * When this facet value is active, filter on this facet value is remove
141
+ * from @a query. If this facet value is inactive, filter on this facet
142
+ * value is added to @a query.
143
+ *
144
+ * Added filter replace or complete existing filters depending on facet
145
+ * configuration. */
146
+ public $query = null;
147
+ /** @brief Generated link from above @a query. */
148
+ public $link = null;
149
+ /** @brief List of child facet values.
150
+ *
151
+ * This list is usually empty except for tree facets. */
152
+ public $values = null;
153
+
154
+ /** @brief Meta data associated to facet value. */
155
+ private $meta = null;
156
+
157
+ /** @brief Construct new instance. (see class attributes for details) */
158
+ public function __construct($label, $key, $count, $meta, $active, $query,
159
+ $link, $children)
160
+ {
161
+ $this->label = $label;
162
+ $this->key = $key;
163
+ $this->count = $count;
164
+ $this->active = $active;
165
+ $this->query = $query;
166
+ $this->link = $link;
167
+ $this->values = $children;
168
+ $this->meta = $meta;
169
+ }
170
+
171
+ /** @brief Retrieves meta data associated to the facet value.
172
+ *
173
+ * @param $name [in] Name of the metadata to retrieve. Default is null which
174
+ * means retrieves all meta data as array of values (keys correspond
175
+ * to meta data name, values correspond to meta data value associated
176
+ * to current facet value).
177
+ *
178
+ * @return required meta data or all meta data (see @a name).
179
+ *
180
+ * @exception OutOfBoundsException when required meta data name does not
181
+ * exist.
182
+ */
183
+ public function get_meta($name=null)
184
+ {
185
+ if (is_null($name)) {
186
+ return $this->meta;
187
+ } elseif (array_key_exists($name, $this->meta)) {
188
+ return $this->meta[$name];
189
+ } else {
190
+ throw new OutOfBoundsException('No meta data available with name: ' . $name);
191
+ }
192
+ }
193
+
194
+ /** @brief Retrieve facet element as array.
195
+ *
196
+ * All data are store in <tt>key => value</tt> format:
197
+ * @li @c label: label of the facet value,
198
+ * @li @c key: key of the facet value,
199
+ * @li @c count: number of element of the facet value,
200
+ * @li @c active: state of the facet value: true when this facet value is
201
+ * used in current query, false otherwise,
202
+ * @li @c query: query associated to the facet value (see @a query property
203
+ * for more details),
204
+ * @li @c link: link generated from the @a query,
205
+ * @li @c values: list of children facet values. This list is not empty for
206
+ * tree facets only.
207
+ * @li @c meta: key-value pairs of meta data identifiers and values.
208
+ *
209
+ * @remark: When helpers are used to create such facet value, if @a link is
210
+ * generated from @a query, then the query is no more necessary and not
211
+ * provided. So one of @c query and @c link is null.
212
+ *
213
+ * @return array filled with key and values.
214
+ */
215
+ public function format()
216
+ {
217
+ if (! empty($this->values)) {
218
+ $formatted_values = array();
219
+ foreach ($this->values as $value)
220
+ $formatted_values[] = $value->format();
221
+ $this->values = $formatted_values;
222
+ }
223
+
224
+ return array_filter(get_object_vars($this), is_not_null);
225
+ }
226
+ }
227
+
228
+
229
+ /** @brief Helper to build facet elements.
230
+ *
231
+ * Facet elements are built recursively when necessary. */
232
+ class AfsFacetElementBuilder
233
+ {
234
+ private $facet_mgr = null;
235
+ private $query = null;
236
+
237
+ /** @brief Constructs new instance of facet element builder.
238
+ *
239
+ * @param $facet_mgr [in] in conjunction with @a query, it is used to produce
240
+ * adequate query for each facet element.
241
+ * @param $query [in] query which has led to current reply state.
242
+ */
243
+ public function __construct(AfsFacetManager $facet_mgr, AfsQuery $query)
244
+ {
245
+ $this->facet_mgr = $facet_mgr;
246
+ $this->query = $query;
247
+ }
248
+
249
+ /** @brief Creates recursively facet elements.
250
+ *
251
+ * @param $facet_id [in] current facet id. This value is used to update
252
+ * current query for each facet element.
253
+ * @param $facet_element [in] starting point used to create facet elements.
254
+ * @param $config [in] helper configuration object.
255
+ *
256
+ * @return list of facet elements (see @ AfsFacetValueHelper).
257
+ */
258
+ public function create_elements($facet_id, $facet_element,
259
+ AfsHelperConfiguration $config)
260
+ {
261
+ $formatter = AfsFacetHelperRetriever::get_formatter($facet_id, $this->query);
262
+ return $this->create_elements_recursively($facet_id, $facet_element,
263
+ $formatter, $config);
264
+ }
265
+
266
+ /** @internal
267
+ * @brief Creates recursively facet elements.
268
+ *
269
+ * @param $facet_id [in] current facet id. This value is used to update
270
+ * current query for each facet element.
271
+ * @param $facet_element [in] starting point used to create facet elements.
272
+ * @param $formatter [in] formatter used for facet value identifiers.
273
+ * @param $config [in] helper configuration object.
274
+ *
275
+ * @return list of facet elements (see @ AfsFacetValueHelper).
276
+ */
277
+ private function create_elements_recursively($facet_id, $facet_element,
278
+ AfsFacetValueIdFormatter $formatter, AfsHelperConfiguration $config)
279
+ {
280
+ $elements = array();
281
+
282
+ if (property_exists($facet_element, 'node')) {
283
+ $elem_name = 'node';
284
+ } else {
285
+ $elem_name = 'interval';
286
+ }
287
+
288
+ foreach ($facet_element->$elem_name as $elem) {
289
+ // First create children
290
+ $children = array();
291
+ if (property_exists($elem, 'node')) {
292
+ $children = $this->create_elements_recursively($facet_id,
293
+ $elem, $formatter, $config);
294
+ }
295
+
296
+ $value_id = $formatter->format($elem->key);
297
+ $label = $this->extract_label($elem);
298
+ $meta = $this->extract_meta($elem);
299
+ $active = $this->query->has_filter($facet_id, $value_id);
300
+ $query = $this->generate_query($facet_id, $value_id, $active);
301
+ if ($config->has_query_coder()) {
302
+ $link = $config->get_query_coder()->generate_link($query);
303
+ $query = null; // we don't need it anymore
304
+ } else {
305
+ $link = null;
306
+ }
307
+ $elements[] = new AfsFacetValueHelper($label, $value_id, $elem->items,
308
+ $meta, $active, $query, $link, $children);
309
+
310
+ }
311
+ return $elements;
312
+ }
313
+
314
+ private function extract_label($element)
315
+ {
316
+ if (property_exists($element, 'labels')) {
317
+ return $element->labels[0]->label;
318
+ } else {
319
+ return $element->key;
320
+ }
321
+ }
322
+
323
+ private function extract_meta($element)
324
+ {
325
+ $result = array();
326
+ if (property_exists($element, 'meta')) {
327
+ foreach($element->meta as $meta) {
328
+ $result[$meta->key] = $meta->value;
329
+ }
330
+ }
331
+ return $result;
332
+ }
333
+
334
+ private function generate_query($facet_id, $value_id, $active)
335
+ {
336
+ $result = null;
337
+ $facet = $this->facet_mgr->get_facet($facet_id);
338
+ if ($active) {
339
+ $result = $this->query->remove_filter($facet_id, $value_id);
340
+ } else {
341
+ if ($facet->has_single_mode()) {
342
+ $result = $this->query->set_filter($facet_id, $value_id);
343
+ } else {
344
+ $result = $this->query->add_filter($facet_id, $value_id);
345
+ }
346
+ }
347
+ return $result;
348
+ }
349
+ }
350
+
351
+
lib/antidot/AFS/SEARCH/afs_facet_helper_retriever.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_facet_value_formatter.php';
3
+
4
+ /** @brief Retrieves facet helper from list of helpers. */
5
+ class AfsFacetHelperRetriever
6
+ {
7
+ /** @brief Retrieves helper with specified facet identifier.
8
+ *
9
+ * @param $id [in] Facet identifier to look for.
10
+ * @param $facet_helpers [in] List of helpers to consider.
11
+ *
12
+ * @return Requested helper or null when not found.
13
+ */
14
+ public static function get_helper($id, array& $facet_helpers)
15
+ {
16
+ $helper = null;
17
+ if (array_key_exists($id, $facet_helpers))
18
+ $helper = $facet_helpers[$id];
19
+ else {
20
+ foreach ($facet_helpers as $facet_helper) {
21
+ if ($id == $facet_helper->get_id()) {
22
+ $helper = $facet_helper;
23
+ break;
24
+ }
25
+ }
26
+ }
27
+ return $helper;
28
+ }
29
+
30
+ /** @brief Retrieves facet value formatter.
31
+ *
32
+ * Formatters are needed to surround facet value with double quotes for
33
+ * specific facet type and layout.
34
+ *
35
+ * @param $facet_id [in] Facet identifier.
36
+ * @param $query [in] AfsQuery which should contain facet information.
37
+ *
38
+ * @return appropriate formatter.
39
+ */
40
+ public static function get_formatter($facet_id, $query)
41
+ {
42
+ try {
43
+ $facet = $query->get_facet_manager()->get_facet($facet_id);
44
+ if ((AfsFacetType::STRING_TYPE == $facet->get_type()
45
+ || AfsFacetType::DATE_TYPE == $facet->get_type())
46
+ && AfsFacetLayout::TREE == $facet->get_layout()) {
47
+ return new AfsQuoteFacetValueIdFormatter();
48
+ }
49
+ } catch (Exception $e) { }
50
+
51
+ return new AfsNoFacetValueIdFormatter();
52
+ }
53
+ }
lib/antidot/AFS/SEARCH/afs_facet_layout.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Layout of the facets
5
+ *
6
+ * Specify the layout of the facets.
7
+ */
8
+ class AfsFacetLayout extends BasicEnum
9
+ {
10
+ private static $instance = null;
11
+
12
+ static public function check_value($value, $msg=null)
13
+ {
14
+ if (is_null(self::$instance))
15
+ self::$instance = new self();
16
+ BasicEnum::check_val(self::$instance, $value, $msg);
17
+ }
18
+
19
+ /** @brief Tree layout.
20
+ *
21
+ * This layout is used for flat and hierarchical facet values. */
22
+ const TREE = 'TREE';
23
+ /** @brief Interval layout.
24
+ *
25
+ * This layout is used for interval of values such as prices. */
26
+ const INTERVAL = 'INTERVAL';
27
+ /** @brief Unknown layout used for not fully declared facets.
28
+ *
29
+ * This is intended for internal use only. */
30
+ const UNKNOWN = 'UNKNOWN';
31
+ }
32
+
33
+
lib/antidot/AFS/SEARCH/afs_facet_manager.php ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_facet.php';
3
+ require_once 'AFS/SEARCH/afs_facet_exception.php';
4
+ require_once 'AFS/SEARCH/afs_facet_sort.php';
5
+ require_once 'AFS/SEARCH/afs_sort_order.php';
6
+ require_once 'AFS/SEARCH/afs_facet_values_sort_order.php';
7
+ require_once 'COMMON/afs_tools.php';
8
+
9
+ /** @brief AFS facet manager.
10
+ *
11
+ * Add some control over configured facets:
12
+ * - avoids management of the same facet twice,
13
+ * - allows direct access by facet name.
14
+ */
15
+ class AfsFacetManager
16
+ {
17
+ private $facets = array();
18
+ private $facet_mode = AfsFacetMode::AND_MODE;
19
+ private $facet_sort_mode = null;
20
+ private $facet_values_sort_order = null;
21
+
22
+ /** @brief Constructs new facet facet manager.
23
+ * @param $other [in] Instance used to initialize new one (default: creates
24
+ * new instance with default parameters).
25
+ */
26
+ public function __construct(AfsFacetManager $other=null)
27
+ {
28
+ if (! is_null($other)) {
29
+ $this->facets = $other->facets;
30
+ $this->facet_mode = $other->facet_mode;
31
+ $this->facet_sort_mode = $other->facet_sort_mode;
32
+ $this->facet_values_sort_order = $other->facet_values_sort_order;
33
+ }
34
+ }
35
+
36
+ /** @name Global facet management
37
+ * @{ */
38
+
39
+ /** @brief Defines default facet mode.
40
+ *
41
+ * By default, facet mode is set to AfsFacetMode::OR_MODE.
42
+ *
43
+ * @param $mode [in] Facet mode, see @a AfsFacetMode for more details.
44
+ *
45
+ * @exception InvalidArgumentException when provided mode is invalid.
46
+ */
47
+ public function set_default_facets_mode($mode)
48
+ {
49
+ AfsFacetMode::check_value($mode);
50
+ if (AfsFacetMode::UNSPECIFIED_MODE == $mode)
51
+ throw new InvalidArgumentException('Invalid ' . $mode . ' for default facet mode.');
52
+ $this->facet_mode = $mode;
53
+ }
54
+
55
+ /** @brief Retrieves default facet mode.
56
+ * @return facet mode (see AfsFacetMode for more details)
57
+ */
58
+ public function get_default_facets_mode()
59
+ {
60
+ return $this->facet_mode;
61
+ }
62
+ /** @brief Retrieves default stickyness of all facets
63
+ * @return @c true when facets should be sticky, @c false otherwise.
64
+ */
65
+ public function get_default_stickyness()
66
+ {
67
+ return $this->is_mode_sticky($this->facet_mode);
68
+ }
69
+
70
+ /** @brief Defines facet sort order.
71
+ * @param $ids [in] List of facet identifiers in the right sort order.
72
+ * @param $mode [in] Sort order mode (see AfsFacetOrder for more details).
73
+ */
74
+ public function set_facet_order(array $ids, $mode)
75
+ {
76
+ AfsFacetOrder::check_value($mode, 'Invalid sort order: ');
77
+ sort_array_by_key($ids, $this->facets, "simple_facet_creator");
78
+ $this->facet_sort_mode = $mode;
79
+ }
80
+ /** @brief Checks whether facet sort order is set to strict mode.
81
+ * @return @c true when facet sort order mode is strict, @c false otherwise.
82
+ */
83
+ public function is_facet_order_strict()
84
+ {
85
+ return AfsFacetOrder::STRICT == $this->facet_sort_mode;
86
+ }
87
+
88
+ /** @brief Defines sort order for all facet values.
89
+ *
90
+ * AFS search default sort for facet values is alphanumeric. This method
91
+ * allows to change this behaviour.
92
+ *
93
+ * @param $mode [in] Sort mode (see AfsFacetValuesSortMode).
94
+ * @param $order [in] Sort order (see AfsSortOrder).
95
+ *
96
+ * @exception InvalidArgumentException when $mode or $order is invalid.
97
+ */
98
+ public function set_facets_values_sort_order($mode, $order)
99
+ {
100
+ $this->facet_values_sort_order = new AfsFacetValuesSortOrder($mode, $order);
101
+ }
102
+ /** @brief Checks whether specific sort order has been defined on facet values.
103
+ * @return @c True when specific sort order has been defined, @c false otherwise.
104
+ */
105
+ public function has_facets_values_sort_order()
106
+ {
107
+ return (! is_null($this->facet_values_sort_order));
108
+ }
109
+ /** @brief Retrieves sort order defined on facet values.
110
+ * @return sort order or @c null when no sort sorder has been set.
111
+ */
112
+ public function get_facets_values_sort_order()
113
+ {
114
+ return $this->facet_values_sort_order;
115
+ }
116
+ /** @} */
117
+
118
+ /** @name Fine grained facet management
119
+ * @{ */
120
+
121
+ /** @brief Defines facet mode for one or more facets.
122
+ * @param $mode [in] Facet mode to set (see AfsFacetMode for more details).
123
+ * @param $ids [in] Identifier(s) of the facet(s).
124
+ * @exception InvalidArgumentException when provided mode is invalid.
125
+ */
126
+ public function set_facets_mode($mode, $ids)
127
+ {
128
+ if (! is_array($ids))
129
+ $ids = array($ids);
130
+ foreach ($ids as $id) {
131
+ if (! array_key_exists($id, $this->facets))
132
+ $this->facets[$id] = new AfsFacet($id, AfsFacetType::UNKNOWN_TYPE);
133
+ $facet = $this->facets[$id];
134
+ $facet->set_mode($mode);
135
+ }
136
+ }
137
+ /** @brief Adds new facet configuration to manager.
138
+ *
139
+ * The order of added facets influences the order of the facets in AFS
140
+ * output reply stream.
141
+ *
142
+ * @param $facet [in] New facet to manage.
143
+ * @exception InvalidArgumentException facet with same id is already
144
+ * registered.
145
+ */
146
+ public function add_facet(AfsFacet $facet)
147
+ {
148
+ $id = $facet->get_id();
149
+ if (array_key_exists($id, $this->facets)) {
150
+ throw new InvalidArgumentException('Facet with same id (' . $id
151
+ . ') already present.');
152
+ }
153
+ $this->facets[$id] = $facet;
154
+ }
155
+ /** @brief Checks whether provided facet exists and has right parameters.
156
+ *
157
+ * Currently configured facet is updated with parameters of the given facet
158
+ * when it is necessary (update facet mode, facet type...)
159
+ *
160
+ * @param $facet [in] Facet to test.
161
+ * @exception AfsUndefinedFacetException provided facet is not currently
162
+ * managed.
163
+ * @exception AfsInvalidFacetParameterException provided facet does not
164
+ * match currently defined parameters.
165
+ */
166
+ public function check_facet(AfsFacet $facet)
167
+ {
168
+ if (! $this->has_facet($facet->get_id())) {
169
+ throw new AfsUndefinedFacetException('No facet with id \''
170
+ . $facet->get_id() . '\' currently managed');
171
+ }
172
+ $configured = $this->get_facet($facet->get_id());
173
+ if (! $configured->update($facet)) {
174
+ throw new AfsInvalidFacetParameterException('Provided facet is not '
175
+ . 'similar to registered one: ' . $facet . ' =/= ' . $configured);
176
+ }
177
+ }
178
+ /** @brief Checks or adds provided facet.
179
+ *
180
+ * If facet of the same id is already managed, this method check that
181
+ * associated parameters are of the good type. Otherwise, the facet is
182
+ * appended to the list of managed facets.
183
+ *
184
+ * @param $facet [in] facet to check/add.
185
+ *
186
+ * @exception AfsInvalidFacetParameterException when provided facet is
187
+ * incompatible with the one currently registered with same id.
188
+ */
189
+ public function check_or_add_facet($facet)
190
+ {
191
+ try {
192
+ $this->check_facet($facet);
193
+ } catch (AfsUndefinedFacetException $e) {
194
+ $this->add_facet($facet);
195
+ }
196
+ }
197
+ /** @brief Checks whether at least one facet is defined.
198
+ * @return @c true when one or more facet is defined, @c false otherwise.
199
+ */
200
+ public function has_facets()
201
+ {
202
+ return count($this->facets) > 0;
203
+ }
204
+ /** @brief Retrieves all facets.
205
+ * @return all managed facets.
206
+ */
207
+ public function get_facets()
208
+ {
209
+ return $this->facets;
210
+ }
211
+ /** @brief Checks whether facet with provided name has already been defined.
212
+ * @param $name [in] name of the facet to check.
213
+ * @return @c True when facet with provided name is already defined,
214
+ * @c false otherwise.
215
+ */
216
+ public function has_facet($name)
217
+ {
218
+ if (array_key_exists($name, $this->facets)) {
219
+ return true;
220
+ } else {
221
+ return false;
222
+ }
223
+ }
224
+ /** @brief Retrieves specific facet parameters.
225
+ * @param $name [in] facet name to look for.
226
+ * @return @a AfsFacet instance with required @a name.
227
+ * @exception OutOfBoundsException when no facet with required @a name is
228
+ * defined.
229
+ */
230
+ public function get_facet($name)
231
+ {
232
+ if (! $this->has_facet($name)) {
233
+ throw new OutOfBoundsException("No facet named '" . $name
234
+ . "' is currently registered");
235
+ }
236
+ return $this->facets[$name];
237
+ }
238
+ /** @brief Retrieves facet, creates it first if it deos not exist.
239
+ *
240
+ * When necessary, facet is created using default configuration parameters.
241
+ *
242
+ * @param $name [in] Facet identifier.
243
+ *
244
+ * @return facet with appropriate identifier.
245
+ */
246
+ public function get_or_create_facet($name)
247
+ {
248
+ try {
249
+ return $this->get_facet($name);
250
+ } catch (OutOfBoundsException $e) {
251
+ $facet = new AfsFacet($name);
252
+ $facet->set_mode($this->get_default_facets_mode());
253
+ $this->add_facet($facet);
254
+ return $this->get_facet($name);
255
+ }
256
+
257
+ }
258
+ /** @} */
259
+
260
+ /** @name Internal helpers
261
+ * @{ */
262
+
263
+ /** @brief Checks whether provided facet is sticky or not.
264
+ *
265
+ * If facet mode is undefined, rely on default facet mode to determine
266
+ * whether the facet is sticky or not.
267
+ *
268
+ * @param $facet [in] Facet for which mode should be determined.
269
+ *
270
+ * @return @c true when the facet is considered as sticky, @c false
271
+ * otherwise.
272
+ */
273
+ public function is_sticky(AfsFacet $facet)
274
+ {
275
+ $mode = $facet->get_mode();
276
+ if (AfsFacetMode::UNSPECIFIED_MODE == $mode)
277
+ $mode = $this->facet_mode;
278
+
279
+ return $this->is_mode_sticky($mode);
280
+ }
281
+
282
+ /** @brief Copies current instance.
283
+ * @return new instance, copy of current one.
284
+ */
285
+ public function copy()
286
+ {
287
+ return new AfsFacetManager($this);
288
+ }
289
+ /** @} */
290
+
291
+ private function is_mode_sticky($mode)
292
+ {
293
+ if (AfsFacetMode::SINGLE_MODE == $mode
294
+ || AfsFacetMode::OR_MODE == $mode) {
295
+ return true;
296
+ } else {
297
+ return false;
298
+ }
299
+ }
300
+ }
301
+
302
+
303
+ /** @brief Creates AfsFacet object with facet id only.
304
+ *
305
+ * This function is present ofr internal use only!
306
+ *
307
+ * @param $id [in] Identifier of the facet to create.
308
+ * @return newly created facet.
309
+ */
310
+ function simple_facet_creator($id)
311
+ {
312
+ return new AfsFacet($id, AfsFacetType::UNKNOWN_TYPE);
313
+ }
lib/antidot/AFS/SEARCH/afs_facet_mode.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Mode of the facets
5
+ *
6
+ * Specify the mode of the facets. Modes allow to combine or replace values of
7
+ * the facets.
8
+ */
9
+ class AfsFacetMode extends BasicEnum
10
+ {
11
+ private static $instance = null;
12
+
13
+ static public function check_value($value, $msg=null)
14
+ {
15
+ if (is_null(self::$instance))
16
+ self::$instance = new self();
17
+ BasicEnum::check_val(self::$instance, $value, $msg);
18
+ }
19
+
20
+ /** @brief Single mode.
21
+ *
22
+ * New value set for the facet replaces existing one.
23
+ *
24
+ * Example:<br/>
25
+ * Let's suppose your products have one brand each.
26
+ *
27
+ * - A query without any filter should get a reply with all products and
28
+ * following facet values:
29
+ *
30
+ * - Brand1 (3 products)
31
+ * - Brand2 (7 products)
32
+ * - Brand3 (4 products)
33
+ *
34
+ * - Then you can filter on Brand2, reply should only contains products
35
+ * of appropriate brand whereas facet values are not changed. All the brands
36
+ *are still available so that one can filter on other brand.
37
+ *
38
+ * - Brand1 (3 products)
39
+ * - Brand2 (7 products) X
40
+ * - Brand3 (4 products)
41
+ *
42
+ *
43
+ * - You can filter on Brand1 so that you get all products for this brand
44
+ * whereas facet values are still unchanged:
45
+ *
46
+ * - Brand1 (3 products) X
47
+ * - Brand2 (7 products)
48
+ * - Brand3 (4 products)
49
+ */
50
+ const SINGLE_MODE = 'SINGLE_MODE';
51
+
52
+ /** @brief Or mode.
53
+ *
54
+ * New value set for the facet is appended to the list of values already
55
+ * set. All the values are or-combined.
56
+ *
57
+ * Example:<br/>
58
+ * Let's suppose your products have one brand each.
59
+ *
60
+ * - A query without any filter should get a reply with all products and
61
+ * following facet values:
62
+ *
63
+ * - Brand1 (3 products)
64
+ * - Brand2 (7 products)
65
+ * - Brand3 (4 products)
66
+ *
67
+ * - Then you can filter on Brand2, reply should only contains products
68
+ * of appropriate brand whereas facet values are not changed. All the brands
69
+ * are still available so that one can filter on other brand.
70
+ *
71
+ * - Brand1 (3 products)
72
+ * - Brand2 (7 products) X
73
+ * - Brand3 (4 products)
74
+ *
75
+ * - You can add filter on Brand1 so that you get all products for brand
76
+ * 1 and brand 2 whereas facet values are still unchanged:
77
+ *
78
+ * - Brand1 (3 products) X
79
+ * - Brand2 (7 products) X
80
+ * - Brand3 (4 products)
81
+ */
82
+ const OR_MODE = 'OR_MODE';
83
+
84
+ /** @brief And mode.
85
+ *
86
+ * This is the standard mode for Antidot search engine.
87
+ *
88
+ * New value set for the facet is appended to the list of values already
89
+ * set. All the values are and-combined.
90
+ *
91
+ * Example:<br/>
92
+ * Let's suppose your products have one or more colors each.
93
+ *
94
+ * - A query without any filter should get a reply with all products and
95
+ * following facet values:
96
+ *
97
+ * - Green (3 products)
98
+ * - Blue (7 products)
99
+ * - Red (4 products)
100
+ *
101
+ * - Then you can filter on Blue, reply should only contains products
102
+ * of appropriate color and facet values are updated. Only relevant facet
103
+ * values are present in the reply: let's suppose there is no product which
104
+ * is blue and red and there is only two products which are blue and green:
105
+ *
106
+ * - Green (2 products)
107
+ * - Blue (7 products) X
108
+ *
109
+ * - You can add filter on Green so that you get all products which are
110
+ * blue and green. Facet values are updated according to this new filter:
111
+ *
112
+ * - Green (2 products) X
113
+ * - Blue (2 products) X
114
+ */
115
+ const AND_MODE = 'AND_MODE';
116
+
117
+ /** @brief Unspecified mode.
118
+ *
119
+ * When a facet is built with unspecified mode, global mode defined at
120
+ * facet manager level is applied.
121
+ */
122
+ const UNSPECIFIED_MODE = 'UNSPECIFIED_MODE';
123
+ }
124
+
125
+
lib/antidot/AFS/SEARCH/afs_facet_sort.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Sort mode of the facets. */
5
+ class AfsFacetOrder extends BasicEnum
6
+ {
7
+ private static $instance = null;
8
+
9
+ static public function check_value($value, $msg=null)
10
+ {
11
+ if (is_null(self::$instance))
12
+ self::$instance = new self();
13
+ BasicEnum::check_val(self::$instance, $value, $msg);
14
+ }
15
+
16
+ /** @brief Strict mode.
17
+ *
18
+ * All facets are sorted according to provided sort order list. Facets not
19
+ * present in the list are removed from reply. */
20
+ const STRICT = 'STRICT';
21
+ /** @brief Lax mode.
22
+ *
23
+ * Facets are sorted at AfsReplysetHelper level. This allows to retrieve all
24
+ * facets. First facets are sorted according to provided sort order list,
25
+ * other ones follow as they appear in AFS search engine reply. */
26
+ const LAX = 'LAX';
27
+ }
28
+
29
+
lib/antidot/AFS/SEARCH/afs_facet_type.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Type of the facets
5
+ *
6
+ * Specify the type of the facets.
7
+ */
8
+ class AfsFacetType extends BasicEnum
9
+ {
10
+ private static $instance = null;
11
+
12
+ static public function check_value($value, $msg=null)
13
+ {
14
+ if (is_null(self::$instance))
15
+ self::$instance = new self();
16
+ BasicEnum::check_val(self::$instance, $value, $msg);
17
+ }
18
+
19
+ /** @brief Facet values of type integer. */
20
+ const INTEGER_TYPE = 'INTEGER';
21
+ /** @brief Facet values of type real. */
22
+ const REAL_TYPE = 'REAL';
23
+ /** @brief Facet values of type string. */
24
+ const STRING_TYPE = 'STRING';
25
+ /** @brief Facet values of type date. */
26
+ const DATE_TYPE = 'DATE';
27
+ /** @brief Facet values of type boolean. */
28
+ const BOOL_TYPE = 'BOOL';
29
+ /** @brief Unknown facet type. */
30
+ const UNKNOWN_TYPE = 'UNKNOWN';
31
+ }
32
+
33
+
lib/antidot/AFS/SEARCH/afs_facet_value_formatter.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @brief Interface used to format facet values. */
4
+ interface AfsFacetValueIdFormatter
5
+ {
6
+ /** @brief Formats input value
7
+ * @param $value [in] value to be formatted.
8
+ * @return formatted value.
9
+ */
10
+ public function format($value);
11
+ }
12
+
13
+
14
+ /** @brief Simple formatter which does nothing. */
15
+ class AfsNoFacetValueIdFormatter implements AfsFacetValueIdFormatter
16
+ {
17
+ /** @brief Does nothing: return input value.
18
+ * @param $value [in] value to be formatted.
19
+ * @return input value.
20
+ */
21
+ public function format($value)
22
+ {
23
+ return $value;
24
+ }
25
+ }
26
+
27
+
28
+ /** @brief Formatter which surround value with double quotes. */
29
+ class AfsQuoteFacetValueIdFormatter implements AfsFacetValueIdFormatter
30
+ {
31
+ /** @brief Format input value.
32
+ * @param $value [in] vaalue to be formatted.
33
+ * @return input value surrounded by double quotes.
34
+ */
35
+ public function format($value)
36
+ {
37
+ return '"' . $value . '"';
38
+ }
39
+ }
40
+
41
+
lib/antidot/AFS/SEARCH/afs_facet_values_sort_mode.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_tools.php';
3
+
4
+ /** @brief Facet values sort mode enumerator. */
5
+ class AfsFacetValuesSortMode extends BasicEnum
6
+ {
7
+ private static $instance = null;
8
+
9
+ static public function check_value($value, $msg=null)
10
+ {
11
+ if (is_null(self::$instance))
12
+ self::$instance = new self();
13
+ BasicEnum::check_val(self::$instance, $value, $msg);
14
+ }
15
+
16
+ /** @brief Alphabetical sort order on facet value ids. */
17
+ const ALPHA = 'alpha';
18
+ /** @brief Numerical sort order on number of items per facet value. */
19
+ const ITEMS = 'items';
20
+ /** @brief Alphabetical sort order using user specified key. */
21
+ const ALPHA_KEY = 'alphaKey';
22
+ /** @brief Numerical sort order using user specified key. */
23
+ const NUM_KEY = 'numKey';
24
+ }
lib/antidot/AFS/SEARCH/afs_facet_values_sort_order.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_facet_values_sort_mode.php';
3
+ require_once 'AFS/SEARCH/afs_sort_order.php';
4
+
5
+ /** @brief Facet values sort order. */
6
+ class AfsFacetValuesSortOrder
7
+ {
8
+ /** @brief Sort order mode.
9
+ *
10
+ * See AfsFacetValuesSortMode for available values.
11
+ */
12
+ public $mode = null;
13
+ /** @brief Sort order.
14
+ *
15
+ * See AfsSortOrder for available values.
16
+ */
17
+ public $order = null;
18
+
19
+ /** @brief Constructs new instance with appropriate sort mode/order.
20
+ *
21
+ * @param $mode [in] Sort mode (see AfsFacetValuesSortMode for details).
22
+ * @param $order [in] Sort order (see AfsSortOrder for details).
23
+ */
24
+ public function __construct($mode, $order)
25
+ {
26
+ AfsFacetValuesSortMode::check_value($mode, 'Invalid facet values sort mode: ');
27
+ AfsSortOrder::check_value($order, 'Invalid facet values sort order: ');
28
+ $this->mode = $mode;
29
+ $this->order = $order;
30
+ }
31
+
32
+ /** @brief Copies current instance.
33
+ * @return new instance, copy of current one.
34
+ */
35
+ public function copy()
36
+ {
37
+ return new AfsFacetValuesSortOrder($this->mode, $this->order);
38
+ }
39
+
40
+ /** @brief Format instance as array of sort mode then sort order.
41
+ * @return formatted array.
42
+ */
43
+ public function format()
44
+ {
45
+ return array($this->mode, $this->order);
46
+ }
47
+ }
lib/antidot/AFS/SEARCH/afs_feed_coder.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_coder_interface.php";
3
+ require_once "AFS/SEARCH/afs_coder_base.php";
4
+
5
+ /** @brief Default feed coder implementation. */
6
+ class AfsFeedCoder extends AfsCoderBase implements AfsCoderInterface
7
+ {
8
+ private $value_sep;
9
+
10
+ /** @brief Construct new instance.
11
+ *
12
+ * @param $value_separator [in] character used to separate values.
13
+ * @param $escape_character [in] character to escape character when feeds
14
+ * contains previous separator.
15
+ *
16
+ * @exception InvalidArgumentException when @a value_separator and
17
+ * @a escape_character are not strictly different.
18
+ */
19
+ public function __construct($value_separator='_', $escape_character='|')
20
+ {
21
+ $this->check_unicity(func_get_args());
22
+ $this->value_sep = $value_separator;
23
+ parent::__construct($escape_character);
24
+ }
25
+
26
+ /** @brief Encode feeds
27
+ * @param $feeds [in] List of feeds
28
+ * @return string encoded feeds
29
+ */
30
+ public function encode(array $feeds)
31
+ {
32
+ $result = array();
33
+ foreach ($feeds as $feed) {
34
+ $result[] = $this->escape($feed);
35
+ }
36
+ return implode($this->value_sep, $result);
37
+ }
38
+
39
+ /** @brief Decode feeds from string.
40
+ * @param $feeds [in] string representing list of feeds.
41
+ * @return list of feeds.
42
+ */
43
+ public function decode($feeds)
44
+ {
45
+ $result = array();
46
+ $feeds = $this->explode($this->value_sep, $feeds);
47
+ foreach ($feeds as $feed) {
48
+ $result[] = $this->unescape($feed);
49
+ }
50
+ return $result;
51
+ }
52
+
53
+ /** @internal
54
+ * @brief Escape special characters.
55
+ * @param $value [in] value to modify.
56
+ * @return @a value with escaped characters.
57
+ */
58
+ private function escape($value)
59
+ {
60
+ return $this->replace('(' . preg_quote($this->value_sep) . '|'
61
+ . preg_quote($this->escape) . ')',
62
+ $this->escape . '$1', $value);
63
+ }
64
+
65
+ /** @internal
66
+ * @brief Unescape character from feed names.
67
+ * @param $value [in] input value to transform.
68
+ * @return unescaped value.
69
+ */
70
+ private function unescape($value)
71
+ {
72
+ return $this->replace(preg_quote($this->escape) . '('
73
+ . preg_quote($this->value_sep) . '|' . preg_quote($this->escape)
74
+ . ')', '$1', $value);
75
+ }
76
+ }
77
+
lib/antidot/AFS/SEARCH/afs_filter_coder.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_coder_interface.php";
3
+ require_once "AFS/SEARCH/afs_coder_base.php";
4
+
5
+ /** @brief Default filter coder implementation. */
6
+ class AfsFilterCoder extends AfsCoderBase implements AfsCoderInterface
7
+ {
8
+ private $value_sep;
9
+ private $filter_sep;
10
+
11
+ /** @brief Construct new instance.
12
+ *
13
+ * @param $value_separator [in] character used to separate values for each
14
+ * filter.
15
+ * @param $filter_separator [in] character used to separate filters.
16
+ * @param $escape_character [in] character to escape character when filters
17
+ * or values cointains one of the previous separator.
18
+ *
19
+ * @exception InvalidArgumentException when @a value_separator,
20
+ * @a filter_separator and @a escape_character are not strictly
21
+ * different.
22
+ */
23
+ public function __construct($value_separator='_', $filter_separator='-',
24
+ $escape_character='|')
25
+ {
26
+ $this->check_unicity(func_get_args());
27
+ $this->value_sep = $value_separator;
28
+ $this->filter_sep = $filter_separator;
29
+ parent::__construct($escape_character);
30
+ }
31
+
32
+ /** @brief Encode filters.
33
+ * @param $filters [in] List of filters with their values.
34
+ * @return string encoded filters
35
+ */
36
+ public function encode(array $filters)
37
+ {
38
+ $result = array();
39
+ foreach ($filters as $filter => $values) {
40
+ $filter_str = array($this->escape($filter));
41
+ foreach ($values as $value) {
42
+ $filter_str[] = $this->escape($value);
43
+ }
44
+ $result[] = implode($this->value_sep, $filter_str);
45
+ }
46
+ return implode($this->filter_sep, $result);
47
+ }
48
+
49
+ /** @brief Decode filters from string.
50
+ * @param $filters [in] string representing list of filters with their
51
+ * values.
52
+ * @return List of filters with their values.
53
+ */
54
+ public function decode($filters)
55
+ {
56
+ $result = array();
57
+ $filters = $this->explode($this->filter_sep, $filters);
58
+ foreach ($filters as $filter) {
59
+ $values = $this->explode($this->value_sep, $filter);
60
+ $filter_name = $this->unescape(array_shift($values));
61
+ $result[$filter_name] = array();
62
+ foreach ($values as $value) {
63
+ $result[$filter_name][] = $this->unescape($value);
64
+ }
65
+ }
66
+ return $result;
67
+ }
68
+
69
+ /** @internal
70
+ * @brief Escape special characters.
71
+ * @param $value [in] value to modify.
72
+ * @return @a value with escaped characters.
73
+ */
74
+ private function escape($value)
75
+ {
76
+ return $this->replace('(' . preg_quote($this->value_sep) . '|'
77
+ . preg_quote($this->filter_sep) . '|' . preg_quote($this->escape)
78
+ . ')', $this->escape . '$1', $value);
79
+ }
80
+
81
+ /** @internal
82
+ * @brief Unescape character from filter names and values.
83
+ * @param $value [in] input value to transform.
84
+ * @return unescaped value.
85
+ */
86
+ private function unescape($value)
87
+ {
88
+ return $this->replace(preg_quote($this->escape) . '('
89
+ . preg_quote($this->value_sep) . '|' . preg_quote($this->filter_sep)
90
+ . '|' . preg_quote($this->escape) . ')', '$1', $value);
91
+ }
92
+ }
93
+
lib/antidot/AFS/SEARCH/afs_header_helper.php ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_helper_base.php';
3
+
4
+ /** @brief Helper to retrieve useful information from AFS search engine reply header.
5
+ */
6
+ class AfsHeaderHelper extends AfsHelperBase
7
+ {
8
+ private $header = null;
9
+
10
+ /** @brief Constructs AFS search engine reply header helper.
11
+ * @param $header [in] json decoded header reply.
12
+ */
13
+ public function __construct($header)
14
+ {
15
+ $this->header = $header;
16
+ }
17
+
18
+ /** @brief Checks whether an error occured.
19
+ *
20
+ * You are encouraged to check error before accessing any other data.
21
+ * @return @c True on error, @c false otherwise.
22
+ */
23
+ public function in_error()
24
+ {
25
+ return property_exists($this->header, 'error');
26
+ }
27
+
28
+ /** @brief Retrieves error message.
29
+ *
30
+ * You should check whether an error occurred before retrieving error
31
+ * message otherwise you may go into trouble.
32
+ *
33
+ * @return detailled error.
34
+ */
35
+ public function get_error()
36
+ {
37
+ return $this->header->error->message[0];
38
+ }
39
+
40
+ /** @brief Retrieves user identifier.
41
+ *
42
+ * You should check whether an error occurred before retrieving user id
43
+ * otherwise you may go into trouble.
44
+ *
45
+ * @return user identifier.
46
+ */
47
+ public function get_user_id()
48
+ {
49
+ return $this->header->query->userId;
50
+ }
51
+
52
+ /** @brief Retrieves session identifier.
53
+ *
54
+ * You should check whether an error occurred before retrieving session id
55
+ * otherwise you may go into trouble.
56
+ *
57
+ * @return session identifier.
58
+ */
59
+ public function get_session_id()
60
+ {
61
+ return $this->header->query->sessionId;
62
+ }
63
+
64
+ /** @brief Retrieves AFS search engine computation duration.
65
+ *
66
+ * You should check whether an error occurred before retrieving duration
67
+ * otherwise you may go into trouble.
68
+ *
69
+ * @return computation duration in milliseconds.
70
+ */
71
+ public function get_duration()
72
+ {
73
+ return $this->header->performance->durationMs;
74
+ }
75
+
76
+ /** @brief Retrieve query parameter stored in header
77
+ * @input $key : Name of the parameter
78
+ * @return value of the parameter
79
+ *
80
+ */
81
+ public function get_query_parameter($key)
82
+ {
83
+ foreach($this->header->query->queryParam as $param) {
84
+ if ($param->name === $key) {
85
+ return $param->value;
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+ }
91
+
92
+
lib/antidot/AFS/SEARCH/afs_helper_configuration.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/afs_configuration_base.php';
3
+ require_once 'AFS/SEARCH/afs_query_coder.php';
4
+ require_once 'AFS/SEARCH/afs_text_visitor.php';
5
+ require_once 'AFS/SEARCH/afs_spellcheck_text_visitor.php';
6
+
7
+
8
+ /** @brief Configuration class for AFS helper classes. */
9
+ class AfsHelperConfiguration extends AfsConfigurationBase
10
+ {
11
+ private $query_coder = null;
12
+ private $reply_text_visitor = null;
13
+ private $spellcheck_text_visitor = null;
14
+
15
+ /** @brief Constructs new configuration class with default parameters set.
16
+ */
17
+ public function __construct()
18
+ {
19
+ parent::__construct();
20
+ $this->reply_text_visitor = new AfsTextVisitor();
21
+ $this->spellcheck_text_visitor = new AfsSpellcheckTextVisitor();
22
+ }
23
+
24
+ /** @name Query coder
25
+ * @{ */
26
+
27
+ /** @brief Checks whether a query coder has been defined.
28
+ * @return @c True when a query coder is defined, @c false otherwise.
29
+ */
30
+ public function has_query_coder()
31
+ {
32
+ if (is_null($this->query_coder)) {
33
+ return false;
34
+ } else {
35
+ return true;
36
+ }
37
+ }
38
+ /** @brief Retrieves query coder.
39
+ * @return query coder (see AfsQueryCoderInterface).
40
+ */
41
+ public function get_query_coder()
42
+ {
43
+ return $this->query_coder;
44
+ }
45
+ /** @brief Defines new query coder.
46
+ * @param $query_coder [in] new query coder to set.
47
+ * @return current instance.
48
+ */
49
+ public function set_query_coder(AfsQueryCoderInterface $query_coder)
50
+ {
51
+ $this->query_coder = $query_coder;
52
+ return $this;
53
+ }
54
+ /** @} */
55
+
56
+ /** @name Reply text
57
+ * @{ */
58
+
59
+ /** @brief Retrieves reply text visitor.
60
+ *
61
+ * This visitor is used to format text for title and abstract replies.
62
+ * @return reply text visitor (see AfsTextVisitorInterface).
63
+ */
64
+ public function get_reply_text_visitor()
65
+ {
66
+ return $this->reply_text_visitor;
67
+ }
68
+
69
+ /** @brief Defines new reply text visitor.
70
+ * @param $visitor [in] new visitor to set
71
+ * @return current instance.
72
+ */
73
+ public function set_reply_text_visitor(AfsTextVisitorInterface $visitor)
74
+ {
75
+ $this->reply_text_visitor = $visitor;
76
+ return $this;
77
+ }
78
+ /** @} */
79
+
80
+ /** @name Spellcheck text
81
+ * @{ */
82
+
83
+ /** @brief Retrieves spellcheck text visitor.
84
+ * @return spellcheck text visitor (see AfsSpellcheckTextVisitorInterface).
85
+ */
86
+ public function get_spellcheck_text_visitor()
87
+ {
88
+ return $this->spellcheck_text_visitor;
89
+ }
90
+
91
+ /** @brief Defines new spellcheck text visitor.
92
+ * @param $visitor [in] new visitor to set.
93
+ * @return current instance.
94
+ */
95
+ public function set_spellcheck_text_visitor(AfsSpellcheckTextVisitorInterface $visitor)
96
+ {
97
+ $this->spellcheck_text_visitor = $visitor;
98
+ return $this;
99
+ }
100
+ /** @} */
101
+ }
102
+
103
+
lib/antidot/AFS/SEARCH/afs_interval.php ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_interval_exception.php';
3
+
4
+
5
+ /** @brief Defines minimal value allowed for an integer. */
6
+ define('PHP_INT_MIN', ~PHP_INT_MAX);
7
+
8
+
9
+ /** @brief Helper to make intervals.
10
+ */
11
+ class AfsInterval
12
+ {
13
+ private $lower_bound = null;
14
+ private $lower_bound_excluded = false;
15
+ private $upper_bound = null;
16
+ private $upper_bound_excluded = false;
17
+
18
+ /** @brief Constructs new interval helper.
19
+ *
20
+ * Initialize new AfsInterval with at least one interval bound.
21
+ *
22
+ * @param $lower_bound [in] Lower bound of the interval.
23
+ * @param $upper_bound [in] Upper bound of the interval (default: null).
24
+ *
25
+ * @exception AfsIntervalBoundException when both boundaries are null.
26
+ */
27
+ public function __construct($lower_bound, $upper_bound=null)
28
+ {
29
+ if (is_null($lower_bound) && is_null($upper_bound))
30
+ throw new AfsIntervalBoundException('Boundaries cannot be null at the same time');
31
+ if (PHP_INT_MIN == $lower_bound)
32
+ $lower_bound = null;
33
+ if (PHP_INT_MAX == $upper_bound)
34
+ $upper_bound = null;
35
+ $this->lower_bound = $lower_bound;
36
+ $this->upper_bound = $upper_bound;
37
+ }
38
+
39
+ /** @brief Retrieves interval lower bound.
40
+ * @return lower bound of the interval.
41
+ */
42
+ public function get_lower_bound()
43
+ {
44
+ return $this->lower_bound;
45
+ }
46
+ /** @brief Excludes lower bound from the interval.
47
+ *
48
+ * By default, both boundaries are included.
49
+ */
50
+ public function exclude_lower_bound()
51
+ {
52
+ $this->lower_bound_excluded = true;
53
+ return $this;
54
+ }
55
+ /** @brief Checks wether lower bound is excluded or not.
56
+ * @return @c True when lower bound is excluded from the interval, @c false
57
+ * otherwise.
58
+ */
59
+ public function is_lower_bound_excluded()
60
+ {
61
+ return $this->lower_bound_excluded;
62
+ }
63
+
64
+ /** @brief Retrieves interval upper bound.
65
+ * @return upper bound of the interval.
66
+ */
67
+ public function get_upper_bound()
68
+ {
69
+ return $this->upper_bound;
70
+ }
71
+ /** @brief Excludes upper bound from the interval.
72
+ *
73
+ * By default, both boundaries are included.
74
+ */
75
+ public function exclude_upper_bound()
76
+ {
77
+ $this->upper_bound_excluded = true;
78
+ return $this;
79
+ }
80
+ /** @brief Checks wether upper bound is excluded or not.
81
+ * @return @c True when upper bound is excluded from the interval, @c false
82
+ * otherwise.
83
+ */
84
+ public function is_upper_bound_excluded()
85
+ {
86
+ return $this->upper_bound_excluded;
87
+ }
88
+
89
+
90
+ /** @brief Serialize this instance in string format.
91
+ *
92
+ * Returned value can be used to recreate new interval using
93
+ * AfsInterval::parse method.
94
+ *
95
+ * @return string representation of the instance.
96
+ */
97
+ public function __toString()
98
+ {
99
+ return $this->get_left_interval_sign()
100
+ . (is_null($this->lower_bound) ? PHP_INT_MIN : $this->lower_bound)
101
+ . ' .. '
102
+ . (is_null($this->upper_bound) ? PHP_INT_MAX : $this->upper_bound)
103
+ . $this->get_right_interval_sign();
104
+ }
105
+
106
+ private function get_left_interval_sign()
107
+ {
108
+ if ($this->lower_bound_excluded)
109
+ return ']';
110
+ else
111
+ return '[';
112
+ }
113
+ private function get_right_interval_sign()
114
+ {
115
+ if ($this->upper_bound_excluded)
116
+ return '[';
117
+ else
118
+ return ']';
119
+ }
120
+
121
+ /** @brief Creates new interval helper.
122
+ *
123
+ * Initialize new AfsInterval with at least one interval bound.
124
+ *
125
+ * @param $lower_bound [in] Lower bound of the interval (default: null).
126
+ * @param $upper_bound [in] Upper bound of the interval (default: null).
127
+ *
128
+ * @exception AfsIntervalBoundException when both boundaries are null.
129
+ */
130
+ public static function create($lower_bound=null, $upper_bound=null)
131
+ {
132
+ return new AfsInterval($lower_bound, $upper_bound);
133
+ }
134
+
135
+ /** @brief Creates new interval helper from string.
136
+ *
137
+ * @param $value [in] String value to parse in order to create new
138
+ * AfsInterval instance.
139
+ *
140
+ * @return newly created instance.
141
+ */
142
+ public static function parse($value)
143
+ {
144
+ $bound_pattern = '([0-9."-]+)';
145
+ $sign_pattern = '(\[|\])';
146
+ $interval_pattern = '/^' . $sign_pattern . $bound_pattern . ' .. ' . $bound_pattern . $sign_pattern . '$/';
147
+ $matches = array();
148
+ $result = preg_match($interval_pattern, $value, $matches);
149
+ if (1 == $result) {
150
+ if (count($matches) != 5)
151
+ throw new Exception('Invalid number of matching elements, please contact Antidot support team');
152
+ $interval = new AfsInterval($matches[2], $matches[3]);
153
+ if (']' == $matches[1])
154
+ $interval->exclude_lower_bound();
155
+ if ('[' == $matches[4])
156
+ $interval->exclude_upper_bound();
157
+ return $interval;
158
+ } elseif (0 == $result) {
159
+ throw new AfsIntervalInitializerException($value);
160
+ } else {
161
+ throw new Exception('Please contact Antidot support for this PHP API!');
162
+ }
163
+ }
164
+ }
lib/antidot/AFS/SEARCH/afs_interval_exception.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_exception.php';
3
+
4
+ /** @brief Base class for all facet interval exceptions. */
5
+ abstract class AfsIntervalException extends AfsBaseException
6
+ { }
7
+
8
+
9
+ /** @brief Exception raised when invalid bound has been provided to interval. */
10
+ class AfsIntervalBoundException extends AfsIntervalException
11
+ { }
12
+
13
+
14
+ /** @brief Exception raised when invalid string initializer has been provided to create interval. */
15
+ class AfsIntervalInitializerException extends AfsIntervalException
16
+ {
17
+ /** @brief Constructs new AfsIntervalInitializerException instance.
18
+ * @param $value [in] Invalid string value.
19
+ */
20
+ public function __construct($value)
21
+ {
22
+ parent::__construct('Cannot initialize interval using: ' . $value);
23
+ }
24
+ }
lib/antidot/AFS/SEARCH/afs_meta_helper.php ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "COMMON/afs_helper_base.php";
3
+
4
+ /** @brief AFS reply meta data.
5
+ *
6
+ * Wrapper to access main meta data.
7
+ */
8
+ class AfsMetaHelper extends AfsHelperBase
9
+ {
10
+ private $feed = null;
11
+ private $total_replies = null;
12
+ private $replies_per_page = null;
13
+ private $duration = null;
14
+ private $producer = null;
15
+ private $cluster = null;
16
+ private $cluster_label = null;
17
+
18
+ /** @brief Construct new instance from <tt>meta</tt> node.
19
+ * @param $meta [in] meta data node of AFS reply.
20
+ */
21
+ public function __construct($meta)
22
+ {
23
+ $this->feed = $meta->uri;
24
+ $this->total_replies = $meta->totalItems;
25
+ $this->replies_per_page = $meta->pageItems;
26
+ $this->duration = $meta->durationMs;
27
+ $this->producer = $meta->producer;
28
+ if (property_exists($meta, 'cluster')) {
29
+ $this->cluster = $meta->cluster;
30
+ $this->cluster_label = $meta->cluster;
31
+ }
32
+ }
33
+
34
+ /** @brief Retrieve feed name of the reply.
35
+ * @return feed name.
36
+ */
37
+ public function get_feed()
38
+ {
39
+ return $this->feed;
40
+ }
41
+ /** @brief Retrieve total number of replies for current feed.
42
+ * @return number of replies for the feed.
43
+ */
44
+ public function get_total_replies()
45
+ {
46
+ return $this->total_replies;
47
+ }
48
+ /** @brief Retrieves number of replies per page.
49
+ * @return number of replies per page.
50
+ */
51
+ public function get_replies_per_page()
52
+ {
53
+ return $this->replies_per_page;
54
+ }
55
+ /** @brief Retrieve reply computation time for the feed.
56
+ * @return duration in milliseconds.
57
+ */
58
+ public function get_duration()
59
+ {
60
+ return $this->duration;
61
+ }
62
+ /** @brief Retrieve producer type.
63
+ * @return producer.
64
+ */
65
+ public function get_producer()
66
+ {
67
+ return $this->producer;
68
+ }
69
+
70
+ /** @brief Checks whether response contains clusters.
71
+ * @return @c True in cluster mode, @c false otherwise.
72
+ */
73
+ public function has_cluster()
74
+ {
75
+ return (! is_null($this->cluster));
76
+ }
77
+
78
+ /** @brief Retrieves filter identifier used to clusterize.
79
+ * @return filter identifier.
80
+ */
81
+ public function get_cluster_id()
82
+ {
83
+ return $this->cluster;
84
+ }
85
+
86
+ /** @brief Retrieves filter label used to clusterize.
87
+ * @return filter label.
88
+ */
89
+ public function get_cluster_label()
90
+ {
91
+ return $this->cluster_label;
92
+ }
93
+
94
+ /** @internal
95
+ * @brief Set cluster label (internal use only).
96
+ */
97
+ public function set_cluster_label($label)
98
+ {
99
+ $this->cluster_label = $label;
100
+ }
101
+
102
+ /** @brief Retrieve meta as array.
103
+ *
104
+ * All data are store in <tt>key => value</tt> format:
105
+ * @li @c feed: feed name,
106
+ * @li @c total_replies: total number of replies for current query,
107
+ * @li @c replies_per_page: number of replies per page,
108
+ * @li @c duration: time to compute the reply (in milliseconds),
109
+ * @li @c producer: name of the producer agent.
110
+ *
111
+ * @return array filled with key/value pairs.
112
+ */
113
+ public function format()
114
+ {
115
+ $result = array('feed' => $this->feed,
116
+ 'total_replies' => $this->total_replies,
117
+ 'replies_per_page' => $this->replies_per_page,
118
+ 'duration' => $this->duration,
119
+ 'producer' => $this->producer);
120
+ if (! is_null($this->cluster)) {
121
+ $result['cluster'] = $this->cluster;
122
+ $result['cluster_label'] = $this->cluster_label;
123
+ }
124
+ return $result;
125
+ }
126
+ }
127
+
128
+
lib/antidot/AFS/SEARCH/afs_pager_helper.php ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_helper_base.php';
3
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
4
+
5
+ /** @brief Helper for pager.
6
+ *
7
+ * This class allows to manage:
8
+ * - <tt>previous</tt> page if it exists.
9
+ * - <tt>next</tt> page if it exists.
10
+ * - <tt>numbered</tt> pages.
11
+ */
12
+ class AfsPagerHelper extends AfsHelperBase
13
+ {
14
+ private $pager = null;
15
+ private $last_page = null;
16
+ private $query = null;
17
+ private $config = null;
18
+ const PREVIOUS_NAME = 'previousPage';
19
+ const NEXT_NAME = 'nextPage';
20
+ const CURRENT_NAME = 'currentPage';
21
+ const LAST_PAGE = 'lastPage';
22
+
23
+ /** @brief Construct helper with pager and current query.
24
+ *
25
+ * @param $pager [in] pager retrieved from reply.
26
+ * @param $meta [in] meta data of the replyset.
27
+ * @param $query [in] current @a AfsQuery which will be used to generate
28
+ * appropriate queries (see bellow @a get_pages, @a get_previous and
29
+ * @a get_next).
30
+ * @param $config [in] helper ocnfiguration object.
31
+ *
32
+ * @exception InvalidArgumentException @a pager is invalid.
33
+ */
34
+ public function __construct($pager, AfsMetaHelper $meta, AfsQuery $query,
35
+ AfsHelperConfiguration $config)
36
+ {
37
+ if (! property_exists($pager, AfsPagerHelper::CURRENT_NAME)) {
38
+ throw new InvalidArgumentException('Pager is of the wrong type.');
39
+ }
40
+ $this->pager = $pager;
41
+ $this->query = $query;
42
+ $this->config = $config;
43
+ $this->initialize_last_page($meta);
44
+ }
45
+
46
+ /** @brief Retrieves all numbered pages.
47
+ *
48
+ * List all pages in ascending order. A query or a URL is associated with
49
+ * each page depending whether no coder or valid one has been provided.
50
+ *
51
+ * @return array of page => query or URL.
52
+ */
53
+ public function get_pages()
54
+ {
55
+ $result = array();
56
+ foreach ($this->pager->page as $page) {
57
+ $query = $this->query->set_page($page);
58
+ $result[$page] = $this->config->has_query_coder()
59
+ ? $this->config->get_query_coder()->generate_link($query)
60
+ : $query;
61
+ }
62
+ return $result;
63
+ }
64
+
65
+ /** @brief Retrieves current page number.
66
+ * @return Current page number.
67
+ */
68
+ public function get_current_no()
69
+ {
70
+ return $this->pager->currentPage;
71
+ }
72
+
73
+ /** @brief Checks whether previous page is present in the pager.
74
+ * @return @c True when previous page exists, @c false otherwise.
75
+ */
76
+ public function has_previous()
77
+ {
78
+ if (property_exists($this->pager, AfsPagerHelper::PREVIOUS_NAME)) {
79
+ return true;
80
+ } else {
81
+ return false;
82
+ }
83
+ }
84
+ /** @brief Retrieves query for previous page.
85
+ * @return query for previous page.
86
+ * @exception OutOfBoundsException when there is no previous page.
87
+ */
88
+ public function get_previous()
89
+ {
90
+ return $this->get_typed(AfsPagerHelper::PREVIOUS_NAME);
91
+ }
92
+
93
+ /** @brief Checks whether next page is present in the pager.
94
+ * @return @c True when next page exists, @c false otherwise.
95
+ */
96
+ public function has_next()
97
+ {
98
+ if (property_exists($this->pager, AfsPagerHelper::NEXT_NAME)) {
99
+ return true;
100
+ } else {
101
+ return false;
102
+ }
103
+ }
104
+ /** @brief Retrieves query for next page.
105
+ * @return query for next page.
106
+ * @exception OutOfBoundsException when there is no next page.
107
+ */
108
+ public function get_next()
109
+ {
110
+ return $this->get_typed(AfsPagerHelper::NEXT_NAME);
111
+ }
112
+
113
+ private function initialize_last_page($meta)
114
+ {
115
+ $page_no = ceil($meta->get_total_replies() / $meta->get_replies_per_page());
116
+ $query = $this->query->set_page($page_no);
117
+ if ($this->config->has_query_coder())
118
+ $second = $this->config->get_query_coder()->generate_link($query);
119
+ else
120
+ $second = $query;
121
+ $this->last_page = array($page_no, $second);
122
+ }
123
+ /** @brief Retrieves last page number along with corresponding query/URL.
124
+ *
125
+ * When a query coder is available, an URL is returned as second paramter
126
+ * instead of AfsQuery.
127
+ *
128
+ * @return Array with last page available and query/URL.
129
+ */
130
+ public function get_last_page()
131
+ {
132
+ return $this->last_page;
133
+ }
134
+ /** @brief Retrieves last page number.
135
+ * @return Last page number.
136
+ */
137
+ public function get_last_page_no()
138
+ {
139
+ return $this->last_page[0];
140
+ }
141
+
142
+ /** @brief Retrieves pages as a simple array with key/value pairs.
143
+ *
144
+ * This include @c previous and @c next pages if they are present in AFS
145
+ * search engine reply.
146
+ *
147
+ * All data are stored in <tt>key => value</tt> format:
148
+ * <ul>
149
+ * <li><tt>previous</tt>: (if present) query or URL to previous page,</li>
150
+ * <li><tt>&lt;page number></tt>: query or URL for each page number,</li>
151
+ * <li><tt>next</tt>: (if present) query or URL to next page,</li>
152
+ * </ul>
153
+ *
154
+ * @return
155
+ */
156
+ public function get_all_pages()
157
+ {
158
+ $pages = array();
159
+ if ($this->has_previous()) {
160
+ $pages['previous'] = $this->get_previous();
161
+ }
162
+ $pages += $this->get_pages();
163
+ if ($this->has_next()) {
164
+ $pages['next'] = $this->get_next();
165
+ }
166
+ return $pages;
167
+ }
168
+
169
+ /** @brief Retrieves pages as array.
170
+ *
171
+ * All data are stored in <tt>key => value</tt> format:
172
+ * <ul>
173
+ * <li><tt>pages</tt>: list of pages (see AfsPagerHelper::get_pages
174
+ * for details on the format)</li>
175
+ * <li><tt>current</tt>: current page number.</li>
176
+ * </ul>
177
+ *
178
+ * Query is returned for each page when no query coder has been provided,
179
+ * otherwise query coder is used to produce appropriate URL.
180
+ *
181
+ * @return array filled with key and values.
182
+ */
183
+ public function format()
184
+ {
185
+ return array('pages' => $this->get_all_pages(),
186
+ 'current' => $this->get_current_no());
187
+ }
188
+
189
+ private function get_typed($type)
190
+ {
191
+ if (property_exists($this->pager, $type)) {
192
+ $query = $this->query->set_page($this->pager->$type);
193
+ return $this->config->has_query_coder()
194
+ ? $this->config->get_query_coder()->generate_link($query) : $query;
195
+ } else {
196
+ throw new OutOfBoundsException("No page type $type available.");
197
+ }
198
+ }
199
+ }
200
+
201
+
lib/antidot/AFS/SEARCH/afs_producer.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "COMMON/afs_tools.php";
3
+
4
+ /** @brief Defines AFS search replies' producers.
5
+ *
6
+ * Specifies which agent produces reply set.
7
+ */
8
+ class AfsProducer extends BasicEnum
9
+ {
10
+ private static $instance = null;
11
+
12
+ static public function check_value($value, $msg=null)
13
+ {
14
+ if (is_null(self::$instance))
15
+ self::$instance = new self();
16
+ BasicEnum::check_val(self::$instance, $value, $msg);
17
+ }
18
+
19
+ /** @brief Reply set result of check query. */
20
+ const CHECK = 'CHECK';
21
+ /** @brief Reply set result of search agent. */
22
+ const SEARCH = 'SEARCH';
23
+ /** @brief Reply set result of spellcheck agent. */
24
+ const SPELLCHECK = 'SPELLCHECK';
25
+ /** @brief Reply set result of concept agent. */
26
+ const CONCEPT = 'CONCEPT';
27
+ /** @brief Reply set result of proxy. */
28
+ const PROXY = 'PROXY';
29
+ /** @brief Reply set result of master agent. */
30
+ const SEARCH_MASTER = 'SEARCH_MASTER';
31
+ /** @brief Reply set result of slave agent. */
32
+ const SEARCH_SLAVE = 'SEARCH_SLAVE';
33
+ }
34
+
35
+
lib/antidot/AFS/SEARCH/afs_promote_reply_helper.php ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_raw_text_visitor.php";
3
+ require_once "AFS/SEARCH/afs_base_reply_helper.php";
4
+ require_once "AFS/SEARCH/afs_client_data_helper.php";
5
+
6
+ /** @brief Promote helper to manager title, abstract and uri of one reply.
7
+ *
8
+ * You are @b highly encouraged to use this helper to format Promote replies.
9
+ *
10
+ * This helper use same visitor for both title and abstract reply. If none is
11
+ * defined while constructing instance, default implementation is used (see
12
+ * @a AfsTextVisitor).
13
+ *
14
+ * In order to deal with client data, you should have to use specific @a
15
+ * AfsClientDataManager.
16
+ */
17
+ class AfsPromoteReplyHelper extends AfsBaseReplyHelper
18
+ {
19
+ private $clientdata_mgr = null;
20
+
21
+ /** @brief Constructs new instance.
22
+ * @param $reply [in] one reply used to initialize the instance.
23
+ */
24
+ public function __construct($reply)
25
+ {
26
+ parent::__construct($reply, new AfsRawTextVisitor());
27
+ if (property_exists($this->reply, 'clientData')) {
28
+ $this->clientdata_mgr = new AfsClientDataManager($this->reply->clientData);
29
+ }
30
+ }
31
+
32
+ /** @brief Retrieves custom data from promote reply.
33
+ * @param $key [in] Identifier of the custom resource. When not specified
34
+ * custom data are returned as key/value pairs.
35
+ * @return value(s) associated to specified key or all key/value pairs.
36
+ * @exception Exception no custom data has been defined.
37
+ */
38
+ public function get_custom_data($key=null)
39
+ {
40
+ if (is_null($this->clientdata_mgr)) {
41
+ throw new Exception('No custom data available for this promote ('
42
+ . $this->get_title() . ')');
43
+ }
44
+
45
+ $clientdata = null;
46
+ try {
47
+ $clientdata = $this->clientdata_mgr->get_clientdata();
48
+ } catch (OutOfBoundsException $e) {
49
+ throw new Exception('Custom data with default identifier is not available!');
50
+ }
51
+
52
+ if ('application/xml' != $clientdata->get_mime_type()) {
53
+ throw new Exception('Custom data is not store in XML format, update'
54
+ . ' your PHP connector or contact Antidot support team.');
55
+ }
56
+
57
+ if (is_null($key)) {
58
+ return $this->extract_key_value_pairs($clientdata);
59
+ } else {
60
+ return $clientdata->get_value("/afs:customData/afs:$key",
61
+ array('afs' => 'http://ref.antidot.net/7.3/bo.xsd'));
62
+ }
63
+ }
64
+
65
+ private function extract_key_value_pairs($clientData)
66
+ {
67
+ $result = array();
68
+ $doc = new DOMDocument();
69
+ $doc->loadXML($clientData->get_value());
70
+ if ($doc->hasChildNodes() && $doc->childNodes->item(0)->hasChildNodes()) {
71
+ foreach ($doc->childNodes->item(0)->childNodes as $node) {
72
+ $result[$node->localName] = $node->nodeValue;
73
+ }
74
+ }
75
+ return $result;
76
+ }
77
+ }
78
+
79
+
lib/antidot/AFS/SEARCH/afs_promote_replyset_helper.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_base_replyset_helper.php';
3
+ require_once 'AFS/SEARCH/afs_reply_helper_factory.php';
4
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
5
+
6
+
7
+ /** @brief Helper for Promote replies.
8
+ *
9
+ * This helper is very similar to @a AfsReplysetHelper.
10
+ *
11
+ * This helper gives access to underlying helpers for metadata, replies, factes
12
+ * and pager (if any).
13
+ */
14
+ class AfsPromoteReplysetHelper extends AfsBaseReplysetHelper
15
+ {
16
+ /** @brief Construct new Promote replyset helper instance.
17
+ *
18
+ * @param $reply_set [in] one reply from decoded json reply.
19
+ * @param $config [in] helper configuration object.
20
+ */
21
+ public function __construct($reply_set, AfsHelperConfiguration $config)
22
+ {
23
+ parent::__construct($reply_set, $config, new AfsReplyHelperFactory());
24
+ }
25
+ }
26
+
27
+
lib/antidot/AFS/SEARCH/afs_query.php ADDED
@@ -0,0 +1,830 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_language.php';
3
+ require_once 'AFS/afs_query_base.php';
4
+ require_once 'AFS/SEARCH/afs_sort_order.php';
5
+ require_once 'AFS/SEARCH/afs_sort_builtins.php';
6
+ require_once 'AFS/SEARCH/afs_cluster_exception.php';
7
+ require_once 'AFS/SEARCH/afs_count.php';
8
+ require_once 'AFS/SEARCH/afs_facet_manager.php';
9
+ require_once 'AFS/SEARCH/afs_facet_default.php';
10
+
11
+ /** @brief Represent an AFS query.
12
+ *
13
+ * All instances of this class are immutable: each method call involves
14
+ * creation of new instance copied from current one. Newly created instance
15
+ * is modified according to called method and returned. So, <b>don't
16
+ * forget</b> to store returned object.
17
+ * @code
18
+ * $query = new AfsQuery();
19
+ * $query->set_query('my query');
20
+ * if (! $query->has_query())
21
+ * {
22
+ * echo 'You do not save the result of set_query!';
23
+ * }
24
+ * @endcode
25
+ */
26
+
27
+ /** @internal
28
+ * key, user, group
29
+ */
30
+ class AfsQuery extends AfsQueryBase
31
+ {
32
+ protected $facet_mgr = null;
33
+
34
+ protected $filter = array(); // afs:filter
35
+ protected $page = 1; // afs:page
36
+ protected $lang = null; // afs:lang
37
+ protected $sort = array(); // afs:sort
38
+ protected $facetDefault = null; // afs:facetDefault
39
+ protected $cluster = null;
40
+ protected $maxClusters = null;
41
+ protected $overspill = null;
42
+ protected $count = null; // afs:count for cluster mode
43
+ protected $advancedFilter = array(); // exposed only to AFS search engine
44
+
45
+ /**
46
+ * @brief Construct new AFS query object.
47
+ * @param $afs_query [in] instance used for initialization (default:
48
+ * create new empty instance).
49
+ */
50
+ public function __construct(AfsQuery $afs_query = null)
51
+ {
52
+ parent::__construct($afs_query);
53
+ if ($afs_query != null) {
54
+ $this->facet_mgr = $afs_query->facet_mgr->copy();
55
+ $this->filter = $afs_query->filter;
56
+ $this->page = $afs_query->page;
57
+ $this->lang = $afs_query->lang;
58
+ $this->sort = $afs_query->sort;
59
+ $this->facetDefault = $afs_query->facetDefault->copy();
60
+ $this->cluster = $afs_query->cluster;
61
+ $this->maxClusters = $afs_query->maxClusters;
62
+ $this->overspill = $afs_query->overspill;
63
+ $this->count = $afs_query->count;
64
+ $this->advancedFilter = $afs_query->advancedFilter;
65
+ } else {
66
+ $this->facet_mgr = new AfsFacetManager();
67
+ $this->lang = new AfsLanguage(null);
68
+ $this->facetDefault = new AfsFacetDefault();
69
+ $this->auto_set_from = false;
70
+ }
71
+ }
72
+
73
+ /** @internal
74
+ * @brief Copy current instance.
75
+ * @return New copied instance.
76
+ */
77
+ protected function copy()
78
+ {
79
+ return new AfsQuery($this);
80
+ }
81
+
82
+ /** @brief Action to perform when an assignment occurs.
83
+ *
84
+ * Page number is reset. */
85
+ protected function on_assignment()
86
+ {
87
+ $this->reset_page();
88
+ }
89
+
90
+
91
+ /** @name Filter management
92
+ * @{ */
93
+
94
+ /** @brief Assign new value(s) to specific facet replacing any existing one.
95
+ * @param $facet_id [in] id of the facet to update.
96
+ * @param $values [in] new value(s) to filter on.
97
+ * @return new up to date instance.
98
+ */
99
+ public function set_filter($facet_id, $values)
100
+ {
101
+ $copy = $this->copy();
102
+ $copy->on_assignment();
103
+ if (! is_array($values))
104
+ $values = array($values);
105
+ $copy->filter[$facet_id] = $values;
106
+ return $this->auto_set_from ? $copy->set_from(AfsOrigin::FACET) : $copy;
107
+ }
108
+ /** @brief Assign new value(s) to specific facet.
109
+ * @param $facet_id [in] id of the facet for which new @a value should be
110
+ * added.
111
+ * @param $values [in] value(s) to add to the facet.
112
+ * @return new up to date instance.
113
+ */
114
+ public function add_filter($facet_id, $values)
115
+ {
116
+ $copy = $this->copy();
117
+ $copy->on_assignment();
118
+ if (empty($copy->filter[$facet_id]))
119
+ $copy->filter[$facet_id] = array();
120
+ if (! is_array($values))
121
+ $values = array($values);
122
+ $copy->filter[$facet_id] = array_merge($copy->filter[$facet_id], $values);
123
+ return $this->auto_set_from ? $copy->set_from(AfsOrigin::FACET) : $copy;
124
+ }
125
+ /** @brief Remove existing value from specific facet.
126
+ * @remark No error is reported when the removed @a value is not already set.
127
+ * @param $facet_id [in] id of the facet to update.
128
+ * @param $value [in] value to be removed from the list of values associated
129
+ * to the facet.
130
+ * @return new up to date instance.
131
+ */
132
+ public function remove_filter($facet_id, $value)
133
+ {
134
+ $copy = $this->copy();
135
+ $copy->on_assignment();
136
+ if (! empty($copy->filter[$facet_id])) {
137
+ $pos = array_search($value, $copy->filter[$facet_id]);
138
+ unset($copy->filter[$facet_id][$pos]);
139
+ if (empty($copy->filter[$facet_id]))
140
+ unset($copy->filter[$facet_id]);
141
+ }
142
+ return $this->auto_set_from ? $copy->set_from(AfsOrigin::FACET) : $copy;
143
+ }
144
+ /** @brief Check whether instance has a @a value associated with specified
145
+ * facet id.
146
+ * @param $facet_id [in] id of the facet to check.
147
+ * @param $value [in] value to check in the list of values for the given
148
+ * @a facet_id.
149
+ * @return true when the @a value is present in the list of values
150
+ * associated with @a facet_id, false otherwise. Always false when provided
151
+ * @a facet_id is unknown.
152
+ */
153
+ public function has_filter($facet_id, $value)
154
+ {
155
+ if (empty($this->filter[$facet_id])) {
156
+ return false;
157
+ } else {
158
+ if (! isset($value))
159
+ return true;
160
+ else
161
+ return in_array($value, $this->filter[$facet_id]);
162
+ }
163
+ }
164
+ /** @brief Retrieve the list of values for specific facet id.
165
+ * @remark You should ensure that the required @a facet_id is valid.
166
+ * @param $facet_id [in] facet id to consider.
167
+ * @return list of values associated to the given @a facet_id.
168
+ */
169
+ public function get_filter_values($facet_id)
170
+ {
171
+ if (array_key_exists($facet_id, $this->filter)) {
172
+ return $this->filter[$facet_id];
173
+ }
174
+ throw new AfsFilterException("$facet_id doesn't exist");
175
+ }
176
+ /** @brief Retrieve the list of all managed facet ids.
177
+ *
178
+ * Only elements from this list should be used to query @a get_filter_values
179
+ * method.
180
+ * @return list of facet ids.
181
+ */
182
+ public function get_filters()
183
+ {
184
+ return array_keys($this->filter);
185
+ }
186
+ /** @} */
187
+
188
+ /** @name Advanced filter management
189
+ *
190
+ * These filters are intended to be exposed to AFS search engine only.
191
+ * @{ */
192
+ /** @brief Checks whether at least one advanced filter is defined.
193
+ * @return @c True when one or more advanced filters have been defined,
194
+ * @c false otherwise.
195
+ */
196
+ public function has_advanced_filter()
197
+ {
198
+ return ! empty($this->advancedFilter);
199
+ }
200
+ /** @brief Retrieves advanced filters.
201
+ * @return Advanced filters.
202
+ */
203
+ public function get_advanced_filters()
204
+ {
205
+ return $this->advancedFilter;
206
+ }
207
+ /** @brief Defines new advanced filter replacing any existing ones.
208
+ * @param $filter [in] Advanced filter to set.
209
+ * @return new up to date instance.
210
+ */
211
+ public function set_advanced_filter(AfsFilterWrapper $filter)
212
+ {
213
+ $copy = $this->copy();
214
+ $copy->advancedFilter = array($filter->to_string());
215
+ return $copy;
216
+ }
217
+ /** @brief Appends new advanced filter to the query.
218
+ * @param $filter [in] Advanced filter to add.
219
+ * @return new up to date instance.
220
+ */
221
+ public function add_advanced_filter(AfsFilterWrapper $filter)
222
+ {
223
+ $copy = $this->copy();
224
+ $copy->advancedFilter[] = $filter->to_string();
225
+ return $copy;
226
+ }
227
+ /** @brief Remove any advanced filter definition from the query.
228
+ * @return new up to date instance.
229
+ */
230
+ public function reset_advanced_filter()
231
+ {
232
+ $copy = $this->copy();
233
+ $copy->advancedFilter = array();
234
+ return $copy;
235
+ }
236
+
237
+ /** @} */
238
+
239
+ /** @name Page management
240
+ * @{ */
241
+
242
+ /** @brief Check whether reply page is set.
243
+ * @return always true.
244
+ */
245
+ public function has_page()
246
+ {
247
+ return $this->page != null;
248
+ }
249
+ /** @brief Define new result page.
250
+ * @param $page [in] result page to output. It should be greater than or
251
+ * equal to 1.
252
+ * @return new up to date instance.
253
+ * @exception Exception on invalid page number.
254
+ */
255
+ public function set_page($page)
256
+ {
257
+ if ($page <= 0)
258
+ {
259
+ throw new Exception('Invalid page number: ' . $page);
260
+ }
261
+ $copy = $this->copy();
262
+ $copy->page = $page;
263
+ return $this->auto_set_from ? $copy->set_from(AfsOrigin::PAGER) : $copy;
264
+ }
265
+ /** @brief Retrieve current reply page.
266
+ * @remark For a new query, this vaue is reset to 1.
267
+ * @return reply page number.
268
+ */
269
+ public function get_page()
270
+ {
271
+ return $this->page;
272
+ }
273
+ /** @brief Shortcut for @a set_page(1).
274
+ *
275
+ * This method do not copy the instance and change current one inplace.
276
+ */
277
+ protected function reset_page()
278
+ {
279
+ return $this->page = 1;
280
+ }
281
+ /** @} */
282
+
283
+ /** @name Language management
284
+ * @{ */
285
+
286
+ /** @brief Check whether language is set.
287
+ * @return true when language parameter is set, false otherwise.
288
+ */
289
+ public function has_lang()
290
+ {
291
+ return $this->lang->lang != null;
292
+ }
293
+ /** @brief Remove filter on language.
294
+ */
295
+ public function reset_lang()
296
+ {
297
+ return $this->set_lang(null);
298
+ }
299
+ /** @brief Define new language.
300
+ *
301
+ * See @a AfsLanguage for more details on valid values.
302
+ * @remark Page value is preserved when this method is called.
303
+ *
304
+ * @param $lang [in] New language to filter on. Empty string or null value
305
+ * resets current language filter.
306
+ * @exception Exception when provided language is invalid.
307
+ */
308
+ public function set_lang($lang)
309
+ {
310
+ $lang = new AfsLanguage($lang);
311
+ $copy = $this->copy();
312
+ $copy->lang = $lang;
313
+ return $copy;
314
+ }
315
+ /** @brief Retrieve current language filter.
316
+ * @return language filter or null when no language is set.
317
+ */
318
+ public function get_lang()
319
+ {
320
+ return $this->lang;
321
+ }
322
+ /** @} */
323
+
324
+ /** @name Sort order management
325
+ * @{ */
326
+
327
+ /** @brief Checks whether sort parameter is set.
328
+ * @param $name [in] check this specific parameter name (default=null:
329
+ * checks whether at least one sort parameter is set).
330
+ * @return true when sort parameter is set, false otherwise.
331
+ */
332
+ public function has_sort($name=null)
333
+ {
334
+ if (is_null($name)) {
335
+ return ! empty($this->sort);
336
+ } else {
337
+ return array_key_exists($name, $this->sort);
338
+ }
339
+ }
340
+ /** @brief Resets sort order to AFS default sort order.
341
+ */
342
+ public function reset_sort()
343
+ {
344
+ return $this->set_sort(null);
345
+ }
346
+ /** @brief Defines new sort order.
347
+ *
348
+ * Provided sort parameter should be a built-in facet like: @c afs:weight,
349
+ * @c afs:relevance, @c afs:words ... or user defined facet
350
+ *
351
+ * @param $sort_param [in] new sort parameter. When set to emty string or
352
+ * null, this call to this method is equivalent to call to
353
+ * @a reset_sort.
354
+ * @param $order [in] order applied to the given parameter. Allowed values
355
+ * are AfsSortOrder::DESC (default) or AfsSortOrder:ASC.
356
+ *
357
+ * @exception Exception when provided sort parameter does not conform to
358
+ * required syntax.
359
+ */
360
+ public function set_sort($sort_param, $order=AfsSortOrder::DESC)
361
+ {
362
+ return $this->internal_add_sort(null, $sort_param, $order);
363
+ }
364
+ /** @brief Defines additional sort order.
365
+ *
366
+ * Provided sort parameter should be a built-in facet like: @c afs:weight,
367
+ * @c afs:relevance, @c afs:words (see AfsSortBuiltins)... or user defined
368
+ * facet.
369
+ *
370
+ * @param $sort_param [in] new sort parameter. When set to emty string or
371
+ * null, this call to this method is equivalent to call to
372
+ * @a reset_sort.
373
+ * @param $order [in] order applied to the given parameter. Allowed values
374
+ * are AfsSortOrder::DESC (default) or AfsSortOrder:ASC.
375
+ *
376
+ * @exception Exception when provided sort parameter does not conform to
377
+ * required syntax.
378
+ */
379
+ public function add_sort($sort_param, $order=AfsSortOrder::DESC)
380
+ {
381
+ return $this->internal_add_sort($this->sort, $sort_param, $order);
382
+ }
383
+ /** @brief Retrieves sort order.
384
+ * @deprecated This method will be removed soon!
385
+ * @return sort order as string.
386
+ */
387
+ public function get_sort()
388
+ {
389
+ $result = '';
390
+ $sorts = array();
391
+ foreach ($this->sort as $k => $v) {
392
+ $sorts[] = $k . ',' . $v;
393
+ }
394
+ if (! empty($sorts)) {
395
+ $result = implode(';', $sorts);
396
+ }
397
+ return $result;
398
+ }
399
+ /** @brief Retrieves sort order of the specified parameter.
400
+ * @param $name [in] parameter name to check.
401
+ * @return AfsSortOrder::ASC or AfsSortOrder::DESC.
402
+ * @exception OutOfBoundsException when required sort parameter is not
403
+ * defined.
404
+ */
405
+ public function get_sort_order($name)
406
+ {
407
+ if (array_key_exists($name, $this->sort)) {
408
+ return $this->sort[$name];
409
+ } else {
410
+ throw new OutOfBoundsException('Unknown sort parameter: ' . $name);
411
+ }
412
+ }
413
+ /** @brief Adds new sort parameter or substitutes existing one.
414
+ *
415
+ * @param $current_value [in] current sort order value.
416
+ * @param $sort_param [in] new sort parameter
417
+ * @param $order [in] sort order
418
+ *
419
+ * @return copy of current query.
420
+ */
421
+ private function internal_add_sort($current_value, $sort_param, $order)
422
+ {
423
+ if ($sort_param == '') {
424
+ $sort_param = null;
425
+ }
426
+ if (! is_null($sort_param)) {
427
+ if (strncmp('afs:', $sort_param, 4) == 0) {
428
+ AfsSortBuiltins::check_value($sort_param, 'Invalid sort parameter: ');
429
+ } elseif (1 != preg_match('/^[a-zA-Z][a-zA-Z0-9_-]*$/', $sort_param)) {
430
+ throw new Exception('Invalid sort parameter provided: ' . $sort_param);
431
+ }
432
+ AfsSortOrder::check_value($order, 'Invalid sort order provided: ');
433
+
434
+ $new_value = $current_value;
435
+ $new_value[$sort_param] = $order;
436
+ } else {
437
+ $new_value = array();
438
+ }
439
+
440
+ $copy = $this->copy();
441
+ $copy->on_assignment();
442
+ $copy->sort = $new_value;
443
+ return $copy;
444
+ }
445
+ /** @} */
446
+
447
+ /** @name Clustering
448
+ * @{ */
449
+
450
+ /** @brief Checks whether cluster is set for current query.
451
+ * @return @c True when cluster is defined for the query, @c false otherwise.
452
+ */
453
+ public function has_cluster()
454
+ {
455
+ return ! is_null($this->cluster);
456
+ }
457
+ /** @brief Defines new cluster query.
458
+ *
459
+ * @param $facet_id [in] Facet identifier used to make clusters.
460
+ * @param $replies_per_cluster [in] Number of replies provided by AFS
461
+ * search engine per cluster reply.
462
+ * @return new up to date instance.
463
+ */
464
+ public function set_cluster($facet_id, $replies_per_cluster)
465
+ {
466
+ $copy = $this->copy();
467
+ $copy->on_assignment();
468
+ $copy->cluster = $facet_id . ',' . $replies_per_cluster;
469
+ return $copy;
470
+ }
471
+ /** @brief Unsets cluster definition.
472
+ * @return new up to date instance.
473
+ */
474
+ public function unset_cluster()
475
+ {
476
+ $copy = $this->copy();
477
+ $copy->cluster = null;
478
+ $copy->maxClusters = null;
479
+ $copy->overspill = null;
480
+ $copy->count = null;
481
+ return $copy;
482
+ }
483
+ /** @brief Retrieves cluster identifier.
484
+ * @return Filter identifier used to make the clusters.
485
+ * @exception AfsUninitializedClusterException when no cluster has been previously set.
486
+ */
487
+ public function get_cluster_id()
488
+ {
489
+ $tmp = $this->get_splitted_cluster_definition();
490
+ return $tmp[0];
491
+ }
492
+ /** @brief Retrieves maximum number of replies per cluster.
493
+ * @return Number of replies per cluster reply.
494
+ * @exception AfsUninitializedClusterException when no cluster has been previously set.
495
+ */
496
+ public function get_nb_replies_per_cluster()
497
+ {
498
+ $tmp = $this->get_splitted_cluster_definition();
499
+ return $tmp[1];
500
+ }
501
+ /** @internal
502
+ * @brief Split cluster property to retrieve facet id and nb replies per cluster.
503
+ */
504
+ private function get_splitted_cluster_definition()
505
+ {
506
+ $this->check_cluster_initialization();
507
+ return explode(',', $this->cluster);
508
+ }
509
+ /** @brief Checks whether number of cluster is limited.
510
+ *
511
+ * When no limit is set, one cluster is created per facet value.
512
+ * @return @c True when number of clusters is limited, @c false otherwise.
513
+ */
514
+ public function has_max_clusters()
515
+ {
516
+ return ! is_null($this->maxClusters);
517
+ }
518
+ /** @brief Defines maximum number of cluster replies shown in AFS response.
519
+ * @param $max_nb_of_clusters [in] Maximum number of clusters.
520
+ * @return new up to date instance.
521
+ */
522
+ public function set_max_clusters($max_nb_of_clusters)
523
+ {
524
+ $this->check_cluster_initialization();
525
+ $copy = $this->copy();
526
+ $copy->on_assignment();
527
+ $copy->maxClusters = $max_nb_of_clusters;
528
+ return $copy;
529
+ }
530
+ /** @internal
531
+ * Useful for parse_from_parameter method.
532
+ */
533
+ protected function set_maxClusters($max_nb_of_clusters)
534
+ {
535
+ return $this->set_max_clusters($max_nb_of_clusters);
536
+ }
537
+ /** @brief Retrieves maximum number of clusters.
538
+ * @return Maximum number of clusters or @c null when not set (ie no limit).
539
+ */
540
+ public function get_max_clusters()
541
+ {
542
+ return $this->maxClusters;
543
+ }
544
+ /** @brief Checks wether overspill has been activated.
545
+ * @return @c True when overspill is active, @c false otherwise.
546
+ */
547
+ public function has_overspill()
548
+ {
549
+ return ! is_null($this->overspill);
550
+ }
551
+ /** @brief Activates or deactivate overspill mode.
552
+ *
553
+ * When overspill is activated, replies that could not be fitted in
554
+ * clusters are added, in sorted order, after all clusters.
555
+ *
556
+ * @param $status [in] Activates (default: @c true) or deactivates (@c false)
557
+ * overspill.
558
+ * @return new up to date instance.
559
+ */
560
+ public function set_overspill($status=true)
561
+ {
562
+ $this->check_cluster_initialization();
563
+ $copy = $this->copy();
564
+ if (true == $status)
565
+ $copy->overspill = 'true';
566
+ else
567
+ $copy->overspill = null;
568
+ return $copy;
569
+ }
570
+ /** @brief Retrieves count mode when cluster mode is active.
571
+ *
572
+ * Count mode influences number of replies value which corresponds to the
573
+ * number of documents (default) or the number of clusters.
574
+ *
575
+ * @return Current count mode. AfsCount::DOCUMENTS, AfsCount::CLUSTERS or
576
+ * @c null when no specific count mode has been set.
577
+ */
578
+ public function get_count_mode()
579
+ {
580
+ return $this->count;
581
+ }
582
+ /** @brief Defines new count mode.
583
+ *
584
+ * @param $count_mode [in] New count mode to set. Available values are
585
+ * AfsCount::DOCUMENTS, AfsCount::CLUSTERS or null to rely on
586
+ * default AFS search engine count mode.
587
+ *
588
+ * @return new up to date instance.
589
+ */
590
+ public function set_count($count_mode)
591
+ {
592
+ $this->check_cluster_initialization();
593
+ if (!is_null($count_mode))
594
+ AfsCount::check_value($count_mode, 'Invalid count mode: ');
595
+ $copy = $this->copy();
596
+ $copy->on_assignment();
597
+ $copy->count = $count_mode;
598
+ return $copy;
599
+ }
600
+ /** @internal
601
+ * Checks whether cluster has been initialized otherwise raise an exception.
602
+ */
603
+ private function check_cluster_initialization()
604
+ {
605
+ if (! $this->has_cluster())
606
+ throw new AfsUninitializedClusterException();
607
+ }
608
+ /** @} */
609
+
610
+ /** @name Full configuration through array of parameters
611
+ * @{ */
612
+
613
+ /** @brief Creates full query from array of parameters.
614
+ *
615
+ * Unknown parameters are silently ignored.
616
+ *
617
+ * @param $params [in] structured array of parameters.
618
+ * @return correctly initialized query.
619
+ */
620
+ public static function create_from_parameters(array $params)
621
+ {
622
+ $result = AfsQuery::work_on_specific_parameters($params);
623
+ $page = $result->get_page(); # page can be reset by some method calls
624
+
625
+ foreach ($params as $param => $values) {
626
+ $adder = 'add_' . $param;
627
+ $setter = 'set_' . $param;
628
+ if ($param == 'filter') {
629
+ foreach ($values as $filter => $filter_values) {
630
+ foreach ($filter_values as $value) {
631
+ $result = $result->add_filter($filter, $value);
632
+ }
633
+ }
634
+ } elseif ($param == 'sort') {
635
+ foreach ($values as $key => $value) {
636
+ $result = $result->$adder($key, $value);
637
+ }
638
+ } elseif (is_object($result) && is_callable(array($result, $adder))) {
639
+ foreach ($values as $value) {
640
+ $result = $result->$adder($value);
641
+ }
642
+ } elseif (is_object($result) && is_callable(array($result, $setter))) {
643
+ $result = $result->$setter($values);
644
+ } else {
645
+ // Store unknown parameter as a custom one
646
+ $result->set_custom_parameter($param, $values);
647
+ }
648
+ }
649
+ $result->page = $page;
650
+ return $result;
651
+ }
652
+
653
+ private static function work_on_specific_parameters(array& $params)
654
+ {
655
+ $result = new AfsQuery();
656
+ if (array_key_exists('cluster', $params)) {
657
+ $result->cluster = $params['cluster'];
658
+ unset($params['cluster']);
659
+ }
660
+ if (array_key_exists('page', $params)) {
661
+ $result->page = $params['page'];
662
+ unset($params['page']);
663
+ }
664
+ return $result;
665
+ }
666
+
667
+
668
+ protected function get_relevant_parameters()
669
+ {
670
+ $params = array('filter', 'sort', 'cluster', 'maxClusters', 'overspill', 'count');
671
+ if ($this->page != 1)
672
+ $params[] = 'page';
673
+ if (! is_null($this->lang->lang))
674
+ $params[] = 'lang';
675
+ return $params;
676
+ }
677
+
678
+ protected function get_additional_parameters()
679
+ {
680
+ return array('facetDefault', 'advancedFilter');
681
+ }
682
+ /** @} */
683
+
684
+ /** @name Facet management
685
+ *@{ */
686
+
687
+ /** @brief Retrieves facet manager.
688
+ * @return Facet manager associated to this instance.
689
+ */
690
+ public function get_facet_manager()
691
+ {
692
+ return $this->facet_mgr;
693
+ }
694
+
695
+ /** @brief Defines standard selection mode for all facets.
696
+ *
697
+ * This is the default mode.
698
+ *
699
+ * Standard selection mode allows to filter on one or more facet values
700
+ * whereas only relevant facet values are present in AFS search reply. See
701
+ * AfsFacetMode::AND_MODE for simple example.
702
+ */
703
+ public function set_default_standard_selection_facets()
704
+ {
705
+ $copy = $this->copy();
706
+ $copy->facet_mgr->set_default_facets_mode(AfsFacetMode::AND_MODE);
707
+ return $copy;
708
+ }
709
+ /** @brief Defines multi-selection mode for all facets.
710
+ *
711
+ * Replaces default mode (standard selection facets).
712
+ *
713
+ * Multi-selection mode allows to filter on one or more facet values whereas
714
+ * all facet values are still present in AFS search reply. See
715
+ * AfsFacetMode::OR_MODE for simple example.
716
+ */
717
+ public function set_default_multi_selection_facets()
718
+ {
719
+ $copy = $this->copy();
720
+ $copy->facet_mgr->set_default_facets_mode(AfsFacetMode::OR_MODE);
721
+ return $copy;
722
+ }
723
+ /** @brief Defines mono-selection mode for all facets.
724
+ *
725
+ * Replaces default mode (standard selection facets).
726
+ *
727
+ * Mono-selection mode allows to filter on one facet value whereas all facet
728
+ * values are still present in AFS search reply. Selecting new facet value
729
+ * replaces previously selected one. See AfsFacetMode::OR_MODE for simple
730
+ * example.
731
+ */
732
+ public function set_default_mono_selection_facets()
733
+ {
734
+ $copy = $this->copy();
735
+ $copy->facet_mgr->set_default_facets_mode(AfsFacetMode::SINGLE_MODE);
736
+ return $copy;
737
+ }
738
+
739
+ /** @brief Defines facet sort order.
740
+ *
741
+ * @remark Parameters are a list of facet identifiers in the right sort
742
+ * order (list of strings or array of strings).
743
+ */
744
+ public function set_facet_order()
745
+ {
746
+ $args = get_function_args_as_array(func_get_args());
747
+ $copy = $this->copy();
748
+ $copy->facet_mgr->set_facet_order($args, AfsFacetOrder::STRICT);
749
+ return $copy;
750
+ }
751
+
752
+ /** @brief Defines sort order for all facet values.
753
+ *
754
+ * AFS search default sort for facet values is alphanumeric. This method
755
+ * allows to change this behaviour.
756
+ * @remark This configuration is not exposed to AfsQueryCoder.
757
+ *
758
+ * @param $mode [in] Sort mode (see AfsFacetValuesSortMode).
759
+ * @param $order [in] Sort order (see AfsSortOrder).
760
+ *
761
+ * @exception InvalidArgumentException when $mode or $order is invalid.
762
+ */
763
+ public function set_facets_values_sort_order($mode, $order)
764
+ {
765
+ $copy = $this->copy();
766
+ $copy->facetDefault->set_sort_order($mode, $order);
767
+ return $copy;
768
+ }
769
+
770
+ /** @brief Defines maximum number of facet values replied per facet.
771
+ *
772
+ * Default maximum value is 1000.
773
+ * @param $nb_replies [in] maximum number of facet values.
774
+ */
775
+ public function set_facets_values_nb_replies($nb_replies)
776
+ {
777
+ $copy = $this->copy();
778
+ $copy->facetDefault->set_nb_replies($nb_replies);
779
+ return $copy;
780
+ }
781
+
782
+ /** @brief Defines standard selection mode for one or more facets.
783
+ *
784
+ * See AfsSearch::set_default_standard_selection_facets or
785
+ * AfsFacetMode::AND_MODE for more details.
786
+ *
787
+ * @remark Parameters: one (string) or more facet identifiers (individual
788
+ * strings or array of strings).
789
+ */
790
+ public function set_standard_selection_facets()
791
+ {
792
+ $args = get_function_args_as_array(func_get_args());
793
+ $copy = $this->copy();
794
+ $copy->facet_mgr->set_facets_mode(AfsFacetMode::AND_MODE, $args);
795
+ return $copy;
796
+ }
797
+ /** @brief Defines multi-selection mode for one or more facets.
798
+ *
799
+ * See AfsSearch::set_default_multi_selection_facets or
800
+ * AfsFacetMode::OR_MODE for more details.
801
+ *
802
+ * @remark Parameters: one (string) or more facet identifiers (individual
803
+ * strings or array of strings).
804
+ */
805
+ public function set_multi_selection_facets($ids)
806
+ {
807
+ $args = get_function_args_as_array(func_get_args());
808
+ $copy = $this->copy();
809
+ $copy->facet_mgr->set_facets_mode(AfsFacetMode::OR_MODE, $args);
810
+ return $copy;
811
+ }
812
+ /** @brief Defines mono-selection mode for one or more facets.
813
+ *
814
+ * See AfsSearch::set_default_mono_selection_facets or
815
+ * AfsFacetMode::SINGLE_MODE for more details.
816
+ *
817
+ * @remark Parameters: one (string) or more facet identifiers (individual
818
+ * strings or array of strings).
819
+ */
820
+ public function set_mono_selection_facets($ids)
821
+ {
822
+ $args = get_function_args_as_array(func_get_args());
823
+ $copy = $this->copy();
824
+ $copy->facet_mgr->set_facets_mode(AfsFacetMode::SINGLE_MODE, $args);
825
+ return $copy;
826
+ }
827
+ /** @} */
828
+ }
829
+
830
+
lib/antidot/AFS/SEARCH/afs_query_coder.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_query_coder_interface.php';
3
+ require_once 'AFS/SEARCH/afs_filter_coder.php';
4
+ require_once 'AFS/SEARCH/afs_feed_coder.php';
5
+ require_once 'AFS/SEARCH/afs_sort_coder.php';
6
+ require_once 'AFS/SEARCH/afs_query.php';
7
+
8
+ /** @brief Default query coder implementation. */
9
+ class AfsQueryCoder implements AfsQueryCoderInterface
10
+ {
11
+ private $path = null;
12
+ private $feed_coder = null;
13
+ private $filter_coder = null;
14
+ private $sort_coder = null;
15
+
16
+ /** @brief Construct new instance.
17
+ * @param $path [in] base path used to generate appropriate link (see
18
+ * @a generate_link method). If this parameter is not provided, value
19
+ * of $_SERVER['PHP_SELF'] is used as default value.
20
+ * @param $feed_coder [in] (optional) feed coder. If not set, default
21
+ * implementation is used (see @a AfsFeedCoder).
22
+ * @param $filter_coder [in] (optional) filter coder. If not set, default
23
+ * implementation is used (see @a AfsFilterCoder).
24
+ * @param $sort_coder [in] (optional) sort parameters coder. If not set,
25
+ * default implementation is used (see @a AfsSortCoder).
26
+ */
27
+ public function __construct($path=null, AfsCoderInterface $feed_coder=null,
28
+ AfsCoderInterface $filter_coder=null, AfsCoderInterface $sort_coder=null)
29
+ {
30
+ if (is_null($path))
31
+ $path = $_SERVER['PHP_SELF'];
32
+ if (is_null($feed_coder))
33
+ $feed_coder = new AfsFeedCoder();
34
+ if (is_null($filter_coder))
35
+ $filter_coder = new AfsFilterCoder();
36
+ if (is_null($sort_coder))
37
+ $sort_coder = new AfsSortCoder();
38
+ $this->path = $path;
39
+ $this->feed_coder = $feed_coder;
40
+ $this->filter_coder = $filter_coder;
41
+ $this->sort_coder = $sort_coder;
42
+ }
43
+
44
+ /** @brief Generate URL parameters from @a AfsQuery.
45
+ * @param $query [in] query to transform to URL parameters.
46
+ * @return appropriate URL parameters representing input @a query.
47
+ */
48
+ public function generate_parameters(AfsQuery $query)
49
+ {
50
+ $result = array();
51
+ foreach ($query->get_parameters(false) as $param => $values) {
52
+ $result[] = $param . '=' . htmlspecialchars(urlencode(
53
+ $this->coder($param, $values, 'encode')));
54
+ }
55
+ return implode('&', $result);
56
+ }
57
+
58
+ /** @brief Convenient method to build link.
59
+ *
60
+ * Combine @a path parameter to result of @a generate_parameters call.
61
+ *
62
+ * @param $query [in] @a AfsQuery used to generate appropriate link.
63
+ * @return link which can be directly used to query AFS search engine.
64
+ */
65
+ public function generate_link(AfsQuery $query)
66
+ {
67
+ return $this->path . '?' . $this->generate_parameters($query);
68
+ }
69
+
70
+ /** @brief Generate query from URL parameters.
71
+ * @param $params [in] array of parameters. Usually set to $_GET.
72
+ * @return @a AfsQuery correctly initialized.
73
+ */
74
+ public function build_query(array $params)
75
+ {
76
+ $buffer = array();
77
+ foreach ($params as $param => $values) {
78
+ $buffer[$param] = $this->coder($param, $values, 'decode');
79
+ }
80
+ $query = AfsQuery::create_from_parameters($buffer);
81
+ return $query;
82
+ }
83
+
84
+ /** @internal
85
+ * @brief Encode/decode provided values.
86
+ *
87
+ * If necessary coder exists, it is used to encode/decode provided
88
+ * @a values. Otherwise, @a values parameter is returned as is.
89
+ *
90
+ * @param $param_name [in] name of parameter to work on.
91
+ * @param $values [in] one or multiple values to encode/decode.
92
+ * @param $action [in] @c encode or @c decode (see interface
93
+ * @a AfsCoderInterface).
94
+ *
95
+ * @return encode/decode or unmodified values.
96
+ */
97
+ private function coder($param_name, $values, $action)
98
+ {
99
+ $coder = $param_name . '_coder';
100
+ if (property_exists($this, $coder)) {
101
+ return $this->$coder->$action($values);
102
+ } else {
103
+ return $values;
104
+ }
105
+ }
106
+ }
107
+
108
+
lib/antidot/AFS/SEARCH/afs_query_coder_interface.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @brief Interface for encoding/decoding queries.
4
+ *
5
+ * Encoded query is used to generate parameters suitable to build link for
6
+ * specific action.<br/>
7
+ * Query is decoded from array of parameters. This array usually comes from
8
+ * <tt>$_GET</tt> value.
9
+ *
10
+ * For example:
11
+ * - Suppose you are looking for shoes in your favorite web store.
12
+ * - First you search for the word <tt>shoes</tt>.
13
+ * - Then some results are presented along with @c size and @c color facets.
14
+ * - Now you want to filter on @c red color.
15
+ * - You have to build the @c query object from URL parameters, otherwise
16
+ * if you filter on the color only, you will get all red items. So you should
17
+ * initialize your query with:
18
+ * @code $query = $query_coder->build_query($_GET);@endcode
19
+ * - Now you want to filter on red color, you have to add a filter to the query:
20
+ * @code $query = $query->add_filter('color', 'red'); @endcode
21
+ * - Then you can generate parameters suitable for a valid HTTP link by calling:
22
+ * @code $params = $query_coder->generate_parameters($query); @endcode.
23
+ */
24
+ interface AfsQueryCoderInterface
25
+ {
26
+ /** @brief Generate suitable parameters which can be used in URL.
27
+ * @param $query [in] query object to encode.
28
+ * @return encoded query object.
29
+ */
30
+ public function generate_parameters(AfsQuery $query);
31
+ /** @brief Convenient method to build link.
32
+ *
33
+ * This method generally calls @a generate_parameters to build appropriate
34
+ * link.
35
+ * @param $query [in] @a AfsQuery used to generate appropriate link.
36
+ * @return link which can be directly used to query AFS search engine.
37
+ */
38
+ public function generate_link(AfsQuery $query);
39
+ /** @brief Generate query object from array of parameters.
40
+ * @param $params [in] array of parameters used to build query object.
41
+ * @return properly initialized query object.
42
+ */
43
+ public function build_query(array $params);
44
+ }
45
+
46
+
lib/antidot/AFS/SEARCH/afs_query_object_interface.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /** @brief Interface used for complex query options.
4
+ *
5
+ * This interface should never be used outside API internals.
6
+ */
7
+ interface AfsQueryObjectInterface
8
+ {
9
+ /** @brief Produces new instance copied from current one.
10
+ * @return copy of the current instance.
11
+ */
12
+ public function copy();
13
+
14
+ /** @brief Format object to appropriate string form.
15
+ *
16
+ * This method can returns single string of array of string when
17
+ * multiple values can be assigned to one single AFS search engine
18
+ * query option.
19
+ *
20
+ * @return formatted object.
21
+ */
22
+ public function format();
23
+ }
lib/antidot/AFS/SEARCH/afs_raw_text_visitor.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_text_helper.php";
3
+
4
+ /** @brief Implementation for text visitor without any text formatting.
5
+ */
6
+ class AfsRawTextVisitor implements AfsTextVisitorInterface
7
+ {
8
+ /** @brief Visit @a AfsStringText instance.
9
+ * @param $afs_text [in] visited instance.
10
+ * @return text value associated to visited instance.
11
+ */
12
+ public function visit_AfsStringText(AfsStringText $afs_text)
13
+ {
14
+ return $afs_text->get_text();
15
+ }
16
+
17
+ /** @brief Visit @a AfsMatchText instance.
18
+ * @param $afs_text [in] visited instance.
19
+ * @return text value associated to visited instance.
20
+ */
21
+ public function visit_AfsMatchText(AfsMatchText $afs_text)
22
+ {
23
+ return $afs_text->get_text();
24
+ }
25
+
26
+ /** @brief Visit @a AfsTruncateText instance.
27
+ * @param $afs_text [in] visited instance.
28
+ * @return static value: <tt>...</tt>
29
+ */
30
+ public function visit_AfsTruncateText(AfsTruncateText $afs_text)
31
+ {
32
+ return '...';
33
+ }
34
+ }
35
+
36
+
lib/antidot/AFS/SEARCH/afs_reply_helper.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_base_reply_helper.php";
3
+ require_once "AFS/SEARCH/afs_text_helper.php";
4
+ require_once "AFS/SEARCH/afs_text_visitor.php";
5
+ require_once "AFS/SEARCH/afs_client_data_helper.php";
6
+ require_once "COMMON/afs_helper_base.php";
7
+
8
+ /** @brief Helper to manager title, abstract and uri of one reply.
9
+ *
10
+ * You are @b highly encouraged to use this helper to format each reply.
11
+ *
12
+ * This helper use same visitor for both title and abstract reply. If none is
13
+ * defined while constructing instance, default implementation is used (see
14
+ * @a AfsTextVisitor).
15
+ *
16
+ * In order to deal with client data, you should have to use specific @a
17
+ * AfsClientDataManager.
18
+ */
19
+ class AfsReplyHelper extends AfsBaseReplyHelper
20
+ {
21
+ /** @brief Construct new instance.
22
+ *
23
+ * @param $reply [in] should correspond to one reply.
24
+ * @param $visitor [in] (optional) visitor used to traverse title and
25
+ * abstract reply texts. Default implementation is used
26
+ * (@a AfsTextVisitor) when none is provided.
27
+ */
28
+ public function __construct($reply, AfsTextVisitorInterface $visitor=null)
29
+ {
30
+ parent::__construct($reply, is_null($visitor) ? new AfsTextVisitor() : $visitor);
31
+ }
32
+
33
+ /** @brief Checks whether at least one client data is defined.
34
+ * @return @c True when one or more clientdatas are available, @c false
35
+ * otherwise.
36
+ */
37
+ public function has_clientdata()
38
+ {
39
+ if (array_key_exists('clientData', $this->reply)) {
40
+ return true;
41
+ } else {
42
+ return false;
43
+ }
44
+ }
45
+ /** @brief Retrieves client data manager.
46
+ * @return Manager of client data (see AfsClientDataManager).
47
+ * @exception Exception when no client data is available.
48
+ */
49
+ public function get_clientdatas()
50
+ {
51
+ if ($this->has_clientdata()) {
52
+ return new AfsClientDataManager($this->reply->clientData);
53
+ } else {
54
+ throw new Exception('No client data available!');
55
+ }
56
+ }
57
+
58
+ /** @brief Retrieves specific client data.
59
+ *
60
+ * @param $id [in] Id of the client data to retrieve (default: 'main').
61
+ * @return Client data helper with appropriate id
62
+ *
63
+ * @exception OutOfBoundsException when required client data is not found.
64
+ */
65
+ public function get_clientdata($id='main')
66
+ {
67
+ return $this->get_clientdatas()->get_clientdata($id);
68
+ }
69
+ }
70
+
71
+
lib/antidot/AFS/SEARCH/afs_reply_helper_factory.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once "AFS/SEARCH/afs_text_helper.php";
3
+ require_once "AFS/SEARCH/afs_reply_helper.php";
4
+ require_once "AFS/SEARCH/afs_promote_reply_helper.php";
5
+
6
+ /** @brief Factory for reply helper. */
7
+ class AfsReplyHelperFactory
8
+ {
9
+ private $visitor = null;
10
+
11
+ /** @brief Constructs new factory instance.
12
+ * @param $visitor [in] visitor used to format title and client data texts.
13
+ */
14
+ public function __construct(AfsTextVisitorInterface $visitor=null)
15
+ {
16
+ $this->visitor = $visitor;
17
+ }
18
+
19
+ /** @brief Creates appropriate reply helper.
20
+ *
21
+ * @param $feed [in] name of the feed reply.
22
+ * @param $reply [in] JSON decoded reply used to initialize the helper.
23
+ *
24
+ * @return standard or Promote reply helper.
25
+ */
26
+ public function create($feed, $reply)
27
+ {
28
+ if ('Promote' == $feed) {
29
+ return new AfsPromoteReplyHelper($reply);
30
+ } else {
31
+ return new AfsReplyHelper($reply, $this->visitor);
32
+ }
33
+ }
34
+
35
+ /** @brief Creates list of reply helpers.
36
+ *
37
+ * @param $feed [in] name of the feed reply.
38
+ * @param $replies [in] JSON decoded object which may contain replies.
39
+ *
40
+ * @return list of reply helpers.
41
+ */
42
+ public function create_replies($feed, $replies)
43
+ {
44
+ $result = array();
45
+ if (property_exists($replies, 'reply')) {
46
+ foreach ($replies->reply as $reply)
47
+ $result[] = $this->create($feed, $reply);
48
+ }
49
+ return $result;
50
+ }
51
+ }
52
+
53
+
lib/antidot/AFS/SEARCH/afs_replyset_helper.php ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/SEARCH/afs_pager_helper.php';
3
+ require_once 'AFS/SEARCH/afs_cluster_helper.php';
4
+ require_once 'AFS/SEARCH/afs_facet_helper.php';
5
+ require_once 'AFS/SEARCH/afs_query.php';
6
+ require_once 'AFS/SEARCH/afs_producer.php';
7
+ require_once 'AFS/SEARCH/afs_base_replyset_helper.php';
8
+ require_once 'AFS/SEARCH/afs_reply_helper_factory.php';
9
+ require_once 'AFS/SEARCH/afs_response_helper.php';
10
+ require_once 'AFS/SEARCH/afs_facet_helper_retriever.php';
11
+ require_once 'COMMON/afs_tools.php';
12
+
13
+
14
+ /** @brief Helper for replies from one feed.
15
+ *
16
+ * This helper gives access to underlying helpers for metadata, replies, factes
17
+ * and pager.
18
+ */
19
+ class AfsReplysetHelper extends AfsBaseReplysetHelper
20
+ {
21
+ private $facets = null;
22
+ private $pager = null;
23
+ private $clusters = array();
24
+
25
+ /** @brief Construct new replyset helper instance.
26
+ *
27
+ * @param $reply_set [in] one reply from decoded json reply.
28
+ * @param $query [in] query which has produced current reply.
29
+ * @param $config [in] helper configuration object.
30
+ */
31
+ public function __construct($reply_set, AfsQuery $query, AfsHelperConfiguration $config)
32
+ {
33
+ $reply_helper_factory = new AfsReplyHelperFactory($config->get_reply_text_visitor());
34
+ parent::__construct($reply_set, $config, $reply_helper_factory);
35
+ $this->initialize_facet($reply_set, $query, $config);
36
+ $this->initialize_cluster($reply_set, $query, $config);
37
+ $this->initialize_pager($reply_set, $query, $config);
38
+ }
39
+
40
+ protected function initialize_facet($reply_set, $query, $config)
41
+ {
42
+ $facets = array();
43
+ if (property_exists($reply_set, 'facets') && property_exists($reply_set->facets, 'facet')) {
44
+ foreach ($reply_set->facets->facet as $facet) {
45
+ $helper = new AfsFacetHelper($facet, $query, $config);
46
+ $facets[$helper->get_id()] = $helper;
47
+ }
48
+ }
49
+ $facet_mgr = $query->get_facet_manager();
50
+ if ($facet_mgr->has_facets())
51
+ sort_array_by_key(array_keys($facet_mgr->get_facets()), $facets);
52
+ $this->facets = array_values($facets); // preserve compatibility
53
+ }
54
+
55
+ protected function initialize_cluster($reply_set, $query, $config)
56
+ {
57
+ if (property_exists($reply_set, 'content') && property_exists($reply_set->content, 'cluster')) {
58
+ $clustering_id = $this->get_meta()->get_cluster_id();
59
+ $facet_helper = AfsFacetHelperRetriever::get_helper($clustering_id, $this->facets);
60
+ $this->update_meta($facet_helper);
61
+ foreach ($reply_set->content->cluster as $cluster) {
62
+ $helper = new AfsClusterHelper($cluster, $this->get_meta(), $facet_helper, $query, $config);
63
+ $this->clusters[$helper->get_id()] = $helper;
64
+ }
65
+ }
66
+ }
67
+
68
+ protected function update_meta($facet_helper)
69
+ {
70
+ if (! is_null($facet_helper))
71
+ $this->get_meta()->set_cluster_label($facet_helper->get_label());
72
+ }
73
+
74
+ protected function initialize_pager($reply_set, $query, $config)
75
+ {
76
+ if (property_exists($reply_set, 'pager'))
77
+ $this->pager = new AfsPagerHelper($reply_set->pager, $this->meta, $query, $config);
78
+ }
79
+
80
+
81
+ /** @brief Check whether facets are defined.
82
+ * @return @c True when at least one facet is defined, @c false otherwise.
83
+ */
84
+ public function has_facet()
85
+ {
86
+ return ! empty($this->facets);
87
+ }
88
+ /** @brief List of facets.
89
+ * @return facets.
90
+ */
91
+ public function get_facets()
92
+ {
93
+ return $this->facets;
94
+ }
95
+
96
+ /** @brief Check whether clusters are defined.
97
+ * @return @c True when at least one cluster is defined, @c false otherwise.
98
+ */
99
+ public function has_cluster()
100
+ {
101
+ return ! empty($this->clusters);
102
+ }
103
+ /** @brief Retrieves all cluster replies.
104
+ * @return cluster replies.
105
+ */
106
+ public function get_clusters()
107
+ {
108
+ return $this->clusters;
109
+ }
110
+ /** @brief Retrieves replies from all clusters.
111
+ *
112
+ * Replies from all cluster are merged preserving cluster result order.
113
+ * @return Merged replies from all clusters.
114
+ */
115
+ public function get_cluster_replies()
116
+ {
117
+ $replies = array();
118
+ foreach ($this->clusters as $cluster)
119
+ $replies = array_merge($replies, $cluster->get_replies());
120
+ return $replies;
121
+ }
122
+ /** @brief Retrieves replies from all clusters and overspill replies.
123
+ *
124
+ * Replies from all clusters are merged with overspill replies. This is a
125
+ * shortcut for a call of both following methods: AfsReplysetHelper::get_cluster_replies
126
+ * and AfsReplysetHelper::get_replies.
127
+ *
128
+ * @return merged replies.
129
+ */
130
+ public function get_all_replies()
131
+ {
132
+ return array_merge($this->get_cluster_replies(), $this->get_replies());
133
+ }
134
+
135
+ /** @brief Checks whether pager is defined.
136
+ * @return @c True when pager exists, @c false otherwise.
137
+ */
138
+ public function has_pager()
139
+ {
140
+ if (is_null($this->pager))
141
+ return false;
142
+ else
143
+ return true;
144
+ }
145
+ /** @brief Retrieves pager object.
146
+ * @return instance of @a AfsPagerHelper.
147
+ */
148
+ public function get_pager()
149
+ {
150
+ return $this->pager;
151
+ }
152
+
153
+ /** @brief Retrieve replyset as array.
154
+ *
155
+ * All data are store in <tt>key => value</tt> format:
156
+ * @li @c meta: array of meta data (@a AfsMetaHelper::format),
157
+ * @li @c nb_replies: number of replies on the current page.
158
+ * @li @c replies: standard or Promote reply.
159
+ * @li @c facets: array of facets (@a AfsFacetHelper::format),
160
+ * @li @c pager: array of pages (@a AfsPagerHelper::format),
161
+ *
162
+ * @return array filled with key and values.
163
+ */
164
+ public function format()
165
+ {
166
+ $result = parent::format();
167
+ if ($this->has_facet()) {
168
+ $result['facets'] = array();
169
+ foreach ($this->get_facets() as $facet_id => $facet) {
170
+ $result['facets'][$facet_id] = $facet->format();
171
+ }
172
+ }
173
+ if ($this->has_cluster()) {
174
+ $formatted_cluster = array();
175
+ foreach ($this->get_clusters() as $cluster_id => $cluster)
176
+ $formatted_cluster[$cluster_id] = $cluster->format();
177
+ $result['clusters'] = $formatted_cluster;
178
+ }
179
+ if ($this->has_pager())
180
+ $result['pager'] = $this->get_pager()->format();
181
+ return $result;
182
+ }
183
+ }
184
+
185
+
lib/antidot/AFS/SEARCH/afs_response_exception.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_exception.php';
3
+
4
+ /** @brief Base class for all exceptions related to AFS search engine response. */
5
+ abstract class AfsResponseException extends AfsBaseException
6
+ { }
7
+
8
+ /** @brief Exception raised when trying to access object whereas no reply was available. */
9
+ class AfsNoReplyException extends AfsResponseException
10
+ { }
lib/antidot/AFS/SEARCH/afs_response_helper.php ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'AFS/afs_response_helper_base.php';
3
+ require_once 'AFS/SEARCH/afs_header_helper.php';
4
+ require_once 'AFS/SEARCH/afs_replyset_helper.php';
5
+ require_once 'AFS/SEARCH/afs_promote_replyset_helper.php';
6
+ require_once 'AFS/SEARCH/afs_spellcheck_helper.php';
7
+ require_once 'AFS/SEARCH/afs_concept_helper.php';
8
+ require_once 'AFS/SEARCH/afs_producer.php';
9
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
10
+ require_once 'AFS/SEARCH/afs_response_exception.php';
11
+ require_once 'COMMON/afs_helper_format.php';
12
+
13
+ /** @brief Main helper for AFS search reply.
14
+ *
15
+ * This helper is intended to be initiliazed with the reply provided by @a
16
+ * AfsSearchQueryManager::send. It allows to manage replies of one of the
17
+ * available replysets, including facets and pager. Connection and query errors
18
+ * are managed in a uniform way to simplify integration.
19
+ */
20
+ class AfsResponseHelper extends AfsResponseHelperBase
21
+ {
22
+ private $config = null;
23
+ private $has_reply = false;
24
+ private $header = null;
25
+ private $replysets = array();
26
+ private $spellcheck_mgr = null;
27
+ private $promote = null;
28
+ private $concepts = null;
29
+
30
+ /** @brief Construct new response helper instance.
31
+ *
32
+ * @param $response [in] result from @a AfsSearchQueryManager::send call.
33
+ * @param $query [in] query which has produced current reply.
34
+ * @param $config [in] helper configuration object.
35
+ *
36
+ * @exception InvalidArgumentException when one of the parameters is
37
+ * invalid.
38
+ */
39
+ public function __construct($response, AfsQuery $query,
40
+ AfsHelperConfiguration $config)
41
+ {
42
+ $query = $query->auto_set_from();
43
+ $this->config = $config;
44
+ $this->header = new AfsHeaderHelper($response->header);
45
+
46
+ if (property_exists($response, 'replySet')) {
47
+ $this->has_reply = true;
48
+ $us_mgr = $config->get_user_session_manager();
49
+ $us_mgr->set_user_id($this->header->get_user_id());
50
+ $us_mgr->set_session_id($this->header->get_session_id());
51
+
52
+ $this->spellcheck_mgr = new AfsSpellcheckManager($query, $config);
53
+ $this->concepts = new AfsConceptManager();
54
+
55
+ $this->initialize_replysets($response->replySet, $query, $config);
56
+ } elseif ($this->header->in_error()) {
57
+ $this->set_error_msg($this->header->get_error());
58
+ }
59
+ }
60
+
61
+ private function initialize_replysets($replysets, AfsQuery $query,
62
+ AfsHelperConfiguration $config)
63
+ {
64
+ foreach ($replysets as $replyset) {
65
+ if (property_exists($replyset, 'meta')
66
+ && property_exists($replyset->meta, 'producer')) {
67
+ $producer = $replyset->meta->producer;
68
+ if ($producer == AfsProducer::SEARCH) {
69
+ if ('Promote' == $replyset->meta->uri) {
70
+ $this->promote = new AfsPromoteReplysetHelper($replyset, $config);
71
+ } else {
72
+ $replyset_helper = new AfsReplysetHelper($replyset, $query, $config);
73
+ $feed = $replyset_helper->get_meta()->get_feed();
74
+ if ($config->is_array_format()) {
75
+ $this->replysets[$feed] = $replyset_helper->format();
76
+ } else {
77
+ $this->replysets[$feed] = $replyset_helper;
78
+ }
79
+ }
80
+ } elseif ($producer == AfsProducer::SPELLCHECK) {
81
+ $this->spellcheck_mgr->add_spellcheck($replyset);
82
+ } elseif ($producer == AfsProducer::CONCEPT) {
83
+ $this->concepts->add_concept($replyset);
84
+ }
85
+ }
86
+ }
87
+ if ($config->is_array_format()) {
88
+ if (! is_null($this->promote)) {
89
+ $this->promote = $this->promote->format();
90
+ }
91
+ }
92
+ }
93
+
94
+ /** @name Replies
95
+ * @{ */
96
+
97
+ /** @brief Check whether reponse has a reply.
98
+ * @return true when a reply is available, false otherwise.
99
+ */
100
+ public function has_replyset()
101
+ {
102
+ return $this->has_reply && (! empty($this->replysets));
103
+ }
104
+ /** @brief Retrieves all replysets.
105
+ * @return all defined reply sets.
106
+ */
107
+ public function get_replysets()
108
+ {
109
+ $this->check_reply('replysets');
110
+ return $this->replysets;
111
+ }
112
+ /** @brief Retrieves replyset from the @a response.
113
+ *
114
+ * @param $feed [in] name of the feed to filter on
115
+ * (default: null -> retrieves first replyset).
116
+ * @return @a AfsReplysetHelper or formatted replyset depending on @a format
117
+ * parameter.
118
+ */
119
+ public function get_replyset($feed=null)
120
+ {
121
+ $this->check_reply('replysets');
122
+ if (is_null($feed)) {
123
+ if ($this->has_replyset()) {
124
+ return reset($this->replysets);
125
+ }
126
+ } elseif (array_key_exists($feed, $this->replysets)) {
127
+ return $this->replysets[$feed];
128
+ }
129
+ throw new OutOfBoundsException('No reply set '
130
+ . (is_null($feed) ? '' : 'named \'' . $feed . '\' '). 'available');
131
+ }
132
+ /** @} */
133
+
134
+ /** @name Spellcheck
135
+ * @{ */
136
+
137
+ /** @brief Checks whether at least one spellcheck is defined.
138
+ * @return @c True when one or more spellchecks are defined, @c false
139
+ * otherwise.
140
+ */
141
+ public function has_spellcheck()
142
+ {
143
+ return $this->has_reply and (!is_null($this->spellcheck_mgr))
144
+ and $this->spellcheck_mgr->has_spellcheck();
145
+ }
146
+ /** @brief Retrieves spellchecks from the @a response.
147
+ * @return list of @a AfsSpellcheckHelper or formatted spellcheck depending
148
+ * on parameter initialization.
149
+ */
150
+ public function get_spellchecks()
151
+ {
152
+ $this->check_reply('spellcheck_mgr');
153
+ return $this->spellcheck_mgr->get_spellchecks();
154
+ }
155
+ /** @brief Retrieves default, available or specified spellcheck.
156
+ * @param $feed [in] Feed for which spellcheck should be retrieved. Default
157
+ * value is <tt>null</tt>, two cases can occur:
158
+ * - there is only one spellcheck reply which is returned,
159
+ * - there is multiple spellcheck replies and one corresponds to
160
+ * default spellcheck reply (AFS_DEFAULT_SPELLCHECK); this one is
161
+ * returned.
162
+ * @return spellcheck helper (see @a AfsSpellcheckHelper).
163
+ * @exception OutOfBoundsException when required feed has not produced any
164
+ * spellcheck reply.
165
+ */
166
+ public function get_spellcheck($feed=null)
167
+ {
168
+ $this->check_reply('spellcheck_mgr');
169
+ return $this->spellcheck_mgr->get_spellcheck($feed);
170
+ }
171
+ /** @} */
172
+
173
+ /** @name Promote
174
+ * @{ */
175
+
176
+ /** @brief Checks whether at least one promote is available.
177
+ * @return @c True when one or more promotes is available, @c false otherwise.
178
+ */
179
+ public function has_promote()
180
+ {
181
+ return $this->has_reply and (! is_null($this->promote))
182
+ and $this->promote->has_reply();
183
+ }
184
+ /** @brief Retrieves all promote helpers.
185
+ * @return promote replies.
186
+ */
187
+ public function get_promotes()
188
+ {
189
+ $this->check_reply('promote');
190
+ return $this->promote->get_replies();
191
+ }
192
+ /** @brief Retrieves promote replyset helper.
193
+ *
194
+ * This allows to retrieve metadata of the promote.
195
+ * For more details see @a AfsPromoteReplysetHelper.
196
+ *
197
+ * @return promote replyset helper.
198
+ */
199
+ public function get_promote()
200
+ {
201
+ $this->check_reply('promote');
202
+ return $this->promote;
203
+ }
204
+ /** @} */
205
+
206
+ /** @name Concept
207
+ * @{ */
208
+
209
+ /** @brief Checks whether at least one concept is available.
210
+ * @return @c True when one or more concepts is available, @c false otherwise.
211
+ */
212
+ public function has_concept()
213
+ {
214
+ return $this->has_reply and (! is_null($this->concepts))
215
+ and $this->concepts->has_concept();
216
+ }
217
+ /** @brief Retrieves all concept helpers.
218
+ * @return concept replies.
219
+ */
220
+ public function get_concepts()
221
+ {
222
+ $this->check_reply('concepts');
223
+ return$this->concepts->get_concepts();
224
+ }
225
+ /** @brief Retrieves default or specified concept.
226
+ *
227
+ * For more details see @a AfsConceptManager::get_concept.
228
+ *
229
+ * @param $feed [in] Concept generated by this feed should be retrieved
230
+ * (default: default concept is retrieved).
231
+ *
232
+ * @return appropriate concept.
233
+ */
234
+ public function get_concept($feed=null)
235
+ {
236
+ $this->check_reply('concepts');
237
+ return $this->concepts->get_concept($feed);
238
+ }
239
+ /** @} */
240
+
241
+ /** @name Miscellaneaous
242
+ * @{ */
243
+
244
+ /** @brief Retrieves AFS search engine computation duration.
245
+ *
246
+ * Individual durations are available for each replyset, see
247
+ * @a AfsReplysetHelper for more details.
248
+ *
249
+ * @return Computation duration in milliseconds.
250
+ */
251
+ public function get_duration()
252
+ {
253
+ return $this->header->get_duration();
254
+ }
255
+
256
+ /** @brief Retrieves reply data as array.
257
+ *
258
+ * This method is intended for internal use only.
259
+ *
260
+ * All data are stored in <tt>key => value</tt> format:
261
+ * @li @c replysets: replies per feed,
262
+ * @li @c spellchecks: spellcheck replies per feed.
263
+ *
264
+ * @return array filled with key and values.
265
+ */
266
+ public function format()
267
+ {
268
+ if ($this->in_error()) {
269
+ return array('error' => $this->get_error_msg());
270
+ } else {
271
+ $result = array('duration' => $this->get_duration());
272
+ if ($this->has_replyset())
273
+ $result['replysets'] = $this->get_replysets();
274
+ if ($this->spellcheck_mgr->has_spellcheck())
275
+ $result['spellchecks'] = $this->spellcheck_mgr->format();
276
+ return $result;
277
+ }
278
+ }
279
+
280
+
281
+ private function check_reply($param=null)
282
+ {
283
+ if (! $this->has_reply)
284
+ throw new AfsNoReplyException('No reply available!');
285
+ if (! is_null($param) && is_null($this->$param))
286
+ throw new AfsNoReplyException('No ' . $param . ' reply available!');
287
+ }
288
+
289
+ /** @brief Retrieve query parameter stored in header
290
+ * @input $key : Name of the parameter
291
+ * @return value of the parameter
292
+ *
293
+ */
294
+ public function get_query_parameter($key)
295
+ {
296
+ return $this->header->get_query_parameter($key);
297
+ }
298
+ /** @} */
299
+
300
+ }
301
+
302
+
lib/antidot/AFS/SEARCH/afs_search.php ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once 'COMMON/afs_service.php';
3
+ require_once 'AFS/SEARCH/afs_search_connector.php';
4
+ require_once 'AFS/SEARCH/afs_helper_configuration.php';
5
+ require_once 'AFS/SEARCH/afs_query_coder.php';
6
+ require_once 'AFS/SEARCH/afs_facet_manager.php';
7
+ require_once 'AFS/SEARCH/afs_search_query_manager.php';
8
+ require_once 'AFS/SEARCH/afs_response_helper.php';
9
+ require_once 'AFS/SEARCH/afs_query.php';
10
+
11
+ /** @brief Facade for AFS search engine query. */
12
+ class AfsSearch
13
+ {
14
+ private $service = null;
15
+ private $connector = null;
16
+ private $config = null;
17
+ private $query = null;
18
+
19
+ /** @brief Constructs AFS search facade.
20
+ *
21
+ * @param $host [in] server hosting the required service.
22
+ * @param $id [in] identifier of the desired service.
23
+ * @param $status [in] status of the desired service (see @ref AfsServiceStatus).
24
+ */
25
+ public function __construct($host, $id, $status=AfsServiceStatus::STABLE, $curlConnector=null)
26
+ {
27
+ $this->service = new AfsService($id, $status);
28
+ $this->connector = new AfsSearchConnector($host, $this->service, AFS_SCHEME_HTTP, $curlConnector);
29
+ $this->config = new AfsHelperConfiguration();
30
+ $this->query = new AfsQuery();
31
+ }
32
+
33
+ /** @name Query coder
34
+ *
35
+ * This coder is useful only when you want AFS helpers to generate
36
+ * appropriate links.
37
+ * @{ */
38
+
39
+ /** @brief Defines new query coder.
40
+ * @param $query_coder [in] query coder used to encode query into URL format
41
+ * and decode query from URL parameters.
42
+ */
43
+ public function set_query_coder(AfsQueryCoderInterface $query_coder)
44
+ {
45
+ $this->config->set_query_coder($query_coder);
46
+ }
47
+ /** @brief Builds query using URL parameters.
48
+ *
49
+ * Use defined query coder or instanciate default query coder when none has
50
+ * yet been defined.
51
+ *
52
+ * @return the built query.
53
+ */
54
+ public function build_query_from_url_parameters()
55
+ {
56
+ if (! $this->config->has_query_coder()) {
57
+ $this->config->set_query_coder(new AfsQueryCoder());
58
+ }
59
+ $this->query = $this->config->get_query_coder()->build_query($_GET);
60
+ return $this->query;
61
+ }
62
+ /** @} */
63
+
64
+ /** @name Query management
65
+ *
66
+ * Remember that AfsQuery objects are immutable.
67
+ * @{ */
68
+
69
+ /** @brief Retrieves current query.
70
+ * @return AFS search query.
71
+ */
72
+ public function get_query()
73
+ {
74
+ return $this->query;
75
+ }
76
+ /** @brief Defines new query.
77
+ * @param $query [in] New