diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f425b09..9aa69aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ ## CHANGE LOG +### 1.11.0 + +## FEATURES +- Added option to turn on autocomplete's `debug` option from configuration (#865) +- The extension now displays the right image for a color variant depending on search query or selected color filter (#883) + +## UPDATES +- Added CSS class for proper function of collapsible IS widgets (#859) +- Changed Magento archive URLs in dev containers (#874) +- Updated Magento 1.9.3 version to the latest one in dev container (#882) +- Optimization of `getPopularQueries()` method (#888) + +## FIXES +- Fixed the hardcode admin URL for fetching queue info (#854) +- Fixed issue when some attributes weren't set as retrievable when custom groups were enabled (#856) +- Fixed back button which returned all products on category pages (#852) +- Removed not-necessary additional query on category page (#852) +- Fixed displayed link to analytics documentation (#869) +- Fixed store specific facets (#868) +- Fixed option to disable the module by disabling it's output (#866) + + ### 1.10.0 #### FEATURES diff --git a/README.md b/README.md index be6645ed..facd5cb8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ Algolia Search for Magento 1.6+ ================== -![Latest version](https://img.shields.io/badge/latest-1.10.0-green.svg) +![Latest version](https://img.shields.io/badge/latest-1.11.0-green.svg) + [![Build Status](https://travis-ci.org/algolia/algoliasearch-magento.svg?branch=master)](https://travis-ci.org/algolia/algoliasearch-magento) ![PHP >= 5.3](https://img.shields.io/badge/php-%3E=5.3-green.svg) diff --git a/app/code/community/Algolia/Algoliasearch/Helper/Config.php b/app/code/community/Algolia/Algoliasearch/Helper/Config.php index 9c604fb1..bffbe856 100644 --- a/app/code/community/Algolia/Algoliasearch/Helper/Config.php +++ b/app/code/community/Algolia/Algoliasearch/Helper/Config.php @@ -13,6 +13,7 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract const SEARCH_ONLY_API_KEY = 'algoliasearch/credentials/search_only_api_key'; const INDEX_PREFIX = 'algoliasearch/credentials/index_prefix'; const IS_INSTANT_ENABLED = 'algoliasearch/credentials/is_instant_enabled'; + const USE_ADAPTIVE_IMAGE = 'algoliasearch/credentials/use_adaptive_image'; const REPLACE_CATEGORIES = 'algoliasearch/instant/replace_categories'; const INSTANT_SELECTOR = 'algoliasearch/instant/instant_selector'; @@ -30,6 +31,7 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract const MIN_NUMBER_OF_RESULTS = 'algoliasearch/autocomplete/min_number_of_results'; const DISPLAY_SUGGESTIONS_CATEGORIES = 'algoliasearch/autocomplete/display_categories_with_suggestions'; const RENDER_TEMPLATE_DIRECTIVES = 'algoliasearch/autocomplete/render_template_directives'; + const AUTOCOMPLETE_MENU_DEBUG = 'algoliasearch/autocomplete/debug'; const NUMBER_OF_PRODUCT_RESULTS = 'algoliasearch/products/number_product_results'; const PRODUCT_ATTRIBUTES = 'algoliasearch/products/product_additional_attributes'; @@ -48,6 +50,9 @@ class Algolia_Algoliasearch_Helper_Config extends Mage_Core_Helper_Abstract const IS_ACTIVE = 'algoliasearch/queue/active'; const NUMBER_OF_ELEMENT_BY_PAGE = 'algoliasearch/queue/number_of_element_by_page'; const NUMBER_OF_JOB_TO_RUN = 'algoliasearch/queue/number_of_job_to_run'; + const RETRY_LIMIT = 'algoliasearch/queue/number_of_retries'; + const CHECK_PRICE_INDEX = 'algoliasearch/queue/check_price_index'; + const CHECK_STOCK_INDEX = 'algoliasearch/queue/check_stock_index'; const XML_PATH_IMAGE_WIDTH = 'algoliasearch/image/width'; const XML_PATH_IMAGE_HEIGHT = 'algoliasearch/image/height'; @@ -259,6 +264,21 @@ public function isQueueActive($storeId = null) return Mage::getStoreConfigFlag(self::IS_ACTIVE, $storeId); } + public function shouldCheckPriceIndex($storeId = null) + { + return Mage::getStoreConfigFlag(self::CHECK_PRICE_INDEX, $storeId); + } + + public function shouldCheckStockIndex($storeId = null) + { + return Mage::getStoreConfigFlag(self::CHECK_STOCK_INDEX, $storeId); + } + + public function getRetryLimit($storeId = null) + { + return (int) Mage::getStoreConfig(self::RETRY_LIMIT, $storeId); + } + public function getRemoveWordsIfNoResult($storeId = null) { return Mage::getStoreConfig(self::REMOVE_IF_NO_RESULT, $storeId); @@ -294,6 +314,11 @@ public function isInstantEnabled($storeId = null) return Mage::getStoreConfigFlag(self::IS_INSTANT_ENABLED, $storeId); } + public function useAdaptiveImage($storeId = null) + { + return Mage::getStoreConfigFlag(self::USE_ADAPTIVE_IMAGE, $storeId); + } + public function getInstantSelector($storeId = null) { return Mage::getStoreConfig(self::INSTANT_SELECTOR, $storeId); @@ -315,6 +340,11 @@ public function getRenderTemplateDirectives($storeId = null) return Mage::getStoreConfigFlag(self::RENDER_TEMPLATE_DIRECTIVES, $storeId); } + public function isAutocompleteDebugEnabled($storeId = null) + { + return Mage::getStoreConfigFlag(self::AUTOCOMPLETE_MENU_DEBUG, $storeId); + } + public function getSortingIndices($storeId = null) { /** @var Algolia_Algoliasearch_Helper_Entity_Producthelper $product_helper */ @@ -424,7 +454,7 @@ public function getAttributesToRetrieve($group_id) $attributes = array_unique($attributes); - return $attributes; + return array_values($attributes); } public function getCategoryAdditionalAttributes($storeId = null) @@ -497,13 +527,17 @@ public function getCurrency($storeId = null) public function getPopularQueries($storeId = null) { + if (!$this->isInstantEnabled($storeId) || !$this->showSuggestionsOnNoResultsPage($storeId)) { + return array(); + } + if ($storeId === null) { $storeId = Mage::app()->getStore()->getId(); } - /** @var Algolia_Algoliasearch_Helper_Entity_Suggestionhelper $suggestion_helper */ - $suggestion_helper = Mage::helper('algoliasearch/entity_suggestionhelper'); - $popularQueries = $suggestion_helper->getPopularQueries($storeId); + /** @var Algolia_Algoliasearch_Helper_Entity_Suggestionhelper $suggestionHelper */ + $suggestionHelper = Mage::helper('algoliasearch/entity_suggestionhelper'); + $popularQueries = $suggestionHelper->getPopularQueries($storeId); return $popularQueries; } diff --git a/app/code/community/Algolia/Algoliasearch/Helper/Data.php b/app/code/community/Algolia/Algoliasearch/Helper/Data.php index a6066605..8ffa3a16 100644 --- a/app/code/community/Algolia/Algoliasearch/Helper/Data.php +++ b/app/code/community/Algolia/Algoliasearch/Helper/Data.php @@ -185,7 +185,7 @@ public function rebuildStoreAdditionalSectionsIndex($storeId) } } - public function rebuildStorePageIndex($storeId) + public function rebuildStorePageIndex($storeId, $pageIds = null) { if ($this->config->isEnabledBackend($storeId) === false) { $this->logger->log('INDEXING IS DISABLED FOR '.$this->logger->getStoreName($storeId)); @@ -193,19 +193,24 @@ public function rebuildStorePageIndex($storeId) return; } - $emulationInfo = $this->startEmulation($storeId); + $shouldUseTmpIndex = ($pageIds === null); - $index_name = $this->page_helper->getIndexName($storeId); + $emulationInfo = $this->startEmulation($storeId); - $pages = $this->page_helper->getPages($storeId); + $indexName = $this->page_helper->getIndexName($storeId, $shouldUseTmpIndex); + /** @var array $pages */ + $pages = $this->page_helper->getPages($storeId, $pageIds); foreach (array_chunk($pages, 100) as $chunk) { - $this->algolia_helper->addObjects($chunk, $index_name.'_tmp'); + $this->algolia_helper->addObjects($chunk, $indexName); } - $this->algolia_helper->moveIndex($index_name.'_tmp', $index_name); + if ($shouldUseTmpIndex === true) { + $finalIndexName = $this->page_helper->getIndexName($storeId); - $this->algolia_helper->setSettings($index_name, $this->page_helper->getIndexSettings($storeId)); + $this->algolia_helper->moveIndex($indexName, $finalIndexName); + $this->algolia_helper->setSettings($finalIndexName, $this->page_helper->getIndexSettings($storeId)); + } $this->stopEmulation($emulationInfo); } @@ -444,6 +449,19 @@ protected function getProductsRecords($storeId, $collection, $potentiallyDeleted $potentiallyDeletedProductsIds = array(); } + if (method_exists('Mage', 'getEdition') === true && Mage::getEdition() === Mage::EDITION_ENTERPRISE) { + $productIds = array(); + + /** @var Mage_Catalog_Model_Product $products */ + foreach ($collection as $products) { + $productIds[] = $products->getId(); + } + + /** @var Algolia_Algoliasearch_Helper_IndexChecker $indexChecker */ + $indexChecker = Mage::helper('algoliasearch/indexChecker'); + $indexChecker->checkIndexers($storeId, $productIds); + } + $this->logger->start('CREATE RECORDS '.$this->logger->getStoreName($storeId)); $this->logger->log(count($collection).' product records to create'); diff --git a/app/code/community/Algolia/Algoliasearch/Helper/Entity/Helper.php b/app/code/community/Algolia/Algoliasearch/Helper/Entity/Helper.php index 9b622b86..e75b46bf 100644 --- a/app/code/community/Algolia/Algoliasearch/Helper/Entity/Helper.php +++ b/app/code/community/Algolia/Algoliasearch/Helper/Entity/Helper.php @@ -272,6 +272,8 @@ public static function getStores($store_id) $store_ids[] = $store->getId(); } } + } elseif (is_array($store_id)) { + return $store_id; } else { $store_ids = array($store_id); } diff --git a/app/code/community/Algolia/Algoliasearch/Helper/Entity/Pagehelper.php b/app/code/community/Algolia/Algoliasearch/Helper/Entity/Pagehelper.php index 208e7bf5..84c1a806 100644 --- a/app/code/community/Algolia/Algoliasearch/Helper/Entity/Pagehelper.php +++ b/app/code/community/Algolia/Algoliasearch/Helper/Entity/Pagehelper.php @@ -21,7 +21,7 @@ public function getIndexSettings($storeId) return $indexSettings; } - public function getPages($storeId) + public function getPages($storeId, $pageIds = null) { /** @var Mage_Cms_Model_Page $cmsPage */ $cmsPage = Mage::getModel('cms/page'); @@ -29,6 +29,10 @@ public function getPages($storeId) /** @var Mage_Cms_Model_Resource_Page_Collection $pages */ $pages = $cmsPage->getCollection()->addStoreFilter($storeId)->addFieldToFilter('is_active', 1); + if ($pageIds && count($pageIds) > 0) { + $pages = $pages->addFieldToFilter('page_id', array('in' => $pageIds)); + } + Mage::dispatchEvent('algolia_after_pages_collection_build', array('store' => $storeId, 'collection' => $pages)); $ids = $pages->toOptionArray(); @@ -85,4 +89,17 @@ public function getPages($storeId) return $pages; } + + public function shouldIndexPages($storeId) + { + $autocompleteSections = $this->config->getAutocompleteSections($storeId); + + foreach ($autocompleteSections as $section) { + if ($section['name'] === 'pages') { + return true; + } + } + + return false; + } } diff --git a/app/code/community/Algolia/Algoliasearch/Helper/Entity/Producthelper.php b/app/code/community/Algolia/Algoliasearch/Helper/Entity/Producthelper.php index 7ea44faa..60dd5294 100644 --- a/app/code/community/Algolia/Algoliasearch/Helper/Entity/Producthelper.php +++ b/app/code/community/Algolia/Algoliasearch/Helper/Entity/Producthelper.php @@ -229,7 +229,7 @@ public function setSettings($storeId, $saveToTmpIndicesToo = false) $customRankingAttributes = array(); - $facets = $this->config->getFacets(); + $facets = $this->config->getFacets($storeId); /** @var Mage_Directory_Model_Currency $directoryCurrency */ $directoryCurrency = Mage::getModel('directory/currency'); @@ -291,7 +291,7 @@ public function setSettings($storeId, $saveToTmpIndicesToo = false) /* * Handle replicas */ - $sorting_indices = $this->config->getSortingIndices(); + $sorting_indices = $this->config->getSortingIndices($storeId); if (count($sorting_indices) > 0) { $replicas = array(); @@ -905,15 +905,16 @@ public function getObject(Mage_Catalog_Model_Product $product) if ($attribute_resource) { $attribute_resource->setStoreId($product->getStoreId()); + $values = array(); + $subProductImages = array(); + /** * if $value is missing or if the attribute is SKU, * use values from child products. */ if (($value === null || 'sku' == $attribute_name) && ($type == 'configurable' || $type == 'grouped' || $type == 'bundle')) { - if ($value === null) { - $values = array(); - } else { - $values = array($this->getValueOrValueText($product, $attribute_name, $attribute_resource)); + if ($value !== null) { + $values[] = $this->getValueOrValueText($product, $attribute_name, $attribute_resource); } $all_sub_products_out_of_stock = true; @@ -931,7 +932,23 @@ public function getObject(Mage_Catalog_Model_Product $product) $value = $sub_product->getData($attribute_name); if ($value) { - $values[] = $this->getValueOrValueText($sub_product, $attribute_name, $attribute_resource); + $textValue = $this->getValueOrValueText($sub_product, $attribute_name, $attribute_resource); + + $values[] = $textValue; + + if (mb_strtolower($attribute_name, 'utf-8') === 'color') { + $image = $imageHelper->init($sub_product, $this->config->getImageType()) + ->resize($this->config->getImageWidth(), + $this->config->getImageHeight()); + + try { + $textValueInLower = mb_strtolower($textValue, 'utf-8'); + $subProductImages[$textValueInLower] = $image->toString(); + } catch (\Exception $e) { + $this->logger->log($e->getMessage()); + $this->logger->log($e->getTraceAsString()); + } + } } } } @@ -940,6 +957,10 @@ public function getObject(Mage_Catalog_Model_Product $product) $customData[$attribute_name] = array_values(array_unique($values, SORT_REGULAR)); } + if (empty($subProductImages) === false) { + $customData['images_data'] = $subProductImages; + } + // Set main product out of stock if all // sub-products are out of stock. if ($customData['in_stock'] && $all_sub_products_out_of_stock) { diff --git a/app/code/community/Algolia/Algoliasearch/Helper/IndexChecker.php b/app/code/community/Algolia/Algoliasearch/Helper/IndexChecker.php new file mode 100644 index 00000000..a9c1a6df --- /dev/null +++ b/app/code/community/Algolia/Algoliasearch/Helper/IndexChecker.php @@ -0,0 +1,149 @@ +configHelper = Mage::helper('algoliasearch/config'); + } + + /** + * @param $storeId + * @param $productIds + * @throws Exception + */ + public function checkIndexers($storeId, $productIds) + { + if ($this->configHelper->isQueueActive($storeId) === false) { + return; + } + + if (!is_array($productIds)) { + $productIds = array($productIds); + } + + if (empty($productIds)) { + return; + } + + $pendingProductIds = $this->getPendingProductIds($storeId); + + if (empty($pendingProductIds)) { + return; + } + + foreach ($productIds as $id) { + if (isset($this->pendingProductIds[$id])) { + // Throw an exception - this exception is caught in Queue class and it'll retry next time Algolia is processed. + throw new Algolia_Algoliasearch_Model_Exception_IndexPendingException('Reindexing (price / stock) is still pending for entity ID ' . $id); + } + } + } + + private function getPendingProductIds($storeId) + { + if (isset($this->pendingProductIds) === false) { + $priceIndexes = $this->pendingPriceIndex($storeId); + $stockIndexes = $this->pendingStockIndex($storeId); + + $pendingProductIds = array_unique(array_merge($priceIndexes, $stockIndexes)); + $this->pendingProductIds = array_flip($pendingProductIds); + } + + return $this->pendingProductIds; + } + + /** + * Find all productId's pending a price reindex. + * + * @param $storeId + * @return array + */ + private function pendingPriceIndex($storeId) + { + $returnArray = array(); + + if ($this->configHelper->shouldCheckPriceIndex($storeId)) { + $maxVersion = $this->findLatestVersion('catalog_product_index_price_cl'); + $returnArray = $this->findProductIdsPending('catalog_product_index_price_cl', 'entity_id', $maxVersion); + } + + return $returnArray; + } + + /** + * Find all productId's pending a stock reindex. + * + * @param $storeId + * @return array + */ + private function pendingStockIndex($storeId) + { + $returnArray = array(); + + if ($this->configHelper->shouldCheckStockIndex($storeId)) { + $maxVersion = $this->findLatestVersion('cataloginventory_stock_status_cl'); + $returnArray = $this->findProductIdsPending('cataloginventory_stock_status_cl', 'product_id', $maxVersion); + } + + return $returnArray; + } + + /** + * Find all product ID's who have a version ID greater then the maxVersion. + * + * @param $changeLogTable + * @param $idColumn + * @param $maxVersion + * + * @return mixed - array of ID's + */ + private function findProductIdsPending($changeLogTable, $idColumn, $maxVersion) + { + $connection = $this->getConnection(); + $select = $connection->select() + ->distinct() + ->from($changeLogTable, $idColumn) + ->join(array('catalog_product_entity' => 'catalog_product_entity'), "$changeLogTable.$idColumn = catalog_product_entity.entity_id", array()) + ->where('version_id > ?', $maxVersion); + + return $connection->fetchCol($select); + } + + /** + * Find the latest version ID for a particular changeLog + * + * @param $changeLogTable + * @return mixed + */ + private function findLatestVersion($changeLogTable) + { + $connection = $this->getConnection(); + $select = $connection->select() + ->from('enterprise_mview_metadata', 'version_id') + ->where('changelog_name = ?', $changeLogTable); + + return $connection->fetchOne($select); + } + + private function getConnection() + { + if (isset($this->dbConnection) === false) { + /** @var Mage_Core_Model_Resource $coreResource */ + $coreResource = Mage::getSingleton('core/resource'); + $this->dbConnection = $coreResource->getConnection('core_read'); + } + + return $this->dbConnection; + } +} diff --git a/app/code/community/Algolia/Algoliasearch/Model/Exception/IndexPendingException.php b/app/code/community/Algolia/Algoliasearch/Model/Exception/IndexPendingException.php new file mode 100644 index 00000000..e3d360ba --- /dev/null +++ b/app/code/community/Algolia/Algoliasearch/Model/Exception/IndexPendingException.php @@ -0,0 +1,6 @@ +config->isModuleOutputEnabled() === false) { + return; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { if (self::$credential_error === false) { /** @var Mage_Adminhtml_Model_Session $session */ @@ -205,6 +209,10 @@ protected function _processEvent(Mage_Index_Model_Event $event) */ public function reindexAll() { + if ($this->config->isModuleOutputEnabled() === false) { + return $this; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { /** @var Mage_Adminhtml_Model_Session $session */ $session = Mage::getSingleton('adminhtml/session'); @@ -212,7 +220,7 @@ public function reindexAll() $this->logger->log('ERROR Credentials not configured correctly'); - return; + return $this; } $this->logger->start('PRODUCTS FULL REINDEX'); diff --git a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaadditionalsections.php b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaadditionalsections.php index 6ccef085..89c74e81 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaadditionalsections.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaadditionalsections.php @@ -68,12 +68,16 @@ protected function _processEvent(Mage_Index_Model_Event $event) */ public function reindexAll() { + if ($this->config->isModuleOutputEnabled() === false) { + return $this; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { /** @var Mage_Adminhtml_Model_Session $session */ $session = Mage::getSingleton('adminhtml/session'); $session->addError('Algolia reindexing failed: You need to configure your Algolia credentials in System > Configuration > Algolia Search.'); - return; + return $this; } $this->engine->rebuildAdditionalSections(); diff --git a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliacategories.php b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliacategories.php index 7806bf9e..47c455f4 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliacategories.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliacategories.php @@ -111,6 +111,10 @@ protected function _registerCatalogCategoryEvent(Mage_Index_Model_Event $event) protected function _processEvent(Mage_Index_Model_Event $event) { + if ($this->config->isModuleOutputEnabled() === false) { + return; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { if (self::$credential_error === false) { /** @var Mage_Adminhtml_Model_Session $session */ @@ -172,6 +176,10 @@ protected function _processEvent(Mage_Index_Model_Event $event) */ public function reindexAll() { + if ($this->config->isModuleOutputEnabled() === false) { + return $this; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { /** @var Mage_Adminhtml_Model_Session $session */ $session = Mage::getSingleton('adminhtml/session'); diff --git a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliapages.php b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliapages.php index f2c38c24..4428ad44 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliapages.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliapages.php @@ -68,12 +68,16 @@ protected function _processEvent(Mage_Index_Model_Event $event) */ public function reindexAll() { + if ($this->config->isModuleOutputEnabled() === false) { + return $this; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { /** @var Mage_Adminhtml_Model_Session $session */ $session = Mage::getSingleton('adminhtml/session'); $session->addError('Algolia reindexing failed: You need to configure your Algolia credentials in System > Configuration > Algolia Search.'); - return; + return $this; } $this->engine->rebuildPages(); diff --git a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaqueuerunner.php b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaqueuerunner.php index 083ccce6..1c4ae6a3 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaqueuerunner.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliaqueuerunner.php @@ -68,7 +68,7 @@ public function reindexAll() $session = Mage::getSingleton('adminhtml/session'); $session->addError('Algolia reindexing failed: You need to configure your Algolia credentials in System > Configuration > Algolia Search.'); - return; + return $this; } $this->queue->runCron(); diff --git a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliasuggestions.php b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliasuggestions.php index daba6f2c..8f2e5b97 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliasuggestions.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Indexer/Algoliasuggestions.php @@ -72,12 +72,16 @@ protected function _processEvent(Mage_Index_Model_Event $event) */ public function reindexAll() { + if ($this->config->isModuleOutputEnabled() === false) { + return $this; + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { /** @var Mage_Adminhtml_Model_Session $session */ $session = Mage::getSingleton('adminhtml/session'); $session->addError('Algolia reindexing failed: You need to configure your Algolia credentials in System > Configuration > Algolia Search.'); - return; + return $this; } $this->engine->rebuildSuggestions(); diff --git a/app/code/community/Algolia/Algoliasearch/Model/Observer.php b/app/code/community/Algolia/Algoliasearch/Model/Observer.php index b4f60695..f52a62ae 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Observer.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Observer.php @@ -91,6 +91,20 @@ public function saveProduct(Varien_Event_Observer $observer) Algolia_Algoliasearch_Model_Indexer_Algolia::$product_categories[$product->getId()] = $product->getCategoryIds(); } + public function savePage(Varien_Event_Observer $observer) + { + /** @var Mage_Cms_Model_Page $page */ + $page = $observer->getDataObject(); + $page = Mage::getModel('cms/page')->load($page->getId()); + + $storeIds = $page->getStoreId(); + + /** @var Algolia_Algoliasearch_Model_Resource_Engine $engine */ + $engine = Mage::getResourceModel('algoliasearch/engine'); + + $engine->rebuildPages($storeIds, $page->getId()); + } + public function deleteProductsStoreIndices(Varien_Object $event) { $storeId = $event->getStoreId(); @@ -123,8 +137,9 @@ public function rebuildAdditionalSectionsIndex(Varien_Object $event) public function rebuildPageIndex(Varien_Object $event) { $storeId = $event->getStoreId(); + $pageIds = $event->getPageIds(); - $this->helper->rebuildStorePageIndex($storeId); + $this->helper->rebuildStorePageIndex($storeId, $pageIds); } public function rebuildSuggestionIndex(Varien_Object $event) diff --git a/app/code/community/Algolia/Algoliasearch/Model/Queue.php b/app/code/community/Algolia/Algoliasearch/Model/Queue.php index 98ad9ee6..1c6d880f 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Queue.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Queue.php @@ -27,6 +27,8 @@ class Algolia_Algoliasearch_Model_Queue 'moveStoreSuggestionIndex', ); + private $noOfFailedJobs = 0; + public function __construct() { /** @var Mage_Core_Model_Resource $coreResource */ @@ -80,13 +82,28 @@ public function run($maxJobs) // Run all reserved jobs foreach ($jobs as $job) { + // If there are some failed jobs before move, we want to skip the move + // as most probably not all products have prices reindexed + // and therefore are not indexed yet in TMP index + if ($job['method'] === 'moveProductsTmpIndex' && $this->noOfFailedJobs > 0) { + // Set pid to NULL so it's not deleted after + $this->db->query("UPDATE {$this->db->quoteIdentifier($this->table, true)} SET pid = NULL WHERE job_id = ".$job['job_id']); + + continue; + } + try { $model = Mage::getSingleton($job['class']); $method = $job['method']; - $model->{$method}(new Varien_Object($job['data'])); - } catch (Exception $e) { - // Increment retries and log error information + } catch (\Exception $e) { + $this->noOfFailedJobs++; + + // Increment retries, set the job ID back to NULL + $updateQuery = "UPDATE {$this->db->quoteIdentifier($this->table, true)} SET pid = NULL, retries = retries + 1 WHERE job_id IN (".implode(', ', (array) $job['merged_ids']).")"; + $this->db->query($updateQuery); + + // log error information $this->logger->log("Queue processing {$job['pid']} [KO]: Mage::getSingleton({$job['class']})->{$job['method']}(".json_encode($job['data']).')'); $this->logger->log(date('c').' ERROR: '.get_class($e).": '{$e->getMessage()}' in {$e->getFile()}:{$e->getLine()}\n"."Stack trace:\n".$e->getTraceAsString()); } @@ -108,6 +125,15 @@ public function run($maxJobs) private function getJobs($maxJobs, $pid) { + // Clear jobs with crossed max retries count + $retryLimit = $this->config->getRetryLimit(); + if ($retryLimit > 0) { + $where = $this->db->quoteInto('retries >= ?', $retryLimit); + $this->db->delete($this->table, $where); + } else { + $this->db->delete($this->table, 'retries > max_retries'); + } + $jobs = array(); $limit = $maxJobs = ($maxJobs === -1) ? $this->config->getNumberOfJobToRun() : $maxJobs; @@ -175,8 +201,7 @@ private function getJobs($maxJobs, $pid) if (isset($firstJobId)) { - // Last job has always assigned the last processed ID - $lastJobId = $jobs[count($jobs) - 1]['job_id']; + $lastJobId = $this->maxValueInArray($jobs, 'job_id'); // Reserve all new jobs since last run $this->db->query("UPDATE {$this->db->quoteIdentifier($this->table, true)} SET pid = ".$pid.' WHERE job_id >= '.$firstJobId." AND job_id <= $lastJobId"); @@ -189,6 +214,7 @@ private function prepareJobs($jobs) { foreach ($jobs as &$job) { $job['data'] = json_decode($job['data'], true); + $job['merged_ids'][] = $job['job_id']; } return $jobs; @@ -211,12 +237,12 @@ protected function mergeJobs($oldJobs) // Use the job_id of the the very last job to properly mark processed jobs $currentJob['job_id'] = max((int) $currentJob['job_id'], (int) $nextJob['job_id']); + $currentJob['merged_ids'][] = $nextJob['job_id']; + if (isset($currentJob['data']['product_ids'])) { $currentJob['data']['product_ids'] = array_merge($currentJob['data']['product_ids'], $nextJob['data']['product_ids']); - $currentJob['data_size'] = count($currentJob['data']['product_ids']); } elseif (isset($currentJob['data']['category_ids'])) { $currentJob['data']['category_ids'] = array_merge($currentJob['data']['category_ids'], $nextJob['data']['category_ids']); - $currentJob['data_size'] = count($currentJob['data']['category_ids']); } continue; @@ -227,10 +253,12 @@ protected function mergeJobs($oldJobs) if (isset($currentJob['data']['product_ids'])) { $currentJob['data']['product_ids'] = array_unique($currentJob['data']['product_ids']); + $currentJob['data_size'] = count($currentJob['data']['product_ids']); } if (isset($currentJob['data']['category_ids'])) { $currentJob['data']['category_ids'] = array_unique($currentJob['data']['category_ids']); + $currentJob['data_size'] = count($currentJob['data']['category_ids']); } $jobs[] = $currentJob; @@ -242,8 +270,6 @@ protected function mergeJobs($oldJobs) private function sortJobs($oldJobs) { - // Method sorts the jobs and preserves the order of jobs with static methods defined in $this->staticJobMethods - $sortedJobs = array(); $tempSortableJobs = array(); @@ -340,4 +366,19 @@ private function arrayMultisort() return array_pop($args); } + + private function maxValueInArray($array, $keyToSearch) + { + $currentMax = null; + + foreach ($array as $arr) { + foreach ($arr as $key => $value) { + if ($key == $keyToSearch && ($value >= $currentMax)) { + $currentMax = $value; + } + } + } + + return $currentMax; + } } diff --git a/app/code/community/Algolia/Algoliasearch/Model/Resource/Engine.php b/app/code/community/Algolia/Algoliasearch/Model/Resource/Engine.php index bcc40e3e..b8b2a20e 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Resource/Engine.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Resource/Engine.php @@ -22,6 +22,9 @@ class Algolia_Algoliasearch_Model_Resource_Engine extends Mage_CatalogSearch_Mod /** @var Algolia_Algoliasearch_Helper_Entity_Categoryhelper */ protected $category_helper; + /** @var Algolia_Algoliasearch_Helper_Entity_Pagehelper */ + protected $page_helper; + /** @var Algolia_Algoliasearch_Helper_Entity_Suggestionhelper */ protected $suggestion_helper; @@ -34,6 +37,7 @@ public function _construct() $this->logger = Mage::helper('algoliasearch/logger'); $this->product_helper = Mage::helper('algoliasearch/entity_producthelper'); $this->category_helper = Mage::helper('algoliasearch/entity_categoryhelper'); + $this->page_helper = Mage::helper('algoliasearch/entity_pagehelper'); $this->suggestion_helper = Mage::helper('algoliasearch/entity_suggestionhelper'); } @@ -92,25 +96,16 @@ public function rebuildCategoryIndex($storeId = null, $categoryIds = null) return $this; } - public function rebuildPages() + public function rebuildPages($storeId = null, $pageIds = null) { - /** @var Mage_Core_Model_Store $store */ - foreach (Mage::app()->getStores() as $store) { - if ($this->config->isEnabledBackend($store->getId()) === false) { - if (php_sapi_name() === 'cli') { - echo '[ALGOLIA] INDEXING IS DISABLED FOR '.$this->logger->getStoreName($store->getId())."\n"; - } - - /** @var Mage_Adminhtml_Model_Session $session */ - $session = Mage::getSingleton('adminhtml/session'); - $session->addWarning('[ALGOLIA] INDEXING IS DISABLED FOR '.$this->logger->getStoreName($store->getId())); - - $this->logger->log('INDEXING IS DISABLED FOR '.$this->logger->getStoreName($store->getId())); + $storeIds = Algolia_Algoliasearch_Helper_Entity_Helper::getStores($storeId); - continue; + /** @var Mage_Core_Model_Store $store */ + foreach ($storeIds as $storeId) { + if ($this->page_helper->shouldIndexPages($storeId) === true) { + $this->addToQueue('algoliasearch/observer', 'rebuildPageIndex', + array('store_id' => $storeId, 'page_ids' => $pageIds), 1); } - - $this->addToQueue('algoliasearch/observer', 'rebuildPageIndex', array('store_id' => $store->getId()), 1); } } @@ -164,8 +159,10 @@ public function rebuildSuggestions() $this->addToQueue('algoliasearch/observer', 'rebuildSuggestionIndex', $data, 1); } - $this->addToQueue('algoliasearch/observer', 'moveStoreSuggestionIndex', - array('store_id' => $store->getId()), 1); + if ($nb_page > 0) { + $this->addToQueue('algoliasearch/observer', 'moveStoreSuggestionIndex', + array('store_id' => $store->getId()), 1); + } } return $this; diff --git a/app/code/community/Algolia/Algoliasearch/Model/Resource/Fulltext.php b/app/code/community/Algolia/Algoliasearch/Model/Resource/Fulltext.php index 43137587..26ef4893 100644 --- a/app/code/community/Algolia/Algoliasearch/Model/Resource/Fulltext.php +++ b/app/code/community/Algolia/Algoliasearch/Model/Resource/Fulltext.php @@ -47,6 +47,10 @@ public function rebuildIndex($storeId = null, $productIds = null) return parent::rebuildIndex($storeId, $productIds); } + if ($this->config->isModuleOutputEnabled() === false) { + return parent::rebuildIndex($storeId, $productIds); + } + if (!$this->config->getApplicationID() || !$this->config->getAPIKey() || !$this->config->getSearchOnlyAPIKey()) { /** @var Mage_Adminhtml_Model_Session $session */ $session = Mage::getSingleton('adminhtml/session'); diff --git a/app/code/community/Algolia/Algoliasearch/Model/System/Config/Source/Dropdown/RetryValues.php b/app/code/community/Algolia/Algoliasearch/Model/System/Config/Source/Dropdown/RetryValues.php new file mode 100644 index 00000000..a517ce15 --- /dev/null +++ b/app/code/community/Algolia/Algoliasearch/Model/System/Config/Source/Dropdown/RetryValues.php @@ -0,0 +1,19 @@ + '1','label' => '1'), + array('value' => '2','label' => '2'), + array('value' => '3','label' => '3'), + array('value' => '5','label' => '5'), + array('value' => '10','label' => '10'), + array('value' => '20','label' => '20'), + array('value' => '50','label' => '50'), + array('value' => '100','label' => '100'), + array('value' => '9999999','label' => 'unlimited') + ); + } +} diff --git a/app/code/community/Algolia/Algoliasearch/controllers/Adminhtml/QueueController.php b/app/code/community/Algolia/Algoliasearch/controllers/Adminhtml/QueueController.php index 73fe89c1..ec94fbbf 100644 --- a/app/code/community/Algolia/Algoliasearch/controllers/Adminhtml/QueueController.php +++ b/app/code/community/Algolia/Algoliasearch/controllers/Adminhtml/QueueController.php @@ -2,6 +2,11 @@ class Algolia_Algoliasearch_Adminhtml_QueueController extends Mage_Adminhtml_Controller_Action { + public function _isAllowed() + { + return true; + } + public function indexAction() { /** @var Algolia_Algoliasearch_Helper_Config $config */ diff --git a/app/code/community/Algolia/Algoliasearch/etc/config.xml b/app/code/community/Algolia/Algoliasearch/etc/config.xml index b1315305..8770511b 100644 --- a/app/code/community/Algolia/Algoliasearch/etc/config.xml +++ b/app/code/community/Algolia/Algoliasearch/etc/config.xml @@ -2,7 +2,7 @@ - 1.10.0 + 1.11.0 @@ -130,6 +130,15 @@ + + + + + algoliasearch/observer + savePage + + + @@ -173,6 +182,7 @@ magento_ 1 0 + 0 9 @@ -201,6 +211,7 @@ 2 1 1 + 0 5 @@ -209,9 +220,12 @@ 0 - 0 100 + 0 10 + 3 + 0 + 0 265 diff --git a/app/code/community/Algolia/Algoliasearch/etc/system.xml b/app/code/community/Algolia/Algoliasearch/etc/system.xml index 06ba24bf..5525a6d1 100644 --- a/app/code/community/Algolia/Algoliasearch/etc/system.xml +++ b/app/code/community/Algolia/Algoliasearch/etc/system.xml @@ -4,7 +4,7 @@ .algoliasearch-admin-menu span { padding-left: 38px !important; @@ -183,6 +183,22 @@ ]]> + + Replace default image by matching variation + select + adminhtml/system_config_source_yesno + 90 + 1 + 1 + 1 + + If a customer searches for "blue dress" and refines by "red" color, Algolia will display images for red (refined) color. + ]]> + + @@ -324,6 +340,21 @@ ]]> + + Enable autocomplete menu's debug mode + select + adminhtml/system_config_source_yesno + 35 + 1 + 1 + 1 + + + + @@ -714,11 +745,21 @@ ]]> + + Max number of element per indexing job + text + validate-digits + 10 + 1 + 1 + 1 + The max number of element by indexing job. Default value is 100. + Queue Enabled select adminhtml/system_config_source_yesno - 10 + 20 1 1 1 @@ -733,21 +774,11 @@ ]]> - - Max number of element per indexing job - text - validate-digits - 30 - 1 - 1 - 1 - The max number of element by indexing job. Default value is 100. - Number of jobs to run each time the cron is run text validate-digits - 40 + 30 1 1 1 @@ -763,6 +794,55 @@ 1 + + Number of times to retry processing of queued jobs + 40 + select + Select the number of times to retry events that have failed the indexes check. + algoliasearch/system_config_source_dropdown_retryValues + 1 + 1 + 1 + + 1 + + + + Should check if the prices were reindexed before pushing to Algolia? + 40 + select + + + If set to Yes, the queue checks if all the products which are supposed to be indexed in Algolia has reindexed prices. If they don't those products will be skipped and pushed to Algolia when they have its' prices reindexed. + ]]> + + adminhtml/system_config_source_yesno + 1 + 1 + 1 + + 1 + + + + Should check if the stock was reindexed before pushing to Algolia? + 50 + select + + + If set to Yes, the queue checks if all the products which are supposed to be indexed in Algolia has reindexed stock. If they don't those products will be skipped and pushed to Algolia when they have its' stock reindexed. + ]]> + + adminhtml/system_config_source_yesno + 1 + 1 + 1 + + 1 + + @@ -778,7 +858,7 @@ Analytics documentation: - https://community.algolia.com/magento/faq/#why-are-images-not-showing-up + https://community.algolia.com/magento/doc/m1/analytics/ diff --git a/app/design/frontend/base/default/template/algoliasearch/internals/configuration.phtml b/app/design/frontend/base/default/template/algoliasearch/internals/configuration.phtml index 0f33c389..0c881847 100644 --- a/app/design/frontend/base/default/template/algoliasearch/internals/configuration.phtml +++ b/app/design/frontend/base/default/template/algoliasearch/internals/configuration.phtml @@ -213,6 +213,7 @@ $algoliaJsConfig = array( 'nbOfCategoriesSuggestions' => $config->getNumberOfCategoriesSuggestions(), 'nbOfQueriesSuggestions' => $config->getNumberOfQueriesSuggestions(), 'displaySuggestionsCategories' => $config->displaySuggestionsCategories(), + 'isDebugEnabled' => $config->isAutocompleteDebugEnabled(), ), 'extensionVersion' => $config->getExtensionVersion(), 'applicationId' => $config->getApplicationID(), @@ -247,6 +248,7 @@ $algoliaJsConfig = array( 'showSuggestionsOnNoResultsPage' => $config->showSuggestionsOnNoResultsPage(), 'baseUrl' => $baseUrl, 'popularQueries' => $config->getPopularQueries(), + 'useAdaptiveImage' => $config->useAdaptiveImage(), 'urls' => array( 'logo' => $this->getSkinUrl('algoliasearch/search-by-algolia.svg'), ), diff --git a/app/etc/modules/Algolia_Algoliasearch.xml b/app/etc/modules/Algolia_Algoliasearch.xml index 863a815a..bb6612bf 100644 --- a/app/etc/modules/Algolia_Algoliasearch.xml +++ b/app/etc/modules/Algolia_Algoliasearch.xml @@ -4,7 +4,7 @@ true community - 1.10.0 + 1.11.0 diff --git a/dev/restart.sh b/dev/restart.sh index e99fe501..63cfc43a 100755 --- a/dev/restart.sh +++ b/dev/restart.sh @@ -115,7 +115,7 @@ case "$MAGENTO_VERSION" in MAGENTO_VERSION=1.9.2.1 ;; 193) - MAGENTO_VERSION=1.9.3.1 + MAGENTO_VERSION=1.9.3.4 ;; 18) MAGENTO_VERSION=1.8.1 diff --git a/js/algoliasearch/autocomplete.js b/js/algoliasearch/autocomplete.js index 1176b696..7c863ea5 100644 --- a/js/algoliasearch/autocomplete.js +++ b/js/algoliasearch/autocomplete.js @@ -68,7 +68,7 @@ document.addEventListener("DOMContentLoaded", function(event) { dropdownMenu: '#menu-template' }, dropdownMenuContainer: "#algolia-autocomplete-container", - debug: false + debug: algoliaConfig.autocomplete.isDebugEnabled }; if (isMobile() === true) { diff --git a/js/algoliasearch/instantsearch.js b/js/algoliasearch/instantsearch.js index 94d23bc9..36fb0a54 100644 --- a/js/algoliasearch/instantsearch.js +++ b/js/algoliasearch/instantsearch.js @@ -59,6 +59,22 @@ document.addEventListener("DOMContentLoaded", function (event) { } }; + if (algoliaConfig.request.path.length > 0 && window.location.hash.indexOf('categories.level0') === -1) { + if (algoliaConfig.areCategoriesInFacets) { + instantsearchOptions.searchParameters = { + hierarchicalFacetsRefinements: { + 'categories.level0': [algoliaConfig.request.path] + } + }; + } else { + instantsearchOptions.searchParameters = { + facetsRefinements: { } + }; + + instantsearchOptions.searchParameters['facetsRefinements']['categories.level' + algoliaConfig.request.level] = [algoliaConfig.request.path]; + } + } + if (typeof algoliaHookBeforeInstantsearchInit === 'function') { instantsearchOptions = algoliaHookBeforeInstantsearchInit(instantsearchOptions); } @@ -115,13 +131,6 @@ document.addEventListener("DOMContentLoaded", function (event) { data.helper.toggleRefine(algoliaConfig.request.refinementKey, algoliaConfig.request.refinementValue); } - if (algoliaConfig.areCategoriesInFacets === false && algoliaConfig.request.path.length > 0) { - var facet = 'categories.level' + algoliaConfig.request.level; - - data.helper.state.facets.push(facet); - data.helper.toggleRefine(facet, algoliaConfig.request.path); - } - data.helper.setPage(page); }, render: function (data) { @@ -227,7 +236,7 @@ document.addEventListener("DOMContentLoaded", function (event) { transformData: { allItems: function (results) { for (var i = 0; i < results.hits.length; i++) { - results.hits[i] = transformHit(results.hits[i], algoliaConfig.priceKey); + results.hits[i] = transformHit(results.hits[i], algoliaConfig.priceKey, search.helper); results.hits[i].isAddToCartEnabled = algoliaConfig.instant.isAddToCartEnabled; results.hits[i].algoliaConfig = window.algoliaConfig; @@ -462,14 +471,6 @@ document.addEventListener("DOMContentLoaded", function (event) { search = algoliaHookAfterInstantsearchStart(search); } - if (algoliaConfig.request.path.length > 0 - && 'categories.level0' in search.helper.state.hierarchicalFacetsRefinements === false - && 'categories.level0' in search.helper.state.facetsRefinements === false) { - var page = search.helper.state.page; - - search.helper.toggleRefinement('categories.level0', algoliaConfig.request.path).setPage(page).search(); - } - handleInputCrossInstant($(instant_selector)); var instant_search_bar = $(instant_selector); diff --git a/js/algoliasearch/internals/adminhtml/admin_scripts.js b/js/algoliasearch/internals/adminhtml/admin_scripts.js index 4994164f..c9e51525 100644 --- a/js/algoliasearch/internals/adminhtml/admin_scripts.js +++ b/js/algoliasearch/internals/adminhtml/admin_scripts.js @@ -34,7 +34,13 @@ algoliaAdminBundle.$(function($) { } }); - $.getJSON('/index.php/admin/queue', function(queueInfo) { + // Queue info + + var url = window.location.href, + position = url.indexOf('/system_config/edit/'), + baseUrl = url.substring(0, position); + + $.getJSON(baseUrl + '/queue', function(queueInfo) { var message = '⚠ ' + 'Indexing queue is not enabled' + 'It\'s highly recommended to enable it, especially if you are on production environment. ' + @@ -74,7 +80,7 @@ algoliaAdminBundle.$(function($) { } }, 200); - $.getJSON('/index.php/admin/queue/truncate', function(payload) { + $.getJSON(baseUrl + '/queue/truncate', function(payload) { window.clearInterval(dots); if (payload.status === 'ok') { diff --git a/js/algoliasearch/internals/frontend/common.js b/js/algoliasearch/internals/frontend/common.js index 18119c43..8e9860e5 100644 --- a/js/algoliasearch/internals/frontend/common.js +++ b/js/algoliasearch/internals/frontend/common.js @@ -8,7 +8,7 @@ document.addEventListener("DOMContentLoaded", function (e) { return check; }; - window.transformHit = function (hit, price_key) { + window.transformHit = function (hit, price_key, helper) { if (Array.isArray(hit.categories)) { hit.categories = hit.categories.join(', '); } @@ -20,7 +20,19 @@ document.addEventListener("DOMContentLoaded", function (e) { hit.categories_without_path = hit.categories_without_path.join(', '); } - + + var matchedColors = []; + + if (helper && algoliaConfig.useAdaptiveImage === true) { + if (hit.images_data && helper.state.facetsRefinements.color) { + matchedColors = helper.state.disjunctiveFacetsRefinements.color.slice(0); // slice to clone + } + + if (hit.images_data && helper.state.disjunctiveFacetsRefinements.color) { + matchedColors = helper.state.disjunctiveFacetsRefinements.color.slice(0); // slice to clone + } + } + if (Array.isArray(hit.color)) { var colors = []; @@ -28,16 +40,42 @@ document.addEventListener("DOMContentLoaded", function (e) { if (color.matchLevel === 'none') { return; } - + colors.push(color.value); + + if (algoliaConfig.useAdaptiveImage === true) { + var re = /(.*?)<\/em>/g; + var matchedWords = color.value.match(re).map(function (val) { + return val.replace(/<\/?em>/g, ''); + }); + + var matchedColor = matchedWords.join(' '); + + if (hit.images_data && color.fullyHighlighted && color.fullyHighlighted === true) { + matchedColors.push(matchedColor); + } + } }); colors = colors.join(', '); - hit._highlightResult.color = {value: colors}; + hit._highlightResult.color = { value: colors }; } else if (hit._highlightResult.color && hit._highlightResult.color.matchLevel === 'none') { - hit._highlightResult.color = {value: ''}; + hit._highlightResult.color = { value: '' }; + } + + if (algoliaConfig.useAdaptiveImage === true) { + $.each(matchedColors, function (i, color) { + color = color.toLowerCase(); + + if (hit.images_data[color]) { + hit.image_url = hit.images_data[color]; + hit.thumbnail_url = hit.images_data[color]; + + return false; + } + }); } if (hit._highlightResult.color && hit._highlightResult.color.value && hit.categories_without_path) { @@ -46,7 +84,6 @@ document.addEventListener("DOMContentLoaded", function (e) { } } - if (Array.isArray(hit._highlightResult.name)) { hit._highlightResult.name = hit._highlightResult.name[0]; } diff --git a/lib/AlgoliaSearch/AlgoliaConnectionException.php b/lib/AlgoliaSearch/AlgoliaConnectionException.php new file mode 100644 index 00000000..a9abec29 --- /dev/null +++ b/lib/AlgoliaSearch/AlgoliaConnectionException.php @@ -0,0 +1,30 @@ +connectTimeout = 1; @@ -112,11 +112,11 @@ public function __construct($applicationID, $apiKey, $hostsArray, $placesEnabled $this->writeHostsArray = $this->getDefaultWriteHosts(); } - if ($this->applicationID == null || mb_strlen($this->applicationID) == 0) { + if (($this->applicationID == null || mb_strlen($this->applicationID) == 0) && $placesEnabled === false) { throw new Exception('AlgoliaSearch requires an applicationID.'); } - if ($this->apiKey == null || mb_strlen($this->apiKey) == 0) { + if (($this->apiKey == null || mb_strlen($this->apiKey) == 0) && $placesEnabled === false) { throw new Exception('AlgoliaSearch requires an apiKey.'); } diff --git a/lib/AlgoliaSearch/Index.php b/lib/AlgoliaSearch/Index.php index 82d76612..60ab28f6 100644 --- a/lib/AlgoliaSearch/Index.php +++ b/lib/AlgoliaSearch/Index.php @@ -383,7 +383,22 @@ public function deleteObjects($objects) return $this->batch($requests); } + public function deleteBy(array $args) + { + return $this->client->request( + $this->context, + 'POST', + '/1/indexes/'.$this->urlIndexName.'/deleteByQuery', + null, + array('params' => $this->client->buildQuery($args)), + $this->context->writeHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + /** + * @deprecated use `deleteBy()` instead. * Delete all objects matching a query. * * @param string $query the query string @@ -1505,6 +1520,142 @@ public function searchFacet($facetName, $facetQuery, $query = array()) return $this->searchForFacetValues($facetName, $facetQuery, $query); } + /** + * @param $params + * + * @return mixed + * + * @throws AlgoliaException + */ + public function searchRules(array $params = array()) + { + return $this->client->request( + $this->context, + 'POST', + '/1/indexes/'.$this->urlIndexName.'/rules/search', + null, + $params, + $this->context->readHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + + /** + * @param $objectID + * + * @return mixed + * + * @throws AlgoliaException + */ + public function getRule($objectID) + { + return $this->client->request( + $this->context, + 'GET', + '/1/indexes/'.$this->urlIndexName.'/rules/'.urlencode($objectID), + null, + null, + $this->context->readHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + + /** + * @param $objectID + * @param $forwardToReplicas + * + * @return mixed + * + * @throws AlgoliaException + */ + public function deleteRule($objectID, $forwardToReplicas = false) + { + return $this->client->request( + $this->context, + 'DELETE', + '/1/indexes/'.$this->urlIndexName.'/rules/'.urlencode($objectID).'?forwardToReplicas='.($forwardToReplicas ? 'true' : 'false'), + null, + null, + $this->context->writeHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + + /** + * @param bool $forwardToReplicas + * + * @return mixed + * + * @throws AlgoliaException + */ + public function clearRules($forwardToReplicas = false) + { + return $this->client->request( + $this->context, + 'POST', + '/1/indexes/'.$this->urlIndexName.'/rules/clear?forwardToReplicas='.($forwardToReplicas ? 'true' : 'false'), + null, + null, + $this->context->writeHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + + /** + * @param $rules + * @param bool $forwardToReplicas + * @param bool $clearExistingRules + * + * @return mixed + * + * @throws AlgoliaException + */ + public function batchRules($rules, $forwardToReplicas = false, $clearExistingRules = false) + { + return $this->client->request( + $this->context, + 'POST', + '/1/indexes/'.$this->urlIndexName.'/rules/batch?clearExistingRules='.($clearExistingRules ? 'true' : 'false') + .'&forwardToReplicas='.($forwardToReplicas ? 'true' : 'false'), + null, + $rules, + $this->context->writeHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + + /** + * @param $objectID + * @param $content + * @param bool $forwardToReplicas + * + * @return mixed + * + * @throws AlgoliaException + */ + public function saveRule($objectID, $content, $forwardToReplicas = false) + { + if (!isset($content['objectID'])) { + $content['objectID'] = $objectID; + } + + return $this->client->request( + $this->context, + 'PUT', + '/1/indexes/'.$this->urlIndexName.'/rules/'.urlencode($objectID).'?forwardToReplicas='.($forwardToReplicas ? 'true' : 'false'), + null, + $content, + $this->context->writeHostsArray, + $this->context->connectTimeout, + $this->context->readTimeout + ); + } + /** * @param string $name * @param array $arguments @@ -1521,6 +1672,6 @@ public function __call($name, $arguments) return call_user_func_array(array($this, 'doBcBrowse'), $arguments); } - return; + throw new \BadMethodCallException(sprintf('No method named %s was found.', $name)); } } diff --git a/lib/AlgoliaSearch/Version.php b/lib/AlgoliaSearch/Version.php index 18b9c605..0eb09f02 100644 --- a/lib/AlgoliaSearch/Version.php +++ b/lib/AlgoliaSearch/Version.php @@ -29,7 +29,7 @@ class Version { - const VALUE = '1.17.0'; + const VALUE = '1.19.0'; public static $custom_value = ''; diff --git a/lib/AlgoliaSearch/loader.php b/lib/AlgoliaSearch/loader.php index 994f95a0..7f26f13b 100644 --- a/lib/AlgoliaSearch/loader.php +++ b/lib/AlgoliaSearch/loader.php @@ -25,14 +25,15 @@ */ require_once __DIR__.'/AlgoliaException.php'; +require_once __DIR__.'/AlgoliaConnectionException.php'; require_once __DIR__.'/Client.php'; require_once __DIR__.'/ClientContext.php'; +require_once __DIR__.'/FailingHostsCache.php'; +require_once __DIR__.'/FileFailingHostsCache.php'; require_once __DIR__.'/Index.php'; require_once __DIR__.'/IndexBrowser.php'; +require_once __DIR__.'/InMemoryFailingHostsCache.php'; +require_once __DIR__.'/Json.php'; require_once __DIR__.'/PlacesIndex.php'; require_once __DIR__.'/SynonymType.php'; require_once __DIR__.'/Version.php'; -require_once __DIR__.'/Json.php'; -require_once __DIR__.'/FailingHostsCache.php'; -require_once __DIR__.'/FileFailingHostsCache.php'; -require_once __DIR__.'/InMemoryFailingHostsCache.php'; diff --git a/skin/frontend/base/default/algoliasearch/algoliasearch.css b/skin/frontend/base/default/algoliasearch/algoliasearch.css index 2f1a5bf7..50901d85 100644 --- a/skin/frontend/base/default/algoliasearch/algoliasearch.css +++ b/skin/frontend/base/default/algoliasearch/algoliasearch.css @@ -323,6 +323,11 @@ a.ais-current-refined-values--link:hover .ais-range-slider--marker-large:first-child { margin-left: 0; } + +.ais-root__collapsed .ais-body, .ais-root__collapsed .ais-footer { + display: none; +} + /****************** ** ** Auto-completion menu diff --git a/tests/QueueTest.php b/tests/QueueTest.php index f8d33b76..15f5eb34 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -211,7 +211,7 @@ public function testMerging() $this->assertEquals(6, count($mergedJobs)); $expectedCategoryJob = array( - 'job_id' => '7', + 'job_id' => 7, 'pid' => NULL, 'class' => 'algoliasearch/observer', 'method' => 'rebuildCategoryIndex', @@ -228,12 +228,13 @@ public function testMerging() 'error_log' => '', 'data_size' => 3, 'store_id' => '1', + 'merged_ids' => array('1', '7'), ); $this->assertEquals($expectedCategoryJob, $mergedJobs[0]); $expectedProductJob = array( - 'job_id' => '10', + 'job_id' => 10, 'pid' => NULL, 'class' => 'algoliasearch/observer', 'method' => 'rebuildProductIndex', @@ -249,6 +250,7 @@ public function testMerging() 'error_log' => '', 'data_size' => 2, 'store_id' => '1', + 'merged_ids' => array('4', '10'), ); $this->assertEquals($expectedProductJob, $mergedJobs[3]); @@ -334,6 +336,7 @@ public function testGetJobs() 'error_log' => '', 'data_size' => 3, 'store_id' => '1', + 'merged_ids' => array('1', '7'), ); $expectedLastJob = array( @@ -353,6 +356,7 @@ public function testGetJobs() 'error_log' => '', 'data_size' => 2, 'store_id' => '3', + 'merged_ids' => array('6', '12'), ); $this->assertEquals($expectedFirstJob, reset($jobs)); @@ -443,4 +447,62 @@ public function testMaxSingleJobSize() $this->assertEquals($pid, $firstJob['pid']); $this->assertEquals($pid, $lastJob['pid']); } + + public function testFaildedJob() + { + setConfig('algoliasearch/queue/number_of_retries', 3); + + $this->writeConnection->query('TRUNCATE TABLE algoliasearch_queue'); + + // Setting "not-existing-store" as store_id throws exception during processing the job + $this->writeConnection->query('INSERT INTO `algoliasearch_queue` (`job_id`, `pid`, `class`, `method`, `data`, `max_retries`, `retries`, `error_log`, `data_size`) VALUES + (1, NULL, \'algoliasearch/observer\', \'rebuildCategoryIndex\', \'{"store_id":"not-existing-store","category_ids":["9","22"]}\', 3, 0, \'\', 2), + (2, NULL, \'algoliasearch/observer\', \'rebuildCategoryIndex\', \'{"store_id":"2","category_ids":["9","22"]}\', 3, 0, \'\', 2), + (3, NULL, \'algoliasearch/observer\', \'rebuildProductIndex\', \'{"store_id":"not-existing-store","product_ids":["448"]}\', 3, 0, \'\', 1), + (4, NULL, \'algoliasearch/observer\', \'rebuildProductIndex\', \'{"store_id":"2","product_ids":["448"]}\', 3, 0, \'\', 1), + (5, NULL, \'algoliasearch/observer\', \'rebuildCategoryIndex\', \'{"store_id":"not-existing-store","category_ids":["40"]}\', 3, 0, \'\', 1), + (6, NULL, \'algoliasearch/observer\', \'rebuildCategoryIndex\', \'{"store_id":"2","category_ids":["40"]}\', 3, 0, \'\', 1), + (7, NULL, \'algoliasearch/observer\', \'rebuildProductIndex\', \'{"store_id":"not-existing-store","product_ids":["405"]}\', 3, 0, \'\', 1), + (8, NULL, \'algoliasearch/observer\', \'rebuildProductIndex\', \'{"store_id":"2","product_ids":["405"]}\', 3, 0, \'\', 1)'); + + $queue = new Algolia_Algoliasearch_Model_Queue(); + + $pid = getmypid(); + $jobs = invokeMethod($queue, 'getJobs', array('maxJobs' => 10, 'pid' => $pid)); + + // Check is jobs are correctly merged + $this->assertEquals(4, count($jobs)); + + // Reset pid + $this->writeConnection->query('UPDATE algoliasearch_queue SET pid = NULL'); + + $queue->run(10); + + $jobs = $this->readConnection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); + $this->assertEquals(4, count($jobs)); + + foreach ($jobs as $job) { + $this->assertNull($job['pid']); + $this->assertEquals('1', $job['retries']); + } + + $queue->run(10); + + $jobs = $this->readConnection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); + $this->assertEquals(4, count($jobs)); + + foreach ($jobs as $job) { + $this->assertNull($job['pid']); + $this->assertEquals('2', $job['retries']); + } + + // 3rd run, 3rd retry - retries are maxed out + $queue->run(10); + + // 4th run - should clean the table from maxed out jobs + $queue->run(10); + + $jobs = $this->readConnection->query('SELECT * FROM algoliasearch_queue')->fetchAll(); + $this->assertEquals(0, count($jobs)); + } }