Version Notes
- NEW: Add an option to include data from out-of-stock sub products
- NEW: Use secured api keys to only retrieve one group price in the frontend
- NEW: Better update strategy to simplify the indexer code and to avoid missing deleted products event
- UPDATE: Better handling of include in navigation config
- UPDATE: underlying php client
- UPDATE: Conditionally render template directives
- UPDATE: Make sub product skus searchable
- FIX: slaves creation issue
- FIX: small price issue
- FIX: fallback to default search in case there is a error from the api
Download this release
Release Info
Developer | Algolia Team |
Extension | algoliasearch |
Version | 1.5.5 |
Comparing to | |
See all releases |
Code changes from version 1.5.4 to 1.5.5
- app/code/community/Algolia/Algoliasearch/Helper/Algoliahelper.php +8 -0
- app/code/community/Algolia/Algoliasearch/Helper/Config.php +52 -4
- app/code/community/Algolia/Algoliasearch/Helper/Data.php +3 -1
- app/code/community/Algolia/Algoliasearch/Helper/Entity/Additionalsectionshelper.php +7 -4
- app/code/community/Algolia/Algoliasearch/Helper/Entity/Categoryhelper.php +14 -12
- app/code/community/Algolia/Algoliasearch/Helper/Entity/Helper.php +2 -1
- app/code/community/Algolia/Algoliasearch/Helper/Entity/Pagehelper.php +8 -3
- app/code/community/Algolia/Algoliasearch/Helper/Entity/Producthelper.php +61 -53
- app/code/community/Algolia/Algoliasearch/Helper/Entity/Suggestionhelper.php +42 -16
- app/code/community/Algolia/Algoliasearch/Helper/Logger.php +2 -2
- app/code/community/Algolia/Algoliasearch/Model/Indexer/Algolia.php +26 -145
- app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliacategories.php +1 -1
- app/code/community/Algolia/Algoliasearch/Model/Resource/Fulltext/Collection.php +13 -1
- app/code/community/Algolia/Algoliasearch/etc/config.xml +5 -2
- app/code/community/Algolia/Algoliasearch/etc/system.xml +49 -7
- app/design/frontend/base/default/template/algoliasearch/beforetopsearch.phtml +11 -1
- app/design/frontend/base/default/template/algoliasearch/topsearch.phtml +2 -1
- app/etc/modules/Algolia_Algoliasearch.xml +1 -1
- lib/AlgoliaSearch/AlgoliaException.php +3 -1
- lib/AlgoliaSearch/Client.php +759 -255
- lib/AlgoliaSearch/ClientContext.php +164 -23
- lib/AlgoliaSearch/Index.php +848 -371
- lib/AlgoliaSearch/IndexBrowser.php +160 -0
- lib/AlgoliaSearch/PlacesIndex.php +81 -0
- lib/AlgoliaSearch/Version.php +4 -2
- package.xml +14 -8
app/code/community/Algolia/Algoliasearch/Helper/Algoliahelper.php
CHANGED
@@ -7,10 +7,13 @@ if (class_exists('AlgoliaSearch\Client', false) == false)
|
|
7 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/ClientContext.php';
|
8 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Client.php';
|
9 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Index.php';
|
|
|
|
|
10 |
}
|
11 |
|
12 |
class Algolia_Algoliasearch_Helper_Algoliahelper extends Mage_Core_Helper_Abstract
|
13 |
{
|
|
|
14 |
protected $client;
|
15 |
protected $config;
|
16 |
|
@@ -26,6 +29,11 @@ class Algolia_Algoliasearch_Helper_Algoliahelper extends Mage_Core_Helper_Abstra
|
|
26 |
$this->client = new \AlgoliaSearch\Client($this->config->getApplicationID(), $this->config->getAPIKey());
|
27 |
}
|
28 |
|
|
|
|
|
|
|
|
|
|
|
29 |
public function getIndex($name)
|
30 |
{
|
31 |
return $this->client->initIndex($name);
|
7 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/ClientContext.php';
|
8 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Client.php';
|
9 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Index.php';
|
10 |
+
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/PlacesIndex.php';
|
11 |
+
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/IndexBrowser.php';
|
12 |
}
|
13 |
|
14 |
class Algolia_Algoliasearch_Helper_Algoliahelper extends Mage_Core_Helper_Abstract
|
15 |
{
|
16 |
+
/** @var \AlgoliaSearch\Client */
|
17 |
protected $client;
|
18 |
protected $config;
|
19 |
|
29 |
$this->client = new \AlgoliaSearch\Client($this->config->getApplicationID(), $this->config->getAPIKey());
|
30 |
}
|
31 |
|
32 |
+
public function generateSearchSecuredApiKey($key, $params = array())
|
33 |
+
{
|
34 |
+
return $this->client->generateSecuredApiKey($key, $params);
|
35 |
+
}
|
36 |
+
|
37 |
public function getIndex($name)
|
38 |
{
|
39 |
return $this->client->initIndex($name);
|
app/code/community/Algolia/Algoliasearch/Helper/Config.php
CHANGED
@@ -28,16 +28,19 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract
|
|
28 |
const EXCLUDED_PAGES = 'algoliasearch/autocomplete/excluded_pages';
|
29 |
const MIN_POPULARITY = 'algoliasearch/autocomplete/min_popularity';
|
30 |
const MIN_NUMBER_OF_RESULTS = 'algoliasearch/autocomplete/min_number_of_results';
|
|
|
31 |
|
32 |
const NUMBER_OF_PRODUCT_RESULTS = 'algoliasearch/products/number_product_results';
|
33 |
const PRODUCT_ATTRIBUTES = 'algoliasearch/products/product_additional_attributes';
|
34 |
const PRODUCT_CUSTOM_RANKING = 'algoliasearch/products/custom_ranking_product_attributes';
|
35 |
const RESULTS_LIMIT = 'algoliasearch/products/results_limit';
|
36 |
const SHOW_SUGGESTIONS_NO_RESULTS = 'algoliasearch/products/show_suggestions_on_no_result_page';
|
|
|
37 |
|
38 |
const CATEGORY_ATTRIBUTES = 'algoliasearch/categories/category_additional_attributes2';
|
39 |
const INDEX_PRODUCT_COUNT = 'algoliasearch/categories/index_product_count';
|
40 |
const CATEGORY_CUSTOM_RANKING = 'algoliasearch/categories/custom_ranking_category_attributes';
|
|
|
41 |
|
42 |
|
43 |
const IS_ACTIVE = 'algoliasearch/queue/active';
|
@@ -60,6 +63,16 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract
|
|
60 |
|
61 |
protected $_productTypeMap = array();
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
public function isDefaultSelector($storeId = null)
|
64 |
{
|
65 |
return '.algolia-search-input' === $this->getAutocompleteSelector($storeId);
|
@@ -259,6 +272,11 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract
|
|
259 |
return array();
|
260 |
}
|
261 |
|
|
|
|
|
|
|
|
|
|
|
262 |
public function getSortingIndices($storeId = NULL)
|
263 |
{
|
264 |
$product_helper = Mage::helper('algoliasearch/entity_producthelper');
|
@@ -297,22 +315,52 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract
|
|
297 |
|
298 |
public function getApplicationID($storeId = NULL)
|
299 |
{
|
300 |
-
return Mage::getStoreConfig(self::APPLICATION_ID, $storeId);
|
301 |
}
|
302 |
|
303 |
public function getAPIKey($storeId = NULL)
|
304 |
{
|
305 |
-
return Mage::getStoreConfig(self::API_KEY, $storeId);
|
306 |
}
|
307 |
|
308 |
public function getSearchOnlyAPIKey($storeId = NULL)
|
309 |
{
|
310 |
-
return Mage::getStoreConfig(self::SEARCH_ONLY_API_KEY, $storeId);
|
311 |
}
|
312 |
|
313 |
public function getIndexPrefix($storeId = NULL)
|
314 |
{
|
315 |
-
return Mage::getStoreConfig(self::INDEX_PREFIX, $storeId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
316 |
}
|
317 |
|
318 |
public function getCategoryAdditionalAttributes($storeId = NULL)
|
28 |
const EXCLUDED_PAGES = 'algoliasearch/autocomplete/excluded_pages';
|
29 |
const MIN_POPULARITY = 'algoliasearch/autocomplete/min_popularity';
|
30 |
const MIN_NUMBER_OF_RESULTS = 'algoliasearch/autocomplete/min_number_of_results';
|
31 |
+
const RENDER_TEMPLATE_DIRECTIVES = 'algoliasearch/autocomplete/render_template_directives';
|
32 |
|
33 |
const NUMBER_OF_PRODUCT_RESULTS = 'algoliasearch/products/number_product_results';
|
34 |
const PRODUCT_ATTRIBUTES = 'algoliasearch/products/product_additional_attributes';
|
35 |
const PRODUCT_CUSTOM_RANKING = 'algoliasearch/products/custom_ranking_product_attributes';
|
36 |
const RESULTS_LIMIT = 'algoliasearch/products/results_limit';
|
37 |
const SHOW_SUGGESTIONS_NO_RESULTS = 'algoliasearch/products/show_suggestions_on_no_result_page';
|
38 |
+
const INDEX_OUT_OF_STOCK_OPTIONS = 'algoliasearch/products/index_out_of_stock_options';
|
39 |
|
40 |
const CATEGORY_ATTRIBUTES = 'algoliasearch/categories/category_additional_attributes2';
|
41 |
const INDEX_PRODUCT_COUNT = 'algoliasearch/categories/index_product_count';
|
42 |
const CATEGORY_CUSTOM_RANKING = 'algoliasearch/categories/custom_ranking_category_attributes';
|
43 |
+
const SHOW_CATS_NOT_INCLUDED_IN_NAVIGATION = 'algoliasearch/categories/show_cats_not_included_in_navigation';
|
44 |
|
45 |
|
46 |
const IS_ACTIVE = 'algoliasearch/queue/active';
|
63 |
|
64 |
protected $_productTypeMap = array();
|
65 |
|
66 |
+
public function indexOutOfStockOptions($storeId = null)
|
67 |
+
{
|
68 |
+
return Mage::getStoreConfigFlag(self::INDEX_OUT_OF_STOCK_OPTIONS, $storeId);
|
69 |
+
}
|
70 |
+
|
71 |
+
public function showCatsNotIncludedInNavigation($storeId = null)
|
72 |
+
{
|
73 |
+
return Mage::getStoreConfigFlag(self::SHOW_CATS_NOT_INCLUDED_IN_NAVIGATION, $storeId);
|
74 |
+
}
|
75 |
+
|
76 |
public function isDefaultSelector($storeId = null)
|
77 |
{
|
78 |
return '.algolia-search-input' === $this->getAutocompleteSelector($storeId);
|
272 |
return array();
|
273 |
}
|
274 |
|
275 |
+
public function getRenderTemplateDirectives($storeId = NULL)
|
276 |
+
{
|
277 |
+
return Mage::getStoreConfigFlag(self::RENDER_TEMPLATE_DIRECTIVES, $storeId);
|
278 |
+
}
|
279 |
+
|
280 |
public function getSortingIndices($storeId = NULL)
|
281 |
{
|
282 |
$product_helper = Mage::helper('algoliasearch/entity_producthelper');
|
315 |
|
316 |
public function getApplicationID($storeId = NULL)
|
317 |
{
|
318 |
+
return trim(Mage::getStoreConfig(self::APPLICATION_ID, $storeId));
|
319 |
}
|
320 |
|
321 |
public function getAPIKey($storeId = NULL)
|
322 |
{
|
323 |
+
return trim(Mage::getStoreConfig(self::API_KEY, $storeId));
|
324 |
}
|
325 |
|
326 |
public function getSearchOnlyAPIKey($storeId = NULL)
|
327 |
{
|
328 |
+
return trim(Mage::getStoreConfig(self::SEARCH_ONLY_API_KEY, $storeId));
|
329 |
}
|
330 |
|
331 |
public function getIndexPrefix($storeId = NULL)
|
332 |
{
|
333 |
+
return trim(Mage::getStoreConfig(self::INDEX_PREFIX, $storeId));
|
334 |
+
}
|
335 |
+
|
336 |
+
public function getAttributesToRetrieve($group_id)
|
337 |
+
{
|
338 |
+
if (false === $this->isCustomerGroupsEnabled()) {
|
339 |
+
return [];
|
340 |
+
}
|
341 |
+
|
342 |
+
$attributes = array();
|
343 |
+
foreach ($this->getProductAdditionalAttributes() as $attribute) {
|
344 |
+
if ($attribute['attribute'] !== 'price') {
|
345 |
+
$attributes[] = $attribute['attribute'];
|
346 |
+
}
|
347 |
+
}
|
348 |
+
|
349 |
+
$attributes = array_merge($attributes, ['objectID', 'name', 'url', 'visibility_search', 'visibility_catalog', 'categories', 'categories_without_path', 'thumbnail_url', 'image_url', 'in_stock', 'type_id']);
|
350 |
+
|
351 |
+
$currencies = Mage::getModel('directory/currency')->getConfigAllowCurrencies();
|
352 |
+
|
353 |
+
foreach ($currencies as $currency) {
|
354 |
+
$attributes[] = 'price.'.$currency.'.default';
|
355 |
+
$attributes[] = 'price.'.$currency.'.default_formated';
|
356 |
+
$attributes[] = 'price.'.$currency.'.group_'.$group_id;
|
357 |
+
$attributes[] = 'price.'.$currency.'.group_'.$group_id.'_formated';
|
358 |
+
$attributes[] = 'price.'.$currency.'.special_from_date';
|
359 |
+
$attributes[] = 'price.'.$currency.'.special_to_date';
|
360 |
+
}
|
361 |
+
|
362 |
+
|
363 |
+
return ['attributesToRetrieve' => $attributes];
|
364 |
}
|
365 |
|
366 |
public function getCategoryAdditionalAttributes($storeId = NULL)
|
app/code/community/Algolia/Algoliasearch/Helper/Data.php
CHANGED
@@ -7,6 +7,8 @@ if (class_exists('AlgoliaSearch\Client', false) == false)
|
|
7 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/ClientContext.php';
|
8 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Client.php';
|
9 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Index.php';
|
|
|
|
|
10 |
}
|
11 |
|
12 |
class Algolia_Algoliasearch_Helper_Data extends Mage_Core_Helper_Abstract
|
@@ -25,7 +27,7 @@ class Algolia_Algoliasearch_Helper_Data extends Mage_Core_Helper_Abstract
|
|
25 |
|
26 |
public function __construct()
|
27 |
{
|
28 |
-
\AlgoliaSearch\Version::$custom_value = " Magento (1.5.
|
29 |
|
30 |
$this->algolia_helper = Mage::helper('algoliasearch/algoliahelper');
|
31 |
|
7 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/ClientContext.php';
|
8 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Client.php';
|
9 |
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/Index.php';
|
10 |
+
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/PlacesIndex.php';
|
11 |
+
require_once Mage::getBaseDir('lib').'/AlgoliaSearch/IndexBrowser.php';
|
12 |
}
|
13 |
|
14 |
class Algolia_Algoliasearch_Helper_Data extends Mage_Core_Helper_Abstract
|
27 |
|
28 |
public function __construct()
|
29 |
{
|
30 |
+
\AlgoliaSearch\Version::$custom_value = " Magento (1.5.5)";
|
31 |
|
32 |
$this->algolia_helper = Mage::helper('algoliasearch/algoliahelper');
|
33 |
|
app/code/community/Algolia/Algoliasearch/Helper/Entity/Additionalsectionshelper.php
CHANGED
@@ -21,11 +21,13 @@ class Algolia_Algoliasearch_Helper_Entity_Additionalsectionshelper extends Algol
|
|
21 |
$products = Mage::getResourceModel('catalog/product_collection')
|
22 |
->addStoreFilter($storeId)
|
23 |
->addAttributeToFilter('visibility', array('in' => Mage::getSingleton('catalog/product_visibility')->getVisibleInSearchIds()))
|
|
|
24 |
->addAttributeToFilter($attributeCode, array('notnull' => true))
|
25 |
->addAttributeToFilter($attributeCode, array('neq' => ''))
|
26 |
->addAttributeToSelect($attributeCode);
|
27 |
|
28 |
-
$usedAttributeValues =
|
|
|
29 |
|
30 |
$attributeModel = Mage::getSingleton('eav/config')
|
31 |
->getAttribute('catalog_product', $attributeCode)
|
@@ -45,7 +47,7 @@ class Algolia_Algoliasearch_Helper_Entity_Additionalsectionshelper extends Algol
|
|
45 |
$values = array($values);
|
46 |
}
|
47 |
|
48 |
-
$values = array_map(function ($value) use ($section) {
|
49 |
|
50 |
$record = array(
|
51 |
'objectID' => $value,
|
@@ -54,7 +56,8 @@ class Algolia_Algoliasearch_Helper_Entity_Additionalsectionshelper extends Algol
|
|
54 |
|
55 |
$transport = new Varien_Object($record);
|
56 |
|
57 |
-
Mage::dispatchEvent('algolia_additional_section_item_index_before',
|
|
|
58 |
|
59 |
$record = $transport->getData();
|
60 |
|
@@ -63,4 +66,4 @@ class Algolia_Algoliasearch_Helper_Entity_Additionalsectionshelper extends Algol
|
|
63 |
|
64 |
return $values;
|
65 |
}
|
66 |
-
}
|
21 |
$products = Mage::getResourceModel('catalog/product_collection')
|
22 |
->addStoreFilter($storeId)
|
23 |
->addAttributeToFilter('visibility', array('in' => Mage::getSingleton('catalog/product_visibility')->getVisibleInSearchIds()))
|
24 |
+
->addAttributeToFilter('status', array('eq' => Mage_Catalog_Model_Product_Status::STATUS_ENABLED))
|
25 |
->addAttributeToFilter($attributeCode, array('notnull' => true))
|
26 |
->addAttributeToFilter($attributeCode, array('neq' => ''))
|
27 |
->addAttributeToSelect($attributeCode);
|
28 |
|
29 |
+
$usedAttributeValues = array_keys(array_flip( // array unique
|
30 |
+
explode(',', implode(',', $products->getColumnValues($attributeCode)))));
|
31 |
|
32 |
$attributeModel = Mage::getSingleton('eav/config')
|
33 |
->getAttribute('catalog_product', $attributeCode)
|
47 |
$values = array($values);
|
48 |
}
|
49 |
|
50 |
+
$values = array_map(function ($value) use ($section, $storeId) {
|
51 |
|
52 |
$record = array(
|
53 |
'objectID' => $value,
|
56 |
|
57 |
$transport = new Varien_Object($record);
|
58 |
|
59 |
+
Mage::dispatchEvent('algolia_additional_section_item_index_before',
|
60 |
+
array('section' => $section, 'record' => $transport, 'store_id' => $storeId));
|
61 |
|
62 |
$record = $transport->getData();
|
63 |
|
66 |
|
67 |
return $values;
|
68 |
}
|
69 |
+
}
|
app/code/community/Algolia/Algoliasearch/Helper/Entity/Categoryhelper.php
CHANGED
@@ -66,13 +66,14 @@ class Algolia_Algoliasearch_Helper_Entity_Categoryhelper extends Algolia_Algolia
|
|
66 |
foreach ($unserializedCategorysAttrs as $attr)
|
67 |
$additionalAttr[] = $attr['attribute'];
|
68 |
|
|
|
|
|
69 |
$categories
|
70 |
->addPathFilter($storeRootCategoryPath)
|
71 |
->addNameToResult()
|
72 |
->addUrlRewriteToResult()
|
73 |
->addIsActiveFilter()
|
74 |
->setStoreId($storeId)
|
75 |
-
->addAttributeToFilter('include_in_menu', '1')
|
76 |
->addAttributeToSelect(array_merge(array('name'), $additionalAttr))
|
77 |
->addFieldToFilter('level', array('gt' => 1));
|
78 |
|
@@ -140,14 +141,15 @@ class Algolia_Algoliasearch_Helper_Entity_Categoryhelper extends Algolia_Algolia
|
|
140 |
} catch (Exception $e) { /* no image, no default: not fatal */
|
141 |
}
|
142 |
$data = array(
|
143 |
-
'objectID'
|
144 |
-
'name'
|
145 |
-
'path'
|
146 |
-
'level'
|
147 |
-
'url'
|
148 |
-
'
|
149 |
-
'
|
150 |
-
'
|
|
|
151 |
);
|
152 |
|
153 |
if ( ! empty($image_url)) {
|
@@ -158,11 +160,11 @@ class Algolia_Algoliasearch_Helper_Entity_Categoryhelper extends Algolia_Algolia
|
|
158 |
{
|
159 |
$value = $category->getData($attribute['attribute']);
|
160 |
|
161 |
-
$
|
162 |
|
163 |
-
if ($
|
164 |
{
|
165 |
-
$value = $
|
166 |
}
|
167 |
|
168 |
if (isset($data[$attribute['attribute']]))
|
66 |
foreach ($unserializedCategorysAttrs as $attr)
|
67 |
$additionalAttr[] = $attr['attribute'];
|
68 |
|
69 |
+
$additionalAttr[] = 'include_in_menu';
|
70 |
+
|
71 |
$categories
|
72 |
->addPathFilter($storeRootCategoryPath)
|
73 |
->addNameToResult()
|
74 |
->addUrlRewriteToResult()
|
75 |
->addIsActiveFilter()
|
76 |
->setStoreId($storeId)
|
|
|
77 |
->addAttributeToSelect(array_merge(array('name'), $additionalAttr))
|
78 |
->addFieldToFilter('level', array('gt' => 1));
|
79 |
|
141 |
} catch (Exception $e) { /* no image, no default: not fatal */
|
142 |
}
|
143 |
$data = array(
|
144 |
+
'objectID' => $category->getId(),
|
145 |
+
'name' => $category->getName(),
|
146 |
+
'path' => $path,
|
147 |
+
'level' => $category->getLevel(),
|
148 |
+
'url' => $category->getUrl(),
|
149 |
+
'include_in_menu' => $category->getIncludeInMenu(),
|
150 |
+
'_tags' => array('category'),
|
151 |
+
'popularity' => 1,
|
152 |
+
'product_count' => $category->getProductCount()
|
153 |
);
|
154 |
|
155 |
if ( ! empty($image_url)) {
|
160 |
{
|
161 |
$value = $category->getData($attribute['attribute']);
|
162 |
|
163 |
+
$attribute_resource = $category->getResource()->getAttribute($attribute['attribute']);
|
164 |
|
165 |
+
if ($attribute_resource)
|
166 |
{
|
167 |
+
$value = $attribute_resource->getFrontend()->getValue($category);
|
168 |
}
|
169 |
|
170 |
if (isset($data[$attribute['attribute']]))
|
app/code/community/Algolia/Algoliasearch/Helper/Entity/Helper.php
CHANGED
@@ -70,6 +70,7 @@ abstract class Algolia_Algoliasearch_Helper_Entity_Helper
|
|
70 |
$s = trim(preg_replace('/\s+/', ' ', $s));
|
71 |
$s = preg_replace('/ /', ' ', $s);
|
72 |
$s = preg_replace('!\s+!', ' ', $s);
|
|
|
73 |
|
74 |
return trim(strip_tags($s));
|
75 |
}
|
@@ -231,4 +232,4 @@ abstract class Algolia_Algoliasearch_Helper_Entity_Helper
|
|
231 |
return $store_ids;
|
232 |
}
|
233 |
|
234 |
-
}
|
70 |
$s = trim(preg_replace('/\s+/', ' ', $s));
|
71 |
$s = preg_replace('/ /', ' ', $s);
|
72 |
$s = preg_replace('!\s+!', ' ', $s);
|
73 |
+
$s = preg_replace('/\{\{[^}]+\}\}/', ' ', $s);
|
74 |
|
75 |
return trim(strip_tags($s));
|
76 |
}
|
232 |
return $store_ids;
|
233 |
}
|
234 |
|
235 |
+
}
|
app/code/community/Algolia/Algoliasearch/Helper/Entity/Pagehelper.php
CHANGED
@@ -45,14 +45,19 @@ class Algolia_Algoliasearch_Helper_Entity_Pagehelper extends Algolia_Algoliasear
|
|
45 |
if (! $page->getId())
|
46 |
continue;
|
47 |
|
48 |
-
$
|
|
|
|
|
|
|
|
|
49 |
|
|
|
50 |
$page_obj['url'] = Mage::helper('cms/page')->getPageUrl($page->getId());
|
51 |
-
$page_obj['content'] = $this->strip($
|
52 |
|
53 |
$pages[] = $page_obj;
|
54 |
}
|
55 |
|
56 |
return $pages;
|
57 |
}
|
58 |
-
}
|
45 |
if (! $page->getId())
|
46 |
continue;
|
47 |
|
48 |
+
$content = $page->getContent();
|
49 |
+
if ($this->config->getRenderTemplateDirectives()) {
|
50 |
+
$tmplProc = Mage::helper('cms')->getPageTemplateProcessor();
|
51 |
+
$content = $tmplProc->filter($content);
|
52 |
+
}
|
53 |
|
54 |
+
$page_obj['objectID'] = $page->getId();
|
55 |
$page_obj['url'] = Mage::helper('cms/page')->getPageUrl($page->getId());
|
56 |
+
$page_obj['content'] = $this->strip($content);
|
57 |
|
58 |
$pages[] = $page_obj;
|
59 |
}
|
60 |
|
61 |
return $pages;
|
62 |
}
|
63 |
+
}
|
app/code/community/Algolia/Algoliasearch/Helper/Entity/Producthelper.php
CHANGED
@@ -33,13 +33,7 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
33 |
|
34 |
$productAttributes = array_merge(array('name', 'path', 'categories', 'categories_without_path', 'description', 'ordered_qty', 'total_ordered', 'stock_qty', 'rating_summary', 'media_gallery'), $allAttributes);
|
35 |
|
36 |
-
$excludedAttributes =
|
37 |
-
'all_children', 'available_sort_by', 'children', 'children_count', 'custom_apply_to_products',
|
38 |
-
'custom_design', 'custom_design_from', 'custom_design_to', 'custom_layout_update', 'custom_use_parent_settings',
|
39 |
-
'default_sort_by', 'display_mode', 'filter_price_range', 'global_position', 'image', 'include_in_menu', 'is_active',
|
40 |
-
'is_always_include_in_menu', 'is_anchor', 'landing_page', 'level', 'lower_cms_block',
|
41 |
-
'page_layout', 'path_in_store', 'position', 'small_image', 'thumbnail', 'url_key', 'url_path',
|
42 |
-
'visible_in_menu');
|
43 |
|
44 |
$productAttributes = array_diff($productAttributes, $excludedAttributes);
|
45 |
|
@@ -59,6 +53,18 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
59 |
return $attributes;
|
60 |
}
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
public function isAttributeEnabled($additionalAttributes, $attr_name)
|
63 |
{
|
64 |
foreach ($additionalAttributes as $attr)
|
@@ -76,21 +82,26 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
76 |
$products = $products->setStoreId($storeId)
|
77 |
->addStoreFilter($storeId);
|
78 |
|
79 |
-
if ($only_visible)
|
80 |
$products = $products->addAttributeToFilter('visibility', array('in' => Mage::getSingleton('catalog/product_visibility')->getVisibleInSiteIds()));
|
|
|
|
|
|
|
81 |
|
82 |
-
if (false === $this->config->getShowOutOfStock($storeId))
|
83 |
Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($products);
|
|
|
84 |
|
85 |
-
$products = $products->
|
86 |
-
->addAttributeToSelect('special_from_date')
|
87 |
->addAttributeToSelect('special_to_date')
|
88 |
->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
|
89 |
|
90 |
$additionalAttr = $this->config->getProductAdditionalAttributes($storeId);
|
91 |
|
92 |
-
foreach
|
93 |
-
|
|
|
|
|
94 |
|
95 |
$products = $products->addAttributeToSelect(array_values(array_merge(static::$_predefinedProductAttributes, $additionalAttr)));
|
96 |
|
@@ -197,18 +208,15 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
197 |
|
198 |
foreach ($sorting_indices as $values)
|
199 |
{
|
200 |
-
if ($this->config->isCustomerGroupsEnabled($storeId))
|
201 |
{
|
202 |
-
|
203 |
{
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
$slaves[] = $this->getIndexName($storeId) . '_' .$values['attribute'].'_' . $suffix_index_name . '_' . $values['sort'];
|
211 |
-
}
|
212 |
}
|
213 |
}
|
214 |
else
|
@@ -224,7 +232,7 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
224 |
|
225 |
foreach ($sorting_indices as $values)
|
226 |
{
|
227 |
-
if ($this->config->isCustomerGroupsEnabled($storeId) &&
|
228 |
{
|
229 |
foreach ($groups = Mage::getModel('customer/group')->getCollection() as $group)
|
230 |
{
|
@@ -232,7 +240,7 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
232 |
|
233 |
$suffix_index_name = 'group_' . $group_id;
|
234 |
|
235 |
-
$sort_attribute =
|
236 |
|
237 |
$mergeSettings['ranking'] = array($values['sort'] . '(' . $sort_attribute . ')', 'typo', 'geo', 'words', 'proximity', 'attribute', 'exact', 'custom');
|
238 |
|
@@ -241,7 +249,7 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
241 |
}
|
242 |
else
|
243 |
{
|
244 |
-
$sort_attribute =
|
245 |
|
246 |
$mergeSettings['ranking'] = array($values['sort'] . '(' . $sort_attribute . ')', 'typo', 'geo', 'words', 'proximity', 'attribute', 'exact', 'custom');
|
247 |
|
@@ -468,6 +476,15 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
468 |
}
|
469 |
}
|
470 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
471 |
public function getObject(Mage_Catalog_Model_Product $product)
|
472 |
{
|
473 |
$type = $this->config->getMappedProductType($product->getTypeId());
|
@@ -512,7 +529,6 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
512 |
$categoryCollection = Mage::getResourceModel('catalog/category_collection')
|
513 |
->addAttributeToSelect('name')
|
514 |
->addAttributeToFilter('entity_id', $_categoryIds)
|
515 |
-
->addAttributeToFilter('include_in_menu', '1')
|
516 |
->addFieldToFilter('level', array('gt' => 1))
|
517 |
->addIsActiveFilter();
|
518 |
|
@@ -684,23 +700,28 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
684 |
|
685 |
foreach ($additionalAttributes as $attribute)
|
686 |
{
|
687 |
-
|
|
|
688 |
continue;
|
689 |
|
690 |
-
$value = $product->getData($
|
691 |
|
692 |
-
$
|
693 |
|
694 |
-
if ($
|
695 |
{
|
696 |
-
$
|
697 |
|
698 |
-
if ($value === null)
|
699 |
{
|
700 |
/** Get values as array in children */
|
701 |
if ($type == 'configurable' || $type == 'grouped' || $type == 'bundle')
|
702 |
{
|
703 |
-
$
|
|
|
|
|
|
|
|
|
704 |
|
705 |
$all_sub_products_out_of_stock = true;
|
706 |
|
@@ -708,27 +729,22 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
708 |
{
|
709 |
$isInStock = (int) $sub_product->getStockItem()->getIsInStock();
|
710 |
|
711 |
-
if ($isInStock == false)
|
712 |
continue;
|
713 |
|
714 |
$all_sub_products_out_of_stock = false;
|
715 |
|
716 |
-
$value = $sub_product->getData($
|
717 |
|
718 |
if ($value)
|
719 |
{
|
720 |
-
$
|
721 |
-
|
722 |
-
if ($value_text)
|
723 |
-
$values[] = $value_text;
|
724 |
-
else
|
725 |
-
$values[] = $attribute_ressource->getFrontend()->getValue($sub_product);
|
726 |
}
|
727 |
}
|
728 |
|
729 |
if (is_array($values) && count($values) > 0)
|
730 |
{
|
731 |
-
$customData[$
|
732 |
}
|
733 |
|
734 |
if ($customData['in_stock'] && $all_sub_products_out_of_stock) {
|
@@ -740,19 +756,11 @@ class Algolia_Algoliasearch_Helper_Entity_Producthelper extends Algolia_Algolias
|
|
740 |
}
|
741 |
else
|
742 |
{
|
743 |
-
$
|
744 |
-
|
745 |
-
if ($value_text)
|
746 |
-
$value = $value_text;
|
747 |
-
else
|
748 |
-
{
|
749 |
-
$attribute_ressource = $attribute_ressource->setStoreId($product->getStoreId());
|
750 |
-
$value = $attribute_ressource->getFrontend()->getValue($product);
|
751 |
-
}
|
752 |
|
753 |
if ($value)
|
754 |
{
|
755 |
-
$customData[$
|
756 |
}
|
757 |
}
|
758 |
}
|
33 |
|
34 |
$productAttributes = array_merge(array('name', 'path', 'categories', 'categories_without_path', 'description', 'ordered_qty', 'total_ordered', 'stock_qty', 'rating_summary', 'media_gallery'), $allAttributes);
|
35 |
|
36 |
+
$excludedAttributes = $this->getExcludedAttributes();
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
$productAttributes = array_diff($productAttributes, $excludedAttributes);
|
39 |
|
53 |
return $attributes;
|
54 |
}
|
55 |
|
56 |
+
protected function getExcludedAttributes()
|
57 |
+
{
|
58 |
+
return array(
|
59 |
+
'all_children', 'available_sort_by', 'children', 'children_count', 'custom_apply_to_products',
|
60 |
+
'custom_design', 'custom_design_from', 'custom_design_to', 'custom_layout_update', 'custom_use_parent_settings',
|
61 |
+
'default_sort_by', 'display_mode', 'filter_price_range', 'global_position', 'image', 'include_in_menu', 'is_active',
|
62 |
+
'is_always_include_in_menu', 'is_anchor', 'landing_page', 'level', 'lower_cms_block',
|
63 |
+
'page_layout', 'path_in_store', 'position', 'small_image', 'thumbnail', 'url_key', 'url_path',
|
64 |
+
'visible_in_menu'
|
65 |
+
);
|
66 |
+
}
|
67 |
+
|
68 |
public function isAttributeEnabled($additionalAttributes, $attr_name)
|
69 |
{
|
70 |
foreach ($additionalAttributes as $attr)
|
82 |
$products = $products->setStoreId($storeId)
|
83 |
->addStoreFilter($storeId);
|
84 |
|
85 |
+
if ($only_visible) {
|
86 |
$products = $products->addAttributeToFilter('visibility', array('in' => Mage::getSingleton('catalog/product_visibility')->getVisibleInSiteIds()));
|
87 |
+
$products = $products->addFinalPrice();
|
88 |
+
}
|
89 |
+
|
90 |
|
91 |
+
if (false === $this->config->getShowOutOfStock($storeId) && $only_visible == true) {
|
92 |
Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($products);
|
93 |
+
}
|
94 |
|
95 |
+
$products = $products->addAttributeToSelect('special_from_date')
|
|
|
96 |
->addAttributeToSelect('special_to_date')
|
97 |
->addAttributeToFilter('status', Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
|
98 |
|
99 |
$additionalAttr = $this->config->getProductAdditionalAttributes($storeId);
|
100 |
|
101 |
+
/** Map instead of foreach because otherwise it adds quotes to the last attribute **/
|
102 |
+
$additionalAttr = array_map(function($attr) {
|
103 |
+
return $attr['attribute'];
|
104 |
+
}, $additionalAttr);
|
105 |
|
106 |
$products = $products->addAttributeToSelect(array_values(array_merge(static::$_predefinedProductAttributes, $additionalAttr)));
|
107 |
|
208 |
|
209 |
foreach ($sorting_indices as $values)
|
210 |
{
|
211 |
+
if ($this->config->isCustomerGroupsEnabled($storeId) && $values['attribute'] === 'price')
|
212 |
{
|
213 |
+
foreach ($groups = Mage::getModel('customer/group')->getCollection() as $group)
|
214 |
{
|
215 |
+
$group_id = (int) $group->getData('customer_group_id');
|
216 |
+
|
217 |
+
$suffix_index_name = 'group_'.$group_id;
|
218 |
+
|
219 |
+
$slaves[] = $this->getIndexName($storeId).'_'.$values['attribute'].'_'.$suffix_index_name.'_'.$values['sort'];
|
|
|
|
|
|
|
220 |
}
|
221 |
}
|
222 |
else
|
232 |
|
233 |
foreach ($sorting_indices as $values)
|
234 |
{
|
235 |
+
if ($this->config->isCustomerGroupsEnabled($storeId) && $values['attribute'] === 'price')
|
236 |
{
|
237 |
foreach ($groups = Mage::getModel('customer/group')->getCollection() as $group)
|
238 |
{
|
240 |
|
241 |
$suffix_index_name = 'group_' . $group_id;
|
242 |
|
243 |
+
$sort_attribute = $values['attribute'] === 'price' ? $values['attribute'] . '.' . $currencies[0] . '.' . $suffix_index_name : $values['attribute'];
|
244 |
|
245 |
$mergeSettings['ranking'] = array($values['sort'] . '(' . $sort_attribute . ')', 'typo', 'geo', 'words', 'proximity', 'attribute', 'exact', 'custom');
|
246 |
|
249 |
}
|
250 |
else
|
251 |
{
|
252 |
+
$sort_attribute = $values['attribute'] === 'price' ? $values['attribute'] . '.' . $currencies[0] . '.' . 'default' : $values['attribute'];
|
253 |
|
254 |
$mergeSettings['ranking'] = array($values['sort'] . '(' . $sort_attribute . ')', 'typo', 'geo', 'words', 'proximity', 'attribute', 'exact', 'custom');
|
255 |
|
476 |
}
|
477 |
}
|
478 |
|
479 |
+
protected function getValueOrValueText(Mage_Catalog_Model_Product $product, $name, $resource)
|
480 |
+
{
|
481 |
+
$value_text = $product->getAttributeText($name);
|
482 |
+
if (!$value_text) {
|
483 |
+
$value_text = $resource->getFrontend()->getValue($product);
|
484 |
+
}
|
485 |
+
return $value_text;
|
486 |
+
}
|
487 |
+
|
488 |
public function getObject(Mage_Catalog_Model_Product $product)
|
489 |
{
|
490 |
$type = $this->config->getMappedProductType($product->getTypeId());
|
529 |
$categoryCollection = Mage::getResourceModel('catalog/category_collection')
|
530 |
->addAttributeToSelect('name')
|
531 |
->addAttributeToFilter('entity_id', $_categoryIds)
|
|
|
532 |
->addFieldToFilter('level', array('gt' => 1))
|
533 |
->addIsActiveFilter();
|
534 |
|
700 |
|
701 |
foreach ($additionalAttributes as $attribute)
|
702 |
{
|
703 |
+
$attribute_name = $attribute['attribute'];
|
704 |
+
if (isset($customData[$attribute_name]))
|
705 |
continue;
|
706 |
|
707 |
+
$value = $product->getData($attribute_name);
|
708 |
|
709 |
+
$attribute_resource = $product->getResource()->getAttribute($attribute_name);
|
710 |
|
711 |
+
if ($attribute_resource)
|
712 |
{
|
713 |
+
$attribute_resource->setStoreId($product->getStoreId());
|
714 |
|
715 |
+
if ($value === null || 'sku' == $attribute_name)
|
716 |
{
|
717 |
/** Get values as array in children */
|
718 |
if ($type == 'configurable' || $type == 'grouped' || $type == 'bundle')
|
719 |
{
|
720 |
+
if ($value === null) {
|
721 |
+
$values = array();
|
722 |
+
} else {
|
723 |
+
$values = array($this->getValueOrValueText($product, $attribute_name, $attribute_resource));
|
724 |
+
}
|
725 |
|
726 |
$all_sub_products_out_of_stock = true;
|
727 |
|
729 |
{
|
730 |
$isInStock = (int) $sub_product->getStockItem()->getIsInStock();
|
731 |
|
732 |
+
if ($isInStock == false && $this->config->indexOutOfStockOptions($product->getStoreId()) == false)
|
733 |
continue;
|
734 |
|
735 |
$all_sub_products_out_of_stock = false;
|
736 |
|
737 |
+
$value = $sub_product->getData($attribute_name);
|
738 |
|
739 |
if ($value)
|
740 |
{
|
741 |
+
$values[] = $this->getValueOrValueText($sub_product, $attribute_name, $attribute_resource);
|
|
|
|
|
|
|
|
|
|
|
742 |
}
|
743 |
}
|
744 |
|
745 |
if (is_array($values) && count($values) > 0)
|
746 |
{
|
747 |
+
$customData[$attribute_name] = array_values(array_unique($values));
|
748 |
}
|
749 |
|
750 |
if ($customData['in_stock'] && $all_sub_products_out_of_stock) {
|
756 |
}
|
757 |
else
|
758 |
{
|
759 |
+
$value = $this->getValueOrValueText($product, $attribute_name, $attribute_resource);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
760 |
|
761 |
if ($value)
|
762 |
{
|
763 |
+
$customData[$attribute_name] = $value;
|
764 |
}
|
765 |
}
|
766 |
}
|
app/code/community/Algolia/Algoliasearch/Helper/Entity/Suggestionhelper.php
CHANGED
@@ -2,6 +2,9 @@
|
|
2 |
|
3 |
class Algolia_Algoliasearch_Helper_Entity_Suggestionhelper extends Algolia_Algoliasearch_Helper_Entity_Helper
|
4 |
{
|
|
|
|
|
|
|
5 |
protected function getIndexNameSuffix()
|
6 |
{
|
7 |
return '_suggestions';
|
@@ -32,27 +35,50 @@ class Algolia_Algoliasearch_Helper_Entity_Suggestionhelper extends Algolia_Algol
|
|
32 |
|
33 |
public function getPopularQueries($storeId)
|
34 |
{
|
35 |
-
$
|
36 |
-
$collection->getSelect()->where('num_results >= '.$this->config->getMinNumberOfResults().' AND popularity >= ' . $this->config->getMinPopularity() .' AND query_text != "__empty__"');
|
37 |
-
$collection->getSelect()->limit(12);
|
38 |
-
$collection->setOrder('popularity', 'DESC');
|
39 |
-
$collection->setOrder('num_results', 'DESC');
|
40 |
-
$collection->setOrder('updated_at', 'ASC');
|
41 |
|
42 |
-
|
43 |
-
$
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
-
|
47 |
|
48 |
-
|
49 |
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
-
return
|
56 |
}
|
57 |
|
58 |
public function getSuggestionCollectionQuery($storeId)
|
2 |
|
3 |
class Algolia_Algoliasearch_Helper_Entity_Suggestionhelper extends Algolia_Algoliasearch_Helper_Entity_Helper
|
4 |
{
|
5 |
+
protected $_popularQueries = null;
|
6 |
+
protected $_popularQueriesCacheId = "algoliasearch_popular_queries_cache_tag";
|
7 |
+
|
8 |
protected function getIndexNameSuffix()
|
9 |
{
|
10 |
return '_suggestions';
|
35 |
|
36 |
public function getPopularQueries($storeId)
|
37 |
{
|
38 |
+
if ($this->_popularQueries == null) {
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
+
// load from cache if we can
|
41 |
+
$cachedPopularQueries = Mage::app()->loadCache($this->_popularQueriesCacheId);
|
42 |
+
if ($cachedPopularQueries) {
|
43 |
+
$this->_popularQueries = unserialize($cachedPopularQueries);
|
44 |
+
} else {
|
45 |
+
$collection = Mage::getResourceModel('catalogsearch/query_collection');
|
46 |
+
$collection->getSelect()->where('num_results >= ' . $this->config->getMinNumberOfResults() . ' AND popularity >= ' . $this->config->getMinPopularity() . ' AND query_text != "__empty__"');
|
47 |
+
$collection->getSelect()->limit(12);
|
48 |
+
$collection->setOrder('popularity', 'DESC');
|
49 |
+
$collection->setOrder('num_results', 'DESC');
|
50 |
+
$collection->setOrder('updated_at', 'ASC');
|
51 |
+
|
52 |
+
if ($storeId) {
|
53 |
+
$collection->getSelect()->where('store_id = ?', (int)$storeId);
|
54 |
+
}
|
55 |
|
56 |
+
$collection->load();
|
57 |
|
58 |
+
$suggestions = array();
|
59 |
|
60 |
+
/** @var $suggestion Mage_Catalog_Model_Category */
|
61 |
+
foreach ($collection as $suggestion)
|
62 |
+
if (strlen($suggestion['query_text']) >= 3)
|
63 |
+
$suggestions[] = $suggestion['query_text'];
|
64 |
+
|
65 |
+
$this->_popularQueries = array_slice($suggestions, 0, 9);
|
66 |
+
try { //save to cache
|
67 |
+
$cacheContent = serialize($this->_popularQueries);
|
68 |
+
$tags = array(
|
69 |
+
Mage_CatalogSearch_Model_Query::CACHE_TAG
|
70 |
+
);
|
71 |
+
|
72 |
+
Mage::app()->saveCache($cacheContent, $this->_popularQueriesCacheId, $tags, 604800);
|
73 |
+
|
74 |
+
} catch (Exception $e) {
|
75 |
+
// Exception = no caching
|
76 |
+
Mage::logException($e);
|
77 |
+
}
|
78 |
+
}
|
79 |
+
}
|
80 |
|
81 |
+
return $this->_popularQueries;
|
82 |
}
|
83 |
|
84 |
public function getSuggestionCollectionQuery($storeId)
|
app/code/community/Algolia/Algoliasearch/Helper/Logger.php
CHANGED
@@ -51,9 +51,9 @@ class Algolia_Algoliasearch_Helper_Logger extends Mage_Core_Helper_Abstract
|
|
51 |
$this->log('<<<<< END ' .$action. ' (' . $this->formatTime($this->timers[$action], microtime(true)) . ')');
|
52 |
}
|
53 |
|
54 |
-
public function log($message)
|
55 |
{
|
56 |
-
if ($this->config->isLoggingEnabled()) {
|
57 |
Mage::log($message, null, 'algolia.log');
|
58 |
}
|
59 |
}
|
51 |
$this->log('<<<<< END ' .$action. ' (' . $this->formatTime($this->timers[$action], microtime(true)) . ')');
|
52 |
}
|
53 |
|
54 |
+
public function log($message, $forceLog = false)
|
55 |
{
|
56 |
+
if ($this->config->isLoggingEnabled() || $forceLog) {
|
57 |
Mage::log($message, null, 'algolia.log');
|
58 |
}
|
59 |
}
|
app/code/community/Algolia/Algoliasearch/Model/Indexer/Algolia.php
CHANGED
@@ -109,19 +109,16 @@ class Algolia_Algoliasearch_Model_Indexer_Algolia extends Mage_Index_Model_Index
|
|
109 |
|
110 |
$product = Mage::getModel('catalog/product')->load($object->getProductId());
|
111 |
|
112 |
-
|
113 |
{
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
$this->logger->log($e->getMessage());
|
123 |
-
$this->logger->log($e->getTraceAsString());
|
124 |
-
}
|
125 |
}
|
126 |
}
|
127 |
}
|
@@ -132,84 +129,33 @@ class Algolia_Algoliasearch_Model_Indexer_Algolia extends Mage_Index_Model_Index
|
|
132 |
case Mage_Index_Model_Event::TYPE_SAVE:
|
133 |
/** @var $product Mage_Catalog_Model_Product */
|
134 |
$product = $event->getDataObject();
|
135 |
-
$
|
136 |
-
$
|
137 |
-
|
138 |
-
if ($product->getStatus() == Mage_Catalog_Model_Product_Status::STATUS_DISABLED)
|
139 |
-
{
|
140 |
-
$delete = TRUE;
|
141 |
-
}
|
142 |
-
elseif (! in_array($product->getData('visibility'), $visibleInSite))
|
143 |
-
{
|
144 |
-
$delete = TRUE;
|
145 |
-
}
|
146 |
|
147 |
-
|
148 |
-
|
149 |
-
$event->addNewData('catalogsearch_delete_product_id', $product->getId());
|
150 |
-
$event->addNewData('catalogsearch_update_category_id', $product->getCategoryIds());
|
151 |
-
}
|
152 |
-
else
|
153 |
{
|
154 |
-
$
|
|
|
155 |
|
156 |
-
|
157 |
-
if (isset(static::$product_categories[$product->getId()]))
|
158 |
-
{
|
159 |
-
$oldCategories = static::$product_categories[$product->getId()];
|
160 |
-
$newCategories = $product->getCategoryIds();
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
$event->addNewData('catalogsearch_update_category_id', $diffCategories);
|
165 |
-
}
|
166 |
}
|
167 |
|
168 |
-
break;
|
169 |
case Mage_Index_Model_Event::TYPE_DELETE:
|
170 |
|
171 |
/** @var $product Mage_Catalog_Model_Product */
|
172 |
$product = $event->getDataObject();
|
173 |
-
$event->addNewData('
|
174 |
$event->addNewData('catalogsearch_update_category_id', $product->getCategoryIds());
|
175 |
-
|
176 |
break;
|
|
|
177 |
case Mage_Index_Model_Event::TYPE_MASS_ACTION:
|
178 |
/** @var $actionObject Varien_Object */
|
179 |
$actionObject = $event->getDataObject();
|
180 |
|
181 |
-
$
|
182 |
-
|
183 |
-
// Check if status changed
|
184 |
-
$attrData = $actionObject->getAttributesData();
|
185 |
-
|
186 |
-
if (isset($attrData['status']))
|
187 |
-
{
|
188 |
-
$reindexData['catalogsearch_status'] = $attrData['status'];
|
189 |
-
}
|
190 |
-
|
191 |
-
// Check changed websites
|
192 |
-
if ($actionObject->getWebsiteIds())
|
193 |
-
{
|
194 |
-
$reindexData['catalogsearch_website_ids'] = $actionObject->getWebsiteIds();
|
195 |
-
$reindexData['catalogsearch_action_type'] = $actionObject->getActionType();
|
196 |
-
}
|
197 |
-
|
198 |
-
$reindexData['catalogsearch_force_reindex'] = TRUE;
|
199 |
-
|
200 |
-
if ($actionObject->getIsDeleted())
|
201 |
-
{
|
202 |
-
$reindexData['catalogsearch_delete_product_id'] = $actionObject->getProductIds();
|
203 |
-
}
|
204 |
-
else
|
205 |
-
{
|
206 |
-
$reindexData['catalogsearch_product_ids'] = $actionObject->getProductIds();
|
207 |
-
}
|
208 |
-
|
209 |
-
foreach ($reindexData as $k => $v)
|
210 |
-
{
|
211 |
-
$event->addNewData($k, $v);
|
212 |
-
}
|
213 |
|
214 |
break;
|
215 |
}
|
@@ -247,81 +193,18 @@ class Algolia_Algoliasearch_Model_Indexer_Algolia extends Mage_Index_Model_Index
|
|
247 |
$process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX);
|
248 |
}
|
249 |
|
250 |
-
/*
|
251 |
-
* Clear index for the deleted product.
|
252 |
-
*/
|
253 |
-
else if ( ! empty($data['catalogsearch_delete_product_id'])) {
|
254 |
-
$productId = $data['catalogsearch_delete_product_id'];
|
255 |
-
|
256 |
-
if ( ! $this->_isProductComposite($productId)) {
|
257 |
-
$parentIds = $this->_getResource()->getRelationsByChild($productId);
|
258 |
-
if ( ! empty($parentIds)) {
|
259 |
-
$this->engine
|
260 |
-
->rebuildProductIndex(null, $parentIds);
|
261 |
-
}
|
262 |
-
}
|
263 |
-
|
264 |
-
$this->engine
|
265 |
-
->removeProducts(null, $productId);
|
266 |
-
}
|
267 |
-
|
268 |
-
// Mass action
|
269 |
-
else if ( ! empty($data['catalogsearch_product_ids'])) {
|
270 |
-
$productIds = $data['catalogsearch_product_ids'];
|
271 |
-
|
272 |
-
if (!empty($productIds))
|
273 |
-
{
|
274 |
-
if ( ! empty($data['catalogsearch_website_ids']))
|
275 |
-
{
|
276 |
-
$websiteIds = $data['catalogsearch_website_ids'];
|
277 |
-
$actionType = $data['catalogsearch_action_type'];
|
278 |
-
foreach ($websiteIds as $websiteId)
|
279 |
-
{
|
280 |
-
foreach (Mage::app()->getWebsite($websiteId)->getStoreIds() as $storeId) {
|
281 |
-
if ($actionType == 'remove')
|
282 |
-
{
|
283 |
-
$this->engine->removeProducts($storeId, $productIds);
|
284 |
-
}
|
285 |
-
else if ($actionType == 'add')
|
286 |
-
{
|
287 |
-
$this->engine->rebuildProductIndex($storeId, $productIds);
|
288 |
-
}
|
289 |
-
}
|
290 |
-
}
|
291 |
-
}
|
292 |
-
else if (isset($data['catalogsearch_status']))
|
293 |
-
{
|
294 |
-
$status = $data['catalogsearch_status'];
|
295 |
-
if ($status == Mage_Catalog_Model_Product_Status::STATUS_ENABLED) {
|
296 |
-
$this->engine->rebuildProductIndex(null, $productIds);
|
297 |
-
}
|
298 |
-
else
|
299 |
-
{
|
300 |
-
$this->engine->removeProducts(null, $productIds);
|
301 |
-
}
|
302 |
-
}
|
303 |
-
else if (isset($data['catalogsearch_force_reindex']))
|
304 |
-
{
|
305 |
-
$this->engine
|
306 |
-
->rebuildProductIndex(null, $productIds);
|
307 |
-
}
|
308 |
-
}
|
309 |
-
}
|
310 |
-
|
311 |
if ( ! empty($data['catalogsearch_update_category_id'])) {
|
312 |
$updateCategoryIds = $data['catalogsearch_update_category_id'];
|
313 |
$updateCategoryIds = is_array($updateCategoryIds) ? $updateCategoryIds : array($updateCategoryIds);
|
314 |
|
315 |
-
foreach ($updateCategoryIds as $id)
|
316 |
-
{
|
317 |
$categories = Mage::getModel('catalog/category')->getCategories($id);
|
318 |
|
319 |
foreach ($categories as $category)
|
320 |
$updateCategoryIds[] = $category->getId();
|
321 |
}
|
322 |
|
323 |
-
$this->engine
|
324 |
-
->rebuildCategoryIndex(null, $updateCategoryIds);
|
325 |
}
|
326 |
|
327 |
/*
|
@@ -333,20 +216,18 @@ class Algolia_Algoliasearch_Model_Indexer_Algolia extends Mage_Index_Model_Index
|
|
333 |
$updateProductIds = is_array($updateProductIds) ? $updateProductIds : array($updateProductIds);
|
334 |
$productIds = $updateProductIds;
|
335 |
|
336 |
-
foreach ($updateProductIds as $updateProductId)
|
337 |
-
|
338 |
-
if (! $this->_isProductComposite($updateProductId))
|
339 |
-
{
|
340 |
$parentIds = $this->_getResource()->getRelationsByChild($updateProductId);
|
341 |
|
342 |
-
if (! empty($parentIds))
|
343 |
-
{
|
344 |
$productIds = array_merge($productIds, $parentIds);
|
345 |
}
|
346 |
}
|
347 |
}
|
348 |
|
349 |
if (!empty($productIds)) {
|
|
|
350 |
$this->engine->rebuildProductIndex(null, $productIds);
|
351 |
}
|
352 |
}
|
109 |
|
110 |
$product = Mage::getModel('catalog/product')->load($object->getProductId());
|
111 |
|
112 |
+
try // In case of wrong credentials or overquota or block account. To avoid checkout process to fail
|
113 |
{
|
114 |
+
$event->addNewData('catalogsearch_delete_product_id', $product->getId());
|
115 |
+
$event->addNewData('catalogsearch_update_category_id', $product->getCategoryIds());
|
116 |
+
}
|
117 |
+
catch(\Exception $e)
|
118 |
+
{
|
119 |
+
$this->logger->log('Error while trying to update stock');
|
120 |
+
$this->logger->log($e->getMessage());
|
121 |
+
$this->logger->log($e->getTraceAsString());
|
|
|
|
|
|
|
122 |
}
|
123 |
}
|
124 |
}
|
129 |
case Mage_Index_Model_Event::TYPE_SAVE:
|
130 |
/** @var $product Mage_Catalog_Model_Product */
|
131 |
$product = $event->getDataObject();
|
132 |
+
$event->addNewData('catalogsearch_update_product_id', $product->getId());
|
133 |
+
$event->addNewData('catalogsearch_update_category_id', $product->getCategoryIds());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
+
/* product_categories is filled in Observer::saveProduct */
|
136 |
+
if (isset(static::$product_categories[$product->getId()]))
|
|
|
|
|
|
|
|
|
137 |
{
|
138 |
+
$oldCategories = static::$product_categories[$product->getId()];
|
139 |
+
$newCategories = $product->getCategoryIds();
|
140 |
|
141 |
+
$diffCategories = array_merge(array_diff($oldCategories, $newCategories), array_diff($newCategories, $oldCategories));
|
|
|
|
|
|
|
|
|
142 |
|
143 |
+
$event->addNewData('catalogsearch_update_category_id', $diffCategories);
|
|
|
|
|
|
|
144 |
}
|
145 |
|
|
|
146 |
case Mage_Index_Model_Event::TYPE_DELETE:
|
147 |
|
148 |
/** @var $product Mage_Catalog_Model_Product */
|
149 |
$product = $event->getDataObject();
|
150 |
+
$event->addNewData('catalogsearch_update_product_id', $product->getId());
|
151 |
$event->addNewData('catalogsearch_update_category_id', $product->getCategoryIds());
|
|
|
152 |
break;
|
153 |
+
|
154 |
case Mage_Index_Model_Event::TYPE_MASS_ACTION:
|
155 |
/** @var $actionObject Varien_Object */
|
156 |
$actionObject = $event->getDataObject();
|
157 |
|
158 |
+
$event->addNewData('catalogsearch_update_product_id', $actionObject->getProductIds());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
160 |
break;
|
161 |
}
|
193 |
$process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX);
|
194 |
}
|
195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
196 |
if ( ! empty($data['catalogsearch_update_category_id'])) {
|
197 |
$updateCategoryIds = $data['catalogsearch_update_category_id'];
|
198 |
$updateCategoryIds = is_array($updateCategoryIds) ? $updateCategoryIds : array($updateCategoryIds);
|
199 |
|
200 |
+
foreach ($updateCategoryIds as $id) {
|
|
|
201 |
$categories = Mage::getModel('catalog/category')->getCategories($id);
|
202 |
|
203 |
foreach ($categories as $category)
|
204 |
$updateCategoryIds[] = $category->getId();
|
205 |
}
|
206 |
|
207 |
+
$this->engine->rebuildCategoryIndex(null, $updateCategoryIds);
|
|
|
208 |
}
|
209 |
|
210 |
/*
|
216 |
$updateProductIds = is_array($updateProductIds) ? $updateProductIds : array($updateProductIds);
|
217 |
$productIds = $updateProductIds;
|
218 |
|
219 |
+
foreach ($updateProductIds as $updateProductId) {
|
220 |
+
if (! $this->_isProductComposite($updateProductId)) {
|
|
|
|
|
221 |
$parentIds = $this->_getResource()->getRelationsByChild($updateProductId);
|
222 |
|
223 |
+
if (! empty($parentIds)) {
|
|
|
224 |
$productIds = array_merge($productIds, $parentIds);
|
225 |
}
|
226 |
}
|
227 |
}
|
228 |
|
229 |
if (!empty($productIds)) {
|
230 |
+
$this->engine->removeProducts(null, $productIds);
|
231 |
$this->engine->rebuildProductIndex(null, $productIds);
|
232 |
}
|
233 |
}
|
app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliacategories.php
CHANGED
@@ -70,7 +70,7 @@ class Algolia_Algoliasearch_Model_Indexer_Algoliacategories extends Mage_Index_M
|
|
70 |
$category = $event->getDataObject();
|
71 |
$productIds = $category->getAffectedProductIds();
|
72 |
|
73 |
-
if (! $category->getData('is_active')
|
74 |
{
|
75 |
$event->addNewData('catalogsearch_delete_category_id', array_merge(array($category->getId()), $category->getAllChildren(TRUE)));
|
76 |
|
70 |
$category = $event->getDataObject();
|
71 |
$productIds = $category->getAffectedProductIds();
|
72 |
|
73 |
+
if (! $category->getData('is_active'))
|
74 |
{
|
75 |
$event->addNewData('catalogsearch_delete_category_id', array_merge(array($category->getId()), $category->getAllChildren(TRUE)));
|
76 |
|
app/code/community/Algolia/Algoliasearch/Model/Resource/Fulltext/Collection.php
CHANGED
@@ -20,7 +20,19 @@ class Algolia_Algoliasearch_Model_Resource_Fulltext_Collection extends Mage_Cata
|
|
20 |
if ($config->isInstantEnabled($storeId) === false || $config->makeSeoRequest($storeId))
|
21 |
{
|
22 |
$algolia_query = $query !== '__empty__' ? $query : '';
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
}
|
25 |
|
26 |
|
20 |
if ($config->isInstantEnabled($storeId) === false || $config->makeSeoRequest($storeId))
|
21 |
{
|
22 |
$algolia_query = $query !== '__empty__' ? $query : '';
|
23 |
+
|
24 |
+
try
|
25 |
+
{
|
26 |
+
$data = Mage::helper('algoliasearch')->getSearchResult($algolia_query, $storeId);
|
27 |
+
}
|
28 |
+
catch (\Exception $e)
|
29 |
+
{
|
30 |
+
$logger = Mage::helper('algoliasearch/logger');
|
31 |
+
$logger->log($e->getMessage(), true);
|
32 |
+
$logger->log($e->getTraceAsString(), true);
|
33 |
+
|
34 |
+
return parent::addSearchFilter($query);
|
35 |
+
}
|
36 |
}
|
37 |
|
38 |
|
app/code/community/Algolia/Algoliasearch/etc/config.xml
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
<config>
|
3 |
<modules>
|
4 |
<Algolia_Algoliasearch>
|
5 |
-
<version>1.5.
|
6 |
</Algolia_Algoliasearch>
|
7 |
</modules>
|
8 |
<frontend>
|
@@ -151,6 +151,7 @@
|
|
151 |
<custom_ranking_product_attributes>a:1:{s:18:"_1427960305274_274";a:2:{s:9:"attribute";s:11:"ordered_qty";s:5:"order";s:4:"desc";}}</custom_ranking_product_attributes>
|
152 |
<results_limit>1000</results_limit>
|
153 |
<show_suggestions_on_no_result_page>1</show_suggestions_on_no_result_page>
|
|
|
154 |
</products>
|
155 |
<instant>
|
156 |
<replace_categories>1</replace_categories>
|
@@ -167,11 +168,13 @@
|
|
167 |
<sections>a:1:{s:18:"_1450089283397_397";a:3:{s:4:"name";s:5:"pages";s:5:"label";s:5:"Pages";s:11:"hitsPerPage";s:1:"2";}}</sections>
|
168 |
<min_popularity>1000</min_popularity>
|
169 |
<min_number_of_results>2</min_number_of_results>
|
|
|
170 |
</autocomplete>
|
171 |
<categories>
|
172 |
<number_category_suggestions>5</number_category_suggestions>
|
173 |
<category_additional_attributes2>a:7:{s:18:"_1427960339954_954";a:4:{s:9:"attribute";s:4:"name";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427960354437_437";a:4:{s:9:"attribute";s:4:"path";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427961004989_989";a:4:{s:9:"attribute";s:11:"description";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:9:"unordered";}s:18:"_1427961205511_511";a:4:{s:9:"attribute";s:10:"meta_title";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427961216134_134";a:4:{s:9:"attribute";s:13:"meta_keywords";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427961216916_916";a:4:{s:9:"attribute";s:16:"meta_description";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:9:"unordered";}s:18:"_1427977778338_338";a:4:{s:9:"attribute";s:13:"product_count";s:10:"searchable";s:1:"0";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}}</category_additional_attributes2>
|
174 |
<custom_ranking_category_attributes>a:1:{s:18:"_1427961035192_192";a:2:{s:9:"attribute";s:13:"product_count";s:5:"order";s:4:"desc";}}</custom_ranking_category_attributes>
|
|
|
175 |
</categories>
|
176 |
<queue>
|
177 |
<active>0</active>
|
@@ -199,4 +202,4 @@
|
|
199 |
</product_map>
|
200 |
</algoliasearch>
|
201 |
</default>
|
202 |
-
</config>
|
2 |
<config>
|
3 |
<modules>
|
4 |
<Algolia_Algoliasearch>
|
5 |
+
<version>1.5.5</version>
|
6 |
</Algolia_Algoliasearch>
|
7 |
</modules>
|
8 |
<frontend>
|
151 |
<custom_ranking_product_attributes>a:1:{s:18:"_1427960305274_274";a:2:{s:9:"attribute";s:11:"ordered_qty";s:5:"order";s:4:"desc";}}</custom_ranking_product_attributes>
|
152 |
<results_limit>1000</results_limit>
|
153 |
<show_suggestions_on_no_result_page>1</show_suggestions_on_no_result_page>
|
154 |
+
<index_out_of_stock_options>0</index_out_of_stock_options>
|
155 |
</products>
|
156 |
<instant>
|
157 |
<replace_categories>1</replace_categories>
|
168 |
<sections>a:1:{s:18:"_1450089283397_397";a:3:{s:4:"name";s:5:"pages";s:5:"label";s:5:"Pages";s:11:"hitsPerPage";s:1:"2";}}</sections>
|
169 |
<min_popularity>1000</min_popularity>
|
170 |
<min_number_of_results>2</min_number_of_results>
|
171 |
+
<render_template_directives>1</render_template_directives>
|
172 |
</autocomplete>
|
173 |
<categories>
|
174 |
<number_category_suggestions>5</number_category_suggestions>
|
175 |
<category_additional_attributes2>a:7:{s:18:"_1427960339954_954";a:4:{s:9:"attribute";s:4:"name";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427960354437_437";a:4:{s:9:"attribute";s:4:"path";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427961004989_989";a:4:{s:9:"attribute";s:11:"description";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:9:"unordered";}s:18:"_1427961205511_511";a:4:{s:9:"attribute";s:10:"meta_title";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427961216134_134";a:4:{s:9:"attribute";s:13:"meta_keywords";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}s:18:"_1427961216916_916";a:4:{s:9:"attribute";s:16:"meta_description";s:10:"searchable";s:1:"1";s:11:"retrievable";s:1:"1";s:5:"order";s:9:"unordered";}s:18:"_1427977778338_338";a:4:{s:9:"attribute";s:13:"product_count";s:10:"searchable";s:1:"0";s:11:"retrievable";s:1:"1";s:5:"order";s:7:"ordered";}}</category_additional_attributes2>
|
176 |
<custom_ranking_category_attributes>a:1:{s:18:"_1427961035192_192";a:2:{s:9:"attribute";s:13:"product_count";s:5:"order";s:4:"desc";}}</custom_ranking_category_attributes>
|
177 |
+
<show_cats_not_included_in_navigation>0</show_cats_not_included_in_navigation>
|
178 |
</categories>
|
179 |
<queue>
|
180 |
<active>0</active>
|
202 |
</product_map>
|
203 |
</algoliasearch>
|
204 |
</default>
|
205 |
+
</config>
|
app/code/community/Algolia/Algoliasearch/etc/system.xml
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
<algoliasearch translate="label" module="algoliasearch">
|
5 |
<label>
|
6 |
<![CDATA[
|
7 |
-
Algolia Search 1.5.
|
8 |
<style>
|
9 |
.algoliasearch-admin-menu span {
|
10 |
padding-left: 38px !important;
|
@@ -32,7 +32,7 @@
|
|
32 |
<sort_order>10</sort_order>
|
33 |
<show_in_default>1</show_in_default>
|
34 |
<show_in_website>1</show_in_website>
|
35 |
-
<show_in_store>1</show_in_store>
|
36 |
<expanded>1</expanded>
|
37 |
<comment>
|
38 |
<![CDATA[
|
@@ -146,7 +146,7 @@
|
|
146 |
<show_in_store>1</show_in_store>
|
147 |
<comment>
|
148 |
<![CDATA[
|
149 |
-
If set to Yes, the
|
150 |
]]>
|
151 |
</comment>
|
152 |
</is_popup_enabled>
|
@@ -258,11 +258,25 @@
|
|
258 |
<show_in_store>1</show_in_store>
|
259 |
<comment>
|
260 |
<![CDATA[
|
261 |
-
Configure here the pages you don't want to search in
|
262 |
-
<span style="font-size: 20px; color: #D83900">⚠</span> Do not forget to trigger the Algolia Search indexers whenever you modify those settings.
|
263 |
]]>
|
264 |
</comment>
|
265 |
</excluded_pages>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
</fields>
|
267 |
</autocomplete>
|
268 |
<instant>
|
@@ -428,6 +442,20 @@
|
|
428 |
]]>
|
429 |
</comment>
|
430 |
</show_suggestions_on_no_result_page>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
431 |
</fields>
|
432 |
</products>
|
433 |
<categories translate="label">
|
@@ -474,6 +502,20 @@
|
|
474 |
]]>
|
475 |
</comment>
|
476 |
</custom_ranking_category_attributes>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
477 |
</fields>
|
478 |
</categories>
|
479 |
<image translate="label">
|
@@ -503,7 +545,7 @@
|
|
503 |
<show_in_website>1</show_in_website>
|
504 |
<show_in_store>1</show_in_store>
|
505 |
<comment><![CDATA[You can specify the size of the images used at the Search Results Page.<br />
|
506 |
-
If your images are already present in some size,
|
507 |
</height>
|
508 |
<type translate="label comment">
|
509 |
<label>Type</label>
|
@@ -536,7 +578,7 @@
|
|
536 |
<show_in_store>1</show_in_store>
|
537 |
<comment>
|
538 |
<![CDATA[
|
539 |
-
If enabled, all
|
540 |
<br>
|
541 |
To schedule the run you need to add this in your crontab:<br>
|
542 |
*/5 * * * * php -f /absolute/path/to/magento/shell/indexer.php -- -reindex algolia_queue_runner
|
4 |
<algoliasearch translate="label" module="algoliasearch">
|
5 |
<label>
|
6 |
<![CDATA[
|
7 |
+
Algolia Search 1.5.5
|
8 |
<style>
|
9 |
.algoliasearch-admin-menu span {
|
10 |
padding-left: 38px !important;
|
32 |
<sort_order>10</sort_order>
|
33 |
<show_in_default>1</show_in_default>
|
34 |
<show_in_website>1</show_in_website>
|
35 |
+
<show_in_store>1</show_in_store>
|
36 |
<expanded>1</expanded>
|
37 |
<comment>
|
38 |
<![CDATA[
|
146 |
<show_in_store>1</show_in_store>
|
147 |
<comment>
|
148 |
<![CDATA[
|
149 |
+
If set to Yes, the search box will display a search-as-you-type drop-down menu. It requires your theme to expose a <code>top.search</code> and <code>content</code> block.
|
150 |
]]>
|
151 |
</comment>
|
152 |
</is_popup_enabled>
|
258 |
<show_in_store>1</show_in_store>
|
259 |
<comment>
|
260 |
<![CDATA[
|
261 |
+
Configure here the pages you don't want to search in.
|
|
|
262 |
]]>
|
263 |
</comment>
|
264 |
</excluded_pages>
|
265 |
+
<render_template_directives translate="label comment">
|
266 |
+
<label>Render template directives</label>
|
267 |
+
<frontend_type>select</frontend_type>
|
268 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
269 |
+
<sort_order>30</sort_order>
|
270 |
+
<show_in_default>1</show_in_default>
|
271 |
+
<show_in_website>1</show_in_website>
|
272 |
+
<show_in_store>1</show_in_store>
|
273 |
+
<comment>
|
274 |
+
<![CDATA[
|
275 |
+
For CMS pages, template directives (e.g. <code>{{block type="core/template" ...}}</code>) will be processed by default. Set this to "No" if you don't want to have template content indexed.<br><br>
|
276 |
+
<span style="font-size: 20px; color: #D83900">⚠</span> Do not forget to trigger the Algolia Search indexers whenever you modify those settings.
|
277 |
+
]]>
|
278 |
+
</comment>
|
279 |
+
</render_template_directives>
|
280 |
</fields>
|
281 |
</autocomplete>
|
282 |
<instant>
|
442 |
]]>
|
443 |
</comment>
|
444 |
</show_suggestions_on_no_result_page>
|
445 |
+
<index_out_of_stock_options translate="label comment">
|
446 |
+
<label>Index out of stock options for configurable products</label>
|
447 |
+
<frontend_type>select</frontend_type>
|
448 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
449 |
+
<sort_order>60</sort_order>
|
450 |
+
<show_in_default>1</show_in_default>
|
451 |
+
<show_in_website>1</show_in_website>
|
452 |
+
<show_in_store>1</show_in_store>
|
453 |
+
<comment>
|
454 |
+
<![CDATA[
|
455 |
+
Choose here if you want to index out of stock options for configurable products
|
456 |
+
]]>
|
457 |
+
</comment>
|
458 |
+
</index_out_of_stock_options>
|
459 |
</fields>
|
460 |
</products>
|
461 |
<categories translate="label">
|
502 |
]]>
|
503 |
</comment>
|
504 |
</custom_ranking_category_attributes>
|
505 |
+
<show_cats_not_included_in_navigation translate="label comment">
|
506 |
+
<label>Show categories that are not included in the navigation menu</label>
|
507 |
+
<frontend_type>select</frontend_type>
|
508 |
+
<source_model>adminhtml/system_config_source_yesno</source_model>
|
509 |
+
<sort_order>40</sort_order>
|
510 |
+
<show_in_default>1</show_in_default>
|
511 |
+
<show_in_website>1</show_in_website>
|
512 |
+
<show_in_store>1</show_in_store>
|
513 |
+
<comment>
|
514 |
+
<![CDATA[
|
515 |
+
If set to Yes, Algolia will display all categories in the autocomplete menu ignoring the setting: "Include in navigation menu". Default value: false
|
516 |
+
]]>
|
517 |
+
</comment>
|
518 |
+
</show_cats_not_included_in_navigation>
|
519 |
</fields>
|
520 |
</categories>
|
521 |
<image translate="label">
|
545 |
<show_in_website>1</show_in_website>
|
546 |
<show_in_store>1</show_in_store>
|
547 |
<comment><![CDATA[You can specify the size of the images used at the Search Results Page.<br />
|
548 |
+
If your images are already present in some size, e.g. 265x265, Algolia's index job may not have to resize those, potentially saving time and resources.]]></comment>
|
549 |
</height>
|
550 |
<type translate="label comment">
|
551 |
<label>Type</label>
|
578 |
<show_in_store>1</show_in_store>
|
579 |
<comment>
|
580 |
<![CDATA[
|
581 |
+
If enabled, all indexing operations (add, remove & update operations) will be done asynchronously using the CRON mechanism.<br />
|
582 |
<br>
|
583 |
To schedule the run you need to add this in your crontab:<br>
|
584 |
*/5 * * * * php -f /absolute/path/to/magento/shell/indexer.php -- -reindex algolia_queue_runner
|
app/design/frontend/base/default/template/algoliasearch/beforetopsearch.phtml
CHANGED
@@ -4,6 +4,7 @@ $config = Mage::helper('algoliasearch/config');
|
|
4 |
$catalogSearchHelper = $this->helper('catalogsearch'); /** @var $catalogSearchHelper Mage_CatalogSearch_Helper_Data */
|
5 |
$algoliaSearchHelper = $this->helper('algoliasearch'); /** @var $algoliaSearchHelper Algolia_Algoliasearch_Helper_Data */
|
6 |
$product_helper = Mage::helper('algoliasearch/entity_producthelper');
|
|
|
7 |
|
8 |
$base_url = Mage::getBaseUrl();
|
9 |
|
@@ -12,6 +13,7 @@ $isCategoryPage = false;
|
|
12 |
|
13 |
$currency_code = Mage::app()->getStore()->getCurrentCurrencyCode();
|
14 |
$group_id = Mage::getSingleton('customer/session')->getCustomerGroupId();
|
|
|
15 |
$price_key = $config->isCustomerGroupsEnabled(Mage::app()->getStore()->getStoreId()) ? '.'.$currency_code.'.group_'.$group_id : '.'.$currency_code.'.default';
|
16 |
|
17 |
$allDepartments = "All departments";
|
@@ -106,7 +108,7 @@ if ($config->isInstantEnabled() && $isSearchPage) {
|
|
106 |
},
|
107 |
applicationId: '<?php echo $config->getApplicationID() ?>',
|
108 |
indexName: '<?php echo $product_helper->getBaseIndexName(); ?>',
|
109 |
-
apiKey: '<?php echo $config->getSearchOnlyAPIKey() ?>',
|
110 |
facets: <?php echo json_encode($config->getFacets()); ?>,
|
111 |
hitsPerPage: <?php echo (int) $config->getNumberOfProductResults() ?>,
|
112 |
sortingIndices: <?php echo json_encode(array_values($config->getSortingIndices())); ?>,
|
@@ -116,6 +118,7 @@ if ($config->isInstantEnabled() && $isSearchPage) {
|
|
116 |
priceKey: '<?php echo $price_key; ?>',
|
117 |
currencySymbol: '<?php echo Mage::app()->getLocale()->currency(Mage::app()->getStore()->getCurrentCurrencyCode())->getSymbol(); ?>',
|
118 |
currency_code: '<?php echo $currency_code; ?>',
|
|
|
119 |
autofocus: true,
|
120 |
request: {
|
121 |
query:<?php echo json_encode(array("value" => html_entity_decode($query))); ?>.value,
|
@@ -123,6 +126,7 @@ if ($config->isInstantEnabled() && $isSearchPage) {
|
|
123 |
refinement_value: '<?php echo $refinement_value; ?>',
|
124 |
path: '<?php echo $path; ?>'
|
125 |
},
|
|
|
126 |
showSuggestionsOnNoResultsPage: <?php echo $config->showSuggestionsOnNoResultsPage() ? "true" : "false"; ?>,
|
127 |
baseUrl: '<?php echo $base_url ?>',
|
128 |
popularQueries: <?php echo json_encode($config->getPopularQueries()); ?>
|
@@ -211,6 +215,7 @@ if ($config->isInstantEnabled() && $isSearchPage) {
|
|
211 |
return algoliaBundle.instantsearch.widgets.refinementList({
|
212 |
container: facet.wrapper.appendChild(document.createElement('div')),
|
213 |
attributeName: facet.attribute,
|
|
|
214 |
operator: 'and',
|
215 |
templates: templates,
|
216 |
cssClasses: {
|
@@ -225,6 +230,7 @@ if ($config->isInstantEnabled() && $isSearchPage) {
|
|
225 |
return algoliaBundle.instantsearch.widgets.refinementList({
|
226 |
container: facet.wrapper.appendChild(document.createElement('div')),
|
227 |
attributeName: facet.attribute,
|
|
|
228 |
operator: 'or',
|
229 |
templates: templates,
|
230 |
cssClasses: {
|
@@ -301,6 +307,10 @@ if ($config->isInstantEnabled() && $isSearchPage) {
|
|
301 |
}
|
302 |
else if (section.name === "categories" || section.name === "pages")
|
303 |
{
|
|
|
|
|
|
|
|
|
304 |
source = {
|
305 |
source: $.fn.autocomplete.sources.hits(algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), options),
|
306 |
name: i,
|
4 |
$catalogSearchHelper = $this->helper('catalogsearch'); /** @var $catalogSearchHelper Mage_CatalogSearch_Helper_Data */
|
5 |
$algoliaSearchHelper = $this->helper('algoliasearch'); /** @var $algoliaSearchHelper Algolia_Algoliasearch_Helper_Data */
|
6 |
$product_helper = Mage::helper('algoliasearch/entity_producthelper');
|
7 |
+
$algolia_helper = Mage::helper('algoliasearch/algoliahelper');
|
8 |
|
9 |
$base_url = Mage::getBaseUrl();
|
10 |
|
13 |
|
14 |
$currency_code = Mage::app()->getStore()->getCurrentCurrencyCode();
|
15 |
$group_id = Mage::getSingleton('customer/session')->getCustomerGroupId();
|
16 |
+
|
17 |
$price_key = $config->isCustomerGroupsEnabled(Mage::app()->getStore()->getStoreId()) ? '.'.$currency_code.'.group_'.$group_id : '.'.$currency_code.'.default';
|
18 |
|
19 |
$allDepartments = "All departments";
|
108 |
},
|
109 |
applicationId: '<?php echo $config->getApplicationID() ?>',
|
110 |
indexName: '<?php echo $product_helper->getBaseIndexName(); ?>',
|
111 |
+
apiKey: '<?php echo $algolia_helper->generateSearchSecuredApiKey($config->getSearchOnlyAPIKey(), $config->getAttributesToRetrieve($group_id)) ?>',
|
112 |
facets: <?php echo json_encode($config->getFacets()); ?>,
|
113 |
hitsPerPage: <?php echo (int) $config->getNumberOfProductResults() ?>,
|
114 |
sortingIndices: <?php echo json_encode(array_values($config->getSortingIndices())); ?>,
|
118 |
priceKey: '<?php echo $price_key; ?>',
|
119 |
currencySymbol: '<?php echo Mage::app()->getLocale()->currency(Mage::app()->getStore()->getCurrentCurrencyCode())->getSymbol(); ?>',
|
120 |
currency_code: '<?php echo $currency_code; ?>',
|
121 |
+
maxValuesPerFacet: <?php echo (int) $config->getMaxValuesPerFacet(); ?>,
|
122 |
autofocus: true,
|
123 |
request: {
|
124 |
query:<?php echo json_encode(array("value" => html_entity_decode($query))); ?>.value,
|
126 |
refinement_value: '<?php echo $refinement_value; ?>',
|
127 |
path: '<?php echo $path; ?>'
|
128 |
},
|
129 |
+
show_cats_not_included_in_navigation: <?php echo $config->showCatsNotIncludedInNavigation() ? "true" : "false"; ?>,
|
130 |
showSuggestionsOnNoResultsPage: <?php echo $config->showSuggestionsOnNoResultsPage() ? "true" : "false"; ?>,
|
131 |
baseUrl: '<?php echo $base_url ?>',
|
132 |
popularQueries: <?php echo json_encode($config->getPopularQueries()); ?>
|
215 |
return algoliaBundle.instantsearch.widgets.refinementList({
|
216 |
container: facet.wrapper.appendChild(document.createElement('div')),
|
217 |
attributeName: facet.attribute,
|
218 |
+
limit: algoliaConfig.maxValuesPerFacet,
|
219 |
operator: 'and',
|
220 |
templates: templates,
|
221 |
cssClasses: {
|
230 |
return algoliaBundle.instantsearch.widgets.refinementList({
|
231 |
container: facet.wrapper.appendChild(document.createElement('div')),
|
232 |
attributeName: facet.attribute,
|
233 |
+
limit: algoliaConfig.maxValuesPerFacet,
|
234 |
operator: 'or',
|
235 |
templates: templates,
|
236 |
cssClasses: {
|
307 |
}
|
308 |
else if (section.name === "categories" || section.name === "pages")
|
309 |
{
|
310 |
+
if (section.name === "categories" && algoliaConfig.show_cats_not_included_in_navigation == false) {
|
311 |
+
options.numericFilters = 'include_in_menu=1';
|
312 |
+
}
|
313 |
+
|
314 |
source = {
|
315 |
source: $.fn.autocomplete.sources.hits(algolia_client.initIndex(algoliaConfig.indexName + "_" + section.name), options),
|
316 |
name: i,
|
app/design/frontend/base/default/template/algoliasearch/topsearch.phtml
CHANGED
@@ -9,7 +9,7 @@
|
|
9 |
<?php
|
10 |
$config = Mage::helper('algoliasearch/config');
|
11 |
$catalogSearchHelper = $this->helper('catalogsearch');
|
12 |
-
$group_id = Mage::getSingleton('customer/session')->
|
13 |
$currency_code = Mage::app()->getStore()->getCurrentCurrencyCode();
|
14 |
$price_key = $config->isCustomerGroupsEnabled(Mage::app()->getStore()->getStoreId()) ? '.'.$currency_code.'.group_'.$group_id : '.'.$currency_code.'.default';
|
15 |
|
@@ -635,6 +635,7 @@ $placeholder = $this->__('Search for products, categories, ...');
|
|
635 |
attributes: hierarchical_levels,
|
636 |
separator: ' /// ',
|
637 |
alwaysGetRootLevel: true,
|
|
|
638 |
templates: templates,
|
639 |
sortBy: ['name:asc'],
|
640 |
cssClasses: {
|
9 |
<?php
|
10 |
$config = Mage::helper('algoliasearch/config');
|
11 |
$catalogSearchHelper = $this->helper('catalogsearch');
|
12 |
+
$group_id = Mage::getSingleton('customer/session')->getCustomerGroupId();
|
13 |
$currency_code = Mage::app()->getStore()->getCurrentCurrencyCode();
|
14 |
$price_key = $config->isCustomerGroupsEnabled(Mage::app()->getStore()->getStoreId()) ? '.'.$currency_code.'.group_'.$group_id : '.'.$currency_code.'.default';
|
15 |
|
635 |
attributes: hierarchical_levels,
|
636 |
separator: ' /// ',
|
637 |
alwaysGetRootLevel: true,
|
638 |
+
limit: algoliaConfig.maxValuesPerFacet,
|
639 |
templates: templates,
|
640 |
sortBy: ['name:asc'],
|
641 |
cssClasses: {
|
app/etc/modules/Algolia_Algoliasearch.xml
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
<Algolia_Algoliasearch>
|
5 |
<active>true</active>
|
6 |
<codePool>community</codePool>
|
7 |
-
<version>1.5.
|
8 |
</Algolia_Algoliasearch>
|
9 |
</modules>
|
10 |
</config>
|
4 |
<Algolia_Algoliasearch>
|
5 |
<active>true</active>
|
6 |
<codePool>community</codePool>
|
7 |
+
<version>1.5.5</version>
|
8 |
</Algolia_Algoliasearch>
|
9 |
</modules>
|
10 |
</config>
|
lib/AlgoliaSearch/AlgoliaException.php
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
<?php
|
|
|
2 |
/*
|
3 |
* Copyright (c) 2013 Algolia
|
4 |
* http://www.algolia.com/
|
@@ -23,8 +24,9 @@
|
|
23 |
*
|
24 |
*
|
25 |
*/
|
|
|
26 |
namespace AlgoliaSearch;
|
27 |
|
28 |
class AlgoliaException extends \Exception
|
29 |
{
|
30 |
-
}
|
1 |
<?php
|
2 |
+
|
3 |
/*
|
4 |
* Copyright (c) 2013 Algolia
|
5 |
* http://www.algolia.com/
|
24 |
*
|
25 |
*
|
26 |
*/
|
27 |
+
|
28 |
namespace AlgoliaSearch;
|
29 |
|
30 |
class AlgoliaException extends \Exception
|
31 |
{
|
32 |
+
}
|
lib/AlgoliaSearch/Client.php
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
<?php
|
|
|
2 |
/*
|
3 |
* Copyright (c) 2013 Algolia
|
4 |
* http://www.algolia.com/
|
@@ -23,109 +24,215 @@
|
|
23 |
*
|
24 |
*
|
25 |
*/
|
|
|
26 |
namespace AlgoliaSearch;
|
27 |
|
28 |
/**
|
29 |
* Entry point in the PHP API.
|
30 |
* You should instantiate a Client object with your ApplicationID, ApiKey and Hosts
|
31 |
-
* to start using Algolia Search API
|
32 |
*/
|
33 |
-
class Client
|
|
|
|
|
|
|
|
|
34 |
|
|
|
|
|
|
|
35 |
protected $context;
|
36 |
-
protected $cainfoPath;
|
37 |
|
38 |
-
|
39 |
-
*
|
40 |
-
* @param applicationID the application ID you have in your admin interface
|
41 |
-
* @param apiKey a valid API key for the service
|
42 |
-
* @param hostsArray the list of hosts that you have received for the service
|
43 |
*/
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
throw new \Exception('AlgoliaSearch requires the CURL PHP extension.');
|
52 |
}
|
53 |
-
if(!function_exists('json_decode')){
|
54 |
throw new \Exception('AlgoliaSearch requires the JSON PHP extension.');
|
55 |
}
|
56 |
-
|
|
|
57 |
foreach ($options as $option => $value) {
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
}
|
63 |
}
|
|
|
|
|
64 |
}
|
65 |
|
66 |
-
|
67 |
-
* Release curl handle
|
68 |
*/
|
69 |
-
function __destruct()
|
|
|
70 |
}
|
71 |
|
72 |
-
|
73 |
-
* Change the default connect timeout of 2s to a custom value
|
74 |
-
*
|
75 |
-
*
|
76 |
-
* @param
|
|
|
|
|
|
|
|
|
77 |
*/
|
78 |
-
public function setConnectTimeout($connectTimeout, $timeout = 30, $searchTimeout = 5)
|
|
|
79 |
$version = curl_version();
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
82 |
}
|
83 |
$this->context->connectTimeout = $connectTimeout;
|
84 |
$this->context->readTimeout = $timeout;
|
85 |
$this->context->searchTimeout = $searchTimeout;
|
86 |
}
|
87 |
|
88 |
-
|
89 |
* Allow to use IP rate limit when you have a proxy between end-user and Algolia.
|
90 |
-
* This option will set the X-Forwarded-For HTTP header with the client IP
|
91 |
-
*
|
92 |
-
*
|
93 |
-
* @param
|
|
|
|
|
94 |
*/
|
95 |
-
public function enableRateLimitForward($adminAPIKey, $endUserIP, $rateLimitAPIKey)
|
|
|
96 |
$this->context->setRateLimit($adminAPIKey, $endUserIP, $rateLimitAPIKey);
|
97 |
}
|
98 |
|
99 |
-
|
100 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
*/
|
102 |
-
public function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
$this->context->disableRateLimit();
|
104 |
}
|
105 |
|
106 |
-
|
107 |
-
* Call isAlive
|
108 |
*/
|
109 |
-
public function isAlive()
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
}
|
112 |
|
113 |
-
|
114 |
-
* Allow to set custom headers
|
|
|
|
|
|
|
115 |
*/
|
116 |
-
public function setExtraHeader($key, $value)
|
|
|
117 |
$this->context->setExtraHeader($key, $value);
|
118 |
}
|
119 |
|
120 |
-
|
121 |
-
* This method allows to query multiple indexes with one API call
|
|
|
|
|
|
|
|
|
122 |
*
|
|
|
|
|
|
|
|
|
123 |
*/
|
124 |
-
public function multipleQueries($queries, $indexNameKey =
|
|
|
125 |
if ($queries == null) {
|
126 |
throw new \Exception('No query provided');
|
127 |
}
|
128 |
-
$requests =
|
129 |
foreach ($queries as $query) {
|
130 |
if (array_key_exists($indexNameKey, $query)) {
|
131 |
$indexes = $query[$indexNameKey];
|
@@ -133,245 +240,493 @@ class Client {
|
|
133 |
} else {
|
134 |
throw new \Exception('indexName is mandatory');
|
135 |
}
|
136 |
-
|
137 |
-
|
138 |
-
$query[$key] = json_encode($value);
|
139 |
-
}
|
140 |
-
}
|
141 |
-
$req = array("indexName" => $indexes, "params" => http_build_query($query));
|
142 |
array_push($requests, $req);
|
143 |
}
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
}
|
146 |
|
147 |
-
|
148 |
* List all existing indexes
|
149 |
* return an object in the form:
|
150 |
-
* array(
|
151 |
-
*
|
152 |
-
*
|
153 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
*/
|
155 |
-
public function listIndexes()
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
}
|
158 |
|
159 |
-
|
160 |
-
* Delete an index
|
161 |
*
|
162 |
-
* @param indexName the name of index to delete
|
163 |
-
*
|
|
|
164 |
*/
|
165 |
-
public function deleteIndex($indexName)
|
166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
}
|
168 |
|
169 |
/**
|
170 |
* Move an existing index.
|
171 |
-
*
|
172 |
-
* @param
|
|
|
|
|
|
|
|
|
173 |
*/
|
174 |
-
public function moveIndex($srcIndexName, $dstIndexName)
|
175 |
-
|
176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
}
|
178 |
|
179 |
/**
|
180 |
* Copy an existing index.
|
181 |
-
*
|
182 |
-
* @param
|
|
|
|
|
|
|
|
|
183 |
*/
|
184 |
-
public function copyIndex($srcIndexName, $dstIndexName)
|
185 |
-
|
186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
}
|
188 |
|
189 |
/**
|
190 |
* Return last logs entries.
|
191 |
-
*
|
192 |
-
* @param
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
*/
|
194 |
-
public function getLogs($offset = 0, $length = 10, $type =
|
195 |
-
|
|
|
196 |
if ($type) {
|
197 |
-
$type =
|
198 |
} else {
|
199 |
-
$type =
|
200 |
}
|
201 |
}
|
202 |
-
return $this->request($this->context, "GET", "/1/logs?offset=" . $offset . "&length=" . $length . "&type=" . $type, null, null, $this->context->writeHostsArray, $this->context->connectTimeout, $this->context->readTimeout);
|
203 |
-
}
|
204 |
|
205 |
-
|
206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
|
208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
*/
|
210 |
-
public function initIndex($indexName)
|
|
|
211 |
if (empty($indexName)) {
|
212 |
throw new AlgoliaException('Invalid index name: empty string');
|
213 |
}
|
|
|
214 |
return new Index($this->context, $this, $indexName);
|
215 |
}
|
216 |
|
217 |
-
|
218 |
-
* List all existing user keys with their associated ACLs
|
|
|
|
|
219 |
*
|
|
|
220 |
*/
|
221 |
-
public function listUserKeys()
|
222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
}
|
224 |
|
225 |
-
|
226 |
-
* Get ACL of a user key
|
227 |
*
|
|
|
|
|
|
|
228 |
*/
|
229 |
-
public function getUserKeyACL($key)
|
230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
231 |
}
|
232 |
|
233 |
-
|
234 |
-
* Delete an existing user key
|
|
|
|
|
235 |
*
|
|
|
236 |
*/
|
237 |
-
public function deleteUserKey($key)
|
238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
239 |
}
|
240 |
|
241 |
-
|
242 |
-
* Create a new user key
|
243 |
-
*
|
244 |
-
* @param
|
245 |
-
*
|
246 |
-
*
|
247 |
-
*
|
248 |
-
*
|
249 |
-
*
|
250 |
-
*
|
251 |
-
*
|
252 |
-
*
|
253 |
-
*
|
254 |
-
*
|
255 |
-
*
|
256 |
-
*
|
257 |
-
*
|
258 |
-
*
|
259 |
-
*
|
260 |
-
*
|
261 |
-
*
|
262 |
-
*
|
263 |
-
* @param validity
|
264 |
-
*
|
265 |
-
* @param
|
266 |
-
*
|
267 |
-
|
268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
if ($obj !== array_values($obj)) { // is dict of value
|
270 |
$params = $obj;
|
271 |
-
$params[
|
272 |
-
$params[
|
273 |
-
$params[
|
274 |
} else {
|
275 |
-
$params =
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
}
|
282 |
|
283 |
if ($indexes != null) {
|
284 |
$params['indexes'] = $indexes;
|
285 |
}
|
286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
287 |
}
|
288 |
|
289 |
-
|
290 |
-
* Update a user key
|
291 |
-
*
|
292 |
-
* @param
|
293 |
-
*
|
294 |
-
*
|
295 |
-
*
|
296 |
-
*
|
297 |
-
*
|
298 |
-
*
|
299 |
-
*
|
300 |
-
*
|
301 |
-
*
|
302 |
-
*
|
303 |
-
*
|
304 |
-
*
|
305 |
-
*
|
306 |
-
*
|
307 |
-
*
|
308 |
-
*
|
309 |
-
*
|
310 |
-
*
|
311 |
-
*
|
312 |
-
* @param
|
313 |
-
*
|
314 |
-
* @param
|
315 |
-
|
316 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
317 |
if ($obj !== array_values($obj)) { // is dict of value
|
318 |
$params = $obj;
|
319 |
-
$params[
|
320 |
-
$params[
|
321 |
-
$params[
|
322 |
} else {
|
323 |
-
$params =
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
}
|
330 |
if ($indexes != null) {
|
331 |
$params['indexes'] = $indexes;
|
332 |
}
|
333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
334 |
}
|
335 |
|
336 |
/**
|
337 |
-
* Send a batch request targeting multiple indices
|
338 |
-
*
|
|
|
|
|
|
|
339 |
*/
|
340 |
-
public function batch($requests)
|
341 |
-
|
342 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
}
|
344 |
|
345 |
-
|
346 |
-
* Generate a secured and public API Key from a list of
|
347 |
-
* optional user token identifying the current user
|
348 |
*
|
349 |
-
* @param privateApiKey your private API Key
|
350 |
-
* @param
|
351 |
-
* @param userToken
|
352 |
*
|
|
|
353 |
*/
|
354 |
-
public function generateSecuredApiKey($privateApiKey, $
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
362 |
}
|
363 |
-
array_push($tmp, '(' . join(',', $tmp2) . ')');
|
364 |
-
} else {
|
365 |
-
array_push($tmp, $tag);
|
366 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
}
|
368 |
-
$tagFilters = join(',', $tmp);
|
369 |
}
|
370 |
-
|
|
|
371 |
}
|
372 |
|
373 |
-
|
374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
$cnt = 0;
|
376 |
foreach ($hostsArray as &$host) {
|
377 |
$cnt += 1;
|
@@ -381,25 +736,51 @@ class Client {
|
|
381 |
}
|
382 |
try {
|
383 |
$res = $this->doRequest($context, $method, $host, $path, $params, $data, $connectTimeout, $readTimeout);
|
384 |
-
if ($res !== null)
|
385 |
return $res;
|
|
|
386 |
} catch (AlgoliaException $e) {
|
387 |
throw $e;
|
388 |
} catch (\Exception $e) {
|
389 |
$exceptions[$host] = $e->getMessage();
|
390 |
}
|
391 |
}
|
392 |
-
throw new AlgoliaException('Hosts unreachable: '
|
393 |
}
|
394 |
|
395 |
-
|
396 |
-
|
397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
398 |
} else {
|
399 |
-
$url =
|
400 |
}
|
|
|
401 |
if ($params != null && count($params) > 0) {
|
402 |
-
$params2 =
|
403 |
foreach ($params as $key => $val) {
|
404 |
if (is_array($val)) {
|
405 |
$params2[$key] = json_encode($val);
|
@@ -407,35 +788,47 @@ class Client {
|
|
407 |
$params2[$key] = $val;
|
408 |
}
|
409 |
}
|
410 |
-
$url .=
|
411 |
-
|
412 |
}
|
|
|
413 |
// initialize curl library
|
414 |
$curlHandle = curl_init();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
//curl_setopt($curlHandle, CURLOPT_VERBOSE, true);
|
416 |
if ($context->adminAPIKey == null) {
|
417 |
-
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge(
|
418 |
-
'X-Algolia-Application-Id: '
|
419 |
-
'X-Algolia-API-Key: '
|
420 |
-
'Content-type: application/json'
|
421 |
-
|
422 |
} else {
|
423 |
-
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge(
|
424 |
-
'X-Algolia-Application-Id: '
|
425 |
-
'X-Algolia-API-Key: '
|
426 |
-
'X-Forwarded-For: '
|
427 |
-
'X-
|
428 |
-
'
|
429 |
-
|
430 |
-
|
431 |
-
|
|
|
|
|
432 |
//Return the output instead of printing it
|
433 |
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
|
434 |
curl_setopt($curlHandle, CURLOPT_FAILONERROR, true);
|
435 |
curl_setopt($curlHandle, CURLOPT_ENCODING, '');
|
436 |
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, true);
|
437 |
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, 2);
|
438 |
-
curl_setopt($curlHandle, CURLOPT_CAINFO, $this->
|
439 |
|
440 |
curl_setopt($curlHandle, CURLOPT_URL, $url);
|
441 |
$version = curl_version();
|
@@ -447,26 +840,30 @@ class Client {
|
|
447 |
curl_setopt($curlHandle, CURLOPT_TIMEOUT, $readTimeout);
|
448 |
}
|
449 |
|
450 |
-
|
|
|
|
|
451 |
curl_setopt($curlHandle, CURLOPT_FAILONERROR, false);
|
452 |
|
453 |
if ($method === 'GET') {
|
454 |
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'GET');
|
455 |
curl_setopt($curlHandle, CURLOPT_HTTPGET, true);
|
456 |
curl_setopt($curlHandle, CURLOPT_POST, false);
|
457 |
-
} else
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
|
|
|
|
470 |
}
|
471 |
$mhandle = $context->getMHandle($curlHandle);
|
472 |
|
@@ -475,6 +872,7 @@ class Client {
|
|
475 |
do {
|
476 |
$mrc = curl_multi_exec($mhandle, $running);
|
477 |
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
|
|
|
478 |
while ($running && $mrc == CURLM_OK) {
|
479 |
if (curl_multi_select($mhandle, 0.1) == -1) {
|
480 |
usleep(100);
|
@@ -483,17 +881,21 @@ class Client {
|
|
483 |
$mrc = curl_multi_exec($mhandle, $running);
|
484 |
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
|
485 |
}
|
486 |
-
|
|
|
487 |
$response = curl_multi_getcontent($curlHandle);
|
488 |
$error = curl_error($curlHandle);
|
|
|
489 |
if (!empty($error)) {
|
490 |
throw new \Exception($error);
|
491 |
}
|
|
|
492 |
if ($http_status === 0 || $http_status === 503) {
|
493 |
// Could not reach host or service unavailable, try with another one if we have it
|
494 |
$context->releaseMHandle($curlHandle);
|
495 |
curl_close($curlHandle);
|
496 |
-
|
|
|
497 |
}
|
498 |
|
499 |
$answer = json_decode($response, true);
|
@@ -501,10 +903,9 @@ class Client {
|
|
501 |
curl_close($curlHandle);
|
502 |
|
503 |
if (intval($http_status / 100) == 4) {
|
504 |
-
throw new AlgoliaException(isset($answer['message']) ? $answer['message'] : $http_status +
|
505 |
-
}
|
506 |
-
|
507 |
-
throw new \Exception($http_status . ": " . $response);
|
508 |
}
|
509 |
|
510 |
switch (json_last_error()) {
|
@@ -520,7 +921,8 @@ class Client {
|
|
520 |
case JSON_ERROR_STATE_MISMATCH:
|
521 |
$errorMsg = 'JSON parsing error: underflow or the modes mismatch';
|
522 |
break;
|
523 |
-
|
|
|
524 |
$errorMsg = 'JSON parsing error: malformed UTF-8 characters, possibly incorrectly encoded';
|
525 |
break;
|
526 |
case JSON_ERROR_NONE:
|
@@ -528,10 +930,112 @@ class Client {
|
|
528 |
$errorMsg = null;
|
529 |
break;
|
530 |
}
|
531 |
-
if ($errorMsg !== null)
|
532 |
throw new AlgoliaException($errorMsg);
|
|
|
533 |
|
534 |
return $answer;
|
535 |
}
|
536 |
-
}
|
537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<?php
|
2 |
+
|
3 |
/*
|
4 |
* Copyright (c) 2013 Algolia
|
5 |
* http://www.algolia.com/
|
24 |
*
|
25 |
*
|
26 |
*/
|
27 |
+
|
28 |
namespace AlgoliaSearch;
|
29 |
|
30 |
/**
|
31 |
* Entry point in the PHP API.
|
32 |
* You should instantiate a Client object with your ApplicationID, ApiKey and Hosts
|
33 |
+
* to start using Algolia Search API.
|
34 |
*/
|
35 |
+
class Client
|
36 |
+
{
|
37 |
+
const CAINFO = 'cainfo';
|
38 |
+
const CURLOPT = 'curloptions';
|
39 |
+
const PLACES_ENABLED = 'placesEnabled';
|
40 |
|
41 |
+
/**
|
42 |
+
* @var ClientContext
|
43 |
+
*/
|
44 |
protected $context;
|
|
|
45 |
|
46 |
+
/**
|
47 |
+
* @var string
|
|
|
|
|
|
|
48 |
*/
|
49 |
+
protected $caInfoPath;
|
50 |
+
|
51 |
+
/**
|
52 |
+
* @var array
|
53 |
+
*/
|
54 |
+
protected $curlConstants;
|
55 |
+
|
56 |
+
/**
|
57 |
+
* @var array
|
58 |
+
*/
|
59 |
+
protected $curlOptions = [];
|
60 |
+
|
61 |
+
/**
|
62 |
+
* @var bool
|
63 |
+
*/
|
64 |
+
protected $placesEnabled = false;
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Algolia Search initialization.
|
68 |
+
*
|
69 |
+
* @param string $applicationID the application ID you have in your admin interface
|
70 |
+
* @param string $apiKey a valid API key for the service
|
71 |
+
* @param array|null $hostsArray the list of hosts that you have received for the service
|
72 |
+
* @param array $options
|
73 |
+
*
|
74 |
+
* @throws \Exception
|
75 |
+
*/
|
76 |
+
public function __construct($applicationID, $apiKey, $hostsArray = null, $options = [])
|
77 |
+
{
|
78 |
+
if (!function_exists('curl_init')) {
|
79 |
throw new \Exception('AlgoliaSearch requires the CURL PHP extension.');
|
80 |
}
|
81 |
+
if (!function_exists('json_decode')) {
|
82 |
throw new \Exception('AlgoliaSearch requires the JSON PHP extension.');
|
83 |
}
|
84 |
+
|
85 |
+
$this->caInfoPath = __DIR__.'/resources/ca-bundle.crt';
|
86 |
foreach ($options as $option => $value) {
|
87 |
+
switch ($option) {
|
88 |
+
case self::CAINFO:
|
89 |
+
$this->caInfoPath = $value;
|
90 |
+
break;
|
91 |
+
case self::CURLOPT:
|
92 |
+
$this->curlOptions = $this->checkCurlOptions($value);
|
93 |
+
break;
|
94 |
+
case self::PLACES_ENABLED:
|
95 |
+
$this->placesEnabled = (bool) $value;
|
96 |
+
break;
|
97 |
+
default:
|
98 |
+
throw new \Exception('Unknown option: '.$option);
|
99 |
}
|
100 |
}
|
101 |
+
|
102 |
+
$this->context = new ClientContext($applicationID, $apiKey, $hostsArray, $this->placesEnabled);
|
103 |
}
|
104 |
|
105 |
+
/**
|
106 |
+
* Release curl handle.
|
107 |
*/
|
108 |
+
public function __destruct()
|
109 |
+
{
|
110 |
}
|
111 |
|
112 |
+
/**
|
113 |
+
* Change the default connect timeout of 2s to a custom value
|
114 |
+
* (only useful if your server has a very slow connectivity to Algolia backend).
|
115 |
+
*
|
116 |
+
* @param int $connectTimeout the connection timeout
|
117 |
+
* @param int $timeout the read timeout for the query
|
118 |
+
* @param int $searchTimeout the read timeout used for search queries only
|
119 |
+
*
|
120 |
+
* @throws AlgoliaException
|
121 |
*/
|
122 |
+
public function setConnectTimeout($connectTimeout, $timeout = 30, $searchTimeout = 5)
|
123 |
+
{
|
124 |
$version = curl_version();
|
125 |
+
$isPhpOld = version_compare(phpversion(), '5.2.3', '<');
|
126 |
+
$isCurlOld = version_compare($version['version'], '7.16.2', '<');
|
127 |
+
|
128 |
+
if (($isPhpOld || $isCurlOld) && $this->context->connectTimeout < 1) {
|
129 |
+
throw new AlgoliaException(
|
130 |
+
"The timeout can't be a float with a PHP version less than 5.2.3 or a curl version less than 7.16.2"
|
131 |
+
);
|
132 |
}
|
133 |
$this->context->connectTimeout = $connectTimeout;
|
134 |
$this->context->readTimeout = $timeout;
|
135 |
$this->context->searchTimeout = $searchTimeout;
|
136 |
}
|
137 |
|
138 |
+
/**
|
139 |
* Allow to use IP rate limit when you have a proxy between end-user and Algolia.
|
140 |
+
* This option will set the X-Forwarded-For HTTP header with the client IP
|
141 |
+
* and the X-Forwarded-API-Key with the API Key having rate limits.
|
142 |
+
*
|
143 |
+
* @param string $adminAPIKey the admin API Key you can find in your dashboard
|
144 |
+
* @param string $endUserIP the end user IP (you can use both IPV4 or IPV6 syntax)
|
145 |
+
* @param string $rateLimitAPIKey the API key on which you have a rate limit
|
146 |
*/
|
147 |
+
public function enableRateLimitForward($adminAPIKey, $endUserIP, $rateLimitAPIKey)
|
148 |
+
{
|
149 |
$this->context->setRateLimit($adminAPIKey, $endUserIP, $rateLimitAPIKey);
|
150 |
}
|
151 |
|
152 |
+
/**
|
153 |
+
* The aggregation of the queries to retrieve the latest query
|
154 |
+
* uses the IP or the user token to work efficiently.
|
155 |
+
* If the queries are made from your backend server,
|
156 |
+
* the IP will be the same for all of the queries.
|
157 |
+
* We're supporting the following HTTP header to forward the IP of your end-user
|
158 |
+
* to the engine, you just need to set it for each query.
|
159 |
+
*
|
160 |
+
* @see https://www.algolia.com/doc/faq/analytics/will-the-analytics-still-work-if-i-perform-the-search-through-my-backend
|
161 |
+
*
|
162 |
+
* @param string $ip
|
163 |
*/
|
164 |
+
public function setForwardedFor($ip)
|
165 |
+
{
|
166 |
+
$this->context->setForwardedFor($ip);
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* It's possible to use the following token to track users that have the same IP
|
171 |
+
* or to track users that use different devices.
|
172 |
+
*
|
173 |
+
* @see https://www.algolia.com/doc/faq/analytics/will-the-analytics-still-work-if-i-perform-the-search-through-my-backend
|
174 |
+
*
|
175 |
+
* @param string $token
|
176 |
+
*/
|
177 |
+
public function setAlgoliaUserToken($token)
|
178 |
+
{
|
179 |
+
$this->context->setAlgoliaUserToken($token);
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Disable IP rate limit enabled with enableRateLimitForward() function.
|
184 |
+
*/
|
185 |
+
public function disableRateLimitForward()
|
186 |
+
{
|
187 |
$this->context->disableRateLimit();
|
188 |
}
|
189 |
|
190 |
+
/**
|
191 |
+
* Call isAlive.
|
192 |
*/
|
193 |
+
public function isAlive()
|
194 |
+
{
|
195 |
+
$this->request(
|
196 |
+
$this->context,
|
197 |
+
'GET',
|
198 |
+
'/1/isalive',
|
199 |
+
null,
|
200 |
+
null,
|
201 |
+
$this->context->readHostsArray,
|
202 |
+
$this->context->connectTimeout,
|
203 |
+
$this->context->readTimeout
|
204 |
+
);
|
205 |
}
|
206 |
|
207 |
+
/**
|
208 |
+
* Allow to set custom headers.
|
209 |
+
*
|
210 |
+
* @param string $key
|
211 |
+
* @param string $value
|
212 |
*/
|
213 |
+
public function setExtraHeader($key, $value)
|
214 |
+
{
|
215 |
$this->context->setExtraHeader($key, $value);
|
216 |
}
|
217 |
|
218 |
+
/**
|
219 |
+
* This method allows to query multiple indexes with one API call.
|
220 |
+
*
|
221 |
+
* @param array $queries
|
222 |
+
* @param string $indexNameKey
|
223 |
+
* @param string $strategy
|
224 |
*
|
225 |
+
* @return mixed
|
226 |
+
*
|
227 |
+
* @throws AlgoliaException
|
228 |
+
* @throws \Exception
|
229 |
*/
|
230 |
+
public function multipleQueries($queries, $indexNameKey = 'indexName', $strategy = 'none')
|
231 |
+
{
|
232 |
if ($queries == null) {
|
233 |
throw new \Exception('No query provided');
|
234 |
}
|
235 |
+
$requests = [];
|
236 |
foreach ($queries as $query) {
|
237 |
if (array_key_exists($indexNameKey, $query)) {
|
238 |
$indexes = $query[$indexNameKey];
|
240 |
} else {
|
241 |
throw new \Exception('indexName is mandatory');
|
242 |
}
|
243 |
+
$req = ['indexName' => $indexes, 'params' => $this->buildQuery($query)];
|
244 |
+
|
|
|
|
|
|
|
|
|
245 |
array_push($requests, $req);
|
246 |
}
|
247 |
+
|
248 |
+
return $this->request(
|
249 |
+
$this->context,
|
250 |
+
'POST',
|
251 |
+
'/1/indexes/*/queries?strategy='.$strategy,
|
252 |
+
[],
|
253 |
+
['requests' => $requests],
|
254 |
+
$this->context->readHostsArray,
|
255 |
+
$this->context->connectTimeout,
|
256 |
+
$this->context->searchTimeout
|
257 |
+
);
|
258 |
}
|
259 |
|
260 |
+
/**
|
261 |
* List all existing indexes
|
262 |
* return an object in the form:
|
263 |
+
* array(
|
264 |
+
* "items" => array(
|
265 |
+
* array("name" => "contacts", "createdAt" => "2013-01-18T15:33:13.556Z"),
|
266 |
+
* array("name" => "notes", "createdAt" => "2013-01-18T15:33:13.556Z")
|
267 |
+
* )
|
268 |
+
* ).
|
269 |
+
*
|
270 |
+
* @return mixed
|
271 |
+
*
|
272 |
+
* @throws AlgoliaException
|
273 |
*/
|
274 |
+
public function listIndexes()
|
275 |
+
{
|
276 |
+
return $this->request(
|
277 |
+
$this->context,
|
278 |
+
'GET',
|
279 |
+
'/1/indexes/',
|
280 |
+
null,
|
281 |
+
null,
|
282 |
+
$this->context->readHostsArray,
|
283 |
+
$this->context->connectTimeout,
|
284 |
+
$this->context->readTimeout
|
285 |
+
);
|
286 |
}
|
287 |
|
288 |
+
/**
|
289 |
+
* Delete an index.
|
290 |
*
|
291 |
+
* @param string $indexName the name of index to delete
|
292 |
+
*
|
293 |
+
* @return mixed an object containing a "deletedAt" attribute
|
294 |
*/
|
295 |
+
public function deleteIndex($indexName)
|
296 |
+
{
|
297 |
+
return $this->request(
|
298 |
+
$this->context,
|
299 |
+
'DELETE',
|
300 |
+
'/1/indexes/'.urlencode($indexName),
|
301 |
+
null,
|
302 |
+
null,
|
303 |
+
$this->context->writeHostsArray,
|
304 |
+
$this->context->connectTimeout,
|
305 |
+
$this->context->readTimeout
|
306 |
+
);
|
307 |
}
|
308 |
|
309 |
/**
|
310 |
* Move an existing index.
|
311 |
+
*
|
312 |
+
* @param string $srcIndexName the name of index to copy.
|
313 |
+
* @param string $dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overwritten
|
314 |
+
* if it already exist).
|
315 |
+
*
|
316 |
+
* @return mixed
|
317 |
*/
|
318 |
+
public function moveIndex($srcIndexName, $dstIndexName)
|
319 |
+
{
|
320 |
+
$request = ['operation' => 'move', 'destination' => $dstIndexName];
|
321 |
+
|
322 |
+
return $this->request(
|
323 |
+
$this->context,
|
324 |
+
'POST',
|
325 |
+
'/1/indexes/'.urlencode($srcIndexName).'/operation',
|
326 |
+
[],
|
327 |
+
$request,
|
328 |
+
$this->context->writeHostsArray,
|
329 |
+
$this->context->connectTimeout,
|
330 |
+
$this->context->readTimeout
|
331 |
+
);
|
332 |
}
|
333 |
|
334 |
/**
|
335 |
* Copy an existing index.
|
336 |
+
*
|
337 |
+
* @param string $srcIndexName the name of index to copy.
|
338 |
+
* @param string $dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overwritten
|
339 |
+
* if it already exist).
|
340 |
+
*
|
341 |
+
* @return mixed
|
342 |
*/
|
343 |
+
public function copyIndex($srcIndexName, $dstIndexName)
|
344 |
+
{
|
345 |
+
$request = ['operation' => 'copy', 'destination' => $dstIndexName];
|
346 |
+
|
347 |
+
return $this->request(
|
348 |
+
$this->context,
|
349 |
+
'POST',
|
350 |
+
'/1/indexes/'.urlencode($srcIndexName).'/operation',
|
351 |
+
[],
|
352 |
+
$request,
|
353 |
+
$this->context->writeHostsArray,
|
354 |
+
$this->context->connectTimeout,
|
355 |
+
$this->context->readTimeout
|
356 |
+
);
|
357 |
}
|
358 |
|
359 |
/**
|
360 |
* Return last logs entries.
|
361 |
+
*
|
362 |
+
* @param int $offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
|
363 |
+
* @param int $length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000.
|
364 |
+
* @param mixed $type
|
365 |
+
*
|
366 |
+
* @return mixed
|
367 |
+
*
|
368 |
+
* @throws AlgoliaException
|
369 |
*/
|
370 |
+
public function getLogs($offset = 0, $length = 10, $type = 'all')
|
371 |
+
{
|
372 |
+
if (gettype($type) == 'boolean') { //Old prototype onlyError
|
373 |
if ($type) {
|
374 |
+
$type = 'error';
|
375 |
} else {
|
376 |
+
$type = 'all';
|
377 |
}
|
378 |
}
|
|
|
|
|
379 |
|
380 |
+
return $this->request(
|
381 |
+
$this->context,
|
382 |
+
'GET',
|
383 |
+
'/1/logs?offset='.$offset.'&length='.$length.'&type='.$type,
|
384 |
+
null,
|
385 |
+
null,
|
386 |
+
$this->context->writeHostsArray,
|
387 |
+
$this->context->connectTimeout,
|
388 |
+
$this->context->readTimeout
|
389 |
+
);
|
390 |
+
}
|
391 |
|
392 |
+
/**
|
393 |
+
* Get the index object initialized (no server call needed for initialization).
|
394 |
+
*
|
395 |
+
* @param string $indexName the name of index
|
396 |
+
*
|
397 |
+
* @return Index
|
398 |
+
*
|
399 |
+
* @throws AlgoliaException
|
400 |
*/
|
401 |
+
public function initIndex($indexName)
|
402 |
+
{
|
403 |
if (empty($indexName)) {
|
404 |
throw new AlgoliaException('Invalid index name: empty string');
|
405 |
}
|
406 |
+
|
407 |
return new Index($this->context, $this, $indexName);
|
408 |
}
|
409 |
|
410 |
+
/**
|
411 |
+
* List all existing user keys with their associated ACLs.
|
412 |
+
*
|
413 |
+
* @return mixed
|
414 |
*
|
415 |
+
* @throws AlgoliaException
|
416 |
*/
|
417 |
+
public function listUserKeys()
|
418 |
+
{
|
419 |
+
return $this->request(
|
420 |
+
$this->context,
|
421 |
+
'GET',
|
422 |
+
'/1/keys',
|
423 |
+
null,
|
424 |
+
null,
|
425 |
+
$this->context->readHostsArray,
|
426 |
+
$this->context->connectTimeout,
|
427 |
+
$this->context->readTimeout
|
428 |
+
);
|
429 |
}
|
430 |
|
431 |
+
/**
|
432 |
+
* Get ACL of a user key.
|
433 |
*
|
434 |
+
* @param string $key
|
435 |
+
*
|
436 |
+
* @return mixed
|
437 |
*/
|
438 |
+
public function getUserKeyACL($key)
|
439 |
+
{
|
440 |
+
return $this->request(
|
441 |
+
$this->context,
|
442 |
+
'GET',
|
443 |
+
'/1/keys/'.$key,
|
444 |
+
null,
|
445 |
+
null,
|
446 |
+
$this->context->readHostsArray,
|
447 |
+
$this->context->connectTimeout,
|
448 |
+
$this->context->readTimeout
|
449 |
+
);
|
450 |
}
|
451 |
|
452 |
+
/**
|
453 |
+
* Delete an existing user key.
|
454 |
+
*
|
455 |
+
* @param string $key
|
456 |
*
|
457 |
+
* @return mixed
|
458 |
*/
|
459 |
+
public function deleteUserKey($key)
|
460 |
+
{
|
461 |
+
return $this->request(
|
462 |
+
$this->context,
|
463 |
+
'DELETE',
|
464 |
+
'/1/keys/'.$key,
|
465 |
+
null,
|
466 |
+
null,
|
467 |
+
$this->context->writeHostsArray,
|
468 |
+
$this->context->connectTimeout,
|
469 |
+
$this->context->readTimeout
|
470 |
+
);
|
471 |
}
|
472 |
|
473 |
+
/**
|
474 |
+
* Create a new user key.
|
475 |
+
*
|
476 |
+
* @param $obj can be two different parameters:
|
477 |
+
* The list of parameters for this key. Defined by an array that
|
478 |
+
* can contain the following values:
|
479 |
+
* - acl: array of string
|
480 |
+
* - indices: array of string
|
481 |
+
* - validity: int
|
482 |
+
* - referrers: array of string
|
483 |
+
* - description: string
|
484 |
+
* - maxHitsPerQuery: integer
|
485 |
+
* - queryParameters: string
|
486 |
+
* - maxQueriesPerIPPerHour: integer
|
487 |
+
* Or the list of ACL for this key. Defined by an array of NSString that
|
488 |
+
* can contains the following values:
|
489 |
+
* - search: allow to search (https and http)
|
490 |
+
* - addObject: allows to add/update an object in the index (https only)
|
491 |
+
* - deleteObject : allows to delete an existing object (https only)
|
492 |
+
* - deleteIndex : allows to delete index content (https only)
|
493 |
+
* - settings : allows to get index settings (https only)
|
494 |
+
* - editSettings : allows to change index settings (https only)
|
495 |
+
* @param int $validity the number of seconds after which the key will be automatically removed (0 means
|
496 |
+
* no time limit for this key)
|
497 |
+
* @param int $maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
|
498 |
+
* Defaults to 0 (no rate limit).
|
499 |
+
* @param int $maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
|
500 |
+
* Defaults to 0 (unlimited)
|
501 |
+
* @param array|null $indexes Specify the list of indices to target (null means all)
|
502 |
+
*
|
503 |
+
* @return mixed
|
504 |
+
*
|
505 |
+
* @throws AlgoliaException
|
506 |
+
*/
|
507 |
+
public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0, $indexes = null)
|
508 |
+
{
|
509 |
if ($obj !== array_values($obj)) { // is dict of value
|
510 |
$params = $obj;
|
511 |
+
$params['validity'] = $validity;
|
512 |
+
$params['maxQueriesPerIPPerHour'] = $maxQueriesPerIPPerHour;
|
513 |
+
$params['maxHitsPerQuery'] = $maxHitsPerQuery;
|
514 |
} else {
|
515 |
+
$params = [
|
516 |
+
'acl' => $obj,
|
517 |
+
'validity' => $validity,
|
518 |
+
'maxQueriesPerIPPerHour' => $maxQueriesPerIPPerHour,
|
519 |
+
'maxHitsPerQuery' => $maxHitsPerQuery,
|
520 |
+
];
|
521 |
}
|
522 |
|
523 |
if ($indexes != null) {
|
524 |
$params['indexes'] = $indexes;
|
525 |
}
|
526 |
+
|
527 |
+
return $this->request(
|
528 |
+
$this->context,
|
529 |
+
'POST',
|
530 |
+
'/1/keys',
|
531 |
+
[],
|
532 |
+
$params,
|
533 |
+
$this->context->writeHostsArray,
|
534 |
+
$this->context->connectTimeout,
|
535 |
+
$this->context->readTimeout
|
536 |
+
);
|
537 |
}
|
538 |
|
539 |
+
/**
|
540 |
+
* Update a user key.
|
541 |
+
*
|
542 |
+
* @param string $key
|
543 |
+
* @param mixed $obj can be two different parameters:
|
544 |
+
* The list of parameters for this key. Defined by a array that
|
545 |
+
* can contains the following values:
|
546 |
+
* - acl: array of string
|
547 |
+
* - indices: array of string
|
548 |
+
* - validity: int
|
549 |
+
* - referrers: array of string
|
550 |
+
* - description: string
|
551 |
+
* - maxHitsPerQuery: integer
|
552 |
+
* - queryParameters: string
|
553 |
+
* - maxQueriesPerIPPerHour: integer
|
554 |
+
* Or the list of ACL for this key. Defined by an array of NSString that
|
555 |
+
* can contains the following values:
|
556 |
+
* - search: allow to search (https and http)
|
557 |
+
* - addObject: allows to add/update an object in the index (https only)
|
558 |
+
* - deleteObject : allows to delete an existing object (https only)
|
559 |
+
* - deleteIndex : allows to delete index content (https only)
|
560 |
+
* - settings : allows to get index settings (https only)
|
561 |
+
* - editSettings : allows to change index settings (https only)
|
562 |
+
* @param int $validity the number of seconds after which the key will be automatically removed (0 means
|
563 |
+
* no time limit for this key)
|
564 |
+
* @param int $maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
|
565 |
+
* Defaults to 0 (no rate limit).
|
566 |
+
* @param int $maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. Defaults
|
567 |
+
* to 0 (unlimited)
|
568 |
+
* @param array|null $indexes Specify the list of indices to target (null means all)
|
569 |
+
*
|
570 |
+
* @return mixed
|
571 |
+
*
|
572 |
+
* @throws AlgoliaException
|
573 |
+
*/
|
574 |
+
public function updateUserKey(
|
575 |
+
$key,
|
576 |
+
$obj,
|
577 |
+
$validity = 0,
|
578 |
+
$maxQueriesPerIPPerHour = 0,
|
579 |
+
$maxHitsPerQuery = 0,
|
580 |
+
$indexes = null
|
581 |
+
) {
|
582 |
if ($obj !== array_values($obj)) { // is dict of value
|
583 |
$params = $obj;
|
584 |
+
$params['validity'] = $validity;
|
585 |
+
$params['maxQueriesPerIPPerHour'] = $maxQueriesPerIPPerHour;
|
586 |
+
$params['maxHitsPerQuery'] = $maxHitsPerQuery;
|
587 |
} else {
|
588 |
+
$params = [
|
589 |
+
'acl' => $obj,
|
590 |
+
'validity' => $validity,
|
591 |
+
'maxQueriesPerIPPerHour' => $maxQueriesPerIPPerHour,
|
592 |
+
'maxHitsPerQuery' => $maxHitsPerQuery,
|
593 |
+
];
|
594 |
}
|
595 |
if ($indexes != null) {
|
596 |
$params['indexes'] = $indexes;
|
597 |
}
|
598 |
+
|
599 |
+
return $this->request(
|
600 |
+
$this->context,
|
601 |
+
'PUT',
|
602 |
+
'/1/keys/'.$key,
|
603 |
+
[],
|
604 |
+
$params,
|
605 |
+
$this->context->writeHostsArray,
|
606 |
+
$this->context->connectTimeout,
|
607 |
+
$this->context->readTimeout
|
608 |
+
);
|
609 |
}
|
610 |
|
611 |
/**
|
612 |
+
* Send a batch request targeting multiple indices.
|
613 |
+
*
|
614 |
+
* @param array $requests an associative array defining the batch request body
|
615 |
+
*
|
616 |
+
* @return mixed
|
617 |
*/
|
618 |
+
public function batch($requests)
|
619 |
+
{
|
620 |
+
return $this->request(
|
621 |
+
$this->context,
|
622 |
+
'POST',
|
623 |
+
'/1/indexes/*/batch',
|
624 |
+
[],
|
625 |
+
['requests' => $requests],
|
626 |
+
$this->context->writeHostsArray,
|
627 |
+
$this->context->connectTimeout,
|
628 |
+
$this->context->readTimeout
|
629 |
+
);
|
630 |
}
|
631 |
|
632 |
+
/**
|
633 |
+
* Generate a secured and public API Key from a list of query parameters and an
|
634 |
+
* optional user token identifying the current user.
|
635 |
*
|
636 |
+
* @param string $privateApiKey your private API Key
|
637 |
+
* @param mixed $query the list of query parameters applied to the query (used as security)
|
638 |
+
* @param string|null $userToken an optional token identifying the current user
|
639 |
*
|
640 |
+
* @return string
|
641 |
*/
|
642 |
+
public static function generateSecuredApiKey($privateApiKey, $query, $userToken = null)
|
643 |
+
{
|
644 |
+
$urlEncodedQuery = '';
|
645 |
+
if (is_array($query)) {
|
646 |
+
$queryParameters = [];
|
647 |
+
if (array_keys($query) !== array_keys(array_keys($query))) {
|
648 |
+
// array of query parameters
|
649 |
+
$queryParameters = $query;
|
650 |
+
} else {
|
651 |
+
// array of tags
|
652 |
+
$tmp = [];
|
653 |
+
foreach ($query as $tag) {
|
654 |
+
if (is_array($tag)) {
|
655 |
+
array_push($tmp, '('.implode(',', $tag).')');
|
656 |
+
} else {
|
657 |
+
array_push($tmp, $tag);
|
658 |
}
|
|
|
|
|
|
|
659 |
}
|
660 |
+
$tagFilters = implode(',', $tmp);
|
661 |
+
$queryParameters['tagFilters'] = $tagFilters;
|
662 |
+
}
|
663 |
+
if ($userToken != null && strlen($userToken) > 0) {
|
664 |
+
$queryParameters['userToken'] = $userToken;
|
665 |
+
}
|
666 |
+
$urlEncodedQuery = static::buildQuery($queryParameters);
|
667 |
+
} else {
|
668 |
+
if (strpos($query, '=') === false) {
|
669 |
+
// String of tags
|
670 |
+
$queryParameters = ['tagFilters' => $query];
|
671 |
+
|
672 |
+
if ($userToken != null && strlen($userToken) > 0) {
|
673 |
+
$queryParameters['userToken'] = $userToken;
|
674 |
+
}
|
675 |
+
$urlEncodedQuery = static::buildQuery($queryParameters);
|
676 |
+
} else {
|
677 |
+
// url encoded query
|
678 |
+
$urlEncodedQuery = $query;
|
679 |
+
if ($userToken != null && strlen($userToken) > 0) {
|
680 |
+
$urlEncodedQuery = $urlEncodedQuery.'&userToken='.urlencode($userToken);
|
681 |
+
}
|
682 |
+
}
|
683 |
+
}
|
684 |
+
$content = hash_hmac('sha256', $urlEncodedQuery, $privateApiKey).$urlEncodedQuery;
|
685 |
+
|
686 |
+
return base64_encode($content);
|
687 |
+
}
|
688 |
+
|
689 |
+
/**
|
690 |
+
* @param array $args
|
691 |
+
*
|
692 |
+
* @return string
|
693 |
+
*/
|
694 |
+
public static function buildQuery($args)
|
695 |
+
{
|
696 |
+
foreach ($args as $key => $value) {
|
697 |
+
if (gettype($value) == 'array') {
|
698 |
+
$args[$key] = json_encode($value);
|
699 |
}
|
|
|
700 |
}
|
701 |
+
|
702 |
+
return http_build_query($args);
|
703 |
}
|
704 |
|
705 |
+
/**
|
706 |
+
* @param ClientContext $context
|
707 |
+
* @param string $method
|
708 |
+
* @param string $path
|
709 |
+
* @param array $params
|
710 |
+
* @param array $data
|
711 |
+
* @param array $hostsArray
|
712 |
+
* @param int $connectTimeout
|
713 |
+
* @param int $readTimeout
|
714 |
+
*
|
715 |
+
* @return mixed
|
716 |
+
*
|
717 |
+
* @throws AlgoliaException
|
718 |
+
*/
|
719 |
+
public function request(
|
720 |
+
$context,
|
721 |
+
$method,
|
722 |
+
$path,
|
723 |
+
$params,
|
724 |
+
$data,
|
725 |
+
$hostsArray,
|
726 |
+
$connectTimeout,
|
727 |
+
$readTimeout
|
728 |
+
) {
|
729 |
+
$exceptions = [];
|
730 |
$cnt = 0;
|
731 |
foreach ($hostsArray as &$host) {
|
732 |
$cnt += 1;
|
736 |
}
|
737 |
try {
|
738 |
$res = $this->doRequest($context, $method, $host, $path, $params, $data, $connectTimeout, $readTimeout);
|
739 |
+
if ($res !== null) {
|
740 |
return $res;
|
741 |
+
}
|
742 |
} catch (AlgoliaException $e) {
|
743 |
throw $e;
|
744 |
} catch (\Exception $e) {
|
745 |
$exceptions[$host] = $e->getMessage();
|
746 |
}
|
747 |
}
|
748 |
+
throw new AlgoliaException('Hosts unreachable: '.implode(',', $exceptions));
|
749 |
}
|
750 |
|
751 |
+
/**
|
752 |
+
* @param ClientContext $context
|
753 |
+
* @param string $method
|
754 |
+
* @param string $host
|
755 |
+
* @param string $path
|
756 |
+
* @param array $params
|
757 |
+
* @param array $data
|
758 |
+
* @param int $connectTimeout
|
759 |
+
* @param int $readTimeout
|
760 |
+
*
|
761 |
+
* @return mixed
|
762 |
+
*
|
763 |
+
* @throws AlgoliaException
|
764 |
+
* @throws \Exception
|
765 |
+
*/
|
766 |
+
public function doRequest(
|
767 |
+
$context,
|
768 |
+
$method,
|
769 |
+
$host,
|
770 |
+
$path,
|
771 |
+
$params,
|
772 |
+
$data,
|
773 |
+
$connectTimeout,
|
774 |
+
$readTimeout
|
775 |
+
) {
|
776 |
+
if (strpos($host, 'http') === 0) {
|
777 |
+
$url = $host.$path;
|
778 |
} else {
|
779 |
+
$url = 'https://'.$host.$path;
|
780 |
}
|
781 |
+
|
782 |
if ($params != null && count($params) > 0) {
|
783 |
+
$params2 = [];
|
784 |
foreach ($params as $key => $val) {
|
785 |
if (is_array($val)) {
|
786 |
$params2[$key] = json_encode($val);
|
788 |
$params2[$key] = $val;
|
789 |
}
|
790 |
}
|
791 |
+
$url .= '?'.http_build_query($params2);
|
|
|
792 |
}
|
793 |
+
|
794 |
// initialize curl library
|
795 |
$curlHandle = curl_init();
|
796 |
+
|
797 |
+
// set curl options
|
798 |
+
try {
|
799 |
+
foreach ($this->curlOptions as $curlOption => $optionValue) {
|
800 |
+
curl_setopt($curlHandle, constant($curlOption), $optionValue);
|
801 |
+
}
|
802 |
+
} catch (\Exception $e) {
|
803 |
+
$this->invalidOptions($this->curlOptions, $e->getMessage());
|
804 |
+
}
|
805 |
+
|
806 |
//curl_setopt($curlHandle, CURLOPT_VERBOSE, true);
|
807 |
if ($context->adminAPIKey == null) {
|
808 |
+
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge([
|
809 |
+
'X-Algolia-Application-Id: '.$context->applicationID,
|
810 |
+
'X-Algolia-API-Key: '.$context->apiKey,
|
811 |
+
'Content-type: application/json',
|
812 |
+
], $context->headers));
|
813 |
} else {
|
814 |
+
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array_merge([
|
815 |
+
'X-Algolia-Application-Id: '.$context->applicationID,
|
816 |
+
'X-Algolia-API-Key: '.$context->adminAPIKey,
|
817 |
+
'X-Forwarded-For: '.$context->endUserIP,
|
818 |
+
'X-Algolia-UserToken: '.$context->algoliaUserToken,
|
819 |
+
'X-Forwarded-API-Key: '.$context->rateLimitAPIKey,
|
820 |
+
'Content-type: application/json',
|
821 |
+
], $context->headers));
|
822 |
+
}
|
823 |
+
|
824 |
+
curl_setopt($curlHandle, CURLOPT_USERAGENT, 'Algolia for PHP '.Version::get());
|
825 |
//Return the output instead of printing it
|
826 |
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
|
827 |
curl_setopt($curlHandle, CURLOPT_FAILONERROR, true);
|
828 |
curl_setopt($curlHandle, CURLOPT_ENCODING, '');
|
829 |
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, true);
|
830 |
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYHOST, 2);
|
831 |
+
curl_setopt($curlHandle, CURLOPT_CAINFO, $this->caInfoPath);
|
832 |
|
833 |
curl_setopt($curlHandle, CURLOPT_URL, $url);
|
834 |
$version = curl_version();
|
840 |
curl_setopt($curlHandle, CURLOPT_TIMEOUT, $readTimeout);
|
841 |
}
|
842 |
|
843 |
+
// The problem is that on (Li|U)nix, when libcurl uses the standard name resolver,
|
844 |
+
// a SIGALRM is raised during name resolution which libcurl thinks is the timeout alarm.
|
845 |
+
curl_setopt($curlHandle, CURLOPT_NOSIGNAL, 1);
|
846 |
curl_setopt($curlHandle, CURLOPT_FAILONERROR, false);
|
847 |
|
848 |
if ($method === 'GET') {
|
849 |
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'GET');
|
850 |
curl_setopt($curlHandle, CURLOPT_HTTPGET, true);
|
851 |
curl_setopt($curlHandle, CURLOPT_POST, false);
|
852 |
+
} else {
|
853 |
+
if ($method === 'POST') {
|
854 |
+
$body = ($data) ? json_encode($data) : '';
|
855 |
+
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'POST');
|
856 |
+
curl_setopt($curlHandle, CURLOPT_POST, true);
|
857 |
+
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $body);
|
858 |
+
} elseif ($method === 'DELETE') {
|
859 |
+
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
860 |
+
curl_setopt($curlHandle, CURLOPT_POST, false);
|
861 |
+
} elseif ($method === 'PUT') {
|
862 |
+
$body = ($data) ? json_encode($data) : '';
|
863 |
+
curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PUT');
|
864 |
+
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $body);
|
865 |
+
curl_setopt($curlHandle, CURLOPT_POST, true);
|
866 |
+
}
|
867 |
}
|
868 |
$mhandle = $context->getMHandle($curlHandle);
|
869 |
|
872 |
do {
|
873 |
$mrc = curl_multi_exec($mhandle, $running);
|
874 |
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
|
875 |
+
|
876 |
while ($running && $mrc == CURLM_OK) {
|
877 |
if (curl_multi_select($mhandle, 0.1) == -1) {
|
878 |
usleep(100);
|
881 |
$mrc = curl_multi_exec($mhandle, $running);
|
882 |
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
|
883 |
}
|
884 |
+
|
885 |
+
$http_status = (int) curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);
|
886 |
$response = curl_multi_getcontent($curlHandle);
|
887 |
$error = curl_error($curlHandle);
|
888 |
+
|
889 |
if (!empty($error)) {
|
890 |
throw new \Exception($error);
|
891 |
}
|
892 |
+
|
893 |
if ($http_status === 0 || $http_status === 503) {
|
894 |
// Could not reach host or service unavailable, try with another one if we have it
|
895 |
$context->releaseMHandle($curlHandle);
|
896 |
curl_close($curlHandle);
|
897 |
+
|
898 |
+
return;
|
899 |
}
|
900 |
|
901 |
$answer = json_decode($response, true);
|
903 |
curl_close($curlHandle);
|
904 |
|
905 |
if (intval($http_status / 100) == 4) {
|
906 |
+
throw new AlgoliaException(isset($answer['message']) ? $answer['message'] : $http_status + ' error');
|
907 |
+
} elseif (intval($http_status / 100) != 2) {
|
908 |
+
throw new \Exception($http_status.': '.$response);
|
|
|
909 |
}
|
910 |
|
911 |
switch (json_last_error()) {
|
921 |
case JSON_ERROR_STATE_MISMATCH:
|
922 |
$errorMsg = 'JSON parsing error: underflow or the modes mismatch';
|
923 |
break;
|
924 |
+
// PHP 5.3 less than 1.2.2 (Ubuntu 10.04 LTS)
|
925 |
+
case defined('JSON_ERROR_UTF8') ? JSON_ERROR_UTF8 : -1:
|
926 |
$errorMsg = 'JSON parsing error: malformed UTF-8 characters, possibly incorrectly encoded';
|
927 |
break;
|
928 |
case JSON_ERROR_NONE:
|
930 |
$errorMsg = null;
|
931 |
break;
|
932 |
}
|
933 |
+
if ($errorMsg !== null) {
|
934 |
throw new AlgoliaException($errorMsg);
|
935 |
+
}
|
936 |
|
937 |
return $answer;
|
938 |
}
|
|
|
939 |
|
940 |
+
/**
|
941 |
+
* Checks if curl option passed are valid curl options.
|
942 |
+
*
|
943 |
+
* @param array $curlOptions must be array but no type required while first test throw clear Exception
|
944 |
+
*
|
945 |
+
* @return array
|
946 |
+
*/
|
947 |
+
protected function checkCurlOptions($curlOptions)
|
948 |
+
{
|
949 |
+
if (!is_array($curlOptions)) {
|
950 |
+
throw new \InvalidArgumentException(
|
951 |
+
sprintf(
|
952 |
+
'AlgoliaSearch requires %s option to be array of valid curl options.',
|
953 |
+
static::CURLOPT
|
954 |
+
)
|
955 |
+
);
|
956 |
+
}
|
957 |
+
|
958 |
+
$checkedCurlOptions = array_intersect(array_keys($curlOptions), array_keys($this->getCurlConstants()));
|
959 |
+
|
960 |
+
if (count($checkedCurlOptions) !== count($curlOptions)) {
|
961 |
+
$this->invalidOptions($curlOptions);
|
962 |
+
}
|
963 |
+
|
964 |
+
return $curlOptions;
|
965 |
+
}
|
966 |
+
|
967 |
+
/**
|
968 |
+
* Get all php curl available options.
|
969 |
+
*
|
970 |
+
* @return array
|
971 |
+
*/
|
972 |
+
protected function getCurlConstants()
|
973 |
+
{
|
974 |
+
if (!is_null($this->curlConstants)) {
|
975 |
+
return $this->curlConstants;
|
976 |
+
}
|
977 |
+
|
978 |
+
$curlAllConstants = get_defined_constants(true);
|
979 |
+
|
980 |
+
if (isset($curlAllConstants['curl'])) {
|
981 |
+
$curlAllConstants = $curlAllConstants['curl'];
|
982 |
+
} elseif (isset($curlAllConstants['Core'])) { // hhvm
|
983 |
+
$curlAllConstants = $curlAllConstants['Core'];
|
984 |
+
} else {
|
985 |
+
return $this->curlConstants;
|
986 |
+
}
|
987 |
+
|
988 |
+
$curlConstants = [];
|
989 |
+
foreach ($curlAllConstants as $constantName => $constantValue) {
|
990 |
+
if (strpos($constantName, 'CURLOPT') === 0) {
|
991 |
+
$curlConstants[$constantName] = $constantValue;
|
992 |
+
}
|
993 |
+
}
|
994 |
+
|
995 |
+
$this->curlConstants = $curlConstants;
|
996 |
+
|
997 |
+
return $this->curlConstants;
|
998 |
+
}
|
999 |
+
|
1000 |
+
/**
|
1001 |
+
* throw clear Exception when bad curl option is set.
|
1002 |
+
*
|
1003 |
+
* @param array $curlOptions
|
1004 |
+
* @param string $errorMsg add specific message for disambiguation
|
1005 |
+
*/
|
1006 |
+
protected function invalidOptions(array $curlOptions = [], $errorMsg = '')
|
1007 |
+
{
|
1008 |
+
throw new \OutOfBoundsException(
|
1009 |
+
sprintf(
|
1010 |
+
'AlgoliaSearch %s options keys are invalid. %s given. error message : %s',
|
1011 |
+
static::CURLOPT,
|
1012 |
+
json_encode($curlOptions),
|
1013 |
+
$errorMsg
|
1014 |
+
)
|
1015 |
+
);
|
1016 |
+
}
|
1017 |
+
|
1018 |
+
/**
|
1019 |
+
* @return PlacesIndex
|
1020 |
+
*/
|
1021 |
+
private function getPlacesIndex()
|
1022 |
+
{
|
1023 |
+
return new PlacesIndex($this->context, $this);
|
1024 |
+
}
|
1025 |
+
|
1026 |
+
/**
|
1027 |
+
* @param string $appId
|
1028 |
+
* @param string $apiKey
|
1029 |
+
* @param array $hostsArray
|
1030 |
+
* @param array $options
|
1031 |
+
*
|
1032 |
+
* @return PlacesIndex
|
1033 |
+
*/
|
1034 |
+
public static function initPlaces($appId, $apiKey, $hostsArray = null, $options = [])
|
1035 |
+
{
|
1036 |
+
$options['placesEnabled'] = true;
|
1037 |
+
$client = new static($appId, $apiKey, $hostsArray, $options);
|
1038 |
+
|
1039 |
+
return $client->getPlacesIndex();
|
1040 |
+
}
|
1041 |
+
}
|
lib/AlgoliaSearch/ClientContext.php
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
<?php
|
|
|
2 |
/*
|
3 |
* Copyright (c) 2013 Algolia
|
4 |
* http://www.algolia.com/
|
@@ -23,53 +24,159 @@
|
|
23 |
*
|
24 |
*
|
25 |
*/
|
|
|
26 |
namespace AlgoliaSearch;
|
27 |
|
28 |
use Exception;
|
29 |
|
30 |
-
class ClientContext
|
31 |
-
|
|
|
|
|
|
|
32 |
public $applicationID;
|
|
|
|
|
|
|
|
|
33 |
public $apiKey;
|
|
|
|
|
|
|
|
|
34 |
public $readHostsArray;
|
|
|
|
|
|
|
|
|
35 |
public $writeHostsArray;
|
|
|
|
|
|
|
|
|
36 |
public $curlMHandle;
|
|
|
|
|
|
|
|
|
37 |
public $adminAPIKey;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
public $connectTimeout;
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
$this->applicationID = $applicationID;
|
45 |
$this->apiKey = $apiKey;
|
46 |
$this->readHostsArray = $hostsArray;
|
47 |
$this->writeHostsArray = $hostsArray;
|
|
|
48 |
if ($this->readHostsArray == null || count($this->readHostsArray) == 0) {
|
49 |
-
$this->readHostsArray =
|
50 |
-
$this->writeHostsArray =
|
51 |
}
|
|
|
52 |
if ($this->applicationID == null || mb_strlen($this->applicationID) == 0) {
|
53 |
throw new Exception('AlgoliaSearch requires an applicationID.');
|
54 |
}
|
|
|
55 |
if ($this->apiKey == null || mb_strlen($this->apiKey) == 0) {
|
56 |
throw new Exception('AlgoliaSearch requires an apiKey.');
|
57 |
}
|
58 |
|
59 |
-
$this->curlMHandle =
|
60 |
-
$this->adminAPIKey =
|
61 |
-
$this->endUserIP =
|
62 |
-
$this->
|
63 |
-
$this->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
}
|
65 |
|
66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
if ($this->curlMHandle != null) {
|
68 |
curl_multi_close($this->curlMHandle);
|
69 |
}
|
70 |
}
|
71 |
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
if ($this->curlMHandle == null) {
|
74 |
$this->curlMHandle = curl_multi_init();
|
75 |
}
|
@@ -78,24 +185,58 @@ class ClientContext {
|
|
78 |
return $this->curlMHandle;
|
79 |
}
|
80 |
|
81 |
-
|
|
|
|
|
|
|
|
|
82 |
curl_multi_remove_handle($this->curlMHandle, $curlHandle);
|
83 |
}
|
84 |
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
$this->adminAPIKey = $adminAPIKey;
|
87 |
$this->endUserIP = $endUserIP;
|
88 |
$this->rateLimitAPIKey = $rateLimitAPIKey;
|
89 |
}
|
90 |
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
|
|
|
|
|
|
96 |
}
|
97 |
|
98 |
-
|
|
|
|
|
|
|
|
|
|
|
99 |
$this->headers[$key] = $value;
|
100 |
}
|
101 |
}
|
1 |
<?php
|
2 |
+
|
3 |
/*
|
4 |
* Copyright (c) 2013 Algolia
|
5 |
* http://www.algolia.com/
|
24 |
*
|
25 |
*
|
26 |
*/
|
27 |
+
|
28 |
namespace AlgoliaSearch;
|
29 |
|
30 |
use Exception;
|
31 |
|
32 |
+
class ClientContext
|
33 |
+
{
|
34 |
+
/**
|
35 |
+
* @var string
|
36 |
+
*/
|
37 |
public $applicationID;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @var string
|
41 |
+
*/
|
42 |
public $apiKey;
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @var array
|
46 |
+
*/
|
47 |
public $readHostsArray;
|
48 |
+
|
49 |
+
/**
|
50 |
+
* @var array
|
51 |
+
*/
|
52 |
public $writeHostsArray;
|
53 |
+
|
54 |
+
/**
|
55 |
+
* @var resource
|
56 |
+
*/
|
57 |
public $curlMHandle;
|
58 |
+
|
59 |
+
/**
|
60 |
+
* @var string
|
61 |
+
*/
|
62 |
public $adminAPIKey;
|
63 |
+
|
64 |
+
/**
|
65 |
+
* @var string
|
66 |
+
*/
|
67 |
+
public $endUserIP;
|
68 |
+
|
69 |
+
/**
|
70 |
+
* @var string
|
71 |
+
*/
|
72 |
+
public $algoliaUserToken;
|
73 |
+
|
74 |
+
/**
|
75 |
+
* @var int
|
76 |
+
*/
|
77 |
public $connectTimeout;
|
78 |
|
79 |
+
/**
|
80 |
+
* ClientContext constructor.
|
81 |
+
*
|
82 |
+
* @param string $applicationID
|
83 |
+
* @param string $apiKey
|
84 |
+
* @param array $hostsArray
|
85 |
+
* @param bool $placesEnabled
|
86 |
+
*
|
87 |
+
* @throws Exception
|
88 |
+
*/
|
89 |
+
public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled = false)
|
90 |
+
{
|
91 |
+
// connect timeout of 2s by default
|
92 |
+
$this->connectTimeout = 2;
|
93 |
+
|
94 |
+
// global timeout of 30s by default
|
95 |
+
$this->readTimeout = 30;
|
96 |
+
|
97 |
+
// search timeout of 5s by default
|
98 |
+
$this->searchTimeout = 5;
|
99 |
+
|
100 |
$this->applicationID = $applicationID;
|
101 |
$this->apiKey = $apiKey;
|
102 |
$this->readHostsArray = $hostsArray;
|
103 |
$this->writeHostsArray = $hostsArray;
|
104 |
+
|
105 |
if ($this->readHostsArray == null || count($this->readHostsArray) == 0) {
|
106 |
+
$this->readHostsArray = $this->getDefaultReadHosts($placesEnabled);
|
107 |
+
$this->writeHostsArray = $this->getDefaultWriteHosts();
|
108 |
}
|
109 |
+
|
110 |
if ($this->applicationID == null || mb_strlen($this->applicationID) == 0) {
|
111 |
throw new Exception('AlgoliaSearch requires an applicationID.');
|
112 |
}
|
113 |
+
|
114 |
if ($this->apiKey == null || mb_strlen($this->apiKey) == 0) {
|
115 |
throw new Exception('AlgoliaSearch requires an apiKey.');
|
116 |
}
|
117 |
|
118 |
+
$this->curlMHandle = null;
|
119 |
+
$this->adminAPIKey = null;
|
120 |
+
$this->endUserIP = null;
|
121 |
+
$this->algoliaUserToken = null;
|
122 |
+
$this->rateLimitAPIKey = null;
|
123 |
+
$this->headers = [];
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* @param bool $placesEnabled
|
128 |
+
*
|
129 |
+
* @return array
|
130 |
+
*/
|
131 |
+
private function getDefaultReadHosts($placesEnabled)
|
132 |
+
{
|
133 |
+
if ($placesEnabled) {
|
134 |
+
return [
|
135 |
+
'places-dsn.algolia.net',
|
136 |
+
'places-1.algolianet.com',
|
137 |
+
'places-2.algolianet.com',
|
138 |
+
'places-3.algolianet.com',
|
139 |
+
];
|
140 |
+
}
|
141 |
+
|
142 |
+
return [
|
143 |
+
$this->applicationID.'-dsn.algolia.net',
|
144 |
+
$this->applicationID.'-1.algolianet.com',
|
145 |
+
$this->applicationID.'-2.algolianet.com',
|
146 |
+
$this->applicationID.'-3.algolianet.com',
|
147 |
+
];
|
148 |
}
|
149 |
|
150 |
+
/**
|
151 |
+
* @return array
|
152 |
+
*/
|
153 |
+
private function getDefaultWriteHosts()
|
154 |
+
{
|
155 |
+
return [
|
156 |
+
$this->applicationID.'.algolia.net',
|
157 |
+
$this->applicationID.'-1.algolianet.com',
|
158 |
+
$this->applicationID.'-2.algolianet.com',
|
159 |
+
$this->applicationID.'-3.algolianet.com',
|
160 |
+
];
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Closes eventually opened curl handles.
|
165 |
+
*/
|
166 |
+
public function __destruct()
|
167 |
+
{
|
168 |
if ($this->curlMHandle != null) {
|
169 |
curl_multi_close($this->curlMHandle);
|
170 |
}
|
171 |
}
|
172 |
|
173 |
+
/**
|
174 |
+
* @param $curlHandle
|
175 |
+
*
|
176 |
+
* @return resource
|
177 |
+
*/
|
178 |
+
public function getMHandle($curlHandle)
|
179 |
+
{
|
180 |
if ($this->curlMHandle == null) {
|
181 |
$this->curlMHandle = curl_multi_init();
|
182 |
}
|
185 |
return $this->curlMHandle;
|
186 |
}
|
187 |
|
188 |
+
/**
|
189 |
+
* @param $curlHandle
|
190 |
+
*/
|
191 |
+
public function releaseMHandle($curlHandle)
|
192 |
+
{
|
193 |
curl_multi_remove_handle($this->curlMHandle, $curlHandle);
|
194 |
}
|
195 |
|
196 |
+
/**
|
197 |
+
* @param string $ip
|
198 |
+
*/
|
199 |
+
public function setForwardedFor($ip)
|
200 |
+
{
|
201 |
+
$this->endUserIP = $ip;
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* @param string $token
|
206 |
+
*/
|
207 |
+
public function setAlgoliaUserToken($token)
|
208 |
+
{
|
209 |
+
$this->algoliaUserToken = $token;
|
210 |
+
}
|
211 |
+
|
212 |
+
/**
|
213 |
+
* @param string $adminAPIKey
|
214 |
+
* @param string $endUserIP
|
215 |
+
* @param string $rateLimitAPIKey
|
216 |
+
*/
|
217 |
+
public function setRateLimit($adminAPIKey, $endUserIP, $rateLimitAPIKey)
|
218 |
+
{
|
219 |
$this->adminAPIKey = $adminAPIKey;
|
220 |
$this->endUserIP = $endUserIP;
|
221 |
$this->rateLimitAPIKey = $rateLimitAPIKey;
|
222 |
}
|
223 |
|
224 |
+
/**
|
225 |
+
* Disables the rate limit.
|
226 |
+
*/
|
227 |
+
public function disableRateLimit()
|
228 |
+
{
|
229 |
+
$this->adminAPIKey = null;
|
230 |
+
$this->endUserIP = null;
|
231 |
+
$this->rateLimitAPIKey = null;
|
232 |
}
|
233 |
|
234 |
+
/**
|
235 |
+
* @param string $key
|
236 |
+
* @param string $value
|
237 |
+
*/
|
238 |
+
public function setExtraHeader($key, $value)
|
239 |
+
{
|
240 |
$this->headers[$key] = $value;
|
241 |
}
|
242 |
}
|
lib/AlgoliaSearch/Index.php
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
<?php
|
|
|
2 |
/*
|
3 |
* Copyright (c) 2013 Algolia
|
4 |
* http://www.algolia.com/
|
@@ -23,310 +24,527 @@
|
|
23 |
*
|
24 |
*
|
25 |
*/
|
|
|
26 |
namespace AlgoliaSearch;
|
27 |
|
28 |
/*
|
29 |
* Contains all the functions related to one index
|
30 |
* You should use Client.initIndex(indexName) to retrieve this object
|
31 |
*/
|
32 |
-
class Index
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
-
|
|
|
|
|
35 |
private $client;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
private $urlIndexName;
|
37 |
|
38 |
-
|
39 |
-
* Index initialization (You should not
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
*/
|
41 |
-
public function __construct($context, $client, $indexName)
|
|
|
42 |
$this->context = $context;
|
43 |
$this->client = $client;
|
44 |
$this->indexName = $indexName;
|
45 |
$this->urlIndexName = urlencode($indexName);
|
46 |
}
|
47 |
|
48 |
-
|
49 |
-
* Perform batch operation on several objects
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
*
|
51 |
-
* @
|
52 |
-
*
|
53 |
-
* @
|
54 |
*/
|
55 |
-
public function batchObjects($objects, $objectIDKey =
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
|
58 |
foreach ($objects as $obj) {
|
59 |
// If no or invalid action, assume updateObject
|
60 |
-
if (!
|
61 |
throw new \Exception('invalid or no action detected');
|
62 |
}
|
63 |
|
64 |
$action = $obj[$objectActionKey];
|
65 |
-
unset($obj[$objectActionKey]); // The action key is not included in the object
|
66 |
|
67 |
-
|
|
|
|
|
|
|
68 |
|
69 |
if (array_key_exists($objectIDKey, $obj)) {
|
70 |
-
$req[
|
71 |
}
|
72 |
|
73 |
$requests[] = $req;
|
74 |
}
|
75 |
|
76 |
-
return $this->batch(
|
77 |
}
|
78 |
|
79 |
-
|
80 |
-
* Add an object in this index
|
81 |
*
|
82 |
-
* @param content
|
83 |
-
*
|
84 |
-
* @param objectID (optional) an objectID you want to attribute to this object
|
85 |
-
*
|
|
|
|
|
86 |
*/
|
87 |
-
public function addObject($content, $objectID = null)
|
88 |
-
|
89 |
if ($objectID === null) {
|
90 |
-
return $this->client->request(
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
}
|
95 |
|
96 |
-
|
97 |
-
* Add several objects
|
98 |
*
|
99 |
-
* @param objects
|
|
|
|
|
|
|
100 |
*/
|
101 |
-
public function addObjects($objects, $objectIDKey =
|
102 |
-
|
|
|
|
|
103 |
return $this->batch($requests);
|
104 |
}
|
105 |
|
106 |
-
|
107 |
-
* Get an object from this index
|
108 |
*
|
109 |
-
* @param objectID
|
110 |
-
* @param attributesToRetrieve (optional) if set, contains the list of attributes to retrieve as a string
|
|
|
|
|
|
|
111 |
*/
|
112 |
-
public function getObject($objectID, $attributesToRetrieve = null)
|
|
|
113 |
$id = urlencode($objectID);
|
114 |
-
if ($attributesToRetrieve === null)
|
115 |
-
return $this->client->request(
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
}
|
119 |
|
120 |
-
|
121 |
-
* Get several objects from this index
|
|
|
|
|
122 |
*
|
123 |
-
* @
|
|
|
|
|
124 |
*/
|
125 |
-
public function getObjects($objectIDs)
|
|
|
126 |
if ($objectIDs == null) {
|
127 |
throw new \Exception('No list of objectID provided');
|
128 |
}
|
129 |
-
|
|
|
130 |
foreach ($objectIDs as $object) {
|
131 |
-
$req =
|
132 |
array_push($requests, $req);
|
133 |
}
|
134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
}
|
136 |
|
137 |
-
|
138 |
-
* Update partially an object (only update attributes passed in argument)
|
|
|
|
|
|
|
|
|
139 |
*
|
140 |
-
* @
|
141 |
-
*
|
|
|
142 |
*/
|
143 |
-
public function partialUpdateObject($partialObject, $createIfNotExists = true)
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
}
|
146 |
|
147 |
-
|
148 |
-
* Partially Override the content of several objects
|
|
|
|
|
|
|
|
|
149 |
*
|
150 |
-
* @
|
151 |
*/
|
152 |
-
public function partialUpdateObjects($objects, $objectIDKey =
|
|
|
153 |
if ($createIfNotExists) {
|
154 |
-
$requests = $this->buildBatch(
|
155 |
} else {
|
156 |
-
$requests = $this->buildBatch(
|
157 |
}
|
|
|
158 |
return $this->batch($requests);
|
159 |
}
|
160 |
|
161 |
-
|
162 |
-
* Override the content of object
|
|
|
|
|
163 |
*
|
164 |
-
* @
|
165 |
*/
|
166 |
-
public function saveObject($object)
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
}
|
169 |
|
170 |
-
|
171 |
-
* Override the content of several objects
|
|
|
|
|
|
|
172 |
*
|
173 |
-
* @
|
174 |
*/
|
175 |
-
public function saveObjects($objects, $objectIDKey =
|
176 |
-
|
|
|
|
|
177 |
return $this->batch($requests);
|
178 |
}
|
179 |
|
180 |
-
|
181 |
-
* Delete an object from the index
|
|
|
|
|
|
|
|
|
182 |
*
|
183 |
-
* @
|
|
|
184 |
*/
|
185 |
-
public function deleteObject($objectID)
|
|
|
186 |
if ($objectID == null || mb_strlen($objectID) == 0) {
|
187 |
throw new \Exception('objectID is mandatory');
|
188 |
}
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
}
|
191 |
|
192 |
-
|
193 |
-
* Delete several objects
|
|
|
|
|
194 |
*
|
195 |
-
* @
|
196 |
*/
|
197 |
-
public function deleteObjects($objects)
|
198 |
-
|
|
|
199 |
foreach ($objects as $key => $id) {
|
200 |
-
$objectIDs[$key] =
|
201 |
}
|
202 |
-
$requests = $this->buildBatch(
|
|
|
203 |
return $this->batch($requests);
|
204 |
}
|
205 |
|
206 |
-
|
207 |
-
* Delete all objects matching a query
|
208 |
*
|
209 |
-
* @param query
|
210 |
-
* @param
|
|
|
|
|
|
|
|
|
211 |
*/
|
212 |
-
public function deleteByQuery($query, $args =
|
213 |
-
|
214 |
-
$
|
|
|
|
|
|
|
215 |
$results = $this->search($query, $args);
|
216 |
while ($results['nbHits'] != 0) {
|
217 |
-
$objectIDs =
|
218 |
foreach ($results['hits'] as $elt) {
|
219 |
array_push($objectIDs, $elt['objectID']);
|
220 |
}
|
221 |
$res = $this->deleteObjects($objectIDs);
|
|
|
|
|
|
|
222 |
$this->waitTask($res['taskID']);
|
223 |
$results = $this->search($query, $args);
|
224 |
}
|
225 |
}
|
226 |
|
227 |
-
|
228 |
-
* Search inside the index
|
229 |
-
*
|
230 |
-
* @param query the full text query
|
231 |
-
* @param args
|
232 |
-
*
|
233 |
-
*
|
234 |
-
*
|
235 |
-
*
|
236 |
-
*
|
237 |
-
*
|
238 |
-
*
|
239 |
-
*
|
240 |
-
*
|
241 |
-
*
|
242 |
-
*
|
243 |
-
*
|
244 |
-
*
|
245 |
-
*
|
246 |
-
*
|
247 |
-
*
|
248 |
-
*
|
249 |
-
*
|
250 |
-
*
|
251 |
-
*
|
252 |
-
*
|
253 |
-
*
|
254 |
-
*
|
255 |
-
*
|
256 |
-
*
|
257 |
-
*
|
258 |
-
*
|
259 |
-
*
|
260 |
-
*
|
261 |
-
*
|
262 |
-
*
|
263 |
-
*
|
264 |
-
*
|
265 |
-
*
|
266 |
-
*
|
267 |
-
*
|
268 |
-
*
|
269 |
-
*
|
270 |
-
*
|
271 |
-
*
|
272 |
-
*
|
273 |
-
*
|
274 |
-
*
|
275 |
-
*
|
276 |
-
*
|
277 |
-
*
|
278 |
-
*
|
279 |
-
*
|
280 |
-
*
|
281 |
-
*
|
282 |
-
*
|
283 |
-
*
|
284 |
-
*
|
285 |
-
*
|
286 |
-
*
|
287 |
-
*
|
288 |
-
*
|
289 |
-
|
290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
291 |
if ($args === null) {
|
292 |
-
$args =
|
293 |
}
|
294 |
-
$args[
|
295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
296 |
}
|
297 |
|
298 |
-
|
299 |
-
* Perform a search with disjunctive facets generating as many queries as number of disjunctive facets
|
|
|
|
|
|
|
|
|
|
|
|
|
300 |
*
|
301 |
-
* @
|
302 |
-
*
|
303 |
-
* @
|
304 |
-
* @
|
305 |
-
* ex: { "my_facet1" => ["my_value1", ["my_value2"], "my_disjunctive_facet1" => ["my_value1", "my_value2"] }
|
306 |
*/
|
307 |
-
public function searchDisjunctiveFaceting($query, $disjunctive_facets, $params =
|
308 |
-
|
309 |
-
|
|
|
310 |
}
|
311 |
-
|
312 |
-
|
|
|
313 |
}
|
314 |
|
315 |
-
if (gettype($disjunctive_facets) ==
|
316 |
-
$disjunctive_facets =
|
317 |
}
|
318 |
|
319 |
-
$disjunctive_refinements =
|
320 |
foreach ($refinements as $key => $value) {
|
321 |
if (in_array($key, $disjunctive_facets)) {
|
322 |
$disjunctive_refinements[$key] = $value;
|
323 |
}
|
324 |
}
|
325 |
-
$queries =
|
326 |
-
$filters =
|
327 |
|
328 |
foreach ($refinements as $key => $value) {
|
329 |
-
$r = array_map(
|
|
|
|
|
|
|
|
|
|
|
330 |
|
331 |
if (in_array($key, $disjunctive_refinements)) {
|
332 |
$filter = array_merge($filters, $r);
|
@@ -334,15 +552,20 @@ class Index {
|
|
334 |
array_push($filters, $r);
|
335 |
}
|
336 |
}
|
337 |
-
$params[
|
338 |
-
$params[
|
339 |
-
$params[
|
340 |
array_push($queries, $params);
|
341 |
foreach ($disjunctive_facets as $disjunctive_facet) {
|
342 |
-
$filters =
|
343 |
foreach ($refinements as $key => $value) {
|
344 |
if ($key != $disjunctive_facet) {
|
345 |
-
$r = array_map(
|
|
|
|
|
|
|
|
|
|
|
346 |
|
347 |
if (in_array($key, $disjunctive_refinements)) {
|
348 |
$filter = array_merge($filters, $r);
|
@@ -351,22 +574,22 @@ class Index {
|
|
351 |
}
|
352 |
}
|
353 |
}
|
354 |
-
$params[
|
355 |
-
$params[
|
356 |
-
$params[
|
357 |
-
$params[
|
358 |
-
$params[
|
359 |
-
$params[
|
360 |
-
$params[
|
361 |
-
$params[
|
362 |
-
$params[
|
363 |
-
$params[
|
364 |
array_push($queries, $params);
|
365 |
}
|
366 |
$answers = $this->client->multipleQueries($queries);
|
367 |
|
368 |
$aggregated_answer = $answers['results'][0];
|
369 |
-
$aggregated_answer['disjunctiveFacets'] =
|
370 |
for ($i = 1; $i < count($answers['results']); $i++) {
|
371 |
foreach ($answers['results'][$i]['facets'] as $key => $facet) {
|
372 |
$aggregated_answer['disjunctiveFacets'][$key] = $facet;
|
@@ -380,245 +603,499 @@ class Index {
|
|
380 |
}
|
381 |
}
|
382 |
}
|
|
|
383 |
return $aggregated_answer;
|
384 |
}
|
385 |
|
386 |
-
|
387 |
-
* Browse all index content
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
*
|
389 |
-
* @
|
390 |
-
* Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
|
391 |
-
* @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000.
|
392 |
*/
|
393 |
-
|
394 |
-
|
395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
396 |
}
|
397 |
|
398 |
-
|
399 |
* Wait the publication of a task on the server.
|
400 |
* All server task are asynchronous and you can check with this method that the task is published.
|
401 |
*
|
402 |
-
* @param taskID
|
403 |
-
* @param timeBeforeRetry the time in milliseconds before retry (default = 100ms)
|
|
|
|
|
404 |
*/
|
405 |
-
public function waitTask($taskID, $timeBeforeRetry = 100)
|
|
|
406 |
while (true) {
|
407 |
-
$res = $this->
|
408 |
-
if ($res[
|
409 |
return $res;
|
|
|
410 |
usleep($timeBeforeRetry * 1000);
|
411 |
}
|
412 |
}
|
413 |
|
414 |
-
|
415 |
-
*
|
|
|
|
|
|
|
416 |
*
|
|
|
417 |
*/
|
418 |
-
public function
|
419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
}
|
421 |
|
422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
423 |
* This function deletes the index content. Settings and index specific API keys are kept untouched.
|
|
|
|
|
|
|
|
|
424 |
*/
|
425 |
-
public function clearIndex()
|
426 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
427 |
}
|
428 |
|
429 |
-
|
430 |
-
* Set settings for this index
|
431 |
-
*
|
432 |
-
* @param
|
433 |
-
*
|
434 |
-
*
|
435 |
-
*
|
436 |
-
*
|
437 |
-
*
|
438 |
-
*
|
439 |
-
*
|
440 |
-
*
|
441 |
-
*
|
442 |
-
*
|
443 |
-
*
|
444 |
-
*
|
445 |
-
*
|
446 |
-
*
|
447 |
-
*
|
448 |
-
*
|
449 |
-
*
|
450 |
-
*
|
451 |
-
*
|
452 |
-
*
|
453 |
-
*
|
454 |
-
*
|
455 |
-
*
|
456 |
-
*
|
457 |
-
*
|
458 |
-
*
|
459 |
-
*
|
460 |
-
*
|
461 |
-
*
|
462 |
-
*
|
463 |
-
*
|
464 |
-
*
|
465 |
-
*
|
466 |
-
*
|
467 |
-
*
|
468 |
-
*
|
469 |
-
*
|
470 |
-
*
|
471 |
-
*
|
472 |
-
*
|
473 |
-
*
|
474 |
-
*
|
475 |
-
*
|
476 |
-
*
|
477 |
-
*
|
478 |
-
*
|
479 |
-
|
480 |
-
|
481 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
482 |
}
|
483 |
|
484 |
-
|
485 |
-
* List all existing user keys associated to this index with their associated ACLs
|
486 |
*
|
|
|
|
|
|
|
487 |
*/
|
488 |
-
public function listUserKeys()
|
489 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
490 |
}
|
491 |
|
492 |
-
|
493 |
-
* Get ACL of a user key associated to this index
|
494 |
*
|
|
|
|
|
|
|
|
|
|
|
495 |
*/
|
496 |
-
public function getUserKeyACL($key)
|
497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
498 |
}
|
499 |
|
500 |
-
|
501 |
-
* Delete an existing user key associated to this index
|
|
|
|
|
502 |
*
|
|
|
|
|
|
|
503 |
*/
|
504 |
-
public function deleteUserKey($key)
|
505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
506 |
}
|
507 |
|
508 |
-
|
509 |
-
* Create a new user key associated to this index
|
510 |
-
*
|
511 |
-
* @param obj
|
512 |
-
*
|
513 |
-
*
|
514 |
-
*
|
515 |
-
*
|
516 |
-
*
|
517 |
-
*
|
518 |
-
*
|
519 |
-
*
|
520 |
-
*
|
521 |
-
*
|
522 |
-
*
|
523 |
-
*
|
524 |
-
*
|
525 |
-
*
|
526 |
-
*
|
527 |
-
*
|
528 |
-
*
|
529 |
-
*
|
530 |
-
* @param validity
|
531 |
-
*
|
532 |
-
* @param
|
533 |
-
|
534 |
-
|
535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
536 |
$params = $obj;
|
537 |
-
$params[
|
538 |
-
$params[
|
539 |
-
$params[
|
540 |
} else {
|
541 |
-
$params =
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
}
|
548 |
-
|
549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
550 |
}
|
551 |
|
552 |
-
|
553 |
-
* Update a user key associated to this index
|
554 |
-
*
|
555 |
-
* @param
|
556 |
-
*
|
557 |
-
*
|
558 |
-
*
|
559 |
-
*
|
560 |
-
*
|
561 |
-
*
|
562 |
-
*
|
563 |
-
*
|
564 |
-
*
|
565 |
-
*
|
566 |
-
*
|
567 |
-
*
|
568 |
-
*
|
569 |
-
*
|
570 |
-
*
|
571 |
-
*
|
572 |
-
*
|
573 |
-
*
|
574 |
-
*
|
575 |
-
* @param
|
576 |
-
*
|
577 |
-
|
578 |
-
|
579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
580 |
$params = $obj;
|
581 |
-
$params[
|
582 |
-
$params[
|
583 |
-
$params[
|
584 |
} else {
|
585 |
-
$params =
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
}
|
592 |
-
|
593 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
594 |
}
|
595 |
|
596 |
/**
|
597 |
-
* Send a batch request
|
598 |
-
*
|
|
|
|
|
|
|
599 |
*/
|
600 |
-
public function batch($requests)
|
601 |
-
|
602 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
}
|
604 |
|
605 |
/**
|
606 |
-
* Build a batch request
|
607 |
-
*
|
608 |
-
* @param
|
609 |
-
* @param $
|
610 |
-
* @param
|
|
|
|
|
611 |
* @return array
|
612 |
*/
|
613 |
-
private function buildBatch($action, $objects, $withObjectID, $objectIDKey =
|
614 |
-
|
|
|
615 |
foreach ($objects as $obj) {
|
616 |
-
$req =
|
617 |
if ($withObjectID && array_key_exists($objectIDKey, $obj)) {
|
618 |
-
$req[
|
619 |
}
|
620 |
array_push($requests, $req);
|
621 |
}
|
622 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
623 |
}
|
624 |
}
|
1 |
<?php
|
2 |
+
|
3 |
/*
|
4 |
* Copyright (c) 2013 Algolia
|
5 |
* http://www.algolia.com/
|
24 |
*
|
25 |
*
|
26 |
*/
|
27 |
+
|
28 |
namespace AlgoliaSearch;
|
29 |
|
30 |
/*
|
31 |
* Contains all the functions related to one index
|
32 |
* You should use Client.initIndex(indexName) to retrieve this object
|
33 |
*/
|
34 |
+
class Index
|
35 |
+
{
|
36 |
+
/**
|
37 |
+
* @var ClientContext
|
38 |
+
*/
|
39 |
+
private $context;
|
40 |
|
41 |
+
/**
|
42 |
+
* @var Client
|
43 |
+
*/
|
44 |
private $client;
|
45 |
+
|
46 |
+
/**
|
47 |
+
* @var string
|
48 |
+
*/
|
49 |
+
public $indexName;
|
50 |
+
|
51 |
+
/**
|
52 |
+
* @var string
|
53 |
+
*/
|
54 |
private $urlIndexName;
|
55 |
|
56 |
+
/**
|
57 |
+
* Index initialization (You should not instantiate this yourself).
|
58 |
+
*
|
59 |
+
* @param ClientContext $context
|
60 |
+
* @param Client $client
|
61 |
+
* @param string $indexName
|
62 |
+
*
|
63 |
+
* @internal
|
64 |
*/
|
65 |
+
public function __construct(ClientContext $context, Client $client, $indexName)
|
66 |
+
{
|
67 |
$this->context = $context;
|
68 |
$this->client = $client;
|
69 |
$this->indexName = $indexName;
|
70 |
$this->urlIndexName = urlencode($indexName);
|
71 |
}
|
72 |
|
73 |
+
/**
|
74 |
+
* Perform batch operation on several objects.
|
75 |
+
*
|
76 |
+
* @param array $objects contains an array of objects to update (each object must contains an objectID
|
77 |
+
* attribute)
|
78 |
+
* @param string $objectIDKey the key in each object that contains the objectID
|
79 |
+
* @param string $objectActionKey the key in each object that contains the action to perform (addObject, updateObject,
|
80 |
+
* deleteObject or partialUpdateObject)
|
81 |
*
|
82 |
+
* @return mixed
|
83 |
+
*
|
84 |
+
* @throws \Exception
|
85 |
*/
|
86 |
+
public function batchObjects($objects, $objectIDKey = 'objectID', $objectActionKey = 'objectAction')
|
87 |
+
{
|
88 |
+
$requests = [];
|
89 |
+
$allowedActions = [
|
90 |
+
'addObject',
|
91 |
+
'updateObject',
|
92 |
+
'deleteObject',
|
93 |
+
'partialUpdateObject',
|
94 |
+
'partialUpdateObjectNoCreate',
|
95 |
+
];
|
96 |
|
97 |
foreach ($objects as $obj) {
|
98 |
// If no or invalid action, assume updateObject
|
99 |
+
if (!isset($obj[$objectActionKey]) || !in_array($obj[$objectActionKey], $allowedActions)) {
|
100 |
throw new \Exception('invalid or no action detected');
|
101 |
}
|
102 |
|
103 |
$action = $obj[$objectActionKey];
|
|
|
104 |
|
105 |
+
// The action key is not included in the object
|
106 |
+
unset($obj[$objectActionKey]);
|
107 |
+
|
108 |
+
$req = ['action' => $action, 'body' => $obj];
|
109 |
|
110 |
if (array_key_exists($objectIDKey, $obj)) {
|
111 |
+
$req['objectID'] = (string) $obj[$objectIDKey];
|
112 |
}
|
113 |
|
114 |
$requests[] = $req;
|
115 |
}
|
116 |
|
117 |
+
return $this->batch(['requests' => $requests]);
|
118 |
}
|
119 |
|
120 |
+
/**
|
121 |
+
* Add an object in this index.
|
122 |
*
|
123 |
+
* @param array $content contains the object to add inside the index.
|
124 |
+
* The object is represented by an associative array
|
125 |
+
* @param string|null $objectID (optional) an objectID you want to attribute to this object
|
126 |
+
* (if the attribute already exist the old object will be overwrite)
|
127 |
+
*
|
128 |
+
* @return mixed
|
129 |
*/
|
130 |
+
public function addObject($content, $objectID = null)
|
131 |
+
{
|
132 |
if ($objectID === null) {
|
133 |
+
return $this->client->request(
|
134 |
+
$this->context,
|
135 |
+
'POST',
|
136 |
+
'/1/indexes/'.$this->urlIndexName,
|
137 |
+
[],
|
138 |
+
$content,
|
139 |
+
$this->context->writeHostsArray,
|
140 |
+
$this->context->connectTimeout,
|
141 |
+
$this->context->readTimeout
|
142 |
+
);
|
143 |
}
|
144 |
+
|
145 |
+
return $this->client->request(
|
146 |
+
$this->context,
|
147 |
+
'PUT',
|
148 |
+
'/1/indexes/'.$this->urlIndexName.'/'.urlencode($objectID),
|
149 |
+
[],
|
150 |
+
$content,
|
151 |
+
$this->context->writeHostsArray,
|
152 |
+
$this->context->connectTimeout,
|
153 |
+
$this->context->readTimeout
|
154 |
+
);
|
155 |
}
|
156 |
|
157 |
+
/**
|
158 |
+
* Add several objects.
|
159 |
*
|
160 |
+
* @param array $objects contains an array of objects to add. If the object contains an objectID
|
161 |
+
* @param string $objectIDKey
|
162 |
+
*
|
163 |
+
* @return mixed
|
164 |
*/
|
165 |
+
public function addObjects($objects, $objectIDKey = 'objectID')
|
166 |
+
{
|
167 |
+
$requests = $this->buildBatch('addObject', $objects, true, $objectIDKey);
|
168 |
+
|
169 |
return $this->batch($requests);
|
170 |
}
|
171 |
|
172 |
+
/**
|
173 |
+
* Get an object from this index.
|
174 |
*
|
175 |
+
* @param $objectID the unique identifier of the object to retrieve
|
176 |
+
* @param $attributesToRetrieve (optional) if set, contains the list of attributes to retrieve as a string
|
177 |
+
* separated by ","
|
178 |
+
*
|
179 |
+
* @return mixed
|
180 |
*/
|
181 |
+
public function getObject($objectID, $attributesToRetrieve = null)
|
182 |
+
{
|
183 |
$id = urlencode($objectID);
|
184 |
+
if ($attributesToRetrieve === null) {
|
185 |
+
return $this->client->request(
|
186 |
+
$this->context,
|
187 |
+
'GET',
|
188 |
+
'/1/indexes/'.$this->urlIndexName.'/'.$id,
|
189 |
+
null,
|
190 |
+
null,
|
191 |
+
$this->context->readHostsArray,
|
192 |
+
$this->context->connectTimeout,
|
193 |
+
$this->context->readTimeout
|
194 |
+
);
|
195 |
+
}
|
196 |
+
|
197 |
+
return $this->client->request(
|
198 |
+
$this->context,
|
199 |
+
'GET',
|
200 |
+
'/1/indexes/'.$this->urlIndexName.'/'.$id,
|
201 |
+
['attributes' => $attributesToRetrieve],
|
202 |
+
null,
|
203 |
+
$this->context->readHostsArray,
|
204 |
+
$this->context->connectTimeout,
|
205 |
+
$this->context->readTimeout
|
206 |
+
);
|
207 |
}
|
208 |
|
209 |
+
/**
|
210 |
+
* Get several objects from this index.
|
211 |
+
*
|
212 |
+
* @param array $objectIDs the array of unique identifier of objects to retrieve
|
213 |
*
|
214 |
+
* @return mixed
|
215 |
+
*
|
216 |
+
* @throws \Exception
|
217 |
*/
|
218 |
+
public function getObjects($objectIDs)
|
219 |
+
{
|
220 |
if ($objectIDs == null) {
|
221 |
throw new \Exception('No list of objectID provided');
|
222 |
}
|
223 |
+
|
224 |
+
$requests = [];
|
225 |
foreach ($objectIDs as $object) {
|
226 |
+
$req = ['indexName' => $this->indexName, 'objectID' => $object];
|
227 |
array_push($requests, $req);
|
228 |
}
|
229 |
+
|
230 |
+
return $this->client->request(
|
231 |
+
$this->context,
|
232 |
+
'POST',
|
233 |
+
'/1/indexes/*/objects',
|
234 |
+
[],
|
235 |
+
['requests' => $requests],
|
236 |
+
$this->context->readHostsArray,
|
237 |
+
$this->context->connectTimeout,
|
238 |
+
$this->context->readTimeout
|
239 |
+
);
|
240 |
}
|
241 |
|
242 |
+
/**
|
243 |
+
* Update partially an object (only update attributes passed in argument).
|
244 |
+
*
|
245 |
+
* @param array $partialObject contains the object attributes to override, the
|
246 |
+
* object must contains an objectID attribute
|
247 |
+
* @param bool $createIfNotExists
|
248 |
*
|
249 |
+
* @return mixed
|
250 |
+
*
|
251 |
+
* @throws AlgoliaException
|
252 |
*/
|
253 |
+
public function partialUpdateObject($partialObject, $createIfNotExists = true)
|
254 |
+
{
|
255 |
+
$queryString = $createIfNotExists ? '' : '?createIfNotExists=false';
|
256 |
+
|
257 |
+
return $this->client->request(
|
258 |
+
$this->context,
|
259 |
+
'POST',
|
260 |
+
'/1/indexes/'.$this->urlIndexName.'/'.urlencode($partialObject['objectID']).'/partial'.$queryString,
|
261 |
+
[],
|
262 |
+
$partialObject,
|
263 |
+
$this->context->writeHostsArray,
|
264 |
+
$this->context->connectTimeout,
|
265 |
+
$this->context->readTimeout
|
266 |
+
);
|
267 |
}
|
268 |
|
269 |
+
/**
|
270 |
+
* Partially Override the content of several objects.
|
271 |
+
*
|
272 |
+
* @param array $objects contains an array of objects to update (each object must contains a objectID attribute)
|
273 |
+
* @param string $objectIDKey
|
274 |
+
* @param bool $createIfNotExists
|
275 |
*
|
276 |
+
* @return mixed
|
277 |
*/
|
278 |
+
public function partialUpdateObjects($objects, $objectIDKey = 'objectID', $createIfNotExists = true)
|
279 |
+
{
|
280 |
if ($createIfNotExists) {
|
281 |
+
$requests = $this->buildBatch('partialUpdateObject', $objects, true, $objectIDKey);
|
282 |
} else {
|
283 |
+
$requests = $this->buildBatch('partialUpdateObjectNoCreate', $objects, true, $objectIDKey);
|
284 |
}
|
285 |
+
|
286 |
return $this->batch($requests);
|
287 |
}
|
288 |
|
289 |
+
/**
|
290 |
+
* Override the content of object.
|
291 |
+
*
|
292 |
+
* @param array $object contains the object to save, the object must contains an objectID attribute
|
293 |
*
|
294 |
+
* @return mixed
|
295 |
*/
|
296 |
+
public function saveObject($object)
|
297 |
+
{
|
298 |
+
return $this->client->request(
|
299 |
+
$this->context,
|
300 |
+
'PUT',
|
301 |
+
'/1/indexes/'.$this->urlIndexName.'/'.urlencode($object['objectID']),
|
302 |
+
[],
|
303 |
+
$object,
|
304 |
+
$this->context->writeHostsArray,
|
305 |
+
$this->context->connectTimeout,
|
306 |
+
$this->context->readTimeout
|
307 |
+
);
|
308 |
}
|
309 |
|
310 |
+
/**
|
311 |
+
* Override the content of several objects.
|
312 |
+
*
|
313 |
+
* @param array $objects contains an array of objects to update (each object must contains a objectID attribute)
|
314 |
+
* @param string $objectIDKey
|
315 |
*
|
316 |
+
* @return mixed
|
317 |
*/
|
318 |
+
public function saveObjects($objects, $objectIDKey = 'objectID')
|
319 |
+
{
|
320 |
+
$requests = $this->buildBatch('updateObject', $objects, true, $objectIDKey);
|
321 |
+
|
322 |
return $this->batch($requests);
|
323 |
}
|
324 |
|
325 |
+
/**
|
326 |
+
* Delete an object from the index.
|
327 |
+
*
|
328 |
+
* @param $objectID the unique identifier of object to delete
|
329 |
+
*
|
330 |
+
* @return mixed
|
331 |
*
|
332 |
+
* @throws AlgoliaException
|
333 |
+
* @throws \Exception
|
334 |
*/
|
335 |
+
public function deleteObject($objectID)
|
336 |
+
{
|
337 |
if ($objectID == null || mb_strlen($objectID) == 0) {
|
338 |
throw new \Exception('objectID is mandatory');
|
339 |
}
|
340 |
+
|
341 |
+
return $this->client->request(
|
342 |
+
$this->context,
|
343 |
+
'DELETE',
|
344 |
+
'/1/indexes/'.$this->urlIndexName.'/'.urlencode($objectID),
|
345 |
+
null,
|
346 |
+
null,
|
347 |
+
$this->context->writeHostsArray,
|
348 |
+
$this->context->connectTimeout,
|
349 |
+
$this->context->readTimeout
|
350 |
+
);
|
351 |
}
|
352 |
|
353 |
+
/**
|
354 |
+
* Delete several objects.
|
355 |
+
*
|
356 |
+
* @param array $objects contains an array of objectIDs to delete. If the object contains an objectID
|
357 |
*
|
358 |
+
* @return mixed
|
359 |
*/
|
360 |
+
public function deleteObjects($objects)
|
361 |
+
{
|
362 |
+
$objectIDs = [];
|
363 |
foreach ($objects as $key => $id) {
|
364 |
+
$objectIDs[$key] = ['objectID' => $id];
|
365 |
}
|
366 |
+
$requests = $this->buildBatch('deleteObject', $objectIDs, true);
|
367 |
+
|
368 |
return $this->batch($requests);
|
369 |
}
|
370 |
|
371 |
+
/**
|
372 |
+
* Delete all objects matching a query.
|
373 |
*
|
374 |
+
* @param string $query the query string
|
375 |
+
* @param array $args the optional query parameters
|
376 |
+
* @param bool $waitLastCall
|
377 |
+
* /!\ Be safe with "waitLastCall"
|
378 |
+
* In really rare cases you can have the number of hits smaller than the hitsPerPage
|
379 |
+
* param if you trigger the timeout of the search, in that case you won't remove all the records
|
380 |
*/
|
381 |
+
public function deleteByQuery($query, $args = [], $waitLastCall = true)
|
382 |
+
{
|
383 |
+
$args['attributesToRetrieve'] = 'objectID';
|
384 |
+
$args['hitsPerPage'] = 1000;
|
385 |
+
$args['distinct'] = false;
|
386 |
+
|
387 |
$results = $this->search($query, $args);
|
388 |
while ($results['nbHits'] != 0) {
|
389 |
+
$objectIDs = [];
|
390 |
foreach ($results['hits'] as $elt) {
|
391 |
array_push($objectIDs, $elt['objectID']);
|
392 |
}
|
393 |
$res = $this->deleteObjects($objectIDs);
|
394 |
+
if ($results['nbHits'] < $args['hitsPerPage'] && false === $waitLastCall) {
|
395 |
+
break;
|
396 |
+
}
|
397 |
$this->waitTask($res['taskID']);
|
398 |
$results = $this->search($query, $args);
|
399 |
}
|
400 |
}
|
401 |
|
402 |
+
/**
|
403 |
+
* Search inside the index.
|
404 |
+
*
|
405 |
+
* @param string $query the full text query
|
406 |
+
* @param mixed $args (optional) if set, contains an associative array with query parameters:
|
407 |
+
* - page: (integer) Pagination parameter used to select the page to retrieve.
|
408 |
+
* Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
|
409 |
+
* - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page.
|
410 |
+
* Defaults to 20.
|
411 |
+
* - attributesToRetrieve: a string that contains the list of object attributes you want to
|
412 |
+
* retrieve (let you minimize the answer size). Attributes are separated with a comma (for
|
413 |
+
* example "name,address"). You can also use a string array encoding (for example
|
414 |
+
* ["name","address"]). By default, all attributes are retrieved. You can also use '*' to
|
415 |
+
* retrieve all values when an attributesToRetrieve setting is specified for your index.
|
416 |
+
* - attributesToHighlight: a string that contains the list of attributes you want to highlight
|
417 |
+
* according to the query. Attributes are separated by a comma. You can also use a string array
|
418 |
+
* encoding (for example ["name","address"]). If an attribute has no match for the query, the raw
|
419 |
+
* value is returned. By default all indexed text attributes are highlighted. You can use `*` if
|
420 |
+
* you want to highlight all textual attributes. Numerical attributes are not highlighted. A
|
421 |
+
* matchLevel is returned for each highlighted attribute and can contain:
|
422 |
+
* - full: if all the query terms were found in the attribute,
|
423 |
+
* - partial: if only some of the query terms were found,
|
424 |
+
* - none: if none of the query terms were found.
|
425 |
+
* - attributesToSnippet: a string that contains the list of attributes to snippet alongside the
|
426 |
+
* number of words to return (syntax is `attributeName:nbWords`). Attributes are separated by a
|
427 |
+
* comma (Example: attributesToSnippet=name:10,content:10). You can also use a string array
|
428 |
+
* encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is
|
429 |
+
* computed.
|
430 |
+
* - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in
|
431 |
+
* this word. Defaults to 3.
|
432 |
+
* - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos
|
433 |
+
* in this word. Defaults to 7.
|
434 |
+
* - getRankingInfo: if set to 1, the result hits will contain ranking information in
|
435 |
+
* _rankingInfo attribute.
|
436 |
+
* - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats
|
437 |
+
* separated by a comma). For example aroundLatLng=47.316669,5.016670). You can specify the
|
438 |
+
* maximum distance in meters with the aroundRadius parameter (in meters) and the precision for
|
439 |
+
* ranking with aroundPrecision
|
440 |
+
* (for example if you set aroundPrecision=100, two objects that are distant of less than 100m
|
441 |
+
* will be considered as identical for "geo" ranking parameter). At indexing, you should specify
|
442 |
+
* geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409,
|
443 |
+
* "lng":2.348800}})
|
444 |
+
* - insideBoundingBox: search entries inside a given area defined by the two extreme points of a
|
445 |
+
* rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng). For example
|
446 |
+
* insideBoundingBox=47.3165,4.9665,47.3424,5.0201). At indexing, you should specify geoloc of an
|
447 |
+
* object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
|
448 |
+
* - numericFilters: a string that contains the list of numeric filters you want to apply
|
449 |
+
* separated by a comma. The syntax of one filter is `attributeName` followed by `operand`
|
450 |
+
* followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`. You can have
|
451 |
+
* multiple conditions on one attribute like for example numericFilters=price>100,price<1000. You
|
452 |
+
* can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]).
|
453 |
+
* - tagFilters: filter the query by a set of tags. You can AND tags by separating them by
|
454 |
+
* commas.
|
455 |
+
* To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2
|
456 |
+
* OR tag3). You can also use a string array encoding, for example tagFilters:
|
457 |
+
* ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3). At indexing, tags should be added in
|
458 |
+
* the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}).
|
459 |
+
* - facetFilters: filter the query by a list of facets.
|
460 |
+
* Facets are separated by commas and each facet is encoded as `attributeName:value`.
|
461 |
+
* For example: `facetFilters=category:Book,author:John%20Doe`.
|
462 |
+
* You can also use a string array encoding (for example
|
463 |
+
* `["category:Book","author:John%20Doe"]`).
|
464 |
+
* - facets: List of object attributes that you want to use for faceting.
|
465 |
+
* Attributes are separated with a comma (for example `"category,author"` ).
|
466 |
+
* You can also use a JSON string array encoding (for example ["category","author"]).
|
467 |
+
* Only attributes that have been added in **attributesForFaceting** index setting can be used in
|
468 |
+
* this parameter. You can also use `*` to perform faceting on all attributes specified in
|
469 |
+
* **attributesForFaceting**.
|
470 |
+
* - queryType: select how the query words are interpreted, it can be one of the following value:
|
471 |
+
* - prefixAll: all query words are interpreted as prefixes,
|
472 |
+
* - prefixLast: only the last word is interpreted as a prefix (default behavior),
|
473 |
+
* - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
|
474 |
+
* - optionalWords: a string that contains the list of words that should be considered as
|
475 |
+
* optional when found in the query. The list of words is comma separated.
|
476 |
+
* - distinct: If set to 1, enable the distinct feature (disabled by default) if the
|
477 |
+
* attributeForDistinct index setting is set. This feature is similar to the SQL "distinct"
|
478 |
+
* keyword: when enabled in a query with the distinct=1 parameter, all hits containing a
|
479 |
+
* duplicate value for the attributeForDistinct attribute are removed from results. For example,
|
480 |
+
* if the chosen attribute is show_name and several hits have the same value for show_name, then
|
481 |
+
* only the best one is kept and others are removed.
|
482 |
+
*
|
483 |
+
* @return mixed
|
484 |
+
*/
|
485 |
+
public function search($query, $args = null)
|
486 |
+
{
|
487 |
if ($args === null) {
|
488 |
+
$args = [];
|
489 |
}
|
490 |
+
$args['query'] = $query;
|
491 |
+
|
492 |
+
return $this->client->request(
|
493 |
+
$this->context,
|
494 |
+
'POST',
|
495 |
+
'/1/indexes/'.$this->urlIndexName.'/query',
|
496 |
+
[],
|
497 |
+
['params' => $this->client->buildQuery($args)],
|
498 |
+
$this->context->readHostsArray,
|
499 |
+
$this->context->connectTimeout,
|
500 |
+
$this->context->searchTimeout
|
501 |
+
);
|
502 |
}
|
503 |
|
504 |
+
/**
|
505 |
+
* Perform a search with disjunctive facets generating as many queries as number of disjunctive facets.
|
506 |
+
*
|
507 |
+
* @param string $query the query
|
508 |
+
* @param array $disjunctive_facets the array of disjunctive facets
|
509 |
+
* @param array $params a hash representing the regular query parameters
|
510 |
+
* @param array $refinements a hash ("string" -> ["array", "of", "refined", "values"]) representing the current refinements
|
511 |
+
* ex: { "my_facet1" => ["my_value1", ["my_value2"], "my_disjunctive_facet1" => ["my_value1", "my_value2"] }
|
512 |
*
|
513 |
+
* @return mixed
|
514 |
+
*
|
515 |
+
* @throws AlgoliaException
|
516 |
+
* @throws \Exception
|
|
|
517 |
*/
|
518 |
+
public function searchDisjunctiveFaceting($query, $disjunctive_facets, $params = [], $refinements = [])
|
519 |
+
{
|
520 |
+
if (gettype($disjunctive_facets) != 'string' && gettype($disjunctive_facets) != 'array') {
|
521 |
+
throw new AlgoliaException('Argument "disjunctive_facets" must be a String or an Array');
|
522 |
}
|
523 |
+
|
524 |
+
if (gettype($refinements) != 'array') {
|
525 |
+
throw new AlgoliaException('Argument "refinements" must be a Hash of Arrays');
|
526 |
}
|
527 |
|
528 |
+
if (gettype($disjunctive_facets) == 'string') {
|
529 |
+
$disjunctive_facets = explode(',', $disjunctive_facets);
|
530 |
}
|
531 |
|
532 |
+
$disjunctive_refinements = [];
|
533 |
foreach ($refinements as $key => $value) {
|
534 |
if (in_array($key, $disjunctive_facets)) {
|
535 |
$disjunctive_refinements[$key] = $value;
|
536 |
}
|
537 |
}
|
538 |
+
$queries = [];
|
539 |
+
$filters = [];
|
540 |
|
541 |
foreach ($refinements as $key => $value) {
|
542 |
+
$r = array_map(
|
543 |
+
function ($val) use ($key) {
|
544 |
+
return $key.':'.$val;
|
545 |
+
},
|
546 |
+
$value
|
547 |
+
);
|
548 |
|
549 |
if (in_array($key, $disjunctive_refinements)) {
|
550 |
$filter = array_merge($filters, $r);
|
552 |
array_push($filters, $r);
|
553 |
}
|
554 |
}
|
555 |
+
$params['indexName'] = $this->indexName;
|
556 |
+
$params['query'] = $query;
|
557 |
+
$params['facetFilters'] = $filters;
|
558 |
array_push($queries, $params);
|
559 |
foreach ($disjunctive_facets as $disjunctive_facet) {
|
560 |
+
$filters = [];
|
561 |
foreach ($refinements as $key => $value) {
|
562 |
if ($key != $disjunctive_facet) {
|
563 |
+
$r = array_map(
|
564 |
+
function ($val) use ($key) {
|
565 |
+
return $key.':'.$val;
|
566 |
+
},
|
567 |
+
$value
|
568 |
+
);
|
569 |
|
570 |
if (in_array($key, $disjunctive_refinements)) {
|
571 |
$filter = array_merge($filters, $r);
|
574 |
}
|
575 |
}
|
576 |
}
|
577 |
+
$params['indexName'] = $this->indexName;
|
578 |
+
$params['query'] = $query;
|
579 |
+
$params['facetFilters'] = $filters;
|
580 |
+
$params['page'] = 0;
|
581 |
+
$params['hitsPerPage'] = 0;
|
582 |
+
$params['attributesToRetrieve'] = [];
|
583 |
+
$params['attributesToHighlight'] = [];
|
584 |
+
$params['attributesToSnippet'] = [];
|
585 |
+
$params['facets'] = $disjunctive_facet;
|
586 |
+
$params['analytics'] = false;
|
587 |
array_push($queries, $params);
|
588 |
}
|
589 |
$answers = $this->client->multipleQueries($queries);
|
590 |
|
591 |
$aggregated_answer = $answers['results'][0];
|
592 |
+
$aggregated_answer['disjunctiveFacets'] = [];
|
593 |
for ($i = 1; $i < count($answers['results']); $i++) {
|
594 |
foreach ($answers['results'][$i]['facets'] as $key => $facet) {
|
595 |
$aggregated_answer['disjunctiveFacets'][$key] = $facet;
|
603 |
}
|
604 |
}
|
605 |
}
|
606 |
+
|
607 |
return $aggregated_answer;
|
608 |
}
|
609 |
|
610 |
+
/**
|
611 |
+
* Browse all index content.
|
612 |
+
*
|
613 |
+
* @param int $page Pagination parameter used to select the page to retrieve.
|
614 |
+
* Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
|
615 |
+
* @param int $hitsPerPage : Pagination parameter used to select the number of hits per page. Defaults to 1000.
|
616 |
+
*
|
617 |
+
* @return mixed
|
618 |
*
|
619 |
+
* @throws AlgoliaException
|
|
|
|
|
620 |
*/
|
621 |
+
private function doBcBrowse($page = 0, $hitsPerPage = 1000)
|
622 |
+
{
|
623 |
+
return $this->client->request(
|
624 |
+
$this->context,
|
625 |
+
'GET',
|
626 |
+
'/1/indexes/'.$this->urlIndexName.'/browse',
|
627 |
+
['page' => $page, 'hitsPerPage' => $hitsPerPage],
|
628 |
+
null,
|
629 |
+
$this->context->readHostsArray,
|
630 |
+
$this->context->connectTimeout,
|
631 |
+
$this->context->readTimeout
|
632 |
+
);
|
633 |
}
|
634 |
|
635 |
+
/**
|
636 |
* Wait the publication of a task on the server.
|
637 |
* All server task are asynchronous and you can check with this method that the task is published.
|
638 |
*
|
639 |
+
* @param string $taskID the id of the task returned by server
|
640 |
+
* @param int $timeBeforeRetry the time in milliseconds before retry (default = 100ms)
|
641 |
+
*
|
642 |
+
* @return mixed
|
643 |
*/
|
644 |
+
public function waitTask($taskID, $timeBeforeRetry = 100)
|
645 |
+
{
|
646 |
while (true) {
|
647 |
+
$res = $this->getTaskStatus($taskID);
|
648 |
+
if ($res['status'] === 'published') {
|
649 |
return $res;
|
650 |
+
}
|
651 |
usleep($timeBeforeRetry * 1000);
|
652 |
}
|
653 |
}
|
654 |
|
655 |
+
/**
|
656 |
+
* get the status of a task on the server.
|
657 |
+
* All server task are asynchronous and you can check with this method that the task is published or not.
|
658 |
+
*
|
659 |
+
* @param string $taskID the id of the task returned by server
|
660 |
*
|
661 |
+
* @return mixed
|
662 |
*/
|
663 |
+
public function getTaskStatus($taskID)
|
664 |
+
{
|
665 |
+
return $this->client->request(
|
666 |
+
$this->context,
|
667 |
+
'GET',
|
668 |
+
'/1/indexes/'.$this->urlIndexName.'/task/'.$taskID,
|
669 |
+
null,
|
670 |
+
null,
|
671 |
+
$this->context->readHostsArray,
|
672 |
+
$this->context->connectTimeout,
|
673 |
+
$this->context->readTimeout
|
674 |
+
);
|
675 |
}
|
676 |
|
677 |
+
/**
|
678 |
+
* Get settings of this index.
|
679 |
+
*
|
680 |
+
* @return mixed
|
681 |
+
*
|
682 |
+
* @throws AlgoliaException
|
683 |
+
*/
|
684 |
+
public function getSettings()
|
685 |
+
{
|
686 |
+
return $this->client->request(
|
687 |
+
$this->context,
|
688 |
+
'GET',
|
689 |
+
'/1/indexes/'.$this->urlIndexName.'/settings',
|
690 |
+
null,
|
691 |
+
null,
|
692 |
+
$this->context->readHostsArray,
|
693 |
+
$this->context->connectTimeout,
|
694 |
+
$this->context->readTimeout
|
695 |
+
);
|
696 |
+
}
|
697 |
+
|
698 |
+
/**
|
699 |
* This function deletes the index content. Settings and index specific API keys are kept untouched.
|
700 |
+
*
|
701 |
+
* @return mixed
|
702 |
+
*
|
703 |
+
* @throws AlgoliaException
|
704 |
*/
|
705 |
+
public function clearIndex()
|
706 |
+
{
|
707 |
+
return $this->client->request(
|
708 |
+
$this->context,
|
709 |
+
'POST',
|
710 |
+
'/1/indexes/'.$this->urlIndexName.'/clear',
|
711 |
+
null,
|
712 |
+
null,
|
713 |
+
$this->context->writeHostsArray,
|
714 |
+
$this->context->connectTimeout,
|
715 |
+
$this->context->readTimeout
|
716 |
+
);
|
717 |
}
|
718 |
|
719 |
+
/**
|
720 |
+
* Set settings for this index.
|
721 |
+
*
|
722 |
+
* @param mixed $settings the settings object that can contains :
|
723 |
+
* - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default =
|
724 |
+
* 3).
|
725 |
+
* - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default
|
726 |
+
* = 7).
|
727 |
+
* - hitsPerPage: (integer) the number of hits per page (default = 10).
|
728 |
+
* - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
|
729 |
+
* If set to null, all attributes are retrieved.
|
730 |
+
* - attributesToHighlight: (array of strings) default list of attributes to highlight.
|
731 |
+
* If set to null, all indexed attributes are highlighted.
|
732 |
+
* - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the
|
733 |
+
* number of words to return (syntax is attributeName:nbWords). By default no snippet is computed.
|
734 |
+
* If set to null, no snippet is computed.
|
735 |
+
* - attributesToIndex: (array of strings) the list of fields you want to index.
|
736 |
+
* If set to null, all textual and numerical attributes of your objects are indexed, but you
|
737 |
+
* should update it to get optimal results. This parameter has two important uses:
|
738 |
+
* - Limit the attributes to index: For example if you store a binary image in base64, you want to
|
739 |
+
* store it and be able to retrieve it but you don't want to search in the base64 string.
|
740 |
+
* - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in
|
741 |
+
* attributes at the beginning of the list will be considered more important than matches in
|
742 |
+
* attributes further down the list. In one attribute, matching text at the beginning of the
|
743 |
+
* attribute will be considered more important than text after, you can disable this behavior if
|
744 |
+
* you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex:
|
745 |
+
* ["title", "unordered(text)"].
|
746 |
+
* - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
|
747 |
+
* All strings in the attribute selected for faceting are extracted and added as a facet. If set
|
748 |
+
* to null, no attribute is used for faceting.
|
749 |
+
* - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature
|
750 |
+
* is similar to the SQL "distinct" keyword: when enabled in query with the distinct=1 parameter,
|
751 |
+
* all hits containing a duplicate value for this attribute are removed from results. For example,
|
752 |
+
* if the chosen attribute is show_name and several hits have the same value for show_name, then
|
753 |
+
* only the best one is kept and others are removed.
|
754 |
+
* - ranking: (array of strings) controls the way results are sorted.
|
755 |
+
* We have six available criteria:
|
756 |
+
* - typo: sort according to number of typos,
|
757 |
+
* - geo: sort according to decreassing distance when performing a geo-location based search,
|
758 |
+
* - proximity: sort according to the proximity of query words in hits,
|
759 |
+
* - attribute: sort according to the order of attributes defined by attributesToIndex,
|
760 |
+
* - exact:
|
761 |
+
* - if the user query contains one word: sort objects having an attribute that is exactly the
|
762 |
+
* query word before others. For example if you search for the "V" TV show, you want to find it
|
763 |
+
* with the "V" query and avoid to have all popular TV show starting by the v letter before it.
|
764 |
+
* - if the user query contains multiple words: sort according to the number of words that matched
|
765 |
+
* exactly (and not as a prefix).
|
766 |
+
* - custom: sort according to a user defined formula set in **customRanking** attribute.
|
767 |
+
* The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
|
768 |
+
* - customRanking: (array of strings) lets you specify part of the ranking.
|
769 |
+
* The syntax of this condition is an array of strings containing attributes prefixed by asc
|
770 |
+
* (ascending order) or desc (descending order) operator. For example `"customRanking" =>
|
771 |
+
* ["desc(population)", "asc(name)"]`
|
772 |
+
* - queryType: Select how the query words are interpreted, it can be one of the following value:
|
773 |
+
* - prefixAll: all query words are interpreted as prefixes,
|
774 |
+
* - prefixLast: only the last word is interpreted as a prefix (default behavior),
|
775 |
+
* - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
|
776 |
+
* - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in
|
777 |
+
* the query result (default to "<em>").
|
778 |
+
* - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in
|
779 |
+
* the query result (default to "</em>").
|
780 |
+
* - optionalWords: (array of strings) Specify a list of words that should be considered as
|
781 |
+
* optional when found in the query.
|
782 |
+
*
|
783 |
+
* @return mixed
|
784 |
+
*/
|
785 |
+
public function setSettings($settings)
|
786 |
+
{
|
787 |
+
return $this->client->request(
|
788 |
+
$this->context,
|
789 |
+
'PUT',
|
790 |
+
'/1/indexes/'.$this->urlIndexName.'/settings',
|
791 |
+
[],
|
792 |
+
$settings,
|
793 |
+
$this->context->writeHostsArray,
|
794 |
+
$this->context->connectTimeout,
|
795 |
+
$this->context->readTimeout
|
796 |
+
);
|
797 |
}
|
798 |
|
799 |
+
/**
|
800 |
+
* List all existing user keys associated to this index with their associated ACLs.
|
801 |
*
|
802 |
+
* @return mixed
|
803 |
+
*
|
804 |
+
* @throws AlgoliaException
|
805 |
*/
|
806 |
+
public function listUserKeys()
|
807 |
+
{
|
808 |
+
return $this->client->request(
|
809 |
+
$this->context,
|
810 |
+
'GET',
|
811 |
+
'/1/indexes/'.$this->urlIndexName.'/keys',
|
812 |
+
null,
|
813 |
+
null,
|
814 |
+
$this->context->readHostsArray,
|
815 |
+
$this->context->connectTimeout,
|
816 |
+
$this->context->readTimeout
|
817 |
+
);
|
818 |
}
|
819 |
|
820 |
+
/**
|
821 |
+
* Get ACL of a user key associated to this index.
|
822 |
*
|
823 |
+
* @param string $key
|
824 |
+
*
|
825 |
+
* @return mixed
|
826 |
+
*
|
827 |
+
* @throws AlgoliaException
|
828 |
*/
|
829 |
+
public function getUserKeyACL($key)
|
830 |
+
{
|
831 |
+
return $this->client->request(
|
832 |
+
$this->context,
|
833 |
+
'GET',
|
834 |
+
'/1/indexes/'.$this->urlIndexName.'/keys/'.$key,
|
835 |
+
null,
|
836 |
+
null,
|
837 |
+
$this->context->readHostsArray,
|
838 |
+
$this->context->connectTimeout,
|
839 |
+
$this->context->readTimeout
|
840 |
+
);
|
841 |
}
|
842 |
|
843 |
+
/**
|
844 |
+
* Delete an existing user key associated to this index.
|
845 |
+
*
|
846 |
+
* @param string $key
|
847 |
*
|
848 |
+
* @return mixed
|
849 |
+
*
|
850 |
+
* @throws AlgoliaException
|
851 |
*/
|
852 |
+
public function deleteUserKey($key)
|
853 |
+
{
|
854 |
+
return $this->client->request(
|
855 |
+
$this->context,
|
856 |
+
'DELETE',
|
857 |
+
'/1/indexes/'.$this->urlIndexName.'/keys/'.$key,
|
858 |
+
null,
|
859 |
+
null,
|
860 |
+
$this->context->writeHostsArray,
|
861 |
+
$this->context->connectTimeout,
|
862 |
+
$this->context->readTimeout
|
863 |
+
);
|
864 |
}
|
865 |
|
866 |
+
/**
|
867 |
+
* Create a new user key associated to this index.
|
868 |
+
*
|
869 |
+
* @param array $obj can be two different parameters:
|
870 |
+
* The list of parameters for this key. Defined by a array that
|
871 |
+
* can contains the following values:
|
872 |
+
* - acl: array of string
|
873 |
+
* - indices: array of string
|
874 |
+
* - validity: int
|
875 |
+
* - referers: array of string
|
876 |
+
* - description: string
|
877 |
+
* - maxHitsPerQuery: integer
|
878 |
+
* - queryParameters: string
|
879 |
+
* - maxQueriesPerIPPerHour: integer
|
880 |
+
* Or the list of ACL for this key. Defined by an array of NSString that
|
881 |
+
* can contains the following values:
|
882 |
+
* - search: allow to search (https and http)
|
883 |
+
* - addObject: allows to add/update an object in the index (https only)
|
884 |
+
* - deleteObject : allows to delete an existing object (https only)
|
885 |
+
* - deleteIndex : allows to delete index content (https only)
|
886 |
+
* - settings : allows to get index settings (https only)
|
887 |
+
* - editSettings : allows to change index settings (https only)
|
888 |
+
* @param int $validity the number of seconds after which the key will be automatically removed (0 means
|
889 |
+
* no time limit for this key)
|
890 |
+
* @param int $maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
|
891 |
+
* Defaults to 0 (no rate limit).
|
892 |
+
* @param int $maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
|
893 |
+
* Defaults to 0 (unlimited)
|
894 |
+
*
|
895 |
+
* @return mixed
|
896 |
+
*
|
897 |
+
* @throws AlgoliaException
|
898 |
+
*/
|
899 |
+
public function addUserKey($obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
|
900 |
+
{
|
901 |
+
// is dict of value
|
902 |
+
if ($obj !== array_values($obj)) {
|
903 |
$params = $obj;
|
904 |
+
$params['validity'] = $validity;
|
905 |
+
$params['maxQueriesPerIPPerHour'] = $maxQueriesPerIPPerHour;
|
906 |
+
$params['maxHitsPerQuery'] = $maxHitsPerQuery;
|
907 |
} else {
|
908 |
+
$params = [
|
909 |
+
'acl' => $obj,
|
910 |
+
'validity' => $validity,
|
911 |
+
'maxQueriesPerIPPerHour' => $maxQueriesPerIPPerHour,
|
912 |
+
'maxHitsPerQuery' => $maxHitsPerQuery,
|
913 |
+
];
|
914 |
}
|
915 |
+
|
916 |
+
return $this->client->request(
|
917 |
+
$this->context,
|
918 |
+
'POST',
|
919 |
+
'/1/indexes/'.$this->urlIndexName.'/keys',
|
920 |
+
[],
|
921 |
+
$params,
|
922 |
+
$this->context->writeHostsArray,
|
923 |
+
$this->context->connectTimeout,
|
924 |
+
$this->context->readTimeout
|
925 |
+
);
|
926 |
}
|
927 |
|
928 |
+
/**
|
929 |
+
* Update a user key associated to this index.
|
930 |
+
*
|
931 |
+
* @param string $key
|
932 |
+
* @param array $obj can be two different parameters:
|
933 |
+
* The list of parameters for this key. Defined by a array that
|
934 |
+
* can contains the following values:
|
935 |
+
* - acl: array of string
|
936 |
+
* - indices: array of string
|
937 |
+
* - validity: int
|
938 |
+
* - referers: array of string
|
939 |
+
* - description: string
|
940 |
+
* - maxHitsPerQuery: integer
|
941 |
+
* - queryParameters: string
|
942 |
+
* - maxQueriesPerIPPerHour: integer
|
943 |
+
* Or the list of ACL for this key. Defined by an array of NSString that
|
944 |
+
* can contains the following values:
|
945 |
+
* - search: allow to search (https and http)
|
946 |
+
* - addObject: allows to add/update an object in the index (https only)
|
947 |
+
* - deleteObject : allows to delete an existing object (https only)
|
948 |
+
* - deleteIndex : allows to delete index content (https only)
|
949 |
+
* - settings : allows to get index settings (https only)
|
950 |
+
* - editSettings : allows to change index settings (https only)
|
951 |
+
* @param int $validity the number of seconds after which the key will be automatically removed (0 means
|
952 |
+
* no time limit for this key)
|
953 |
+
* @param int $maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
|
954 |
+
* Defaults to 0 (no rate limit).
|
955 |
+
* @param int $maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
|
956 |
+
* Defaults to 0 (unlimited)
|
957 |
+
*
|
958 |
+
* @return mixed
|
959 |
+
*
|
960 |
+
* @throws AlgoliaException
|
961 |
+
*/
|
962 |
+
public function updateUserKey($key, $obj, $validity = 0, $maxQueriesPerIPPerHour = 0, $maxHitsPerQuery = 0)
|
963 |
+
{
|
964 |
+
// is dict of value
|
965 |
+
if ($obj !== array_values($obj)) {
|
966 |
$params = $obj;
|
967 |
+
$params['validity'] = $validity;
|
968 |
+
$params['maxQueriesPerIPPerHour'] = $maxQueriesPerIPPerHour;
|
969 |
+
$params['maxHitsPerQuery'] = $maxHitsPerQuery;
|
970 |
} else {
|
971 |
+
$params = [
|
972 |
+
'acl' => $obj,
|
973 |
+
'validity' => $validity,
|
974 |
+
'maxQueriesPerIPPerHour' => $maxQueriesPerIPPerHour,
|
975 |
+
'maxHitsPerQuery' => $maxHitsPerQuery,
|
976 |
+
];
|
977 |
}
|
978 |
+
|
979 |
+
return $this->client->request(
|
980 |
+
$this->context,
|
981 |
+
'PUT',
|
982 |
+
'/1/indexes/'.$this->urlIndexName.'/keys/'.$key,
|
983 |
+
[],
|
984 |
+
$params,
|
985 |
+
$this->context->writeHostsArray,
|
986 |
+
$this->context->connectTimeout,
|
987 |
+
$this->context->readTimeout
|
988 |
+
);
|
989 |
}
|
990 |
|
991 |
/**
|
992 |
+
* Send a batch request.
|
993 |
+
*
|
994 |
+
* @param array $requests an associative array defining the batch request body
|
995 |
+
*
|
996 |
+
* @return mixed
|
997 |
*/
|
998 |
+
public function batch($requests)
|
999 |
+
{
|
1000 |
+
return $this->client->request(
|
1001 |
+
$this->context,
|
1002 |
+
'POST',
|
1003 |
+
'/1/indexes/'.$this->urlIndexName.'/batch',
|
1004 |
+
[],
|
1005 |
+
$requests,
|
1006 |
+
$this->context->writeHostsArray,
|
1007 |
+
$this->context->connectTimeout,
|
1008 |
+
$this->context->readTimeout
|
1009 |
+
);
|
1010 |
}
|
1011 |
|
1012 |
/**
|
1013 |
+
* Build a batch request.
|
1014 |
+
*
|
1015 |
+
* @param string $action the batch action
|
1016 |
+
* @param array $objects the array of objects
|
1017 |
+
* @param string $withObjectID set an 'objectID' attribute
|
1018 |
+
* @param string $objectIDKey the objectIDKey
|
1019 |
+
*
|
1020 |
* @return array
|
1021 |
*/
|
1022 |
+
private function buildBatch($action, $objects, $withObjectID, $objectIDKey = 'objectID')
|
1023 |
+
{
|
1024 |
+
$requests = [];
|
1025 |
foreach ($objects as $obj) {
|
1026 |
+
$req = ['action' => $action, 'body' => $obj];
|
1027 |
if ($withObjectID && array_key_exists($objectIDKey, $obj)) {
|
1028 |
+
$req['objectID'] = (string) $obj[$objectIDKey];
|
1029 |
}
|
1030 |
array_push($requests, $req);
|
1031 |
}
|
1032 |
+
|
1033 |
+
return ['requests' => $requests];
|
1034 |
+
}
|
1035 |
+
|
1036 |
+
/**
|
1037 |
+
* @param string $query
|
1038 |
+
* @param array|null $params
|
1039 |
+
*
|
1040 |
+
* @return IndexBrowser
|
1041 |
+
*/
|
1042 |
+
private function doBrowse($query, $params = null)
|
1043 |
+
{
|
1044 |
+
return new IndexBrowser($this, $query, $params);
|
1045 |
+
}
|
1046 |
+
|
1047 |
+
/**
|
1048 |
+
* @param string $query
|
1049 |
+
* @param array|null $params
|
1050 |
+
* @param $cursor
|
1051 |
+
*
|
1052 |
+
* @return mixed
|
1053 |
+
*/
|
1054 |
+
public function browseFrom($query, $params = null, $cursor = null)
|
1055 |
+
{
|
1056 |
+
if ($params === null) {
|
1057 |
+
$params = [];
|
1058 |
+
}
|
1059 |
+
foreach ($params as $key => $value) {
|
1060 |
+
if (gettype($value) == 'array') {
|
1061 |
+
$params[$key] = json_encode($value);
|
1062 |
+
}
|
1063 |
+
}
|
1064 |
+
if ($query != null) {
|
1065 |
+
$params['query'] = $query;
|
1066 |
+
}
|
1067 |
+
if ($cursor != null) {
|
1068 |
+
$params['cursor'] = $cursor;
|
1069 |
+
}
|
1070 |
+
|
1071 |
+
return $this->client->request(
|
1072 |
+
$this->context,
|
1073 |
+
'GET',
|
1074 |
+
'/1/indexes/'.$this->urlIndexName.'/browse',
|
1075 |
+
$params,
|
1076 |
+
null,
|
1077 |
+
$this->context->readHostsArray,
|
1078 |
+
$this->context->connectTimeout,
|
1079 |
+
$this->context->readTimeout
|
1080 |
+
);
|
1081 |
+
}
|
1082 |
+
|
1083 |
+
/**
|
1084 |
+
* @param string $name
|
1085 |
+
* @param array $arguments
|
1086 |
+
*
|
1087 |
+
* @return mixed
|
1088 |
+
*/
|
1089 |
+
public function __call($name, $arguments)
|
1090 |
+
{
|
1091 |
+
if ($name !== 'browse') {
|
1092 |
+
return;
|
1093 |
+
}
|
1094 |
+
|
1095 |
+
if (count($arguments) >= 1 && is_string($arguments[0])) {
|
1096 |
+
return call_user_func_array([$this, 'doBrowse'], $arguments);
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
return call_user_func_array([$this, 'doBcBrowse'], $arguments);
|
1100 |
}
|
1101 |
}
|
lib/AlgoliaSearch/IndexBrowser.php
ADDED
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
* Copyright (c) 2013 Algolia
|
5 |
+
* http://www.algolia.com/
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*
|
25 |
+
*
|
26 |
+
*/
|
27 |
+
|
28 |
+
namespace AlgoliaSearch;
|
29 |
+
|
30 |
+
class IndexBrowser implements \Iterator
|
31 |
+
{
|
32 |
+
/**
|
33 |
+
* @var string
|
34 |
+
*/
|
35 |
+
private $query;
|
36 |
+
|
37 |
+
/**
|
38 |
+
* @var int
|
39 |
+
*/
|
40 |
+
private $position;
|
41 |
+
|
42 |
+
/**
|
43 |
+
* @var array
|
44 |
+
*/
|
45 |
+
private $hit;
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @var array
|
49 |
+
*/
|
50 |
+
private $params;
|
51 |
+
|
52 |
+
/**
|
53 |
+
* @var array
|
54 |
+
*/
|
55 |
+
private $answer;
|
56 |
+
|
57 |
+
/**
|
58 |
+
* @var Index
|
59 |
+
*/
|
60 |
+
private $index;
|
61 |
+
|
62 |
+
/**
|
63 |
+
* @var int
|
64 |
+
*/
|
65 |
+
private $cursor;
|
66 |
+
|
67 |
+
/**
|
68 |
+
* IndexBrowser constructor.
|
69 |
+
*
|
70 |
+
* @param Index $index
|
71 |
+
* @param string $query
|
72 |
+
* @param array|null $params
|
73 |
+
* @param int|null $cursor
|
74 |
+
*/
|
75 |
+
public function __construct(Index $index, $query, $params = null, $cursor = null)
|
76 |
+
{
|
77 |
+
$this->index = $index;
|
78 |
+
$this->query = $query;
|
79 |
+
$this->params = $params;
|
80 |
+
|
81 |
+
$this->position = 0;
|
82 |
+
|
83 |
+
$this->doQuery($cursor);
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* @return mixed
|
88 |
+
*/
|
89 |
+
public function current()
|
90 |
+
{
|
91 |
+
return $this->hit;
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* @return mixed
|
96 |
+
*/
|
97 |
+
public function next()
|
98 |
+
{
|
99 |
+
return $this->hit;
|
100 |
+
}
|
101 |
+
|
102 |
+
/**
|
103 |
+
* @return int
|
104 |
+
*/
|
105 |
+
public function key()
|
106 |
+
{
|
107 |
+
return $this->position;
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* @return bool
|
112 |
+
*/
|
113 |
+
public function valid()
|
114 |
+
{
|
115 |
+
do {
|
116 |
+
if ($this->position < count($this->answer['hits'])) {
|
117 |
+
$this->hit = $this->answer['hits'][$this->position];
|
118 |
+
$this->position++;
|
119 |
+
|
120 |
+
return true;
|
121 |
+
}
|
122 |
+
|
123 |
+
if (isset($this->answer['cursor']) && $this->answer['cursor']) {
|
124 |
+
$this->position = 0;
|
125 |
+
|
126 |
+
$this->doQuery($this->answer['cursor']);
|
127 |
+
|
128 |
+
continue;
|
129 |
+
}
|
130 |
+
|
131 |
+
return false;
|
132 |
+
} while (true);
|
133 |
+
}
|
134 |
+
|
135 |
+
public function rewind()
|
136 |
+
{
|
137 |
+
$this->cursor = null;
|
138 |
+
$this->position = 0;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* @return int
|
143 |
+
*/
|
144 |
+
public function cursor()
|
145 |
+
{
|
146 |
+
return $this->answer['cursor'];
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* @param int $cursor
|
151 |
+
*/
|
152 |
+
private function doQuery($cursor = null)
|
153 |
+
{
|
154 |
+
if ($cursor !== null) {
|
155 |
+
$this->params['cursor'] = $cursor;
|
156 |
+
}
|
157 |
+
|
158 |
+
$this->answer = $this->index->browseFrom($this->query, $this->params, $cursor);
|
159 |
+
}
|
160 |
+
}
|
lib/AlgoliaSearch/PlacesIndex.php
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/*
|
4 |
+
* Copyright (c) 2013 Algolia
|
5 |
+
* http://www.algolia.com/
|
6 |
+
*
|
7 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
8 |
+
* of this software and associated documentation files (the "Software"), to deal
|
9 |
+
* in the Software without restriction, including without limitation the rights
|
10 |
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11 |
+
* copies of the Software, and to permit persons to whom the Software is
|
12 |
+
* furnished to do so, subject to the following conditions:
|
13 |
+
*
|
14 |
+
* The above copyright notice and this permission notice shall be included in
|
15 |
+
* all copies or substantial portions of the Software.
|
16 |
+
*
|
17 |
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18 |
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19 |
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20 |
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21 |
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22 |
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
23 |
+
* THE SOFTWARE.
|
24 |
+
*
|
25 |
+
*
|
26 |
+
*/
|
27 |
+
|
28 |
+
namespace AlgoliaSearch;
|
29 |
+
|
30 |
+
class PlacesIndex
|
31 |
+
{
|
32 |
+
private $context;
|
33 |
+
private $client;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @param ClientContext $context
|
37 |
+
* @param Client $client
|
38 |
+
*/
|
39 |
+
public function __construct($context, Client $client)
|
40 |
+
{
|
41 |
+
$this->context = $context;
|
42 |
+
$this->client = $client;
|
43 |
+
}
|
44 |
+
|
45 |
+
/**
|
46 |
+
* @param string $query
|
47 |
+
* @param array|null $args
|
48 |
+
*
|
49 |
+
* @return mixed
|
50 |
+
*
|
51 |
+
* @throws AlgoliaException
|
52 |
+
*/
|
53 |
+
public function search($query, $args = null)
|
54 |
+
{
|
55 |
+
if ($args === null) {
|
56 |
+
$args = [];
|
57 |
+
}
|
58 |
+
$args['query'] = $query;
|
59 |
+
|
60 |
+
return $this->client->request(
|
61 |
+
$this->context,
|
62 |
+
'POST',
|
63 |
+
'/1/places/query',
|
64 |
+
[],
|
65 |
+
['params' => $this->client->buildQuery($args)],
|
66 |
+
$this->context->readHostsArray,
|
67 |
+
$this->context->connectTimeout,
|
68 |
+
$this->context->searchTimeout
|
69 |
+
);
|
70 |
+
}
|
71 |
+
|
72 |
+
public function setExtraHeader($key, $value)
|
73 |
+
{
|
74 |
+
$this->context->setExtraHeader($key, $value);
|
75 |
+
}
|
76 |
+
|
77 |
+
public function getContext()
|
78 |
+
{
|
79 |
+
return $this->context;
|
80 |
+
}
|
81 |
+
}
|
lib/AlgoliaSearch/Version.php
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
<?php
|
|
|
2 |
/*
|
3 |
* Copyright (c) 2013 Algolia
|
4 |
* http://www.algolia.com/
|
@@ -23,13 +24,14 @@
|
|
23 |
*
|
24 |
*
|
25 |
*/
|
|
|
26 |
namespace AlgoliaSearch;
|
27 |
|
28 |
class Version
|
29 |
{
|
30 |
-
const VALUE
|
31 |
|
32 |
-
public static $custom_value
|
33 |
|
34 |
public static function get()
|
35 |
{
|
1 |
<?php
|
2 |
+
|
3 |
/*
|
4 |
* Copyright (c) 2013 Algolia
|
5 |
* http://www.algolia.com/
|
24 |
*
|
25 |
*
|
26 |
*/
|
27 |
+
|
28 |
namespace AlgoliaSearch;
|
29 |
|
30 |
class Version
|
31 |
{
|
32 |
+
const VALUE = '1.8.0';
|
33 |
|
34 |
+
public static $custom_value = '';
|
35 |
|
36 |
public static function get()
|
37 |
{
|
package.xml
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
<?xml version="1.0"?>
|
2 |
<package>
|
3 |
<name>algoliasearch</name>
|
4 |
-
<version>1.5.
|
5 |
<stability>stable</stability>
|
6 |
<license uri="https://github.com/algolia/algoliasearch-magento/blob/master/LICENSE.txt">MIT</license>
|
7 |
<channel>community</channel>
|
@@ -11,14 +11,20 @@
|
|
11 |

|
12 |
This extension replaces Magento's FullText Search module and provide an as-you-type auto-completion menu in your searchbar.
|
13 |
</description>
|
14 |
-
<notes>-
|
15 |
-
-
|
16 |
-
-
|
17 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
<authors><author><name>Algolia Team</name><user>algolia</user><email>support@algolia.com</email></author></authors>
|
19 |
-
<date>2016-
|
20 |
-
<time>
|
21 |
-
<contents><target name="mageetc"><dir name="modules"><file name="Algolia_Algoliasearch.xml" hash="
|
22 |
<compatible/>
|
23 |
<dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
|
24 |
</package>
|
1 |
<?xml version="1.0"?>
|
2 |
<package>
|
3 |
<name>algoliasearch</name>
|
4 |
+
<version>1.5.5</version>
|
5 |
<stability>stable</stability>
|
6 |
<license uri="https://github.com/algolia/algoliasearch-magento/blob/master/LICENSE.txt">MIT</license>
|
7 |
<channel>community</channel>
|
11 |

|
12 |
This extension replaces Magento's FullText Search module and provide an as-you-type auto-completion menu in your searchbar.
|
13 |
</description>
|
14 |
+
<notes>- NEW: Add an option to include data from out-of-stock sub products
|
15 |
+
- NEW: Use secured api keys to only retrieve one group price in the frontend
|
16 |
+
- NEW: Better update strategy to simplify the indexer code and to avoid missing deleted products event
|
17 |
+
- UPDATE: Better handling of include in navigation config
|
18 |
+
- UPDATE: underlying php client
|
19 |
+
- UPDATE: Conditionally render template directives
|
20 |
+
- UPDATE: Make sub product skus searchable
|
21 |
+
- FIX: slaves creation issue
|
22 |
+
- FIX: small price issue
|
23 |
+
- FIX: fallback to default search in case there is a error from the api</notes>
|
24 |
<authors><author><name>Algolia Team</name><user>algolia</user><email>support@algolia.com</email></author></authors>
|
25 |
+
<date>2016-04-28</date>
|
26 |
+
<time>15:22:48</time>
|
27 |
+
<contents><target name="mageetc"><dir name="modules"><file name="Algolia_Algoliasearch.xml" hash="9e57a7153901c3302833296623bec4d7"/></dir></target><target name="magecommunity"><dir name="Algolia"><dir name="Algoliasearch"><dir name="Block"><dir name="System"><dir name="Config"><dir name="Form"><dir name="Field"><file name="Custompages.php" hash="f87a9cf7b5559717cd9d6570374dcda7"/><file name="Customrankingcategory.php" hash="6d9575c12dbaecf9054de1cf12736025"/><file name="Customrankingproduct.php" hash="6d1b145e37c4f22d5b56f5783ac47511"/><file name="Customsortorder.php" hash="786c8f8fca2e4b41b8732f5fe270491b"/><file name="Customsortordercategory.php" hash="9908ea7f463138d3047c51b98591db9c"/><file name="Customsortorderproduct.php" hash="ee62901a3911bb7784467e1ca5cd8e84"/><file name="Facets.php" hash="b8c6217811a1c9afd64119d2b021cc5f"/><file name="Sections.php" hash="7aa62da4fb45f693bf81ad4aa0421ac3"/><file name="Select.php" hash="6e3cb4c1798775048bebbdc878e90aa9"/><file name="Sorts.php" hash="fede73c4ecbe39bf0344fbf6de46ed95"/></dir></dir></dir></dir></dir><dir name="Helper"><file name="Algoliahelper.php" hash="19d6a822a08a73a16506bd37062c226a"/><file name="Config.php" hash="63fd2579161ad0c57e914f27f36710e6"/><file name="Data.php" hash="e6732c8c949bc77ec5172552a3d473f8"/><dir name="Entity"><file name="Additionalsectionshelper.php" hash="a543c603d7a5ad19e5cade0ff569c829"/><file name="Categoryhelper.php" hash="f67337a249b04028de1fafc9acf83cfd"/><file name="Helper.php" hash="39eca180885e5ed15d7a7ffbb1cae297"/><file name="Pagehelper.php" hash="4c92750f40f3fc86f470e758e0cade75"/><file name="Producthelper.php" hash="a4281d4f43acd49879ed8e9c60739c3c"/><file name="Suggestionhelper.php" hash="b3eaf3b253376b2387dfaf65570bc5e8"/></dir><file name="Image.php" hash="876292c0612fa87194d1657a7facb916"/><file name="Logger.php" hash="08bc6a4e377b60ca13b96a7982a8fcb5"/></dir><dir name="Model"><dir name="Indexer"><file name="Algolia.php" hash="64a3247da61c4adb4e07e8e1e4863fcf"/><file name="Algoliaadditionalsections.php" hash="649d4468a899f055d33dc959e758a688"/><file name="Algoliacategories.php" hash="8917dcd32ba581c8a5477f373ec17268"/><file name="Algoliapages.php" hash="22230967d949c89f92a794bd46fc6fa9"/><file name="Algoliaqueuerunner.php" hash="dbad890d433eee91732f44e36b6ac7e2"/><file name="Algoliasuggestions.php" hash="7a6702e8299f646356749fed8f57ca0a"/></dir><file name="Observer.php" hash="73ee1e9eb227085407bd130a4665a3ab"/><file name="Queue.php" hash="c3ec68c4441305ff9acc033d123a7823"/><dir name="Resource"><file name="Engine.php" hash="6030a8dbb933ce9f1c423ec1ef078f9d"/><dir name="Fulltext"><file name="Collection.php" hash="e75eea1119840095486710d438d9ca1b"/></dir><file name="Fulltext.php" hash="014ac885c31abeaa8e8ec56bbe54ff3d"/></dir><dir name="System"><file name="Imagetype.php" hash="5fd4dbd98818a15b0253c5988a65a785"/><file name="Removewords.php" hash="25408eb3e3d278da2f2ec1a6b6e6d8e8"/></dir></dir><dir name="etc"><file name="adminhtml.xml" hash="ea4176ed43885e531f90d1f5369f29ee"/><file name="config.xml" hash="3caf38ac6af0159e4d3a9157e8c78ccd"/><file name="system.xml" hash="2d26a885e9e72f5687f2e4b6cabe0a2d"/></dir><dir name="sql"><dir name="algoliasearch_setup"><file name="mysql4-install-0.1.0.php" hash="fffd964f9c60be7909ec216260c37ba0"/><file name="mysql4-upgrade-0.1.0-1.4.8.php" hash="5224f8f1031a0659c64d393392a7f199"/><file name="mysql4-upgrade-1.4.8-1.5.0.php" hash="fa8be181b2d43e955e75dce1ed4a19ca"/></dir></dir></dir></dir></target><target name="magedesign"><dir name="frontend"><dir name="base"><dir name="default"><dir name="layout"><file name="algoliasearch.xml" hash="28b9676bd76adfdbb9eecb54ce9c5f02"/></dir><dir name="template"><dir name="algoliasearch"><file name="beforecontent.phtml" hash="19f2ee9532f4e46c77ade0157976b780"/><file name="beforetopsearch.phtml" hash="d4d1444b59e94e6ee7680aa922130390"/><file name="frontjs.phtml" hash="7d2cdf7bb5c2f47c0f118eac0ba1ead1"/><file name="topsearch.phtml" hash="1647543f31b419380c15b48817e5bfb3"/></dir></dir></dir></dir></dir><dir name="adminhtml"><dir name="default"><dir name="default"><dir name="template"><dir name="algoliasearch"><file name="adminjs.phtml" hash="a5842a5c608c1496060dd4610c5c774f"/></dir></dir><dir name="layout"><file name="algoliasearch.xml" hash="312ecb88cb4ae694d098bda8f580d29e"/></dir></dir></dir></dir></target><target name="mageskin"><dir name="frontend"><dir name="base"><dir name="default"><dir name="algoliasearch"><file name="algolia-admin-menu.png" hash="9202a559c30a43d4d4bbc2f9ee774fd9"/><file name="algolia-logo.png" hash="190884b3e8652f3517754ae15bca31de"/><file name="algoliasearch.css" hash="d7c32abcd1151b88c156bd92253ec5f9"/><file name="cross-circle.png" hash="a9ae2fa7ec458ffaf7c32613ca9593da"/><file name="cross.png" hash="a046cd95cba9761c824063fbd30a26b5"/><dir name="images"><file name="ui-bg_diagonals-thick_18_b81900_40x40.png" hash="62568c006bb1066f40fd5f9cfe4489be"/><file name="ui-bg_diagonals-thick_20_666666_40x40.png" hash="406541454ec466d93217826588335194"/><file name="ui-bg_flat_10_000000_40x100.png" hash="85243ed808c91ae60d33bda3a6bdee3c"/><file name="ui-bg_glass_100_f6f6f6_1x400.png" hash="f912ffca9b1919ab26c64cf1332c5322"/><file name="ui-bg_glass_100_fdf5ce_1x400.png" hash="a9b41e3f4db0fb9be1cd2c649deb253f"/><file name="ui-bg_glass_65_ffffff_1x400.png" hash="ff9e9b45e03f11808144324fd5350612"/><file name="ui-bg_gloss-wave_35_f6a828_500x100.png" hash="08ece8908c07b1c0d18b8db076ff50fc"/><file name="ui-bg_highlight-soft_100_eeeeee_1x100.png" hash="72fe4b0e1bbb83dfd6787989d3583fbe"/><file name="ui-bg_highlight-soft_75_ffe45c_1x100.png" hash="81262299ac7f591fd1763c1ccee0691f"/><file name="ui-icons_222222_256x240.png" hash="3a3c5468f484f07ac4a320d9e22acb8c"/><file name="ui-icons_228ef1_256x240.png" hash="92b29683b6a48eae7de7eb4b1cfa039c"/><file name="ui-icons_ef8c08_256x240.png" hash="f492970693640894fb54166c75dd2925"/><file name="ui-icons_ffd27a_256x240.png" hash="dda1b6f694b0d196aefc66a1d6d758f6"/><file name="ui-icons_ffffff_256x240.png" hash="41612b0f4a034424f8321c9f824a94da"/></dir></dir></dir></dir></dir></target><target name="mage"><dir name="js"><dir name="algoliasearch"><file name="Function.prototype.bind.js" hash="eb15975feb0cc976face88cb194294ae"/><file name="admin_scripts.js" hash="877a9fcbc5d3d627772464a9311ae0b3"/><file name="algoliaAdminBundle.min.js" hash="26d145a6c347021083bc140a0d07f0bb"/><file name="algoliaAdminBundle.min.js.map" hash="39d5d78a949a11321a596561d95ef01e"/><file name="algoliaBundle.min.js" hash="a5cf86fa138eccdd8aa3d39b170a1687"/><file name="algoliaBundle.min.js.map" hash="95baad19b0b4f8de10eee092ef060174"/></dir></dir><dir name="lib"><dir name="AlgoliaSearch"><file name="AlgoliaException.php" hash="ad654ffdc97c62dc798983cacddc17fd"/><file name="Client.php" hash="4e8312c04d8e13d9fed49416b20d46b8"/><file name="ClientContext.php" hash="8d23dddb0b737db86505c254ae0fbc22"/><file name="Index.php" hash="182e30382a5484a297732f88d4355e59"/><file name="IndexBrowser.php" hash="6e9b065d97a5ac67a120a451024c7c17"/><file name="PlacesIndex.php" hash="d5cbb714b558097344cbbad9f2cc1bb6"/><file name="Version.php" hash="dbfcd97d08bf3043f183fc8cee18d8e6"/><dir name="resources"><file name="ca-bundle.crt" hash="47961e7ef15667c93cd99be01b51f00a"/></dir></dir></dir></target></contents>
|
28 |
<compatible/>
|
29 |
<dependencies><required><php><min>5.2.0</min><max>6.0.0</max></php></required></dependencies>
|
30 |
</package>
|