diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 289f8823..70dd4f98 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,9 +1,16 @@ # Changelog -## 1.1.0 +## [Unreleased] +### Added +- Adding support for video data. Small change will be needed in VSF #19 +- Add support for "product_count" in category. When products are reassign to category, category data is not updated automatically in ES. +There is not need really, VSF only has to know if `product_count > 0`, so if you already had products assign to categories (before running ` bin/magento indexer:reindex vsbridge_category_indexer`) +category will be visible in menu sidebar. +- Add support for reviews. Reviews are exported without ratings (VSF does not support ratings for now) +- Add support for custom options. -## 1.0.0 (2019.04.03) +## [1.0.0] (2019.04.03) First version ## March 2019 @@ -11,7 +18,7 @@ First version ### Added - Adding 'url_path' for products. You have to delete existing index from Elasticsearch and run full reindexation. -Need to get correct mapping for new product field. +You need to get correct mapping for new product field. - Add support for "available_sort_by" and "default_sort_by" default values. ### Fixed diff --git a/composer.json b/composer.json index 1f749acc..9993305d 100644 --- a/composer.json +++ b/composer.json @@ -38,12 +38,14 @@ "src/module-vsbridge-indexer-core/registration.php", "src/module-vsbridge-indexer-catalog/registration.php", "src/module-vsbridge-indexer-cms/registration.php", + "src/module-vsbridge-indexer-review/registration.php", "src/module-vsbridge-indexer-tax/registration.php" ], "psr-4": { "Divante\\VsbridgeIndexerCore\\": "src/module-vsbridge-indexer-core", "Divante\\VsbridgeIndexerCatalog\\": "src/module-vsbridge-indexer-catalog", "Divante\\VsbridgeIndexerCms\\": "src/module-vsbridge-indexer-cms", + "Divante\\VsbridgeIndexerReview\\": "src/module-vsbridge-indexer-review", "Divante\\VsbridgeIndexerTax\\": "src/module-vsbridge-indexer-tax" } }, diff --git a/src/module-vsbridge-indexer-catalog/Index/Mapping/AbstractMapping.php b/src/module-vsbridge-indexer-catalog/Index/Mapping/AbstractMapping.php index 6a132dfa..6baef5b6 100644 --- a/src/module-vsbridge-indexer-catalog/Index/Mapping/AbstractMapping.php +++ b/src/module-vsbridge-indexer-catalog/Index/Mapping/AbstractMapping.php @@ -4,7 +4,6 @@ use Divante\VsbridgeIndexerCore\Api\Mapping\FieldInterface; use Magento\Eav\Model\Entity\Attribute; -use Magento\Framework\Stdlib\DateTime; /** * Class AbstractMapping diff --git a/src/module-vsbridge-indexer-catalog/Index/Mapping/Category.php b/src/module-vsbridge-indexer-catalog/Index/Mapping/Category.php index 65caf384..a967607a 100644 --- a/src/module-vsbridge-indexer-catalog/Index/Mapping/Category.php +++ b/src/module-vsbridge-indexer-catalog/Index/Mapping/Category.php @@ -89,6 +89,7 @@ public function getMappingProperties() $properties = $this->generalMapping->getCommonProperties(); $properties['children_count'] = ['type' => FieldInterface::TYPE_INTEGER]; + $properties['product_count'] = ['type' => FieldInterface::TYPE_INTEGER]; $childMapping = $this->getChildrenDataMapping($attributesMapping, $properties); $properties['children_data'] = ['properties' => $childMapping]; diff --git a/src/module-vsbridge-indexer-catalog/Index/Mapping/Product.php b/src/module-vsbridge-indexer-catalog/Index/Mapping/Product.php index 97429ace..e16abdfd 100644 --- a/src/module-vsbridge-indexer-catalog/Index/Mapping/Product.php +++ b/src/module-vsbridge-indexer-catalog/Index/Mapping/Product.php @@ -149,6 +149,16 @@ private function getCommonMappingProperties() 'image' => ['type' => FieldInterface::TYPE_TEXT], 'lab' => ['type' => FieldInterface::TYPE_TEXT], 'pos' => ['type' => FieldInterface::TYPE_TEXT], + 'vid' => [ + 'properties' => [ + 'url' => ['type' => FieldInterface::TYPE_TEXT], + 'title' => ['type' => FieldInterface::TYPE_TEXT], + 'desc' => ['type' => FieldInterface::TYPE_TEXT], + 'video_id' => ['type' => FieldInterface::TYPE_TEXT], + 'meta' => ['type' => FieldInterface::TYPE_TEXT], + 'type' => ['type' => FieldInterface::TYPE_TEXT], + ] + ] ], ]; $attributesMapping['final_price'] = ['type' => FieldInterface::TYPE_DOUBLE]; @@ -168,6 +178,7 @@ private function getCustomProperties() 'properties' => [ 'option_id' => ['type' => FieldInterface::TYPE_LONG], 'position' => ['type' => FieldInterface::TYPE_LONG], + 'title' => ['type' => FieldInterface::TYPE_TEXT], 'sku' => ['type' => FieldInterface::TYPE_KEYWORD], 'product_links' => [ 'properties' => [ @@ -214,6 +225,32 @@ private function getCustomProperties() 'name' => ['type' => FieldInterface::TYPE_TEXT], ], ], + 'custom_options' => [ + 'properties' => [ + 'image_size_x' => ['type' => FieldInterface::TYPE_TEXT], + 'image_size_y' => ['type' => FieldInterface::TYPE_TEXT], + 'file_extension' => ['type' => FieldInterface::TYPE_TEXT], + 'is_require' => ['type' => FieldInterface::TYPE_BOOLEAN], + 'max_characters' => ['type' => FieldInterface::TYPE_TEXT], + 'option_id' => ['type' => FieldInterface::TYPE_LONG], + 'price' => ['type' => FieldInterface::TYPE_DOUBLE], + 'price_type' => ['type' => FieldInterface::TYPE_TEXT], + 'sku' => ['type' => FieldInterface::TYPE_KEYWORD], + 'sort_order' => ['type' => FieldInterface::TYPE_LONG], + 'title' => ['type' => FieldInterface::TYPE_TEXT], + 'type' => ['type' => FieldInterface::TYPE_TEXT], + 'values' => [ + 'properties' => [ + 'sku' => ['type' => FieldInterface::TYPE_KEYWORD], + 'price' => ['type' => FieldInterface::TYPE_DOUBLE], + 'title' => ['type' => FieldInterface::TYPE_TEXT], + 'price_type' => ['type' => FieldInterface::TYPE_TEXT], + 'sort_order' => ['type' => FieldInterface::TYPE_LONG], + 'option_type_id' => ['type' => FieldInterface::TYPE_INTEGER], + ] + ] + ] + ], 'tier_prices' => [ 'properties' => [ 'customer_group_d' => ['type' => FieldInterface::TYPE_INTEGER], diff --git a/src/module-vsbridge-indexer-catalog/Model/GalleryProcessor.php b/src/module-vsbridge-indexer-catalog/Model/GalleryProcessor.php index 60eb0f0b..6e89900a 100644 --- a/src/module-vsbridge-indexer-catalog/Model/GalleryProcessor.php +++ b/src/module-vsbridge-indexer-catalog/Model/GalleryProcessor.php @@ -13,17 +13,34 @@ */ class GalleryProcessor { + /** + * Youtube regex + * @var string + */ + private $youtubeRegex = + '%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i'; + + /** + * Vimeo regex + * @var array + */ + private $vimeoRegex = [ + '%^https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)', + "?|groups\/([^\/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)(?:[?]?.*)$%im", + ]; + /** * @param array $gallerySet + * @param array $videoSet * * @return array */ - public function prepareMediaGallery(array $gallerySet) + public function prepareMediaGallery(array $gallerySet, array $videoSet = []) { $galleryPerProduct = []; foreach ($gallerySet as $mediaImage) { - $linkFieldId = $mediaImage['row_id']; + $linkFieldId = $mediaImage['row_id']; $image['typ'] = 'image'; $image = [ 'typ' => 'image', @@ -32,12 +49,45 @@ public function prepareMediaGallery(array $gallerySet) 'pos' => (int)($this->getValue('position', $mediaImage)), ]; + $valueId = $mediaImage['value_id']; + + if (isset($videoSet[$valueId])) { + $image['vid'] = $this->prepareVideoData($videoSet[$valueId]); + } + $galleryPerProduct[$linkFieldId][] = $image; } return $galleryPerProduct; } + /** + * @param array $video + * + * @return array + */ + private function prepareVideoData(array $video) + { + $vimeoRegex = implode('', $this->vimeoRegex); + $id = null; + $type = null; + $reg = []; + $url = $video['url']; + + if (preg_match($this->youtubeRegex, $url, $reg)) { + $id = $reg[1]; + $type = 'youtube'; + } elseif (preg_match($vimeoRegex, $video['url'], $reg)) { + $id = $reg[3]; + $type = 'vimeo'; + } + + $video['video_id'] = $id; + $video['type'] = $type; + + return $video; + } + /** * @param string $fieldKey * @param array $image diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php index 556babb0..a71abb51 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Category/AttributeData.php @@ -8,6 +8,7 @@ use Divante\VsbridgeIndexerCatalog\Model\ConfigSettings; use Divante\VsbridgeIndexerCatalog\Model\SlugGenerator; use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\AttributeDataProvider; +use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category\ProductCount as ProductCountResourceModel; /** * Class AttributeData @@ -47,6 +48,11 @@ class AttributeData */ private $childrenResourceModel; + /** + * @var ProductCountResourceModel + */ + private $productCountResource; + /** * @var \Divante\VsbridgeIndexerCore\Indexer\DataFilter */ @@ -57,6 +63,11 @@ class AttributeData */ private $childrenRowAttributes = []; + /** + * @var array + */ + private $childrenProductCount = []; + /** * @var ConfigSettings */ @@ -80,6 +91,7 @@ class AttributeData public function __construct( AttributeDataProvider $attributeResource, CategoryChildrenResource $childrenResource, + ProductCountResourceModel $productCountResource, SlugGenerator\Proxy $catalogHelper, ConfigSettings $configSettings, CategoryChildAttributes $categoryChildAttributes, @@ -87,6 +99,7 @@ public function __construct( ) { $this->settings = $configSettings; $this->slugGenerator = $catalogHelper; + $this->productCountResource = $productCountResource; $this->attributeResourceModel = $attributeResource; $this->childrenResourceModel = $childrenResource; $this->dataFilter = $dataFilter; @@ -105,12 +118,17 @@ public function addData(array $indexData, $storeId) /** * TODO add option to load only specific categories */ - $attributes = $this->attributeResourceModel->loadAttributesData($storeId, array_keys($indexData)); + + $categoryIds = array_keys($indexData); + $attributes = $this->attributeResourceModel->loadAttributesData($storeId, $categoryIds); + $productCount = $this->productCountResource->loadProductCount($categoryIds); foreach ($attributes as $entityId => $attributesData) { $categoryData = array_merge($indexData[$entityId], $attributesData); $categoryData = $this->prepareCategory($categoryData); $categoryData = $this->addSortOptions($categoryData, $storeId); + $categoryData['product_count'] = $productCount[$entityId]; + $indexData[$entityId] = $categoryData; } @@ -126,6 +144,9 @@ public function addData(array $indexData, $storeId) $this->childAttributes->getRequiredAttributes() ); + $this->childrenProductCount = $this->productCountResource->loadProductCount( + array_keys($groupedChildrenById) + ); $indexData[$categoryId] = $this->addChildrenData($categoryData, $groupedChildrenById); } @@ -168,7 +189,7 @@ private function groupChildrenById(array $children) /** * @param array $categories - * @param $rootId + * @param int $rootId * * @return array */ @@ -188,6 +209,7 @@ private function plotTree(array $categories, $rootId) $categoryData = array_merge($categoryData, $this->childrenRowAttributes[$categoryId]); } + $categoryData['product_count'] = $this->childrenProductCount[$categoryId]; $categoryData = $this->prepareCategory($categoryData); $categoryData['children_data'] = $this->plotTree($categories, $categoryId); $categoryData['children_count'] = count($categoryData['children_data']); diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php index 5ccff98c..55fa1128 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/ConfigurableData.php @@ -129,7 +129,7 @@ public function addData(array $indexData, $storeId) /** * @param array $indexData - * @param $storeId + * @param int $storeId * * @return array * @throws \Exception @@ -299,7 +299,7 @@ private function hasPrice(array $product) } /** - * @param $storeId + * @param int $storeId * @param array $allChildren * @param array $requiredAttributes * @@ -345,11 +345,11 @@ private function loadChildrenRawAttributesInBatches($storeId, array $allChildren /** * @param array $documents - * @param $size + * @param int $batchSize * * @return \Generator */ - private function getChildrenInBatches(array $documents, $size) + private function getChildrenInBatches(array $documents, $batchSize) { $i = 0; $batch = []; @@ -357,7 +357,7 @@ private function getChildrenInBatches(array $documents, $size) foreach ($documents as $documentName => $documentValue) { $batch[$documentName] = $documentValue; - if (++$i == $size) { + if (++$i == $batchSize) { yield $batch; $i = 0; $batch = []; diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/CustomOptions.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/CustomOptions.php new file mode 100644 index 00000000..51acab18 --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/CustomOptions.php @@ -0,0 +1,89 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +declare(strict_types = 1); + +namespace Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product; + +use Divante\VsbridgeIndexerCore\Api\DataProviderInterface; +use Divante\VsbridgeIndexerCatalog\Model\ProductOptionProcessor; +use Divante\VsbridgeIndexerCatalog\Model\ProductMetaData; +use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product\CustomOptions as Resource; +use Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product\CustomOptionValues as OptionValuesResource; + +/** + * Class CustomOptions + */ +class CustomOptions implements DataProviderInterface +{ + /** + * @var Resource + */ + private $optionsResourceModel; + + /** + * @var OptionValuesResource + */ + private $optionValuesResourceModel; + + /** + * @var ProductMetaData + */ + private $productMetaData; + + /** + * @var ProductOptionProcessor + */ + private $productOptionProcessor; + + /** + * CustomOptions constructor. + * + * @param Resource $resource + * @param OptionValuesResource $customOptionValues + * @param ProductOptionProcessor $processor + * @param ProductMetaData $productMetaData + */ + public function __construct( + Resource $resource, + OptionValuesResource $customOptionValues, + ProductOptionProcessor $processor, + ProductMetaData $productMetaData + ) { + $this->optionsResourceModel = $resource; + $this->optionValuesResourceModel = $customOptionValues; + $this->productMetaData = $productMetaData; + $this->productOptionProcessor = $processor; + } + + /** + * @inheritdoc + */ + public function addData(array $indexData, $storeId) + { + $storeId = (int)$storeId; + $linkField = $this->productMetaData->get()->getLinkField(); + $linkFieldIds = array_column($indexData, $linkField); + + $options = $this->optionsResourceModel->loadProductOptions($linkFieldIds, $storeId); + $optionIds = array_column($options, 'option_id'); + $values = $this->optionValuesResourceModel->loadOptionValues($optionIds, $storeId); + + $optionsByProduct = $this->productOptionProcessor->prepareOptions($options, $values); + + foreach ($indexData as $productId => $productData) { + $linkFieldValue = $productData[$linkField]; + + if (isset($optionsByProduct[$linkFieldValue])) { + $indexData[$productId]['custom_options'] = $optionsByProduct[$linkFieldValue]; + } + } + + return $indexData; + } +} diff --git a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/MediaGalleryData.php b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/MediaGalleryData.php index 68be6306..b60a14b9 100644 --- a/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/MediaGalleryData.php +++ b/src/module-vsbridge-indexer-catalog/Model/Indexer/DataProvider/Product/MediaGalleryData.php @@ -18,6 +18,7 @@ */ class MediaGalleryData implements DataProviderInterface { + const VIDEO_TYPE = 'external-video'; /** * @var \Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product\Gallery @@ -38,6 +39,7 @@ class MediaGalleryData implements DataProviderInterface * MediaGalleryData constructor. * * @param Resource $resource + * @param ProductMetaData $productMetaData * @param GalleryProcessor $galleryProcessor */ public function __construct( @@ -57,9 +59,12 @@ public function addData(array $indexData, $storeId) { $linkField = $this->productMetaData->get()->getLinkField(); $linkFieldIds = array_column($indexData, $linkField); + $gallerySet = $this->resourceModel->loadGallerySet($linkFieldIds, $storeId); + $valueIds = $this->getValueIds($gallerySet); - $galleryPerProduct = $this->galleryProcessor->prepareMediaGallery($gallerySet); + $galleryVideos = $this->resourceModel->loadVideos($valueIds, $storeId); + $galleryPerProduct = $this->galleryProcessor->prepareMediaGallery($gallerySet, $galleryVideos); foreach ($indexData as $productId => $productData) { $linkFieldValue = $productData[$linkField]; @@ -73,4 +78,22 @@ public function addData(array $indexData, $storeId) return $indexData; } + + /** + * @param array $mediaGallery + * + * @return array + */ + private function getValueIds(array $mediaGallery) + { + $valueIds = []; + + foreach ($mediaGallery as $mediaItem) { + if (self::VIDEO_TYPE === $mediaItem['media_type']) { + $valueIds[] = $mediaItem['value_id']; + } + } + + return $valueIds; + } } diff --git a/src/module-vsbridge-indexer-catalog/Model/ProductOptionProcessor.php b/src/module-vsbridge-indexer-catalog/Model/ProductOptionProcessor.php new file mode 100644 index 00000000..7e9cda2a --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/Model/ProductOptionProcessor.php @@ -0,0 +1,123 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +declare(strict_types = 1); + +namespace Divante\VsbridgeIndexerCatalog\Model; + +use Divante\VsbridgeIndexerCore\Indexer\DataFilter; + +/** + * Class ProductOptionProcessor + */ +class ProductOptionProcessor +{ + /** + * @var array + */ + private $fieldsToDelete = [ + 'default_title', + 'store_title', + 'default_price', + 'default_price_type', + 'store_price', + 'store_price_type', + 'product_id', + ]; + + /** + * @var DataFilter + */ + private $dataFilter; + + /** + * ProductOptionProcessor constructor. + * + * @param DataFilter $dataFilter + */ + public function __construct(DataFilter $dataFilter) + { + $this->dataFilter = $dataFilter; + } + + /** + * @param array $options + * @param array $optionValues + * + * @return array + */ + public function prepareOptions(array $options, array $optionValues): array + { + $groupOption = []; + + foreach ($optionValues as $optionValue) { + $optionId = $optionValue['option_id']; + $optionValue = $this->prepareValue($optionValue); + $options[$optionId]['values'][] = $optionValue; + } + + foreach ($options as $option) { + $productId = $option['product_id']; + $option = $this->prepareOption($option); + $groupOption[$productId][] = $option; + } + + return $groupOption; + } + + /** + * @param array $option + * + * @return array + */ + private function prepareValue(array $option): array + { + $option = $this->unsetFields($option); + unset($option['option_id']); + + return $option; + } + + /** + * @param array $option + * + * @return array + */ + private function unsetFields(array $option): array + { + $option = $this->dataFilter->execute($option, $this->fieldsToDelete); + + if (isset($option['sku']) !== true) { + unset($option['sku']); + } + + if (isset($option['file_extension']) !== true) { + unset($option['file_extension']); + } + + return $option; + } + + /** + * @param array $option + * + * @return array + */ + private function prepareOption(array $option): array + { + $option = $this->unsetFields($option); + + $option = $this->dataFilter->execute($option, $this->fieldsToDelete); + + if ('drop_down' === $option['type']) { + $option['type'] = 'select'; + } + + return $option; + } +} diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category.php index d14a3f79..168a4844 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category.php @@ -77,9 +77,11 @@ public function getCategories($storeId = 1, array $categoryIds = [], $fromId = 0 } /** - * @param $storeId + * @param int $storeId * * @return \Magento\Framework\DB\Select + * @throws \Exception + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function filterByStore($storeId) { @@ -103,7 +105,7 @@ public function filterByStore($storeId) } /** - * @param $storeId + * @param int $storeId * @param array $productIds * * @return array diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/Children.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/Children.php index 369a20fe..89808e5c 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/Children.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/Children.php @@ -65,7 +65,7 @@ public function __construct( /** * @param array $category - * @param $storeId + * @param int $storeId * * @return array * @throws \Exception @@ -88,7 +88,7 @@ public function loadChildren(array $category, $storeId) /** * @param array $category - * @param $storeId + * @param int $storeId * @param bool $recursive * * @return array diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/ProductCount.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/ProductCount.php new file mode 100644 index 00000000..166a2dc8 --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Category/ProductCount.php @@ -0,0 +1,103 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Category; + +use Magento\Framework\App\ResourceConnection; + +/** + * Class ProductCount + */ +class ProductCount +{ + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var array + */ + private $categoryProductCountCache = []; + + /** + * Category constructor. + * + * @param ResourceConnection $resourceConnection + */ + public function __construct(ResourceConnection $resourceConnection) + { + $this->resource = $resourceConnection; + } + + /** + * @param array $categoryIds + * + * @return array + */ + public function loadProductCount(array $categoryIds) + { + if (null === $this->categoryProductCountCache) { + $this->categoryProductCountCache = []; + } + + $loadCategoryIds = $categoryIds; + + if (!empty($this->categoryProductCountCache)) { + $loadCategoryIds = array_diff($categoryIds, array_keys($this->categoryProductCountCache)); + } + + $loadCategoryIds = array_map('intval', $loadCategoryIds); + + if (!empty($loadCategoryIds)) { + $result = $this->getProductCount($loadCategoryIds); + + foreach ($loadCategoryIds as $categoryId) { + $categoryId = (int)$categoryId; + $this->categoryProductCountCache[$categoryId] = 0; + + if (isset($result[$categoryId])) { + $this->categoryProductCountCache[$categoryId] = (int)$result[$categoryId]; + } + } + } + + return $this->categoryProductCountCache; + } + + /** + * @param array $categoryIds + * + * @return array + */ + public function getProductCount(array $categoryIds) + { + $productTable = $this->resource->getTableName('catalog_category_product'); + + $select = $this->getConnection()->select()->from( + ['main_table' => $productTable], + [ + 'category_id', + new \Zend_Db_Expr('COUNT(main_table.product_id)') + ] + )->where('main_table.category_id in (?)', $categoryIds); + + $select->group('main_table.category_id'); + + return $this->getConnection()->fetchPairs($select); + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + return $this->resource->getConnection(); + } +} diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product.php index 95285ef0..df4b41e0 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product.php @@ -65,6 +65,7 @@ class Product * @param AttributeDataProvider $attributeDataProvider * @param ResourceConnection $resourceConnection * @param StoreManagerInterface $storeManager + * @param ProductMetaData $productMetaData * @param DbHelper $dbHelper */ public function __construct( @@ -90,6 +91,7 @@ public function __construct( * @param int $limit * * @return array + * @throws \Exception * @throws \Magento\Framework\Exception\NoSuchEntityException * @throws \Magento\Framework\Exception\LocalizedException */ @@ -203,7 +205,7 @@ private function addWebsiteFilter(Select $select, $storeId) } /** - * @param $storeId + * @param int $storeId * * @return int * @throws \Magento\Framework\Exception\NoSuchEntityException diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php index 21aa3417..55f9e984 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Category.php @@ -110,10 +110,10 @@ private function loadCategoryNames(array $categoryIds, $storeId) $loadCategoryIds = array_map('intval', $loadCategoryIds); if (!empty($loadCategoryIds)) { - $select = $this->prepareCategoryNameSelect($loadCategoryIds, $storeId); + $categoryName = $this->loadCategoryName($loadCategoryIds, $storeId); - foreach ($this->getConnection()->fetchAll($select) as $row) { - $categoryId = (int) $row['entity_id']; + foreach ($categoryName as $row) { + $categoryId = (int)$row['entity_id']; $this->categoryNameCache[$storeId][$categoryId] = $row['name']; } } @@ -125,10 +125,10 @@ private function loadCategoryNames(array $categoryIds, $storeId) * @param array $loadCategoryIds * @param int $storeId * - * @return Select + * @return array * @throws \Magento\Framework\Exception\LocalizedException */ - private function prepareCategoryNameSelect(array $loadCategoryIds, $storeId) + private function loadCategoryName(array $loadCategoryIds, $storeId) { /** @var CategoryCollection $categoryCollection */ $categoryCollection = $this->categoryCollectionFactory->create(); @@ -139,7 +139,7 @@ private function prepareCategoryNameSelect(array $loadCategoryIds, $storeId) $select = $categoryCollection->getSelect(); - return $select; + return $this->getConnection()->fetchAll($select); } /** diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/CustomOptionValues.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/CustomOptionValues.php new file mode 100644 index 00000000..35acac38 --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/CustomOptionValues.php @@ -0,0 +1,173 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +declare(strict_types = 1); + +namespace Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product; + +use Divante\VsbridgeIndexerCatalog\Model\ProductMetaData; +use Magento\Eav\Model\Entity\Attribute as EntityAttribute; +use Magento\Framework\DB\Select; +use Magento\Framework\App\ResourceConnection; +use Magento\Store\Model\Store; + +/** + * Class CustomOptionValues + */ +class CustomOptionValues +{ + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var EntityAttribute + */ + private $entityAttribute; + + /** + * Gallery constructor. + * + * @param ResourceConnection $resourceModel + * @param EntityAttribute $attribute + */ + public function __construct( + ResourceConnection $resourceModel, + EntityAttribute $attribute + ) { + $this->entityAttribute = $attribute; + $this->resource = $resourceModel; + } + + /** + * @param array $optionIds + * @param int $storeId + * + * @return array + */ + public function loadOptionValues(array $optionIds, int $storeId): array + { + $select = $this->getProductOptionSelect($optionIds, $storeId); + + return $this->getConnection()->fetchAll($select); + } + + /** + * @param array $optionIds + * @param int $storeId + * + * @return Select + */ + private function getProductOptionSelect(array $optionIds, int $storeId): Select + { + $connection = $this->getConnection(); + $mainTableAlias = 'main_table'; + + $select = $connection->select()->from( + [$mainTableAlias => $this->resource->getTableName('catalog_product_option_type_value')] + ); + + $select->where($mainTableAlias. '.option_id IN (?)', $optionIds); + + $select = $this->addTitleToResult($select, $storeId); + $select = $this->addPriceToResult($select, $storeId); + + $select->order('sort_order ASC'); + $select->order('title ASC'); + + return $select; + } + + /** + * @param Select $select + * @param int $storeId + * + * @return Select + */ + private function addTitleToResult(Select $select, int $storeId): Select + { + $optionTitleTable = $this->resource->getTableName('catalog_product_option_type_title'); + $titleExpr = $this->getConnection()->getCheckSql( + 'store_value_title.title IS NULL', + 'default_value_title.title', + 'store_value_title.title' + ); + + $joinExpr = 'store_value_title.option_type_id = main_table.option_type_id AND ' . + $this->getConnection()->quoteInto('store_value_title.store_id = ?', $storeId); + $select->join( + ['default_value_title' => $optionTitleTable], + 'default_value_title.option_type_id = main_table.option_type_id', + ['default_title' => 'title'] + )->joinLeft( + ['store_value_title' => $optionTitleTable], + $joinExpr, + ['store_title' => 'title', 'title' => $titleExpr] + )->where( + 'default_value_title.store_id = ?', + Store::DEFAULT_STORE_ID + ); + + return $select; + } + + /** + * @param Select $select + * @param int $storeId + * + * @return Select + */ + private function addPriceToResult(Select $select, int $storeId): Select + { + $optionTypeTable = $this->resource->getTableName('catalog_product_option_type_price'); + $priceExpr = $this->getConnection()->getCheckSql( + 'store_value_price.price IS NULL', + 'default_value_price.price', + 'store_value_price.price' + ); + $priceTypeExpr = $this->getConnection()->getCheckSql( + 'store_value_price.price_type IS NULL', + 'default_value_price.price_type', + 'store_value_price.price_type' + ); + + $joinExprDefault = 'default_value_price.option_type_id = main_table.option_type_id AND ' . + $this->getConnection()->quoteInto( + 'default_value_price.store_id = ?', + Store::DEFAULT_STORE_ID + ); + $joinExprStore = 'store_value_price.option_type_id = main_table.option_type_id AND ' . + $this->getConnection()->quoteInto('store_value_price.store_id = ?', $storeId); + $select->joinLeft( + ['default_value_price' => $optionTypeTable], + $joinExprDefault, + ['default_price' => 'price', 'default_price_type' => 'price_type'] + )->joinLeft( + ['store_value_price' => $optionTypeTable], + $joinExprStore, + [ + 'store_price' => 'price', + 'store_price_type' => 'price_type', + 'price' => $priceExpr, + 'price_type' => $priceTypeExpr + ] + ); + + return $select; + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + return $this->resource->getConnection(); + } +} diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/CustomOptions.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/CustomOptions.php new file mode 100644 index 00000000..e8185cea --- /dev/null +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/CustomOptions.php @@ -0,0 +1,177 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +declare(strict_types = 1); + +namespace Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product; + +use Magento\Eav\Model\Entity\Attribute as EntityAttribute; +use Magento\Framework\DB\Select; +use Magento\Framework\App\ResourceConnection; +use Magento\Store\Model\Store; + +/** + * Class CustomOptions + */ +class CustomOptions +{ + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var EntityAttribute + */ + private $entityAttribute; + + /** + * Gallery constructor. + * + * @param ResourceConnection $resourceModel + * @param EntityAttribute $attribute + */ + public function __construct( + ResourceConnection $resourceModel, + EntityAttribute $attribute + ) { + $this->entityAttribute = $attribute; + $this->resource = $resourceModel; + } + + /** + * @param array $linkFieldIds + * @param int $storeId + * + * @return array + */ + public function loadProductOptions(array $linkFieldIds, int $storeId): array + { + $select = $this->getProductOptionSelect($linkFieldIds, $storeId); + + return $this->getConnection()->fetchAssoc($select); + } + + /** + * @param array $linkFieldIds + * @param int $storeId + * + * @return Select + */ + private function getProductOptionSelect(array $linkFieldIds, int $storeId): Select + { + $connection = $this->getConnection(); + $mainTableAlias = 'main_table'; + + $select = $connection->select()->from( + [$mainTableAlias => $this->resource->getTableName('catalog_product_option')] + ); + + $select->where($mainTableAlias. '.product_id IN (?)', $linkFieldIds); + + $select = $this->addTitleToResult($select, $storeId); + $select = $this->addPriceToResult($select, $storeId); + + return $select; + } + + /** + * @param Select $select + * @param int $storeId + * + * @return Select + */ + private function addTitleToResult(Select $select, int $storeId): Select + { + $productOptionTitleTable = $this->resource->getTableName('catalog_product_option_title'); + $connection = $this->getConnection(); + $titleExpr = $connection->getCheckSql( + 'store_option_title.title IS NULL', + 'default_option_title.title', + 'store_option_title.title' + ); + + $select->join( + ['default_option_title' => $productOptionTitleTable], + 'default_option_title.option_id = main_table.option_id', + ['default_title' => 'title'] + )->joinLeft( + ['store_option_title' => $productOptionTitleTable], + 'store_option_title.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'store_option_title.store_id = ?', + $storeId + ), + [ + 'store_title' => 'title', + 'title' => $titleExpr + ] + )->where( + 'default_option_title.store_id = ?', + Store::DEFAULT_STORE_ID + ); + + return $select; + } + + /** + * @param Select $select + * @param int $storeId + * + * @return Select + */ + private function addPriceToResult(Select $select, int $storeId): Select + { + $productOptionPriceTable = $this->resource->getTableName('catalog_product_option_price'); + $connection = $this->getConnection(); + $priceExpr = $connection->getCheckSql( + 'store_option_price.price IS NULL', + 'default_option_price.price', + 'store_option_price.price' + ); + $priceTypeExpr = $connection->getCheckSql( + 'store_option_price.price_type IS NULL', + 'default_option_price.price_type', + 'store_option_price.price_type' + ); + + $select->joinLeft( + ['default_option_price' => $productOptionPriceTable], + 'default_option_price.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'default_option_price.store_id = ?', + Store::DEFAULT_STORE_ID + ), + [ + 'default_price' => 'price', + 'default_price_type' => 'price_type' + ] + )->joinLeft( + ['store_option_price' => $productOptionPriceTable], + 'store_option_price.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'store_option_price.store_id = ?', + $storeId + ), + [ + 'store_price' => 'price', + 'store_price_type' => 'price_type', + 'price' => $priceExpr, + 'price_type' => $priceTypeExpr + ] + ); + + return $select; + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + return $this->resource->getConnection(); + } +} diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Gallery.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Gallery.php index e23fa5bd..fbc551c5 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Gallery.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Gallery.php @@ -20,6 +20,16 @@ */ class Gallery { + /** + * @var array + */ + private $videoProperties = [ + 'url' => 'url', + 'title' => 'title', + 'desc' => 'description', + 'meta' => 'metadata', + ]; + /** * @var ResourceConnection */ @@ -54,7 +64,7 @@ public function __construct( /** * @param array $linkFieldIds - * @param $storeId + * @param int $storeId * * @return array * @throws \Exception @@ -67,7 +77,7 @@ public function loadGallerySet(array $linkFieldIds, $storeId) } /** - * @return mixed + * @return int * @throws \Magento\Framework\Exception\LocalizedException */ private function getMediaGalleryAttributeId() @@ -77,6 +87,94 @@ private function getMediaGalleryAttributeId() return $attribute->getId(); } + /** + * @param array $valueIds + * @param int $storeId + * + * @return array + */ + public function loadVideos(array $valueIds, $storeId) + { + if (empty($valueIds)) { + return []; + } + + $result = $this->getVideoRawData($valueIds, $storeId); + $groupByValueId = []; + + foreach ($result as $item) { + $valueId = $item['value_id']; + $item = $this->substituteNullsWithDefaultValues($item); + unset($item['value_id']); + $groupByValueId[$valueId] = $item; + } + + return $groupByValueId; + } + + /** + * @param array $valueIds + * @param int $storeId + * + * @return array + */ + private function getVideoRawData(array $valueIds, $storeId) + { + $connection = $this->getConnection(); + $mainTableAlias = 'main'; + $videoTable = $this->resource->getTableName('catalog_product_entity_media_gallery_value_video'); + + // Select gallery images for product + $select = $connection->select() + ->from( + [$mainTableAlias => $videoTable], + [ + 'value_id' => 'value_id', + 'url_default' => 'url', + 'title_default' => 'title', + 'desc_default' => 'description', + 'meta_default' => 'metadata' + ] + ); + + $select->where($mainTableAlias . '.store_id = ?', Store::DEFAULT_STORE_ID); + $select->where($mainTableAlias . '.value_id IN (?)', $valueIds); + + $select->joinLeft( + ['value' => $videoTable], + implode( + ' AND ', + [ + $mainTableAlias . '.value_id = value.value_id', + $this->getConnection()->quoteInto('value.store_id = ?', (int)$storeId), + ] + ), + $this->videoProperties + ); + + return $connection->fetchAll($select); + } + + /** + * @param array $rowData + * + * @return array + */ + private function substituteNullsWithDefaultValues(array $rowData) + { + $columns = array_keys($this->videoProperties); + + foreach ($columns as $key) { + if (empty($rowData[$key]) && !empty($rowData[$key . '_default'])) { + $rowData[$key] = $rowData[$key . '_default']; + } + + unset($rowData[$key . '_default']); + } + + return $rowData; + } + /** * @param array $linkFieldIds * @param int $storeId @@ -103,6 +201,7 @@ private function getLoadGallerySelect(array $linkFieldIds, $storeId) [$mainTableAlias => $this->resource->getTableName(GalleryResource::GALLERY_TABLE)], [ 'value_id', + 'media_type', 'file' => 'value' ] )->joinInner( diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Links.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Links.php index 0afd321a..5afaac87 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Links.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Links.php @@ -71,6 +71,8 @@ public function clear() /** * @param array $products + * + * @return void */ public function setProducts(array $products) { diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Prices.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Prices.php index c7538e5c..e3712f5d 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Prices.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/Prices.php @@ -6,11 +6,17 @@ * @license See LICENSE_DIVANTE.txt for license details. */ +declare(strict_types = 1); + namespace Divante\VsbridgeIndexerCatalog\Model\ResourceModel\Product; use Magento\Framework\App\ResourceConnection; use Magento\Store\Model\StoreManagerInterface; use Divante\VsbridgeIndexerCatalog\Model\ProductMetaData; +use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; /** * Class Prices @@ -32,21 +38,42 @@ class Prices */ private $productMetaData; + /** + * @var \Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver + */ + private $priceTableResolver; + + /** + * @var \Magento\Framework\Indexer\DimensionFactory + */ + private $dimensionFactory; + + /** + * @var array + */ + private $priceIndexTableName = []; + /** * Prices constructor. * * @param ResourceConnection $resourceModel * @param StoreManagerInterface $storeManager * @param ProductMetaData $productMetaData + * @param PriceTableResolver $priceTableResolver + * @param DimensionFactory $dimensionFactory */ public function __construct( ResourceConnection $resourceModel, StoreManagerInterface $storeManager, - ProductMetaData $productMetaData + ProductMetaData $productMetaData, + PriceTableResolver $priceTableResolver, + DimensionFactory $dimensionFactory ) { $this->resource = $resourceModel; $this->storeManager = $storeManager; $this->productMetaData = $productMetaData; + $this->priceTableResolver = $priceTableResolver; + $this->dimensionFactory = $dimensionFactory; } /** @@ -56,27 +83,62 @@ public function __construct( * @return array * @throws \Magento\Framework\Exception\NoSuchEntityException */ - public function loadPriceData($storeId, array $productIds) + public function loadPriceData(int $storeId, array $productIds): array { $entityIdField = $this->productMetaData->get()->getIdentifierField(); - $websiteId = $this->getStore($storeId)->getWebsiteId(); + $websiteId = (int)$this->getStore($storeId)->getWebsiteId(); + + // only default customer group Id is supported now + $customerGroupId = 0; + $priceIndexTableName = $this->getPriceIndexTableName($websiteId, $customerGroupId); $select = $this->getConnection()->select() ->from( - ['p' => $this->resource->getTableName('catalog_product_index_price')], + ['p' => $priceIndexTableName], [ $entityIdField, 'price', 'final_price', ] ) - ->where('p.customer_group_id = 0') + ->where('p.customer_group_id = ?', $customerGroupId) ->where('p.website_id = ?', $websiteId) ->where("p.$entityIdField IN (?)", $productIds); return $this->getConnection()->fetchAll($select); } + /** + * @param int $websiteId + * @param int $customerGroupId + * + * @return string + */ + private function getPriceIndexTableName(int $websiteId, int $customerGroupId): string + { + $key = $websiteId . '_' . $customerGroupId; + + if (!isset($this->priceIndexTableName[$key])) { + $priceIndexTableName = $this->priceTableResolver->resolve( + 'catalog_product_index_price', + [ + $this->dimensionFactory->create( + WebsiteDimensionProvider::DIMENSION_NAME, + (string)$websiteId + ), + $this->dimensionFactory->create( + CustomerGroupDimensionProvider::DIMENSION_NAME, + (string)$customerGroupId + ), + ] + ); + + $this->priceIndexTableName[$key] = (string)$priceIndexTableName; + } + + return $this->priceIndexTableName[$key]; + } + /** * @param int $storeId * diff --git a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/TierPrices.php b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/TierPrices.php index fcf3d808..dc0e8657 100644 --- a/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/TierPrices.php +++ b/src/module-vsbridge-indexer-catalog/Model/ResourceModel/Product/TierPrices.php @@ -32,6 +32,7 @@ class TierPrices * TierPrices constructor. * * @param ResourceConnection $resourceModel + * @param ProductMetaData $productMetaData */ public function __construct( ResourceConnection $resourceModel, @@ -46,6 +47,7 @@ public function __construct( * @param array $linkFieldIds * * @return array + * @throws \Exception */ public function loadTierPrices($websiteId, array $linkFieldIds) { diff --git a/src/module-vsbridge-indexer-catalog/Model/TierPriceProcessor.php b/src/module-vsbridge-indexer-catalog/Model/TierPriceProcessor.php index 4a190d50..c06c440a 100644 --- a/src/module-vsbridge-indexer-catalog/Model/TierPriceProcessor.php +++ b/src/module-vsbridge-indexer-catalog/Model/TierPriceProcessor.php @@ -77,7 +77,7 @@ public function syncTierPrices() /** * @param array $indexData - * @param $storeId + * @param int $storeId * * @return array * @throws \Exception diff --git a/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateCategoryData.php b/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateCategoryData.php index 6316f58a..3fc5652a 100644 --- a/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateCategoryData.php +++ b/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Category/Save/UpdateCategoryData.php @@ -35,6 +35,8 @@ public function __construct(CategoryProcessor $processor) * Reindex data after product save/delete resource commit * * @param Category $category + * + * @return void */ public function afterReindex(Category $category) { diff --git a/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Product/Save/UpdateProductData.php b/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Product/Save/UpdateProductData.php index 98a892c6..0dab83d9 100644 --- a/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Product/Save/UpdateProductData.php +++ b/src/module-vsbridge-indexer-catalog/Plugin/Indexer/Product/Save/UpdateProductData.php @@ -33,7 +33,10 @@ public function __construct(ProductProcessor $processor) /** * Reindex data after product save/delete resource commit + * * @param Product $product + * + * @return void */ public function afterReindex(Product $product) { diff --git a/src/module-vsbridge-indexer-catalog/etc/di.xml b/src/module-vsbridge-indexer-catalog/etc/di.xml index cc37ab83..b6bd26d7 100644 --- a/src/module-vsbridge-indexer-catalog/etc/di.xml +++ b/src/module-vsbridge-indexer-catalog/etc/di.xml @@ -50,13 +50,31 @@ - Divante\VsbridgeIndexerCatalog\Indexer\CategoryDataFilterVirtual + + + + sort_order + option_id + option_type_id + + + price + + + + + + Divante\VsbridgeIndexerCatalog\Indexer\CustomOptionsDataFilterVirtual + + + diff --git a/src/module-vsbridge-indexer-catalog/etc/mview.xml b/src/module-vsbridge-indexer-catalog/etc/mview.xml index ca74600e..291cab2b 100644 --- a/src/module-vsbridge-indexer-catalog/etc/mview.xml +++ b/src/module-vsbridge-indexer-catalog/etc/mview.xml @@ -11,7 +11,7 @@
-
+
diff --git a/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml index 95e82331..6d8ef766 100644 --- a/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml +++ b/src/module-vsbridge-indexer-catalog/etc/vsbridge_indices.xml @@ -12,6 +12,7 @@ Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ProductLinksDataDivante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\InventoryDivante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\ConfigurableData + Divante\VsbridgeIndexerCatalog\Model\Indexer\DataProvider\Product\CustomOptions diff --git a/src/module-vsbridge-indexer-core/Indexer/DataFilter.php b/src/module-vsbridge-indexer-core/Indexer/DataFilter.php index 265c24e5..b6ec90eb 100644 --- a/src/module-vsbridge-indexer-core/Indexer/DataFilter.php +++ b/src/module-vsbridge-indexer-core/Indexer/DataFilter.php @@ -18,14 +18,23 @@ class DataFilter */ private $integerProperties = []; + /** + * @var array + */ + private $floatProperties = []; + /** * DataFilter constructor. * * @param array $integerProperties + * @param array $floatProperties */ - public function __construct(array $integerProperties = []) - { + public function __construct( + array $integerProperties = [], + array $floatProperties = [] + ) { $this->integerProperties = $integerProperties; + $this->floatProperties = $floatProperties; } /** @@ -45,6 +54,8 @@ public function execute(array $dtoToFilter, array $blackList = null) } else { if (in_array($key, $this->integerProperties)) { $dtoToFilter[$key] = (int)$val; + } elseif (in_array($key, $this->floatProperties)) { + $dtoToFilter[$key] = (float)$val; } } } diff --git a/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php b/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php index d772614b..5505ddfe 100644 --- a/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php +++ b/src/module-vsbridge-indexer-core/Indexer/GenericIndexerHandler.php @@ -118,7 +118,7 @@ public function saveIndex(\Traversable $documents, StoreInterface $store) foreach ($this->batch->getItems($documents, $this->getBatchSize()) as $docs) { foreach ($type->getDataProviders() as $dataProvider) { if (!empty($docs)) { - $docs = $dataProvider->addData($docs, $store->getId()); + $docs = $dataProvider->addData($docs, (int)$store->getId()); } } @@ -161,7 +161,7 @@ public function cleanUpByTransactionKey(StoreInterface $store, array $docIds = n $query = ['query' => ['bool' => $transactionKeyQuery]]; if ($docIds) { - $query['query']['bool']['must']['terms'] = ['_id' => $docIds]; + $query['query']['bool']['must']['terms'] = ['_id' => array_values($docIds)]; } $query = [ diff --git a/src/module-vsbridge-indexer-review/Index/Mapping/Review.php b/src/module-vsbridge-indexer-review/Index/Mapping/Review.php new file mode 100644 index 00000000..4b1a8e73 --- /dev/null +++ b/src/module-vsbridge-indexer-review/Index/Mapping/Review.php @@ -0,0 +1,88 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerReview\Index\Mapping; + +use Divante\VsbridgeIndexerCore\Api\Mapping\FieldInterface; +use Divante\VsbridgeIndexerCore\Api\MappingInterface; +use Magento\Framework\Event\ManagerInterface as EventManager; +use Magento\Framework\Stdlib\DateTime; + +/** + * Class Review + */ +class Review implements MappingInterface +{ + + /** + * @var EventManager + */ + private $eventManager; + + /** + * @var string + */ + private $type; + + /** + * CmsBlock constructor. + * + * @param EventManager $eventManager + */ + public function __construct(EventManager $eventManager) + { + $this->eventManager = $eventManager; + } + + /** + * @inheritdoc + */ + public function setType(string $type) + { + $this->type = $type; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @inheritdoc + */ + public function getMappingProperties() + { + $properties = [ + 'created_at' => [ + 'type' => FieldInterface::TYPE_DATE, + 'format' => FieldInterface::DATE_FORMAT, + ], + 'id' => ['type' => FieldInterface::TYPE_LONG], + 'product_id' => ['type' => FieldInterface::TYPE_LONG], + 'title' => ['type' => FieldInterface::TYPE_TEXT], + 'detail' => ['type' => FieldInterface::TYPE_TEXT], + 'nickname' => ['type' => FieldInterface::TYPE_TEXT], + 'review_status' => ['type' => FieldInterface::TYPE_INTEGER], + 'customer_id' => ['type' => FieldInterface::TYPE_INTEGER], + ]; + + $mappingObject = new \Magento\Framework\DataObject(); + $mappingObject->setData('properties', $properties); + + $this->eventManager->dispatch( + 'elasticsearch_review_mapping_properties', + ['mapping' => $mappingObject] + ); + + return $mappingObject->getData(); + } +} diff --git a/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php b/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php new file mode 100644 index 00000000..92449d21 --- /dev/null +++ b/src/module-vsbridge-indexer-review/Model/Indexer/Action/Review.php @@ -0,0 +1,60 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerReview\Model\Indexer\Action; + +use Divante\VsbridgeIndexerReview\ResourceModel\Review as ResourceModel; + +/** + * Class Review + */ +class Review +{ + /** + * @var ResourceModel + */ + private $resourceModel; + + /** + * Review constructor. + * + * @param ResourceModel $resource + */ + public function __construct(ResourceModel $resource) + { + $this->resourceModel = $resource; + } + + /** + * @param int $storeId + * @param array $reviewIds + * + * @return \Traversable + */ + public function rebuild(int $storeId = 1, array $reviewIds = []) + { + $lastReviewId = 0; + + do { + $reviews = $this->resourceModel->getReviews($storeId, $reviewIds, $lastReviewId); + + foreach ($reviews as $review) { + $review['id'] = (int)($review['review_id']); + $review['product_id'] = (int)$review['entity_pk_value']; + $review['review_status'] = $review['status_id']; + unset($review['review_id'], $review['entity_pk_value'], $review['status_id']); + $lastReviewId = $review['id']; + + yield $lastReviewId => $review; + } + } while (!empty($reviews)); + } +} diff --git a/src/module-vsbridge-indexer-review/Model/Indexer/Review.php b/src/module-vsbridge-indexer-review/Model/Indexer/Review.php new file mode 100644 index 00000000..09d43c24 --- /dev/null +++ b/src/module-vsbridge-indexer-review/Model/Indexer/Review.php @@ -0,0 +1,94 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerReview\Model\Indexer; + +use Divante\VsbridgeIndexerCore\Indexer\GenericIndexerHandler; +use Divante\VsbridgeIndexerCore\Indexer\StoreManager; +use Divante\VsbridgeIndexerReview\Model\Indexer\Action\Review as Action; + +/** + * Class Review + */ +class Review implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface +{ + /** + * @var Action + */ + private $action; + + /** + * @var GenericIndexerHandler + */ + private $indexHandler; + + /** + * @var StoreManager + */ + private $storeManager; + + /** + * Review constructor. + * + * @param GenericIndexerHandler $indexerHandler + * @param StoreManager $storeManager + * @param Action $action + */ + public function __construct( + GenericIndexerHandler $indexerHandler, + StoreManager $storeManager, + Action $action + ) { + $this->action = $action; + $this->storeManager = $storeManager; + $this->indexHandler = $indexerHandler; + } + + /** + * @inheritdoc + */ + public function execute($ids) + { + $stores = $this->storeManager->getStores(); + + foreach ($stores as $store) { + $this->indexHandler->saveIndex($this->action->rebuild((int)$store->getId(), $ids), $store); + $this->indexHandler->cleanUpByTransactionKey($store, $ids); + } + } + + /** + * @inheritdoc + */ + public function executeFull() + { + $stores = $this->storeManager->getStores(); + + foreach ($stores as $store) { + $this->indexHandler->saveIndex($this->action->rebuild((int)$store->getId()), $store); + $this->indexHandler->cleanUpByTransactionKey($store); + } + } + + /** + * @inheritdoc + */ + public function executeList(array $ids) + { + $this->execute($ids); + } + + /** + * @inheritdoc + */ + public function executeRow($id) + { + $this->execute([$id]); + } +} diff --git a/src/module-vsbridge-indexer-review/Model/Indexer/ReviewProcessor.php b/src/module-vsbridge-indexer-review/Model/Indexer/ReviewProcessor.php new file mode 100644 index 00000000..0b46dc62 --- /dev/null +++ b/src/module-vsbridge-indexer-review/Model/Indexer/ReviewProcessor.php @@ -0,0 +1,21 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerReview\Model\Indexer; + +/** + * Class ReviewProcessor + */ +class ReviewProcessor extends \Magento\Framework\Indexer\AbstractProcessor +{ + /** + * Indexer ID + */ + const INDEXER_ID = 'vsbridge_review_indexer'; +} diff --git a/src/module-vsbridge-indexer-review/Plugin/Indexer/Review/Save/UpdateReview.php b/src/module-vsbridge-indexer-review/Plugin/Indexer/Review/Save/UpdateReview.php new file mode 100644 index 00000000..fae96937 --- /dev/null +++ b/src/module-vsbridge-indexer-review/Plugin/Indexer/Review/Save/UpdateReview.php @@ -0,0 +1,65 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerReview\Plugin\Indexer\Review\Save; + +use Divante\VsbridgeIndexerReview\Model\Indexer\ReviewProcessor; +use Magento\Review\Model\Review; + +/** + * Class UpdateReview + */ +class UpdateReview +{ + + /** + * @var ReviewProcessor + */ + private $reviewProcessor; + + /** + * Save constructor. + * + * @param ReviewProcessor $reviewProcessor + */ + public function __construct(ReviewProcessor $reviewProcessor) + { + $this->reviewProcessor = $reviewProcessor; + } + + /** + * @param Review $subject + * @param Review $result + * + * @return Review + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAfterSave(Review $subject, Review $result) + { + $result->getResource()->addCommitCallback(function () use ($result) { + $this->reviewProcessor->reindexRow($result->getId()); + }); + + return $result; + } + + /** + * @param Review $subject + * @param Review $result + * + * @return Review + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterAfterDeleteCommit(Review $subject, Review $result) + { + $this->reviewProcessor->reindexRow($result->getId()); + + return $result; + } +} diff --git a/src/module-vsbridge-indexer-review/ResourceModel/Review.php b/src/module-vsbridge-indexer-review/ResourceModel/Review.php new file mode 100644 index 00000000..c5d07e8f --- /dev/null +++ b/src/module-vsbridge-indexer-review/ResourceModel/Review.php @@ -0,0 +1,132 @@ + + * @copyright 2019 Divante Sp. z o.o. + * @license See LICENSE_DIVANTE.txt for license details. + */ + +namespace Divante\VsbridgeIndexerReview\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; + +/** + * Class Review + */ +class Review +{ + + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var array + */ + private $entityIdByCode = []; + + /** + * Rates constructor. + * + * @param ResourceConnection $resourceConnection + */ + public function __construct(ResourceConnection $resourceConnection) + { + $this->resource = $resourceConnection; + } + + /** + * @param int $storeId + * @param array $reviewIds + * @param int $fromId + * @param int $limit + * + * @return array + */ + public function getReviews(int $storeId = 1, array $reviewIds = [], int $fromId = 0, int $limit = 1000): array + { + $select = $this->getConnection()->select() + ->from( + ['main_table' => $this->resource->getTableName('review')], + [ + 'review_id', + 'created_at', + 'entity_pk_value', + 'status_id', + ] + ); + + $select->joinLeft( + ['store' => $this->resource->getTableName('review_store')], + 'main_table.review_id = store.review_id' + )->where('store.store_id = ?', $storeId); + + if (!empty($reviewIds)) { + $select->where('main_table.review_id IN (?)', $reviewIds); + } + + $entityId = $this->getEntityIdByCode(\Magento\Review\Model\Review::ENTITY_PRODUCT_CODE); + $select->where('entity_id = ? ', $entityId); + $select = $this->joinReviewDetails($select); + + $select->where('main_table.status_id = ?', 1); + $select->where('main_table.review_id > ?', $fromId); + $select->order('main_table.review_id'); + $select->limit($limit); + + return $this->getConnection()->fetchAssoc($select); + } + + /** + * @param Select $select + * + * @return Select + */ + private function joinReviewDetails(Select $select): Select + { + $select->joinLeft( + ['detail' => $this->resource->getTableName('review_detail')], + 'main_table.review_id = detail.review_id', + [ + 'title', + 'nickname', + 'customer_id', + 'detail', + ] + ); + + return $select; + } + + /** + * @param string $entityCode + * + * @return int + */ + private function getEntityIdByCode(string $entityCode): int + { + if (!isset($this->entityIdByCode[$entityCode])) { + $connection = $this->getConnection(); + $select = $connection->select() + ->from('review_entity', ['entity_id']) + ->where('entity_code = :entity_code'); + + $this->entityIdByCode[$entityCode] = (int) $connection->fetchOne($select, [':entity_code' => $entityCode]); + } + + return $this->entityIdByCode[$entityCode]; + } + + /** + * @return \Magento\Framework\DB\Adapter\AdapterInterface + */ + private function getConnection() + { + return $this->resource->getConnection(); + } +} diff --git a/src/module-vsbridge-indexer-review/composer.json b/src/module-vsbridge-indexer-review/composer.json new file mode 100644 index 00000000..3b55e49e --- /dev/null +++ b/src/module-vsbridge-indexer-review/composer.json @@ -0,0 +1,21 @@ +{ + "name": "divante/module-vsbridge-indexer-review", + "type": "magento2-module", + "authors":[ + { + "name":"Agata", + "email":"afirlejczyk@divante.pl" + } + ], + "require": { + "elasticsearch/elasticsearch": "~5.0" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Divante\\VsbridgeIndexerCore\\": "" + } + } +} diff --git a/src/module-vsbridge-indexer-review/etc/di.xml b/src/module-vsbridge-indexer-review/etc/di.xml new file mode 100644 index 00000000..06796ed6 --- /dev/null +++ b/src/module-vsbridge-indexer-review/etc/di.xml @@ -0,0 +1,20 @@ + + + + + vue_storefront_catalog + review + + + + + + Divante\VsbridgeIndexerReview\Indexer\ReviewIndexerHandlerVirtual + + + + + + + diff --git a/src/module-vsbridge-indexer-review/etc/indexer.xml b/src/module-vsbridge-indexer-review/etc/indexer.xml new file mode 100644 index 00000000..3ed4248d --- /dev/null +++ b/src/module-vsbridge-indexer-review/etc/indexer.xml @@ -0,0 +1,13 @@ + + + + + Vsbridge Review Indexer + Update Reviews in Elastic + + diff --git a/src/module-vsbridge-indexer-review/etc/module.xml b/src/module-vsbridge-indexer-review/etc/module.xml new file mode 100644 index 00000000..4cc4aa62 --- /dev/null +++ b/src/module-vsbridge-indexer-review/etc/module.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/module-vsbridge-indexer-review/etc/mview.xml b/src/module-vsbridge-indexer-review/etc/mview.xml new file mode 100644 index 00000000..16d266ab --- /dev/null +++ b/src/module-vsbridge-indexer-review/etc/mview.xml @@ -0,0 +1,14 @@ + + + + + +
+ + + diff --git a/src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml b/src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml new file mode 100644 index 00000000..fde541a6 --- /dev/null +++ b/src/module-vsbridge-indexer-review/etc/vsbridge_indices.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/src/module-vsbridge-indexer-review/registration.php b/src/module-vsbridge-indexer-review/registration.php new file mode 100644 index 00000000..d77bd442 --- /dev/null +++ b/src/module-vsbridge-indexer-review/registration.php @@ -0,0 +1,9 @@ + - vue_storefront_catalog @@ -10,7 +10,7 @@ - Divante\VsbridgeIndexerTax\Indexer\CmsIndexerHandlerVirtual + Divante\VsbridgeIndexerTax\Indexer\TaxRuleIndexerHandlerVirtual