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 @@ 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)); + } }