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>
|
