Doofinder_Feed - Version 1.8.0

Version Notes

Big update: Doofinder PHP client library.

Download this release

Release Info

Developer Carlos Escribano Rey
Extension Doofinder_Feed
Version 1.8.0
Comparing to
See all releases


Code changes from version 1.7.2 to 1.8.0

Files changed (78) hide show
  1. app/code/community/Doofinder/Feed/Block/Adminhtml/Log/View.php +1 -1
  2. app/code/community/Doofinder/Feed/Block/Adminhtml/Map/Additional.php +1 -1
  3. app/code/community/Doofinder/Feed/Block/Integration.php +1 -1
  4. app/code/community/Doofinder/Feed/Block/Settings/Buttons/Generate.php +1 -1
  5. app/code/community/Doofinder/Feed/Block/Settings/Buttons/ViewLog.php +1 -1
  6. app/code/community/Doofinder/Feed/Block/Settings/Panel/Crondescription.php +1 -1
  7. app/code/community/Doofinder/Feed/Block/Settings/Panel/Datetime.php +1 -1
  8. app/code/community/Doofinder/Feed/Block/Settings/Panel/Description.php +1 -1
  9. app/code/community/Doofinder/Feed/Block/Settings/Panel/File.php +1 -1
  10. app/code/community/Doofinder/Feed/Block/Settings/Panel/Layerdescription.php +1 -1
  11. app/code/community/Doofinder/Feed/Block/Settings/Panel/Message.php +1 -1
  12. app/code/community/Doofinder/Feed/Helper/Data.php +2 -2
  13. app/code/community/Doofinder/Feed/Helper/Log.php +2 -2
  14. app/code/community/Doofinder/Feed/Helper/Search.php +103 -15
  15. app/code/community/Doofinder/Feed/Helper/Tax.php +2 -2
  16. app/code/community/Doofinder/Feed/Model/Adminhtml/System/Config/Backend/Cron.php +1 -1
  17. app/code/community/Doofinder/Feed/Model/Config.php +2 -2
  18. app/code/community/Doofinder/Feed/Model/Cron.php +1 -1
  19. app/code/community/Doofinder/Feed/Model/Generator.php +2 -2
  20. app/code/community/Doofinder/Feed/Model/Log.php +1 -1
  21. app/code/community/Doofinder/Feed/Model/Map/Product/Abstract.php +4 -6
  22. app/code/community/Doofinder/Feed/Model/Map/Product/Associated.php +2 -2
  23. app/code/community/Doofinder/Feed/Model/Map/Product/Bundle.php +2 -2
  24. app/code/community/Doofinder/Feed/Model/Map/Product/Configurable.php +2 -2
  25. app/code/community/Doofinder/Feed/Model/Map/Product/Downloadable.php +2 -2
  26. app/code/community/Doofinder/Feed/Model/Map/Product/Grouped.php +2 -2
  27. app/code/community/Doofinder/Feed/Model/Map/Product/Simple.php +2 -2
  28. app/code/community/Doofinder/Feed/Model/Map/Product/Virtual.php +2 -2
  29. app/code/community/Doofinder/Feed/Model/Mysql4/Cron.php +1 -1
  30. app/code/community/Doofinder/Feed/Model/Mysql4/Cron/Collection.php +1 -1
  31. app/code/community/Doofinder/Feed/Model/Mysql4/Log.php +1 -1
  32. app/code/community/Doofinder/Feed/Model/Mysql4/Log/Collection.php +1 -1
  33. app/code/community/Doofinder/Feed/Model/Observers/Feed.php +2 -16
  34. app/code/community/Doofinder/Feed/Model/Observers/Logs.php +1 -1
  35. app/code/community/Doofinder/Feed/Model/Observers/Schedule.php +1 -1
  36. app/code/community/Doofinder/Feed/Model/Resource/Mysql4/Setup.php +1 -1
  37. app/code/community/Doofinder/Feed/Model/System/Config/Backend/Map/Additional.php +1 -1
  38. app/code/community/Doofinder/Feed/Model/System/Config/Reset.php +1 -1
  39. app/code/community/Doofinder/Feed/Model/System/Config/Source/Product/Attributes.php +1 -1
  40. app/code/community/Doofinder/Feed/Model/Tools.php +2 -2
  41. app/code/community/Doofinder/Feed/controllers/DoofinderFeedFeedController.php +1 -1
  42. app/code/community/Doofinder/Feed/controllers/DoofinderFeedLogController.php +1 -1
  43. app/code/community/Doofinder/Feed/controllers/FeedController.php +2 -2
  44. app/code/community/Doofinder/Feed/controllers/IndexController.php +2 -2
  45. app/code/community/Doofinder/Feed/etc/config.xml +1 -1
  46. lib/Doofinder/doofinder_api.php +0 -804
  47. lib/Doofinder/doofinder_management_api.php +0 -408
  48. lib/Doofinder/errors.php +0 -48
  49. lib/php-doofinder/CHANGELOG.txt +38 -0
  50. lib/php-doofinder/README.md +599 -0
  51. lib/php-doofinder/autoload.php +21 -0
  52. lib/php-doofinder/composer.json +27 -0
  53. lib/php-doofinder/phpunit.xml +8 -0
  54. lib/php-doofinder/src/Management/AggregatesIterator.php +59 -0
  55. lib/php-doofinder/src/Management/Client.php +144 -0
  56. lib/php-doofinder/src/Management/Errors/BadRequest.php +5 -0
  57. lib/php-doofinder/src/Management/Errors/NotAllowed.php +5 -0
  58. lib/php-doofinder/src/Management/Errors/NotFound.php +4 -0
  59. lib/php-doofinder/src/Management/Errors/NotProcessedResponse.php +5 -0
  60. lib/php-doofinder/src/Management/Errors/QuotaExhausted.php +5 -0
  61. lib/php-doofinder/src/Management/Errors/ThrottledResponse.php +5 -0
  62. lib/php-doofinder/src/Management/Errors/Utils.php +53 -0
  63. lib/php-doofinder/src/Management/Errors/WrongResponse.php +5 -0
  64. lib/php-doofinder/src/Management/ItemsResultSet.php +60 -0
  65. lib/php-doofinder/src/Management/ScrollIterator.php +49 -0
  66. lib/php-doofinder/src/Management/SearchEngine.php +269 -0
  67. lib/php-doofinder/src/Management/TopTermsIterator.php +54 -0
  68. lib/php-doofinder/src/Search/Client.php +581 -0
  69. lib/php-doofinder/src/Search/Error.php +10 -0
  70. lib/php-doofinder/src/Search/Results.php +220 -0
  71. lib/php-doofinder/src/Test/Management/AggregatesIteratorTest.php +77 -0
  72. lib/php-doofinder/src/Test/Management/ClientTest.php +94 -0
  73. lib/php-doofinder/src/Test/Management/ScrollIteratorTest.php +67 -0
  74. lib/php-doofinder/src/Test/Management/SearchEngineTest.php +355 -0
  75. lib/php-doofinder/src/Test/Management/TopTermsIteratorTest.php +96 -0
  76. lib/php-doofinder/src/Test/Search/ClientTest.php +381 -0
  77. lib/php-doofinder/src/Test/Search/ResultsTests.php +94 -0
  78. package.xml +6 -6
app/code/community/Doofinder/Feed/Block/Adminhtml/Log/View.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Adminhtml_Log_View extends Mage_Adminhtml_Block_Widget_Grid
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Adminhtml_Log_View extends Mage_Adminhtml_Block_Widget_Grid
app/code/community/Doofinder/Feed/Block/Adminhtml/Map/Additional.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Adminhtml_Map_Additional extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Adminhtml_Map_Additional extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Block/Integration.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Integration extends Mage_Core_Block_Abstract
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Integration extends Mage_Core_Block_Abstract
app/code/community/Doofinder/Feed/Block/Settings/Buttons/Generate.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Buttons_Generate extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Buttons_Generate extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Block/Settings/Buttons/ViewLog.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Buttons_ViewLog extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Buttons_ViewLog extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Block/Settings/Panel/Crondescription.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_CronDescription extends Doofinder_Feed_Block_Settings_Panel_Description
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_CronDescription extends Doofinder_Feed_Block_Settings_Panel_Description
app/code/community/Doofinder/Feed/Block/Settings/Panel/Datetime.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_Datetime extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_Datetime extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Block/Settings/Panel/Description.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_Description extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_Description extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Block/Settings/Panel/File.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_File extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_File extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Block/Settings/Panel/Layerdescription.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_LayerDescription extends Doofinder_Feed_Block_Settings_Panel_Description
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_LayerDescription extends Doofinder_Feed_Block_Settings_Panel_Description
app/code/community/Doofinder/Feed/Block/Settings/Panel/Message.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_Message extends Mage_Adminhtml_Block_System_Config_Form_Field
6
  /**
7
  * @category blocks
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Block_Settings_Panel_Message extends Mage_Adminhtml_Block_System_Config_Form_Field
app/code/community/Doofinder/Feed/Helper/Data.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Helpers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Data helper for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Helper_Data extends Mage_Core_Helper_Abstract
6
  /**
7
  * @category Helpers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Data helper for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Helper_Data extends Mage_Core_Helper_Abstract
app/code/community/Doofinder/Feed/Helper/Log.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Helpers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Log helper for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Helper_Log extends Mage_Core_Helper_Abstract
6
  /**
7
  * @category Helpers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Log helper for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Helper_Log extends Mage_Core_Helper_Abstract
app/code/community/Doofinder/Feed/Helper/Search.php CHANGED
@@ -1,5 +1,4 @@
1
  <?php
2
- require_once(Mage::getBaseDir('lib') . DS. 'Doofinder' . DS .'doofinder_api.php');
3
 
4
  class Doofinder_Feed_Helper_Search extends Mage_Core_Helper_Abstract
5
  {
@@ -9,6 +8,43 @@ class Doofinder_Feed_Helper_Search extends Mage_Core_Helper_Abstract
9
  protected $_lastSearch = null;
10
  protected $_lastResults = null;
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  /**
13
  * Perform a doofinder search on given key.
14
  *
@@ -20,31 +56,31 @@ class Doofinder_Feed_Helper_Search extends Mage_Core_Helper_Abstract
20
  */
21
  public function performDoofinderSearch($queryText)
22
  {
23
- $hashId = Mage::getStoreConfig('doofinder_search/internal_settings/hash_id', Mage::app()->getStore());
24
- $apiKey = Mage::getStoreConfig('doofinder_search/internal_settings/api_key', Mage::app()->getStore());
25
  $limit = Mage::getStoreConfig('doofinder_search/internal_settings/request_limit', Mage::app()->getStore());
26
- $ids = false;
27
 
28
- $df = new DoofinderApi($hashId, $apiKey);
29
- $dfResults = $df->query($queryText, null, array('rpp' => $limit, 'transformer' => 'onlyid', 'filter' => array()));
 
30
 
31
  // Store objects
32
- $this->_lastSearch = $df;
33
- $this->_lastResults = $dfResults;
34
 
35
- return $this->retrieveIds($dfResults);
36
  }
37
 
38
  /**
39
  * Retrieve ids from Doofinder Results
40
  *
41
- * @param DoofinderResults $dfResults
42
  * @return array
43
  */
44
- protected function retrieveIds(DoofinderResults $dfResults)
45
  {
46
- $ids = array();
47
- foreach($dfResults->getResults() as $result) {
48
  $ids[] = $result['id'];
49
  }
50
 
@@ -61,8 +97,8 @@ class Doofinder_Feed_Helper_Search extends Mage_Core_Helper_Abstract
61
  $limit = Mage::getStoreConfig('doofinder_search/internal_settings/total_limit', Mage::app()->getStore());
62
  $ids = $this->retrieveIds($this->_lastResults);
63
 
64
- while (count($ids) < $limit && ($dfResults = $this->_lastSearch->nextPage())) {
65
- $ids = array_merge($ids, $this->retrieveIds($dfResults));
66
  }
67
 
68
  return $ids;
@@ -77,4 +113,56 @@ class Doofinder_Feed_Helper_Search extends Mage_Core_Helper_Abstract
77
  {
78
  return $this->_lastResults->getProperty('total');
79
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
1
  <?php
 
2
 
3
  class Doofinder_Feed_Helper_Search extends Mage_Core_Helper_Abstract
4
  {
8
  protected $_lastSearch = null;
9
  protected $_lastResults = null;
10
 
11
+ /**
12
+ * @var \Doofinder\Api\Management\SearchEngine[]
13
+ */
14
+ protected $_searchEngines = null;
15
+
16
+ /**
17
+ * Load Doofinder PHP library
18
+ */
19
+ protected function loadDoofinderLibrary()
20
+ {
21
+ spl_autoload_register(array($this, 'autoload'), true, true);
22
+ }
23
+
24
+ /**
25
+ * Get api key
26
+ *
27
+ * @param string $storeCode
28
+ * @return string
29
+ */
30
+ protected function getApiKey($storeCode = null)
31
+ {
32
+ $storeCode = $storeCode === null ? Mage::app()->getStore() : $storeCode;
33
+ return Mage::getStoreConfig('doofinder_search/internal_settings/api_key', $storeCode);
34
+ }
35
+
36
+ /**
37
+ * Get hash id
38
+ *
39
+ * @param string $storeCode
40
+ * @return string
41
+ */
42
+ protected function getHashId($storeCode = null)
43
+ {
44
+ $storeCode = $storeCode === null ? Mage::app()->getStore() : $storeCode;
45
+ return Mage::getStoreConfig('doofinder_search/internal_settings/hash_id', $storeCode);
46
+ }
47
+
48
  /**
49
  * Perform a doofinder search on given key.
50
  *
56
  */
57
  public function performDoofinderSearch($queryText)
58
  {
59
+ $hashId = $this->getHashId();
60
+ $apiKey = $this->getApiKey();
61
  $limit = Mage::getStoreConfig('doofinder_search/internal_settings/request_limit', Mage::app()->getStore());
 
62
 
63
+ $this->loadDoofinderLibrary();
64
+ $client = new \Doofinder\Api\Search\Client($hashId, $apiKey);
65
+ $results = $client->query($queryText, null, ['rpp' => $limit, 'transformer' => 'onlyid', 'filter' => []]);
66
 
67
  // Store objects
68
+ $this->_lastSearch = $client;
69
+ $this->_lastResults = $results;
70
 
71
+ return $this->retrieveIds($results);
72
  }
73
 
74
  /**
75
  * Retrieve ids from Doofinder Results
76
  *
77
+ * @param \Doofinder\Api\Search\Results $results
78
  * @return array
79
  */
80
+ protected function retrieveIds(\Doofinder\Api\Search\Results $results)
81
  {
82
+ $ids = [];
83
+ foreach ($results->getResults() as $result) {
84
  $ids[] = $result['id'];
85
  }
86
 
97
  $limit = Mage::getStoreConfig('doofinder_search/internal_settings/total_limit', Mage::app()->getStore());
98
  $ids = $this->retrieveIds($this->_lastResults);
99
 
100
+ while (count($ids) < $limit && ($results = $this->_lastSearch->nextPage())) {
101
+ $ids = array_merge($ids, $this->retrieveIds($results));
102
  }
103
 
104
  return $ids;
113
  {
114
  return $this->_lastResults->getProperty('total');
115
  }
116
+
117
+ /**
118
+ * Get Doofinder Search Engine
119
+ *
120
+ * @param string $storeCode
121
+ * @return \Doofinder\Api\Management\SearchEngine
122
+ */
123
+ public function getDoofinderSearchEngine($storeCode)
124
+ {
125
+ if ($this->_searchEngines === null) {
126
+ $this->_searchEngines = [];
127
+
128
+ // Create DoofinderManagementApi instance
129
+ $this->loadDoofinderLibrary();
130
+ $doofinderManagementApi = new \Doofinder\Api\Management\Client($this->getApiKey($storeCode));
131
+
132
+ foreach ($doofinderManagementApi->getSearchEngines() as $searchEngine) {
133
+ $this->_searchEngines[$searchEngine->hashid] = $searchEngine;
134
+ }
135
+ }
136
+
137
+ // Prepare SearchEngine instance
138
+ $hashId = $this->getHashId($storeCode);
139
+ if (!empty($this->_searchEngines[$hashId])) {
140
+ return $this->_searchEngines[$hashId];
141
+ }
142
+
143
+ return false;
144
+ }
145
+
146
+ /**
147
+ * Autoloader for 'php-doofinder' library
148
+ */
149
+ protected function autoload($className)
150
+ {
151
+ $libraryPrefix = 'Doofinder\\Api\\';
152
+ $libraryDirectory = Mage::getBaseDir('lib') . DS. 'php-doofinder' . DS . 'src' . DS;
153
+
154
+ $len = strlen($libraryPrefix);
155
+
156
+ // Binary safe comparison of $len first characters
157
+ if (strncmp($libraryPrefix, $className, $len) !== 0) {
158
+ return;
159
+ }
160
+
161
+ $classPath = str_replace('\\', '/', substr($className, $len)) . '.php';
162
+ $file = $libraryDirectory . $classPath;
163
+
164
+ if (file_exists($file)) {
165
+ require $file;
166
+ }
167
+ }
168
  }
app/code/community/Doofinder/Feed/Helper/Tax.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Helpers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Tax helper for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Helper_Tax extends Mage_Tax_Helper_Data
6
  /**
7
  * @category Helpers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Tax helper for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Helper_Tax extends Mage_Tax_Helper_Data
app/code/community/Doofinder/Feed/Model/Adminhtml/System/Config/Backend/Cron.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Adminhtml_System_Config_Backend_Cron extends Mage_Core_Model_Config_Data {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Adminhtml_System_Config_Backend_Cron extends Mage_Core_Model_Config_Data {
app/code/community/Doofinder/Feed/Model/Config.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Config model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Config extends Mage_Core_Model_Config_Data
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Config model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Config extends Mage_Core_Model_Config_Data
app/code/community/Doofinder/Feed/Model/Cron.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Cron extends Mage_Core_Model_Abstract {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Cron extends Mage_Core_Model_Abstract {
app/code/community/Doofinder/Feed/Model/Generator.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Generator model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  if (!defined('DS'))
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Generator model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  if (!defined('DS'))
app/code/community/Doofinder/Feed/Model/Log.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Log extends Mage_Core_Model_Abstract {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Log extends Mage_Core_Model_Abstract {
app/code/community/Doofinder/Feed/Model/Map/Product/Abstract.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Abstract Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Abstract extends Varien_Object
@@ -309,8 +309,7 @@ class Doofinder_Feed_Model_Map_Product_Abstract extends Varien_Object
309
  $stock_status = $defaultVal;
310
  $stock_status = trim(strtolower($stock_status));
311
 
312
- if (false === array_search($stock_status,
313
- $this->getConfig()->getAllowedStockStatuses()))
314
  $stock_status = $this->getConfig()->getOutOfStockStatus();
315
 
316
  $fieldData = $stock_status;
@@ -340,8 +339,7 @@ class Doofinder_Feed_Model_Map_Product_Abstract extends Varien_Object
340
  $defaultVal = isset($map['default_value']) ? $map['default_value'] : "";
341
  $defaultVal = trim(strtolower($defaultVal));
342
 
343
- if (false === array_search($defaultVal,
344
- $this->getConfig()->getAllowedConditions()))
345
  $defaultVal = $this->getConfig()->getConditionNew();
346
 
347
  $fieldData = $defaultVal;
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Abstract Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Abstract extends Varien_Object
309
  $stock_status = $defaultVal;
310
  $stock_status = trim(strtolower($stock_status));
311
 
312
+ if (false === array_search($stock_status, (array) $this->getConfig()->getAllowedStockStatuses()))
 
313
  $stock_status = $this->getConfig()->getOutOfStockStatus();
314
 
315
  $fieldData = $stock_status;
339
  $defaultVal = isset($map['default_value']) ? $map['default_value'] : "";
340
  $defaultVal = trim(strtolower($defaultVal));
341
 
342
+ if (false === array_search($defaultVal, (array) $this->getConfig()->getAllowedConditions()))
 
343
  $defaultVal = $this->getConfig()->getConditionNew();
344
 
345
  $fieldData = $defaultVal;
app/code/community/Doofinder/Feed/Model/Map/Product/Associated.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Associated Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Associated
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Associated Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Associated
app/code/community/Doofinder/Feed/Model/Map/Product/Bundle.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Bundle Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Bundle
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Bundle Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Bundle
app/code/community/Doofinder/Feed/Model/Map/Product/Configurable.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Configurable Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Configurable
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Configurable Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Configurable
app/code/community/Doofinder/Feed/Model/Map/Product/Downloadable.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Downloadable Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Downloadable
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Downloadable Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Downloadable
app/code/community/Doofinder/Feed/Model/Map/Product/Grouped.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Grouped Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Grouped
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Grouped Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Grouped
app/code/community/Doofinder/Feed/Model/Map/Product/Simple.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Simple Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Simple
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Simple Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Simple
app/code/community/Doofinder/Feed/Model/Map/Product/Virtual.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Virtual Product Map Model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Virtual
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Virtual Product Map Model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Map_Product_Virtual
app/code/community/Doofinder/Feed/Model/Mysql4/Cron.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Cron extends Mage_Core_Model_Mysql4_Abstract {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Cron extends Mage_Core_Model_Mysql4_Abstract {
app/code/community/Doofinder/Feed/Model/Mysql4/Cron/Collection.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Cron_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Cron_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract {
app/code/community/Doofinder/Feed/Model/Mysql4/Log.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Log extends Mage_Core_Model_Mysql4_Abstract {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Log extends Mage_Core_Model_Mysql4_Abstract {
app/code/community/Doofinder/Feed/Model/Mysql4/Log/Collection.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Log_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Mysql4_Log_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
app/code/community/Doofinder/Feed/Model/Observers/Feed.php CHANGED
@@ -6,11 +6,9 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
- require_once(Mage::getBaseDir('lib') . DS. 'Doofinder' . DS .'doofinder_management_api.php');
13
-
14
  class Doofinder_Feed_Model_Observers_Feed
15
  {
16
 
@@ -55,18 +53,6 @@ class Doofinder_Feed_Model_Observers_Feed
55
  // Terminate updates where there is no store enabled
56
  if (empty($storeCodes)) return;
57
 
58
- // Get search engines
59
- $apiKey = Mage::getStoreConfig('doofinder_search/internal_settings/api_key', Mage::app()->getStore());
60
- $dma = new DoofinderManagementApi($apiKey);
61
- $searchEngines = $dma->getSearchEngines();
62
-
63
- // Set engines array key as hashid
64
- foreach ($searchEngines as $key => $searchEngine) {
65
- $searchEngines[$searchEngine->hashid] = $searchEngine;
66
- unset($searchEngines[$key]);
67
- }
68
-
69
-
70
  // Loop over all stores and update relevant search engines
71
  foreach ($storeCodes as $storeCode) {
72
  // Set store code
@@ -107,7 +93,7 @@ class Doofinder_Feed_Model_Observers_Feed
107
  continue;
108
  }
109
 
110
- $searchEngine = $searchEngines[$hashId];
111
 
112
  // Check if search engine exists and skip foreach iteration if not.
113
  if (!$searchEngine) {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
 
 
12
  class Doofinder_Feed_Model_Observers_Feed
13
  {
14
 
53
  // Terminate updates where there is no store enabled
54
  if (empty($storeCodes)) return;
55
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  // Loop over all stores and update relevant search engines
57
  foreach ($storeCodes as $storeCode) {
58
  // Set store code
93
  continue;
94
  }
95
 
96
+ $searchEngine = Mage::helper('doofinder_feed/search')->getDoofinderSearchEngine($this->storeCode);
97
 
98
  // Check if search engine exists and skip foreach iteration if not.
99
  if (!$searchEngine) {
app/code/community/Doofinder/Feed/Model/Observers/Logs.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Observers_Logs
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Observers_Logs
app/code/community/Doofinder/Feed/Model/Observers/Schedule.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Observers_Schedule
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Observers_Schedule
app/code/community/Doofinder/Feed/Model/Resource/Mysql4/Setup.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_Resource_Mysql4_Setup extends Mage_Core_Model_Resource_Setup {
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_Resource_Mysql4_Setup extends Mage_Core_Model_Resource_Setup {
app/code/community/Doofinder/Feed/Model/System/Config/Backend/Map/Additional.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_System_Config_Backend_Map_Additional extends Mage_Adminhtml_Model_System_Config_Backend_Serialized
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_System_Config_Backend_Map_Additional extends Mage_Adminhtml_Model_System_Config_Backend_Serialized
app/code/community/Doofinder/Feed/Model/System/Config/Reset.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_System_Config_Reset extends Mage_Core_Model_Config_Data
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_System_Config_Reset extends Mage_Core_Model_Config_Data
app/code/community/Doofinder/Feed/Model/System/Config/Source/Product/Attributes.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_Model_System_Config_Source_Product_Attributes
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_Model_System_Config_Source_Product_Attributes
app/code/community/Doofinder/Feed/Model/Tools.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Tools model for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Tools extends Varien_Object
6
  /**
7
  * @category Models
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Tools model for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_Model_Tools extends Varien_Object
app/code/community/Doofinder/Feed/controllers/DoofinderFeedFeedController.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_DoofinderFeedFeedController extends Mage_Adminhtml_Controller_Action
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_DoofinderFeedFeedController extends Mage_Adminhtml_Controller_Action
app/code/community/Doofinder/Feed/controllers/DoofinderFeedLogController.php CHANGED
@@ -6,7 +6,7 @@
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  class Doofinder_Feed_DoofinderFeedLogController extends Mage_Adminhtml_Controller_Action
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  class Doofinder_Feed_DoofinderFeedLogController extends Mage_Adminhtml_Controller_Action
app/code/community/Doofinder/Feed/controllers/FeedController.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Feed controller for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_FeedController extends Mage_Core_Controller_Front_Action
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Feed controller for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_FeedController extends Mage_Core_Controller_Front_Action
app/code/community/Doofinder/Feed/controllers/IndexController.php CHANGED
@@ -6,13 +6,13 @@
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
- * @version 1.7.2
10
  */
11
 
12
  /**
13
  * Index controller for Doofinder Feed
14
  *
15
- * @version 1.7.2
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_IndexController extends Mage_Core_Controller_Front_Action
6
  /**
7
  * @category controllers
8
  * @package Doofinder_Feed
9
+ * @version 1.8.0
10
  */
11
 
12
  /**
13
  * Index controller for Doofinder Feed
14
  *
15
+ * @version 1.8.0
16
  * @package Doofinder_Feed
17
  */
18
  class Doofinder_Feed_IndexController extends Mage_Core_Controller_Front_Action
app/code/community/Doofinder/Feed/etc/config.xml CHANGED
@@ -3,7 +3,7 @@
3
 
4
  <modules>
5
  <Doofinder_Feed>
6
- <version>1.7.2</version>
7
  </Doofinder_Feed>
8
  </modules>
9
  <global>
3
 
4
  <modules>
5
  <Doofinder_Feed>
6
+ <version>1.8.0</version>
7
  </Doofinder_Feed>
8
  </modules>
9
  <global>
lib/Doofinder/doofinder_api.php DELETED
@@ -1,804 +0,0 @@
1
- <?php
2
- /**
3
- * Author:: JoeZ99 (<jzarate@gmail.com>). all credit to Gilles Devaux (<gilles.devaux@gmail.com>) (https://github.com/flaptor/indextank-php)
4
- *
5
- * License:: Apache License, Version 2.0
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the "License"); you may
8
- * not use this file except in compliance with the License. You may obtain
9
- * a copy of the License at
10
- *
11
- * http://www.apache.org/licenses/LICENSE-2.0
12
- *
13
- * Unless required by applicable law or agreed to in writing, software
14
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
- * License for the specific language governing permissions and limitations
17
- * under the License.
18
- */
19
-
20
-
21
- class DoofinderApi{
22
- /*
23
- * Basic client for an account.
24
- * It needs an API url to be constructed.
25
- * Its only method is to query the doofinder search server
26
- * Returns a DoofinderResults object
27
- */
28
-
29
- const URL_SUFFIX = '-search.doofinder.com';
30
- const DEFAULT_TIMEOUT = 10000;
31
- const DEFAULT_RPP = 10;
32
- const DEFAULT_PARAMS_PREFIX = 'dfParam_';
33
- const DEFAULT_API_VERSION = '4';
34
- const VERSION = '5.2.5';
35
-
36
- private $api_key = null; // user API_KEY
37
- private $hashid = null; // hashid of the doofinder account
38
-
39
- private $apiVersion = null;
40
- private $url = null;
41
- private $results = null;
42
- private $query = null;
43
- private $search_options = array(); // assoc. array with doofinder options to be sent as request parameters
44
-
45
- private $page = 1; // the page of the search results we're at
46
- private $queryName = null; // the name of the last successfull query made
47
- private $lastQuery = null; // the last successfull query made
48
- private $total = null; // total number of results obtained
49
- private $maxScore = null;
50
- private $paramsPrefix = self::DEFAULT_PARAMS_PREFIX;
51
- private $serializationArray = null;
52
- private $queryParameter = 'query'; // the parameter used for querying
53
- private $allowedParameters = array('page', 'rpp', 'timeout', 'types', 'filter', 'query_name', 'transformer'); // request parameters that doofinder handle
54
-
55
- /**
56
- * Constructor. account's hashid and api version set here
57
- *
58
- * @param string $hashid the account's hashid
59
- * @param boolean $fromParams if set, the object is unserialized from GET or POST params
60
- * @param array $init_options. associative array with some options:
61
- * -'prefix' (default: 'dfParam_')=> the prefix to use when serializing.
62
- * -'queryParameter' (default: 'query') => the parameter used for querying
63
- * -'apiVersion' (default: '4')=> the api of the search server to query
64
- * -'restrictedRequest'(default: $_REQUEST): =>restrict request object
65
- * to look for params when unserializing. either 'get' or 'post'
66
- * @throws DoofinderException if $hashid is not a md5 hash or api is no 4, 3.0 or 1.0
67
- */
68
- function __construct($hashid, $api_key, $fromParams=false, $init_options = array()){
69
- $zone_key_array = explode('-', $api_key);
70
-
71
- if(2 === count($zone_key_array)){
72
- $this->api_key = $zone_key_array[1];
73
- $this->zone = $zone_key_array[0];
74
- $this->url = "https://" . $this->zone . self::URL_SUFFIX;
75
- } else {
76
- throw new DoofinderException("API Key is no properly set.");
77
- }
78
-
79
- if(array_key_exists('prefix', $init_options)){
80
- $this->paramsPrefix = $init_options['prefix'];
81
- }
82
-
83
-
84
- $this->allowedParameters = array_map(array($this, 'addprefix'), $this->allowedParameters);
85
-
86
-
87
- if(array_key_exists('queryParameter', $init_options)){
88
- $this->queryParameter = $init_options['queryParameter'];
89
- } else {
90
- $this->queryParameter = $this->paramsPrefix.$this->queryParameter;
91
- }
92
-
93
-
94
- $this->apiVersion = array_key_exists('apiVersion', $init_options) ?
95
- $init_options['apiVersion'] : self::DEFAULT_API_VERSION;
96
- $this->serializationArray = $_REQUEST;
97
- if(array_key_exists('restrictedRequest', $init_options))
98
- {
99
- switch(strtolower($init_options['restrictedRequest'])){
100
- case 'get':
101
- $this->serializationArray = $_GET;
102
- break;
103
- case 'post':
104
- $this->serializationArray = $_POST;
105
- break;
106
- }
107
- }
108
- $patt = '/^[0-9a-f]{32}$/i';
109
- if(!preg_match($patt, $hashid))
110
- {
111
- throw new DoofinderException("Wrong hashid");
112
- }
113
- if(!in_array($this->apiVersion, array('5', '4', '3.0','1.0')))
114
- {
115
- throw new DoofinderException('Wrong API');
116
- }
117
- $this->hashid = $hashid;
118
- if($fromParams)
119
- {
120
- $this->fromQuerystring();
121
- }
122
-
123
- }
124
-
125
- private function addprefix($value){
126
- return $this->paramsPrefix.$value;
127
- }
128
-
129
- /*
130
- * translateFilter
131
- *
132
- * translates a range filter to the new ES format
133
- * 'from'=>9, 'to'=>20 to 'gte'=>9, 'lte'=>20
134
- *
135
- * @param array $filter
136
- * @return array the translated filter
137
- */
138
- private function translateFilter($filter){
139
- $new_filter = array();
140
- foreach($filter as $key => $value){
141
- if ($key === 'from') {
142
- $new_filter['gte'] = $value;
143
- } else if ($key === 'to') {
144
- $new_filter['lte'] = $value;
145
- } else {
146
- $new_filter[$key] = $value;
147
- }
148
- }
149
- return $new_filter;
150
- }
151
-
152
- private function reqHeaders(){
153
- $headers = array();
154
- $headers[] = 'Expect:'; //Fixes the HTTP/1.1 417 Expectation Failed
155
- $authHeaderName = $this->apiVersion == '4' ? 'API Token: ' : 'authorization: ';
156
- $headers[] = $authHeaderName . $this->api_key; //API Authorization
157
- return $headers;
158
- }
159
-
160
- private function apiCall($entry_point='search', $params=array()){
161
- $params['hashid'] = $this->hashid;
162
- $args = http_build_query($this->sanitize($params)); // remove any null value from the array
163
-
164
- $url = $this->url.'/'.$this->apiVersion.'/'.$entry_point.'?'.$args;
165
-
166
- $session = curl_init($url);
167
- curl_setopt($session, CURLOPT_CUSTOMREQUEST, 'GET');
168
- curl_setopt($session, CURLOPT_HEADER, false); // Tell curl not to return headers
169
- curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Tell curl to return the response
170
- curl_setopt($session, CURLOPT_HTTPHEADER, $this->reqHeaders()); // Adding request headers
171
- $response = curl_exec($session);
172
- $httpCode = curl_getinfo($session, CURLINFO_HTTP_CODE);
173
- curl_close($session);
174
-
175
- if (floor($httpCode / 100) == 2) {
176
- return $response;
177
- }
178
- throw new DoofinderException($httpCode.' - '.$response, $httpCode);
179
- }
180
-
181
- public function getOptions(){
182
- return $this->apiCall('options/'.$this->hashid);
183
- }
184
-
185
- /**
186
- * query. makes the query to the doofinder search server.
187
- * also set several search parameters through it's $options argument
188
- *
189
- * @param string $query the search query
190
- * @param int $page the page number or the results to show
191
- * @param array $options query options:
192
- * - 'rpp'=> number of results per page. default 10
193
- * - 'timeout' => timeout after which the search server drops the conn.
194
- * defaults to 10 seconds
195
- * - 'types' => types of index to search at. default: all.
196
- * - 'filter' => filter to apply. ['color'=>['red','blue'], 'price'=>['from'=>33]]
197
- * - any other param will be sent as a request parameter
198
- * @return DoofinderResults results
199
- */
200
- public function query($query=null, $page=null, $options = array()){
201
- if($query){
202
- $this->search_options['query'] = $query;
203
- }
204
- if($page){
205
- $this->search_options['page'] = (int)$page;
206
- }
207
- foreach($options as $optionName => $optionValue){
208
- $this->search_options[$optionName] = $options[$optionName];
209
- }
210
-
211
- $params = $this->search_options;
212
-
213
- // translate filters
214
- if(!empty($params['filter']))
215
- {
216
- foreach($params['filter'] as $filterName => $filterValue){
217
- $params['filter'][$filterName] = $this->translateFilter($filterValue);
218
- }
219
- }
220
-
221
- // no query? then match all documents
222
- if(!$this->optionExists('query') || !trim($this->search_options['query'])){
223
- $params['query_name'] = 'match_all';
224
- }
225
-
226
- // if filters without query_name, pre-query first to obtain it.
227
- if (empty($params['query_name']) && !empty($params['filter']))
228
- {
229
- $filter = $params['filter'];
230
- unset($params['filter']);
231
- $dfResults = new DoofinderResults($this->apiCall('search', $params));
232
- $params['query_name'] = $dfResults->getProperty('query_name');
233
- $params['filter'] = $filter;
234
- }
235
- $dfResults = new DoofinderResults($this->apiCall('search', $params));
236
- $this->page = $dfResults->getProperty('page');
237
- $this->total = $dfResults->getProperty('total');
238
- $this->search_options['query'] = $dfResults->getProperty('query');
239
- $this->maxScore = $dfResults->getProperty('max_score');
240
- $this->queryName = $dfResults->getProperty('query_name');
241
- $this->lastQuery = $dfResults->getProperty('query');
242
-
243
- return $dfResults;
244
- }
245
-
246
- /**
247
- * hasNext
248
- *
249
- * @return boolean true if there is another page of results
250
- */
251
- public function hasNext(){
252
- return $this->page*$this->getRpp() < $this->total;
253
- }
254
-
255
- /**
256
- * hasPrev
257
- *
258
- * @return true if there is a previous page of results
259
- */
260
- public function hasPrev(){
261
- return ($this->page-1)*$this->getRpp() > 0;
262
- }
263
-
264
-
265
- /**
266
- * getPage
267
- *
268
- * obtain the current page number
269
- * @return int the page number
270
- */
271
- public function getPage(){
272
- return $this->page;
273
- }
274
-
275
- /**
276
- * setFilter
277
- *
278
- * set a filter for the query
279
- * @param string filterName the name of the filter to set
280
- * @param array filter if simple array, terms filter assumed
281
- * if 'from', 'to' in keys, range filter assumed
282
- */
283
- public function setFilter($filterName, $filter){
284
- if(!$this->optionExists('filter')){
285
- $this->search_options['filter'] = array();
286
- }
287
- $this->search_options['filter'][$filterName] = $filter;
288
- }
289
-
290
- /**
291
- * getFilter
292
- *
293
- * get conditions for certain filter
294
- * @param string filterName
295
- * @return array filter conditions: - simple array if terms filter
296
- * - 'from', 'to' assoc array if range f.
297
- * @return false if no filter definition found
298
- */
299
- public function getFilter($filterName){
300
- if($this->optionExists('filter') && isset($this->search_options['filter'][$filterName])){
301
- return $this->filter[$filterName];
302
- }
303
- return false;
304
- }
305
-
306
- /**
307
- * getFilters
308
- *
309
- * get all filters and their configs
310
- * @return array assoc array filterName => filterConditions
311
- */
312
- public function getFilters(){
313
- return $this->search_options['filter'];
314
- }
315
-
316
-
317
- /**
318
- * addTerm
319
- *
320
- * add a term to a terms filter
321
- * @param string filterName the filter to add the term to
322
- * @param string term the term to add
323
- */
324
- public function addTerm($filterName, $term){
325
- if(!$this->optionExists('filter')){
326
- $this->search_options['filter'] = array($filterName => array());
327
- }
328
- if(!isset($this->search_options['filter'][$filterName]))
329
- {
330
- $this->filter[$filterName] = array();
331
- $this->search_options['filter'][$filterName] = array();
332
- }
333
- $this->filter[$filterName][] = $term;
334
- $this->search_options['filter'][$filterName][] = $term;
335
- }
336
-
337
- /**
338
- * removeTerm
339
- *
340
- * remove a term from a terms filter
341
- * @param string filterName the filter to remove the term from
342
- * @param string term the term to be removed
343
- */
344
- public function removeTerm($filterName, $term){
345
- if($this->optionExists('filter') && isset($this->search_options['filter'][$filterName]) &&
346
- in_array($term, $this->search_options['filter'][$filterName]))
347
- {
348
- function filter_me($value){
349
- global $term;
350
- return $value != $term;
351
- }
352
- $this->search_options['filter'][$filterName] =
353
- array_filter($this->search_options['filter'][$filterName], 'filter_me');
354
- }
355
- }
356
-
357
- /**
358
- * setRange
359
- *
360
- * set a range filter
361
- * @param string filterName the filter to set
362
- * @param int from the lower bound value. included
363
- * @param int to the upper bound value. included
364
- */
365
- public function setRange($filterName, $from=null, $to=null){
366
- if(!$this->optionExists('filter')){
367
- $this->search_options['filter'] = array($filterName=>array());
368
- }
369
- if(!isset($this->search_options['filter'][$filterName]))
370
- {
371
- $this->search_options['filter'][$filterName] = array();
372
- }
373
- if($from)
374
- {
375
- $this->search_options['filter'][$filterName]['from'] = $from;
376
- }
377
- if($to)
378
- {
379
- $this->search_options['filter'][$filterName]['to'] = $from;
380
- }
381
- }
382
-
383
- /**
384
- * toQuerystring
385
- *
386
- * 'serialize' the object's state to querystring params
387
- * @param int $page the pagenumber. defaults to the current page
388
- */
389
- public function toQuerystring($page=null){
390
-
391
- foreach($this->search_options as $paramName => $paramValue){
392
- if($paramName == 'query'){
393
- $toParams[$this->queryParameter] = $paramValue;
394
- } else {
395
- $toParams[$this->paramsPrefix.$paramName] = $paramValue;
396
- }
397
- }
398
- if($page){
399
- $toParams[$this->paramsPrefix.'page'] = $page;
400
- }
401
- return http_build_query($toParams);
402
- }
403
-
404
- /**
405
- * fromQuerystring
406
- *
407
- * obtain object's state from querystring params
408
- * @param string $params where to obtain params from:
409
- * - 'GET' $_GET params (default)
410
- * - 'POST' $_POST params
411
- */
412
- public function fromQuerystring(){
413
- $doofinderReqParams = array_filter(array_keys($this->serializationArray),
414
- array($this, 'belongsToDoofinder'));
415
-
416
- foreach($doofinderReqParams as $dfReqParam){
417
- if($dfReqParam == $this->queryParameter){
418
- $keey = 'query';
419
- } else {
420
- $keey = substr($dfReqParam, strlen($this->paramsPrefix));
421
- }
422
- $this->search_options[$keey] = $this->serializationArray[$dfReqParam];
423
- }
424
- }
425
-
426
- /**
427
- * sanitize
428
- *
429
- * Clean array of keys with empty values
430
- *
431
- * @param array $params array to be cleaned
432
- * @return array array with no empty keys
433
- */
434
- private function sanitize($params){
435
- $result = array();
436
- foreach($params as $name => $value){
437
- if (is_array($value)){
438
- $result[$name] = $this->sanitize($value);
439
- } else if (trim($value)){
440
- $result[$name] = $value;
441
- }
442
- }
443
- return $result;
444
- }
445
-
446
- /**
447
- * belongsToDoofinder
448
- *
449
- * to know if certain parameter name belongs to doofinder serialization parameters
450
- *
451
- * @param string $paramName name of the param
452
- * @return boolean true or false.
453
- */
454
- private function belongsToDoofinder($paramName){
455
- if($pos = strpos($paramName, '[')){
456
- $paramName = substr($paramName, 0, $pos);
457
- }
458
- return in_array($paramName, $this->allowedParameters) || $paramName == $this->queryParameter;
459
- }
460
-
461
- /**
462
- * optionExists
463
- *
464
- * checks whether a search option is defined in $this->search_options
465
- *
466
- * @param string $optionName
467
- * @return boolean
468
- */
469
- private function optionExists($optionName){
470
- return array_key_exists($optionName, $this->search_options);
471
- }
472
-
473
- /**
474
- * nextPage
475
- *
476
- * obtain the results for the next page
477
- * @return DoofinderResults if there are results.
478
- * @return null otherwise
479
- */
480
- public function nextPage(){
481
- if($this->hasNext())
482
- {
483
- return $this->query($this->lastQuery, $this->page+1 );
484
- }
485
- return null;
486
- }
487
-
488
-
489
- /**
490
- * prevPage
491
- *
492
- * obtain results for the previous page
493
- * @return DoofinderResults
494
- * @return null otherwise
495
- */
496
- public function prevPage(){
497
- if($this->hasPrev())
498
- {
499
- return $this->query($this->lastQuery, $this->page-1 );
500
- }
501
- return null;
502
- }
503
-
504
- /**
505
- * numPages
506
- *
507
- * @return integer the number of pages
508
- */
509
- public function numPages(){
510
- return ceil($this->total / $this->getRpp());
511
- }
512
-
513
- public function getRpp(){
514
- $rpp = $this->optionExists('rpp') ? $this->search_options['rpp'] : null;
515
- $rpp = $rpp ? $rpp: self::DEFAULT_RPP;
516
- return $rpp;
517
- }
518
- /**
519
- * setApiVersion
520
- *
521
- * sets the api version to use.
522
- * @param string $apiVersion the api version , '1.0' or '3.0' or '4'
523
- */
524
- public function setApiVersion($apiVersion){
525
- $this->apiVersion = $apiVersion;
526
- }
527
-
528
- /**
529
- * setPrefix
530
- *
531
- * sets the prefix that will be used for serialization to querystring params
532
- * @param string $prefix the prefix
533
- */
534
- public function setPrefix($prefix){
535
- $this->paramsPrefix = $prefix;
536
- }
537
-
538
- /**
539
- * setQueryName
540
- *
541
- * sets query_name
542
- * CAUTION: node will complain if this is wrong
543
- */
544
- public function setQueryName($queryName){
545
- $this->queryName = $queryName;
546
- }
547
-
548
- /**
549
- * getFilterType
550
- * obtain the filter type (i.e. 'terms' or 'numeric range' from its conditions)
551
- * @param array filter conditions
552
- * @return string 'terms' or 'numericrange' false otherwise
553
- */
554
- private function getFilterType($filter){
555
- if(!is_array($filter))
556
- {
557
- return false;
558
- }
559
- if(count(array_intersect(array('from', 'to'), array_keys($filter)))>0)
560
- {
561
- return 'numericrange';
562
- }
563
- return 'terms';
564
- }
565
-
566
-
567
- }
568
-
569
- /**
570
- * @author JoeZ99 <jzarate@gmail.com>
571
- *
572
- * DoofinderResults
573
- *
574
- * Very thin wrapper of the results obtained from the doofinder server
575
- * it holds to accessor:
576
- * - getProperty : get single property of the search results (rpp, page, etc....)
577
- * - getResults: get an array with the results
578
- */
579
- class DoofinderResults{
580
-
581
- // doofinder status
582
- const SUCCESS = 'success'; // everything ok
583
- const NOTFOUND = 'notfound'; // no account with the provided hashid found
584
- const EXHAUSTED = 'exhausted'; // the account has reached its query limit
585
-
586
- private $properties = null;
587
- private $results = null;
588
- private $facets = null;
589
- private $filter = null;
590
- public $status = null;
591
-
592
- /**
593
- * Constructor
594
- *
595
- * @param string $jsonString stringified json returned by doofinder search server
596
- */
597
- function __construct($jsonString){
598
- $rawResults = json_decode($jsonString, true);
599
- foreach($rawResults as $kkey => $vall){
600
- if(!is_array($vall)){
601
- $this->properties[$kkey] = $vall;
602
- }
603
- }
604
- // doofinder status
605
- $this->status = isset($this->properties['doofinder_status'])?
606
- $this->properties['doofinder_status'] : self::SUCCESS;
607
-
608
- // results
609
- $this->results = array();
610
-
611
- if(isset($rawResults['results']) && is_array($rawResults['results']))
612
- {
613
- $this->results = $rawResults['results'];
614
- }
615
-
616
- // build a friendly filter array
617
- $this->filter = array();
618
- // reorder filter, before assigning it to $this
619
- if(isset($rawResults['filter']))
620
- {
621
- foreach($rawResults['filter'] as $filterType => $filters)
622
- {
623
- foreach($filters as $filterName => $filterProperties)
624
- {
625
- $this->filter[$filterName] = $filterProperties;
626
- }
627
- }
628
- }
629
-
630
- // facets
631
- $this->facets = array();
632
- if(isset($rawResults['facets']))
633
- {
634
- $this->facets = $rawResults['facets'];
635
-
636
- // mark "selected" true or false according to filters presence
637
- foreach($this->facets as $facetName => $facetProperties){
638
- switch($facetProperties['_type']){
639
- case 'terms':
640
- foreach($facetProperties['terms'] as $pos => $term){
641
- if(isset($this->filter[$facetName]) && in_array($term['term'], $this->filter[$facetName])){
642
- $this->facets[$facetName]['terms'][$pos]['selected'] = true;
643
- } else {
644
- $this->facets[$facetName]['terms'][$pos]['selected'] = false;
645
- }
646
- }
647
- break;
648
- case 'range':
649
- foreach($facetProperties['ranges'] as $pos => $range){
650
- $this->facets[$facetName]['ranges'][$pos]['selected_from'] = false;
651
- $this->facets[$facetName]['ranges'][$pos]['selected_to'] = false;
652
- if(isset($this->filter[$facetName]) && isset($this->filter[$facetName]['gte'])){
653
- $this->facets[$facetName]['ranges'][$pos]['selected_from'] = $this->filter[$facetName]['gte'];
654
- }
655
- if(isset($this->filter[$facetName]) && isset($this->filter[$facetName]['lte'])){
656
- $this->facets[$facetName]['ranges'][$pos]['selected_to'] = $this->filter[$facetName]['lte'];
657
- }
658
-
659
- }
660
- break;
661
- }
662
- }
663
- }
664
- }
665
-
666
- /**
667
- * getProperty
668
- *
669
- * get single property from the results
670
- * @param string @propertyName: 'results_per_page', 'query', 'max_score', 'page', 'total', 'hashid'
671
- * @return mixed the value of the property
672
- */
673
- public function getProperty($propertyName){
674
- return array_key_exists($propertyName, $this->properties) ?
675
- $this->properties[$propertyName]: null;
676
- }
677
-
678
- /**
679
- * getResults
680
- *
681
- * @return array search results. at the moment, only the 'cooked' version.
682
- * Each result is of the form:
683
- * array('header'=>...,
684
- * 'body' => ..,
685
- * 'price' => ..,
686
- * 'href' => ...,
687
- * 'image' => ...,
688
- * 'type' => ...,
689
- * 'id' => ..)
690
- */
691
- public function getResults(){
692
- return $this->results;
693
- }
694
-
695
- /**
696
- *
697
- * getFacetsNames
698
- *
699
- * @return array facets names.
700
- */
701
- public function getFacetsNames(){
702
- return array_keys($this->facets);
703
- }
704
-
705
- /**
706
- * getFacet
707
- *
708
- * @param string name the facet name whose results are wanted
709
- *
710
- * @return array facet search data
711
- * - for terms facets
712
- * array(
713
- * '_type'=> 'terms', // type of facet 'terms' or 'range'
714
- * 'missing'=> 3, // # of elements with no value for this facet
715
- * 'others'=> 2, // # of terms not present in the search response
716
- * 'total'=> 6, // # number of possible terms for this facet
717
- * 'terms'=> array(
718
- * array('count'=>6, 'term'=>'Blue', 'selected'=>false), // in the response, there are 6 'blue' terms
719
- * array('count'=>3, 'term': 'Red', 'selected'=>true), // if 'selected'=>true, that term has been selected as filter
720
- * ...
721
- * )
722
- * )
723
- * - for range facets
724
- * array(
725
- * '_type'=> 'range',
726
- * 'ranges'=> array(
727
- * array(
728
- * 'count'=>6, // in the response, 6 elements within that range.
729
- * 'from':0,
730
- * 'min': 30
731
- * 'max': 90,
732
- * 'mean'=>33.2,
733
- * 'total'=>432,
734
- * 'total_count'=>6,
735
- * 'selected_from'=> 34.3 // if present. this value has been used as filter. false otherwise
736
- * 'selected_to'=> 99.3 // if present. this value has been used as filter. false otherwise
737
- * ),
738
- * ...
739
- * )
740
- * )
741
- *
742
- *
743
- */
744
- public function getFacet($facetName){
745
- return $this->facets[$facetName];
746
- }
747
-
748
- /**
749
- * getFacets
750
- *
751
- * get the whole facets associative array:
752
- * array('color'=>array(...), 'brand'=>array(...))
753
- * each array is defined as in getFacet() docstring
754
- *
755
- * @return array facets assoc. array
756
- */
757
- public function getFacets(){
758
- return $this->facets;
759
- }
760
-
761
- /**
762
- * getAppliedFilters
763
- *
764
- * get the filters the query has defined
765
- * array('categories' => array( // filter name . same as facet name
766
- * 'Sillas de paseo', // if simple array, it's a terms facet
767
- * 'Sacos sillas de paseo'
768
- * ),
769
- * 'color' => array(
770
- * 'red',
771
- * 'blue'
772
- * ),
773
- * 'price' => array(
774
- * 'include_upper'=>true, // if 'from' , 'to' keys, it's a range facet
775
- * 'from'=>35.19,
776
- * 'to'=>9999
777
- * )
778
- * )
779
- * MEANING OF THE EXAMPLE FILTER:
780
- * "FROM the query results, filter only results that have ('Sillas de paseo' OR 'Sacos sillas de paseo') categories
781
- * AND ('red' OR 'blue') color AND price is BETWEEN 34.3 and 99.3"
782
-
783
- */
784
- public function getAppliedFilters(){
785
- return $this->filter;
786
- }
787
-
788
- /**
789
- * isOk
790
- *
791
- * checks if all went well
792
- * @return boolean true if the status is 'success'.
793
- * false if the status is not.
794
- */
795
- public function isOk(){
796
- return $this->status == self::SUCCESS;
797
- }
798
- }
799
-
800
-
801
- class DoofinderException extends Exception{
802
-
803
- }
804
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Doofinder/doofinder_management_api.php DELETED
@@ -1,408 +0,0 @@
1
- <?php
2
- /**
3
- * Author:: JoeZ99 (<jzarate@gmail.com>).
4
- *
5
- * License:: Apache License, Version 2.0
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the "License"); you may
8
- * not use this file except in compliance with the License. You may obtain
9
- * a copy of the License at
10
- *
11
- * http://www.apache.org/licenses/LICENSE-2.0
12
- *
13
- * Unless required by applicable law or agreed to in writing, software
14
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
- * License for the specific language governing permissions and limitations
17
- * under the License.
18
- */
19
- require_once dirname(__FILE__).'/errors.php';
20
-
21
-
22
- /**
23
- * Class to manage the connection with the API servers.
24
- *
25
- * Needs APIKEY in initialization.
26
- * Example: $dma = new DoofinderManagementApi('eu1-d531af87f10969f90792a4296e2784b089b8a875')
27
- */
28
- class DoofinderManagementApi
29
- {
30
- const REMOTE_API_ENDPOINT = "https://%s-api.doofinder.com/v1";
31
- const LOCAL_API_ENDPOINT = "http://localhost:8000/api/v1";
32
-
33
- private $apiKey = null;
34
- private $clusterRegion = 'eu1';
35
- private $token = null;
36
- private $baseManagementUrl = null;
37
-
38
- function __construct($apiKey, $local = false){
39
- $this->apiKey = $apiKey;
40
- $clusterToken = explode('-', $apiKey);
41
- $this->clusterRegion = $clusterToken[0];
42
- $this->token = $clusterToken[1];
43
- if ($local === true){
44
- $this->baseManagementUrl = self::LOCAL_API_ENDPOINT;
45
- } else {
46
- $this->baseManagementUrl = sprintf(self::REMOTE_API_ENDPOINT, $this->clusterRegion);
47
- }
48
- }
49
-
50
- /**
51
- * Makes the actual request to the API server and normalize response
52
- *
53
- * @param string $method The HTTP method to use. 'GET|PUT|POST|DELETE'
54
- * @param string $entryPoint The path to use. '/<hashid>/items/product'
55
- * @param array $params If any, url request parameters
56
- * @param array $data If any, body request parameters
57
- * @return array Array with both status code and response .
58
- */
59
- function managementApiCall($method='GET', $entryPoint='', $params=null, $data=null){
60
- $headers = array('Authorization: Token '.$this->token, // for Auth
61
- 'Content-Type: application/json',
62
- 'Expect:'); // Fixes the HTTP/1.1 417 Expectation Failed
63
-
64
- $url = $this->baseManagementUrl.'/'.$entryPoint;
65
- if (is_array($params) && sizeof($params) > 0){
66
- $url .= '?'.http_build_query($params);
67
- }
68
-
69
- $session = curl_init($url);
70
- curl_setopt($session, CURLOPT_CUSTOMREQUEST, $method);
71
- curl_setopt($session, CURLOPT_HEADER, false);
72
- curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Tell curl to return the response
73
- curl_setopt($session, CURLOPT_HTTPHEADER, $headers); // Adding request headers
74
-
75
- if (in_array($method, array('POST', 'PUT'))){
76
- curl_setopt($session, CURLOPT_POSTFIELDS, $data);
77
- }
78
-
79
- $response = curl_exec($session);
80
- $httpCode = curl_getinfo($session, CURLINFO_HTTP_CODE);
81
- curl_close($session);
82
-
83
- handleErrors($httpCode, $response);
84
-
85
- $return = array('statusCode' => $httpCode);
86
- $return['response'] = ($decoded = json_decode($response, true)) ? $decoded : $response;
87
-
88
- return $return;
89
- }
90
-
91
- /**
92
- * To get info on all possible api entry points
93
- * @return array An assoc. array with the different entry points
94
- */
95
- function getApiRoot(){
96
- $response = $this->managementApiCall();
97
- return $response['response'];
98
- }
99
-
100
- /**
101
- * Obtain a list of SearchEngines objects, ready to interact with the API
102
- * @return array list of searchEngines objects
103
- */
104
- function getSearchEngines(){
105
- $searchEngines = array();
106
- $apiRoot = $this->getApiRoot();
107
- unset($apiRoot['searchengines']);
108
-
109
- foreach($apiRoot as $hashid => $props){
110
- $searchEngines[] = new SearchEngine($this, $hashid, $props['name']);
111
- }
112
-
113
- return $searchEngines;
114
- }
115
-
116
- }
117
-
118
- /**
119
- * Class with all the capabilities described in the API
120
- *
121
- * see http://www.doofinder.com/developer/topics/api/management-api
122
- */
123
- class SearchEngine {
124
- public $name = null;
125
- public $hashid = null;
126
-
127
- private $dma = null; // DoofinderManagementApi instance
128
-
129
- function __construct($dma, $hashid, $name){
130
- $this->name = $name;
131
- $this->hashid = $hashid;
132
- $this->dma = $dma;
133
- }
134
-
135
- /**
136
- * Get a list of searchengine's types
137
- *
138
- * @return array list of types
139
- */
140
- function getDatatypes(){
141
- return $this->getTypes();
142
- }
143
-
144
- /**
145
- * Get a list of searchengine's types
146
- *
147
- * @return array list of types
148
- */
149
- function getTypes(){
150
- $result = $this->dma->managementApiCall('GET', "{$this->hashid}/types");
151
- return $result['response'];
152
- }
153
-
154
- /**
155
- * Add a type to the searchengine
156
- *
157
- * @param string $dType the type name
158
- * @return new list of searchengine's types
159
- */
160
- function addType($dType){
161
- $result = $this->dma->managementApiCall('POST', "{$this->hashid}/types", null,
162
- json_encode(array('name' => $dType)));
163
- return $result['response'];
164
- }
165
-
166
- /**
167
- * Delete a type and all its items. HANDLE WITH CARE
168
- *
169
- * @param string $dType the Type to delete. All items belonging
170
- * to that type will be removed. mandatory
171
- * @return boolean true on success
172
- */
173
- function deleteType($dType){
174
- $result = $this->dma->managementApiCall('DELETE', "{$this->hashid}/types/{$dType}");
175
- return $result['statusCode'] == 204;
176
- }
177
-
178
- /**
179
- * Get paginated indexed items belonging to a searchengine's type
180
- *
181
- * It only paginates forward. Can't go backwards
182
- * @param string $dType Type of the items to list
183
- * @param string $scrollId identifier of the pagination set
184
- * @return array Assoc array with scroll_id ,paginated results and total results.
185
- */
186
- public function getScrolledItemsPage($dType, $scrollId = null){
187
- $params = $scrollId ? array("scroll_id" => $scrollId) : null;
188
- $result = $this->dma->managementApiCall('GET', "{$this->hashid}/items/{$dType}", $params);
189
- return array(
190
- 'scroll_id' => $result['response']['scroll_id'],
191
- 'results' => $result['response']['results'],
192
- 'total' => $result['response']['count'],
193
- );
194
- }
195
-
196
- function items($dType){
197
- return new ItemsRS($this, $dType);
198
- }
199
-
200
- /**
201
- * Get details of a specific item
202
- *
203
- * @param string $dType Type of the item.
204
- * @param string $itemId the id of the item
205
- * @return array Assoc array representing the item.
206
- */
207
- function getItem($dType, $itemId){
208
- $result = $this->dma->managementApiCall('GET', "{$this->hashid}/items/{$dType}/{$itemId}");
209
- return $result['response'];
210
- }
211
-
212
- /**
213
- * Add an item to the search engine
214
- *
215
- * - If the 'id' field is present, use that as item's id or overwrite an existing
216
- * item with that id.
217
- * - It the 'id' field is not present, create one.
218
- *
219
- * @param string $dType type of the. If not provided, first available type is used
220
- * @param array $itemDescription Assoc array representation of the item
221
- * @return string the id of the item just created
222
- */
223
- function addItem($dType, $itemDescription){
224
- $result = $this->dma->managementApiCall('POST', "{$this->hashid}/items/{$dType}", null,
225
- json_encode($itemDescription));
226
- return $result['response']['id'];
227
- }
228
-
229
- /**
230
- * Add items in bulk to the search engine
231
- *
232
- * For each item:
233
- * - If the 'id' field is present, use that as item's id or overwrite an existing
234
- * item with that id.
235
- * - It the 'id' field is not present, create one.
236
- *
237
- * @param string $dType type of the. If not provided, first available type is used
238
- * @param array $itemsDescription List of Assoc array representation of the item
239
- * @return array List of ids of the added items
240
- */
241
- function addItems($dType, $itemsDescription){
242
- $result = $this->dma->managementApiCall('POST', "{$this->hashid}/items/{$dType}", null,
243
- json_encode($itemsDescription));
244
-
245
- function fetchId($item){
246
- return $item['id'];
247
- };
248
-
249
- return array_map('fetchId', $result['response']);
250
- }
251
-
252
- /**
253
- * Update or create an item of the search engine
254
- *
255
- * In case of conflict between itemDescription's id or $itemId,
256
- * the latter is used.
257
- *
258
- * @param string $dType type of the Item.
259
- * @param string $itemId Id of the item to be updated/added
260
- * @param array $itemDescription Assoc array representating the item.
261
- * @return boolean true on success.
262
- */
263
- function updateItem($dType, $itemId, $itemDescription){
264
- $result = $this->dma->managementApiCall('PUT', "{$this->hashid}/items/{$dType}/{$itemId}", null,
265
- json_encode($itemDescription));
266
- return $result['statusCode'] == 200;
267
- }
268
-
269
- /**
270
- * Bulk update of several items
271
- *
272
- * Each item description must contain the 'id' field
273
- *
274
- * @param string $dType type of the items.
275
- * @param array $itemsDescription List of assoc array representing items
276
- * @return boolean true on success
277
- */
278
- function updateItems($dType, $itemsDescription){
279
- $result = $this->dma->managementApiCall('PUT', "{$this->hashid}/items/{$dType}", null,
280
- json_encode($itemsDescription));
281
- return $result['statusCode'] == 200;
282
- }
283
-
284
- /**
285
- * Delete an item
286
- *
287
- * @param string $dType type of the item
288
- * @param string $itemId id of the item
289
- * @return boolean true if success, false if failure
290
- */
291
- function deleteItem($dType, $itemId){
292
- $result = $this->dma->managementApiCall('DELETE', "{$this->hashid}/items/{$dType}/{$itemId}");
293
- return $result['statusCode'] == 204 ;
294
- }
295
-
296
- /**
297
- * Ask the server to process the search engine's feeds
298
- *
299
- * @return array Assoc array with:
300
- * - 'task_created': boolean true if a new task has been created
301
- * - 'task_id': if task created, the id of the task.
302
- */
303
- function process(){
304
- $result = $this->dma->managementApiCall('POST', "{$this->hashid}/tasks/process");
305
- $taskCreated = ($result['statusCode'] == 201);
306
- $taskId = $taskCreated ? obtainId($result['response']['link']) : null;
307
- return array('task_created'=>$taskCreated, 'task_id' => $taskId);
308
- }
309
-
310
- /**
311
- * Obtain info of the last processing task sent to the server
312
- *
313
- * @return array Assoc array with 'state' and 'message' indicating status of the
314
- * last asked processing task
315
- */
316
- function processInfo(){
317
- $result = $this->dma->managementApiCall('GET', "{$this->hashid}/tasks/process");
318
- unset($result['response']['task_name']);
319
- return $result['response'];
320
- }
321
-
322
- /**
323
- * Obtain info about how a task is going or its result
324
- *
325
- * @return array Assoc array with 'state' and 'message' indicating the status
326
- * of the task
327
- */
328
- function taskInfo($taskId){
329
- $result = $this->dma->managementApiCall('GET', "{$this->hashid}/tasks/{$taskId}");
330
- unset($result['response']['task_name']);
331
- return $result['response'];
332
- }
333
-
334
- /**
335
- * Obtain logs of the latest feed processing tasks done
336
- *
337
- * @return array list of arrays representing the logs
338
- */
339
- function logs(){
340
- $result = $this->dma->managementApiCall("GET", "{$this->hashid}/logs");
341
- return $result['response'];
342
- }
343
- }
344
-
345
- /**
346
- * Helper class to iterate through the search engine's items
347
- *
348
- * Implemets Iterator interface so foreach() can work with ItemRS
349
- */
350
- class ItemsRS implements Iterator {
351
-
352
- private $searchEngine = null;
353
- private $resultsPage = null;
354
- private $scrollId = null;
355
- private $position = 0;
356
- private $total = null;
357
-
358
- function __construct($searchEngine, $dType){
359
- $this->dType = $dType;
360
- $this->searchEngine = $searchEngine;
361
- }
362
-
363
- private function fetchResults(){
364
- $apiResults = $this->searchEngine->getScrolledItemsPage($this->dType, $this->scrollId);
365
- $this->total = $apiResults['total'];
366
- $this->resultsPage = $apiResults['results'];
367
- $this->scrollId = $apiResults['scroll_id'];
368
- $this->currentItem = each($this->resultsPage);
369
- }
370
-
371
- function rewind(){
372
- $this->scrollId = null;
373
- $this->fetchResults();
374
- }
375
-
376
- function valid(){
377
- return $this->position < $this->total;
378
- }
379
-
380
- function current(){
381
- return $this->currentItem['value'];
382
- }
383
-
384
- function key(){
385
- return $this->position;
386
- }
387
-
388
- function next(){
389
- ++$this->position;
390
- $this->currentItem = each($this->resultsPage);
391
- if (!$this->currentItem && $this->position < $this->total){
392
- $this->fetchResults();
393
- reset($this->resultsPage);
394
- $this->currentItem = each($this->resultsPage);
395
- }
396
- }
397
- }
398
-
399
- /**
400
- * Extracts identificator from an item or task url.
401
- *
402
- * @param string $url item or task resource locator
403
- * @return the item identificator
404
- */
405
- function obtainId($url){
406
- preg_match('~/\w{32}/(items/\w+|tasks)/([\w-_]+)/?$~', $url, $matches);
407
- return $matches[2];
408
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/Doofinder/errors.php DELETED
@@ -1,48 +0,0 @@
1
- <?php
2
-
3
- class NotAllowed extends Exception {}
4
-
5
- class BadRequest extends Exception {}
6
-
7
- class QuotaExhausted extends Exception {}
8
-
9
- class WrongResponse extends Exception {}
10
-
11
- class ThrottledResponse extends Exception {}
12
-
13
- function readError($response) {
14
- $error = json_decode($response, true);
15
- $error = $error['detail'];
16
- if (!isset($error)) {
17
- $error = $response;
18
- }
19
- return $error;
20
- }
21
-
22
- function handleErrors($statusCode, $response){
23
- switch($statusCode)
24
- {
25
- case 403:
26
- throw new NotAllowed("The user does not have permissions to perform this operation: ".readError($response));
27
- case 401:
28
- throw new NotAllowed("The user hasn't provided valid authorization: ".readError($response));
29
- case 404:
30
- throw new BadRequest("Not Found: ".readError($response));
31
- case 409: // trying to post with an already used id
32
- throw new BadRequest("Request conflict: ".readError($response));
33
- case 429:
34
- if (stripos($response, 'throttled')) {
35
- throw new ThrottledResponse(readError($response));
36
- } else {
37
- throw new QuotaExhausted("The query quota has been reached. No more queries can be requested right now");
38
- }
39
- }
40
-
41
- if ($statusCode >= 500) {
42
- throw new WrongResponse("Server error: ".readError($response));
43
- }
44
-
45
- if ($statusCode >= 400) {
46
- throw new BadRequest("The client made a bad request: ".readError($response));
47
- }
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/php-doofinder/CHANGELOG.txt ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Version 4.
2
+ ----------
3
+
4
+ - CamelCase convention for everything. "has_next(0" is now "hasNext()"
5
+ - And empty query() prompts a "match all" query
6
+ - Facets/Filtering support
7
+
8
+ Version 5.
9
+ ----------
10
+
11
+ - API Key Authorization. API Key required in client constructor
12
+ - Allow unprefixed params
13
+ - Allow custom query parameter name
14
+ - v5.2.1
15
+ - Search Client: getOptions method and https mandatory
16
+ - v5.2.2
17
+ - mainteinance release
18
+ - v5.2.3
19
+ - bugfixes
20
+ - v5.2.4
21
+ - bugfixes
22
+ - v5.2.5
23
+ - stats retrieval
24
+ - v5.2.6
25
+ - added sort parameter.
26
+ - v5.3.1
27
+ - complete rewrite of the file structure: psr4 compliant
28
+ - added to the composer repository
29
+ - v5.4.3
30
+ - bugfixes
31
+ - autoload for those not using composes
32
+ - decent formatting
33
+ - v5.5.0
34
+ - Added phpunit tests
35
+ - Added phpunit tests
36
+ - some small refactoring
37
+ - v5.5.1
38
+ - added deleteItems method
lib/php-doofinder/README.md ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [![Build Status](https://api.travis-ci.org/doofinder/php-doofinder.svg?branch=master)](https://travis-ci.org/doofinder/php-doofinder)
2
+ # Official PHP Client for doofinder
3
+
4
+ <!-- MarkdownTOC depth=3 -->
5
+
6
+ - [Installation](#installation)
7
+ - [Download Method](#download-method)
8
+ - [Using Composer](#using-composer)
9
+ - [Search Client](#search-client)
10
+ - [Quick & Dirty](#quick--dirty)
11
+ - [Searching from HTML Forms](#searching-from-html-forms)
12
+ - [Tips](#tips)
13
+ - [Extra Search Options](#extra-search-options)
14
+ - [Extra Constructor Options](#extra-constructor-options)
15
+ - [API reference](#api-reference)
16
+ - [One quick example](#one-quick-example)
17
+ - [Management API](#management-api)
18
+ - [Quick & Dirty](#quick--dirty-1)
19
+
20
+ <!-- /MarkdownTOC -->
21
+
22
+ ## Installation
23
+
24
+ To install the library you can download it from the project's [releases](https://github.com/doofinder/php-doofinder/releases) page or use [Composer](https://packagist.org/packages/doofinder/doofinder).
25
+
26
+ Requires PHP 5.3 or later. Not tested in previous versions.
27
+
28
+ ### Download Method
29
+
30
+ Just include the provided `autoload.php` file and use:
31
+
32
+ ```php
33
+ require_once('path/to/php-doofinder/autoload.php');
34
+ $client = new \Doofinder\Api\Search\Client(HASHID, API_KEY);
35
+ ```
36
+
37
+ ### Using Composer
38
+
39
+ Add Doofinder to your `composer.json` file by running:
40
+
41
+ ```bash
42
+ $ composer require doofinder/doofinder
43
+ ```
44
+
45
+ If you're already using composer your autoload.php file will be updated. If not, a new one will be generated and you will have to include it:
46
+
47
+ ```php
48
+ <?php
49
+ require_once dirname(__FILE__).'/vendor/autoload.php';
50
+
51
+ use \Doofinder\Api\Search\Client as SearchClient;
52
+ use \Doofinder\Api\Management\Client as ManagementClient;
53
+
54
+ $client = new SearchClient(HASHID, API_KEY);
55
+ ```
56
+
57
+ ## Search Client
58
+
59
+ ### Quick & Dirty
60
+
61
+ ```php
62
+ require_once('path/to/php-doofinder/autoload.php');
63
+
64
+ define('HASHID', '6a9gbc4dcx735x123b1e0198gf92e6e9');
65
+ define('API_KEY', 'eu1-384fdag73c7ff0a59g589xf9f4083bxb9727f9c3')
66
+
67
+ // Set hashid and API Key
68
+ $client = new \Doofinder\Api\Search\Client(HASHID, API_KEY);
69
+
70
+ // You can specify filters
71
+ $client->setFilter('brand', array('nike', 'converse')); // brand must be 'nike' or 'converse' AND ...
72
+ $client->setFilter('color', array('red', 'blue')); // ... color must be 'red' or 'blue' AND ...
73
+ $client->setFilter('price', array('from'=>33.2)); // ... price must be upper than 33.2
74
+
75
+ // You can also use more specific methods for that.
76
+ $client->addTerm('brand', 'adidas'); // add 'adidas' to the 'brand' filter
77
+ $client->removeTerm('brand', 'nike'); // remove 'nike' from the 'brand' filter
78
+ $client->setRange('price', null, 99.9); // add an upper limit to the price
79
+
80
+ // Feeling adventurous? sort!
81
+
82
+ $client->addSort('price', 'desc'); // sort by price (descending)...
83
+ $client->addSort('title', 'asc'); // ... and then by title (ascending)
84
+
85
+ // Do the query, specify the page if you want.
86
+ // 'page' = 1. optional . 'transformer' = 'dflayer'. optional.
87
+ $results = $client->query('test query', 1, array('transformer' => 'dflayer'));
88
+
89
+ // With the results object, fetch specific properties, facets or the results
90
+ // itself as an array.
91
+
92
+ $results->getProperty('results_per_page'); // returns results per page.
93
+ $results->getProperty('page'); // returns the page of the results
94
+ $results->getProperty('total'); // total number of results
95
+ $results->getProperty('query'); // query used
96
+ $results->getProperty('hashid');
97
+ $results->getProperty('max_score'); // maximum score obtained in the search results
98
+ $results->getProperty('doofinder_status'); // special Doofinder status, see below
99
+
100
+ // If you use the 'dflayer' transformer ...
101
+ foreach($results->getResults() as $result){
102
+ echo $result['body']."\n"; // description of the item
103
+ echo $result['dfid']."\n"; // doofinder id. uniquely identifies this item
104
+ echo $result['price']."\n"; // string, may come with currency sign
105
+ echo $result['sale_price']."\n"; // may or may not be present
106
+ echo $result['header']."\n"; // title of the item
107
+ echo $result['href']."\n" ; // url of the item's page
108
+ echo $result['image']."\n" ; // url of the item's image
109
+ echo $result['type']."\n" ; // item's type. "product" at the moment
110
+ echo $result['id']."\n" ; // item's id, as it comes from the xml feed
111
+ }
112
+
113
+ $category_facet = $results->getFacet('category');
114
+
115
+ foreach($category_facet['terms'] as $term) {
116
+ // Category: Trousers : 5 results found
117
+ echo "Category: ".$term['term']." : ".$term['count']." results found\n";
118
+ }
119
+
120
+ $price_facet = $results->getFacet('price');
121
+
122
+ echo "Min price found: ".$price_facet['ranges'][0]['min']."\n";
123
+ // Min price found: 33.6
124
+ echo "Max price found: ".$price_facet['ranges'][0]['max']."\n";
125
+ ```
126
+
127
+ __Notice:__
128
+
129
+ - For non-numeric fields you'll have to set those fields as _sortable_ in Doofinder's control panel before you can sort by them.
130
+ - Every search request is made through secure protocol.
131
+
132
+ ### Searching from HTML Forms
133
+
134
+ #### Be careful with the `query_name` parameter
135
+
136
+ When you issue a query to Doofinder, the search engine tries different types of search in order to provide the best possible results. This "types of search" are controlled by the `query_name` parameter.
137
+
138
+ However, if you're going to apply filters to a query, that means you're going to make the search again with certain added restrictions, therefore you're not interested in let Doofinder find the best _"_type of search_"_ for you again, but you rather do the search exactly the same way you did when first querying, so the results with applied filters are consistent with that.
139
+
140
+ `$results->getProperty('query_name')` gives you the type of query that was used to fetch those results. If you plan to filter on those, you should use the same type of query. You can do that with:
141
+
142
+ ```php
143
+ // make the initial query. no filters and no "query_name" specified
144
+ $results = $client->query("baby gloves");
145
+ // "query_name" is automatically set to be the same it used for the first query
146
+ // add a filter
147
+ $client->addTerm('category', 'More than 6 years');
148
+ // do the same query. this time filtered and with a specific query_name
149
+ $client->query("baby gloves");
150
+ ```
151
+
152
+ or with a form parameter:
153
+
154
+ ```html
155
+ <form>
156
+ <input type="text" name="dfParam_query">
157
+ <input type="hidden" name="dfParam_query_name" value="text_all">
158
+ ...
159
+ </form>
160
+ ```
161
+
162
+ In short:
163
+
164
+ - You make a query to Doofinder either with filters or not. You don't need to specify `query_name`. Doofinder will find the more suitable `query_name`.
165
+ - Once you got the results, you can use `$results->getProperty('query_name')` to know which `query_name` was the one Doofinder chose.
166
+ - If you want to make further filtering on those search results, you should instruct Doofinder to use the same `query_name` you got from the first search results.
167
+ - Each time you do any new query, don't specify `query_name`. Let Doofinder find the best.
168
+ - **Warning:** don't try to figure out a `query_name` on your own. query names may change in the future. You can always count on `$results->getParameter('query_name')` to get the `query_name` that led to those `$results`
169
+
170
+ #### `toQuerystring()`
171
+
172
+ Dumps the complete state of the client (query, page, filters, rpp) into a querystring. Every param has the (configurable) `dfParam_` prefix to avoid conflicts.
173
+
174
+ ```php
175
+ $page = 3; // Results page number. Optional.
176
+ echo $client->toQuerystring($page);
177
+ // query=dfParam_test+query&dfParam_rpp=4&dfParam_timeout=8000&dfParam_page=3
178
+ ```
179
+
180
+ You can use it to build links to search results:
181
+
182
+ ```html
183
+ <a href="results.php?<?php echo $client->toQuerystring(4)?>">Next Page</a>
184
+ ```
185
+
186
+ #### `fromQuerystring()`
187
+
188
+ Gets information from the PHP request globals and initialises the client with search parameters (query, page, filters, rpp).
189
+
190
+ ```php
191
+ $client = new \Doofinder\Api\Search\Client(HASHID, API_KEY);
192
+ $client->fromQuerystring();
193
+ $results = $client->query();
194
+ ```
195
+
196
+ You can do the same in the constructor by passing `true` as the third parameter. This code is equivalent to the code above:
197
+
198
+ ```php
199
+ <?php
200
+ $client = new \Doofinder\Api\Search\Client(HASHID, API_KEY, true);
201
+ $results = $client->query();
202
+ ```
203
+
204
+ #### Filter Parameters
205
+
206
+ When specifying filters in HTML forms, follow this convention:
207
+
208
+ - All the filters are passed in an array called _filter_ prefixed with the _prefix_ specified in `$client` constructor (default: `dfParam_`).
209
+ - Each key is a filter name. Each value is filter definition.
210
+ - Filter definition for terms filter: use an array with terms
211
+ - Filter definition for range filter: use an array with `from` and/or `to` keys.
212
+
213
+ __Example:__ _color_ (terms filter) must be `blue` or `red` and _price_ (range filter) must be greater than `10.2`.
214
+
215
+ ```html
216
+ <input name="dfParam_filter[color][]" value="blue">
217
+ <input name="dfParam_filter[color][]" value="red">
218
+ <input name="dfParam_filter[price][from]" value="10.2">
219
+ ```
220
+
221
+
222
+ This constructs the array:
223
+
224
+ ```php
225
+ dfParam_filter = array(
226
+ 'color' => array('blue', 'red'),
227
+ 'price' => array('from' => 10.2)
228
+ );
229
+ ```
230
+
231
+ #### Sort Parameters
232
+
233
+ As with other params, the parameters must be prefixed with the `prefix` specified in `$client` constructor (default: `dfParam_`).
234
+
235
+ If you sorting for one field in ascending order, you can simply send the `sort` parameter with the name of the field to sort by as value:
236
+
237
+ ```html
238
+ <input name="dfParam_sort" value="price">
239
+ ```
240
+
241
+ If you want to specify the sort direction, you'll have to to send, for the `sort` param, an array, being the key the field to sort on and the value either `asc` or `desc`:
242
+
243
+ ```html
244
+ <input name="dfParam_sort[price]" value="desc".
245
+ ```
246
+
247
+ If you want to sort by several fields, just compound the previous definition in an array.
248
+
249
+ __Example:__ sort in descending order by price and if same price, sort by title in ascending order.
250
+
251
+ ```html
252
+ <input name="dfParam_sort[0][price]" value="desc">
253
+ <input name="dfParam_sort[1][title]" value="asc">
254
+ ```
255
+
256
+ This constructs the array:
257
+
258
+ ```php
259
+ dfParam_sort = array(
260
+ array('price' => 'desc'),
261
+ array('title', 'asc')
262
+ );
263
+ ```
264
+
265
+ Please read carefully the [sort parameters](http://www.doofinder.com/support/developer/api/search-api#sort-parameters) section in our search API documentation.
266
+
267
+ ### Tips
268
+
269
+ #### Empty queries
270
+
271
+ An empty query matches all documents. Of course, if the query is filtered, even if the search term is none, the results are filtered too.
272
+
273
+ #### UTF-8 encoding
274
+
275
+ The results are always in UTF-8 encoding. If you're using it on an ISO-8859-1 encoded page, you can use `utf8_decode`:
276
+
277
+ ```php
278
+ foreach ($results->getResults() as $result) {
279
+ echo utf8_decode($result['body']).PHP_EOL;
280
+ }
281
+ ```
282
+
283
+ ### Extra Search Options
284
+
285
+ ```php
286
+ $query = 'test query';
287
+ $page = 3;
288
+ $results = $client->query($query, $page, array(
289
+ // Results Per Page (default: 10)
290
+ 'rpp' => 4,
291
+ // types of item to search (default: all)
292
+ 'types' => array('product', 'question'),
293
+ // Template used to return items
294
+ 'transformer' => 'dflayer',
295
+ // Filtering options
296
+ 'filter' => array(
297
+ 'brand' => array('nike', 'converse'),
298
+ 'price' => array('from'=> 33.2, 'to'=> 99),
299
+ ),
300
+ // Sorting options
301
+ 'sort' => array(
302
+ array('price' => 'asc'),
303
+ array('title' => 'desc'),
304
+ )
305
+ ));
306
+ ```
307
+
308
+ ### Extra Constructor Options
309
+
310
+ ```php
311
+ $client = new \Doofinder\Api\Search\Client(
312
+ HASHID,
313
+ API_KEY,
314
+ true, // Whether to import request parameters or not (default: false)
315
+ array(
316
+ // Prefix to use with toQuerystring() (default: dfParam_)
317
+ 'prefix' => 'sp_df_df_',
318
+ // Parameter name to use for the query parameter (default: query)
319
+ 'queryParameter' => 'q',
320
+ // API version of the search server (default: 5)
321
+ 'apiVersion' => '5',
322
+ // Use only parameters from $_POST or $_GET methods
323
+ // (default: unset, uses $_REQUEST)
324
+ 'restrictedRequest' => 'post'
325
+ )
326
+ );
327
+ ```
328
+
329
+ ### API reference
330
+
331
+ #### `\Doofinder\Api\Search\Client`
332
+
333
+ ```php
334
+ $client->query($query, $page, $options); // Perform search
335
+ $client->hasNext(); // Boolean, true if there is a next page of results
336
+ $client->hasPrev(); // Boolean, true if there is a prev page of results
337
+ $client->numPages(); // Total number of pages
338
+ $client->getPage(); // Get the actual page number
339
+ $client->setFilter($filterName, $filter); // Set a filter
340
+ $client->getFilter($filterName); // Get a filter by name
341
+ $client->getFilters(); // Get all filters
342
+ $client->addTerm($filterName, $term); // Add a term to a terms type $filterName
343
+ $client->removeTerm($filterName, $term);
344
+ $client->setRange($filterName, $from, $to); // Specify parameters for a range filter
345
+ $client->getFilter($filter_name); // Get filter specifications for $filter_name, if any
346
+ $client->getFilters(); // Get filter specifications for all defined filters
347
+ $client->addSort($sortField, $direction); // Tells doofinder to sort results
348
+ $client->setPrefix($prefix); // Sets prefix for dumping/recovering from querystring
349
+ $client->toQuerystring($page); // Dumps state info to a querystring
350
+ $client->fromQuerystring(); // Recover state from a querystring
351
+ $client->nextPage(); // Obtain results for the nextpage
352
+ $client->prevPage(); // Obtain results for the prev page
353
+ $client->numPages(); // Num of pages
354
+ $client->getRpp(); // Get the number of results per page
355
+ $client->getTimeout();
356
+ $client->setApiVersion($apiVersion); // Sets API version to use (default: 5)
357
+ ```
358
+
359
+ #### `\Doofinder\Api\Search\Results`
360
+
361
+ ```php
362
+ $results->getProperty($propertyName); // Get the property $propertyName
363
+ $results->getResults(); // Get results
364
+ $results->getFacetsNames(); // Array with facet names
365
+ $results->getFacet($facetName); // Obtain search results for facet $facetName
366
+ $results->getFacets(); // All facets
367
+ $results->getAppliedFilters(); // Filters that have been applied to obtain these results
368
+ $results->isOk(); // Checks if all went OK
369
+ $results->status; // Account status info. 'success', 'exhausted', 'notfound'
370
+ ```
371
+
372
+ ### One quick example
373
+
374
+ ```php
375
+ <?php
376
+ require_once('path/to/php-doofinder/autoload.php');
377
+
378
+ define('HASHID', '6a9gbc4dcx735x123b1e0198gf92e6e9');
379
+ define('API_KEY', 'eu1-384fdag73c7ff0a59g589xf9f4083bxb9727f9c3')
380
+
381
+ $client = new \Doofinder\Api\Search\Client(HASHID, API_KEY, true);
382
+ // if no dfParam_query, fetch all the results, to fetch all possible facets
383
+ $results = $client->query(null, null, array('transformer'=>'dflayer'));
384
+ ?>
385
+
386
+ <form method="get" action="">
387
+ <input type="text" name="dfParam_query" onchange="emptyQueryName()" value="<?php echo $results->getProperty('query') ?>">
388
+ <input type="hidden" name="dfParam_rpp" value="3">
389
+ <input type="hidden" name="dfParam_transformer" value="dflayer">
390
+ <!-- this has to be removed via javascript if we want doofinder to find the best search for us. -->
391
+ <input type="hidden" id="query_name" name="dfParam_query_name" value="<?php echo $results->getProperty('query_name') ?>">
392
+ <input type="submit" value="search!">
393
+
394
+ <p>Filter by:</p>
395
+ <ul>
396
+ <?php foreach ($results->getFacets() as $facetName => $facetResults): ?>
397
+ <li>
398
+ <?php echo $facetName ?>
399
+ <ul>
400
+ <?php if ($facetResults['_type'] == 'terms'): ?>
401
+ <?php foreach ($facetResults['terms'] as $term):?>
402
+ <li>
403
+ <input type="checkbox"
404
+ name="dfParam_filter[<?php echo $facetName ?>][]"
405
+ <?php echo $term['selected'] ? 'checked': ''?>
406
+ value="<?php echo $term['term']?>">
407
+ <?php echo $term['term']?>: <?php echo $term['count']?>
408
+ </li>
409
+ <?php endforeach ?>
410
+ <?php endif ?>
411
+ <?php if $facetResults['_type'] == 'range'): $range = $facetResults['ranges'][0]; ?>
412
+ <li>
413
+ Range: <?php echo $range['min']?> -- <?php echo $range['max']?><br/>
414
+ From: <input type="text" name="dfParam_filter[<?php echo $facetName?>][from]" value="<?php echo $range['selected_from']?>">
415
+ To: <input type="text" name="dfParam_filter[<?php echo $facetName?>][to]" value="<?php echo $range['selected_to']?>">
416
+ </li>
417
+ <?php endif?>
418
+ </ul>
419
+ </li>
420
+ <?php endforeach ?>
421
+ </ul>
422
+ </form>
423
+
424
+ <h1>Results</h1>
425
+
426
+ <ul>
427
+ <?php foreach ($results->getResults() as $result): ?>
428
+ <li><?php echo $result['header'] ?></li>
429
+ <?php endforeach ?>
430
+ </ul>
431
+
432
+ <?php if ($client->hasPrev()): ?>
433
+ <a href="?<?php echo $client->toQuerystring($client->getPage() - 1) ?>">Prev</a>
434
+ <?php endif?>
435
+ Number of pages: <?php echo $client->numPages() ?>
436
+ <?php if ($client->hasNext()): ?>
437
+ <a href="?<?php echo $client->toQuerystring($client->getPage() + 1) ?>">Next</a>
438
+ <?php endif?>
439
+
440
+ <script>
441
+ // if the search box changes, a new query is being made
442
+ // don't tell doofinder which search type to use
443
+ function emptyQueryName(){
444
+ document.getElementById('query_name').value = '';
445
+ return true;
446
+ }
447
+ </script>
448
+ ```
449
+
450
+ ## Management API
451
+
452
+ ### Quick & Dirty
453
+
454
+ ```php
455
+ require_once('path/to/php-doofinder/autoload.php');
456
+
457
+ define('API_KEY', 'eu1-384fdag73c7ff0a59g589xf9f4083bxb9727f9c3')
458
+
459
+ // Instantiate the object, use your doofinder's API_KEY.
460
+ $client = new \Doofinder\Api\Management\Client(API_KEY);
461
+
462
+ // Get a list of search engines
463
+ $searchEngines = $client->getSearchEngines();
464
+ // From the list, we will choose the first one
465
+ $mySearchEngine = $searchEngines[0];
466
+ ```
467
+
468
+ The `SearchEngine` object gives you methods to manage a search engine.
469
+
470
+ #### Types Management
471
+
472
+ ```php
473
+ $types = $mySearchEngine->getTypes(); // Obtain search engine's datatypes
474
+ $new_types = $mySearchEngine->addType('product'); // Add new type
475
+ $mySearchEngine->deleteType('product'); // Remove the type and all items within it.
476
+ ```
477
+
478
+ #### Items Management
479
+
480
+ ##### Single Item
481
+
482
+ ```php
483
+ // Obtain item info within 'product' type
484
+ $item = $mySearchEngine->getItem('product', '888493');
485
+ // Add a new item to the 'product' type
486
+ $added_item = $mySearchEngine->addItem('product', array('id'=> 'newid', 'title'=>'a title'));
487
+ // Remove item
488
+ $mySearchEngine->deleteItem('product', 'newid');
489
+ // Update the '888493' item belonging to the 'product' type.
490
+ $mySearchEngine->updateItem('product', '888493', array('title'=>'modifiled title'));
491
+ ```
492
+
493
+ ##### Bulk Add/Update/Delete
494
+
495
+ ```php
496
+ $mySearchEngine->updateItems('product', array(
497
+ array('title' => 'first item', 'id' => 'id1'),
498
+ array('title' => 'second item', 'id' => 'id2'),
499
+ ));
500
+
501
+ $mySearchEngine->addItems('product', array(
502
+ array('title' => 'first item', 'id' => 'newid1'),
503
+ array('title' => 'second item'),
504
+ ));
505
+
506
+ $mySearchEngine->deleteItems('product', array('id1', 'id2'));
507
+ ```
508
+
509
+ ##### Iterating Items
510
+
511
+ If you want to go through **every item in your index** you can only do it forwards. To do that, you'll need to use the *listing/scrolling* method…
512
+
513
+ __WARNING:__ You won't be iterating a standard PHP array. Items will be provided as an iterator object instance.
514
+
515
+ ```php
516
+ $items = $mySearchEngine->items('product');
517
+ foreach ($items as $item) {
518
+ echo $item['title'];
519
+ }
520
+ ```
521
+
522
+ You can't retrieve a specific item by index:
523
+
524
+ ```php
525
+ $items = $mySearchEngine->items('product');
526
+ $item = $items[4]; // WRONG!!!!!
527
+ PHP Fatal error: Cannot use object of type ItemsRS as array…
528
+ ```
529
+
530
+ #### Stats
531
+
532
+ ```php
533
+ // Some PHP versions may need this
534
+ date_default_timezone_set('America/Havana');
535
+
536
+ // If not $from_date or $to_date provided, default is last 15 days
537
+ $from_date = new DateTime("2011-01-07");
538
+ $to_date = new DateTime("2011-02-07");
539
+
540
+ foreach ($mySearchEngine->stats($from_date, $to_date) as $key => $aggregated){
541
+ echo $aggregated['date']; // date of the aggregated data
542
+ echo $aggregated['clicked']; // # clicks in search results
543
+ echo $aggregated['searches']; // # complete searches. i.e.: "mp3 player"
544
+ echo $aggregated['api']; // # requests made through our API
545
+ echo $aggregated['parser']; // # requests used in feeds parsing
546
+ // (1 per each 100 parsed items)
547
+ echo $aggregated['queries']; // # "raw" search requests
548
+ // i.e.: "mp3", "mp3 p", "mp3 pl" ..
549
+ echo $aggregated['requests']; // total # of requests for that day
550
+ }
551
+
552
+ $top_clicked = $mySearchEngine->topTerms('clicked', $from_date, $to_date);
553
+
554
+ foreach ($top_clicked as $key => $clicked) {
555
+ echo $clicked['term']; // title of the clicked item
556
+ echo $clicked['count']; // # of clicks on that item
557
+ }
558
+
559
+ $top_searches = $mySearchEngine->topTerms('searches', $from_date, $to_date);
560
+
561
+ foreach ($top_searches as $key => $search) {
562
+ echo $search['term']; // search terms used
563
+ echo $search['count']; // # of times it's been used
564
+ }
565
+
566
+ $top_opportunities = $mySearchEngine->topTerms('opportunities', $from_date, $to_date);
567
+
568
+ foreach ($top_opportunities as $key => $opportunity) {
569
+ echo $opportunity['term']; // search terms used (that haven't yielded any result)
570
+ echo $opportunity['count']; // # of times it's been used
571
+ }
572
+ ```
573
+
574
+ #### Tasks management
575
+
576
+ ```php
577
+ // Ask our server to process a search engine's feeds
578
+ $taskResult = $mySearchEngine->process();
579
+ // Retrieve info about the last or current process of the search engine
580
+ $taskInfo = $mySearchEngine->processInfo();
581
+ // Retrieve info about a certain task
582
+ $taskInfo = $mySearchEngine->taskInfo($taskResult['task_id']);
583
+ // Get log info about the last processes
584
+ $logs = $mySearchEngine->logs();
585
+ ```
586
+
587
+ ## Run Tests
588
+
589
+ To run tests.
590
+
591
+ - Make sure you have (phpunit) [https://phpunit.de/] (version 4.8) and (php-mock-phpunit)[https://github.com/php-mock/php-mock-phpunit] (version 1.1.*). If you have composer you can run
592
+ ````shell
593
+ $ composer.phar install
594
+ ````
595
+
596
+ - Run the tests!!
597
+ ````shell
598
+ $ phpunit
599
+ ````
lib/php-doofinder/autoload.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ spl_autoload_register('autoload_doofinder_classes');
3
+
4
+ function autoload_doofinder_classes($className) {
5
+ $libraryPrefix = 'Doofinder\\Api\\';
6
+ $libraryDirectory = __DIR__ . '/src/';
7
+
8
+ $len = strlen($libraryPrefix);
9
+
10
+ // Binary safe comparison of $len first characters
11
+ if (strncmp($libraryPrefix, $className, $len) !== 0) {
12
+ return;
13
+ }
14
+
15
+ $classPath = str_replace('\\', '/', substr($className, $len)) . '.php';
16
+ $file = $libraryDirectory . $classPath;
17
+
18
+ if (file_exists($file)) {
19
+ require $file;
20
+ }
21
+ }
lib/php-doofinder/composer.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "doofinder/doofinder",
3
+ "type": "library",
4
+ "description": "Doofinder PHP API Client",
5
+ "keywords": ["search", "api", "doofinder"],
6
+ "homepage": "https://github.com/doofinder/php-doofinder",
7
+ "license": "MIT",
8
+ "authors": [
9
+ {
10
+ "name": "JoeZ99",
11
+ "email": "jzarate@gmail.com",
12
+ "role": "Developer"
13
+ }
14
+ ],
15
+ "require": {
16
+ "php": ">=5.3.0"
17
+ },
18
+ "require-dev": {
19
+ "phpunit/phpunit": "4.8.*",
20
+ "php-mock/php-mock-phpunit": "1.1.*"
21
+ },
22
+ "autoload": {
23
+ "psr-4": {
24
+ "Doofinder\\Api\\": "src/"
25
+ }
26
+ }
27
+ }
lib/php-doofinder/phpunit.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <phpunit colors="true">
3
+ <testsuites>
4
+ <testsuite name="Application Test Suite">
5
+ <directory>./src/Test/</directory>
6
+ </testsuite>
7
+ </testsuites>
8
+ </phpunit>
lib/php-doofinder/src/Management/AggregatesIterator.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management;
4
+
5
+ use Doofinder\Api\Management\SearchEngine;
6
+ use Doofinder\Api\Management\ItemsResultSet;
7
+
8
+ class AggregatesIterator extends ItemsResultSet {
9
+
10
+ /**
11
+ * Class to Iterate through SearchEngine's aggregated stats data for a certain period.
12
+ */
13
+ protected $last_page = 0;
14
+ protected $searchParams = array();
15
+
16
+ /**
17
+ * @param SearchEngine $searchEngine
18
+ * @param DateTime $from_date . Starting date of the period. Default: 15 days ago
19
+ * @param DateTime $to_date. Ending date of the period. Default: today.
20
+ */
21
+ public function __construct(SearchEngine $searchEngine, $from_date = null, $to_date = null){
22
+ $this->last_page = 0;
23
+
24
+ if (!is_null($from_date)) {
25
+ $this->searchParams['from'] = $from_date->format("Ymd");
26
+ }
27
+ if (!is_null($to_date)) {
28
+ $this->searchParams['to'] = $to_date->format("Ymd");
29
+ }
30
+
31
+ parent::__construct($searchEngine);
32
+ }
33
+
34
+ protected function fetchResultsAndTotal() {
35
+ $params = $this->last_page > 0 ? array("page" => $this->last_page + 1) : array();
36
+
37
+ try{
38
+ $apiResponse = $this->searchEngine->client->managementApiCall(
39
+ 'GET',
40
+ "{$this->searchEngine->hashid}/stats",
41
+ array_merge($params, $this->searchParams)
42
+ );
43
+
44
+ $this->resultsPage = $apiResponse['response']['aggregates'];
45
+ $this->total = $apiResponse['response']['count'];
46
+ $this->last_page++;
47
+ $this->currentItem = each($this->resultsPage);
48
+ } catch (NotFound $nfe) {
49
+ $this->resultsPage = array();
50
+ }
51
+
52
+ reset($this->resultsPage);
53
+ }
54
+
55
+ public function rewind() {
56
+ $this->last_page = 0;
57
+ parent::rewind();
58
+ }
59
+ }
lib/php-doofinder/src/Management/Client.php ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Author:: JoeZ99 (<jzarate@gmail.com>).
4
+ *
5
+ * License:: Apache License, Version 2.0
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
8
+ * not use this file except in compliance with the License. You may obtain
9
+ * a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ * License for the specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ namespace Doofinder\Api\Management;
21
+
22
+ use Doofinder\Api\Management\SearchEngine;
23
+ use Doofinder\Api\Management\Errors\Utils;
24
+
25
+ /**
26
+ * Class to manage the connection with the API servers.
27
+ *
28
+ * Needs APIKEY in initialization.
29
+ * Example: $dma = new DoofinderManagementApi('eu1-d531af87f10969f90792a4296e2784b089b8a875')
30
+ */
31
+ class Client
32
+ {
33
+ const REMOTE_API_ENDPOINT = "https://%s-api.doofinder.com/v1";
34
+ const LOCAL_API_ENDPOINT = "http://localhost:8000/api/v1";
35
+
36
+ private $apiKey = null;
37
+ private $clusterRegion = 'eu1';
38
+ private $token = null;
39
+ private $baseManagementUrl = null;
40
+
41
+ public function __construct($apiKey, $local = false) {
42
+ $this->apiKey = $apiKey;
43
+ $clusterToken = explode('-', $apiKey);
44
+ $this->clusterRegion = $clusterToken[0];
45
+ $this->token = $clusterToken[1];
46
+
47
+ if ($local === true){
48
+ $this->baseManagementUrl = self::LOCAL_API_ENDPOINT;
49
+ } else {
50
+ $this->baseManagementUrl = sprintf(self::REMOTE_API_ENDPOINT, $this->clusterRegion);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Makes the actual request to the API server and normalize response
56
+ *
57
+ * @param string $method The HTTP method to use. 'GET|PUT|POST|DELETE'
58
+ * @param string $entryPoint The path to use. '/<hashid>/items/product'
59
+ * @param array $params If any, url request parameters
60
+ * @param array $data If any, body request parameters
61
+ * @return array Array with both status code and response .
62
+ */
63
+ public function managementApiCall($method = 'GET', $entryPoint = '', $params = null, $data = null) {
64
+ $headers = array(
65
+ 'Authorization: Token ' . $this->token,
66
+ 'Content-Type: application/json',
67
+ 'Expect:', // Fixes the HTTP/1.1 417 Expectation Failed error
68
+ );
69
+
70
+ $url = $this->baseManagementUrl.'/'.$entryPoint;
71
+
72
+ if(is_array($params) && sizeof($params) > 0){
73
+ $url .= '?'.http_build_query($params);
74
+ }
75
+
76
+ if (!in_array($method, array('POST', 'PUT', 'DELETE'))){
77
+ $data = null;
78
+ }
79
+ $serverResponse = $this->talkToServer($method, $url, $headers, $data);
80
+ $statusCode = $serverResponse['statusCode'];
81
+ $contentResponse = $serverResponse['contentResponse'];
82
+
83
+ $error = Utils::handleErrors($statusCode, $contentResponse);
84
+
85
+ if ($error) {
86
+ throw $error;
87
+ }
88
+
89
+ return array(
90
+ 'statusCode' => $statusCode,
91
+ 'response' => ($decoded = json_decode($contentResponse, true)) ? $decoded : $contentResponse
92
+ );
93
+ }
94
+
95
+ /**
96
+ * To get info on all possible api entry points
97
+ * @return array An assoc. array with the different entry points
98
+ */
99
+ public function getApiRoot() {
100
+ $response = $this->managementApiCall();
101
+ return $response['response'];
102
+ }
103
+
104
+ /**
105
+ * Obtain a list of SearchEngines objects, ready to interact with the API
106
+ * @return array list of searchEngines objects
107
+ */
108
+ public function getSearchEngines(){
109
+ $searchEngines = array();
110
+ $apiRoot = $this->getApiRoot();
111
+ unset($apiRoot['searchengines']);
112
+
113
+ foreach ($apiRoot as $hashid => $props) {
114
+ $searchEngines[] = new SearchEngine($this, $hashid, $props['name']);
115
+ }
116
+
117
+ return $searchEngines;
118
+ }
119
+
120
+ protected function talkToServer($method, $url, $headers, $data)
121
+ {
122
+ $session = curl_init($url);
123
+ curl_setopt($session, CURLOPT_CUSTOMREQUEST, $method);
124
+ curl_setopt($session, CURLOPT_HEADER, false);
125
+ curl_setopt($session, CURLOPT_RETURNTRANSFER, true); // Tell curl to return the response
126
+ curl_setopt($session, CURLOPT_HTTPHEADER, $headers); // Adding request headers
127
+
128
+ if (!is_null($data)) {
129
+ curl_setopt($session, CURLOPT_POSTFIELDS, $data);
130
+ }
131
+
132
+ $contentResponse = curl_exec($session);
133
+ $statusCode = curl_getinfo($session, CURLINFO_HTTP_CODE);
134
+
135
+ curl_close($session);
136
+
137
+ return array(
138
+ 'contentResponse' => $contentResponse,
139
+ 'statusCode' => $statusCode
140
+ );
141
+
142
+ }
143
+
144
+ }
lib/php-doofinder/src/Management/Errors/BadRequest.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ class BadRequest extends \Exception {}
lib/php-doofinder/src/Management/Errors/NotAllowed.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ class NotAllowed extends \Exception {}
lib/php-doofinder/src/Management/Errors/NotFound.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <?php
2
+ namespace Doofinder\Api\Management\Errors;
3
+
4
+ class NotFound extends \Exception {}
lib/php-doofinder/src/Management/Errors/NotProcessedResponse.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ class NotProcessedResponse extends \Exception {}
lib/php-doofinder/src/Management/Errors/QuotaExhausted.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ class QuotaExhausted extends \Exception {}
lib/php-doofinder/src/Management/Errors/ThrottledResponse.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ class ThrottledResponse extends \Exception {}
lib/php-doofinder/src/Management/Errors/Utils.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ use Doofinder\Api\Management\Errors\NotAllowed;
6
+ use Doofinder\Api\Management\Errors\NotFound;
7
+ use Doofinder\Api\Management\Errors\BadRequest;
8
+ use Doofinder\Api\Management\Errors\ThrottledResponse;
9
+ use Doofinder\Api\Management\Errors\QuotaExhausted;
10
+ use Doofinder\Api\Management\Errors\WrongResponse;
11
+
12
+
13
+ class Utils {
14
+ public static function handleErrors($statusCode, $response) {
15
+ switch ($statusCode) {
16
+ case 403:
17
+ return new NotAllowed("The user does not have permissions to perform this operation: ".Utils::readError($response));
18
+ case 401:
19
+ return new NotAllowed("The user hasn't provided valid authorization: ".Utils::readError($response));
20
+ case 404:
21
+ return new NotFound("Not Found: ".Utils::readError($response));
22
+ case 409: // trying to post with an already used id
23
+ return new BadRequest("Request conflict: ".Utils::readError($response));
24
+ case 429:
25
+ if (stripos($response, 'throttled')) {
26
+ return new ThrottledResponse(Utils::readError($response));
27
+ } else {
28
+ return new QuotaExhausted("The query quota has been reached. No more queries can be requested right now");
29
+ }
30
+ }
31
+
32
+ if ($statusCode >= 500) {
33
+ return new WrongResponse("Server error: ".Utils::readError($response));
34
+ }
35
+
36
+ if ($statusCode >= 400) {
37
+ return new BadRequest("The client made a bad request: ".Utils::readError($response));
38
+ }
39
+
40
+ return false;
41
+ }
42
+
43
+ private static function readError($response) {
44
+ $error = json_decode($response, true);
45
+ $error = $error['detail'];
46
+
47
+ if (!isset($error)) {
48
+ $error = $response;
49
+ }
50
+
51
+ return $error;
52
+ }
53
+ }
lib/php-doofinder/src/Management/Errors/WrongResponse.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management\Errors;
4
+
5
+ class WrongResponse extends \Exception {}
lib/php-doofinder/src/Management/ItemsResultSet.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management;
4
+
5
+ use Doofinder\Api\Management\SearchEngine;
6
+
7
+
8
+ /**
9
+ * Helper class to iterate through the search engine's items
10
+ *
11
+ * Implemets Iterator interface so foreach() can work with ItemRS
12
+ * It's supposed to be extended
13
+ */
14
+ class ItemsResultSet implements \Iterator {
15
+
16
+ protected $searchEngine = null;
17
+ protected $resultsPage = null;
18
+ protected $position = 0;
19
+ protected $total = null;
20
+
21
+ public function __construct(SearchEngine $searchEngine) {
22
+ $this->searchEngine = $searchEngine;
23
+ }
24
+
25
+ /**
26
+ * To be implemented in children
27
+ */
28
+ protected function fetchResultsAndTotal() {
29
+ throw new Exception('Not implemented.');
30
+ }
31
+
32
+ public function rewind() {
33
+ $this->position = 0;
34
+ $this->total = null;
35
+ $this->resultsPage = null;
36
+ $this->fetchResultsAndTotal();
37
+ $this->currentItem = each($this->resultsPage);
38
+ }
39
+
40
+ public function valid() {
41
+ return $this->position < $this->total;
42
+ }
43
+
44
+ public function current() {
45
+ return $this->currentItem['value'];
46
+ }
47
+
48
+ public function key() {
49
+ return $this->position;
50
+ }
51
+
52
+ public function next() {
53
+ ++$this->position;
54
+ $this->currentItem = each($this->resultsPage);
55
+ if (!$this->currentItem && $this->position < $this->total) {
56
+ $this->fetchResultsAndTotal();
57
+ $this->currentItem = each($this->resultsPage);
58
+ }
59
+ }
60
+ }
lib/php-doofinder/src/Management/ScrollIterator.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management;
4
+
5
+ use Doofinder\Api\Management\SearchEngine;
6
+ use \Doofinder\Api\Management\ItemsResultSet;
7
+
8
+ /**
9
+ * ScrollIterator
10
+ *
11
+ * Class to Iterate/Scroll through search engine's indexed items of a certain datatype
12
+ */
13
+ class ScrollIterator extends ItemsResultSet {
14
+
15
+ private $scrollId = null;
16
+ private $datatype = null;
17
+
18
+ /**
19
+ * @param SearchEngine $searchEngine
20
+ * @param string $datatype type of item . i.e. 'product'
21
+ */
22
+ public function __construct(SearchEngine $searchEngine, $datatype){
23
+ $this->datatype = $datatype;
24
+ parent::__construct($searchEngine);
25
+ }
26
+
27
+ /**
28
+ * Loads net next batch of api results
29
+ *
30
+ */
31
+ protected function fetchResultsAndTotal(){
32
+ $apiResults = $this->searchEngine->client->managementApiCall(
33
+ 'GET',
34
+ "{$this->searchEngine->hashid}/items/{$this->datatype}",
35
+ ($this->scrollId ? array("scroll_id" => $this->scrollId) : null)
36
+ );
37
+ $this->total = $apiResults['response']['count'];
38
+ $this->scrollId = $apiResults['response']['scroll_id'];
39
+ $this->resultsPage = $apiResults['response']['results'];
40
+ $this->currentItem = each($this->resultsPage);
41
+
42
+ reset($this->resultsPage);
43
+ }
44
+
45
+ public function rewind(){
46
+ $this->scrollId = null;
47
+ parent::rewind();
48
+ }
49
+ }
lib/php-doofinder/src/Management/SearchEngine.php ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class with all the capabilities described in the API
4
+ *
5
+ * see http://www.doofinder.com/developer/topics/api/management-api
6
+ */
7
+
8
+ namespace Doofinder\Api\Management;
9
+
10
+ use Doofinder\Api\Management\Client;
11
+ use Doofinder\Api\Management\Errors\BadRequest;
12
+ use Doofinder\Api\Management\TopTermsIterator;
13
+ use Doofinder\Api\Management\ScrollIterator;
14
+ use Doofinder\Api\Management\AggregatesIterator;
15
+
16
+
17
+ class SearchEngine {
18
+ public $name = null;
19
+ public $hashid = null;
20
+ public $client = null;
21
+
22
+ public function __construct(Client $client, $hashid, $name) {
23
+ $this->name = $name;
24
+ $this->hashid = $hashid;
25
+ $this->client = $client;
26
+ }
27
+
28
+ /**
29
+ * Get a list of searchengine's types
30
+ *
31
+ * @return array list of types
32
+ */
33
+ public function getDatatypes() {
34
+ return $this->getTypes();
35
+ }
36
+
37
+ /**
38
+ * Get a list of searchengine's types
39
+ *
40
+ * @return array list of types
41
+ */
42
+ public function getTypes() {
43
+ $result = $this->client->managementApiCall('GET', "{$this->hashid}/types");
44
+ return $result['response'];
45
+ }
46
+
47
+ /**
48
+ * Add a type to the searchengine
49
+ *
50
+ * @param string $datatype the type name
51
+ * @return new list of searchengine's types
52
+ */
53
+ public function addType($datatype) {
54
+ $result = $this->client->managementApiCall('POST', "{$this->hashid}/types", null, json_encode(array('name' => $datatype)));
55
+ return $result['response'];
56
+ }
57
+
58
+ /**
59
+ * Delete a type and all its items. HANDLE WITH CARE
60
+ *
61
+ * @param string $datatype the Type to delete. All items belonging
62
+ * to that type will be removed. mandatory
63
+ * @return boolean true on success
64
+ */
65
+ public function deleteType($datatype) {
66
+ $result = $this->client->managementApiCall('DELETE', "{$this->hashid}/types/{$datatype}");
67
+ return $result['statusCode'] == 204;
68
+ }
69
+
70
+ public function items($datatype) {
71
+ return new ScrollIterator($this, $datatype);
72
+ }
73
+
74
+ /**
75
+ * Get details of a specific item
76
+ *
77
+ * @param string $datatype Type of the item.
78
+ * @param string $itemId the id of the item
79
+ * @return array Assoc array representing the item.
80
+ */
81
+ public function getItem($datatype, $itemId) {
82
+ $result = $this->client->managementApiCall('GET', "{$this->hashid}/items/{$datatype}/{$itemId}");
83
+ return $result['response'];
84
+ }
85
+
86
+ /**
87
+ * Add an item to the search engine
88
+ *
89
+ * - If the 'id' field is present, use that as item's id or overwrite an existing
90
+ * item with that id.
91
+ * - It the 'id' field is not present, create one.
92
+ *
93
+ * @param string $datatype type of the. If not provided, first available type is used
94
+ * @param array $itemDescription Assoc array representation of the item
95
+ * @return string the id of the item just created
96
+ */
97
+ public function addItem($datatype, $itemDescription) {
98
+ $result = $this->client->managementApiCall('POST', "{$this->hashid}/items/{$datatype}", null, json_encode($itemDescription));
99
+ return $result['response']['id'];
100
+ }
101
+
102
+ /**
103
+ * Add items in bulk to the search engine
104
+ *
105
+ * For each item:
106
+ * - If the 'id' field is present, use that as item's id or overwrite an existing
107
+ * item with that id.
108
+ * - It the 'id' field is not present, create one.
109
+ *
110
+ * @param string $datatype type of the. If not provided, first available type is used
111
+ * @param array $itemsDescription List of Assoc array representation of the item
112
+ * @return array List of ids of the added items
113
+ */
114
+ public function addItems($datatype, $itemsDescription) {
115
+ $result = $this->client->managementApiCall('POST', "{$this->hashid}/items/{$datatype}", null, json_encode($itemsDescription));
116
+
117
+ return array_map(function($it){return $it['id'];}, $result['response']);
118
+ }
119
+
120
+ /**
121
+ * Update or create an item of the search engine
122
+ *
123
+ * In case of conflict between itemDescription's id or $itemId,
124
+ * the latter is used.
125
+ *
126
+ * @param string $datatype type of the Item.
127
+ * @param string $itemId Id of the item to be updated/added
128
+ * @param array $itemDescription Assoc array representating the item.
129
+ * @return boolean true on success.
130
+ */
131
+ public function updateItem($datatype, $itemId, $itemDescription) {
132
+ $result = $this->client->managementApiCall('PUT', "{$this->hashid}/items/{$datatype}/{$itemId}", null, json_encode($itemDescription));
133
+ return $result['statusCode'] == 200;
134
+ }
135
+
136
+ /**
137
+ * Bulk update of several items
138
+ *
139
+ * Each item description must contain the 'id' field
140
+ *
141
+ * @param string $datatype type of the items.
142
+ * @param array $itemsDescription List of assoc array representing items
143
+ * @return boolean true on success
144
+ */
145
+ public function updateItems($datatype, $itemsDescription) {
146
+ $result = $this->client->managementApiCall('PUT', "{$this->hashid}/items/{$datatype}", null, json_encode($itemsDescription));
147
+ return $result['statusCode'] == 200;
148
+ }
149
+
150
+ /**
151
+ * Delete an item
152
+ *
153
+ * @param string $datatype type of the item
154
+ * @param string $itemId id of the item
155
+ * @return boolean true if success, false if failure
156
+ */
157
+ public function deleteItem($datatype, $itemId) {
158
+ $result = $this->client->managementApiCall('DELETE', "{$this->hashid}/items/{$datatype}/{$itemId}");
159
+ return $result['statusCode'] == 204 ;
160
+ }
161
+
162
+ /**
163
+ * Delete items in bulk (up to 100)
164
+ *
165
+ * @param string $datatype type of the items
166
+ * @param array list of item ids to be deleted
167
+ * @return array assoc array with both items which could be deleted and couldn't
168
+ * array('errors'=>array(), 'success'=>array('AX01', 'AX02', 'AXFD')
169
+ */
170
+ public function deleteItems($datatype, $itemsIds) {
171
+ $objects = array_map(function($id){return array('id'=>$id);}, $itemsIds);
172
+ $result = $this->client->managementApiCall(
173
+ 'DELETE', "{$this->hashid}/items/{$datatype}", null, json_encode($objects)
174
+ );
175
+ return $result['response'];
176
+ }
177
+
178
+ /**
179
+ * Obtain stats aggregated data for a certain period.
180
+ *
181
+ * @param DateTime $from_date. Starting date. Default is 15 days ago
182
+ * @param DateTime $to_date. Ending date. Default is today
183
+ *
184
+ * @return ItemsRS iterator through daily aggregated data.
185
+ */
186
+ public function stats($from_date = null, $to_date = null) {
187
+ return new AggregatesIterator($this, $from_date, $to_date);
188
+ }
189
+
190
+ /**
191
+ * Obtain frequency sorted list of therms used for a certain period.
192
+ *
193
+ * @param string term: type of term 'clicked', 'searches', 'opportunities'
194
+ * - 'clicked': clicked items
195
+ * - 'searches': complete searches
196
+ * - 'opportunities': searches without results
197
+ * @param DateTime $from_date. Starting date. Default is 15 days ago
198
+ * @param DateTime $to_date. Ending date. Default is today
199
+ *
200
+ * @return ItemsRS iterator through terms stats.
201
+ */
202
+ public function topTerms($term, $from_date = null, $to_date = null) {
203
+ if (!in_array($term, array('clicked', 'searches', 'opportunities'))) {
204
+ throw new BadRequest("The term {$term} is not allowed");
205
+ }
206
+
207
+ return new TopTermsIterator($this, $term, $from_date, $to_date);
208
+ }
209
+
210
+ /**
211
+ * Ask the server to process the search engine's feeds
212
+ *
213
+ * @return array Assoc array with:
214
+ * - 'task_created': boolean true if a new task has been created
215
+ * - 'task_id': if task created, the id of the task.
216
+ */
217
+ public function process() {
218
+ $result = $this->client->managementApiCall('POST', "{$this->hashid}/tasks/process");
219
+ $taskCreated = ($result['statusCode'] == 201);
220
+ $taskId = $taskCreated ? SearchEngine::obtainId($result['response']['link']) : null;
221
+
222
+ return array('task_created' => $taskCreated, 'task_id' => $taskId);
223
+ }
224
+
225
+ /**
226
+ * Obtain info of the last processing task sent to the server
227
+ *
228
+ * @return array Assoc array with 'state' and 'message' indicating status of the
229
+ * last asked processing task
230
+ */
231
+ public function processInfo() {
232
+ $result = $this->client->managementApiCall('GET', "{$this->hashid}/tasks/process");
233
+ unset($result['response']['task_name']);
234
+ return $result['response'];
235
+ }
236
+
237
+ /**
238
+ * Obtain info about how a task is going or its result
239
+ *
240
+ * @return array Assoc array with 'state' and 'message' indicating the status
241
+ * of the task
242
+ */
243
+ public function taskInfo($taskId) {
244
+ $result = $this->client->managementApiCall('GET', "{$this->hashid}/tasks/{$taskId}");
245
+ unset($result['response']['task_name']);
246
+ return $result['response'];
247
+ }
248
+
249
+ /**
250
+ * Obtain logs of the latest feed processing tasks done
251
+ *
252
+ * @return array list of arrays representing the logs
253
+ */
254
+ public function logs() {
255
+ $result = $this->client->managementApiCall("GET", "{$this->hashid}/logs");
256
+ return $result['response'];
257
+ }
258
+
259
+ /**
260
+ * Extracts identificator from an item or task url.
261
+ *
262
+ * @param string $url item or task resource locator
263
+ * @return the item identificator
264
+ */
265
+ private static function obtainId($url) {
266
+ preg_match('~/\w{32}/(items/\w+|tasks)/([\w-_]+)/?$~', $url, $matches);
267
+ return $matches[2];
268
+ }
269
+ }
lib/php-doofinder/src/Management/TopTermsIterator.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Management;
4
+
5
+ use Doofinder\Api\Management\SearchEngine;
6
+ use Doofinder\Api\Management\AggregatesIterator;
7
+ use Doofinder\Api\Management\Errors\NotProcessedResponse;
8
+ use Doofinder\Api\Management\Errors\MotFound;
9
+
10
+ class TopTermsIterator extends AggregatesIterator {
11
+
12
+ /**
13
+ * Class to Iterate through SearchEngine's top terms stats data for a certain period.
14
+ */
15
+ private $term = null; // type of term: 'clicked', 'searches', 'opportunities'
16
+
17
+ /**
18
+ * Constructor
19
+ *
20
+ * @param SearchEngine $searchEngine
21
+ * @param DateTime $from_date . Starting date of the period. Default: 15 days ago
22
+ * @param DateTime $to_date. Ending date of the period. Default: today.
23
+ * @param string term. type of term: 'clicked', 'searches', 'opportunities'
24
+ */
25
+ public function __construct(SearchEngine $searchEngine, $term, $from_date = null, $to_date = null) {
26
+ $this->term = $term;
27
+ parent::__construct($searchEngine, $from_date, $to_date);
28
+ }
29
+
30
+ protected function fetchResultsAndTotal() {
31
+ $params = $this->last_page > 0 ? array("page" => $this->last_page + 1) : array();
32
+ try {
33
+ $apiResponse = $this->searchEngine->client->managementApiCall(
34
+ 'GET',
35
+ "{$this->searchEngine->hashid}/stats/top_{$this->term}",
36
+ array_merge($params, $this->searchParams)
37
+ );
38
+
39
+ // still generating?
40
+ if ($apiResponse['statusCode'] == 202) {
41
+ throw new NotProcessedResponse("Your request is still being processed. Please try again later");
42
+ }
43
+
44
+ $this->resultsPage = $apiResponse['response'][$this->term];
45
+ $this->total = $apiResponse['response']['count'];
46
+ $this->last_page++;
47
+ $this->currentItem = each($this->resultsPage);
48
+ } catch (NotFound $nfe) {
49
+ $this->resultsPage = array();
50
+ }
51
+
52
+ reset($this->resultsPage);
53
+ }
54
+ }
lib/php-doofinder/src/Search/Client.php ADDED
@@ -0,0 +1,581 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Author:: JoeZ99 (<jzarate@gmail.com>).
4
+ * Author:: @carlosescri (http://github.com/carlosescri)
5
+ *
6
+ * All credit goes to Gilles Devaux (<gilles.devaux@gmail.com>)
7
+ * (https://github.com/flaptor/indextank-php)
8
+ *
9
+ * License:: Apache License, Version 2.0
10
+ *
11
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
12
+ * not use this file except in compliance with the License. You may obtain
13
+ * a copy of the License at
14
+ *
15
+ * http://www.apache.org/licenses/LICENSE-2.0
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
19
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
20
+ * License for the specific language governing permissions and limitations
21
+ * under the License.
22
+ */
23
+
24
+ namespace Doofinder\Api\Search;
25
+
26
+ use Doofinder\Api\Search\Results;
27
+ use Doofinder\Api\Search\Error;
28
+
29
+ /**
30
+ * Doofinder\Api\Client
31
+ *
32
+ * Basic client to perform requests to Doofinder search servers.
33
+ */
34
+ class Client {
35
+ const URL_SUFFIX = '-search.doofinder.com';
36
+ const DEFAULT_TIMEOUT = 10000;
37
+ const DEFAULT_RPP = 10;
38
+ const DEFAULT_PARAMS_PREFIX = 'dfParam_';
39
+ const DEFAULT_API_VERSION = '5';
40
+ const VERSION = '5.4.3';
41
+
42
+ private $api_key = null; // Authentication
43
+ private $hashid = null; // ID of the Search Engine
44
+
45
+ private $apiVersion = null;
46
+ private $url = null;
47
+ private $results = null;
48
+ private $query = null;
49
+ private $search_options = array(); // Assoc. array of request parameters
50
+
51
+ private $page = 1; // Current "page" of results
52
+ private $queryName = null; // Last successful query name
53
+ private $lastQuery = null; // Last successful query made
54
+ private $total = null; // Total number of results
55
+ private $maxScore = null;
56
+ private $paramsPrefix = self::DEFAULT_PARAMS_PREFIX;
57
+ private $serializationArray = null;
58
+ private $queryParameter = 'query';
59
+ private $allowedParameters = array('page', 'rpp', 'timeout', 'types',
60
+ 'filter', 'query_name', 'transformer',
61
+ 'sort'); // Valid parameters
62
+
63
+ /**
64
+ * Constructor.
65
+ *
66
+ * @param string $hashid Search Engine's hashid.
67
+ * @param boolean $fromParams If set, the object is unserialized from GET
68
+ * or POST params.
69
+ * @param array $init_options Associative array with some options:
70
+ *
71
+ * -'prefix' (default: 'dfParam_') Prefix to use when serializing.
72
+ * -'queryParameter' (default: 'query') Parameter used for querying.
73
+ * -'apiVersion' (default: '5') Version of the API to use.
74
+ * Valid versions: 5, 4, 3.0, 1.0
75
+ * -'restrictedRequest' (default: null) Can be 'get' or 'post'. If defined, restricts
76
+ * the request object to either $_GET or $_POST
77
+ * when unserializing to get parameters.
78
+ *
79
+ * @throws Error if $hashid is not a md5 hash or API version is not valid.
80
+ */
81
+ public function __construct($hashid, $api_key, $fromParams = false, $init_options = array()) {
82
+ $zone_key_array = explode('-', $api_key);
83
+
84
+ if (2 === count($zone_key_array)) {
85
+ $this->api_key = $zone_key_array[1];
86
+ $this->zone = $zone_key_array[0];
87
+ $this->url = "https://" . $this->zone . self::URL_SUFFIX;
88
+ } else {
89
+ throw new Error("API Key is no properly set.");
90
+ }
91
+
92
+ if (array_key_exists('prefix', $init_options)) {
93
+ $this->paramsPrefix = $init_options['prefix'];
94
+ }
95
+
96
+ $this->allowedParameters = array_map(array($this, 'addPrefix'), $this->allowedParameters);
97
+
98
+ if (array_key_exists('queryParameter', $init_options)) {
99
+ $this->queryParameter = $init_options['queryParameter'];
100
+ } else {
101
+ $this->queryParameter = $this->paramsPrefix.$this->queryParameter;
102
+ }
103
+
104
+ if (array_key_exists('apiVersion', $init_options)) {
105
+ $this->setApiVersion($init_options['apiVersion']);
106
+ } else {
107
+ $this->setApiVersion(self::DEFAULT_API_VERSION);
108
+ }
109
+
110
+ if (array_key_exists('restrictedRequest', $init_options)) {
111
+ switch(strtolower($init_options['restrictedRequest'])) {
112
+ case 'get':
113
+ $this->serializationArray = $_GET;
114
+ break;
115
+ case 'post':
116
+ $this->serializationArray = $_POST;
117
+ break;
118
+ default:
119
+ throw new Error("Wrong initialization value for 'restrictedRequest'");
120
+ }
121
+ } else {
122
+ $this->serializationArray = $_REQUEST;
123
+ }
124
+
125
+ if (!preg_match('/^[0-9a-f]{32}$/i', $hashid)) {
126
+ throw new Error("Wrong hashid");
127
+ }
128
+
129
+ $this->hashid = $hashid;
130
+
131
+ if ($fromParams) {
132
+ $this->fromQuerystring();
133
+ }
134
+ }
135
+
136
+ private function addPrefix($value) {
137
+ return $this->paramsPrefix.$value;
138
+ }
139
+
140
+ private function getRequestHeaders(){
141
+ $headers = array();
142
+ $headers[] = 'Expect:'; // Fixes HTTP/1.1 "417 Expectation Failed" Error
143
+ if ($this->authenticationHeader !== false) {
144
+ $headers[] = sprintf("%s: %s", $this->authenticationHeader, $this->api_key);
145
+ }
146
+ return $headers;
147
+ }
148
+
149
+ private function apiCall($entry_point = 'search', $params = array()){
150
+ $params['hashid'] = $this->hashid;
151
+
152
+ $session = curl_init(sprintf("%s/%s/%s?%s", $this->url,
153
+ $this->apiVersion,
154
+ $entry_point,
155
+ http_build_query($this->sanitize($params))));
156
+
157
+ // Configure cURL to return response but not headers
158
+ curl_setopt($session, CURLOPT_CUSTOMREQUEST, 'GET');
159
+ curl_setopt($session, CURLOPT_HEADER, false);
160
+ curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
161
+ curl_setopt($session, CURLOPT_HTTPHEADER, $this->getRequestHeaders());
162
+
163
+ $response = curl_exec($session);
164
+ $statusCode = curl_getinfo($session, CURLINFO_HTTP_CODE);
165
+
166
+ curl_close($session);
167
+
168
+ if (floor($statusCode / 100) == 2) {
169
+ return $response;
170
+ }
171
+
172
+ throw new Error($statusCode.' - '.$response, $statusCode);
173
+ }
174
+
175
+ public function getOptions() {
176
+ return $this->apiCall('options/'.$this->hashid);
177
+ }
178
+
179
+ /**
180
+ * query. makes the query to the doofinder search server.
181
+ * also set several search parameters through it's $options argument
182
+ *
183
+ * @param string $query the search query
184
+ * @param int $page the page number or the results to show
185
+ * @param array $options query options:
186
+ * - 'rpp'=> number of results per page. default 10
187
+ * - 'timeout' => timeout after which the search server drops the conn.
188
+ * defaults to 10 seconds
189
+ * - 'types' => types of index to search at. default: all.
190
+ * - 'filter' => filter to apply. ['color'=>['red','blue'], 'price'=>['from'=>33]]
191
+ * - any other param will be sent as a request parameter
192
+ * @return DoofinderResults results
193
+ */
194
+ public function query($query = null, $page = null, $options = array()) {
195
+ if ($query) {
196
+ $this->search_options['query'] = $query;
197
+ }
198
+
199
+ if ($page) {
200
+ $this->search_options['page'] = intval($page);
201
+ }
202
+
203
+ foreach ($options as $optionName => $optionValue) {
204
+ $this->search_options[$optionName] = $options[$optionName];
205
+ }
206
+
207
+ $params = $this->search_options;
208
+
209
+ // translate filters
210
+ if(!empty($params['filter']))
211
+ {
212
+ foreach($params['filter'] as $filterName => $filterValue){
213
+ $params['filter'][$filterName] = $this->updateFilter($filterValue);
214
+ }
215
+ }
216
+
217
+ // no query? then match all documents
218
+ if(!$this->optionExists('query') || !trim($this->search_options['query'])){
219
+ $params['query_name'] = 'match_all';
220
+ }
221
+
222
+ // if filters without query_name, pre-query first to obtain it.
223
+ if (empty($params['query_name']) && !empty($params['filter']))
224
+ {
225
+ $filter = $params['filter'];
226
+ unset($params['filter']);
227
+ $dfResults = new Results($this->apiCall('search', $params));
228
+ $params['query_name'] = $dfResults->getProperty('query_name');
229
+ $params['filter'] = $filter;
230
+ }
231
+ $dfResults = new Results($this->apiCall('search', $params));
232
+ $this->page = $dfResults->getProperty('page');
233
+ $this->total = $dfResults->getProperty('total');
234
+ $this->search_options['query'] = $dfResults->getProperty('query');
235
+ $this->maxScore = $dfResults->getProperty('max_score');
236
+ $this->queryName = $dfResults->getProperty('query_name');
237
+ $this->lastQuery = $dfResults->getProperty('query');
238
+
239
+ return $dfResults;
240
+ }
241
+
242
+ /**
243
+ * hasNext
244
+ *
245
+ * @return boolean true if there is another page of results
246
+ */
247
+ public function hasNext(){
248
+ return $this->page * $this->getRpp() < $this->total;
249
+ }
250
+
251
+ /**
252
+ * hasPrev
253
+ *
254
+ * @return true if there is a previous page of results
255
+ */
256
+ public function hasPrev(){
257
+ return ($this->page - 1) * $this->getRpp() > 0;
258
+ }
259
+
260
+
261
+ /**
262
+ * getPage
263
+ *
264
+ * obtain the current page number
265
+ * @return int the page number
266
+ */
267
+ public function getPage(){
268
+ return $this->page;
269
+ }
270
+
271
+ /**
272
+ * setFilter
273
+ *
274
+ * set a filter for the query
275
+ * @param string filterName the name of the filter to set
276
+ * @param array filter if simple array, terms filter assumed
277
+ * if 'from', 'to' in keys, range filter assumed
278
+ */
279
+ public function setFilter($filterName, $filter){
280
+ $this->search_options['filter'][$filterName] = (array) $filter;
281
+ }
282
+
283
+ /**
284
+ * getFilter
285
+ *
286
+ * get conditions for certain filter
287
+ * @param string filterName
288
+ * @return array filter conditions: - simple array if terms filter
289
+ * - 'from', 'to' assoc array if range f.
290
+ * @return false if no filter definition found
291
+ */
292
+ public function getFilter($filterName){
293
+ if (isset($this->search_options['filter'][$filterName])) {
294
+ return $this->search_options['filter'][$filterName];
295
+ }
296
+
297
+ return false;
298
+ }
299
+
300
+ /**
301
+ * getFilters
302
+ *
303
+ * get all filters and their configs
304
+ * @return array assoc array filterName => filterConditions
305
+ */
306
+ public function getFilters() {
307
+ if (isset($this->search_options['filter'])) {
308
+ return $this->search_options['filter'];
309
+ }
310
+
311
+ return array();
312
+ }
313
+
314
+ /**
315
+ * addTerm
316
+ *
317
+ * add a term to a terms filter
318
+ * @param string filterName the filter to add the term to
319
+ * @param string term the term to add
320
+ */
321
+ public function addTerm($filterName, $term) {
322
+ $this->search_options['filter'][$filterName][] = $term;
323
+ }
324
+
325
+ /**
326
+ * removeTerm
327
+ *
328
+ * remove a term from a terms filter
329
+ * @param string filterName the filter to remove the term from
330
+ * @param string term the term to be removed
331
+ */
332
+ public function removeTerm($filterName, $term) {
333
+ if (isset($this->search_options['filter'][$filterName])) {
334
+ $idx = array_search($term, $this->search_options['filter'][$filterName]);
335
+ if ($idx !== false) {
336
+ array_splice($this->search_options['filter'][$filterName], $idx, 1);
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * setRange
343
+ *
344
+ * set a range filter
345
+ * @param string filterName the filter to set
346
+ * @param int from the lower bound value. included
347
+ * @param int to the upper bound value. included
348
+ */
349
+ public function setRange($filterName, $from = null, $to = null) {
350
+ if (!is_null($from))
351
+ {
352
+ $this->search_options['filter'][$filterName]['from'] = $from;
353
+ }
354
+ if (!is_null($to))
355
+ {
356
+ $this->search_options['filter'][$filterName]['to'] = $to;
357
+ }
358
+ }
359
+
360
+ /**
361
+ * addSort
362
+ *
363
+ * Tells doofinder to sort results, or add a new field to a multiple fields sort
364
+ * @param string fieldName the field to sort by
365
+ * @param string direction 'asc' o 'desc'.
366
+ */
367
+ public function addSort($sortName, $direction) {
368
+ $this->search_options['sort'][] = array($sortName => $direction);
369
+ }
370
+
371
+ /**
372
+ * toQuerystring
373
+ *
374
+ * 'serialize' the object's state to querystring params
375
+ * @param int $page the pagenumber. defaults to the current page
376
+ */
377
+ public function toQuerystring($page = null){
378
+ $toParams = array();
379
+
380
+ foreach ($this->search_options as $paramName => $paramValue) {
381
+ if ($paramName == 'query') {
382
+ $toParams[$this->queryParameter] = $paramValue;
383
+ } else {
384
+ $toParams[$this->paramsPrefix.$paramName] = $paramValue;
385
+ }
386
+ }
387
+
388
+ if (!is_null($page)) {
389
+ $toParams[$this->paramsPrefix.'page'] = $page;
390
+ }
391
+
392
+ return http_build_query($toParams);
393
+ }
394
+
395
+ /**
396
+ * fromQuerystring
397
+ *
398
+ * obtain object's state from querystring params
399
+ * @param string $params where to obtain params from:
400
+ * - 'GET' $_GET params (default)
401
+ * - 'POST' $_POST params
402
+ */
403
+ public function fromQuerystring(){
404
+ $filteredParams = array_filter(array_keys($this->serializationArray),
405
+ array($this, 'belongsToDoofinder'));
406
+
407
+ foreach ($filteredParams as $param) {
408
+ if ($param == $this->queryParameter) {
409
+ $key = 'query';
410
+ } else {
411
+ $key = substr($param, strlen($this->paramsPrefix));
412
+ }
413
+
414
+ $this->search_options[$key] = $this->serializationArray[$param];
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Ensures that range filters uses the most up to date syntax.
420
+ *
421
+ * array('from' => 9, 'to' => 20, 'other': 10)
422
+ *
423
+ * is converted into:
424
+ *
425
+ * array('gte' => 9, 'lte' => 20, 'other': 10)
426
+ *
427
+ * @param array $filter Filter definition.
428
+ * @return array Updated filter.
429
+ */
430
+ private function updateFilter($filter) {
431
+ $new_filter = array();
432
+
433
+ foreach($filter as $key => $value) {
434
+ if ($key === 'from') {
435
+ $new_filter['gte'] = $value;
436
+ } else if ($key === 'to') {
437
+ $new_filter['lte'] = $value;
438
+ } else {
439
+ $new_filter[$key] = $value;
440
+ }
441
+ }
442
+
443
+ return $new_filter;
444
+ }
445
+
446
+ /**
447
+ * Cleans $params array by removing keys with empty/null values.
448
+ *
449
+ * @param array $params Array to be cleaned.
450
+ * @return array Array with no empty keys.
451
+ */
452
+ private function sanitize($params) {
453
+ $result = array();
454
+
455
+ foreach ($params as $name => $value) {
456
+ if (is_array($value)) {
457
+ $result[$name] = $this->sanitize($value);
458
+ } else if (trim($value)) {
459
+ $result[$name] = $value;
460
+ }
461
+ }
462
+
463
+ return $result;
464
+ }
465
+
466
+ /**
467
+ * belongsToDoofinder
468
+ *
469
+ * to know if certain parameter name belongs to doofinder serialization parameters
470
+ *
471
+ * @param string $paramName name of the param
472
+ * @return boolean true or false.
473
+ */
474
+ private function belongsToDoofinder($paramName){
475
+ if ($pos = strpos($paramName, '[')) {
476
+ $paramName = substr($paramName, 0, $pos);
477
+ }
478
+
479
+ return in_array($paramName, $this->allowedParameters) || $paramName == $this->queryParameter;
480
+ }
481
+
482
+ /**
483
+ * optionExists
484
+ *
485
+ * checks whether a search option is defined in $this->search_options
486
+ *
487
+ * @param string $optionName
488
+ * @return boolean
489
+ */
490
+ private function optionExists($optionName) {
491
+ return array_key_exists($optionName, $this->search_options);
492
+ }
493
+
494
+ /**
495
+ * nextPage
496
+ *
497
+ * obtain the results for the next page
498
+ * @return DoofinderResults if there are results.
499
+ * @return null otherwise
500
+ */
501
+ public function nextPage() {
502
+ return $this->hasNext() ? $this->query($this->lastQuery, $this->page + 1) : null;
503
+ }
504
+
505
+
506
+ /**
507
+ * prevPage
508
+ *
509
+ * obtain results for the previous page
510
+ * @return DoofinderResults
511
+ * @return null otherwise
512
+ */
513
+ public function prevPage() {
514
+ return $this->hasPrev() ? $this->query($this->lastQuery, $this->page - 1) : null;
515
+ }
516
+
517
+ /**
518
+ * numPages
519
+ *
520
+ * @return integer the number of pages
521
+ */
522
+ public function numPages() {
523
+ return ceil($this->total / $this->getRpp());
524
+ }
525
+
526
+ public function getRpp() {
527
+ $rpp = $this->optionExists('rpp') ? $this->search_options['rpp'] : null;
528
+ return $rpp ? $rpp : self::DEFAULT_RPP;
529
+ }
530
+
531
+ /**
532
+ * setApiVersion
533
+ *
534
+ * sets the api version to use.
535
+ * @param string $apiVersion the api version , '1.0', '3.0', '4' or '5'
536
+ */
537
+ public function setApiVersion($apiVersion) {
538
+ switch (true) {
539
+ case in_array($apiVersion, array('1.0', '3.0')):
540
+ $this->authenticationHeader = false;
541
+ break;
542
+ case intval($apiVersion) == 4:
543
+ $this->authenticationHeader = 'API Token';
544
+ break;
545
+ case intval($apiVersion) == 5:
546
+ $this->authenticationHeader = 'Authorization';
547
+ break;
548
+ default:
549
+ throw new Error('Wrong API Version');
550
+ }
551
+
552
+ $this->apiVersion = $apiVersion;
553
+ }
554
+
555
+ /**
556
+ * setPrefix
557
+ *
558
+ * sets the prefix that will be used for serialization to querystring params
559
+ * @param string $prefix the prefix
560
+ */
561
+ public function setPrefix($prefix) {
562
+ $this->paramsPrefix = $prefix;
563
+ }
564
+
565
+ /**
566
+ * getFilterType
567
+ * obtain the filter type (i.e. 'terms' or 'numeric range' from its conditions)
568
+ * @param array filter conditions
569
+ * @return string 'terms' or 'numericrange' false otherwise
570
+ */
571
+ private function getFilterType($filter) {
572
+ if (is_array($filter)) {
573
+ if (array_key_exists('from', $filter) || array_key_exists('to', $filter)) {
574
+ return 'numericrange';
575
+ } else {
576
+ return 'term';
577
+ }
578
+ }
579
+ return false;
580
+ }
581
+ }
lib/php-doofinder/src/Search/Error.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Search;
4
+
5
+ /**
6
+ * Doofinder\Api\Search\Error
7
+ *
8
+ * Represents an error returned to the user of the library.
9
+ */
10
+ class Error extends \Exception {}
lib/php-doofinder/src/Search/Results.php ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Doofinder\Api\Search;
3
+
4
+ /**
5
+ * @author JoeZ99 <jzarate@gmail.com>
6
+ * @author carlosescri <carlosescri@gmail.com>
7
+ *
8
+ * Results
9
+ *
10
+ * Very thin wrapper of the results obtained from the doofinder server
11
+ * it holds two accessors:
12
+ *
13
+ * - getProperty : get single property of the search results (rpp, page, etc..)
14
+ * - getResults: get an array with the results
15
+ */
16
+ class Results {
17
+
18
+ // doofinder status
19
+ const SUCCESS = 'success'; // everything ok
20
+ const NOTFOUND = 'notfound'; // no account with the provided hashid found
21
+ const EXHAUSTED = 'exhausted'; // the account has reached its query limit
22
+
23
+ private $properties = array();
24
+ private $results = array();
25
+ private $facets = array();
26
+ private $filter = array();
27
+
28
+ public $status = null;
29
+
30
+ /**
31
+ * Constructor
32
+ *
33
+ * @param string $jsonResponse JSON returned by the Doofinder search server
34
+ */
35
+ public function __construct($jsonResponse) {
36
+ $response = json_decode($jsonResponse, true);
37
+
38
+ foreach ($response as $key => $value){
39
+ if (!is_array($value)) {
40
+ $this->properties[$key] = $value;
41
+ }
42
+ }
43
+
44
+ // Status
45
+ if (isset($this->properties['doofinder_status'])) {
46
+ $this->status = $this->properties['doofinder_status'];
47
+ } else {
48
+ $this->status = self::SUCCESS;
49
+ }
50
+
51
+ // Results
52
+ if (isset($response['results'])) {
53
+ $this->results = $response['results'];
54
+ }
55
+
56
+ // Build a "friendly" filters array
57
+ if (isset($response['filter'])) {
58
+ foreach($response['filter'] as $filterType => $filters) {
59
+ foreach($filters as $filterName => $filterProperties) {
60
+ $this->filter[$filterName] = $filterProperties;
61
+ }
62
+ }
63
+ }
64
+
65
+ // facets
66
+ if (isset($response['facets'])) {
67
+ $this->facets = $response['facets'];
68
+ }
69
+
70
+ // mark "selected" true or false according to filters presence
71
+ foreach ($this->facets as $name => $properties) {
72
+ switch (true) {
73
+ case isset($properties['terms']):
74
+ foreach($properties['terms']['buckets'] as $idx => $bucket) {
75
+ $this->facets[$name]['terms']['buckets'][$idx]['selected'] = isset($this->filter[$name]) &&
76
+ in_array($bucket['key'], $this->filter[$name]);
77
+ }
78
+ break;
79
+ case isset($properties['range']):
80
+ foreach(array_keys($properties['range']['buckets']) as $idx) {
81
+ $this->facets[$name]['range']['buckets'][$idx]['selected_from'] = isset($this->filter[$name]['gte']) ? $this->filter[$name]['gte'] : false;
82
+ $this->facets[$name]['range']['buckets'][$idx]['selected_to'] = isset($this->filter[$name]['lte']) ? $this->filter[$name]['lte'] : false;
83
+ }
84
+ break;
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * getProperty
91
+ *
92
+ * get single property from the results
93
+ * @param string $name: 'results_per_page', 'query', 'max_score', 'page', 'total', 'hashid'
94
+ * @return mixed the value of the property
95
+ */
96
+ public function getProperty($name) {
97
+ return array_key_exists($name, $this->properties) ? $this->properties[$name] : null;
98
+ }
99
+
100
+ /**
101
+ * getResults
102
+ *
103
+ * @return array search results. at the moment, only the 'cooked' version.
104
+ * Each result is of the form:
105
+ * array('header'=>...,
106
+ * 'body' => ..,
107
+ * 'price' => ..,
108
+ * 'href' => ...,
109
+ * 'image' => ...,
110
+ * 'type' => ...,
111
+ * 'id' => ..)
112
+ */
113
+ public function getResults(){
114
+ return $this->results;
115
+ }
116
+
117
+ /**
118
+ *
119
+ * getFacetsNames
120
+ *
121
+ * @return array facets names.
122
+ */
123
+ public function getFacetsNames(){
124
+ return array_keys($this->facets);
125
+ }
126
+
127
+ /**
128
+ * getFacet
129
+ *
130
+ * @param string name the facet name whose results are wanted
131
+ *
132
+ * @return array facet search data
133
+ * - for terms facets
134
+ * array(
135
+ * '_type'=> 'terms', // type of facet 'terms' or 'range'
136
+ * 'missing'=> 3, // # of elements with no value for this facet
137
+ * 'others'=> 2, // # of terms not present in the search response
138
+ * 'total'=> 6, // # number of possible terms for this facet
139
+ * 'terms'=> array(
140
+ * array('count'=>6, 'term'=>'Blue', 'selected'=>false), // in the response, there are 6 'blue' terms
141
+ * array('count'=>3, 'term': 'Red', 'selected'=>true), // if 'selected'=>true, that term has been selected as filter
142
+ * ...
143
+ * )
144
+ * )
145
+ * - for range facets
146
+ * array(
147
+ * '_type'=> 'range',
148
+ * 'ranges'=> array(
149
+ * array(
150
+ * 'count'=>6, // in the response, 6 elements within that range.
151
+ * 'from':0,
152
+ * 'min': 30
153
+ * 'max': 90,
154
+ * 'mean'=>33.2,
155
+ * 'total'=>432,
156
+ * 'total_count'=>6,
157
+ * 'selected_from'=> 34.3 // if present. this value has been used as filter. false otherwise
158
+ * 'selected_to'=> 99.3 // if present. this value has been used as filter. false otherwise
159
+ * ),
160
+ * ...
161
+ * )
162
+ * )
163
+ *
164
+ *
165
+ */
166
+ public function getFacet($facetName){
167
+ return $this->facets[$facetName];
168
+ }
169
+
170
+ /**
171
+ * getFacets
172
+ *
173
+ * get the whole facets associative array:
174
+ * array('color'=>array(...), 'brand'=>array(...))
175
+ * each array is defined as in getFacet() docstring
176
+ *
177
+ * @return array facets assoc. array
178
+ */
179
+ public function getFacets(){
180
+ return $this->facets;
181
+ }
182
+
183
+ /**
184
+ * getAppliedFilters
185
+ *
186
+ * get the filters the query has defined
187
+ * array('categories' => array( // filter name . same as facet name
188
+ * 'Sillas de paseo', // if simple array, it's a terms facet
189
+ * 'Sacos sillas de paseo'
190
+ * ),
191
+ * 'color' => array(
192
+ * 'red',
193
+ * 'blue'
194
+ * ),
195
+ * 'price' => array(
196
+ * 'include_upper'=>true, // if 'from' , 'to' keys, it's a range facet
197
+ * 'from'=>35.19,
198
+ * 'to'=>9999
199
+ * )
200
+ * )
201
+ * MEANING OF THE EXAMPLE FILTER:
202
+ * "FROM the query results, filter only results that have ('Sillas de paseo' OR 'Sacos sillas de paseo') categories
203
+ * AND ('red' OR 'blue') color AND price is BETWEEN 34.3 and 99.3"
204
+
205
+ */
206
+ public function getAppliedFilters(){
207
+ return $this->filter;
208
+ }
209
+
210
+ /**
211
+ * isOk
212
+ *
213
+ * checks if all went well
214
+ * @return boolean true if the status is 'success'.
215
+ * false if the status is not.
216
+ */
217
+ public function isOk() {
218
+ return $this->status == self::SUCCESS;
219
+ }
220
+ }
lib/php-doofinder/src/Test/Management/AggregatesIteratorTest.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test;
4
+
5
+ use Doofinder\Api\Management\AggregatesIterator;
6
+ use Doofinder\Api\Management\SearchEngine;
7
+
8
+
9
+ class AggregatesIteratorTest extends \PHPUnit_Framework_TestCase
10
+ {
11
+ protected function setUp()
12
+ {
13
+ $this->client = $this->prophesize('\Doofinder\Api\Management\Client');
14
+ $this->searchEngine = new SearchEngine($this->client->reveal(), 'testHashid', 'test SE');
15
+ }
16
+
17
+ public function testIterateThroughAllAggregates()
18
+ {
19
+ $firstResponse = array(
20
+ "next"=> "someUrl", "count"=>4, "previous"=>null, "aggregates" => array(
21
+ array("date"=> "2006-10-20", "searches"=> 1, "requests"=>33),
22
+ array("date"=> "2006-10-21", "searches"=> 2, "requests"=>34)
23
+ )
24
+ );
25
+ $secondResponse = array(
26
+ "next"=> "someUrl", "count"=>4, "previous"=>null, "aggregates" => array(
27
+ array("date"=> "2006-10-21", "searches"=> 3, "requests"=>33),
28
+ array("date"=> "2006-10-22", "searches"=> 4, "requests"=>34)
29
+ )
30
+ );
31
+
32
+ $thirdResponse = array(
33
+ "next"=> "someUrl", "count"=>6, "previous"=>null, "aggregates" => array()
34
+ );
35
+
36
+ // after receiving second request, prepare for third request and send second response
37
+ $prepareForThirdRequest = function($args, $client) use ($secondResponse, $thirdResponse) {
38
+ $client->managementApiCall(
39
+ 'GET', 'testHashid/stats',
40
+ array('from'=>'20161020', 'to'=>'20161023', 'page'=>2)
41
+ )
42
+ ->shouldBeCalledTimes(1)
43
+ ->willReturn($thirdResponse);
44
+ return array('response'=>$secondResponse);
45
+ };
46
+
47
+ // after receiving first request, prepare for third request and send second response
48
+ $prepareForSecondRequest = function($args, $client) use ($prepareForThirdRequest, $firstResponse) {
49
+ $client->managementApiCall(
50
+ 'GET', 'testHashid/stats',
51
+ array('from'=>'20161020', 'to'=>'20161023', 'page'=>2)
52
+ )
53
+ ->shouldBeCalledTimes(1)
54
+ ->will($prepareForThirdRequest);
55
+ return array('response'=>$firstResponse);
56
+ };
57
+
58
+ // first request
59
+ $this->client->managementApiCall(
60
+ 'GET', 'testHashid/stats', array('from'=>'20161020', 'to'=>'20161023')
61
+ )
62
+ ->shouldBeCalledTimes(1)
63
+ ->will($prepareForSecondRequest);
64
+
65
+
66
+ $aggregates = new AggregatesIterator(
67
+ $this->searchEngine, new \Datetime('2016-10-20'), new \Datetime('2016-10-23')
68
+ );
69
+
70
+ $counter = 0;
71
+ foreach($aggregates as $agg){
72
+ $counter++;
73
+ $this->assertEquals($counter, $agg['searches']);
74
+ }
75
+ }
76
+
77
+ }
lib/php-doofinder/src/Test/Management/ClientTest.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test\Management;
4
+
5
+ use Doofinder\Api\Management\Client;
6
+
7
+ class ClientTest extends \PHPUnit_Framework_TestCase
8
+ {
9
+
10
+ protected function setUp()
11
+ {
12
+ $this->client = $this->getMockBuilder('Doofinder\Api\Management\Client')
13
+ ->setConstructorArgs(array('xx1-testApiKey'))
14
+ ->setMethods(array('talkToServer'))
15
+ ->getMock();
16
+ }
17
+
18
+ public function testManagementApiCallUrlAndHeaders()
19
+ {
20
+ // right zone-url formation
21
+ // right auth headers
22
+ $this->client->expects($this->once())
23
+ ->method('talkToServer')
24
+ ->with(
25
+ $this->equalTo('GET'),
26
+ $this->equalTo('https://xx1-api.doofinder.com/v1/'),
27
+ $this->equalTo(array("Authorization: Token testApiKey",
28
+ 'Content-Type: application/json',
29
+ 'Expect:'))
30
+ )
31
+ ->willReturn(array('statusCode'=>200, 'contentResponse'=>''));
32
+
33
+ $this->client->getApiRoot();
34
+ }
35
+
36
+ public function testManagementApiCallEntryPointAndParams()
37
+ {
38
+ $expectedUrl = 'https://xx1-api.doofinder.com/v1/testHashid/items/product?foo=bar&cat%5B0%5D=cat1&cat%5B1%5D=cat2';
39
+ // right method, url and url params
40
+ $this->client->expects($this->once())
41
+ ->method('talkToServer')
42
+ ->with(
43
+ $this->equalTo('GET'),
44
+ $this->equalTo($expectedUrl),
45
+ $this->equalTo(array("Authorization: Token testApiKey",
46
+ 'Content-Type: application/json',
47
+ 'Expect:'))
48
+ )
49
+ ->willReturn(array('statusCode'=>200, 'contentResponse'=>''));
50
+
51
+ $this->client->managementApiCall(
52
+ 'GET', 'testHashid/items/product', array('foo'=>'bar', 'cat'=>array('cat1', 'cat2'))
53
+ );
54
+ }
55
+
56
+ public function testManagementApiCallGETMethodAndNoData()
57
+ {
58
+ $expectedUrl = 'https://xx1-api.doofinder.com/v1/testHashid/tasks/';
59
+
60
+ // with no POST, no DATA
61
+ $this->client->expects($this->once())
62
+ ->method('talkToServer')
63
+ ->with(
64
+ $this->equalTo('GET'),
65
+ $this->equalTo($expectedUrl),
66
+ $this->equalTo(array("Authorization: Token testApiKey",
67
+ 'Content-Type: application/json',
68
+ 'Expect:')),
69
+ null
70
+ )
71
+ ->willReturn(array('statusCode'=>200, 'contentResponse'=>''));
72
+ $this->client->managementApiCall('GET', 'testHashid/tasks/', null, array('custom'=>'data'));
73
+ }
74
+
75
+ public function testManagementApiCallPOSTMethodAndData(){
76
+ $expectedUrl = 'https://xx1-api.doofinder.com/v1/testHashid/tasks/';
77
+ // with DATA, POST
78
+ $this->client->expects($this->once())
79
+ ->method('talkToServer')
80
+ ->with(
81
+ $this->equalTo('POST'),
82
+ $this->equalTo($expectedUrl),
83
+ $this->equalTo(array("Authorization: Token testApiKey",
84
+ 'Content-Type: application/json',
85
+ 'Expect:')),
86
+ array('custom'=>'data')
87
+ )
88
+ ->willReturn(array('statusCode'=>200, 'contentResponse'=>''));
89
+
90
+ $this->client->managementApiCall('POST', 'testHashid/tasks/', null, array('custom'=>'data'));
91
+
92
+ }
93
+
94
+ }
lib/php-doofinder/src/Test/Management/ScrollIteratorTest.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test;
4
+
5
+ use Doofinder\Api\Management\ScrollIterator;
6
+ use Doofinder\Api\Management\SearchEngine;
7
+
8
+
9
+ class ScrollIteratorTest extends \PHPUnit_Framework_TestCase
10
+ {
11
+ protected function setUp()
12
+ {
13
+ $this->client = $this->prophesize('\Doofinder\Api\Management\Client');
14
+ $this->searchEngine = new SearchEngine($this->client->reveal(), 'testHashid', 'test SE');
15
+ }
16
+
17
+ public function testIterateThroughAllResults()
18
+ {
19
+ $firstResponse = array(
20
+ "next"=> "someUrl", "count"=>6, "scroll_id"=>"testScroll", "results" => array(
21
+ array("id"=> "id1", "title"=> "title1"),
22
+ array("id"=> "id2", "title"=> "title2"),
23
+ array("id"=> "id3", "title"=> "title3")
24
+ )
25
+ );
26
+ $secondResponse = array(
27
+ "next"=> "someUrl", "count"=>6, "scroll_id"=>"testScroll", "results" => array(
28
+ array("id"=> "id4", "title"=> "title4"),
29
+ array("id"=> "id5", "title"=> "title5"),
30
+ array("id"=> "id6", "title"=> "title6")
31
+ )
32
+ );
33
+ $thirdResponse = array(
34
+ "next"=> "someUrl", "count"=>6, "scroll_id"=>"testScroll", "results" => array()
35
+ );
36
+
37
+ // after receiving second request, prepare for third request and send second response
38
+ $reassignMethodSignature = function($args, $client) use ($secondResponse, $thirdResponse) {
39
+ $client->managementApiCall(
40
+ 'GET', 'testHashid/items/newType', array('scroll_id' => 'testScroll')
41
+ )
42
+ ->shouldBeCalledTimes(1)
43
+ ->willReturn(array('response'=>$thirdResponse));
44
+ return array('response'=>$secondResponse);
45
+ };
46
+
47
+ // first request
48
+ $this->client->managementApiCall('GET', 'testHashid/items/newType', null)
49
+ ->shouldBeCalledTimes(1)
50
+ ->willReturn(array('response'=>$firstResponse));
51
+
52
+ // second request
53
+ $this->client->managementApiCall(
54
+ 'GET', 'testHashid/items/newType', array('scroll_id'=>'testScroll'))
55
+ ->shouldBeCalledTimes(1)
56
+ ->will($reassignMethodSignature);
57
+
58
+ $items = new ScrollIterator($this->searchEngine, 'newType');
59
+
60
+ $counter = 0;
61
+ foreach($items as $item){
62
+ $counter++;
63
+ $this->assertEquals("id$counter",$item['id']);
64
+ }
65
+
66
+ }
67
+ }
lib/php-doofinder/src/Test/Management/SearchEngineTest.php ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test\Management;
4
+
5
+ use Doofinder\Api\Management\SearchEngine;
6
+
7
+
8
+ class SearchEngineTest extends \PHPUnit_Framework_TestCase
9
+ {
10
+
11
+ protected function setUp()
12
+ {
13
+ $this->client = $this->prophesize('\Doofinder\Api\Management\Client');
14
+ $this->searchEngine = new SearchEngine($this->client->reveal(), 'testHashid', 'test SE');
15
+ }
16
+
17
+ public function testGetTypesApiCall()
18
+ {
19
+ // Set up the expectation for the managemetApiCall() method
20
+ // to be called only once and with the strings 'GET' and 'testHashid/types'
21
+ // as its parameters.
22
+ $this->client->managementApiCall('GET', 'testHashid/types')->shouldBeCalledTimes(2);
23
+
24
+ $this->searchEngine->getTypes();
25
+ $this->searchEngine->getDataTypes();
26
+ }
27
+
28
+ public function testAddTypesApiCall()
29
+ {
30
+ $this->client->managementApiCall(
31
+ 'POST',
32
+ 'testHashid/types',
33
+ null,
34
+ json_encode(array('name'=>'newType'))
35
+ )->shouldBeCalledTimes(1);
36
+
37
+ $this->searchEngine->addType('newType');
38
+ }
39
+
40
+ public function testDeleteTypeApiCall()
41
+ {
42
+ $this->client->managementApiCall(
43
+ 'DELETE',
44
+ 'testHashid/types/newType'
45
+ )->shouldBeCalledTimes(1);
46
+
47
+ $this->searchEngine->deleteType('newType');
48
+ }
49
+
50
+ public function testItemsReturnsScrollIterator()
51
+ {
52
+ $scrollIterator = $this->searchEngine->items('newType');
53
+ // returns a ScrollIterator
54
+ $this->assertInstanceOf('\Doofinder\Api\Management\ScrollIterator', $scrollIterator);
55
+ // for the right Datatype
56
+ $this->assertEquals('newType', $this->exposeAttribute($scrollIterator, 'datatype'));
57
+ }
58
+
59
+ public function testGetItemApiCall()
60
+ {
61
+ $this->client->managementApiCall(
62
+ 'GET',
63
+ 'testHashid/items/newType/itemId'
64
+ )->shouldBeCalledTimes(1);
65
+
66
+ $this->searchEngine->getItem('newType', 'itemId');
67
+ }
68
+
69
+ public function testAddItemApiCall()
70
+ {
71
+ $item = array(
72
+ "id"=> "id1",
73
+ "title"=> "my title",
74
+ "categories"=> array("cat1", "cat2")
75
+ );
76
+ $this->client->managementApiCall(
77
+ 'POST',
78
+ 'testHashid/items/newType',
79
+ null,
80
+ json_encode($item)
81
+ )->shouldBeCalledTimes(1)->willReturn(array('response'=>array('id'=>'id1')));
82
+
83
+ $this->assertEquals('id1', $this->searchEngine->addItem('newType', $item));
84
+
85
+ }
86
+
87
+ public function testAddItemsApiCall()
88
+ {
89
+ $items = array(
90
+ array(
91
+ "id"=> "id1",
92
+ "title"=> "my title",
93
+ "categories"=> array("cat1", "cat2")
94
+ ),
95
+ array(
96
+ "id"=> "id2",
97
+ "title"=> "my title2",
98
+ "categories"=> array("cat1", "cat2")
99
+ )
100
+ );
101
+ $this->client->managementApiCall(
102
+ 'POST',
103
+ 'testHashid/items/newType',
104
+ null,
105
+ json_encode($items)
106
+ )->shouldBeCalledTimes(1)->willReturn(
107
+ array(
108
+ 'response'=>array(
109
+ array('id'=>'id1'), array('id'=>'id2')
110
+ )
111
+ )
112
+ );
113
+
114
+ $result = $this->searchEngine->addItems('newType', $items);
115
+ // returns ids inserted
116
+ $this->assertEquals(array('id1', 'id2'), $result);
117
+ }
118
+
119
+ public function testUpdateItemApiCall()
120
+ {
121
+ $item = array("id"=>"id1", "title"=>"title1", "cats"=>array("cat1", "cat2"));
122
+ $this->client->managementApiCall(
123
+ 'PUT',
124
+ 'testHashid/items/newType/idx',
125
+ null,
126
+ json_encode($item)
127
+ )->shouldBeCalledTimes(1)->willReturn(
128
+ array('statusCode' => 200));
129
+
130
+ $this->assertTrue($this->searchEngine->updateItem('newType', 'idx', $item));
131
+ }
132
+
133
+ public function testUpdateItemsApiCall()
134
+ {
135
+ $items = array(
136
+ array('id'=>'id1', 't'=>'t1', 'cats'=>array('cat1', 'cat2')),
137
+ array('id'=>'id2', 't'=>'t2', 'cats'=>array('cat1', 'cat2'))
138
+ );
139
+ $this->client->managementApiCall(
140
+ 'PUT', 'testHashid/items/newType', null, json_encode($items)
141
+ )->willReturn(array('statusCode'=>200));
142
+
143
+ $this->assertTrue($this->searchEngine->updateItems('newType', $items));
144
+ }
145
+
146
+ public function testDeleteItemApiCall()
147
+ {
148
+ $this->client->managementApiCall('DELETE', 'testHashid/items/newType/id1')
149
+ -> willReturn(array('statusCode'=>204));
150
+ $this->assertTrue($this->searchEngine->deleteItem('newType', 'id1'));
151
+ }
152
+
153
+ public function testDeleteItemsApiCall()
154
+ {
155
+ $this->client->managementApiCall(
156
+ 'DELETE', 'testHashid/items/newType', null,
157
+ json_encode(array(array('id'=>'id1'), array('id'=>'id2'), array('id'=>'id3')))
158
+ )
159
+ -> willReturn(
160
+ array(
161
+ 'response'=>array(
162
+ 'error'=>array(),
163
+ 'success'=>array('id1', 'id2', 'id3')
164
+ )
165
+ )
166
+ );
167
+ $this->assertEquals(
168
+ $this->searchEngine->deleteItems('newType', array('id1', 'id2', 'id3')),
169
+ array('error'=>array(), 'success'=>array('id1', 'id2', 'id3'))
170
+ );
171
+ }
172
+
173
+ /**
174
+ * @param \DateTime $from_date
175
+ * @param \DateTime $to_date
176
+ *
177
+ * @dataProvider providerTestStatsReturnsAggregatesIteratorWithDifferentDates
178
+ */
179
+ public function testStatsReturnsAggregatesIteratorWithDifferentDates($from_date, $to_date)
180
+ {
181
+ if(is_null($from_date)){
182
+ $aggregatesIterator = $this->searchEngine->stats();
183
+ } else {
184
+ $aggregatesIterator = $this->searchEngine->stats($from_date, $to_date);
185
+ }
186
+ // returns a aggregatesIterator
187
+ $this->assertInstanceOf(
188
+ '\Doofinder\Api\Management\AggregatesIterator', $aggregatesIterator
189
+ );
190
+ // with the right searchEngine
191
+ $this->assertEquals(
192
+ $this->searchEngine, $this->exposeAttribute($aggregatesIterator, 'searchEngine')
193
+ );
194
+ // and proper dates
195
+ if(is_null($from_date)){
196
+ $expected = array();
197
+ } else {
198
+ $expected = array('from'=>$from_date->format("Ymd"), 'to'=>$to_date->format("Ymd"));
199
+ }
200
+ $this->assertEquals(
201
+ $expected, $this->exposeAttribute($aggregatesIterator, 'searchParams')
202
+ );
203
+ }
204
+
205
+
206
+
207
+ public function providerTestStatsReturnsAggregatesIteratorWithDifferentDates()
208
+ {
209
+ return array(
210
+ array(null, null), //default
211
+ array(new \DateTime("20011-01-07"), new \DateTime("2011-02-07"))
212
+ );
213
+ }
214
+
215
+ /**
216
+ * @param string term
217
+ * @param \DateTime $from_date
218
+ * @param \DateTime $to_date
219
+ *
220
+ * @dataProvider providerTestTopTermsReturnsTopTermsIteratorWithDifferentDatesAndTerms
221
+ */
222
+ public function testTopTermsReturnsTopTermsIteratorWithDifferentDatesAndTerms($term, $from_date, $to_date)
223
+ {
224
+ if(is_null($from_date)){
225
+ $iterator = $this->searchEngine->topTerms($term);
226
+ } else {
227
+ $iterator = $this->searchEngine->topTerms($term, $from_date, $to_date);
228
+ }
229
+ // returns a aggregatesIterator
230
+ $this->assertInstanceOf(
231
+ '\Doofinder\Api\Management\TopTermsIterator', $iterator
232
+ );
233
+ // with the right searchEngine
234
+ $this->assertEquals(
235
+ $this->searchEngine, $this->exposeAttribute($iterator, 'searchEngine')
236
+ );
237
+ // and proper dates
238
+ if(is_null($from_date)){
239
+ $expected = array();
240
+ } else {
241
+ $expected = array('from'=>$from_date->format("Ymd"), 'to'=>$to_date->format("Ymd"));
242
+ }
243
+ $this->assertEquals(
244
+ $expected, $this->exposeAttribute($iterator, 'searchParams')
245
+ );
246
+ // and proter term
247
+ $this->assertEquals(
248
+ $term, $this->exposeAttribute($iterator, 'term')
249
+ );
250
+ }
251
+
252
+
253
+
254
+ public function providerTestTopTermsReturnsTopTermsIteratorWithDifferentDatesAndTerms()
255
+ {
256
+ return array(
257
+ array('clicked', null, null), //default
258
+ array('clicked', new \DateTime("20011-01-07"), new \DateTime("2011-02-07")),
259
+ array('searches', null, null), //default
260
+ array('searches', new \DateTime("20011-01-07"), new \DateTime("2011-02-07")),
261
+ array('opportunities', null, null), //default
262
+ array('opportunities', new \DateTime("20011-01-07"), new \DateTime("2011-02-07"))
263
+ );
264
+ }
265
+
266
+ /**
267
+ * @expectedException \Doofinder\Api\Management\Errors\BadRequest
268
+ */
269
+ public function testTopTermsReturnsBadRequestWithWrongTerm()
270
+ {
271
+ $this->searchEngine->topTerms('bad_term');
272
+ }
273
+
274
+ public function testProcessApiCall()
275
+ {
276
+ $this->client->managementApiCall('POST','testHashid/tasks/process')
277
+ ->shouldBeCalledTimes(1)
278
+ ->willReturn(
279
+ array(
280
+ 'statusCode' => 201,
281
+ 'response' => array(
282
+ 'link' => '/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/tasks/task_id'
283
+ )
284
+ )
285
+ );
286
+ $this->assertEquals(
287
+ $this->searchEngine->process(), array('task_created'=>true, 'task_id'=>'task_id')
288
+ );
289
+
290
+ }
291
+
292
+ public function testProcessInfoApiCall()
293
+ {
294
+ $this->client->managementApiCall('GET','testHashid/tasks/process')
295
+ ->shouldBeCalledTimes(1)
296
+ ->willReturn(
297
+ array(
298
+ 'response' => array(
299
+ 'state' => 'RUNNING',
300
+ 'message' => 'ok'
301
+ )
302
+ )
303
+ );
304
+ $this->assertEquals(
305
+ $this->searchEngine->processInfo(), array('state'=>'RUNNING', 'message' => 'ok')
306
+ );
307
+ }
308
+
309
+ public function testTaskInfoApiCall()
310
+ {
311
+ $this->client->managementApiCall('GET','testHashid/tasks/task-long-id')
312
+ ->shouldBeCalledTimes(1)
313
+ ->willReturn(
314
+ array(
315
+ 'response' => array(
316
+ 'state' => 'RUNNING',
317
+ 'message' => 'ok'
318
+ )
319
+ )
320
+ );
321
+ $this->assertEquals(
322
+ $this->searchEngine->taskInfo('task-long-id'),
323
+ array('state'=>'RUNNING', 'message' => 'ok')
324
+ );
325
+ }
326
+
327
+ public function testLogsApiCall()
328
+ {
329
+ $this->client->managementApiCall('GET','testHashid/logs')
330
+ ->shouldBeCalledTimes(1)
331
+ ->willReturn(
332
+ array(
333
+ 'response' => array('a', 'response')
334
+ )
335
+ );
336
+ $this->assertEquals($this->searchEngine->logs(), array('a', 'response'));
337
+ }
338
+
339
+ /**
340
+ * Expose protected/private attribute of an object.
341
+ *
342
+ * @param object &$object Instantiated object that we will run method on.
343
+ * @param string $attributeName Attribute to expose
344
+ *
345
+ * @return mixed Attribute Value.
346
+ */
347
+ public function exposeAttribute(&$object, $attributeName)
348
+ {
349
+ $reflection = new \ReflectionClass(get_class($object));
350
+ $attribute = $reflection->getProperty($attributeName);
351
+ $attribute->setAccessible(true);
352
+ return $attribute->getValue($object);
353
+ }
354
+
355
+ }
lib/php-doofinder/src/Test/Management/TopTermsIteratorTest.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test;
4
+
5
+ use Doofinder\Api\Management\TopTermsIterator;
6
+ use Doofinder\Api\Management\SearchEngine;
7
+
8
+
9
+ class TopTermsIteratorTest extends \PHPUnit_Framework_TestCase
10
+ {
11
+ protected function setUp()
12
+ {
13
+ $this->client = $this->prophesize('\Doofinder\Api\Management\Client');
14
+ $this->searchEngine = new SearchEngine($this->client->reveal(), 'testHashid', 'test SE');
15
+ }
16
+
17
+ /**
18
+ * @expectedException \Doofinder\Api\Management\Errors\NotProcessedResponse
19
+ */
20
+ public function testNotProcessedResponse()
21
+ {
22
+ $this->client->managementApiCall('GET', "testHashid/stats/top_fakeTerm", [])
23
+ ->shouldBeCalledTimes(1)
24
+ ->willReturn(array('statusCode'=>202));
25
+
26
+ $terms = new TopTermsIterator($this->searchEngine, 'fakeTerm');
27
+ foreach($terms as $t){
28
+ echo "boo";
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @param string $termName
34
+ *
35
+ * @dataProvider providerTermNames
36
+ */
37
+ public function testIterateThroughAllTopTerms($termName)
38
+ {
39
+ $firstResponse = array(
40
+ "next"=> "someUrl", "count"=>3, "previous"=>null,
41
+ "start"=> '2016-10-20', 'end'=> '2016-10-23',
42
+ $termName => array(
43
+ array("term"=> "t1", "count"=> 1),
44
+ array("term"=> "t2", "count"=> 1)
45
+ )
46
+ );
47
+ $secondResponse = array(
48
+ "next"=> "someUrl", "count"=>3, "previous"=>null,
49
+ "start"=> '2016-10-20', 'end'=> '2016-10-23',
50
+ $termName => array(
51
+ array("term"=> "t3", "count"=> 1)
52
+ )
53
+ );
54
+
55
+ // after receiving first request, prepare for second request and send first response
56
+ $prepareForSecondRequest = function($args, $client) use (
57
+ $termName, $secondResponse, $firstResponse
58
+ ) {
59
+ $client->managementApiCall(
60
+ 'GET', "testHashid/stats/top_$termName",
61
+ array('from'=>'20161020', 'to'=>'20161023', 'page'=>2)
62
+ )
63
+ ->shouldBeCalledTimes(1)
64
+ ->willReturn(array('response'=>$secondResponse, 'statusCode'=>200));
65
+ return array('response'=>$firstResponse, 'statusCode'=>200);
66
+ };
67
+
68
+ // first request
69
+ $this->client->managementApiCall(
70
+ 'GET', "testHashid/stats/top_$termName", array('from'=>'20161020', 'to'=>'20161023')
71
+ )
72
+ ->shouldBeCalledTimes(1)
73
+ ->will($prepareForSecondRequest);
74
+
75
+
76
+ $terms = new TopTermsIterator(
77
+ $this->searchEngine, $termName, new \Datetime('2016-10-20'), new \Datetime('2016-10-23')
78
+ );
79
+
80
+ $counter = 0;
81
+ foreach($terms as $term){
82
+ $counter++;
83
+ $this->assertEquals("t$counter", $term['term']);
84
+ }
85
+ }
86
+
87
+ public function providerTermNames()
88
+ {
89
+ return array(
90
+ array('searches'),
91
+ array('clicked'),
92
+ array('opportunities')
93
+ );
94
+ }
95
+
96
+ }
lib/php-doofinder/src/Test/Search/ClientTest.php ADDED
@@ -0,0 +1,381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test;
4
+
5
+ use \phpmock\phpunit\PHPMock;
6
+
7
+
8
+ use Doofinder\Api\Search\Client;
9
+
10
+ class ClientTest extends \PHPUnit_Framework_TestCase
11
+ {
12
+
13
+ use PHPMock;
14
+
15
+ public function setUp()
16
+ {
17
+ $this->testHashid = 'ffffffffffffffffffffffffffffffff';
18
+ $this->searchUrl = "https://eu1-search.doofinder.com/5/search";
19
+ $this->optionsUrl = "https://eu1-search.doofinder.com/5/options";
20
+ $nameSpace = 'Doofinder\API\Search';
21
+ $this->curl_init = $this->getFunctionMock($nameSpace, "curl_init");
22
+ $this->curl_setopt = $this->getFunctionMock($nameSpace, "curl_setopt");
23
+ $this->curl_getinfo = $this->getFunctionMock($nameSpace, "curl_getinfo");
24
+ $this->curl_exec = $this->getFunctionMock($nameSpace, "curl_exec");
25
+ $this->curl_close = $this->getFunctionMock($nameSpace, 'curl_close');
26
+ $this->curl_getinfo->expects($this->any())->willReturn(200);
27
+ $this->curl_setopt->expects($this->any());
28
+ //
29
+ $this->client = new Client($this->testHashid, 'eu1-testApiToken');
30
+ }
31
+
32
+ public function testAuthHeadersAreSent()
33
+ {
34
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
35
+ $this->curl_setopt->expects($this->any())->willReturnCallback(
36
+ function($session, $option, $value){
37
+ if($option == CURLOPT_HTTPHEADER){
38
+ $this->assertEquals($value, array('Expect:', 'Authorization: testApiToken'));
39
+ }
40
+ }
41
+ );
42
+
43
+
44
+ $this->client->getOptions();
45
+ }
46
+
47
+ public function testGetOptions(){
48
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
49
+ $this->curl_init->expects($this->once())->with(
50
+ $this->optionsUrl.'/'.$this->testHashid.'?hashid='.$this->testHashid
51
+ )->willReturn(332);
52
+
53
+ $this->client->getOptions();
54
+ }
55
+
56
+ public function testBasicAndMatchAllQuery()
57
+ {
58
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
59
+ // basic query
60
+ $basicQueryUrl = $this->searchUrl.'?query=ab&hashid='.$this->testHashid;
61
+ // match_all query
62
+ $matchAllQueryUrl = $this->searchUrl.'?query_name=match_all&hashid='.$this->testHashid;
63
+ $this->curl_init->expects($this->exactly(2))
64
+ ->withConsecutive(
65
+ array($basicQueryUrl), array($matchAllQueryUrl)
66
+ )
67
+ ->willReturn(333);
68
+
69
+ // basic query
70
+ $this->client->query('ab');
71
+ // match query
72
+ $this->client->query();
73
+ }
74
+
75
+ public function testFilteredQuery()
76
+ {
77
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
78
+ // filtered query
79
+ $searchParams = array(
80
+ 'filter'=>array(
81
+ 'color'=>array('rojo', 'verde')
82
+ ),
83
+ 'query_name'=>'match_and'
84
+ );
85
+ $filteredQueryUrl = $this->searchUrl.'?query=ab&'.http_build_query($searchParams).
86
+ '&hashid='.$this->testHashid;
87
+ $this->curl_init->expects($this->once())->with($filteredQueryUrl)->willReturn(33);
88
+ $this->client->query('ab', null, $searchParams);
89
+ }
90
+
91
+ public function testRequeryWhenFilterAndNoQueryName()
92
+ {
93
+ $this->curl_exec->expects($this->any())
94
+ ->willReturn(json_encode(array('query_name'=>'test_queryname')));
95
+ // filtered query
96
+ $searchParams = array(
97
+ 'filter'=>array(
98
+ 'color'=>array('rojo', 'verde')
99
+ )
100
+ );
101
+ $preQueryUrl = $this->searchUrl.'?query=ab&hashid='.$this->testHashid;
102
+ $filteredQueryUrl = $this->searchUrl.'?query=ab&query_name=test_queryname&'
103
+ .http_build_query($searchParams).'&hashid='.$this->testHashid;
104
+ $this->curl_init->expects($this->exactly(2))
105
+ ->withConsecutive(array($preQueryUrl), array($filteredQueryUrl));
106
+ $this->client->query('ab', null, $searchParams);
107
+ }
108
+
109
+ public function testAnyOptionGoesToURL()
110
+ {
111
+ $this->curl_exec->expects($this->once())
112
+ ->willReturn(json_encode(array('query_name'=>'test_queryname')));
113
+ $searchQueryUrl= $this->searchUrl.'?query=ab&kiko=pepito&hashid='.$this->testHashid;
114
+ $this->curl_init->expects($this->once())->with($searchQueryUrl);
115
+ $this->client->query('ab', null, array('kiko'=>'pepito'));
116
+ }
117
+
118
+ public function testPageParameter()
119
+ {
120
+ $this->curl_exec->expects($this->once())
121
+ ->willReturn(json_encode(array('query_name'=>'tq')));
122
+ $pagedQueryUrl = $this->searchUrl.'?query=ab&page=2&hashid='.$this->testHashid;
123
+ $this->curl_init->expects($this->once())->with($pagedQueryUrl);
124
+ $this->client->query('ab', 2);
125
+ }
126
+
127
+ public function testSanitizeEmptyValues()
128
+ {
129
+ $this->curl_exec->expects($this->once())
130
+ ->willReturn(json_encode(array()));
131
+ $dirty = array('color'=>array('red', 'brand'=>''));
132
+ $clean = array('color'=>array('red'));
133
+ $cleanFilters = array('filter'=>$clean);
134
+ $dirtyFilters = array('filter'=>$dirty);
135
+ $cleanSParams = array_merge($cleanFilters, array('query_name'=>'a'));
136
+ $dirtySParams = array_merge($dirtyFilters, array('query_name'=>'a'));
137
+ // no mention of 'brand' in url
138
+ $sanitizedQueryUrl = $this->searchUrl.'?query=ab&'.http_build_query($cleanSParams)
139
+ ."&hashid={$this->testHashid}";
140
+ $this->curl_init->expects($this->once())->with($sanitizedQueryUrl);
141
+ $this->client->query('ab', null, $dirtySParams);
142
+ }
143
+
144
+ public function testUpdateRangeFilterKeys()
145
+ {
146
+ $this->curl_exec->expects($this->once())->willReturn(json_encode(array()));
147
+ $cleanParams = array('filter'=>array('price'=>array('gte'=>2, 'lte'=>3)), 'query_name'=>'a');
148
+ $dirtyParams = array('filter'=>array('price'=>array('from'=>2, 'to'=>3)), 'query_name'=>'a');
149
+ $queryUrl = $this->searchUrl.'?query=ab&'.http_build_query($cleanParams)
150
+ ."&hashid={$this->testHashid}";
151
+ $this->curl_init->expects($this->once())->with($queryUrl);
152
+ $this->client->query('ab', null, $dirtyParams);
153
+ }
154
+
155
+ public function testHasNextAndPrev()
156
+ {
157
+ $counter = 0;
158
+ $this->curl_exec->expects($this->exactly(4))->willReturnCallback(function() use(&$counter) {
159
+ $counter++;
160
+ switch($counter){
161
+ case 1:
162
+ // first request (page 2). has next page
163
+ return json_encode(array('total'=>44, 'page'=>2));
164
+ case 2:
165
+ // second request. (last page) doesn't have next page
166
+ return json_encode(array('total'=>44, 'page'=>44));
167
+ case 3:
168
+ // third request. have prev (page 2)
169
+ return json_encode(array('total'=>44, 'page'=>2));
170
+ case 4:
171
+ // fourth request. does not have prev (first page)
172
+ return json_encode(array('total'=>44, 'page'=>1));
173
+ }
174
+ });
175
+
176
+ $this->client->query('ab');// page 2
177
+ $this->assertTrue($this->client->hasNext());
178
+ $this->client->query('ab');// last page
179
+ $this->assertFalse($this->client->hasNext());
180
+ $this->client->query('ab');// page 2
181
+ $this->assertTrue($this->client->hasPrev());
182
+ $this->client->query('ab');// first page
183
+ $this->assertFalse($this->client->hasPrev());
184
+ }
185
+
186
+ public function testSetFilter()
187
+ {
188
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
189
+ $searchParams = array(
190
+ 'filter'=>array(
191
+ 'color'=>array('rojo', 'verde')
192
+ )
193
+ );
194
+ $filteredQueryUrl = $this->searchUrl.'?'.http_build_query($searchParams).'&query=ab&query_name=test_queryname&hashid='.$this->testHashid;
195
+ $this->curl_init->expects($this->once())->with($filteredQueryUrl);
196
+ $this->client->setFilter('color', array('rojo', 'verde'));
197
+ $this->client->query('ab', null, array('query_name'=>'test_queryname'));
198
+ }
199
+
200
+ public function testGetFilter()
201
+ {
202
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
203
+ $searchParams = array(
204
+ 'filter'=>array('color'=>array('rojo', 'verde')),
205
+ 'query_name'=>'a'
206
+ );
207
+ $this->client->query('ab', null, $searchParams);
208
+ $this->assertEquals($searchParams['filter']['color'], $this->client->getFilter('color'));
209
+ }
210
+
211
+ public function testGetFilters()
212
+ {
213
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
214
+ $sParams = array('filter'=>array('color'=>array('red'), 'brand'=>array('nike')),
215
+ 'query_name'=>'a');
216
+ $this->client->query('ab', null, $sParams);
217
+ $this->assertEquals(array('color'=>array('red'), 'brand'=>array('nike')),
218
+ $this->client->getFilters());
219
+ }
220
+
221
+ public function testAddSort()
222
+ {
223
+ $this->curl_exec->expects($this->any())->willReturn(json_encode(array()));
224
+ $sortedQueryUrl = $this->searchUrl.'?'
225
+ .http_build_query(array('sort'=>array(array('rice'=>'desc'))))
226
+ ."&query=ab&hashid={$this->testHashid}";
227
+ $this->curl_init->expects($this->once())->with($sortedQueryUrl);
228
+ $this->client->addSort('rice', 'desc');
229
+ $this->client->query('ab');
230
+ }
231
+
232
+ public function testNextPage()
233
+ {
234
+ $this->curl_exec->expects($this->any())->willReturn(
235
+ json_encode(array('total'=>44,'page'=>1, 'query'=>'ab'))
236
+ );
237
+ $searchParams = array(
238
+ 'filter'=>array('color'=>array('red')),
239
+ 'sort'=>array(array('price'=>'desc')),
240
+ 'query_name'=>'a',
241
+ 'hashid'=>$this->testHashid
242
+ );
243
+ //->query('ab') url
244
+ $page1QueryUrl = $this->searchUrl.'?query=ab&'.http_build_query($searchParams);
245
+ // ->nextPage() url
246
+ $page2QueryUrl = $this->searchUrl.'?query=ab&'.http_build_query($searchParams).'&page=2';
247
+ $this->curl_init->expects($this->exactly(2))
248
+ ->withConsecutive(array($page1QueryUrl), array($page2QueryUrl));
249
+ $this->client->query('ab', null, $searchParams);
250
+ $this->client->nextPage();
251
+ }
252
+
253
+ public function testPrevPage()
254
+ {
255
+ $this->curl_exec->expects($this->any())->willReturn(
256
+ json_encode(array('total'=>44,'page'=>2, 'query'=>'ab'))
257
+ );
258
+ $searchParams = array(
259
+ 'filter'=>array('color'=>array('red')),
260
+ 'sort'=>array(array('price'=>'desc')),
261
+ 'query_name'=>'a',
262
+ 'hashid'=>$this->testHashid
263
+ );
264
+ //->query('ab', 2) url
265
+ $page2QueryUrl = $this->searchUrl.'?query=ab&page=2&'.http_build_query($searchParams);
266
+ //->->prevPage() url
267
+ $page1QueryUrl = $this->searchUrl.'?query=ab&page=1&'.http_build_query($searchParams);
268
+ $this->curl_init->expects($this->exactly(2))
269
+ ->withConsecutive(array($page2QueryUrl), array($page1QueryUrl));
270
+ $this->client->query('ab', 2, $searchParams);
271
+ $this->client->prevPage();
272
+ }
273
+
274
+ public function testToQueryString()
275
+ {
276
+ $this->curl_exec->expects($this->any())->willReturn(
277
+ json_encode(
278
+ array('total'=>44, 'page'=>1, 'query' =>'ab', 'query_name'=>'baba')
279
+ )
280
+ );
281
+ $searchParams = array(
282
+ 'query_name'=>'baba',
283
+ 'filter'=>array('color'=>array('red')),
284
+ 'sort'=>array(array('price'=>'desc'))
285
+ );
286
+ $serialization = 'dfParam_query=ab&dfParam_query_name=baba&dfParam_filter%5Bcolor%5D%5B0%5D=red&dfParam_sort%5B0%5D%5Bprice%5D=desc';
287
+ $this->client->query('ab', null, $searchParams); // do the query to populate state
288
+ $this->assertEquals($serialization, $this->client->toQueryString());
289
+ }
290
+
291
+ public function testToQueryStringWithCustomPrefix()
292
+ {
293
+ $this->curl_exec->expects($this->any())->willReturn(
294
+ json_encode(
295
+ array('total'=>44, 'page'=>1, 'query' =>'ab', 'query_name'=>'baba')
296
+ )
297
+ );
298
+ $searchParams = array(
299
+ 'query_name'=>'baba',
300
+ 'filter'=>array('color'=>array('red')),
301
+ 'sort'=>array(array('price'=>'desc'))
302
+ );
303
+ // create client with custom prefix
304
+ $customPrefixClient = new Client(
305
+ $this->testHashid, 'eu1-testApiToken', false, array('prefix' => 'customP')
306
+ );
307
+ $customPrefixserialization = 'customPquery=ab&customPquery_name=baba&customPfilter%5Bcolor%5D%5B0%5D=red&customPsort%5B0%5D%5Bprice%5D=desc';
308
+ $customPrefixClient->query('ab', null, $searchParams); // do the query to populate state
309
+ $this->assertEquals($customPrefixserialization, $customPrefixClient->toQueryString());
310
+ }
311
+
312
+ public function testFromQueryString()
313
+ {
314
+ $this->curl_exec->expects($this->any())->willReturn(
315
+ json_encode(
316
+ array('total'=>44, 'page'=>1, 'query' =>'ab', 'query_name'=>'baba')
317
+ )
318
+ );
319
+ // to fool fromQueryString
320
+ $_REQUEST = array(
321
+ 'dfParam_query'=>'ab',
322
+ 'dfParam_query_name'=>'baba',
323
+ 'dfParam_filter'=> array('color'=>array('red')),
324
+ 'dfParam_sort'=>array(array('price'=>'desc'))
325
+ );
326
+ $queryUrl = $this->searchUrl.'?query=ab&query_name=baba&filter%5Bcolor%5D%5B0%5D=red&sort%5B0%5D%5Bprice%5D=desc&hashid='.$this->testHashid;
327
+ $this->curl_init->expects($this->once())->with($queryUrl);
328
+ // unserialize client
329
+ $client = new Client($this->testHashid, 'eu1-testApiToken');
330
+ $client->fromQueryString();
331
+ $client->query(); // do the query
332
+ }
333
+
334
+ public function testFromQueryStringWithCustomPrefix()
335
+ {
336
+ $this->curl_exec->expects($this->any())->willReturn(
337
+ json_encode(
338
+ array('total'=>44, 'page'=>1, 'query' =>'ab', 'query_name'=>'baba')
339
+ )
340
+ );
341
+ // to fool fromQueryString
342
+ $_REQUEST = array(
343
+ 'customPquery'=>'ab',
344
+ 'customPquery_name'=>'baba',
345
+ 'customPfilter'=> array('color'=>array('red')),
346
+ 'customPsort'=>array(array('price'=>'desc'))
347
+ );
348
+ // query that should be done with params from $_REQUEST
349
+ $queryUrl = $this->searchUrl.'?query=ab&query_name=baba&filter%5Bcolor%5D%5B0%5D=red&sort%5B0%5D%5Bprice%5D=desc&hashid='.$this->testHashid;
350
+ $this->curl_init->expects($this->once())->with($queryUrl);
351
+ // have to create new one here so it can see $_REQUEST
352
+ $client = new Client($this->testHashid, 'eu1-testApiToken', false, array('prefix'=>'customP'));
353
+ $client->fromQueryString(); // unserialize client
354
+ $client->query(); // do the query
355
+ }
356
+
357
+ public function testConstructorFromQueryStringWithCustomPrefix(){
358
+ $this->curl_exec->expects($this->any())->willReturn(
359
+ json_encode(
360
+ array('total'=>44, 'page'=>1, 'query' =>'ab', 'query_name'=>'baba')
361
+ )
362
+ );
363
+ // to fool fromQueryString
364
+ $_REQUEST = array(
365
+ 'customPquery'=>'ab',
366
+ 'customPquery_name'=>'baba',
367
+ 'customPfilter'=> array('color'=>array('red')),
368
+ 'customPsort'=>array(array('price'=>'desc'))
369
+ );
370
+ $queryUrl = $this->searchUrl.'?query=ab&query_name=baba&filter%5Bcolor%5D%5B0%5D=red&sort%5B0%5D%5Bprice%5D=desc&hashid='.$this->testHashid;
371
+ $this->curl_init->expects($this->once())->with($queryUrl);
372
+ // unserialize client in constructor
373
+ $client = new Client($this->testHashid, 'eu1-testApiToken', true, array('prefix'=>'customP'));
374
+
375
+ $client->query(); // do the query
376
+ }
377
+
378
+
379
+
380
+
381
+ }
lib/php-doofinder/src/Test/Search/ResultsTests.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Doofinder\Api\Test;
4
+
5
+ use Doofinder\Api\Search\Results;
6
+
7
+ class ResultsTest extends \PHPUnit_Framework_TestCase
8
+ {
9
+ public function setUp()
10
+ {
11
+ $this->response = json_encode(
12
+ array(
13
+ "page" => 2, "total" => 44, "results_per_page" => 12, "query" => "ab",
14
+ "hashid" => 'testHashid', 'max_score' => 2.23, 'query_name' => 'match_and',
15
+ 'results' => array(
16
+ array('id'=>'id1', 'title' => 't1', 'cats'=>array('cat1', 'cat2')),
17
+ array('id'=>'id2', 'title' => 't2', 'cats'=>array('cat1', 'caxnt2')),
18
+ array('id'=>'id3', 'title' => 't3', 'cats'=>array('cat1ab', 'cat2'))
19
+ ),
20
+ 'facets' => array(
21
+ 'price' => array('type'=>'range'),
22
+ 'color' => array(
23
+ 'doc_count' => 6,
24
+ 'missing' => array('doc_count' => 0),
25
+ 'terms' => array(
26
+ 'buckets' => array(
27
+ array('key'=>'red', 'doc_count'=>344), array('key'=>'blue', 'doc_count'=>1)
28
+ )
29
+ ),
30
+ 'total' => array('value'=>6)
31
+ )
32
+ ),
33
+ 'filter' => array(
34
+ 'terms' => array('color'=>array('red'))
35
+ )
36
+ )
37
+ );
38
+
39
+ $this->results = new Results($this->response);
40
+ }
41
+
42
+ public function testGetProperty()
43
+ {
44
+ $this->assertEquals('match_and', $this->results->getProperty('query_name'));
45
+ $this->assertEquals(2.23, $this->results->getProperty('max_score'));
46
+ }
47
+
48
+ public function testGetResults()
49
+ {
50
+ $res = array(
51
+ array('id'=>'id1', 'title' => 't1', 'cats'=>array('cat1', 'cat2')),
52
+ array('id'=>'id2', 'title' => 't2', 'cats'=>array('cat1', 'caxnt2')),
53
+ array('id'=>'id3', 'title' => 't3', 'cats'=>array('cat1ab', 'cat2'))
54
+ );
55
+ $this->assertEquals($res, $this->results->getResults());
56
+ }
57
+
58
+ public function testGetFacetsNames()
59
+ {
60
+ $this->assertEquals(array('price', 'color'), $this->results->getFacetsNames());
61
+ }
62
+
63
+ public function testGetFacet()
64
+ {
65
+ $this->assertEquals(array('type'=>'range'), $this->results->getFacet('price'));
66
+ }
67
+
68
+ public function testGetFacetsAndMarkFacetAsSelected()
69
+ {
70
+ $facets = array(
71
+ 'price' => array('type'=>'range'),
72
+ 'color' => array(
73
+ 'doc_count' => 6,
74
+ 'missing' => array('doc_count' => 0),
75
+ 'terms' => array(
76
+ 'buckets' => array(
77
+ array('key'=>'red', 'doc_count'=>344, 'selected'=>true),
78
+ array('key'=>'blue', 'doc_count'=>1, 'selected'=>false)
79
+ )
80
+ ),
81
+ 'total' => array('value'=>6)
82
+ )
83
+ );
84
+ $this->assertEquals($facets, $this->results->getFacets());
85
+ }
86
+
87
+ public function testGetAppliedFilters()
88
+ {
89
+ $this->assertEquals(
90
+ array('color'=>array('red')), $this->results->getAppliedFilters()
91
+ );
92
+ }
93
+
94
+ }
package.xml CHANGED
@@ -1,7 +1,7 @@
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Doofinder_Feed</name>
4
- <version>1.7.2</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL 3.0)</license>
7
  <channel>community</channel>
@@ -48,11 +48,11 @@ Doofinder is fast and innovative. With no doubt, it is the best search engine fo
48
  You can get more info and create your account visiting the Doofinder site:&#xD;
49
  &#xD;
50
  http://www.doofinder.com</description>
51
- <notes>Maintenance version. Previous versions were unstable and were hidden.</notes>
52
  <authors><author><name>Carlos Escribano Rey</name><user>doofinder</user><email>carlos@doofinder.com</email></author></authors>
53
- <date>2017-01-17</date>
54
- <time>13:08:03</time>
55
- <contents><target name="magecommunity"><dir name="Doofinder"><dir name="Feed"><dir name="Block"><dir name="Adminhtml"><dir name="Log"><file name="View.php" hash="367c6248eda3707299ae89520743c850"/></dir><dir name="Map"><file name="Additional.php" hash="6fa2b8b6a954c027c810a8f925c211a1"/></dir></dir><file name="Integration.php" hash="2e08af98f8059bf18ae9c99be0fb36c5"/><dir name="Settings"><dir name="Buttons"><file name="Generate.php" hash="982ca6be10bcbf1dabe9e36364da24f3"/><file name="ViewLog.php" hash="4f6f1afabbce5ae78d6612e016df8df0"/></dir><dir name="Panel"><file name="Cron.php" hash="e93e0471544eef8d6cc1ea5c2a8037dd"/><file name="Crondescription.php" hash="f0ebd84720ae555ceecaea184ef6f73c"/><file name="Datetime.php" hash="af8c975bed83f02d4db2261f3215d839"/><file name="Description.php" hash="324a8d6a38f895262fb34b4e4f4b63bf"/><file name="File.php" hash="3aace983195adcaa8e4057ff48e68784"/><file name="Hashdescription.php" hash="707aa2f0eff7a1e170e6355787e09e84"/><file name="Layerdescription.php" hash="10d5abaae37c098a708314920139ebb3"/><file name="Message.php" hash="003c21f8aba7d622f41009bd38618619"/></dir></dir></dir><dir name="Helper"><file name="Data.php" hash="91a40056d029a5a416e5c41c8f733ad2"/><file name="Log.php" hash="9da17ebc15651df887f40e5c9d462154"/><file name="Search.php" hash="507d1fd5410cd57d4ac3437b9aff1d2f"/><file name="Tax.php" hash="86ab9dc8bcdec6dafaa8e90a4e11d0dc"/></dir><dir name="Model"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Backend"><file name="Cron.php" hash="a8e7f346ab90c8c13a991d5f4b8df445"/></dir><dir name="Validation"><file name="Hashid.php" hash="5c84ddebe10442c48bc8bca05a9253d1"/></dir></dir></dir></dir><dir name="CatalogSearch"><dir name="Resource"><file name="Fulltext.php" hash="a3ece9fe463669dc6c16a232f7160aba"/></dir></dir><file name="Config.php" hash="50cc4715286991370b475da5121853b3"/><file name="Cron.php" hash="5767c6d975b6a2f0eacdd054864f2642"/><file name="Generator.php" hash="59f2a0fee2227c97272a19f89bce57ac"/><file name="Log.php" hash="754ee7c81bd452a4855edd2a12508f6f"/><dir name="Map"><dir name="Product"><file name="Abstract.php" hash="b4fafd06d3eea29622b1f3a3ea6c1e5f"/><file name="Associated.php" hash="41ecc0f15c3278cef80ff4a7b9364824"/><file name="Bundle.php" hash="46efab55df791e07f383a62c7dea9792"/><file name="Configurable.php" hash="89a4825cc70976c5ea781f236e9480bb"/><file name="Downloadable.php" hash="afd95094e3fff4ce538a55fe94f8ba01"/><file name="Grouped.php" hash="7937fda691c57fc06aa90913e36e145c"/><file name="Simple.php" hash="4c4c13235d31035d7468b7abaedabc8c"/><file name="Virtual.php" hash="267872ef255fc28fbfd695a49d136778"/></dir></dir><dir name="Mysql4"><dir name="Cron"><file name="Collection.php" hash="53a3d54670a7397c6637cedc3025711b"/></dir><file name="Cron.php" hash="41d5ca8ca62b60189b55d08658621095"/><dir name="Log"><file name="Collection.php" hash="b931008657cc1b8a113cc9740a045fe4"/></dir><file name="Log.php" hash="03334760a8fd286e71cf8a208a378a1e"/></dir><dir name="Observers"><file name="Feed.php" hash="aef28386fd18303d78577e3a89332a06"/><file name="Logs.php" hash="0aaad31cd511e90a1b3a7ee9d80c390d"/><file name="Schedule.php" hash="f4713b08d887d9c9fb141ebfbda8934e"/></dir><dir name="Resource"><dir name="Mysql4"><file name="Setup.php" hash="649fd92a0c409ac06851d81482503040"/></dir></dir><dir name="System"><dir name="Config"><dir name="Backend"><dir name="Map"><file name="Additional.php" hash="e236dac579af77a6265b2e6aafdbb903"/></dir><dir name="Total"><file name="Limit.php" hash="a23092ea72cbe81e2779b086ad055bf6"/></dir></dir><file name="Reset.php" hash="a268a1094aedbe279960dbc634a109f0"/><dir name="Source"><dir name="Product"><file name="Attributes.php" hash="42ad8f5fd747740075d5a4aff0e34692"/></dir></dir></dir></dir><file name="Tools.php" hash="6481c3d3e032c3c51f43fe81203aa192"/></dir><dir name="Test"><dir name="Controller"><dir name="Index"><dir name="fixtures"><file name="testConfig.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/><file name="testFeed.yaml" hash="694cf25a35a9a301a8ae678866937909"/><file name="testIndex.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/></dir><dir name="providers"><file name="testConfig.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/><file name="testFeed.yaml" hash="1ea2f638be8fdcea22ef47767ed8d7db"/><file name="testIndex.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/></dir></dir><file name="Index.php" hash="2771de706303653d039818bd0f6590ea"/></dir><dir name="Model"><dir name="Product"><dir name="expectations"><file name="testGenerator.yaml" hash="232dda1f4fd88b8ef081393f08044731"/></dir><dir name="fixtures"><file name="testGenerator.yaml" hash="df25e3ca67fd98ab1b933c4951c599ef"/></dir><dir name="providers"><file name="testGenerator.yaml" hash="84779d5dcd8d92abdecf0cd5ee65cfb0"/></dir></dir><file name="Product.php" hash="6c45ae2b36c6cc721ef634855ed6d596"/></dir></dir><dir name="controllers"><file name="DoofinderFeedFeedController.php" hash="37bc831a5f3caf60bbbfc24ea7db2cc2"/><file name="DoofinderFeedLogController.php" hash="c8b08e5ae3058e223e5c980e7619c353"/><file name="FeedController.php" hash="0e9cbf36a209536f6539327c3223793f"/><file name="IndexController.php" hash="6fb8361fa8b4bfa97efabca46df9e9c3"/></dir><dir name="etc"><file name="config.xml" hash="54e893270f9c498366306feba7918157"/><file name="system.xml" hash="4c69b26ff91eedb582883e83a26b843d"/></dir><dir name="sql"><dir name="doofinder_feed_setup"><file name="mysql4-install-1.5.4.php" hash="9dc5ed4e10febbe75ab1911259a1c9fe"/><file name="mysql4-install-1.5.7.php" hash="85baa03d9c4d76f6b744ba107c21f8da"/><file name="mysql4-upgrade-1.5.4-1.5.5.php" hash="df7158f6d6cdded9bdfc5cb72c1dc8e3"/><file name="mysql4-upgrade-1.5.5-1.5.6.php" hash="0f3ca5263356a0bc83d9352b463944dc"/><file name="mysql4-upgrade-1.5.6-1.5.7.php" hash="b0180770655f36d6723483aa3bd1541f"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Doofinder_Feed.xml" hash="9d3b6fbbbec12708461c33260715451c"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="doofinder.xml" hash="a7b9105a4e613086340b042845793d9f"/></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="doofinder.xml" hash="48a8636096950914917461260416c355"/></dir></dir></dir></dir></target><target name="mage"><dir name="js"><dir name="doofinder"><file name="admin.js" hash="ca050b0527ae101c75532fbca1c4a274"/></dir></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="doofinder"><file name="styles.css" hash="d6ec303c3199db3ab4dffa8d2491105e"/></dir></dir></dir></dir></target><target name="magelib"><dir name="Doofinder"><file name="doofinder_api.php" hash="3a27154c61f86990f58bb719f44ccd0f"/><file name="doofinder_management_api.php" hash="71ac700c6e0d04496fd2da0bc012b84e"/><file name="errors.php" hash="6aaf93010cee28c87a6ab4ab0e2606d1"/></dir></target></contents>
56
  <compatible/>
57
- <dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
58
  </package>
1
  <?xml version="1.0"?>
2
  <package>
3
  <name>Doofinder_Feed</name>
4
+ <version>1.8.0</version>
5
  <stability>stable</stability>
6
  <license uri="http://opensource.org/licenses/osl-3.0.php">Open Software License (OSL 3.0)</license>
7
  <channel>community</channel>
48
  You can get more info and create your account visiting the Doofinder site:&#xD;
49
  &#xD;
50
  http://www.doofinder.com</description>
51
+ <notes>Big update: Doofinder PHP client library.</notes>
52
  <authors><author><name>Carlos Escribano Rey</name><user>doofinder</user><email>carlos@doofinder.com</email></author></authors>
53
+ <date>2017-01-26</date>
54
+ <time>12:50:52</time>
55
+ <contents><target name="magecommunity"><dir name="Doofinder"><dir name="Feed"><dir name="Block"><dir name="Adminhtml"><dir name="Log"><file name="View.php" hash="03c3b1af6a8b61400228f328a6320f2d"/></dir><dir name="Map"><file name="Additional.php" hash="29b7565675e33603cde91b9ddc24b13a"/></dir></dir><file name="Integration.php" hash="4993674d96110e803768b5485114bab1"/><dir name="Settings"><dir name="Buttons"><file name="Generate.php" hash="f3d9678ce31253a3f4ef78f7462e1182"/><file name="ViewLog.php" hash="e1a8fe3545be5e1cdf1ce84e465bfc13"/></dir><dir name="Panel"><file name="Cron.php" hash="e93e0471544eef8d6cc1ea5c2a8037dd"/><file name="Crondescription.php" hash="3e2370104c73841bab4b56684ab8156c"/><file name="Datetime.php" hash="a555254d8d190245b530e24337518a24"/><file name="Description.php" hash="ff021f308a3e2a4fa7e2b85097122216"/><file name="File.php" hash="9d3083bd42678b63f8e8ce6f38464a16"/><file name="Hashdescription.php" hash="707aa2f0eff7a1e170e6355787e09e84"/><file name="Layerdescription.php" hash="f4841d566f1fac8d38f26168c0f0ec9c"/><file name="Message.php" hash="7ca8fb843ce000bf5c6e06886158e745"/></dir></dir></dir><dir name="Helper"><file name="Data.php" hash="5df562485701662ad20c3534ce056994"/><file name="Log.php" hash="687c0666e8564a9a742eca7209a4dd45"/><file name="Search.php" hash="adedec93e07dadb8edfcac8dd74fa07c"/><file name="Tax.php" hash="3924ee3d1866960e32f0bb736052fbc7"/></dir><dir name="Model"><dir name="Adminhtml"><dir name="System"><dir name="Config"><dir name="Backend"><file name="Cron.php" hash="733057e70e3829acbceddca44566f04f"/></dir><dir name="Validation"><file name="Hashid.php" hash="5c84ddebe10442c48bc8bca05a9253d1"/></dir></dir></dir></dir><dir name="CatalogSearch"><dir name="Resource"><file name="Fulltext.php" hash="a3ece9fe463669dc6c16a232f7160aba"/></dir></dir><file name="Config.php" hash="608999f8e038964cce511c58385d1e81"/><file name="Cron.php" hash="197f39e1bd59fd69e5e71199f2296990"/><file name="Generator.php" hash="19fde20c0d7444b2a17e58f1088c20a6"/><file name="Log.php" hash="2b99a4837cb292291aed2a2d0a5af4c0"/><dir name="Map"><dir name="Product"><file name="Abstract.php" hash="ec7ab8b3476740e9a1f17ee7e41ea50f"/><file name="Associated.php" hash="15a063d949bdee13c428268dd463a87e"/><file name="Bundle.php" hash="cdc7c9d33fc12b2c89570cbe067371ae"/><file name="Configurable.php" hash="78bc90cdd7a7bd8f8b79d91aa879c3f7"/><file name="Downloadable.php" hash="b7cbe65768f5bf80468cfb06a514a1da"/><file name="Grouped.php" hash="659ca91fcb1807355b9de1d7062e52a6"/><file name="Simple.php" hash="efd6a7f9d722211bc2b5e2e149244c9c"/><file name="Virtual.php" hash="ccb06b3dbb80041c2b746d23be589592"/></dir></dir><dir name="Mysql4"><dir name="Cron"><file name="Collection.php" hash="85d5544835a6cdef9108016580cf5ab1"/></dir><file name="Cron.php" hash="f95b812e618dc87641eb53bc035e6723"/><dir name="Log"><file name="Collection.php" hash="e31683db099aeb80508d067625143361"/></dir><file name="Log.php" hash="9ee95cf8e53d1e00b1b06098d25b04f0"/></dir><dir name="Observers"><file name="Feed.php" hash="52156389f225401667fe90534290100e"/><file name="Logs.php" hash="395d5b9d67b0c4f5aeda9896b582a03f"/><file name="Schedule.php" hash="52277ae8b984e8b3fc1a38c9adda7980"/></dir><dir name="Resource"><dir name="Mysql4"><file name="Setup.php" hash="e3e85f054845581f6d867a7f17d6f0dd"/></dir></dir><dir name="System"><dir name="Config"><dir name="Backend"><dir name="Map"><file name="Additional.php" hash="ae75ae59c24a9a67c68a7ea1d616003c"/></dir><dir name="Total"><file name="Limit.php" hash="a23092ea72cbe81e2779b086ad055bf6"/></dir></dir><file name="Reset.php" hash="e077170e620a06f0f09d197849ee9140"/><dir name="Source"><dir name="Product"><file name="Attributes.php" hash="894e760cd054b167d7b1cff60c6bd2fa"/></dir></dir></dir></dir><file name="Tools.php" hash="a5b84002fb1ef9896f90b926f64d83c9"/></dir><dir name="Test"><dir name="Controller"><dir name="Index"><dir name="fixtures"><file name="testConfig.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/><file name="testFeed.yaml" hash="694cf25a35a9a301a8ae678866937909"/><file name="testIndex.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/></dir><dir name="providers"><file name="testConfig.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/><file name="testFeed.yaml" hash="1ea2f638be8fdcea22ef47767ed8d7db"/><file name="testIndex.yaml" hash="0a1f21a3417389e0c0a13392c79a7a89"/></dir></dir><file name="Index.php" hash="2771de706303653d039818bd0f6590ea"/></dir><dir name="Model"><dir name="Product"><dir name="expectations"><file name="testGenerator.yaml" hash="232dda1f4fd88b8ef081393f08044731"/></dir><dir name="fixtures"><file name="testGenerator.yaml" hash="df25e3ca67fd98ab1b933c4951c599ef"/></dir><dir name="providers"><file name="testGenerator.yaml" hash="84779d5dcd8d92abdecf0cd5ee65cfb0"/></dir></dir><file name="Product.php" hash="6c45ae2b36c6cc721ef634855ed6d596"/></dir></dir><dir name="controllers"><file name="DoofinderFeedFeedController.php" hash="fd84f56326d52c415b3463c954e94453"/><file name="DoofinderFeedLogController.php" hash="cf5fde00612dd0e2b6820445e52daedf"/><file name="FeedController.php" hash="df1e299fed29c601e37d6b88292a8b42"/><file name="IndexController.php" hash="0111c79320841b64cf63efe2f9d10efe"/></dir><dir name="etc"><file name="config.xml" hash="6294122049d5ec4727c6da0abf688fff"/><file name="system.xml" hash="4c69b26ff91eedb582883e83a26b843d"/></dir><dir name="sql"><dir name="doofinder_feed_setup"><file name="mysql4-install-1.5.4.php" hash="9dc5ed4e10febbe75ab1911259a1c9fe"/><file name="mysql4-install-1.5.7.php" hash="85baa03d9c4d76f6b744ba107c21f8da"/><file name="mysql4-upgrade-1.5.4-1.5.5.php" hash="df7158f6d6cdded9bdfc5cb72c1dc8e3"/><file name="mysql4-upgrade-1.5.5-1.5.6.php" hash="0f3ca5263356a0bc83d9352b463944dc"/><file name="mysql4-upgrade-1.5.6-1.5.7.php" hash="b0180770655f36d6723483aa3bd1541f"/></dir></dir></dir></dir></target><target name="mageetc"><dir name="modules"><file name="Doofinder_Feed.xml" hash="9d3b6fbbbec12708461c33260715451c"/></dir></target><target name="magedesign"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="layout"><file name="doofinder.xml" hash="a7b9105a4e613086340b042845793d9f"/></dir></dir></dir></dir><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="doofinder.xml" hash="48a8636096950914917461260416c355"/></dir></dir></dir></dir></target><target name="mage"><dir name="js"><dir name="doofinder"><file name="admin.js" hash="ca050b0527ae101c75532fbca1c4a274"/></dir></dir></target><target name="mageskin"><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="doofinder"><file name="styles.css" hash="d6ec303c3199db3ab4dffa8d2491105e"/></dir></dir></dir></dir></target><target name="magelib"><dir name="php-doofinder"><file name="CHANGELOG.txt" hash="b4fc19b1cb37810d8faa6a1e784ab32b"/><file name="README.md" hash="f9eda30362e3356947f86a9248122c2f"/><file name="autoload.php" hash="1ab27f8a639e30c5a25a6100f1a3035d"/><file name="composer.json" hash="76f54c3a763a3ae683fa677106b41027"/><file name="phpunit.xml" hash="97d89bb72790578c7ca60aebe7e3b1dd"/><dir name="src"><dir name="Management"><file name="AggregatesIterator.php" hash="431618213beb8294f6cab35376c935c8"/><file name="Client.php" hash="3d4dc1dbabcb42c7b1e4835ff5056d21"/><dir name="Errors"><file name="BadRequest.php" hash="45113fccee9500f4ee2a20e845ef8ee2"/><file name="NotAllowed.php" hash="98169302570fcf34a49277c5afbbed20"/><file name="NotFound.php" hash="9b08d95a549cfaea5562ecfa74a28746"/><file name="NotProcessedResponse.php" hash="63984cfbe90b6912e8d53faa293e65d6"/><file name="QuotaExhausted.php" hash="75f95704e509d7b5a4c61ebdb76208f5"/><file name="ThrottledResponse.php" hash="d3cd9c6df62246b5c3e80de3e1ecfc9c"/><file name="Utils.php" hash="be1220fc529292e8cc723dc9f68ef81d"/><file name="WrongResponse.php" hash="3c0fd79d6c04ac2c8f93984912369333"/></dir><file name="ItemsResultSet.php" hash="67532bd88b478fb4e0ce9f05d2ffd94a"/><file name="ScrollIterator.php" hash="0d916403f85a791af8e4e2240ad9e1be"/><file name="SearchEngine.php" hash="417e44ce9f11be52d3a6679161014eb8"/><file name="TopTermsIterator.php" hash="9ef2a784ce1d2cae81e577ce8c13ed08"/></dir><dir name="Search"><file name="Client.php" hash="857ed6a6594776f3060d87c2c9b86aca"/><file name="Error.php" hash="38a4283a860ed443f43a8452cc931c99"/><file name="Results.php" hash="87417657ec8a4908d6fd35868b782d7e"/></dir><dir name="Test"><dir name="Management"><file name="AggregatesIteratorTest.php" hash="eb9abdc3de26ac6396efc007373906ca"/><file name="ClientTest.php" hash="9958013403856eab97a44a14b8c32569"/><file name="ScrollIteratorTest.php" hash="34ef0111f1bb3633e6950151efd5e3ec"/><file name="SearchEngineTest.php" hash="a354a2e690851ab8d2b9a41495fcd979"/><file name="TopTermsIteratorTest.php" hash="9804ca4b89dce16d1e89b6614c11c2c0"/></dir><dir name="Search"><file name="ClientTest.php" hash="6aeac0a72fc4a4798233dfde15014543"/><file name="ResultsTests.php" hash="64503776af865c5d23e261b71d36a6bc"/></dir></dir></dir></dir></target></contents>
56
  <compatible/>
57
+ <dependencies><required><php><min>5.4.0</min><max>6.0.0</max></php></required></dependencies>
58
  </package>