From 6930423dff3802f35c53db7db9ace6bb625c87e0 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Tue, 22 Aug 2023 11:29:58 +0200 Subject: [PATCH 1/4] Refactor content flow --- Api/Config/System/ContentInterface.php | 7 + Api/Content/DataInterface.php | 53 ++-- Api/Content/RepositoryInterface.php | 30 +- Console/Command/ContentAdd.php | 47 +-- Console/Command/ContentInvalidate.php | 58 ++-- Console/Command/ContentUpdate.php | 30 +- Console/CommandOptions/ContentAdd.php | 6 +- Console/CommandOptions/ContentInvalidate.php | 6 +- Console/CommandOptions/ContentUpdate.php | 6 +- Controller/Adminhtml/Content/Add.php | 11 +- Controller/Adminhtml/Content/Invalidate.php | 9 +- Controller/Adminhtml/Content/Update.php | 26 +- Controller/Adminhtml/Product/MassQueue.php | 72 ----- Model/Command/ContentAdd.php | 209 ++++++++----- Model/Command/ContentInvalidate.php | 42 +-- Model/Command/ContentUpdate.php | 286 +++++++----------- Model/Config/Repository.php | 2 +- Model/Config/System/ContentRepository.php | 57 ++-- Model/Content/Data.php | 37 +-- Model/Content/Repository.php | 179 ++--------- Model/Content/ResourceModel.php | 24 +- Model/Cron/ContentMaintenance.php | 212 ++----------- Model/Cron/ContentUpdate.php | 89 ++---- Model/ProductData/Repository.php | 33 +- Observer/InvalidateProduct.php | 32 +- .../Data/AttributeMapper.php | 221 ++++++++------ .../AttributeCollector/Data/Category.php | 91 +++--- .../Data/ConfigurableKey.php | 22 +- .../AttributeCollector/Data/Image.php | 34 ++- .../AttributeCollector/Data/Parents.php | 3 +- .../AttributeCollector/Data/Price.php | 58 ++-- .../AttributeCollector/Data/Stock.php | 158 +++++----- .../AttributeCollector/Data/Url.php | 53 +++- Service/ProductData/Data.php | 78 +++-- Service/ProductData/Filter.php | 141 ++++++--- Service/ProductData/Type.php | 40 +-- etc/crontab.xml | 12 +- etc/db_schema.xml | 111 ++----- etc/db_schema_whitelist.json | 13 +- etc/di.xml | 11 +- view/adminhtml/layout/default.xml | 11 - .../ui_component/product_listing.xml | 19 -- 42 files changed, 1100 insertions(+), 1539 deletions(-) delete mode 100755 Controller/Adminhtml/Product/MassQueue.php delete mode 100755 view/adminhtml/layout/default.xml delete mode 100755 view/adminhtml/ui_component/product_listing.xml diff --git a/Api/Config/System/ContentInterface.php b/Api/Config/System/ContentInterface.php index 9d68868..ead1c2a 100755 --- a/Api/Config/System/ContentInterface.php +++ b/Api/Config/System/ContentInterface.php @@ -69,6 +69,13 @@ interface ContentInterface extends RepositoryInterface */ public function isEnabled(int $storeId = null): bool; + /** + * Get all enabled store Ids for content update + ** + * @return array + */ + public function getContentEnabledStoreIds(): array; + /** * Returns array of attributes * diff --git a/Api/Content/DataInterface.php b/Api/Content/DataInterface.php index 06a22c0..d3db217 100755 --- a/Api/Content/DataInterface.php +++ b/Api/Content/DataInterface.php @@ -16,63 +16,50 @@ interface DataInterface extends ExtensibleDataInterface { - /**#@+ + /** * Constants for keys of data array. Identical to the name of the getter in snake case */ public const ENTITY_ID = 'entity_id'; public const STORE_ID = 'store_id'; - public const CONTENT_ID = 'content_id'; + public const PRODUCT_ID = 'product_id'; public const PARENT_ID = 'parent_id'; - public const CREATED_AT = 'created_at'; public const UPDATED_AT = 'updated_at'; public const UPDATE_MSG = 'update_msg'; public const UPDATE_ATTEMPTS = 'update_attempts'; public const STATUS = 'status'; - /**#@-*/ /** - * @return string + * @return int */ - public function getContentId() : string; + public function getProductId(): int; /** - * @param string $contentId + * @param int $productId * @return $this */ - public function setContentId(string $contentId) : self; + public function setProductId(int $productId): self; /** - * @return string + * @return ?int */ - public function getParentId() : string; + public function getParentId(): ?int; /** - * @param string $parentId + * @param ?int $parentId * @return $this */ - public function setParentId(string $parentId) : self; + public function setParentId(?int $parentId): self; /** * @return int */ - public function getStoreId() : int; + public function getStoreId(): int; /** * @param int $storeId * @return $this */ - public function setStoreId(int $storeId) : self; - - /** - * @return string - */ - public function getCreatedAt() : string; - - /** - * @param string $createdAt - * @return $this - */ - public function setCreatedAt(string $createdAt) : self; + public function setStoreId(int $storeId): self; /** * @return string @@ -83,38 +70,38 @@ public function getUpdatedAt(): string; * @param string $updatedAt * @return $this */ - public function setUpdatedAt(string $updatedAt) : self; + public function setUpdatedAt(string $updatedAt): self; /** * @return string */ - public function getUpdateMsg() : string; + public function getUpdateMsg(): string; /** * @param string $updateMsg * @return $this */ - public function setUpdateMsg(string $updateMsg) : self; + public function setUpdateMsg(string $updateMsg): self; /** - * @return string + * @return int */ - public function getUpdateAttempts() : string; + public function getUpdateAttempts(): int; /** * @param int $updateAttempts * @return $this */ - public function setUpdateAttempts(int $updateAttempts) : self; + public function setUpdateAttempts(int $updateAttempts): self; /** * @return string */ - public function getStatus() : string; + public function getStatus(): string; /** * @param string $status * @return $this */ - public function setStatus(string $status) : self; + public function setStatus(string $status): self; } diff --git a/Api/Content/RepositoryInterface.php b/Api/Content/RepositoryInterface.php index 0464b80..4b7540c 100755 --- a/Api/Content/RepositoryInterface.php +++ b/Api/Content/RepositoryInterface.php @@ -7,13 +7,10 @@ namespace Datatrics\Connect\Api\Content; +use Datatrics\Connect\Api\Content\DataInterface as ContentData; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Datatrics\Connect\Api\Content\DataInterface - as ContentData; -use Datatrics\Connect\Api\Content\SearchResultsInterface; -use Magento\Customer\Model\Customer; /** * Interface Repository @@ -43,12 +40,13 @@ interface RepositoryInterface public const COULD_NOT_SAVE_EXCEPTION = 'Could not save the entity: %1'; /** - * + * Content status key-pair */ public const STATUS = [ 'queued' => 'Queued for Update', 'synced' => 'Synced', 'error' => 'Error', + 'skipped' => 'Skipped', 'failed' => 'Failed' ]; @@ -60,24 +58,24 @@ interface RepositoryInterface * @return ContentData * @throws LocalizedException */ - public function get(int $entityId) : ContentData; + public function get(int $entityId): ContentData; /** * Return Content object * * @return ContentData */ - public function create(); + public function create(): DataInterface; /** - * Retrieves an Content matching the specified criteria. + * Retrieves a Content matching the specified criteria. * * @param SearchCriteriaInterface $searchCriteria * * @return SearchResultsInterface * @throws LocalizedException */ - public function getList(SearchCriteriaInterface $searchCriteria) : SearchResultsInterface; + public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface; /** * Register entity to delete @@ -87,18 +85,18 @@ public function getList(SearchCriteriaInterface $searchCriteria) : SearchResults * @return bool true on success * @throws LocalizedException */ - public function delete(ContentData $entity) : bool; + public function delete(ContentData $entity): bool; /** * Deletes a Content entity by ID * - * @param int $entity + * @param int $entityId * * @return bool true on success * @throws NoSuchEntityException * @throws LocalizedException */ - public function deleteById(int $entity) : bool; + public function deleteById(int $entityId): bool; /** * Perform persist operations for one entity @@ -108,11 +106,5 @@ public function deleteById(int $entity) : bool; * @return ContentData * @throws LocalizedException */ - public function save(ContentData $entity) : ContentData; - - /** - * @param array $productIds - * @return mixed - */ - public function prepareContentData(array $productIds); + public function save(ContentData $entity): ContentData; } diff --git a/Console/Command/ContentAdd.php b/Console/Command/ContentAdd.php index df105f6..d3a584e 100755 --- a/Console/Command/ContentAdd.php +++ b/Console/Command/ContentAdd.php @@ -7,12 +7,14 @@ namespace Datatrics\Connect\Console\Command; +use Datatrics\Connect\Api\Config\System\ContentInterface as ConfigProvider; +use Datatrics\Connect\Console\CommandOptions\ContentAdd as ContentAddOptions; +use Datatrics\Connect\Console\CommandOptions\OptionKeys; +use Datatrics\Connect\Model\Command\ContentAdd as ContentAddProcessing; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; -use Datatrics\Connect\Model\Command\ContentAdd as ContentAddProcessing; -use Datatrics\Connect\Console\CommandOptions\ContentAdd as ContentAddOptions; +use Symfony\Component\Console\Output\OutputInterface; /** * Class ContentAdd @@ -31,57 +33,51 @@ class ContentAdd extends Command * @var ContentAddOptions */ private $options; - /** * @var ContentAddProcessing */ private $contentAddProcessing; - /** - * @var InputValidator + * @var ConfigProvider */ - private $inputValidator; + private $configProvider; /** * SalesUpdate constructor. + * @param ConfigProvider $configProvider * @param ContentAddOptions $options * @param ContentAddProcessing $contentAddProcessing - * @param InputValidator $inputValidator */ public function __construct( + ConfigProvider $configProvider, ContentAddOptions $options, - ContentAddProcessing $contentAddProcessing, - InputValidator $inputValidator + ContentAddProcessing $contentAddProcessing ) { $this->options = $options; $this->contentAddProcessing = $contentAddProcessing; - $this->inputValidator = $inputValidator; + $this->configProvider = $configProvider; parent::__construct(); } /** - * {@inheritdoc} + * @inheritdoc */ public function configure() { $this->setName(self::COMMAND_NAME); $this->setDescription('Collect content to tables'); $this->setDefinition($this->options->getOptionsList()); - $this->setHelp( - <<contentAddProcessing->run($input, $output); + $storeIds = $this->getStoreIds($input); + $this->contentAddProcessing->run($storeIds, $output); $output->writeln('Done'); } catch (\InvalidArgumentException $exception) { $output->writeln('' . $exception->getMessage() . ''); @@ -93,4 +89,15 @@ public function execute(InputInterface $input, OutputInterface $output) return Cli::RETURN_SUCCESS; } + + /** + * @param InputInterface $input + * @return array + */ + private function getStoreIds(InputInterface $input): array + { + return $input->getOption(OptionKeys::STORE_ID) + ? [(int)$input->getOption(OptionKeys::STORE_ID)] + : $this->configProvider->getContentEnabledStoreIds(); + } } diff --git a/Console/Command/ContentInvalidate.php b/Console/Command/ContentInvalidate.php index 9d18872..7c256e2 100755 --- a/Console/Command/ContentInvalidate.php +++ b/Console/Command/ContentInvalidate.php @@ -7,12 +7,14 @@ namespace Datatrics\Connect\Console\Command; +use Datatrics\Connect\Api\Config\System\ContentInterface as ConfigProvider; +use Datatrics\Connect\Console\CommandOptions\ContentInvalidate as ContentInvalidateOptions; +use Datatrics\Connect\Console\CommandOptions\OptionKeys; +use Datatrics\Connect\Model\Command\ContentInvalidate as ContentInvalidateProcessing; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; -use Datatrics\Connect\Model\Command\ContentInvalidate as ContentInvalidateProcessing; -use Datatrics\Connect\Console\CommandOptions\ContentInvalidate as ContentInvalidateOptions; +use Symfony\Component\Console\Output\OutputInterface; /** * Class ContentInvalidate @@ -31,58 +33,54 @@ class ContentInvalidate extends Command * @var ContentInvalidateOptions */ private $options; - /** * @var ContentInvalidateProcessing */ private $contentInvalidateProcessing; - /** - * @var InputValidator + * @var ConfigProvider */ - private $inputValidator; + private $configProvider; /** - * SalesUpdate constructor. * @param ContentInvalidateOptions $options * @param ContentInvalidateProcessing $contentInvalidateProcessing - * @param InputValidator $inputValidator + * @param ConfigProvider $configProvider */ public function __construct( ContentInvalidateOptions $options, ContentInvalidateProcessing $contentInvalidateProcessing, - InputValidator $inputValidator + ConfigProvider $configProvider ) { $this->options = $options; $this->contentInvalidateProcessing = $contentInvalidateProcessing; - $this->inputValidator = $inputValidator; + $this->configProvider = $configProvider; parent::__construct(); } /** - * {@inheritdoc} + * @inheritdoc */ public function configure() { $this->setName(self::COMMAND_NAME); $this->setDescription('Invalidate content'); $this->setDefinition($this->options->getOptionsList()); - $this->setHelp( - <<contentInvalidateProcessing->run($input, $output); + $invalidated = $this->contentInvalidateProcessing->run( + $this->getStoreIds($input), + $this->getProductIds($input) + ); + if (!$invalidated) { $output->writeln('No items to invalidate'); } else { @@ -105,4 +103,24 @@ public function execute(InputInterface $input, OutputInterface $output) return Cli::RETURN_SUCCESS; } + + /** + * @param InputInterface $input + * @return array + */ + private function getStoreIds(InputInterface $input): array + { + return $input->getOption(OptionKeys::STORE_ID) + ? [(int)$input->getOption(OptionKeys::STORE_ID)] + : $this->configProvider->getContentEnabledStoreIds(); + } + + /** + * @param InputInterface $input + * @return array|null + */ + private function getProductIds(InputInterface $input): ?array + { + return $input->getArguments()[OptionKeys::PRODUCT_ID] ?? null; + } } diff --git a/Console/Command/ContentUpdate.php b/Console/Command/ContentUpdate.php index f1b7365..eef966a 100755 --- a/Console/Command/ContentUpdate.php +++ b/Console/Command/ContentUpdate.php @@ -7,12 +7,12 @@ namespace Datatrics\Connect\Console\Command; +use Datatrics\Connect\Console\CommandOptions\ContentUpdate as ContentUpdateOptions; +use Datatrics\Connect\Model\Command\ContentUpdate as ContentUpdateProcessing; use Magento\Framework\Console\Cli; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; -use Datatrics\Connect\Model\Command\ContentUpdate as ContentUpdateProcessing; -use Datatrics\Connect\Console\CommandOptions\ContentUpdate as ContentUpdateOptions; +use Symfony\Component\Console\Output\OutputInterface; /** * Class ContentUpdate @@ -31,54 +31,40 @@ class ContentUpdate extends Command * @var ContentUpdateOptions */ private $options; - /** * @var ContentUpdateProcessing */ private $contentUpdateProcessing; /** - * @var InputValidator - */ - private $inputValidator; - - /** - * SalesUpdate constructor. + * ContentUpdate constructor. * @param ContentUpdateOptions $options * @param ContentUpdateProcessing $contentUpdateProcessing - * @param InputValidator $inputValidator */ public function __construct( ContentUpdateOptions $options, - ContentUpdateProcessing $contentUpdateProcessing, - InputValidator $inputValidator + ContentUpdateProcessing $contentUpdateProcessing ) { $this->options = $options; $this->contentUpdateProcessing = $contentUpdateProcessing; - $this->inputValidator = $inputValidator; parent::__construct(); } /** - * {@inheritdoc} + * @inheritdoc */ public function configure() { $this->setName(self::COMMAND_NAME); $this->setDescription('Push content to platform'); $this->setDefinition($this->options->getOptionsList()); - $this->setHelp( - <<getOption('store-id') === null) { throw new \InvalidArgumentException( diff --git a/Console/CommandOptions/ContentAdd.php b/Console/CommandOptions/ContentAdd.php index 2088be2..a2104fd 100755 --- a/Console/CommandOptions/ContentAdd.php +++ b/Console/CommandOptions/ContentAdd.php @@ -23,7 +23,7 @@ class ContentAdd extends OptionKeys * * @return array */ - public function getOptionsList() + public function getOptionsList(): array { return array_merge( $this->getBasicOptions(), @@ -37,7 +37,7 @@ public function getOptionsList() * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function getBasicOptions() + private function getBasicOptions(): array { return [ new InputOption( @@ -54,7 +54,7 @@ private function getBasicOptions() * * @return array */ - private function getSkipOptions() + private function getSkipOptions(): array { return []; } diff --git a/Console/CommandOptions/ContentInvalidate.php b/Console/CommandOptions/ContentInvalidate.php index 9cfe097..822affd 100755 --- a/Console/CommandOptions/ContentInvalidate.php +++ b/Console/CommandOptions/ContentInvalidate.php @@ -24,7 +24,7 @@ class ContentInvalidate extends OptionKeys * * @return array */ - public function getOptionsList() + public function getOptionsList(): array { return array_merge( $this->getBasicOptions(), @@ -38,7 +38,7 @@ public function getOptionsList() * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function getBasicOptions() + private function getBasicOptions(): array { return [ new InputOption( @@ -60,7 +60,7 @@ private function getBasicOptions() * * @return array */ - private function getSkipOptions() + private function getSkipOptions(): array { return []; } diff --git a/Console/CommandOptions/ContentUpdate.php b/Console/CommandOptions/ContentUpdate.php index 306d52c..460db8f 100755 --- a/Console/CommandOptions/ContentUpdate.php +++ b/Console/CommandOptions/ContentUpdate.php @@ -24,7 +24,7 @@ class ContentUpdate extends OptionKeys * * @return array */ - public function getOptionsList() + public function getOptionsList(): array { return array_merge( $this->getBasicOptions(), @@ -38,7 +38,7 @@ public function getOptionsList() * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function getBasicOptions() + private function getBasicOptions(): array { return [ new InputOption( @@ -78,7 +78,7 @@ private function getBasicOptions() * * @return array */ - private function getSkipOptions() + private function getSkipOptions(): array { return []; } diff --git a/Controller/Adminhtml/Content/Add.php b/Controller/Adminhtml/Content/Add.php index 3880e54..9b238eb 100755 --- a/Controller/Adminhtml/Content/Add.php +++ b/Controller/Adminhtml/Content/Add.php @@ -9,13 +9,11 @@ use Datatrics\Connect\Api\Config\System\ContentInterface as ContentConfigRepository; use Datatrics\Connect\Model\Command\ContentAdd; -use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; use Magento\Backend\App\Action; use Magento\Framework\App\Response\RedirectInterface; use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Controller\ResultInterface; -use Magento\Framework\Exception\LocalizedException; /** * Class Add @@ -40,10 +38,6 @@ class Add extends Action */ public const SUCCESS_MSG = '%1 product(s) were added to content table.'; - /** - * @var ContentResource - */ - private $contentResource; /** * @var ContentAdd */ @@ -61,19 +55,17 @@ class Add extends Action * Check constructor. * * @param Action\Context $context - * @param ContentResource $contentResource * @param ContentAdd $contentAdd * @param ContentConfigRepository $contentConfigRepository + * @param RedirectInterface $redirect */ public function __construct( Action\Context $context, - ContentResource $contentResource, ContentAdd $contentAdd, ContentConfigRepository $contentConfigRepository, RedirectInterface $redirect ) { $this->messageManager = $context->getMessageManager(); - $this->contentResource = $contentResource; $this->contentAdd = $contentAdd; $this->contentConfigRepository = $contentConfigRepository; $this->redirect = $redirect; @@ -82,7 +74,6 @@ public function __construct( /** * @return ResponseInterface|ResultInterface - * @throws LocalizedException */ public function execute() { diff --git a/Controller/Adminhtml/Content/Invalidate.php b/Controller/Adminhtml/Content/Invalidate.php index f9e9117..4017ca7 100755 --- a/Controller/Adminhtml/Content/Invalidate.php +++ b/Controller/Adminhtml/Content/Invalidate.php @@ -8,6 +8,7 @@ namespace Datatrics\Connect\Controller\Adminhtml\Content; use Datatrics\Connect\Api\Config\System\ContentInterface as ContentConfigRepository; +use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository; use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; use Magento\Backend\App\Action; use Magento\Framework\App\Response\RedirectInterface; @@ -57,6 +58,7 @@ class Invalidate extends Action * @param Action\Context $context * @param ContentResource $contentResource * @param ContentConfigRepository $contentConfigRepository + * @param RedirectInterface $redirect */ public function __construct( Action\Context $context, @@ -88,13 +90,10 @@ public function execute() } $connection = $this->contentResource->getConnection(); - $where = [ - 'store_id = ?' => $storeId - ]; $count = $connection->update( $this->contentResource->getTable('datatrics_content_store'), - ['status' => 'Queued for Update'], - $where + ['status' => ContentRepository::STATUS['queued']], + ['store_id = ?' => $storeId] ); if ($count > 0) { diff --git a/Controller/Adminhtml/Content/Update.php b/Controller/Adminhtml/Content/Update.php index 5d9746b..f089186 100755 --- a/Controller/Adminhtml/Content/Update.php +++ b/Controller/Adminhtml/Content/Update.php @@ -9,7 +9,6 @@ use Datatrics\Connect\Api\Config\System\ContentInterface as ContentConfigRepository; use Datatrics\Connect\Model\Command\ContentUpdate; -use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; use Datatrics\Connect\Service\API\ConnectionTest; use Magento\Backend\App\Action; use Magento\Framework\App\Response\RedirectInterface; @@ -40,10 +39,6 @@ class Update extends Action */ public const SUCCESS_MSG = '%1 product(s) were updated. '; - /** - * @var ContentResource - */ - private $contentResource; /** * @var ContentUpdate */ @@ -64,21 +59,19 @@ class Update extends Action /** * Update constructor. * @param Action\Context $context - * @param ContentResource $contentResource * @param ContentUpdate $contentUpdate * @param ContentConfigRepository $contentConfigRepository * @param ConnectionTest $connectionTest + * @param RedirectInterface $redirect */ public function __construct( Action\Context $context, - ContentResource $contentResource, ContentUpdate $contentUpdate, ContentConfigRepository $contentConfigRepository, ConnectionTest $connectionTest, RedirectInterface $redirect ) { $this->messageManager = $context->getMessageManager(); - $this->contentResource = $contentResource; $this->contentUpdate = $contentUpdate; $this->contentConfigRepository = $contentConfigRepository; $this->connectionTest = $connectionTest; @@ -93,6 +86,7 @@ public function execute() { $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); $storeId = (int)$this->getRequest()->getParam('store_id'); + $dryRun = (bool)$this->getRequest()->getParam('dry'); if (!$this->contentConfigRepository->isEnabled($storeId)) { $msg = self::ERROR_MSG_ENABLED; @@ -104,20 +98,8 @@ public function execute() try { $this->connectionTest->executeByStoreId($storeId); - $connection = $this->contentResource->getConnection(); - $selectProductIds = $connection->select()->from( - $this->contentResource->getTable('datatrics_content_store'), - 'product_id' - )->where('status = :status') - ->where('store_id = :store_id') - ->limit($this->contentConfigRepository->getProcessingLimit($storeId)); - $bind = [ - ':status' => 'Queued for Update', - ':store_id' => $storeId - ]; - $productIds = $connection->fetchCol($selectProductIds, $bind); - $count = $productIds ? $this->contentUpdate->prepareData($productIds, $storeId) : 0; - + $productIds = $this->contentUpdate->getProductIds($storeId); + $count = $productIds ? $this->contentUpdate->prepareData($productIds, $storeId, $dryRun) : 0; if ($count > 0) { $msg = self::SUCCESS_MSG; $this->messageManager->addSuccessMessage(__($msg, $count)); diff --git a/Controller/Adminhtml/Product/MassQueue.php b/Controller/Adminhtml/Product/MassQueue.php deleted file mode 100755 index 47d7b6c..0000000 --- a/Controller/Adminhtml/Product/MassQueue.php +++ /dev/null @@ -1,72 +0,0 @@ -contentRepository = $contentRepository; - $this->productCollection = $productCollection; - parent::__construct($context); - } - - /** - * @return Redirect - */ - public function execute() - { - /** @var Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - - $products = $this->getRequest()->getParam('selected'); - if (!$products) { - $products = $this->productCollection->getColumnValues('entity_id'); - } - $result = $this->contentRepository->prepareContentData($products); - $this->messageManager->addSuccessMessage( - __( - '%1 product(s) added, %2 entity(s) skipped. %3 entity(s) invalidated', - $result['added'], - $result['skipped'], - $result['invalidated'] - ) - ); - return $resultRedirect->setPath('catalog/product'); - } -} diff --git a/Model/Command/ContentAdd.php b/Model/Command/ContentAdd.php index 32a0a2f..fe92eb2 100755 --- a/Model/Command/ContentAdd.php +++ b/Model/Command/ContentAdd.php @@ -8,10 +8,13 @@ namespace Datatrics\Connect\Model\Command; use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputInterface; -use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; use Datatrics\Connect\Model\Config\System\ContentRepository as ConfigContentRepository; +use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; +use Datatrics\Connect\Model\ProductData\Repository as ProductDataRepository; +use Exception; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Symfony\Component\Console\Output\OutputInterface; /** * Class ContentAdd @@ -22,93 +25,75 @@ class ContentAdd { /** - * @var ContentRepository + * @var ConfigContentRepository */ - private $contentRepository; - + private $configRepository; /** * @var ContentResource */ private $contentResource; /** - * @var ConfigContentRepository + * @var ProductDataRepository + */ + private $productDataRepository; + /** + * @var string */ - private $configContentRepository; + private $entityId; /** * ContentAdd constructor. - * @param ContentRepository $contentRepository + * @param ConfigContentRepository $configRepository + * @param ProductDataRepository $productDataRepository * @param ContentResource $contentResource - * @param ConfigContentRepository $configContentRepository + * @param MetadataPool $metadataPool + * @throws Exception */ public function __construct( - ContentRepository $contentRepository, + ConfigContentRepository $configRepository, + ProductDataRepository $productDataRepository, ContentResource $contentResource, - ConfigContentRepository $configContentRepository + MetadataPool $metadataPool ) { - $this->contentRepository = $contentRepository; + $this->configRepository = $configRepository; + $this->productDataRepository = $productDataRepository; $this->contentResource = $contentResource; - $this->configContentRepository = $configContentRepository; + $this->entityId = $metadataPool->getMetadata(ProductInterface::class)->getLinkField(); } /** - * @param InputInterface $input + * @param array $storeIds * @param OutputInterface $output * @return int */ - public function run(InputInterface $input, OutputInterface $output) + public function run(array $storeIds, OutputInterface $output): int { - return $this->addProducts(null, $output); + $updates = 0; + foreach ($storeIds as $storeId) { + $updates += $this->addProducts($storeId, $output); + } + + return $updates; } /** - * @param null|int $storeId - * @param null|OutputInterface $output + * @param int|null $storeId + * @param OutputInterface|null $output * @return int - * @throws \Magento\Framework\Exception\LocalizedException */ - public function addProducts($storeId = null, $output = null) + public function addProducts(int $storeId, ?OutputInterface $output = null): int { - $connection = $this->contentResource->getConnection(); - $stores = []; - if (!$storeId) { - $selectStores = $connection->select()->from( - $this->contentResource->getTable('store'), - 'store_id' - ); - foreach ($connection->fetchAll($selectStores) as $store) { - $stores[] = $store['store_id']; - } - } else { - $stores[] = $storeId; - } - $selectContent = $connection->select()->from( - $this->contentResource->getTable('datatrics_content'), - 'content_id' - ); - if ($storeId) { - $selectContent->joinLeft( - ['datatrics_content_store' => $this->contentResource->getTable('datatrics_content_store')], - 'content_id = product_id', - [] - )->where('datatrics_content_store.store_id = ?', $storeId); + if (!$this->configRepository->isEnabled($storeId)) { + return 0; } - $limit = $this->configContentRepository->getProcessingLimitAdd(); - $select = $connection->select()->from( - $this->contentResource->getTable('catalog_product_entity'), - 'entity_id' - )->joinLeft( - ['super_link' => $this->contentResource->getTable('catalog_product_super_link')], - 'super_link.product_id =' . $this->contentResource->getTable('catalog_product_entity') . '.entity_id', - [ - 'parent_id' => 'GROUP_CONCAT(parent_id)' - ] - )->where('entity_id not in (?)', $selectContent) - ->group('entity_id')->limit($limit); - $result = $connection->fetchAll($select); + + $productIds = $this->getAllProductIds(); + $productData = $this->productDataRepository->getProductData($storeId, $productIds); + $currentStoreData = $this->getCurrentStoreData($storeId); + $this->cleanupTables($storeId, array_keys($productData)); if ($output) { - $progressBar = new \Symfony\Component\Console\Helper\ProgressBar($output, count($result)); + $progressBar = new \Symfony\Component\Console\Helper\ProgressBar($output, count($productData)); $progressBar->setMessage('0', 'product'); $progressBar->setFormat( 'Content %current%/%max% [%bar%] %percent:3s%% %elapsed% %memory:6s% @@ -118,16 +103,24 @@ public function addProducts($storeId = null, $output = null) $progressBar->start(); $progressBar->display(); } + $count = 0; - $this->contentResource->beginTransaction(); $pool = 0; - $data = []; - foreach ($result as $entity) { - $count++; + $storeData = []; + + foreach ($productData as $productId => $data) { $pool++; - $content = $this->contentRepository->create(); - $content->setContentId($entity['entity_id']) - ->setParentId((string)$entity['parent_id']); + + if (!isset($currentStoreData[$productId])) { + $storeData[] = [ + (int)$productId, + $data['parent_id'] ?? null, + (int)$storeId, + ContentRepository::STATUS['queued'] + ]; + $count++; + } + if ($pool == 1000) { $pool = 0; if ($output) { @@ -137,32 +130,84 @@ public function addProducts($storeId = null, $output = null) $progressBar->advance(1000); } } - foreach ($stores as $store) { - $data[] = [ - $entity['entity_id'], - $store, - 'Queued for Update' - ]; - } - $this->contentRepository->save($content); } + if ($output) { /** @phpstan-ignore-next-line */ $progressBar->setMessage((string)$count, 'product'); } - if ($data) { - $connection->insertArray( - $this->contentResource->getTable('datatrics_content_store'), - ['product_id', 'store_id', 'status'], - $data - ); - } - $this->contentResource->commit(); + + $this->updateStoreTable($storeData); + if ($output) { /** @phpstan-ignore-next-line */ $progressBar->finish(); $output->writeln(''); } - return $count; + + return count($storeData); + } + + /** + * @return array + */ + private function getAllProductIds(): array + { + $connection = $this->contentResource->getConnection(); + $table = $this->contentResource->getTable('catalog_product_entity'); + $productIds = $connection->select() + ->from($table, $this->entityId); + + return array_flip($connection->fetchCol($productIds)); + } + + /** + * @param $storeId + * @return array + */ + private function getCurrentStoreData($storeId): array + { + $connection = $this->contentResource->getConnection(); + $table = $this->contentResource->getTable('datatrics_content_store'); + $productIds = $connection->select() + ->from($table, 'product_id') + ->where('store_id = ?', $storeId); + + return array_flip($connection->fetchCol($productIds)); + } + + /** + * @param int $storeId + * @param array $productIds + * @return void + */ + private function cleanupTables(int $storeId, array $productIds): void + { + $connection = $this->contentResource->getConnection(); + $connection->delete( + $this->contentResource->getTable('datatrics_content_store'), + [ + 'store_id = ?' => $storeId, + 'product_id NOT IN (?)' => $productIds + ] + ); + } + + /** + * @param array $storeData + * @return void + */ + private function updateStoreTable(array $storeData) + { + if (empty($storeData)) { + return; + } + + $connection = $this->contentResource->getConnection(); + $connection->insertArray( + $this->contentResource->getTable('datatrics_content_store'), + ['product_id', 'parent_id', 'store_id', 'status'], + $storeData + ); } } diff --git a/Model/Command/ContentInvalidate.php b/Model/Command/ContentInvalidate.php index 8768135..9723273 100755 --- a/Model/Command/ContentInvalidate.php +++ b/Model/Command/ContentInvalidate.php @@ -7,9 +7,6 @@ namespace Datatrics\Connect\Model\Command; -use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputInterface; use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; /** @@ -20,57 +17,34 @@ class ContentInvalidate { - /** - * @var ContentRepository - */ - private $contentRepository; - /** * @var ContentResource */ private $contentResource; /** - * SalesUpdate constructor. - * @param ContentRepository $contentRepository + * ContentInvalidate constructor. * @param ContentResource $contentResource */ public function __construct( - ContentRepository $contentRepository, ContentResource $contentResource ) { - $this->contentRepository = $contentRepository; $this->contentResource = $contentResource; } /** - * @param InputInterface $input - * @param OutputInterface $output + * @param array $storeIds + * @param array|null $productIds * @return int */ - public function run(InputInterface $input, OutputInterface $output) + public function run(array $storeIds, ?array $productIds = []): int { - $connection = $this->contentResource->getConnection(); - if ($storeId = $input->getOption('store-id')) { - $stores = [$storeId]; - } else { - $selectStores = $connection->select()->from( - $this->contentResource->getTable('store'), - 'store_id' - ); - $stores = []; - foreach ($connection->fetchAll($selectStores) as $store) { - $stores[] = $store['store_id']; - } + $where = ['store_id IN (?)' => $storeIds]; + if (!empty($productIds)) { + $where['product_id IN (?)'] = $productIds; } - $where = [ - 'store_id IN (?)' => $stores - ]; - - if (!empty($input->getArguments()['product-id'])) { - $where['product_id IN (?)'] = [$input->getArguments()['product-id']]; - } + $connection = $this->contentResource->getConnection(); return $connection->update( $this->contentResource->getTable('datatrics_content_store'), ['status' => 'Queued for Update'], diff --git a/Model/Command/ContentUpdate.php b/Model/Command/ContentUpdate.php index 2fd633a..7fa2043 100755 --- a/Model/Command/ContentUpdate.php +++ b/Model/Command/ContentUpdate.php @@ -7,19 +7,17 @@ namespace Datatrics\Connect\Model\Command; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputInterface; use Datatrics\Connect\Api\API\AdapterInterface as ApiAdapter; use Datatrics\Connect\Api\Config\System\ContentInterface as ContentConfigRepository; -use Magento\Framework\Serialize\Serializer\Json; -use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; -use Datatrics\Connect\Service\Product\Data\AttributeMapper; +use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository; +use Datatrics\Connect\Api\Log\RepositoryInterface as LogRepository; use Datatrics\Connect\Api\ProductData\RepositoryInterface as ProductDataRepository; -use Magento\Store\Api\StoreRepositoryInterface; -use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Datatrics\Connect\Model\Content\CollectionFactory as ContentCollectionFactory; +use Datatrics\Connect\Model\Content\ResourceModel as ContentResource; +use Magento\Framework\Serialize\Serializer\Json; use Symfony\Component\Console\Helper\ProgressBar; -use Datatrics\Connect\Service\Product\Hub; -use Datatrics\Connect\Model\Config\System\ContentRepository as ConfigContentRepository; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; /** * Class ContentUpdate @@ -33,135 +31,71 @@ class ContentUpdate * @var ContentResource */ private $contentResource; - /** * @var ApiAdapter */ private $apiAdapter; - /** * @var ContentConfigRepository */ private $contentConfigRepository; - /** * @var Json */ private $json; - - /** - * @var AttributeMapper - */ - private $attributeMapper; - - /** - * @var StoreRepositoryInterface - */ - private $storeManager; - - /** - * @var ProductCollection - */ - private $productCollection; - /** * @var bool */ private $isDry = false; - - /** - * @var Hub - */ - private $collector; - /** * @var ProgressBar|null */ private $progressBar = null; - /** - * @var array - */ - private $mediaUrl; - - /** - * @var array - */ - private $filters; - - /** - * @var array - */ - private $parents; - - /** - * @var array - */ - private $types; - - /** - * @var array - */ - private $behaviour; - - /** - * @var array + * @var ProductDataRepository */ - private $childOf; - + private $productDataRepository; /** - * @var array + * @var LogRepository */ - private $extraAttributes = []; - + private $logRepository; /** - * @var array + * @var ContentCollectionFactory */ - private $categoryNames = []; - + private $contentCollectionFactory; /** - * @var array + * @var ContentRepository */ - private $storeUrl = []; + private $contentRepository; /** - * @var ProductDataRepository - */ - private $productDataRepository; - /** - * @var ConfigContentRepository - */ - private $configContentRepository; - - /** - * ContentUpdate constructor. * @param ContentResource $contentResource * @param ApiAdapter $apiAdapter + * @param ContentRepository $contentRepository * @param ContentConfigRepository $contentConfigRepository + * @param ContentCollectionFactory $contentCollectionFactory * @param Json $json - * @param StoreRepositoryInterface $storeManager - * @param ProductCollection $productCollection * @param ProductDataRepository $productDataRepository - * @param ConfigContentRepository $configContentRepository + * @param LogRepository $logRepository */ public function __construct( ContentResource $contentResource, ApiAdapter $apiAdapter, + ContentRepository $contentRepository, ContentConfigRepository $contentConfigRepository, + ContentCollectionFactory $contentCollectionFactory, Json $json, - StoreRepositoryInterface $storeManager, - ProductCollection $productCollection, ProductDataRepository $productDataRepository, - ConfigContentRepository $configContentRepository + LogRepository $logRepository ) { $this->contentResource = $contentResource; $this->apiAdapter = $apiAdapter; + $this->contentRepository = $contentRepository; $this->contentConfigRepository = $contentConfigRepository; + $this->contentCollectionFactory = $contentCollectionFactory; $this->json = $json; - $this->storeManager = $storeManager; - $this->productCollection = $productCollection; $this->productDataRepository = $productDataRepository; - $this->configContentRepository = $configContentRepository; + $this->logRepository = $logRepository; } /** @@ -169,57 +103,43 @@ public function __construct( * @param OutputInterface $output * @return int */ - public function run(InputInterface $input, OutputInterface $output) + public function run(InputInterface $input, OutputInterface $output): int { $storeId = (int)$input->getOption('store-id'); if (!$this->contentConfigRepository->isEnabled($storeId)) { - $output->writeln('Product syncronisation disabled'); + $output->writeln('Product synchronisation disabled'); return 0; } + $this->isDry = (bool)$input->getOption('dry'); + $setProductIds = $input->getArgument('product-id'); + $productIds = $this->getProductIds($storeId, $setProductIds); - $connection = $this->contentResource->getConnection(); - $select = $connection->select()->from( - $this->contentResource->getTable('datatrics_content_store'), - [ - 'product_id', - 'update_attempts' - ] - )->where('store_id = ?', $storeId); - if (!$input->getOption('force')) { - $select->where('status <> ?', 'Synced'); - } - if ($limit = $this->configContentRepository->getProcessingLimit($storeId)) { - $select->limit($limit); - } - if ($productIds = $input->getArgument('product-id')) { - $select->where('product_id in (?)', $productIds); - } - if (!$connection->fetchOne($select)) { - return 0; - } - $productIds = $connection->fetchCol($select); $count = $productIds ? $this->prepareData($productIds, $storeId) : 0; $this->initProgressBar($output, $count); return 0; } /** - * @param OutputInterface $output - * @param string|int $size + * @param int $storeId + * @param array|null $setProductIds + * @return array */ - private function initProgressBar($output, $size) + public function getProductIds(int $storeId, ?array $setProductIds = null): array { - /* init progress bar */ - $this->progressBar = new \Symfony\Component\Console\Helper\ProgressBar( - $output, - $size - ); - $this->progressBar->setMessage('0', 'content'); - $this->progressBar->setFormat( - 'Content %current%/%max% [%bar%] %percent:3s%% %elapsed% %memory:6s% - ⏺ Pushed: %content%' - ); + $connection = $this->contentResource->getConnection(); + $selectProductIds = $connection->select() + ->from($this->contentResource->getTable('datatrics_content_store'), 'product_id') + ->where('status != (?)', ContentRepository::STATUS['synced']) + ->where('store_id = ?', $storeId) + ->limit($this->contentConfigRepository->getProcessingLimit($storeId)) + ->order('updated_at ASC'); + + if ($setProductIds) { + $selectProductIds->where('product_id in (?)', $setProductIds); + } + + return $connection->fetchCol($selectProductIds); } /** @@ -227,15 +147,14 @@ private function initProgressBar($output, $size) * * @param array $productIds * @param int $storeId + * @param bool $dryRun * @return int */ - public function prepareData(array $productIds, int $storeId) + public function prepareData(array $productIds, int $storeId, bool $dryRun = false): int { - $connection = $this->contentResource->getConnection(); $count = 0; - $items = [ - 'items' => [] - ]; + $items = ['items' => []]; + $data = $this->productDataRepository->getProductData($storeId, $productIds); foreach ($data as $id => $product) { $preparedData = [ @@ -244,63 +163,54 @@ public function prepareData(array $productIds, int $storeId) "item" => $product ]; try { - $serializedData = $this->json->serialize($preparedData); + $this->json->serialize($preparedData); } catch (\Exception $e) { continue; } $items['items'][] = $preparedData; } - if ($this->isDry) { - // phpcs:ignore Magento2.Functions.DiscouragedFunction - print_r($items); + + if ($this->isDry || $dryRun) { + echo '
'; // phpcs:ignore
+            print_r($items); // phpcs:ignore
             return $count;
         }
+
         if (!$items['items']) {
             $this->updateSkipped($productIds, $storeId);
             return $count;
         }
-        //bulk update
+
         $response = $this->apiAdapter->execute(
             ApiAdapter::BULK_CREATE_CONTENT,
             null,
             $this->json->serialize($items)
         );
+
         if (!$response['success'] || !isset($response['data']['total_elements'])) {
             return $count;
-        }
-        if ($response['success'] == true) {
-            $count += $response['data']['total_elements'];
         } else {
-            return $count;
+            $count += $response['data']['total_elements'];
         }
+
         $updatedProductIds = [];
         foreach ($response['data']['items'] as $item) {
             $updatedProductIds[] = $item['id'];
         }
-        $where = [
-            'product_id IN (?)' => $updatedProductIds,
-            'store_id = ?' => $storeId
-        ];
-        if ($response['success'] == true) {
-            $connection->update(
-                $this->contentResource->getTable('datatrics_content_store'),
-                [
-                    'status' => 'Synced',
-                    'update_msg' => '',
-                    'update_attempts' => 0
-                ],
-                $where
-            );
-        } else {
-            $connection->update(
-                $this->contentResource->getTable('datatrics_content_store'),
-                [
-                    'status' => 'Error',
-                    'update_msg' => ''
-                ],
-                $where
-            );
-        }
+
+        $connection = $this->contentResource->getConnection();
+        $connection->update(
+            $this->contentResource->getTable('datatrics_content_store'),
+            [
+                'status' => 'Synced',
+                'update_msg' => '',
+                'update_attempts' => 0
+            ],
+            [
+                'product_id IN (?)' => $updatedProductIds,
+                'store_id = ?' => $storeId
+            ]
+        );
 
         $skippedProducts = array_diff($productIds, $updatedProductIds);
         if (!empty($skippedProducts)) {
@@ -311,6 +221,7 @@ public function prepareData(array $productIds, int $storeId)
             $this->progressBar->setMessage((string)$count, 'content');
             $this->progressBar->advance($count);
         }
+
         return $count;
     }
 
@@ -320,17 +231,42 @@ public function prepareData(array $productIds, int $storeId)
      */
     private function updateSkipped(array $productIds, int $storeId)
     {
-        $connection = $this->contentResource->getConnection();
-        $connection->update(
-            $this->contentResource->getTable('datatrics_content_store'),
-            [
-                'status' => 'Skipped',
-                'update_msg' => ''
-            ],
-            [
-                'product_id IN (?)' => $productIds,
-                'store_id = ?' => $storeId
-            ]
+        $this->logRepository->addDebugLog('Skipped Products', implode(',', $productIds));
+
+        $collection = $this->contentCollectionFactory->create()
+            ->addFieldToFilter('product_id', ['in' => $productIds])
+            ->addFieldToFilter('store_id', $storeId);
+
+        foreach ($collection as $content) {
+            try {
+                if ($content->getUpdateAttempts() < 2) {
+                    $content->setStatus(ContentRepository::STATUS['skipped'])
+                        ->setUpdateAttempts($content->getUpdateAttempts() + 1);
+                    $this->contentRepository->save($content);
+                } else {
+                    $this->contentRepository->delete($content);
+                }
+            } catch (\Exception $exception) {
+                $this->logRepository->addErrorLog('updateSkipped', $exception->getMessage());
+            }
+        }
+    }
+
+    /**
+     * @param OutputInterface $output
+     * @param string|int $size
+     */
+    private function initProgressBar(OutputInterface $output, $size)
+    {
+        /* init progress bar */
+        $this->progressBar = new \Symfony\Component\Console\Helper\ProgressBar(
+            $output,
+            $size
+        );
+        $this->progressBar->setMessage('0', 'content');
+        $this->progressBar->setFormat(
+            'Content %current%/%max% [%bar%] %percent:3s%% %elapsed% %memory:6s%
+    ⏺ Pushed:    %content%'
         );
     }
 }
diff --git a/Model/Config/Repository.php b/Model/Config/Repository.php
index 99b6d46..c375d50 100755
--- a/Model/Config/Repository.php
+++ b/Model/Config/Repository.php
@@ -25,7 +25,7 @@ class Repository implements ConfigRepositoryInterface
     /**
      * @var StoreManagerInterface
      */
-    private $storeManager;
+    public $storeManager;
     /**
      * @var ScopeConfigInterface
      */
diff --git a/Model/Config/System/ContentRepository.php b/Model/Config/System/ContentRepository.php
index a21dac3..efa8b44 100755
--- a/Model/Config/System/ContentRepository.php
+++ b/Model/Config/System/ContentRepository.php
@@ -16,6 +16,23 @@
 class ContentRepository extends ConfigRepository implements ContentInterface
 {
 
+    /**
+     * @inheritDoc
+     */
+    public function getContentEnabledStoreIds(): array
+    {
+        $storeIds = [];
+        $stores = $this->storeManager->getStores();
+        foreach ($stores as $store) {
+            if (!$this->isEnabled((int)$store->getId())) {
+                continue;
+            }
+            $storeIds[] = (int)$store->getId();
+        }
+
+        return $storeIds;
+    }
+
     /**
      * @inheritDoc
      */
@@ -53,22 +70,6 @@ private function getSkuAttribute(int $storeId): string
         return $this->getStoreValue(self::XML_PATH_SKU, $storeId);
     }
 
-    /**
-     * @inheritDoc
-     */
-    public function getProcessingLimit(int $storeId): int
-    {
-        return (int)$this->getStoreValue(self::XML_PATH_LIMIT, $storeId);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function getProcessingLimitAdd(): int
-    {
-        return (int)$this->getStoreValue(self::XML_PATH_LIMIT_ADD);
-    }
-
     /**
      * Get selected attribute for 'name'
      *
@@ -123,6 +124,22 @@ public function getImageAttributes(int $storeId): array
         }
     }
 
+    /**
+     * @inheritDoc
+     */
+    public function getProcessingLimit(int $storeId): int
+    {
+        return (int)$this->getStoreValue(self::XML_PATH_LIMIT, $storeId);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function getProcessingLimitAdd(): int
+    {
+        return (int)$this->getStoreValue(self::XML_PATH_LIMIT_ADD);
+    }
+
     /**
      * @inheritDoc
      */
@@ -274,7 +291,7 @@ private function bundleParentAttributes(int $storeId): array
     }
 
     /**
-     * Flag to only use fallback to parent 'bundle' attributes on non visible parents
+     * Flag to only use fallback to parent 'bundle' attributes on non-visible parents
      *
      * @param int $storeId
      *
@@ -369,7 +386,7 @@ public function getFilters(int $storeId): array
     {
         return [
             'add_disabled_products' => $this->addDisableProducts($storeId),
-            'filter_by_visibility' => $this->restricProductFeedByVisibility($storeId),
+            'filter_by_visibility' => $this->restrictProductFeedByVisibility($storeId),
             'visibility' => $this->productFeedVisibilityRestrictions($storeId),
             'restrict_by_category' => $this->restrictProductFeedByCategory($storeId),
             'category_restriction_behaviour' => $this->categoryRestrictionsFilterType($storeId),
@@ -390,7 +407,7 @@ private function addDisableProducts(int $storeId): bool
      *
      * @return bool
      */
-    private function restricProductFeedByVisibility(int $storeId): bool
+    private function restrictProductFeedByVisibility(int $storeId): bool
     {
         return $this->isSetFlag(self::XML_PATH_FILTER_BY_VISIBILITY, $storeId);
     }
@@ -447,7 +464,7 @@ private function getCategoryIds(int $storeId): array
     }
 
     /**
-     * Exclude of of stock products
+     * Exclude out of stock products
      *
      * @param int $storeId
      *
diff --git a/Model/Content/Data.php b/Model/Content/Data.php
index 833e1c1..d64bd05 100755
--- a/Model/Content/Data.php
+++ b/Model/Content/Data.php
@@ -7,13 +7,12 @@
 
 namespace Datatrics\Connect\Model\Content;
 
+use Datatrics\Connect\Api\Content\DataInterface as ContentData;
 use Magento\Framework\Api\ExtensibleDataInterface;
 use Magento\Framework\Model\AbstractModel;
-use Datatrics\Connect\Api\Content\DataInterface as ContentData;
 
 /**
  * Datatrics Content data class
- *
  */
 class Data extends AbstractModel implements ExtensibleDataInterface, ContentData
 {
@@ -29,31 +28,31 @@ public function _construct()
     /**
      * @inheritDoc
      */
-    public function getContentId(): string
+    public function getProductId(): int
     {
-        return $this->getData(self::CONTENT_ID);
+        return $this->getData(self::PRODUCT_ID);
     }
 
     /**
      * @inheritDoc
      */
-    public function setContentId(string $contentId): ContentData
+    public function setProductId(int $productId): ContentData
     {
-        return $this->setData(self::CONTENT_ID, $contentId);
+        return $this->setData(self::PRODUCT_ID, $productId);
     }
 
     /**
      * @inheritDoc
      */
-    public function getParentId(): string
+    public function getParentId(): ?int
     {
-        return $this->getData(self::PARENT_ID);
+        return $this->getData(self::PARENT_ID) ? (int)$this->getData(self::PARENT_ID) : null;
     }
 
     /**
      * @inheritDoc
      */
-    public function setParentId(string $parentId): ContentData
+    public function setParentId(?int $parentId): ContentData
     {
         return $this->setData(self::PARENT_ID, $parentId);
     }
@@ -74,22 +73,6 @@ public function setStoreId(int $storeId): ContentData
         return $this->setData(self::STORE_ID, $storeId);
     }
 
-    /**
-     * @inheritDoc
-     */
-    public function getCreatedAt(): string
-    {
-        return $this->getData(self::CREATED_AT);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function setCreatedAt(string $createdAt): ContentData
-    {
-        return $this->setData(self::CREATED_AT, $createdAt);
-    }
-
     /**
      * @inheritDoc
      */
@@ -125,9 +108,9 @@ public function setUpdateMsg(string $updateMsg): ContentData
     /**
      * @inheritDoc
      */
-    public function getUpdateAttempts(): string
+    public function getUpdateAttempts(): int
     {
-        return $this->getData(self::UPDATE_ATTEMPTS);
+        return (int)$this->getData(self::UPDATE_ATTEMPTS);
     }
 
     /**
diff --git a/Model/Content/Repository.php b/Model/Content/Repository.php
index 35d321b..dec84d1 100755
--- a/Model/Content/Repository.php
+++ b/Model/Content/Repository.php
@@ -7,23 +7,17 @@
 
 namespace Datatrics\Connect\Model\Content;
 
+use Datatrics\Connect\Api\Content\DataInterface as ContentData;
+use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository;
 use Datatrics\Connect\Api\Content\SearchResultsInterface;
-use Exception;
-use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
-use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
+use Datatrics\Connect\Api\Content\SearchResultsInterfaceFactory as SearchResultsFactory;
+use Datatrics\Connect\Api\Log\RepositoryInterface as LogRepository;
+use Datatrics\Connect\Model\Content\DataFactory as DataFactory;
 use Magento\Framework\Api\SearchCriteriaInterface;
-use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Exception\CouldNotDeleteException;
 use Magento\Framework\Exception\CouldNotSaveException;
 use Magento\Framework\Exception\InputException;
 use Magento\Framework\Exception\NoSuchEntityException;
-use Datatrics\Connect\Api\Log\RepositoryInterface as LogRepository;
-use Datatrics\Connect\Api\Content\DataInterface as ContentData;
-use Datatrics\Connect\Model\Content\DataFactory as DataFactory;
-use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository;
-use Datatrics\Connect\Api\Content\SearchResultsInterfaceFactory as SearchResultsFactory;
-use Magento\Framework\Encryption\Encryptor;
-use Datatrics\Connect\Api\Config\RepositoryInterface as ConfigRepository;
 
 /**
  * Datatrics content repository class
@@ -40,88 +34,42 @@ class Repository implements ContentRepository
      * @var SearchResultsFactory
      */
     private $searchResultsFactory;
-
     /**
      * @var CollectionFactory
      */
     private $collectionFactory;
-
     /**
      * @var ResourceModel
      */
     private $resource;
-
     /**
      * @var DataFactory
      */
     private $dataFactory;
-
-    /**
-     * @var JoinProcessorInterface
-     */
-    private $extensionAttributesJoinProcessor;
-
-    /**
-     * @var CollectionProcessorInterface|null
-     */
-    private $collectionProcessor;
-
     /**
      * @var LogRepository
      */
     private $logRepository;
 
     /**
-     * @var Encryptor
-     */
-    private $encryptor;
-
-    /**
-     * @var ConfigRepository
-     */
-    private $configRepository;
-
-    /**
-     * Repository constructor.
      * @param SearchResultsFactory $searchResultsFactory
      * @param CollectionFactory $collectionFactory
-     * @param JoinProcessorInterface $extensionAttributesJoinProcessor
      * @param ResourceModel $resource
      * @param DataFactory $dataFactory
      * @param LogRepository $logRepository
-     * @param Encryptor $encryptor
-     * @param ConfigRepository $configRepository
-     * @param CollectionProcessorInterface|null $collectionProcessor
      */
     public function __construct(
         SearchResultsFactory $searchResultsFactory,
         CollectionFactory $collectionFactory,
-        JoinProcessorInterface $extensionAttributesJoinProcessor,
         ResourceModel $resource,
         DataFactory $dataFactory,
-        LogRepository $logRepository,
-        Encryptor $encryptor,
-        ConfigRepository $configRepository,
-        CollectionProcessorInterface $collectionProcessor = null
+        LogRepository $logRepository
     ) {
         $this->searchResultsFactory = $searchResultsFactory;
         $this->collectionFactory = $collectionFactory;
-        $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
         $this->resource = $resource;
         $this->dataFactory = $dataFactory;
         $this->logRepository = $logRepository;
-        $this->encryptor = $encryptor;
-        $this->configRepository = $configRepository;
-        $this->collectionProcessor = $collectionProcessor ?: ObjectManager::getInstance()
-            ->get(CollectionProcessorInterface::class);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function create()
-    {
-        return $this->dataFactory->create();
     }
 
     /**
@@ -136,6 +84,14 @@ public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsI
             ->setTotalCount($collection->getSize());
     }
 
+    /**
+     * @inheritDoc
+     */
+    public function create(): ContentData
+    {
+        return $this->dataFactory->create();
+    }
+
     /**
      * @inheritDoc
      */
@@ -168,7 +124,7 @@ public function delete(ContentData $entity): bool
     {
         try {
             $this->resource->delete($entity);
-        } catch (Exception $exception) {
+        } catch (\Exception $exception) {
             $this->logRepository->addErrorLog('Delete content', $exception->getMessage());
             $exceptionMsg = self::COULD_NOT_DELETE_EXCEPTION;
             throw new CouldNotDeleteException(__(
@@ -182,12 +138,11 @@ public function delete(ContentData $entity): bool
     /**
      * @inheritDoc
      */
-    public function save(
-        ContentData $entity
-    ): ContentData {
+    public function save(ContentData $entity): ContentData
+    {
         try {
             $this->resource->save($entity);
-        } catch (Exception $exception) {
+        } catch (\Exception $exception) {
             $this->logRepository->addErrorLog('Save content', $exception->getMessage());
             $exceptionMsg = self::COULD_NOT_SAVE_EXCEPTION;
             throw new CouldNotSaveException(__(
@@ -197,102 +152,4 @@ public function save(
         }
         return $entity;
     }
-
-    /**
-     * @inheritDoc
-     */
-    public function prepareContentData(array $productIds)
-    {
-        $connection = $this->resource->getConnection();
-        $selectContent = $connection->select()->from(
-            $this->resource->getTable('datatrics_content_store'),
-            [
-                'product_id',
-                'store_id',
-                'status'
-            ]
-        )->where('product_id in (?)', $productIds);
-        $contents = $connection->fetchAll($selectContent);
-        $toInvalidate = [];
-        $skip = 0;
-        $toAdd = array_diff($productIds, $connection->fetchCol($selectContent));
-        foreach ($contents as $content) {
-            if ($content['status'] != 0) {
-                $toInvalidate[] = $content['product_id'];
-            } else {
-                $skip++;
-            }
-        }
-        // invalidating
-        $invalidated = $connection->update(
-            $this->resource->getTable('datatrics_content_store'),
-            ['status' => 0],
-            ['product_id in (?)' => $toInvalidate]
-        );
-
-        //adding
-        $added = $this->addContent($toAdd);
-        return [
-            'added' => $added,
-            'invalidated' => $invalidated,
-            'skipped' => $skip
-        ];
-    }
-
-    /**
-     * Collect products and add to tables
-     *
-     * @param array $productIds
-     * @return int
-     * @throws \Magento\Framework\Exception\LocalizedException
-     */
-    private function addContent($productIds)
-    {
-        $connection = $this->resource->getConnection();
-        $selectStores = $connection->select()->from(
-            $this->resource->getTable('store'),
-            'store_id'
-        );
-        $stores = [];
-        foreach ($connection->fetchAll($selectStores) as $store) {
-            $stores[] = $store['store_id'];
-        }
-        $select = $connection->select()->from(
-            $this->resource->getTable('catalog_product_entity'),
-            'entity_id'
-        )->joinLeft(
-            ['super_link' => $this->resource->getTable('catalog_product_super_link')],
-            'super_link.product_id =' . $this->resource->getTable('catalog_product_entity') . '.entity_id',
-            [
-                'parent_id' => 'GROUP_CONCAT(parent_id)'
-            ]
-        )->where('entity_id in (?)', $productIds)
-            ->group('entity_id')->limit(self::LIMIT);
-        $result = $connection->fetchAll($select);
-        $count = 0;
-        $this->resource->beginTransaction();
-        $data = [];
-        foreach ($result as $entity) {
-            $count++;
-            $content = $this->create();
-            $content->setContentId($entity['entity_id'])
-                ->setParentId((string)$entity['parent_id']);
-            foreach ($stores as $store) {
-                $data[] = [
-                    $entity['entity_id'],
-                    $store
-                ];
-            }
-            $this->save($content);
-        }
-        if ($data) {
-            $connection->insertArray(
-                $this->resource->getTable('datatrics_content_store'),
-                ['product_id', 'store_id'],
-                $data
-            );
-        }
-        $this->resource->commit();
-        return $count;
-    }
 }
diff --git a/Model/Content/ResourceModel.php b/Model/Content/ResourceModel.php
index 28a779d..822e059 100755
--- a/Model/Content/ResourceModel.php
+++ b/Model/Content/ResourceModel.php
@@ -16,7 +16,7 @@
 class ResourceModel extends AbstractDb
 {
 
-    public const ENTITY_TABLE = 'datatrics_content';
+    public const ENTITY_TABLE = 'datatrics_content_store';
     public const PRIMARY = 'entity_id';
 
     /**
@@ -31,10 +31,10 @@ public function _construct()
      * Check is entity exists
      *
      * @param int $entityId
-     * @param string $field
+     * @param string|null $field
      * @return bool
      */
-    public function isExists($entityId, $field = 'entity_id')
+    public function isExists(int $entityId, ?string $field = 'entity_id'): bool
     {
         $connection = $this->getConnection();
         $select = $connection->select()->from(
@@ -45,22 +45,4 @@ public function isExists($entityId, $field = 'entity_id')
         $bind = [':' . $field => $entityId];
         return (bool)$connection->fetchOne($select, $bind);
     }
-
-    /**
-     * Check is entity exists
-     *
-     * @param int $contentId
-     * @return int
-     */
-    public function getIdByContent($contentId)
-    {
-        $connection = $this->getConnection();
-        $select = $connection->select()->from(
-            $this->getTable(self::ENTITY_TABLE),
-            self::PRIMARY
-        );
-        $select->where('content_id = :content_id');
-        $bind = [':content_id' => $contentId];
-        return (int)$connection->fetchOne($select, $bind);
-    }
 }
diff --git a/Model/Cron/ContentMaintenance.php b/Model/Cron/ContentMaintenance.php
index a347688..76662d3 100755
--- a/Model/Cron/ContentMaintenance.php
+++ b/Model/Cron/ContentMaintenance.php
@@ -7,13 +7,8 @@
 
 namespace Datatrics\Connect\Model\Cron;
 
-use Datatrics\Connect\Api\API\AdapterInterface as ApiAdapter;
-use Datatrics\Connect\Api\Content\RepositoryInterface as ContentRepository;
-use Datatrics\Connect\Api\Config\RepositoryInterface as ConfigRepository;
-use Datatrics\Connect\Api\Config\System\ContentInterface as ContentConfigRepository;
-use Magento\Framework\Serialize\Serializer\Json;
-use Datatrics\Connect\Model\Content\ResourceModel as ContentResource;
-use Magento\Store\Api\StoreRepositoryInterface;
+use Datatrics\Connect\Api\Config\System\ContentInterface as ConfigProvider;
+use Datatrics\Connect\Model\Command\ContentAdd;
 
 /**
  * Class ContentMaintenance
@@ -23,204 +18,57 @@
 class ContentMaintenance
 {
 
-    /**
-     * @var ContentRepository
-     */
-    private $contentRepository;
-
-    /**
-     * @var ContentResource
-     */
-    private $contentResource;
+    public $storeIds = [];
 
     /**
-     * @var ConfigRepository
+     * @var ConfigProvider
      */
-    private $configRepository;
-
+    private $configProvider;
     /**
-     * @var Json
+     * @var ContentAdd
      */
-    private $json;
+    private $contentAdd;
 
     /**
-     * @var StoreRepositoryInterface
-     */
-    private $storeManager;
-
-    /**
-     * @var ApiAdapter
-     */
-    private $apiAdapter;
-
-    /**
-     * @var ContentConfigRepository
-     */
-    private $contentConfigRepository;
-
-    /**
-     * ContentMaintenance constructor.
-     * @param ContentResource $contentResource
-     * @param ConfigRepository $configRepository
-     * @param Json $json
-     * @param StoreRepositoryInterface $storeManager
-     * @param ContentRepository $contentRepository
-     * @param ApiAdapter $apiAdapter
-     * @param ContentConfigRepository $contentConfigRepository
+     * @param ConfigProvider $configProvider
+     * @param ContentAdd $contentAdd
      */
     public function __construct(
-        ContentResource $contentResource,
-        ConfigRepository $configRepository,
-        Json $json,
-        StoreRepositoryInterface $storeManager,
-        ContentRepository $contentRepository,
-        ApiAdapter $apiAdapter,
-        ContentConfigRepository $contentConfigRepository
+        ConfigProvider $configProvider,
+        ContentAdd $contentAdd
     ) {
-        $this->contentResource = $contentResource;
-        $this->configRepository = $configRepository;
-        $this->json = $json;
-        $this->storeManager = $storeManager;
-        $this->contentRepository = $contentRepository;
-        $this->apiAdapter = $apiAdapter;
-        $this->contentConfigRepository = $contentConfigRepository;
-    }
-
-    /**
-     * Collect products which should be scheduled to delete from platform
-     */
-    private function collectProductsToDelete()
-    {
-        $connection = $this->contentResource->getConnection();
-        $selectMagentoProducts = $connection->select()->from(
-            $this->contentResource->getTable('catalog_product_entity'),
-            [
-                'entity_id'
-            ]
-        );
-        $magentoProductIds = $connection->fetchCol($selectMagentoProducts);
-        $selectDatatricsProducts = $connection->select()->from(
-            $this->contentResource->getTable('datatrics_content'),
-            [
-                'content_id'
-            ]
-        );
-        $datatricsProductIds = $connection->fetchCol($selectDatatricsProducts);
-        $toDelete = array_diff($datatricsProductIds, $magentoProductIds);
-        $connection->update(
-            $this->contentResource->getTable('datatrics_content_store'),
-            ['status' => 'Queued for Delete'],
-            ['product_id IN (?)' => $toDelete]
-        );
-        foreach ($toDelete as $itemId) {
-            $data = [
-                "source" => "Magento 2",
-                "type" => "item"
-            ];
-            $this->apiAdapter->execute(
-                ApiAdapter::DELETE_CONTENT,
-                $itemId,
-                $data
-            );
-        }
+        $this->configProvider = $configProvider;
+        $this->contentAdd = $contentAdd;
     }
 
     /**
-     * Collect product IDs which should be scheduled to add to platform
-     *
-     * @return array
-     */
-    private function collectProductsToAdd()
-    {
-        $connection = $this->contentResource->getConnection();
-        $selectMagentoProducts = $connection->select()->from(
-            $this->contentResource->getTable('catalog_product_entity'),
-            [
-                'entity_id'
-            ]
-        );
-        $magentoProductIds = $connection->fetchCol($selectMagentoProducts);
-        $selectDatatricsProducts = $connection->select()->from(
-            $this->contentResource->getTable('datatrics_content_store'),
-            [
-                'product_id'
-            ]
-        );
-        $datatricsProductIds = $connection->fetchCol($selectDatatricsProducts);
-        $toAdd = array_diff($magentoProductIds, $datatricsProductIds);
-        return $toAdd;
-    }
-
-    /**
-     * Schedule products to add to platform
+     * Schedule products to delete and add
      *
-     * @param array $productIds
-     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return $this
      */
-    private function addProducts($productIds)
+    public function execute(): ContentMaintenance
     {
-        $connection = $this->contentResource->getConnection();
-        $selectStores = $connection->select()->from(
-            $this->contentResource->getTable('store'),
-            'store_id'
-        );
-        $stores = [];
-        foreach ($connection->fetchAll($selectStores) as $store) {
-            $stores[] = $store['store_id'];
-        }
-        $select = $connection->select()->from(
-            $this->contentResource->getTable('catalog_product_entity'),
-            'entity_id'
-        )->joinLeft(
-            ['super_link' => $this->contentResource->getTable('catalog_product_super_link')],
-            'super_link.product_id =' . $this->contentResource->getTable('catalog_product_entity') . '.entity_id',
-            [
-                'parent_id' => 'GROUP_CONCAT(parent_id)'
-            ]
-        )->where('entity_id in (?)', $productIds)
-            ->group('entity_id')->limit(
-                $this->contentConfigRepository->getProcessingLimitAdd()
-            );
-        $result = $connection->fetchAll($select);
-        $this->contentResource->beginTransaction();
-        $data = [];
-        foreach ($result as $entity) {
-            $content = $this->contentRepository->create();
-            $content->setContentId($entity['entity_id'])
-                ->setParentId((string)$entity['parent_id']);
-            foreach ($stores as $store) {
-                $data[] = [
-                    $entity['entity_id'],
-                    $store,
-                    'Queued for Update'
-                ];
-            }
-            $this->contentRepository->save($content);
+        if (!$this->configProvider->isEnabled()) {
+            return $this;
         }
-        if ($data) {
-            $connection->insertArray(
-                $this->contentResource->getTable('datatrics_content_store'),
-                ['product_id', 'store_id', 'status'],
-                $data
-            );
+
+        $storeIds = $this->getStoreIds();
+        foreach ($storeIds as $storeId) {
+            $this->contentAdd->addProducts($storeId);
         }
-        $this->contentResource->commit();
+
+        return $this;
     }
 
     /**
-     * Schedule products to delete and add
-     *
-     * @return $this
-     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return array
      */
-    public function execute()
+    private function getStoreIds(): array
     {
-        if (!$this->configRepository->isEnabled()) {
-            return $this;
+        if (!$this->storeIds) {
+            $this->storeIds = $this->configProvider->getContentEnabledStoreIds();
         }
-        $this->collectProductsToDelete();
-        $idsToAdd = $this->collectProductsToAdd();
-        $this->addProducts($idsToAdd);
-        return $this;
+
+        return $this->storeIds;
     }
 }
diff --git a/Model/Cron/ContentUpdate.php b/Model/Cron/ContentUpdate.php
index 97a8e15..f761904 100755
--- a/Model/Cron/ContentUpdate.php
+++ b/Model/Cron/ContentUpdate.php
@@ -7,14 +7,10 @@
 
 namespace Datatrics\Connect\Model\Cron;
 
-use Datatrics\Connect\Api\API\AdapterInterface as ApiAdapter;
 use Datatrics\Connect\Api\Config\System\ContentInterface as ContentConfigRepository;
-use Datatrics\Connect\Api\ProductData\RepositoryInterface as ProductDataRepository;
-use Magento\Framework\Serialize\Serializer\Json;
+use Datatrics\Connect\Model\Command\ContentUpdate as CommandContentUpdate;
 use Datatrics\Connect\Model\Content\ResourceModel as ContentResource;
 use Magento\Store\Api\StoreRepositoryInterface;
-use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
-use Datatrics\Connect\Model\Command\ContentUpdate as CommandContentUpdate;
 
 /**
  * Class ContentUpdate
@@ -28,37 +24,14 @@ class ContentUpdate
      * @var ContentResource
      */
     private $contentResource;
-
-    /**
-     * @var ApiAdapter
-     */
-    private $apiAdapter;
-
     /**
      * @var ContentConfigRepository
      */
     private $contentConfigRepository;
-
-    /**
-     * @var Json
-     */
-    private $json;
-
     /**
      * @var StoreRepositoryInterface
      */
     private $storeManager;
-
-    /**
-     * @var ProductCollection
-     */
-    private $productCollection;
-
-    /**
-     * @var ProductDataRepository
-     */
-    private $productDataRepository;
-
     /**
      * @var CommandContentUpdate
      */
@@ -67,77 +40,59 @@ class ContentUpdate
     /**
      * ContentUpdate constructor.
      * @param ContentResource $contentResource
-     * @param ApiAdapter $apiAdapter
      * @param ContentConfigRepository $contentConfigRepository
-     * @param Json $json
      * @param StoreRepositoryInterface $storeManager
-     * @param ProductCollection $productCollection
-     * @param ProductDataRepository $productDataRepository
      * @param CommandContentUpdate $commandContentUpdate
      */
     public function __construct(
         ContentResource $contentResource,
-        ApiAdapter $apiAdapter,
         ContentConfigRepository $contentConfigRepository,
-        Json $json,
         StoreRepositoryInterface $storeManager,
-        ProductCollection $productCollection,
-        ProductDataRepository $productDataRepository,
         CommandContentUpdate $commandContentUpdate
     ) {
         $this->contentResource = $contentResource;
-        $this->apiAdapter = $apiAdapter;
         $this->contentConfigRepository = $contentConfigRepository;
-        $this->json = $json;
         $this->storeManager = $storeManager;
-        $this->productCollection = $productCollection;
-        $this->productDataRepository = $productDataRepository;
         $this->commandContentUpdate = $commandContentUpdate;
     }
 
-    /**
-     * Delete products data
-     */
-    private function deleteProducts()
-    {
-        $connection = $this->contentResource->getConnection();
-        $select = $connection->select()->from(
-            $this->contentResource->getTable('datatrics_content_store'),
-            [
-                'product_id'
-            ]
-        )->where('status = ?', 'Queued for Delete');
-        $productIds = $connection->fetchCol($select);
-        $where = [
-            'product_id in (?)' => $productIds
-        ];
-        $connection->delete($this->contentResource->getTable('datatrics_content_store'), $where);
-        $where = [
-            'content_id in (?)' => $productIds
-        ];
-        $connection->delete($this->contentResource->getTable('datatrics_content'), $where);
-    }
-
     /**
      * Delete and update products data
      * @return $this
      */
-    public function execute()
+    public function execute(): ContentUpdate
     {
         $this->deleteProducts();
+
         foreach ($this->storeManager->getList() as $store) {
             if (!$this->contentConfigRepository->isEnabled((int)$store->getId()) || $store->getId() == 0) {
                 continue;
             }
-            if ($store->getIsActive()
-                && $this->contentConfigRepository->isEnabled((int)$store->getId())
-            ) {
+            if ($store->getIsActive() && $this->contentConfigRepository->isEnabled((int)$store->getId())) {
                 $this->processStoreData((int)$store->getId());
             }
         }
         return $this;
     }
 
+    /**
+     * Delete products data
+     */
+    private function deleteProducts()
+    {
+        $connection = $this->contentResource->getConnection();
+        $select = $connection->select()->from(
+            $this->contentResource->getTable('datatrics_content_store'),
+            ['product_id']
+        )->where('status = ?', 'Queued for Delete');
+        $productIds = $connection->fetchCol($select);
+
+        $connection->delete(
+            $this->contentResource->getTable('datatrics_content_store'),
+            ['product_id in (?)' => $productIds]
+        );
+    }
+
     /**
      * @param int $storeId
      */
diff --git a/Model/ProductData/Repository.php b/Model/ProductData/Repository.php
index d2423d5..451673c 100755
--- a/Model/ProductData/Repository.php
+++ b/Model/ProductData/Repository.php
@@ -12,6 +12,7 @@
 use Datatrics\Connect\Service\ProductData\AttributeCollector\Data\Image;
 use Datatrics\Connect\Service\ProductData\Filter;
 use Datatrics\Connect\Service\ProductData\Type;
+use Magento\Framework\Exception\NoSuchEntityException;
 
 /**
  * ProductData repository class
@@ -27,6 +28,7 @@ class Repository implements ProductData
      * @var array
      */
     private $attributeMap = [
+        'product_id' => 'entity_id',
         'type_id' => 'type_id',
         'created_at' => 'created_at',
         'updated_at' => 'updated_at',
@@ -118,14 +120,17 @@ public function getProductData(int $storeId = 0, array $entityIds = []): array
 
         $result = [];
         foreach ($this->collectProductData($storeId) as $entityId => $productData) {
-            $this->addImageData($storeId, $entityId, $productData);
-            if (isset($productData['category'])) {
-                $this->addCategoryData($productData);
+            if (empty($productData['product_id'])) {
+                continue;
             }
+            $productId = (int)$productData['product_id'];
+            $this->addImageData($storeId, $entityId, $productData);
+            $this->addCategoryData($productData);
+            $result[$productId]['row_id'] = $entityId;
             foreach ($this->resultMap as $index => $attr) {
-                $result[$entityId][$index] = $productData[$attr] ?? '';
-                if (!$result[$entityId][$index]) {
-                    $result[$entityId][$index] = $productData[$index] ?? '';
+                $result[$productId][$index] = $productData[$attr] ?? '';
+                if (!$result[$productId][$index]) {
+                    $result[$productId][$index] = $productData[$index] ?? '';
                 }
             }
         }
@@ -149,7 +154,7 @@ private function collectIds(int $storeId, array $entityIds = []): void
     }
 
     /**
-     * Attritbute collector
+     * Attribute collector
      *
      * @param int $storeId
      */
@@ -246,12 +251,14 @@ private function addImageData(int $storeId, int $entityId, array &$item)
      */
     private function addCategoryData(array &$productData): void
     {
-        foreach ($productData['category'] as &$category) {
-            $category['name'] = $category['path'];
-            $category['categoryid'] = $category['category_id'];
-            unset($category['path']);
-            unset($category['level']);
-            unset($category['category_id']);
+        if (isset($productData['category'])) {
+            foreach ($productData['category'] as &$category) {
+                $category['name'] = $category['path'];
+                $category['categoryid'] = $category['category_id'];
+                unset($category['path']);
+                unset($category['level']);
+                unset($category['category_id']);
+            }
         }
     }
 }
diff --git a/Observer/InvalidateProduct.php b/Observer/InvalidateProduct.php
index 3c6c223..ee6c037 100755
--- a/Observer/InvalidateProduct.php
+++ b/Observer/InvalidateProduct.php
@@ -7,10 +7,10 @@
 
 namespace Datatrics\Connect\Observer;
 
+use Datatrics\Connect\Api\Log\RepositoryInterface as LogRepository;
 use Datatrics\Connect\Model\Content\ResourceModel as ContentResource;
-use Magento\Framework\Event\ObserverInterface;
 use Magento\Framework\Event\Observer;
-use Datatrics\Connect\Api\Log\RepositoryInterface as LogRepository;
+use Magento\Framework\Event\ObserverInterface;
 
 /**
  * Class InvalidateProduct
@@ -22,8 +22,7 @@ class InvalidateProduct implements ObserverInterface
     /**
      * @var ContentResource
      */
-    protected $contentResource;
-
+    private $contentResource;
     /**
      * @var LogRepository
      */
@@ -49,13 +48,22 @@ public function __construct(
      */
     public function execute(Observer $observer)
     {
-        $connection = $this->contentResource->getConnection();
-        $product = $observer->getEvent()->getProduct();
-        $this->logRepository->addDebugLog('Product', 'ID ' . $product->getId() . ' invalidated');
-        $connection->update(
-            $this->contentResource->getTable('datatrics_content_store'),
-            ['status' => 'Queued for Update'],
-            ['product_id = ?' => $product->getId()]
-        );
+        try {
+            $connection = $this->contentResource->getConnection();
+            $product = $observer->getEvent()->getProduct();
+            $this->logRepository->addDebugLog('Product', 'ID ' . $product->getId() . ' invalidated');
+            $connection->update(
+                $this->contentResource->getTable('datatrics_content_store'),
+                ['status' => 'Queued for Update'],
+                ['product_id = ?' => $product->getId()]
+            );
+            $connection->update(
+                $this->contentResource->getTable('datatrics_content_store'),
+                ['status' => 'Queued for Update'],
+                ['parent_id = ?' => $product->getId()]
+            );
+        } catch (\Exception $exception) {
+            $this->logRepository->addErrorLog('InvalidateProduct Observer', $exception->getMessage());
+        }
     }
 }
diff --git a/Service/ProductData/AttributeCollector/Data/AttributeMapper.php b/Service/ProductData/AttributeCollector/Data/AttributeMapper.php
index 38aad0e..681a88c 100755
--- a/Service/ProductData/AttributeCollector/Data/AttributeMapper.php
+++ b/Service/ProductData/AttributeCollector/Data/AttributeMapper.php
@@ -11,6 +11,7 @@
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\UrlInterface;
 use Magento\Store\Model\StoreManagerInterface;
 
 /**
@@ -159,49 +160,35 @@ public function execute(
     }
 
     /**
-     * Collect data not related to attributes
+     * Attribute options collector
+     *
+     * @return array
      */
-    private function collectExtraData()
+    private function collectAttributeOptions(): array
     {
-        $fields = ['entity_id', 'type_id', 'created_at', 'updated_at'];
-        $select = $this->resource->getConnection()->select()
+        $attrOptions = [];
+
+        $select = $this->resource->getConnection()
+            ->select()
             ->from(
-                ['catalog_product_entity' => $this->resource->getTableName('catalog_product_entity')],
-                $fields
-            )->where('entity_id IN (?)', $this->entityIds);
-        $items = $this->resource->getConnection()->fetchAll($select);
-        foreach ($items as $item) {
-            foreach ($fields as $field) {
-                $this->result[$field][$item['entity_id']] = $item[$field];
-            }
-        }
-    }
+                ['eav_attribute_option' => $this->resource->getTableName('eav_attribute_option')],
+                ['attribute_id']
+            )->joinLeft(
+                ['eav_attribute_option_value' => $this->resource->getTableName('eav_attribute_option_value')],
+                'eav_attribute_option_value.option_id = eav_attribute_option.option_id',
+                [
+                    'option_id',
+                    'store_id',
+                    'value'
+                ]
+            );
 
-    /**
-     * @param string $type
-     */
-    public function resetData($type = 'all'): void
-    {
-        if ($type == 'all') {
-            unset($this->entityIds);
-            unset($this->map);
-            unset($this->entityTypeCode);
-            unset($this->storeId);
-        }
-        switch ($type) {
-            case 'store_id':
-                unset($this->storeId);
-                break;
-            case 'entity_ids':
-                unset($this->entityIds);
-                break;
-            case 'map':
-                unset($this->map);
-                break;
-            case 'entity_type_code':
-                unset($this->entityTypeCode);
-                break;
+        $options = $this->resource->getConnection()->fetchAll($select);
+        foreach ($options as $option) {
+            $attrOptions[$option['attribute_id']][$option['option_id']][$option['store_id']] = $option['value'];
         }
+
+        return $attrOptions;
     }
 
     /**
@@ -230,11 +217,30 @@ public function setData($type, $data): void
     }
 
     /**
-     * @return array
+     * Fetch attributes data from eav_attribute table according provided map
+     *
+     * @return array[] 'attribute_id', 'entity_type_id', 'attribute_code' and 'backend_type'
      */
-    public function getRequiredParameters(): array
+    private function getAttributes(): array
     {
-        return self::REQUIRE;
+        $select = $this->resource->getConnection()
+            ->select()
+            ->from(
+                ['eav_attribute' => $this->resource->getTableName('eav_attribute')],
+                self::EAV_ATTRIBUTES_DATA_SET
+            )->joinLeft(
+                ['eav_entity_type' => $this->resource->getTableName('eav_entity_type')],
+                'eav_attribute.entity_type_id = eav_entity_type.entity_type_id',
+                ['entity_table']
+            )->where(
+                'eav_entity_type.entity_type_code = ?',
+                $this->entityTypeCode
+            )->where(
+                'eav_attribute.attribute_code IN (?)',
+                $this->map
+            );
+
+        return $this->resource->getConnection()->fetchAll($select);
     }
 
     /**
@@ -247,7 +253,7 @@ private function collectAttributeValues(array $attributes): void
     {
         $tablesNonStatic = [];
         $attributeIdsNonStatic = [];
-        $attributeStaticCode = ['entity_id', 'type_id'];
+        $attributeStaticCode = array_unique(['entity_id', 'type_id', $this->linkField]);
         $relations = [];
         $withUrl = [];
 
@@ -268,14 +274,22 @@ private function collectAttributeValues(array $attributes): void
 
         foreach ($tablesNonStatic as $table) {
             $fields = ['attribute_id', 'store_id', 'value', 'entity_id' => $this->linkField];
-            $select = $this->resource->getConnection()->select()
+            $select = $this->resource->getConnection()
+                ->select()
                 ->from(
                     [$table => $this->resource->getTableName($table)],
                     $fields
+                )->where(
+                    "{$this->linkField} IN (?)",
+                    $this->entityIds
+                )->where(
+                    'attribute_id in (?)',
+                    $attributeIdsNonStatic
+                )->where(
+                    'store_id in (?)',
+                    $this->storeId
                 );
-            $select->where("{$this->linkField} IN (?)", $this->entityIds);
-            $select->where('attribute_id in (?)', $attributeIdsNonStatic);
-            $select->where('store_id in (?)', $this->storeId);
+
             $result = $this->resource->getConnection()->fetchAll($select);
             foreach ($result as $item) {
                 if (array_key_exists($item['attribute_id'], $this->attrOptions)) {
@@ -287,7 +301,7 @@ private function collectAttributeValues(array $attributes): void
                             $item['value'][] = $this->attrOptions[$attributeId]
                             [$attrValue]
                             [$item['store_id']];
-                        } catch (\Exception $exception) {
+                        } catch (Exception $exception) {
                             continue;
                         }
                     }
@@ -304,6 +318,7 @@ private function collectAttributeValues(array $attributes): void
                     str_replace(["\r", "\n"], '', (string)$item['value']);
             }
         }
+
         $this->adjustTaxClassLabels();
         $select = $this->resource->getConnection()
             ->select()
@@ -313,16 +328,34 @@ private function collectAttributeValues(array $attributes): void
             );
         $select->where("{$this->linkField} IN (?)", $this->entityIds);
         $result = $this->resource->getConnection()->fetchAll($select);
+
         foreach ($result as $item) {
             foreach ($attributeStaticCode as $static) {
                 if ($static == 'entity_id') {
                     continue;
                 }
-                $this->result[$static][$item['entity_id']] = $item[$static];
+                $this->result[$static][$item[$this->linkField]] = $item[$static];
             }
         }
     }
 
+    /**
+     * @param string $path
+     * @return string
+     */
+    private function getMediaUrl($path): string
+    {
+        if ($this->mediaUrl == null) {
+            try {
+                $this->mediaUrl = $this->storeManager->getStore()
+                    ->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
+            } catch (Exception $exception) {
+                $this->mediaUrl = '';
+            }
+        }
+        return $this->mediaUrl . $path;
+    }
+
     private function adjustTaxClassLabels()
     {
         if (!array_key_exists('tax_class_id', $this->result)) {
@@ -340,72 +373,60 @@ private function adjustTaxClassLabels()
     }
 
     /**
-     * @param string $path
-     * @return string
+     * Collect data not related to attributes
      */
-    private function getMediaUrl($path): string
+    private function collectExtraData()
     {
-        if ($this->mediaUrl == null) {
-            try {
-                $this->mediaUrl = $this->storeManager->getStore()
-                    ->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);
-            } catch (\Exception $exception) {
-                $this->mediaUrl = '';
+        $fields = array_unique([$this->linkField, 'type_id', 'created_at', 'updated_at', 'entity_id']);
+        $select = $this->resource->getConnection()->select()
+            ->from(
+                ['catalog_product_entity' => $this->resource->getTableName('catalog_product_entity')],
+                $fields
+            )->where(
+                $this->linkField . ' IN (?)',
+                $this->entityIds
+            );
+
+        $items = $this->resource->getConnection()->fetchAll($select);
+        foreach ($items as $item) {
+            foreach ($fields as $field) {
+                $this->result[$field][$item[$this->linkField]] = $item[$field];
             }
         }
-        return $this->mediaUrl . $path;
     }
 
     /**
-     * Fetch attributes data from eav_attribute table according provided map
-     *
-     * @return array[] 'attribute_id', 'entity_type_id', 'attribute_code' and 'backend_type'
+     * @param string $type
      */
-    private function getAttributes(): array
+    public function resetData($type = 'all'): void
     {
-        $select = $this->resource->getConnection()
-            ->select()
-            ->from(
-                ['eav_attribute' => $this->resource->getTableName('eav_attribute')],
-                self::EAV_ATTRIBUTES_DATA_SET
-            )->joinLeft(
-                ['eav_entity_type' => $this->resource->getTableName('eav_entity_type')],
-                'eav_attribute.entity_type_id = eav_entity_type.entity_type_id',
-                ['entity_table']
-            )->where('eav_entity_type.entity_type_code = ?', $this->entityTypeCode)
-            ->where('eav_attribute.attribute_code IN (?)', $this->map);
-        return $this->resource->getConnection()->fetchAll($select);
+        if ($type == 'all') {
+            unset($this->entityIds);
+            unset($this->map);
+            unset($this->entityTypeCode);
+            unset($this->storeId);
+        }
+        switch ($type) {
+            case 'store_id':
+                unset($this->storeId);
+                break;
+            case 'entity_ids':
+                unset($this->entityIds);
+                break;
+            case 'map':
+                unset($this->map);
+                break;
+            case 'entity_type_code':
+                unset($this->entityTypeCode);
+                break;
+        }
     }
 
     /**
-     * Attribute options collector
-     *
      * @return array
      */
-    private function collectAttributeOptions(): array
+    public function getRequiredParameters(): array
     {
-        $attrOptions = [];
-
-        $select = $this->resource->getConnection()
-            ->select()
-            ->from(
-                ['eav_attribute_option' => $this->resource->getTableName('eav_attribute_option')],
-                ['attribute_id']
-            )->joinLeft(
-                ['eav_attribute_option_value' => $this->resource->getTableName('eav_attribute_option_value')],
-                'eav_attribute_option_value.option_id = eav_attribute_option.option_id',
-                [
-                    'option_id',
-                    'store_id',
-                    'value'
-                ]
-            );
-
-        $options = $this->resource->getConnection()->fetchAll($select);
-        foreach ($options as $option) {
-            $attrOptions[$option['attribute_id']][$option['option_id']][$option['store_id']] = $option['value'];
-        }
-
-        return $attrOptions;
+        return self::REQUIRE;
     }
 }
diff --git a/Service/ProductData/AttributeCollector/Data/Category.php b/Service/ProductData/AttributeCollector/Data/Category.php
index a1a200f..3a6a569 100755
--- a/Service/ProductData/AttributeCollector/Data/Category.php
+++ b/Service/ProductData/AttributeCollector/Data/Category.php
@@ -8,10 +8,9 @@
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
 use Exception;
-use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\Data\CategoryInterface;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
-use Magento\Framework\Exception\NoSuchEntityException;
 use Magento\Store\Api\StoreRepositoryInterface;
 
 /**
@@ -48,22 +47,18 @@ class Category
      * @var array
      */
     private $categoryNames = [];
-
     /**
      * @var array
      */
     private $excluded = [];
-
     /**
      * @var array
      */
     private $exclude = [];
-
     /**
      * @var string
      */
     private $linkField;
-
     /**
      * @var string
      */
@@ -88,7 +83,7 @@ public function __construct(
     ) {
         $this->resource = $resource;
         $this->storeRepository = $storeRepository;
-        $this->linkField = $metadataPool->getMetadata(ProductInterface::class)->getLinkField();
+        $this->linkField = $metadataPool->getMetadata(CategoryInterface::class)->getLinkField();
     }
 
     /**
@@ -102,7 +97,6 @@ public function __construct(
      * @param string $format
      * @param array $extraParameters
      * @return array[]
-     * @throws NoSuchEntityException
      */
     public function execute(
         $entityIds = [],
@@ -128,6 +122,7 @@ public function execute(
         if (isset($extraParameters['category']['add_url'])) {
             return $this->mergeUrl($data);
         }
+
         return $data;
     }
 
@@ -161,14 +156,10 @@ public function setData($type, $data): void
 
     /**
      * Collect categories name according store IDs
+     * @return void
      */
     private function collectCategoryNames(): void
     {
-        $fields = [
-            'entity_id' => $this->linkField,
-            'value',
-            'store_id'
-        ];
         $connection = $this->resource->getConnection();
         $select = $connection->select()->from(
             ['eav_attribute' => $this->resource->getTableName('eav_attribute')],
@@ -176,8 +167,12 @@ private function collectCategoryNames(): void
         )->joinLeft(
             ['catalog_category_entity_varchar' => $this->resource->getTableName('catalog_category_entity_varchar')],
             'catalog_category_entity_varchar.attribute_id = eav_attribute.attribute_id',
-            $fields
-        )->where('eav_attribute.attribute_code = ?', 'name');
+            ['entity_id' => $this->linkField, 'value', 'store_id']
+        )->where(
+            'eav_attribute.attribute_code = ?',
+            'name'
+        );
+
         if ($this->replaceName) {
             $select->orWhere('eav_attribute.attribute_code = ?', $this->replaceName);
         }
@@ -198,15 +193,11 @@ private function collectCategoryNames(): void
     }
 
     /**
-     *
+     * Collect excluded categories
+     * @return void
      */
     private function collectExcluded(): void
     {
-        $fields = [
-            'entity_id' => $this->linkField,
-            'value',
-            'store_id'
-        ];
         $connection = $this->resource->getConnection();
         $select = $connection->select()->from(
             ['eav_attribute' => $this->resource->getTableName('eav_attribute')],
@@ -214,9 +205,15 @@ private function collectExcluded(): void
         )->joinLeft(
             ['catalog_category_entity_varchar' => $this->resource->getTableName('catalog_category_entity_int')],
             'catalog_category_entity_varchar.attribute_id = eav_attribute.attribute_id',
-            $fields
-        )->where('eav_attribute.attribute_code = ?', $this->exclude['code'])
-            ->where('catalog_category_entity_varchar.store_id IN (?)', [0, $this->storeId]);
+            ['entity_id' => $this->linkField, 'value', 'store_id']
+        )->where(
+            'eav_attribute.attribute_code = ?',
+            $this->exclude['code']
+        )->where(
+            'catalog_category_entity_varchar.store_id IN (?)',
+            [0, $this->storeId]
+        );
+
         foreach ($connection->fetchAll($select) as $item) {
             if ($item['value'] == $this->exclude['value']) {
                 $this->excluded[$item['store_id']][] = $item['entity_id'];
@@ -241,7 +238,10 @@ private function collectCategories(): array
                 ['catalog_category_entity' => $this->resource->getTableName('catalog_category_entity')],
                 "catalog_category_entity.{$this->linkField} = catalog_category_product.category_id",
                 ['path']
-            )->where('product_id IN (?)', $this->entityIds);
+            )->where(
+                'product_id IN (?)',
+                $this->entityIds
+            );
         if ($this->excluded) {
             $select->where('catalog_category_entity.' . $this->linkField . ' NOT IN (?)', $this->excluded);
         }
@@ -261,9 +261,9 @@ private function mergeNames(array $data): array
         $result = [];
         $realId = 0;
         $rootCategoryId = $this->getRootCategoryId();
-        foreach ($data as $entityId => $categoryPathes) {
+        foreach ($data as $entityId => $categoryPaths) {
             $usedPath = [];
-            foreach ($categoryPathes as $categoryPath) {
+            foreach ($categoryPaths as $categoryPath) {
                 if (!$categoryPath) {
                     continue;
                 }
@@ -317,11 +317,11 @@ private function mergeNames(array $data): array
     /**
      * @return int|null
      */
-    private function getRootCategoryId()
+    private function getRootCategoryId(): ?int
     {
         try {
-            return $this->storeRepository->getById($this->storeId)->getRootCategoryId();
-        } catch (\Exception $exception) {
+            return (int)$this->storeRepository->getById($this->storeId)->getRootCategoryId();
+        } catch (Exception $exception) {
             return null;
         }
     }
@@ -329,26 +329,41 @@ private function getRootCategoryId()
     /**
      * @param array $data
      * @return array
-     * @throws NoSuchEntityException
      */
     private function mergeUrl(array $data): array
     {
-        $baseUrl = $this->storeRepository->getById((int)$this->storeId)->getBaseUrl();
+        try {
+            $baseUrl = $this->storeRepository->getById((int)$this->storeId)->getBaseUrl();
+        } catch (Exception $exception) {
+            $baseUrl = '';
+        }
+
         $select = $this->resource->getConnection()
             ->select()
             ->from(
+                ['catalog_category_entity' => $this->resource->getTableName('catalog_category_entity')],
+                [$this->linkField]
+            )->join(
                 ['url_rewrite' => $this->resource->getTableName('url_rewrite')],
-                ['entity_id', 'request_path']
-            )->where('entity_id IN (?)', $this->categoryIds)
-            ->where('entity_type = ?', 'category');
-        $url = $this->resource->getConnection()->fetchPairs($select);
+                'catalog_category_entity.entity_id = url_rewrite.entity_id',
+            )->where(
+                "catalog_category_entity.{$this->linkField} in (?)",
+                $this->categoryIds,
+            )->where(
+                'entity_type = ?',
+                'category'
+            );
+
+        $urls = $this->resource->getConnection()->fetchAll($select);
         foreach ($data as &$datum) {
             foreach ($datum as &$item) {
-                if (array_key_exists($item['category_id'], $url)) {
-                    $item['url'] = $baseUrl . $url[$item['category_id']];
+                $key = array_search($item['category_id'], array_column($urls, 'entity_id'));
+                if ($key !== false && isset($urls[$key]['request_path'])) {
+                    $item['url'] = $baseUrl . $urls[$key]['request_path'];
                 }
             }
         }
+
         return $data;
     }
 
diff --git a/Service/ProductData/AttributeCollector/Data/ConfigurableKey.php b/Service/ProductData/AttributeCollector/Data/ConfigurableKey.php
index 107b544..d5cfe05 100755
--- a/Service/ProductData/AttributeCollector/Data/ConfigurableKey.php
+++ b/Service/ProductData/AttributeCollector/Data/ConfigurableKey.php
@@ -7,6 +7,7 @@
 
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
+use Exception;
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
@@ -40,7 +41,7 @@ class ConfigurableKey
      *
      * @param ResourceConnection $resource
      * @param MetadataPool $metadataPool
-     * @throws \Exception
+     * @throws Exception
      */
     public function __construct(
         ResourceConnection $resource,
@@ -75,10 +76,8 @@ public function setData($type, $data): void
         if (!$data) {
             return;
         }
-        switch ($type) {
-            case 'entity_ids':
-                $this->entityIds = $data;
-                break;
+        if ($type == 'entity_ids') {
+            $this->entityIds = $data;
         }
     }
 
@@ -92,6 +91,7 @@ private function collectKeys(): array
         $result = [];
         $condition = 'catalog_product_entity_int.attribute_id = catalog_product_super_attribute.attribute_id
 and catalog_product_entity_int.' . $this->linkField . ' = catalog_product_relation.child_id';
+
         $select = $this->resource->getConnection()
             ->select()->from(
                 ['catalog_product_relation' => $this->resource->getTableName('catalog_product_relation')]
@@ -103,7 +103,11 @@ private function collectKeys(): array
                 ['catalog_product_entity_int' => $this->resource->getTableName('catalog_product_entity_int')],
                 $condition,
                 ['value', 'store_id']
-            )->where('child_id IN (?)', $this->entityIds);
+            )->where(
+                'child_id IN (?)',
+                $this->entityIds
+            );
+
         $keysData = $this->resource->getConnection()->fetchAll($select);
         foreach ($keysData as $item) {
             if (!$item['value']) {
@@ -141,10 +145,8 @@ public function resetData($type = 'all'): void
             unset($this->entityIds);
             unset($this->type);
         }
-        switch ($type) {
-            case 'entity_ids':
-                unset($this->entityIds);
-                break;
+        if ($type == 'entity_ids') {
+            unset($this->entityIds);
         }
     }
 }
diff --git a/Service/ProductData/AttributeCollector/Data/Image.php b/Service/ProductData/AttributeCollector/Data/Image.php
index 57bb1ce..e370d53 100755
--- a/Service/ProductData/AttributeCollector/Data/Image.php
+++ b/Service/ProductData/AttributeCollector/Data/Image.php
@@ -7,6 +7,7 @@
 
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
+use Exception;
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
@@ -59,7 +60,7 @@ class Image
      * @param ResourceConnection $resource
      * @param StoreRepositoryInterface $storeRepository
      * @param MetadataPool $metadataPool
-     * @throws \Exception
+     * @throws Exception
      */
     public function __construct(
         ResourceConnection $resource,
@@ -121,6 +122,7 @@ private function collectImages(): array
     {
         $mediaGalleryTable = $this->resource->getTableName('catalog_product_entity_media_gallery');
         $mediaGalleryValueTable = $this->resource->getTableName('catalog_product_entity_media_gallery_value');
+
         $select = $this->resource->getConnection()
             ->select()->from(
                 ['catalog_product_entity_media_gallery' => $mediaGalleryTable],
@@ -129,12 +131,18 @@ private function collectImages(): array
                 ['catalog_product_entity_media_gallery_value' => $mediaGalleryValueTable],
                 'catalog_product_entity_media_gallery.value_id = catalog_product_entity_media_gallery_value.value_id',
                 ['entity_id' => $this->linkField, 'store_id', 'position']
-            )->where('catalog_product_entity_media_gallery_value.store_id IN (?)', [0, $this->storeId])
-            ->where('catalog_product_entity_media_gallery_value.' . $this->linkField . ' IN (?)', $this->entityIds);
+            )->where(
+                'catalog_product_entity_media_gallery_value.store_id IN (?)',
+                [0, $this->storeId]
+            )->where(
+                'catalog_product_entity_media_gallery_value.' . $this->linkField . ' IN (?)',
+                $this->entityIds
+            );
 
         if (!$this->includeHidden) {
             $select->where('catalog_product_entity_media_gallery_value.disabled = 0', $this->includeHidden);
         }
+
         return $this->resource->getConnection()->fetchAll($select);
     }
 
@@ -154,9 +162,17 @@ private function collectTypes(): array
                 ['catalog_product_entity_varchar' => $this->resource->getTableName('catalog_product_entity_varchar')],
                 'catalog_product_entity_varchar.attribute_id = eav_attribute.attribute_id',
                 $fields
-            )->where('eav_attribute.frontend_input = ?', 'media_image')
-            ->where('catalog_product_entity_varchar.store_id IN (?)', [0, $this->storeId])
-            ->where('catalog_product_entity_varchar.' . $this->linkField . ' IN (?)', $this->entityIds);
+            )->where(
+                'eav_attribute.frontend_input = ?',
+                'media_image'
+            )->where(
+                'catalog_product_entity_varchar.store_id IN (?)',
+                [0, $this->storeId]
+            )->where(
+                'catalog_product_entity_varchar.' . $this->linkField . ' IN (?)',
+                $this->entityIds
+            );
+
         foreach ($this->resource->getConnection()->fetchAll($select) as $item) {
             $data[$item['entity_id']][$item['value']][] = $item['attribute_code'];
         }
@@ -173,7 +189,7 @@ private function combineData(array $imagesData, array $typesData): array
         $result = [];
         foreach ($imagesData as $imageData) {
             $result[$imageData['entity_id']][$imageData['store_id']][$imageData['position']] = [
-                'file' => $this->getMediaurl('catalog/product' . $imageData['value']),
+                'file' => $this->getMediaUrl('catalog/product' . $imageData['value']),
                 'position' => $imageData['position'],
                 'types' => (isset($typesData[$imageData['entity_id']][$imageData['value']]))
                     ? $typesData[$imageData['entity_id']][$imageData['value']]
@@ -187,14 +203,14 @@ private function combineData(array $imagesData, array $typesData): array
      * @param string $path
      * @return string
      */
-    private function getMediaurl(string $path): string
+    private function getMediaUrl(string $path): string
     {
         if ($this->mediaUrl == null) {
             try {
                 $this->mediaUrl = $this->storeRepository
                     ->getById((int)$this->storeId)
                     ->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
-            } catch (\Exception $exception) {
+            } catch (Exception $exception) {
                 $this->mediaUrl = '';
             }
         }
diff --git a/Service/ProductData/AttributeCollector/Data/Parents.php b/Service/ProductData/AttributeCollector/Data/Parents.php
index 98e3dbf..50caf42 100755
--- a/Service/ProductData/AttributeCollector/Data/Parents.php
+++ b/Service/ProductData/AttributeCollector/Data/Parents.php
@@ -7,6 +7,7 @@
 
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
+use Exception;
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
@@ -31,7 +32,7 @@ class Parents
      *
      * @param ResourceConnection $resource
      * @param MetadataPool $metadataPool
-     * @throws \Exception
+     * @throws Exception
      */
     public function __construct(
         ResourceConnection $resource,
diff --git a/Service/ProductData/AttributeCollector/Data/Price.php b/Service/ProductData/AttributeCollector/Data/Price.php
index 9ed560b..7396e4e 100755
--- a/Service/ProductData/AttributeCollector/Data/Price.php
+++ b/Service/ProductData/AttributeCollector/Data/Price.php
@@ -7,6 +7,7 @@
 
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
+use Exception;
 use Magento\Catalog\Helper\Data as CatalogHelper;
 use Magento\Catalog\Model\Product;
 use Magento\Catalog\Model\Product\CatalogPrice;
@@ -70,13 +71,13 @@ class Price
     private $products = null;
 
     /**
-     * Price constructor.
      * @param CatalogPrice $commonPriceModel
      * @param RuleFactory $resourceRuleFactory
      * @param CatalogHelper $catalogHelper
      * @param StoreManagerInterface $storeManager
      * @param TimezoneInterface $localeDate
      * @param CollectionFactory $collectionFactory
+     * @throws Exception
      */
     public function __construct(
         CatalogPrice $commonPriceModel,
@@ -98,7 +99,7 @@ public function __construct(
      * @param array $productIds
      *
      * @param string $groupedPriceType options: min, max, total
-     * @param string $bundlePriceType optins: min, max, total
+     * @param string $bundlePriceType options: min, max, total
      * @param int $storeId
      * @return array
      */
@@ -112,12 +113,13 @@ public function execute(
         $this->setData('products', $this->getProductData($productIds));
         $this->setData('grouped_price_type', $groupedPriceType);
         $this->setData('bundle_price_type', $bundlePriceType);
+
         foreach ($this->products as $product) {
             $this->setPrices($product, $this->groupedPriceType, $this->bundlePriceType);
             if (array_key_exists($product->getTaxClassId(), $this->taxClasses)) {
                 $percent = $this->taxClasses[$product->getTaxClassId()];
             } else {
-                $priceInclTax = $this->processPrice($product, $this->price, true);
+                $priceInclTax = $this->processPrice($product, (float)$this->price);
                 if ($this->price == 0) {
                     $percent = 1;
                 } else {
@@ -140,6 +142,7 @@ public function execute(
                 'tax' => abs(1 - $percent) * 100
             ];
         }
+
         return $result ?? [];
     }
 
@@ -147,11 +150,11 @@ public function execute(
      * @param int $storeId
      * @return int
      */
-    private function getWebsiteId($storeId = 0): int
+    private function getWebsiteId(int $storeId = 0): int
     {
         try {
             return (int)$this->storeManager->getStore($storeId)->getWebsiteId();
-        } catch (\Exception $exception) {
+        } catch (Exception $exception) {
             return 0;
         }
     }
@@ -206,10 +209,10 @@ private function getProductData(array $productIds = [])
 
     /**
      * @param Product $product
-     * @param string $groupedPriceType
-     * @param string $bundlePriceType
+     * @param string|null $groupedPriceType
+     * @param string|null $bundlePriceType
      */
-    private function setPrices($product, $groupedPriceType, $bundlePriceType): void
+    private function setPrices(Product $product, ?string $groupedPriceType, ?string $bundlePriceType): void
     {
         switch ($product->getTypeId()) {
             case 'configurable':
@@ -257,7 +260,7 @@ private function setPrices($product, $groupedPriceType, $bundlePriceType): void
     /**
      * @param Product $product
      */
-    private function setConfigurablePrices($product): void
+    private function setConfigurablePrices(Product $product): void
     {
         /**
          * Check if config has a final_price (data catalog_product_index_price)
@@ -276,9 +279,9 @@ private function setConfigurablePrices($product): void
 
     /**
      * @param Product $product
-     * @param string $groupedPriceType
+     * @param string|null $groupedPriceType
      */
-    private function setGroupedPrices($product, $groupedPriceType)
+    private function setGroupedPrices(Product $product, ?string $groupedPriceType)
     {
         $minPrice = null;
         $maxPrice = null;
@@ -333,9 +336,9 @@ private function setGroupedPrices($product, $groupedPriceType)
 
     /**
      * @param Product $product
-     * @param string $bundlePriceType
+     * @param string|null $bundlePriceType
      */
-    private function setBundlePrices($product, $bundlePriceType): void
+    private function setBundlePrices(Product $product, ?string $bundlePriceType): void
     {
         $this->setSimplePrices($product);
 
@@ -353,10 +356,10 @@ private function setBundlePrices($product, $bundlePriceType): void
     /**
      * @param Product $product
      */
-    private function setSimplePrices($product)
+    private function setSimplePrices(Product $product)
     {
-        $this->price = $product->getData('price') !== (float)0 ? $product->getData('price') : null;
-        $this->finalPrice = $product->getData('final_price') !== (float)0
+        $this->price = $product->getData('price') !== 0.0 ? $product->getData('price') : null;
+        $this->finalPrice = $product->getData('final_price') !== 0.0
             ? $product->getData('final_price') : null;
         $this->specialPrice = $product->getData('special_price')
             ? $product->getData('special_price') : 0;
@@ -371,7 +374,7 @@ private function setSimplePrices($product)
      *
      * @return float
      */
-    private function getRulePrice($product): float
+    private function getRulePrice(Product $product): float
     {
         try {
             $this->rulePrice = $this->resourceRuleFactory->create()->getRulePrice(
@@ -380,8 +383,8 @@ private function getRulePrice($product): float
                 '',
                 $product->getId()
             );
-        } catch (\Exception $exception) {
-            return (float)0;
+        } catch (Exception $exception) {
+            return 0.0;
         }
 
         if ($this->rulePrice !== null && $this->rulePrice !== false) {
@@ -396,13 +399,12 @@ private function getRulePrice($product): float
      *
      * @param Product $product
      * @param float $price inputted product price
-     * @param bool $addTax return price include tax flag
      *
      * @return float
      */
-    private function processPrice($product, $price, $addTax = true): float
+    private function processPrice(Product $product, float $price): float
     {
-        return (float)$this->catalogHelper->getTaxPrice($product, $price, $addTax);
+        return (float)$this->catalogHelper->getTaxPrice($product, $price, true);
     }
 
     /**
@@ -412,7 +414,7 @@ private function processPrice($product, $price, $addTax = true): float
      *
      * @return string
      */
-    private function getSpecialPriceDateRang($product)
+    private function getSpecialPriceDateRang(Product $product): string
     {
         if ($this->specialPrice === null) {
             return '';
@@ -423,15 +425,11 @@ private function getSpecialPriceDateRang($product)
         }
 
         if ($product->getSpecialFromDate() && $product->getSpecialToDate()) {
-
-            /**
-             * Todo use Magento date function
-             */
             $from = date('Y-m-d', strtotime($product->getSpecialFromDate()));
             $to = date('Y-m-d', strtotime($product->getSpecialToDate()));
-
             return $from . '/' . $to;
         }
+
         return '';
     }
 
@@ -440,7 +438,7 @@ private function getSpecialPriceDateRang($product)
      *
      * @return string
      */
-    private function getDiscountPercentage()
+    private function getDiscountPercentage(): string
     {
         if ($this->price > 0 && $this->salesPrice > 0) {
             $discount = ($this->salesPrice - $this->price) / $this->price;
@@ -463,7 +461,7 @@ public function getRequiredParameters(): array
     /**
      * @param string $type
      */
-    public function resetData($type = 'all')
+    public function resetData(string $type = 'all')
     {
         if ($type == 'all') {
             unset($this->products);
diff --git a/Service/ProductData/AttributeCollector/Data/Stock.php b/Service/ProductData/AttributeCollector/Data/Stock.php
index e276a97..cdd32a2 100755
--- a/Service/ProductData/AttributeCollector/Data/Stock.php
+++ b/Service/ProductData/AttributeCollector/Data/Stock.php
@@ -7,16 +7,19 @@
 
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
+use Exception;
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
 use Magento\Framework\Module\Manager as ModuleManager;
+use Magento\Store\Api\StoreRepositoryInterface;
 
 /**
  * Service class for stock data
  */
 class Stock
 {
+
     public const REQUIRE = [
         'entity_ids'
     ];
@@ -37,34 +40,41 @@ class Stock
      * @var string
      */
     private $linkField;
+    /**
+     * @var StoreRepositoryInterface
+     */
+    private $storeRepository;
 
     /**
      * Price constructor.
      *
      * @param ResourceConnection $resource
      * @param ModuleManager $moduleManager
+     * @param StoreRepositoryInterface $storeRepository
      * @param MetadataPool $metadataPool
-     * @throws \Exception
+     * @throws Exception
      */
     public function __construct(
         ResourceConnection $resource,
         ModuleManager $moduleManager,
+        StoreRepositoryInterface $storeRepository,
         MetadataPool $metadataPool
     ) {
         $this->resource = $resource;
         $this->moduleManager = $moduleManager;
+        $this->storeRepository = $storeRepository;
         $this->linkField = $metadataPool->getMetadata(ProductInterface::class)->getLinkField();
     }
 
     /**
      * Get stock data
      *
-     * @param array[] $entityIds
+     * @param array $productIds
      * @return array[]
      */
-    public function execute(array $entityIds = []): array
+    public function execute(array $productIds = []): array
     {
-        $this->setData('entity_ids', $entityIds);
+        $this->setData('entity_ids', $productIds);
         return ($this->isMsiEnabled())
             ? $this->getMsiStock()
             : $this->getNoMsiStock();
@@ -74,15 +84,13 @@ public function execute(array $entityIds = []): array
      * @param string $type
      * @param mixed $data
      */
-    public function setData($type, $data)
+    public function setData(string $type, $data)
     {
         if (!$data) {
             return;
         }
-        switch ($type) {
-            case 'entity_ids':
-                $this->entityIds = $data;
-                break;
+        if ($type == 'entity_ids') {
+            $this->entityIds = $data;
         }
     }
 
@@ -106,8 +114,10 @@ private function isMsiEnabled(): bool
      *      reserved
      *      salable_qty
      *      ["msi"]=> [
-     *          website_id => [
+     *          channel => [
      *              qty
+     *              availability
+     *              is_salable
      *              salable_qty
      *          ]
      *      ]
@@ -120,22 +130,26 @@ private function getMsiStock(): array
         $channels = $this->getChannels();
         $stockData = $this->collectMsi($channels);
         $result = $this->getNoMsiStock(true);
+
         foreach ($stockData as $value) {
             foreach ($channels as $channel) {
                 if (!array_key_exists($value['product_id'], $result)) {
                     continue;
                 }
-                $qty = $result[$value['product_id']]['qty'];
+
+                $qty = $value[sprintf('quantity_%s', (int)$channel)];
                 $reserved = $result[$value['product_id']]['reserved'] * -1;
                 $salableQty = max($qty, $qty - $reserved);
-                $result[$value['product_id']]
-                ['msi']
-                [$value['website_id']] = [
-                    'qty' => $value[sprintf('quantity_%s', $channel)],
-                    'salable_qty' => $salableQty
+
+                $result[$value['product_id']]['msi'][$channel] = [
+                    'qty' => $value[sprintf('quantity_%s', (int)$channel)] ?? 0,
+                    'is_salable' => $value[sprintf('is_salable_%s', (int)$channel)] ?? 0,
+                    'availability' => $value[sprintf('is_salable_%s', (int)$channel)] ?? 0,
+                    'salable_qty' => $salableQty ?? 0
                 ];
             }
         }
+
         return $result;
     }
 
@@ -146,19 +160,10 @@ private function getMsiStock(): array
      */
     private function getChannels(): array
     {
-        $selectChannels = $this->resource->getConnection()
-            ->select()
-            ->from(
-                $this->resource->getTableName('inventory_stock_sales_channel'),
-                [
-                    'stock_id'
-                ]
-            )->where('type = ?', 'website');
-        $channels = array_unique($this->resource->getConnection()->fetchCol($selectChannels));
-        if (count($channels) == 1 && reset($channels) != 1) {
-            $channels = [1];
-        }
-        return $channels;
+        $select = $this->resource->getConnection()->select()
+            ->from($this->resource->getTableName('inventory_stock_sales_channel'), ['stock_id'])
+            ->where('type = ?', 'website');
+        return array_unique($this->resource->getConnection()->fetchCol($select));
     }
 
     /**
@@ -169,49 +174,28 @@ private function getChannels(): array
      */
     private function collectMsi(array $channels): array
     {
-        $channel = min($channels);
-        $channels = array_flip($channels);
-        unset($channels[$channel]);
-        $channels = array_flip($channels);
-
-        $stockTablePrimary = $this->resource->getTableName(sprintf('inventory_stock_%s', $channel));
-        if (!$this->resource->getConnection()->isTableExists($stockTablePrimary)) {
-            return [];
-        }
-
-        $selectStock = $this->resource->getConnection()
-            ->select()
+        $select = $this->resource->getConnection()->select()
             ->from(
-                $stockTablePrimary,
+                ['cpe' => $this->resource->getTableName('catalog_product_entity')],
+                ['product_id' => 'entity_id', $this->linkField]
+            )->where(
+                'cpe.entity_id IN (?)',
+                $this->entityIds
+            );
+
+        foreach ($channels as $channel) {
+            $table = sprintf('inventory_stock_%s', (int)$channel);
+            $select->joinLeft(
+                [$table => $this->resource->getTableName($table)],
+                "cpe.sku = {$table}.sku",
                 [
-                    'product_id',
-                    'website_id',
-                    sprintf('quantity_%s', $channel) => 'quantity'
+                    sprintf('quantity_%s', (int)$channel) => "{$table}.quantity",
+                    sprintf('is_salable_%s', (int)$channel) => "{$table}.is_salable"
                 ]
             );
-        foreach ($channels as $channel) {
-            $stockTable = $this->resource->getTableName(sprintf('inventory_stock_%s', $channel));
-            if (!$this->resource->getConnection()->tableColumnExists($stockTable, 'website_id')) {
-                $selectStock->joinLeft(
-                    $stockTable,
-                    "{$stockTable}.sku = {$stockTablePrimary}.sku",
-                    [
-                        sprintf('quantity_%s', $channel) => 'quantity'
-                    ]
-                );
-            } else {
-                $selectStock->joinLeft(
-                    $stockTable,
-                    "{$stockTable}.website_id = {$stockTablePrimary}.website_id and
-                 {$stockTable}.product_id = {$stockTablePrimary}.product_id",
-                    [
-                        sprintf('quantity_%s', $channel) => 'quantity'
-                    ]
-                );
-            }
         }
-        $selectStock->where("{$stockTablePrimary}.product_id IN (?)", $this->entityIds);
-        return $this->resource->getConnection()->fetchAll($selectStock);
+
+        return $this->resource->getConnection()->fetchAll($select);
     }
 
     /**
@@ -231,7 +215,7 @@ private function collectMsi(array $channels): array
      * @param bool $addMsi
      * @return array[]
      */
-    private function getNoMsiStock($addMsi = false): array
+    private function getNoMsiStock(bool $addMsi = false): array
     {
         $result = [];
         $select = $this->resource->getConnection()
@@ -248,7 +232,7 @@ private function getNoMsiStock($addMsi = false): array
                 ]
             )->joinLeft(
                 ['catalog_product_entity' => $this->resource->getTableName('catalog_product_entity')],
-                "catalog_product_entity.{$this->linkField} = cataloginventory_stock_item.product_id",
+                "catalog_product_entity.entity_id = cataloginventory_stock_item.product_id",
                 ['sku']
             );
         if ($addMsi) {
@@ -266,6 +250,7 @@ private function getNoMsiStock($addMsi = false): array
                 [
                     'qty' => (int)$value['qty'],
                     'is_in_stock' => (int)$value['is_in_stock'],
+                    'availability' => (int)$value['is_in_stock'],
                     'manage_stock' => (int)$value['manage_stock'],
                     'qty_increments' => (int)$value['qty_increments'],
                     'min_sale_qty' => (int)$value['min_sale_qty']
@@ -280,6 +265,33 @@ private function getNoMsiStock($addMsi = false): array
         return $result;
     }
 
+    /**
+     * @param int $storeId
+     * @return string|null
+     */
+    public function getChannelByStoreId(int $storeId): ?string
+    {
+        if (!$this->isMsiEnabled()) {
+            return null;
+        }
+
+        $salesChannelsTable = $this->resource->getTableName('inventory_stock_sales_channel');
+        if (!$this->resource->getConnection()->isTableExists($salesChannelsTable)) {
+            return null;
+        }
+
+        try {
+            $code = $this->storeRepository->getById($storeId)->getWebsite()->getCode();
+            $select = $this->resource->getConnection()->select()
+                ->from($salesChannelsTable, ['stock_id'])
+                ->where('code = ?', $code);
+
+            return $this->resource->getConnection()->fetchOne($select);
+        } catch (Exception $exception) {
+            return null;
+        }
+    }
+
     /**
      * @return string[]
      */
@@ -291,15 +303,13 @@ public function getRequiredParameters(): array
     /**
      * @param string $type
      */
-    public function resetData($type = 'all')
+    public function resetData(string $type = 'all')
     {
         if ($type == 'all') {
             unset($this->entityIds);
         }
-        switch ($type) {
-            case 'entity_ids':
-                unset($this->entityIds);
-                break;
+        if ($type == 'entity_ids') {
+            unset($this->entityIds);
         }
     }
 }
diff --git a/Service/ProductData/AttributeCollector/Data/Url.php b/Service/ProductData/AttributeCollector/Data/Url.php
index cbe3356..66e2434 100755
--- a/Service/ProductData/AttributeCollector/Data/Url.php
+++ b/Service/ProductData/AttributeCollector/Data/Url.php
@@ -7,7 +7,10 @@
 
 namespace Datatrics\Connect\Service\ProductData\AttributeCollector\Data;
 
+use Exception;
+use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\EntityManager\MetadataPool;
 use Magento\Store\Api\StoreRepositoryInterface;
 
 /**
@@ -29,7 +32,7 @@ class Url
     public const URL_PATTERN = '%s%s';
 
     /**
-     * URL pattern for no-rewrite items
+     * URL patterns for no-rewrite items
      */
     public const URL_PATTERN_EXTRA = [
         'product' => '%scatalog/product/view/id/%s',
@@ -57,19 +60,25 @@ class Url
      * @var StoreRepositoryInterface
      */
     private $storeRepository;
+    /**
+     * @var string
+     */
+    private $linkField;
 
     /**
-     * Price constructor.
-     *
      * @param ResourceConnection $resource
      * @param StoreRepositoryInterface $storeRepository
+     * @param MetadataPool $metadataPool
+     * @throws Exception
      */
     public function __construct(
         ResourceConnection $resource,
-        StoreRepositoryInterface $storeRepository
+        StoreRepositoryInterface $storeRepository,
+        MetadataPool $metadataPool
     ) {
         $this->resource = $resource;
         $this->storeRepository = $storeRepository;
+        $this->linkField = $metadataPool->getMetadata(ProductInterface::class)->getLinkField();
     }
 
     /**
@@ -126,17 +135,31 @@ private function collectUrl(): array
         $select = $this->resource->getConnection()
             ->select()
             ->from(
-                $this->resource->getTableName('url_rewrite'),
-                ['entity_id', 'request_path']
-            )->where('entity_id IN (?)', $this->entityIds)
-            ->where('redirect_type = ?', 0)
-            ->where('metadata IS NULL')
-            ->where('store_id = ?', $this->storeId)
-            ->where('entity_type = ?', $this->type);
+                ['catalog_product_entity' => $this->resource->getTableName('catalog_product_entity')],
+                [$this->linkField]
+            )->join(
+                ['url_rewrite' => $this->resource->getTableName('url_rewrite')],
+                'catalog_product_entity.entity_id = url_rewrite.entity_id',
+            )->where(
+                "catalog_product_entity.{$this->linkField} in (?)",
+                $this->entityIds
+            )->where(
+                'url_rewrite.redirect_type = ?',
+                0
+            )->where(
+                'url_rewrite.metadata IS NULL'
+            )->where(
+                'url_rewrite.store_id = ?',
+                $this->storeId
+            )->where(
+                'url_rewrite.entity_type = ?',
+                $this->type
+            );
+
         $values = $this->resource->getConnection()->fetchAll($select);
         $storeUrl = $this->getStoreUrl();
         foreach ($values as $value) {
-            $result[$value['entity_id']] = sprintf(
+            $result[$value[$this->linkField]] = sprintf(
                 self::URL_PATTERN,
                 $storeUrl,
                 $value['request_path']
@@ -144,7 +167,7 @@ private function collectUrl(): array
         }
         foreach ($this->entityIds as $entityId) {
             if (!array_key_exists($entityId, $result)) {
-                $result[$entityId] = sprintf(
+                $result[$this->linkField] = sprintf(
                     self::URL_PATTERN_EXTRA[$this->type],
                     $storeUrl,
                     $entityId
@@ -161,7 +184,7 @@ private function getStoreUrl(): string
     {
         try {
             return $this->storeRepository->getById($this->storeId)->getBaseUrl();
-        } catch (\Exception $exception) {
+        } catch (Exception $exception) {
             return '';
         }
     }
@@ -169,7 +192,7 @@ private function getStoreUrl(): string
     /**
      * @return string[]
      */
-    public function getRequiredParameters()
+    public function getRequiredParameters(): array
     {
         return self::REQUIRE;
     }
diff --git a/Service/ProductData/Data.php b/Service/ProductData/Data.php
index 4b44fb0..d1c036c 100755
--- a/Service/ProductData/Data.php
+++ b/Service/ProductData/Data.php
@@ -7,7 +7,10 @@
 
 namespace Datatrics\Connect\Service\ProductData;
 
-use Magento\Framework\Serialize\Serializer\Json as JsonSerializer;
+use Exception;
+use Magento\Catalog\Api\Data\CategoryInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\EntityManager\MetadataPool;
 
 /**
  * Data class
@@ -17,10 +20,6 @@
 class Data
 {
 
-    /**
-     * @var JsonSerializer
-     */
-    private $json;
     /**
      * @var AttributeCollector\Data\AttributeMapper
      */
@@ -41,30 +40,41 @@ class Data
      * @var AttributeCollector\Data\Price
      */
     private $price;
+    /**
+     * @var ResourceConnection
+     */
+    private $resourceConnection;
+    /**
+     * @var string
+     */
+    private $linkField;
 
     /**
-     * Data constructor.
-     * @param JsonSerializer $json
      * @param AttributeCollector\Data\AttributeMapper $attributeMapper
      * @param AttributeCollector\Data\Url $url
      * @param AttributeCollector\Data\Category $category
      * @param AttributeCollector\Data\Stock $stock
      * @param AttributeCollector\Data\Price $price
+     * @param ResourceConnection $resourceConnection
+     * @param MetadataPool $metadataPool
+     * @throws Exception
      */
     public function __construct(
-        JsonSerializer $json,
         AttributeCollector\Data\AttributeMapper $attributeMapper,
         AttributeCollector\Data\Url $url,
         AttributeCollector\Data\Category $category,
         AttributeCollector\Data\Stock $stock,
-        AttributeCollector\Data\Price $price
+        AttributeCollector\Data\Price $price,
+        ResourceConnection $resourceConnection,
+        MetadataPool $metadataPool
     ) {
-        $this->json = $json;
         $this->attributeMapper = $attributeMapper;
         $this->url = $url;
         $this->category = $category;
         $this->stock = $stock;
         $this->price = $price;
+        $this->resourceConnection = $resourceConnection;
+        $this->linkField = $metadataPool->getMetadata(CategoryInterface::class)->getLinkField();
     }
 
     /**
@@ -74,8 +84,11 @@ public function __construct(
      * @param int $storeId
      * @return array
      */
-    public function execute(array $entityIds, array $attributeMap, array $extraParameters, int $storeId = 0)
+    public function execute(array $entityIds, array $attributeMap, array $extraParameters, int $storeId = 0): array
     {
+        $rowIds = $this->getRowsIds($entityIds);
+        $productIds = array_flip($rowIds);
+
         $result = $this->attributeMapper->execute(
             $entityIds,
             $attributeMap,
@@ -98,43 +111,60 @@ public function execute(array $entityIds, array $attributeMap, array $extraParam
             'product',
             $storeId
         );
+
         foreach ($result as $urlEntityId => $url) {
             $data[$urlEntityId]['url'] = $url;
         }
 
         $result = $this->category->execute(
-            $entityIds,
+            $productIds,
             $storeId,
             'raw',
             $extraParameters
         );
-        foreach ($result as $entityId => $categoryData) {
-            $data[$entityId]['category'] = $categoryData;
+
+        foreach ($result as $productId => $categoryData) {
+            $data[$rowIds[$productId]]['category'] = $categoryData;
         }
 
         if ($extraParameters['stock']['inventory']) {
-            $result = $this->stock->execute(
-                $entityIds
-            );
-
+            $result = $this->stock->execute($productIds);
             $inventoryFields = array_merge(
                 $extraParameters['stock']['inventory_fields'],
-                ['msi', 'salable_qty', 'reserved', 'is_in_stock']
+                ['qty', 'msi', 'salable_qty', 'reserved', 'is_in_stock']
             );
-            foreach ($result as $entityId => $stockData) {
-                $data[$entityId] += array_intersect_key($stockData, array_flip($inventoryFields));
+
+            foreach ($result as $productId => $stockData) {
+                $data[$rowIds[$productId]] += array_intersect_key($stockData, array_flip($inventoryFields));
             }
         }
 
         $result = $this->price->execute(
-            $entityIds,
+            $productIds,
             $extraParameters['behaviour']['grouped']['price_logic'] ?? 'max',
             $extraParameters['behaviour']['bundle']['price_logic'] ?? 'min',
             $storeId
         );
-        foreach ($result as $entityId => $priceData) {
-            $data[$entityId] += $priceData;
+
+        foreach ($result as $productId => $priceData) {
+            $data[$rowIds[$productId]] += $priceData;
         }
+
         return $data;
     }
+
+    /**
+     * @param array $entityIds
+     * @return int[]|string[]
+     */
+    private function getRowsIds(array $entityIds): array
+    {
+        $connection = $this->resourceConnection->getConnection();
+        $table = $this->resourceConnection->getTableName('catalog_product_entity');
+        $select = $connection->select()
+            ->from($table, ['entity_id', $this->linkField])
+            ->where("{$this->linkField} IN (?)", $entityIds);
+
+        return $connection->fetchPairs($select);
+    }
 }
diff --git a/Service/ProductData/Filter.php b/Service/ProductData/Filter.php
index 03cd580..3ed66c8 100755
--- a/Service/ProductData/Filter.php
+++ b/Service/ProductData/Filter.php
@@ -9,9 +9,11 @@
 
 use Exception;
 use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product\Attribute\Source\Status;
 use Magento\Catalog\Model\Product\Visibility;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Store\Model\StoreManagerInterface;
 
 /**
  * Filter class
@@ -27,23 +29,30 @@ class Filter
      * @var string
      */
     private $entityId;
+    /**
+     * @var StoreManagerInterface
+     */
+    private $storeManager;
 
     /**
      * Data constructor.
      * @param ResourceConnection $resourceConnection
+     * @param StoreManagerInterface $storeManager
      * @param MetadataPool $metadataPool
      * @throws Exception
      */
     public function __construct(
         ResourceConnection $resourceConnection,
+        StoreManagerInterface $storeManager,
         MetadataPool $metadataPool
     ) {
         $this->resourceConnection = $resourceConnection;
+        $this->storeManager = $storeManager;
         $this->entityId = $metadataPool->getMetadata(ProductInterface::class)->getLinkField();
     }
 
     /**
-     * Execute filters and return producty entity ids
+     * Execute filters and return product entity ids
      *
      * @param array $filter
      * @param int $storeId
@@ -51,20 +60,13 @@ public function __construct(
      */
     public function execute(array $filter, int $storeId = 0): array
     {
-        if ($filter['filter_by_visibility']) {
-            $visibility = is_array($filter['visibility']) ? $filter['visibility'] : explode(',', $filter['visibility']);
-        } else {
-            $visibility = [
-                Visibility::VISIBILITY_NOT_VISIBLE,
-                Visibility::VISIBILITY_IN_CATALOG,
-                Visibility::VISIBILITY_IN_SEARCH,
-                Visibility::VISIBILITY_BOTH,
-            ];
-        }
-        $entityIds = $this->filterVisibility($visibility);
+        $entityIds = $this->filterVisibility($filter);
+
         if ($storeId) {
-            $entityIds = $this->filterWebsite($entityIds, $storeId);
+            $websiteId = $this->getWebsiteId($storeId);
+            $entityIds = $this->filterWebsite($entityIds, $websiteId);
         }
+
         if (!$filter['add_disabled_products']) {
             $entityIds = $this->filterEnabledStatus($entityIds);
         }
@@ -76,17 +78,31 @@ public function execute(array $filter, int $storeId = 0): array
                 $filter['category']
             );
         }
+
         return $entityIds;
     }
 
     /**
      * Filter entity ids to exclude products based on visibility
      *
-     * @param array $visibility
+     * @param array $filter
      * @return array
      */
-    private function filterVisibility(array $visibility): array
+    private function filterVisibility(array $filter): array
     {
+        if ($filter['filter_by_visibility']) {
+            $visibility = is_array($filter['visibility'])
+                ? $filter['visibility']
+                : explode(',', $filter['visibility']);
+        } else {
+            $visibility = [
+                Visibility::VISIBILITY_NOT_VISIBLE,
+                Visibility::VISIBILITY_IN_CATALOG,
+                Visibility::VISIBILITY_IN_SEARCH,
+                Visibility::VISIBILITY_BOTH,
+            ];
+        }
+
         $connection = $this->resourceConnection->getConnection();
         $select = $connection->select()->distinct()->from(
             ['catalog_product_entity_int' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
@@ -95,31 +111,57 @@ private function filterVisibility(array $visibility): array
             ['eav_attribute' => $this->resourceConnection->getTableName('eav_attribute')],
             'eav_attribute.attribute_id = catalog_product_entity_int.attribute_id',
             []
-        )->where('value IN (?)', $visibility)
-            ->where('attribute_code = ?', 'visibility')
-            ->where('store_id IN (?)', [0]);
+        )->where(
+            'value IN (?)',
+            $visibility
+        )->where(
+            'attribute_code = ?',
+            'visibility'
+        )->where(
+            'store_id IN (?)',
+            [0]
+        );
+
         return $connection->fetchCol($select);
     }
 
+    /**
+     * @param int $storeId
+     * @return int
+     */
+    private function getWebsiteId(int $storeId = 0): int
+    {
+        try {
+            return (int)$this->storeManager->getStore($storeId)->getWebsiteId();
+        } catch (Exception $exception) {
+            return 0;
+        }
+    }
+
     /**
      * Filter entity ids to exclude products by website
      *
      * @param array $entityIds
-     * @param int $storeId
+     * @param int $websiteId
      * @return array
      */
-    private function filterWebsite(array $entityIds, int $storeId): array
+    private function filterWebsite(array $entityIds, int $websiteId): array
     {
         $connection = $this->resourceConnection->getConnection();
         $select = $connection->select()->from(
-            ['store' => $this->resourceConnection->getTableName('store')],
-            []
-        )->joinLeft(
+            ['catalog_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')],
+            [$this->entityId]
+        )->join(
             ['catalog_product_website' => $this->resourceConnection->getTableName('catalog_product_website')],
-            'catalog_product_website.website_id = store.website_id',
-            ['product_id']
-        )->where('store.store_id = ?', $storeId)
-            ->where('catalog_product_website.product_id in (?)', $entityIds);
+            'catalog_product_entity.entity_id = catalog_product_website.product_id',
+        )->where(
+            'catalog_product_website.website_id = ?',
+            $websiteId
+        )->where(
+            "catalog_product_entity.{$this->entityId} in (?)",
+            $entityIds
+        );
+
         return $connection->fetchCol($select);
     }
 
@@ -135,14 +177,24 @@ private function filterEnabledStatus(array $entityIds): array
         $select = $connection->select()->distinct()->from(
             ['catalog_product_entity_int' => $this->resourceConnection->getTableName('catalog_product_entity_int')],
             [$this->entityId]
-        )->joinLeft(
+        )->join(
             ['eav_attribute' => $this->resourceConnection->getTableName('eav_attribute')],
             'eav_attribute.attribute_id = catalog_product_entity_int.attribute_id',
             []
-        )->where('value = ?', 1)
-            ->where('attribute_code = ?', 'status')
-            ->where('store_id IN (?)', [0])
-            ->where($this->entityId . ' IN (?)', $entityIds);
+        )->where(
+            'value = ?',
+            Status::STATUS_ENABLED
+        )->where(
+            'attribute_code = ?',
+            'status'
+        )->where(
+            'store_id IN (?)',
+            [0]
+        )->where(
+            $this->entityId . ' IN (?)',
+            $entityIds
+        );
+
         return $connection->fetchCol($select);
     }
 
@@ -154,21 +206,26 @@ private function filterEnabledStatus(array $entityIds): array
      * @param array $categoryIds
      * @return array
      */
-    private function filterByCategories(
-        array $entityIds,
-        string $behaviour,
-        array $categoryIds
-    ): array {
+    private function filterByCategories(array $entityIds, string $behaviour, array $categoryIds): array
+    {
         $connection = $this->resourceConnection->getConnection();
-        $select = $connection->select()->distinct()->from(
+        $select = $connection->select()->from(
+            ['catalog_product_entity' => $this->resourceConnection->getTableName('catalog_product_entity')],
+            [$this->entityId]
+        )->join(
             ['catalog_category_product' => $this->resourceConnection->getTableName('catalog_category_product')],
-            'product_id'
-        )->where('product_id in (?)', $entityIds);
+            'catalog_product_entity.entity_id = catalog_category_product.product_id',
+        )->where(
+            "catalog_product_entity.{$this->entityId} in (?)",
+            $entityIds
+        )->group($this->entityId);
+
         if ($behaviour == 'in') {
-            $select->where('category_id in (?)', $categoryIds);
+            $select->where('catalog_category_product.category_id in (?)', $categoryIds);
         } else {
-            $select->where('category_id not in (?)', $categoryIds);
+            $select->where('catalog_category_product.category_id not in (?)', $categoryIds);
         }
+
         return $connection->fetchCol($select);
     }
 }
diff --git a/Service/ProductData/Type.php b/Service/ProductData/Type.php
index e5c737d..0ed4c27 100755
--- a/Service/ProductData/Type.php
+++ b/Service/ProductData/Type.php
@@ -7,9 +7,6 @@
 
 namespace Datatrics\Connect\Service\ProductData;
 
-use Magento\Framework\App\ResourceConnection;
-use Magento\Framework\Serialize\Serializer\Json as JsonSerializer;
-use Datatrics\Connect\Service\ProductData\AttributeCollector\Data\AttributeMapper;
 use Datatrics\Connect\Service\ProductData\AttributeCollector\Data\ConfigurableKey;
 use Datatrics\Connect\Service\ProductData\AttributeCollector\Data\Parents;
 
@@ -19,18 +16,6 @@
 class Type
 {
 
-    /**
-     * @var JsonSerializer
-     */
-    private $json;
-    /**
-     * @var AttributeMapper
-     */
-    private $attributeMapper;
-    /**
-     * @var ResourceConnection
-     */
-    private $resourceConnection;
     /**
      * @var ConfigurableKey
      */
@@ -39,7 +24,6 @@ class Type
      * @var Data
      */
     private $data;
-
     /**
      * @var Parents
      */
@@ -47,24 +31,15 @@ class Type
 
     /**
      * Data constructor.
-     * @param JsonSerializer $json
-     * @param AttributeMapper $attributeMapper
-     * @param ResourceConnection $resourceConnection
      * @param Data $data
      * @param ConfigurableKey $configurableKey
      * @param Parents $parents
      */
     public function __construct(
-        JsonSerializer $json,
-        AttributeMapper $attributeMapper,
-        ResourceConnection $resourceConnection,
         Data $data,
         ConfigurableKey $configurableKey,
         Parents $parents
     ) {
-        $this->json = $json;
-        $this->attributeMapper = $attributeMapper;
-        $this->resourceConnection = $resourceConnection;
         $this->data = $data;
         $this->configurableKey = $configurableKey;
         $this->parents = $parents;
@@ -75,27 +50,18 @@ public function __construct(
      * @param array $attributeMap
      * @param array $extraParameters
      * @param int $storeId
-     * @param int $limit
-     * @param int $page
      * @return array
      */
     public function execute(
         array $entityIds,
         array $attributeMap,
         array $extraParameters,
-        int $storeId = 0,
-        int $limit = 10000,
-        int $page = 1
+        int $storeId = 0
     ): array {
         if (empty($entityIds)) {
             return [];
         }
-        $entityIds = array_chunk($entityIds, (int)$limit);
-        if (isset($entityIds[$page - 1])) {
-            $entityIds = $entityIds[$page - 1];
-        } else {
-            $entityIds = $entityIds[0];
-        }
+
         $parents = $this->parents->execute();
         $toUnset = [];
         $parentAttributeToUse = [];
@@ -124,6 +90,7 @@ public function execute(
             $parentAttributes['bundle'][] = 'image';
         }
         $parentType = false;
+
         foreach ($entityIds as $entityId) {
             if (!array_key_exists($entityId, $parents)) {
                 continue;
@@ -206,6 +173,7 @@ public function execute(
                 $data[$entityId]['image_logic'] = 0;
             }
         }
+
         return array_diff_key($data, array_flip($toUnset));
     }
 
diff --git a/etc/crontab.xml b/etc/crontab.xml
index 548f4ed..757f499 100755
--- a/etc/crontab.xml
+++ b/etc/crontab.xml
@@ -5,20 +5,16 @@
   -->
 
     
-        
+        
             datatrics_connect_order/order_sync/cron
         
-        
+        
             datatrics_connect_customer/customer_sync/cron
         
-        
+        
             datatrics_connect_product/product_sync/cron
         
-        
+        
             0 2 * * *
         
     
diff --git a/etc/db_schema.xml b/etc/db_schema.xml
index 7ddc821..b47762b 100755
--- a/etc/db_schema.xml
+++ b/etc/db_schema.xml
@@ -6,97 +6,34 @@
 
 
-    
-        
-        
-        
-        
-        
-        
-            
-        
-        
-    
- + - - - - - - + + + + + + + + - + + + + + + + + + + + +
diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json index c1d920e..42bf8b9 100755 --- a/etc/db_schema_whitelist.json +++ b/etc/db_schema_whitelist.json @@ -19,11 +19,20 @@ "store_id": true, "update_msg": true, "update_attempts": true, - "status": true + "status": true, + "updated_at": true, + "parent_id": true }, "constraint": { "PRIMARY": true, - "DATATRICS_CONTENT_STORE_STORE_ID_STORE_STORE_ID": true + "DATATRICS_CONTENT_STORE_STORE_ID_STORE_STORE_ID": true, + "DATATRICS_CONTENT_STORE_PRODUCT_ID_STORE_ID": true + }, + "index": { + "DATATRICS_CONTENT_STORE_PRODUCT_ID": true, + "DATATRICS_CONTENT_STORE_STORE_ID": true, + "DATATRICS_CONTENT_STORE_STATUS": true, + "DATATRICS_CONTENT_STORE_PARENT_ID": true } }, "datatrics_sales": { diff --git a/etc/di.xml b/etc/di.xml index 11960ec..c2b1fb4 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -171,20 +171,19 @@ - Datatrics\Connect\Model\Command\ContentUpdate\Proxy + Datatrics\Connect\Model\Command\ContentUpdate\Proxy - Datatrics\Connect\Model\Command\ContentAdd\Proxy + Datatrics\Connect\Model\Command\ContentAdd\Proxy + Datatrics\Connect\Api\Config\System\ContentInterface\Proxy - Datatrics\Connect\Model\Command\ContentInvalidate\Proxy + Datatrics\Connect\Model\Command\ContentInvalidate\Proxy + Datatrics\Connect\Api\Config\System\ContentInterface\Proxy diff --git a/view/adminhtml/layout/default.xml b/view/adminhtml/layout/default.xml deleted file mode 100755 index 7a6f26b..0000000 --- a/view/adminhtml/layout/default.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/view/adminhtml/ui_component/product_listing.xml b/view/adminhtml/ui_component/product_listing.xml deleted file mode 100755 index 8081019..0000000 --- a/view/adminhtml/ui_component/product_listing.xml +++ /dev/null @@ -1,19 +0,0 @@ - - -- - - - - - add_to_queue - - - - - - From 753ba55af6f07ead2215eeeafbdf1ae3bdc3e8be Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Tue, 22 Aug 2023 11:30:28 +0200 Subject: [PATCH 2/4] Load pixel block in last order --- view/frontend/layout/default.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/frontend/layout/default.xml b/view/frontend/layout/default.xml index 3ce2f26..946f627 100755 --- a/view/frontend/layout/default.xml +++ b/view/frontend/layout/default.xml @@ -12,7 +12,7 @@ From cd886a3523aad65c9cc4f0f5a7705419155cb017 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Tue, 22 Aug 2023 11:57:04 +0200 Subject: [PATCH 3/4] Implemented new admin UI --- Block/Adminhtml/Datatrics/Heading.php | 4 +- .../System/Config/Button/DebugCheck.php | 54 +-- .../System/Config/Button/ErrorCheck.php | 54 +-- .../System/Config/Button/VersionCheck.php | 2 +- Controller/Adminhtml/Log/Debug.php | 143 ------ Controller/Adminhtml/Log/Error.php | 143 ------ Controller/Adminhtml/Log/Stream.php | 169 +++++++ Logger/DebugLogger.php | 8 +- Logger/ErrorLogger.php | 10 +- Logger/Handler/Debug.php | 2 +- Logger/Handler/Error.php | 2 +- view/adminhtml/requirejs-config.js | 1 + .../system/config/button/credentials.phtml | 14 +- .../system/config/button/debug.phtml | 5 +- .../system/config/button/error.phtml | 5 +- .../system/config/button/integration.phtml | 14 +- .../system/config/button/selftest.phtml | 5 +- .../system/config/button/tracking_test.phtml | 14 +- .../system/config/button/version.phtml | 19 +- .../system/config/fieldset/header.phtml | 35 +- view/adminhtml/web/css/source/_icons.less | 51 --- .../adminhtml/web/css/source/_mm-buttons.less | 103 ----- view/adminhtml/web/css/source/_module.less | 413 ++---------------- .../web/css/source/extend/_credentials.less | 32 ++ .../web/css/source/extend/_table.less | 49 +++ .../web/css/source/module/_debug.less | 80 ++++ .../web/css/source/module/_header.less | 34 ++ view/adminhtml/web/css/source/module/_ui.less | 6 + .../web/css/source/module/_version.less | 92 ++++ .../web/css/source/module/ui/_button.less | 111 +++++ .../web/css/source/module/ui/_modal.less | 129 ++++++ .../web/css/source/variables/_colors.less | 21 + .../web/css/source/variables/_icons.less | 28 -- .../web/css/source/variables/_mm-colors.less | 20 - .../fonts/datatrics-icons/datatrics-icons.eot | Bin 10768 -> 0 bytes .../fonts/datatrics-icons/datatrics-icons.svg | 46 -- .../fonts/datatrics-icons/datatrics-icons.ttf | Bin 10568 -> 0 bytes .../datatrics-icons/datatrics-icons.woff | Bin 6452 -> 0 bytes .../datatrics-icons/datatrics-icons.woff2 | Bin 5444 -> 0 bytes view/adminhtml/web/js/button-functions.js | 357 ++++++--------- view/adminhtml/web/js/show-more.js | 41 ++ 41 files changed, 1036 insertions(+), 1280 deletions(-) delete mode 100755 Controller/Adminhtml/Log/Debug.php delete mode 100755 Controller/Adminhtml/Log/Error.php create mode 100644 Controller/Adminhtml/Log/Stream.php delete mode 100755 view/adminhtml/web/css/source/_icons.less delete mode 100755 view/adminhtml/web/css/source/_mm-buttons.less create mode 100755 view/adminhtml/web/css/source/extend/_credentials.less create mode 100644 view/adminhtml/web/css/source/extend/_table.less create mode 100644 view/adminhtml/web/css/source/module/_debug.less create mode 100644 view/adminhtml/web/css/source/module/_header.less create mode 100644 view/adminhtml/web/css/source/module/_ui.less create mode 100644 view/adminhtml/web/css/source/module/_version.less create mode 100644 view/adminhtml/web/css/source/module/ui/_button.less create mode 100644 view/adminhtml/web/css/source/module/ui/_modal.less create mode 100755 view/adminhtml/web/css/source/variables/_colors.less delete mode 100755 view/adminhtml/web/css/source/variables/_icons.less delete mode 100755 view/adminhtml/web/css/source/variables/_mm-colors.less delete mode 100755 view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.eot delete mode 100755 view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.svg delete mode 100755 view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.ttf delete mode 100755 view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.woff delete mode 100755 view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.woff2 create mode 100755 view/adminhtml/web/js/show-more.js diff --git a/Block/Adminhtml/Datatrics/Heading.php b/Block/Adminhtml/Datatrics/Heading.php index 2a15bab..9213e05 100755 --- a/Block/Adminhtml/Datatrics/Heading.php +++ b/Block/Adminhtml/Datatrics/Heading.php @@ -26,8 +26,8 @@ public function render(AbstractElement $element): string $html = ''; $html .= ' '; $html .= ' '; - $html .= '
' . $element->getData('label') . '
'; - $html .= '
' . $element->getData('comment') . '
'; + $html .= '
' . $element->getData('label') . '
'; + $html .= '
' . $element->getData('comment') . '
'; $html .= ' '; $html .= ' '; $html .= ''; diff --git a/Block/Adminhtml/System/Config/Button/DebugCheck.php b/Block/Adminhtml/System/Config/Button/DebugCheck.php index 4b6d3db..600b8d3 100755 --- a/Block/Adminhtml/System/Config/Button/DebugCheck.php +++ b/Block/Adminhtml/System/Config/Button/DebugCheck.php @@ -7,7 +7,7 @@ namespace Datatrics\Connect\Block\Adminhtml\System\Config\Button; -use Magento\Backend\Block\Template\Context; +use Magento\Backend\Block\Widget\Button; use Magento\Config\Block\System\Config\Form\Field; use Magento\Framework\Data\Form\Element\AbstractElement; @@ -23,41 +23,18 @@ class DebugCheck extends Field protected $_template = 'Datatrics_Connect::system/config/button/debug.phtml'; /** - * @var \Magento\Framework\App\RequestInterface + * @inheritDoc */ - private $request; - - /** - * Credentials constructor. - * - * @param Context $context - * @param array $data - */ - public function __construct( - Context $context, - array $data = [] - ) { - $this->request = $context->getRequest(); - parent::__construct($context, $data); - } - - /** - * @param AbstractElement $element - * - * @return string - */ - public function render(AbstractElement $element) + public function render(AbstractElement $element): string { $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); return parent::render($element); } /** - * @param AbstractElement $element - * - * @return string + * @inheritDoc */ - public function _getElementHtml(AbstractElement $element) + public function _getElementHtml(AbstractElement $element): string { return $this->_toHtml(); } @@ -65,24 +42,25 @@ public function _getElementHtml(AbstractElement $element) /** * @return string */ - public function getDebugCheckUrl() + public function getDebugCheckUrl(): string { - return $this->getUrl('datatrics/log/debug'); + return $this->getUrl('datatrics/log/stream', ['type' => 'debug']); } /** - * @return mixed + * @return string */ - public function getButtonHtml() + public function getButtonHtml(): string { - $buttonData = ['id' => 'mm-button_debug', 'label' => __('Check last 100 debug log records')]; try { - $button = $this->getLayout()->createBlock( - \Magento\Backend\Block\Widget\Button::class - )->setData($buttonData); - return $button->toHtml(); + return $this->getLayout() + ->createBlock(Button::class) + ->setData([ + 'id' => 'mm-datatrics-button_debug', + 'label' => __('Check last 50 debug log records') + ])->toHtml(); } catch (\Exception $e) { - return false; + return ''; } } } diff --git a/Block/Adminhtml/System/Config/Button/ErrorCheck.php b/Block/Adminhtml/System/Config/Button/ErrorCheck.php index 31ebf42..389b026 100755 --- a/Block/Adminhtml/System/Config/Button/ErrorCheck.php +++ b/Block/Adminhtml/System/Config/Button/ErrorCheck.php @@ -7,7 +7,7 @@ namespace Datatrics\Connect\Block\Adminhtml\System\Config\Button; -use Magento\Backend\Block\Template\Context; +use Magento\Backend\Block\Widget\Button; use Magento\Config\Block\System\Config\Form\Field; use Magento\Framework\Data\Form\Element\AbstractElement; @@ -23,41 +23,18 @@ class ErrorCheck extends Field protected $_template = 'Datatrics_Connect::system/config/button/error.phtml'; /** - * @var \Magento\Framework\App\RequestInterface + * @inheritDoc */ - private $request; - - /** - * Credentials constructor. - * - * @param Context $context - * @param array $data - */ - public function __construct( - Context $context, - array $data = [] - ) { - $this->request = $context->getRequest(); - parent::__construct($context, $data); - } - - /** - * @param AbstractElement $element - * - * @return string - */ - public function render(AbstractElement $element) + public function render(AbstractElement $element): string { $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); return parent::render($element); } /** - * @param AbstractElement $element - * - * @return string + * @inheritDoc */ - public function _getElementHtml(AbstractElement $element) + public function _getElementHtml(AbstractElement $element): string { return $this->_toHtml(); } @@ -65,24 +42,25 @@ public function _getElementHtml(AbstractElement $element) /** * @return string */ - public function getErrorCheckUrl() + public function getErrorCheckUrl(): string { - return $this->getUrl('datatrics/log/error'); + return $this->getUrl('datatrics/log/stream', ['type' => 'error']); } /** - * @return mixed + * @return string */ - public function getButtonHtml() + public function getButtonHtml(): string { - $buttonData = ['id' => 'mm-button_error', 'label' => __('Check last 100 error log records')]; try { - $button = $this->getLayout()->createBlock( - \Magento\Backend\Block\Widget\Button::class - )->setData($buttonData); - return $button->toHtml(); + return $this->getLayout() + ->createBlock(Button::class) + ->setData([ + 'id' => 'mm-datatrics-button_error', + 'label' => __('Check last 50 error log records') + ])->toHtml(); } catch (\Exception $e) { - return false; + return ''; } } } diff --git a/Block/Adminhtml/System/Config/Button/VersionCheck.php b/Block/Adminhtml/System/Config/Button/VersionCheck.php index a2af007..0eca814 100755 --- a/Block/Adminhtml/System/Config/Button/VersionCheck.php +++ b/Block/Adminhtml/System/Config/Button/VersionCheck.php @@ -99,7 +99,7 @@ public function getChangeLogUrl() */ public function getButtonHtml() { - $buttonData = ['id' => 'mm-button_version', 'label' => __('Check for latest versions')]; + $buttonData = ['id' => 'mm-datatrics-button_version', 'label' => __('Check for latest versions')]; try { $button = $this->getLayout()->createBlock( \Magento\Backend\Block\Widget\Button::class diff --git a/Controller/Adminhtml/Log/Debug.php b/Controller/Adminhtml/Log/Debug.php deleted file mode 100755 index 2cfc91f..0000000 --- a/Controller/Adminhtml/Log/Debug.php +++ /dev/null @@ -1,143 +0,0 @@ -resultJsonFactory = $resultJsonFactory; - $this->configRepository = $configRepository; - $this->dir = $dir; - $this->file = $file; - $this->serializerJson = $serializerJson; - parent::__construct($context); - } - - /** - * @return \Magento\Framework\App\ResponseInterface|Json|\Magento\Framework\Controller\ResultInterface - * @throws \Magento\Framework\Exception\FileSystemException - */ - public function execute() - { - $resultJson = $this->resultJsonFactory->create(); - if ($this->isLogExists(self::DEBUG_LOG_FILE)) { - $result = $this->prepareLogText(self::DEBUG_LOG_FILE); - } else { - $result = []; - } - return $resultJson->setData(['result' => $result]); - } - - /** - * Prepare encoded log text - * - * @param string $file - * - * @return array - * @throws \Magento\Framework\Exception\FileSystemException - */ - private function prepareLogText(string $file) : array - { - $logFile = sprintf($file, $this->dir->getPath('var')); - $fileContent = explode(PHP_EOL, $this->file->fileGetContents($logFile)); - if (count($fileContent) > 100) { - $fileContent = array_slice($fileContent, -100, 100, true); - } - $result = []; - foreach ($fileContent as $line) { - $data = explode('] ', $line); - $date = ltrim(array_shift($data), '['); - $data = implode('] ', $data); - $data = explode(': ', $data); - array_shift($data); - $result[] = [ - 'date' => $date, - 'msg' => implode(': ', $data) - ]; - } - return $result; - } - - /** - * Check is log file exists - * @param string $file - * - * @return bool - */ - private function isLogExists(string $file) : bool - { - try { - $logFile = sprintf($file, $this->dir->getPath('var')); - return $this->file->isExists($logFile); - } catch (\Exception $e) { - return false; - } - } -} diff --git a/Controller/Adminhtml/Log/Error.php b/Controller/Adminhtml/Log/Error.php deleted file mode 100755 index 0ec42ab..0000000 --- a/Controller/Adminhtml/Log/Error.php +++ /dev/null @@ -1,143 +0,0 @@ -resultJsonFactory = $resultJsonFactory; - $this->configRepository = $configRepository; - $this->dir = $dir; - $this->file = $file; - $this->serializerJson = $serializerJson; - parent::__construct($context); - } - - /** - * @return \Magento\Framework\App\ResponseInterface|Json|\Magento\Framework\Controller\ResultInterface - * @throws \Magento\Framework\Exception\FileSystemException - */ - public function execute() - { - $resultJson = $this->resultJsonFactory->create(); - if ($this->isLogExists(self::ERROR_LOG_FILE)) { - $result = $this->prepareLogText(self::ERROR_LOG_FILE); - } else { - $result = []; - } - return $resultJson->setData(['result' => $result]); - } - - /** - * Prepare encoded log text - * - * @param string $file - * - * @return array - * @throws \Magento\Framework\Exception\FileSystemException - */ - private function prepareLogText(string $file) : array - { - $logFile = sprintf($file, $this->dir->getPath('var')); - $fileContent = explode(PHP_EOL, $this->file->fileGetContents($logFile)); - if (count($fileContent) > 100) { - $fileContent = array_slice($fileContent, -100, 100, true); - } - $result = []; - foreach ($fileContent as $line) { - $data = explode('] ', $line); - $date = ltrim(array_shift($data), '['); - $data = implode('] ', $data); - $data = explode(': ', $data); - array_shift($data); - $result[] = [ - 'date' => $date, - 'msg' => implode(': ', $data) - ]; - } - return $result; - } - - /** - * Check is log file exists - * @param string $file - * - * @return bool - */ - private function isLogExists(string $file) : bool - { - try { - $logFile = sprintf($file, $this->dir->getPath('var')); - return $this->file->isExists($logFile); - } catch (\Exception $e) { - return false; - } - } -} diff --git a/Controller/Adminhtml/Log/Stream.php b/Controller/Adminhtml/Log/Stream.php new file mode 100644 index 0000000..d9606bb --- /dev/null +++ b/Controller/Adminhtml/Log/Stream.php @@ -0,0 +1,169 @@ +resultJsonFactory = $resultJsonFactory; + $this->request = $context->getRequest(); + $this->dir = $dir; + $this->file = $file; + $this->dateTime = $dateTime; + parent::__construct($context); + } + + /** + * @return Json + * @throws FileSystemException + */ + public function execute(): Json + { + $resultJson = $this->resultJsonFactory->create(); + $logFilePath = $this->getLogFilePath(); + + if ($logFilePath && $this->isLogExists($logFilePath)) { + $result = ['result' => $this->prepareLogText($logFilePath)]; + } else { + $result = __('Log is empty'); + } + + return $resultJson->setData($result); + } + + /** + * @return string + */ + private function getLogFilePath(): ?string + { + try { + $type = $this->request->getParam('type') == 'error' ? 'error' : 'debug'; + return sprintf(self::LOG_FILE, $this->dir->getPath('var'), $type); + } catch (\Exception $exception) { + return null; + } + } + + /** + * Check is log file exists + * + * @param $logFilePath + * @return bool + */ + private function isLogExists($logFilePath): bool + { + try { + return $this->file->isExists($logFilePath); + } catch (Exception $e) { + return false; + } + } + + /** + * @param $logFilePath + * @return array + * @throws FileSystemException + */ + private function prepareLogText($logFilePath): array + { + $stream = $this->file->fileOpen($logFilePath, 'r'); + + $this->file->fileSeek($stream, 0, SEEK_END); + $pos = $this->file->fileTell($stream); + $numberOfLines = self::MAX_LINES; + while ($pos >= 0 && $numberOfLines > 0) { + $this->file->fileSeek($stream, $pos); + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $char = fgetc($stream); + if ($char === "\n") { + $numberOfLines--; + } + $pos--; + } + + $result = []; + // phpcs:ignore Magento2.Functions.DiscouragedFunction + while (!feof($stream) && $numberOfLines < self::MAX_LINES) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + if ($line = fgets($stream)) { + $data = explode('] ', $line); + $date = ltrim(array_shift($data), '['); + $data = implode('] ', $data); + $data = explode(': ', $data); + unset($data[0]); + $type = $data[1] ?? '--'; + array_shift($data); + + $result[] = [ + 'date' => $this->dateTime->date('Y-m-d H:i:s', $date) . ' - ' . $type, + 'msg' => implode(': ', $data) + ]; + } + } + + $this->file->fileClose($stream); + return array_reverse($result); + } +} diff --git a/Logger/DebugLogger.php b/Logger/DebugLogger.php index 6048d9a..e71262d 100755 --- a/Logger/DebugLogger.php +++ b/Logger/DebugLogger.php @@ -24,10 +24,10 @@ class DebugLogger extends Logger /** * DebugLogger constructor. * - * @param Json $json + * @param Json $json * @param string $name - * @param array $handlers - * @param array $processors + * @param array $handlers + * @param array $processors */ public function __construct( Json $json, @@ -43,7 +43,7 @@ public function __construct( * Add debug data to Datatrics Log * * @param string $type - * @param mixed $data + * @param mixed $data */ public function addLog(string $type, $data) { diff --git a/Logger/ErrorLogger.php b/Logger/ErrorLogger.php index 2329cb8..f8761b6 100755 --- a/Logger/ErrorLogger.php +++ b/Logger/ErrorLogger.php @@ -24,10 +24,10 @@ class ErrorLogger extends Logger /** * ErrorLogger constructor. * - * @param Json $json + * @param Json $json * @param string $name - * @param array $handlers - * @param array $processors + * @param array $handlers + * @param array $processors */ public function __construct( Json $json, @@ -43,9 +43,9 @@ public function __construct( * Add error data to Datatrics Log * * @param string $type - * @param mixed $data + * @param mixed $data */ - public function addLog($type, $data) + public function addLog(string $type, $data) { if (is_array($data) || is_object($data)) { $this->addRecord(static::ERROR, $type . ': ' . $this->json->serialize($data)); diff --git a/Logger/Handler/Debug.php b/Logger/Handler/Debug.php index c4cd3ba..5b0ee3e 100755 --- a/Logger/Handler/Debug.php +++ b/Logger/Handler/Debug.php @@ -24,5 +24,5 @@ class Debug extends Base /** * @var string */ - protected $fileName = '/var/log/datatrics/debug.log'; + protected $fileName = '/var/log/datatrics-debug.log'; } diff --git a/Logger/Handler/Error.php b/Logger/Handler/Error.php index c31b9b5..0661266 100755 --- a/Logger/Handler/Error.php +++ b/Logger/Handler/Error.php @@ -24,5 +24,5 @@ class Error extends Base /** * @var string */ - protected $fileName = '/var/log/datatrics/error.log'; + protected $fileName = '/var/log/datatrics-error.log'; } diff --git a/view/adminhtml/requirejs-config.js b/view/adminhtml/requirejs-config.js index f4eb79c..b2320e9 100755 --- a/view/adminhtml/requirejs-config.js +++ b/view/adminhtml/requirejs-config.js @@ -1,5 +1,6 @@ var config = { deps: [ 'Datatrics_Connect/js/button-functions', + 'Datatrics_Connect/js/show-more', ] }; diff --git a/view/adminhtml/templates/system/config/button/credentials.phtml b/view/adminhtml/templates/system/config/button/credentials.phtml index ecc9e9f..879f405 100755 --- a/view/adminhtml/templates/system/config/button/credentials.phtml +++ b/view/adminhtml/templates/system/config/button/credentials.phtml @@ -14,9 +14,8 @@ use Datatrics\Connect\Block\Adminhtml\System\Config\Button\Credentials; -getButtonHtml() ?> -
- - + +
+ getButtonHtml() ?> +
+ + +
diff --git a/view/adminhtml/templates/system/config/button/debug.phtml b/view/adminhtml/templates/system/config/button/debug.phtml index 2a5289a..b473065 100755 --- a/view/adminhtml/templates/system/config/button/debug.phtml +++ b/view/adminhtml/templates/system/config/button/debug.phtml @@ -10,8 +10,9 @@ */ ?> -
-
+
+
diff --git a/view/adminhtml/templates/system/config/button/error.phtml b/view/adminhtml/templates/system/config/button/error.phtml index 5520a89..669fbb3 100755 --- a/view/adminhtml/templates/system/config/button/error.phtml +++ b/view/adminhtml/templates/system/config/button/error.phtml @@ -10,8 +10,9 @@ */ ?> -
-
+
+
diff --git a/view/adminhtml/templates/system/config/button/integration.phtml b/view/adminhtml/templates/system/config/button/integration.phtml index e7b8ce9..e64c7e3 100755 --- a/view/adminhtml/templates/system/config/button/integration.phtml +++ b/view/adminhtml/templates/system/config/button/integration.phtml @@ -14,7 +14,6 @@ use Datatrics\Connect\Block\Adminhtml\System\Config\Button\Integration; -getButtonHtml() ?> -
- - -
+ +
+ getButtonHtml() ?> +
+ + +
+
\ No newline at end of file diff --git a/view/adminhtml/templates/system/config/button/selftest.phtml b/view/adminhtml/templates/system/config/button/selftest.phtml index 3f6a62d..1405a53 100755 --- a/view/adminhtml/templates/system/config/button/selftest.phtml +++ b/view/adminhtml/templates/system/config/button/selftest.phtml @@ -9,8 +9,9 @@ * @var \Datatrics\Connect\Block\Adminhtml\System\Config\Button\Credentials $block */ ?> -
-
+
+
    diff --git a/view/adminhtml/templates/system/config/button/tracking_test.phtml b/view/adminhtml/templates/system/config/button/tracking_test.phtml index 2ee57c4..01fce46 100755 --- a/view/adminhtml/templates/system/config/button/tracking_test.phtml +++ b/view/adminhtml/templates/system/config/button/tracking_test.phtml @@ -14,7 +14,6 @@ use Datatrics\Connect\Block\Adminhtml\System\Config\Button\Credentials; -getButtonHtml() ?> -
    - - -
    + +
    + getButtonHtml() ?> +
    + + +
    +
    \ No newline at end of file diff --git a/view/adminhtml/templates/system/config/button/version.phtml b/view/adminhtml/templates/system/config/button/version.phtml index 8ccd027..38c5e43 100755 --- a/view/adminhtml/templates/system/config/button/version.phtml +++ b/view/adminhtml/templates/system/config/button/version.phtml @@ -11,12 +11,21 @@ ?> -
    +
    escapeHtml($block->getVersion()); ?> - getButtonHtml() ?> -
    -
    -
    + +
    + getButtonHtml() ?> + + + +
    + +
    +
    +
      diff --git a/view/adminhtml/templates/system/config/fieldset/header.phtml b/view/adminhtml/templates/system/config/fieldset/header.phtml index 31cab6d..fe76546 100755 --- a/view/adminhtml/templates/system/config/fieldset/header.phtml +++ b/view/adminhtml/templates/system/config/fieldset/header.phtml @@ -6,23 +6,32 @@ */ ?>
      -
      -

      - Developed by Magmodules.
      - A Magento only eCommerce Agency located in the Netherlands. -

      +
      + + escapeHTML(__('Developed by Magmodules.')); ?> + +
      + escapeHTML(__('A Magento only eCommerce Agency located in the Netherlands.')); ?> +
      - - - Manual & Support + + + + + + escapeHTML(__('Manual & Support')); ?> -
      diff --git a/view/adminhtml/web/css/source/_icons.less b/view/adminhtml/web/css/source/_icons.less deleted file mode 100755 index e09e25b..0000000 --- a/view/adminhtml/web/css/source/_icons.less +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright © Magmodules.eu. All rights reserved. - * See COPYING.txt for license details. - */ - -@import "variables/_icons.less"; - -.lib-font-face( - @family-name: @icons-datatrics__font-name, - @font-path: @icons-datatrics__font-name-path, - @font-weight: normal, - @font-style: normal -); - -[class^="mm-"]:before, -[class*=" mm-"]:before { - font-family: @icons-datatrics__font-name; - font-style: normal; - font-weight: normal; - speak: never; - display: inline-block; - text-decoration: inherit; - width: 2.5rem; - margin-right: 0.5rem; - text-align: center; - font-variant: normal; - text-transform: none; - line-height: 2.5rem; - margin-left: 0.5rem; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.mm-icon__download-alt:before { content: @mm-icon__download-alt; } -.mm-icon__list:before { content: @mm-icon__list; } -.mm-icon__datatrics:before { content: @mm-icon__datatrics; } -.mm-icon__list-unordered:before { content: @mm-icon__list-unordered; } -.mm-icon__help-rounded:before { content: @mm-icon__help-rounded; } -.mm-icon__thumbs-down:before { content: @mm-icon__thumbs-down; } -.mm-icon__thumbs-up:before { content: @mm-icon__thumbs-up; } -.mm-icon__font-icons:before { content: @mm-icon__font-icons; } -.mm-icon__info-rounded1:before { content: @mm-icon__info-rounded1; } -.mm-icon__download:before { content: @mm-icon__download; } -.mm-icon__external-link:before { content: @mm-icon__external-link; } -.mm-icon__notification-rounded:before { content: @mm-icon__notification-rounded; } -.mm-icon__envelop:before { content: @mm-icon__envelop; } -.mm-icon__list-unordered-four:before { content: @mm-icon__list-unordered-four; } -.mm-icon__question-circled:before { content: @mm-icon__question-circled; } -.mm-icon__list-thumbnails:before { content: @mm-icon__list-thumbnails; } -.mm-icon__repeat:before { content: @mm-icon__repeat; } -.mm-icon__info-rounded0:before { content: @mm-icon__info-rounded0; } diff --git a/view/adminhtml/web/css/source/_mm-buttons.less b/view/adminhtml/web/css/source/_mm-buttons.less deleted file mode 100755 index 1463745..0000000 --- a/view/adminhtml/web/css/source/_mm-buttons.less +++ /dev/null @@ -1,103 +0,0 @@ -#mm-button { - - &_test, - &_help { - color: @color-white; - font-weight: 700; - font-size: 1.4rem; - padding: 1.9rem 3rem 1.5rem 1.5rem; - border: none; - text-shadow: 0px 1px 1px @mm-color__3; - display: inline-block; - min-width:180px; - vertical-align: top; - } - - &_help { - background: @mm-color__1; - - &:hover { - color: @color-white; - text-decoration: none; - } - } - - &_test { - background: @mm-bg__blue; - } - - &_version { - background: transparent; - padding-left: 0.85rem; - margin: -0.8rem 0 -1rem 2rem; - white-space: nowrap; - color: @mm-version-btn-border; - border: 1px solid @color-gray_light; - transition: 0.3s color, 0.3s border; - - &:before { - content: @mm-icon__repeat; - font-family: @icons-datatrics__font-name; - background-color: @mm-version-btn-icon-bg; - color: @color-white; - border-radius: 50%; - width: 2.2rem; - height: 2.2rem; - display: inline-block; - line-height: 2.4rem; - font-size: 1.20rem; - transition: 0.3s background-color; - margin-right:10px; - } - - &:hover { - color: @mm-color__2; - - &::before { - background-color: @mm-color__2; - } - } - } - - &_error, - &_debug { - border: none; - background: none; - padding: 0; - font-weight: 500; - font-size: 1.2rem; - color: @mm-color__dark-gray; - - span { - text-decoration: underline; - } - - &:before { - content: @mm-icon__external-link; - font-family: @icons-datatrics__font-name; - font-size: 1.5rem; - line-height: 1.5rem; - display: inline-block; - vertical-align: middle; - margin-right: 0.6rem; - } - } - - &_changelog { - background: transparent; - border: none; - text-decoration: underline; - padding: 0; - display: inline; - } -} - -.mm-button__download:before { - margin-right: 0.6rem; - line-height: 1rem; -} - -.mm-disabled { - position: absolute; - top: 0; -} diff --git a/view/adminhtml/web/css/source/_module.less b/view/adminhtml/web/css/source/_module.less index b23cd47..27b0b2e 100755 --- a/view/adminhtml/web/css/source/_module.less +++ b/view/adminhtml/web/css/source/_module.less @@ -1,400 +1,43 @@ -/** - * Copyright © Magmodules.eu. All rights reserved. - * See COPYING.txt for license details. - */ +// +// Imports +// --------------------------------------------- -@import '_icons.less'; -@import '_mm-buttons'; -@import './variables/_mm-colors.less'; +@import './variables/_colors.less'; +@import './module/_ui.less'; +@import './module/_header.less'; +@import './module/_version.less'; +@import './module/_debug.less'; -.admin__menu .item-menu.parent.level-0 > a:before { - content: @mm-icon__datatrics; - font-family: @icons-datatrics__font-name; - font-size: 2rem; - padding-top: .2rem; -} - -.datatrics-extensions .title:before { - width: 20px; - height: 20px; - content: " "; - background-image: url(https://assets.datatrics.com/img/logo/icon.png); - background-size: cover; - display: inline-block; - float: left; - margin-right: 5px; -} +@import './extend/_credentials.less'; +@import './extend/_table.less'; -[data-ui-id="menu-datatrics-core-menu"] strong:before { - display: none; -} -ul[role="menu"] [data-ui-id^="menu-datatrics"] strong:before { - content: @mm-icon__datatrics; - font-family: @icons-datatrics__font-name; - font-size: 1.5rem; - margin-right: .8rem; -} +// +// Common styles +// --------------------------------------------- -.mm-datatrics-header, -.mm-heading-block { - border: 0.1rem solid @color-gray90; - border-left: 3px solid @bordercolor; - font-size: 1.3rem; - display: flex; - text-align: left; -} - -.mm-heading-block { - background: @color-white-fog; - padding: 1rem; - text-align: left; -} - -.mm-heading-comment { - padding: 1rem 0rem 0rem 1rem; - font-size: 1.2rem; - line-height: 2.5rem; - border-left: 4px solid @color-white-fog; -} - -.mm-heading-recommend { - color: @bordercolor; - font-weight: 600; - display: block; - padding: 3px 0px 3px 0px; -} - -.mm-datatrics-header { - background: @color-white-fog no-repeat left 0.8rem center / 6rem; - padding: 1.8rem 1.8rem 1.8rem 8.8rem; - - &-text { - margin-top: 0.7rem; - line-height: 2.2rem; - } - - &-links { - i, - a { - color: @mm-color__3; - display: inline-block; - } - - a { - margin-right: 2rem; - } - - i { - font-size: 1.75rem; - vertical-align: middle; - line-height: 1.5rem; - } - } - - &-actions { - margin-left: auto; - max-width: 50%; - text-align: center; - - i { - font-size: 2.5rem; - vertical-align: middle; - line-height: 1.5rem; - } - } +.admin__menu .item-menu.parent.level-0 > a:before { + font-size: 2rem; + padding-top: .2rem; } -#row_datatrics_connect_debug { - - &_debug_button { - - .value { - padding-top: 1rem; - } - } - - &_error_button { - - .value { - padding-top: 0.5rem; - } - } +.magmodules-extensions .title:before { + font-size: 1.3rem; + padding-top: .2rem; } -#row_datatrics_connect_debug_selftest_heading { - - .mm-heading-block { +[data-ui-id="menu-magmodules-core-menu"] strong:before { display: none; - } -} - -.mm-result_version-wrapper { - align-items: center; - display: flex; - position: relative; - margin-top: -0.3rem; - margin-bottom: -0.3rem; - - .loading-mask, - .popup-loading { - position: absolute; - padding: 0; - height: 100%; - top: 0; - left: 0; - width: 100%; - margin: 0; - } - - .loading-mask { - height: 200%; - top: -50%; - } - - .popup-loading:after { - margin: 0; - left: 0; - top: 0; - height: 100%; - width: 100%; - background-repeat: no-repeat; - background-position: center; - background-size: contain; - } -} - -.mm-datatrics-version { - border: 0 solid @color-gray68; - margin-left: 1rem; - padding: .6rem 1em .6rem 0.4rem; - white-space: nowrap; - font-weight: 600; - - a { - color: inherit; - text-decoration: underline; - } - - span { - font-weight: 500; - } - - &:before { - border-radius: 50%; - color: @color-white; - width: 2.4rem; - height: 2.4rem; - line-height: 2.6rem; - font-size: 1.2rem; - margin-right: .5rem; - } - - &.mm-icon__thumbs-up:before { - background-color: @mm-color__green-dark; - line-height: 2rem; - } - - &.mm-icon__thumbs-down:before { - background-color: @mm-color__yellow; - } -} - -.mm-result { - - &_error-item, - &_debug-item { - flex-wrap: wrap; - display: flex; - padding: 1.5rem 1rem; - background: @color-white; - - &:nth-child(odd) { - background: @mm-bg__odd; - } - - strong { - margin-bottom: 0.7rem; - margin-right: 20px; - } - } - - &_test-item { - display: flex; - border-bottom: 0.1rem solid @color-white; - padding: 1.5rem 0 1.5rem 1rem; - min-height: 75px; - - &:nth-child(odd) { - background: @mm-bg__odd; - } - - &:before { - margin-right: 2rem; - font-size: 2.5rem; - font-family: @icons-datatrics__font-name; - } - - &.success:before { - content: @mm-icon__notification-rounded; - color: @mm-color__green-light; - } - - &.success { - color: @mm-color__green-light; - } - - &.failed { - - &:before { - content: @mm-icon__info-rounded0; - color: @mm-color__red; - } - - em { - color: @mm-color__orange; - } - } - - strong { - flex-basis: 10%; - font-weight: 600; - } - - span { - flex-basis: 60%; - } - - .mm-icon__help-rounded { - color: @mm-color__red; - font-size: 2rem; - margin-left: auto; - - &:hover { - text-decoration: none; - } - } - } -} - -#mm-result_changelog .result { - list-style-type: none; - padding: 0; - margin: 1rem 0 0; -} - -.mm-result_changelog-item { - padding: 0.8rem 1.6rem 1.6rem; - - - &:nth-child(odd) { - background: #F6F8FA; - } - - &:nth-child(even) { - background: #EDEFF1; - } - - & > div { - margin-top: 1rem; - } - - .mm-divider { - display: inline-block; - padding: 0 0.4rem; - font-size: 1.8rem; - opacity: 0.5; - } -} - -.modal-content .popup-loading { - padding: 50px 0 10px; } -.modal-slide .modal-header .action-close { - padding: 2.1rem 2.6rem; +ul[role="menu"] [data-ui-id^="menu-magmodules"] strong:before { + font-size: 1.5rem; + margin-right: .8rem; } -#result_api .connecting { - padding: 10px; - display: none; - background: #504944; - margin-top: 10px; - border: 1px solid #504944; - font-weight: bold; - color: #ffffff; - float: left; -} - - -#result_api .result { - padding: 10px; - display: none; - background: #007dbd; - margin-top: 10px; - border: 1px solid #007dbd; - font-weight: bold; - color: #ffffff; - float: left; +.mm-datatrics-recommend { + color: @mm-datatrics-color11; + font-weight: 600; + display: block; + padding: 3px 0px 3px 0px; } - -.mm-block-datatrics-table { - border-top: 1px solid #dfdedd; - width: auto; - color: #666; - text-align: left; - border-right: 1px solid #dfdedd; - border-bottom: 1px solid #dfdedd; - border-left: 2px solid #EA4C00; - font-size: 14px; - margin: 20px 0 0; - padding: 20px; - background: #F8F8F8; -} - -.mm-block-datatrics-table thead { - border-bottom: 1px solid #dfdedd; -} - -.mm-block-datatrics-table thead th { - padding: 20px; -} - -.mm-block-datatrics-content-table tr td { - border-bottom: 1px solid #dddddd; -} - -.mm-block-datatrics-table tr td { - padding: 20px; - width: 250px; - vertical-align: top; -} - -.mm-block-datatrics-table tr a { - color: #ec5302; -} - -.mm-block-datatrics-table tr a:before { - content: '\e610'; - font-family: 'Admin Icons'; - margin-right: 10px; - margin-top: 5px; - /* margin-bottom: 30px; */ - font-size: 10px; - float: left; - color: #ec5302; -} - -.mm-datatrics-table .title { - clear: both; - color: #0A0A0A; - cursor: pointer; - display: block; - font-size: 1.7rem; - font-weight: 600; - letter-spacing: .025em; - padding: 1.9rem 2.8rem 1.9rem 0; - position: relative; - text-decoration: none; - transition: color .15s linear; -} \ No newline at end of file diff --git a/view/adminhtml/web/css/source/extend/_credentials.less b/view/adminhtml/web/css/source/extend/_credentials.less new file mode 100755 index 0000000..a18e318 --- /dev/null +++ b/view/adminhtml/web/css/source/extend/_credentials.less @@ -0,0 +1,32 @@ +.check-credentials-wrapper { + position: relative; + display: inline-flex; + align-items: center; + gap: 10px; + + #result_api, + #result_integration { + position: absolute; + right: 0; + transform: translateX(~'calc(100% + 15px)'); + min-width:300px; + } + + .connecting { + color: @mm-datatrics-color6; + } + + .result { + color: @mm-datatrics-color18; + } + + .result .success { + color: @mm-datatrics-color15; + } + + button { + .button_base(@color: @mm-datatrics-color9); + padding: .7rem 1.2rem; + border: 1px solid @mm-datatrics-color5; + } +} diff --git a/view/adminhtml/web/css/source/extend/_table.less b/view/adminhtml/web/css/source/extend/_table.less new file mode 100644 index 0000000..194f96d --- /dev/null +++ b/view/adminhtml/web/css/source/extend/_table.less @@ -0,0 +1,49 @@ +.mm-datatrics-table .title { + &:extend(.section-config > .admin__collapsible-block > a); +} + +.mm-block-datatrics-table { + &:extend(.mm-datatrics-heading-block); + + display: table; + width: 100%; + margin-top: 16px; + border-top: 1px solid fade(@mm-datatrics-color5, 50%); + border-right: 1px solid fade(@mm-datatrics-color5, 50%); + border-bottom: 1px solid fade(@mm-datatrics-color5, 50%); + + th, td { + padding: 16px 12px; + vertical-align: top; + } + + td { + opacity: .8; + } + + th { + border-bottom: 1px solid fade(@mm-datatrics-color5, 50%); + } + + a { + &:extend(.mm-datatrics-show-more-actions a); + + display: flex; + align-items: center; + gap: 6px; + + font-weight: 600; + text-decoration: none; + + &::before { + content: @icon-checkmark; + font-family: @icons-admin__font-name; + font-size: 10px; + } + + &:hover { + text-decoration: underline; + } + } +} + diff --git a/view/adminhtml/web/css/source/module/_debug.less b/view/adminhtml/web/css/source/module/_debug.less new file mode 100644 index 0000000..06be59d --- /dev/null +++ b/view/adminhtml/web/css/source/module/_debug.less @@ -0,0 +1,80 @@ +// +// [ Debug ]: Heading +// --------------------------------------------- + +.mm-datatrics-heading-block { + display: flex; + padding: 1rem; + + border-left: 3px solid @mm-datatrics-color18; + background-color: @mm-datatrics-color2; + + color: @mm-datatrics-color8; + font-size: 1.3rem; + text-align: left; +} + +.mm-datatrics-heading-comment { + padding: 1.2rem 0 0 1rem; + font-size: 1.2rem; + line-height: 2rem; + border-left: 3px solid @mm-datatrics-color2; +} + + +// +// [ Debug ]: Show more/less +// --------------------------------------------- +.mm-datatrics-show-more-actions { + margin: 8px 0 0 12px; + + a { + color: @mm-datatrics-color18; + font-size: 1.2rem; + text-decoration: underline; + transition: .3s; + + &:hover { + opacity: .75; + } + } +} + +.mm-datatrics-heading-comment { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + + &.show { + -webkit-line-clamp: initial; + } + + span { + display: block; + + & + span { + margin-top: 10px; + } + } +} + +// +// [ Debug ]: Actions +// --------------------------------------------- + +#mm-datatrics-button_debug { + margin-top: 8px; +} + +#row_datatrics_connect_general_debug_debug_button .value { + padding-top: 1rem; +} + +#row_datatrics_connect_general_debug_error_button .value { + padding-top: .5rem; +} + +#row_datatrics_connect_general_debug_selftest_button .mm-datatrics-heading-block { + display: none; +} diff --git a/view/adminhtml/web/css/source/module/_header.less b/view/adminhtml/web/css/source/module/_header.less new file mode 100644 index 0000000..c01d58b --- /dev/null +++ b/view/adminhtml/web/css/source/module/_header.less @@ -0,0 +1,34 @@ +// +// [ datatrics ]: Header +// --------------------------------------------- + +.mm-datatrics-header { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 25px; + margin-bottom: 25px; + padding: 1.8rem 1.8rem 1.8rem 8.8rem; + + border-left: 3px solid @mm-datatrics-color18; + background: ~'@{mm-datatrics-color2} no-repeat .8rem 1.5rem / 6rem'; + + font-size: 1.3rem; + text-align: left; +} + +.mm-datatrics-header-text { + display: grid; + grid-gap: 4px; + margin-top: 4px; + + strong { + font-size: 1.4rem; + } +} + +.mm-datatrics-header-actions { + display: flex; + flex-wrap: wrap; + gap: 4px; +} diff --git a/view/adminhtml/web/css/source/module/_ui.less b/view/adminhtml/web/css/source/module/_ui.less new file mode 100644 index 0000000..f4528da --- /dev/null +++ b/view/adminhtml/web/css/source/module/_ui.less @@ -0,0 +1,6 @@ +// +// Imports UI elements +// --------------------------------------------- + +@import './ui/_button.less'; +@import './ui/_modal.less'; diff --git a/view/adminhtml/web/css/source/module/_version.less b/view/adminhtml/web/css/source/module/_version.less new file mode 100644 index 0000000..8071a08 --- /dev/null +++ b/view/adminhtml/web/css/source/module/_version.less @@ -0,0 +1,92 @@ +// +// [ datatrics ]: Version block +// --------------------------------------------- + +.mm-datatrics-result_version-wrapper { + position: relative; + display: flex; + align-items: center; + margin-top: -1.3rem; + margin-bottom: -.3rem; + + .loading-mask { + & ~ .button-wrapper { + display: none; + } + + & ~ span { + margin-top: 1rem; + } + + & ~ #mm-datatrics-result_version { + position: relative; + top: 5px; + } + } + + .button-wrapper { + position: relative; + margin-left: 15px; + + #mm-datatrics-button_version { + padding: 1.2rem 1.6rem 1.2rem 42px; + + &:hover ~ svg { + background-color: @mm-datatrics-color9; + } + } + + svg { + position: absolute; + top: 9px; + left: 10px; + + width: 13px; + height: 13px; + padding: 7px; + + background-color: @mm-datatrics-color7; + border-radius: 50%; + pointer-events: none; + transition: .3s; + } + } + + .loading-mask, + .popup-loading { + position: absolute; + padding: 0; + height: 100%; + top: 0; + left: 0; + width: 100%; + margin: 0; + } + + .loading-mask { + height: 200%; + top: -50%; + } + + .popup-loading:after { + margin: 0; + left: 0; + top: 0; + height: 100%; + width: 100%; + background-repeat: no-repeat; + background-position: center; + background-size: contain; + } +} + +.mm-datatrics-version { + margin-left: 1rem; + padding: .6rem 1em .6rem .4rem; + white-space: nowrap; + font-weight: @font-weight__semibold; + + span { + color: @mm-datatrics-color16; + } +} diff --git a/view/adminhtml/web/css/source/module/ui/_button.less b/view/adminhtml/web/css/source/module/ui/_button.less new file mode 100644 index 0000000..ec0cec9 --- /dev/null +++ b/view/adminhtml/web/css/source/module/ui/_button.less @@ -0,0 +1,111 @@ +// +// [ Buttons ]: Mixin +// --------------------------------------------- + +.button_base( + @bg: none, + @bg-hover: none, + @border: none, + @color: @mm-datatrics-color1, + @color-hover: @color, + @icon-width: 25px, + @icon-color: @color, + @icon-color-hover: @color-hover, +) { + & when not (@bg = none) { background-color: @bg; } + + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + min-width: 180px; + padding: 1.2rem 1.6rem; + + border: @border; + color: @color; + font: @font-weight__bold 1.4rem @font-family__base; + text-decoration: none; + transition: .3s; + + &:hover { + & when not (@bg-hover = none) { background-color: @bg-hover; } + color: @color-hover; + + i[class*="icon"] { + & when not (@icon-color = none) { fill: @icon-color-hover; } + } + } + + i[class*="icon"] { + & when not (@icon-width = none) { width: @icon-width; height: @icon-width; } + & when not (@icon-color = none) { fill: @icon-color; } + transition: .3s; + } +} + +.button-as-link(@color: @mm-datatrics-color18) { + display: inline; + padding: 0; + border: none; + background: transparent; + color: @color; + text-decoration: underline; +} + +// +// [ Buttons ] +// --------------------------------------------- + +button.action.primary-new { + .button_base(@color: @mm-datatrics-color9); + + box-shadow: none; + text-shadow: none; + text-transform: uppercase; + padding: 1rem 1.4rem; +} + + +.mm-datatrics-button__download, +#mm-datatrics-button_help { + .button_base(@bg: @mm-datatrics-color8, @bg-hover: @mm-datatrics-color10); + padding: 1.2rem 1.6rem; +} + +#mm-datatrics-button_test { + .button_base(@bg: @mm-datatrics-color13, @bg-hover: @mm-datatrics-color14); +} + +#mm-datatrics-button_version { + .button_base(@bg: transparent, @border: 1px solid @mm-datatrics-color5, @color: @mm-datatrics-color6, @color-hover: @mm-datatrics-color9); +} + +#mm-datatrics-button_changelog { + .button-as-link(); +} + +#mm-datatrics-button_debug, +#mm-datatrics-button_error { + .button-as-link(@color: @mm-datatrics-color8); + + display: flex; + align-items: center; + gap: 6px; + + font: @font-weight__regular 1.3rem @font-family__base; + text-decoration: none; + transition: .3s; + + &:hover { + color: @mm-datatrics-color18; + } + + &::before { + content: @icon-info__content; + font: 1.5rem @icons-admin__font-name; + } + + span { + text-decoration: underline; + } +} diff --git a/view/adminhtml/web/css/source/module/ui/_modal.less b/view/adminhtml/web/css/source/module/ui/_modal.less new file mode 100644 index 0000000..6a6a964 --- /dev/null +++ b/view/adminhtml/web/css/source/module/ui/_modal.less @@ -0,0 +1,129 @@ +// +// [ Modal popup ] +// --------------------------------------------- + +.mm-datatrics-modal { + .popup-loading { + padding: 50px 0 10px; + } + + .modal-header { + padding-bottom: 0; + + .modal-title { + padding: 0 5.7rem 12px 0; + margin-right: 0; + border-bottom: 1px solid @mm-datatrics-color3; + } + + .action-close { + padding: 2.1rem 2.6rem; + } + } + + .modal-content { + padding-bottom: 0; + + > div { + padding: 12px 0; + } + + ul { + display: grid; + grid-gap: 6px; + margin: 0; + list-style: none; + + > li { + padding: 10px 15px; + + &:nth-child(even) { + background: @mm-datatrics-color4; + } + + & > div { + margin-top: 1rem; + } + } + + .mm-datatrics-divider { + display: inline-block; + padding: 0 0.4rem; + font-size: 1.8rem; + opacity: .5; + } + } + + .mm-datatrics-result_test-item { + position: relative; + + strong { + display: flex; + align-items: center; + font-size: 1.5rem; + font-weight: @font-weight__semibold; + + &::before { + display: inline-flex; + align-items: center; + width: 20px; + height: 20px; + margin-right: 6px; + font: 1.1rem @icons-admin__font-name; + } + } + + div { + margin-top: 4px; + padding-left: 25px; + font-size: 1.3rem; + } + + em { + color: @mm-datatrics-color18; + } + + .mm-datatrics-icon__help-rounded { + display: inline-block; + margin: 10px 0 0 25px; + font-size: 1.3rem; + text-decoration: underline; + transition: .3s; + + &:hover { + opacity: .75; + } + } + + &.success strong { + color: @mm-datatrics-color15; + + &::before { + content: @icon-check-mage__content; + padding: 2px 2px 2px 3px; + border: 1px solid @mm-datatrics-color15; + border-radius: 50%; + } + } + + &.failed strong { + color: @mm-datatrics-color19; + + &::before { + content: @icon-help__content; + font-size: 1.8rem; + + } + } + } + } + + .modal-footer { + display: flex; + justify-content: flex-end; + gap: 6px; + margin: 0 30px; + padding: 25px 0; + border-top: 1px solid @mm-datatrics-color3; + } +} diff --git a/view/adminhtml/web/css/source/variables/_colors.less b/view/adminhtml/web/css/source/variables/_colors.less new file mode 100755 index 0000000..f489292 --- /dev/null +++ b/view/adminhtml/web/css/source/variables/_colors.less @@ -0,0 +1,21 @@ +// New colors +@mm-datatrics-color1: #fff; +@mm-datatrics-color2: #f8f8f8; +@mm-datatrics-color3: #edeff1; // even +@mm-datatrics-color4: #f6f8fa; // odd +@mm-datatrics-color5: #b4b4b4; // gray light +@mm-datatrics-color6: #948f8b; +@mm-datatrics-color7: #96928e; +@mm-datatrics-color8: #504944; +@mm-datatrics-color9: #514943; + +@mm-datatrics-color10: #41362f; +@mm-datatrics-color11: #4f4943; +@mm-datatrics-color12: #626262; // gray dark +@mm-datatrics-color13: #007dbd; // blue +@mm-datatrics-color14: #0470a8; +@mm-datatrics-color15: #79a12d; // green light +@mm-datatrics-color16: #2e701e; // green dark +@mm-datatrics-color17: #d3b83a; // yellow +@mm-datatrics-color18: #d85321; // orange +@mm-datatrics-color19: #ce2525; // red diff --git a/view/adminhtml/web/css/source/variables/_icons.less b/view/adminhtml/web/css/source/variables/_icons.less deleted file mode 100755 index e0d4e9f..0000000 --- a/view/adminhtml/web/css/source/variables/_icons.less +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright © Magmodules.eu. All rights reserved. - * See COPYING.txt for license details. - */ - -@icons-datatrics__font-name: 'datatrics-icons'; -@icons-datatrics__file-name: 'datatrics-icons'; -@icons-datatrics__font-name-path: '@{baseDir}Datatrics_Connect/fonts/@{icons-datatrics__file-name}/@{icons-datatrics__file-name}'; - -@mm-icon__download-alt: '\e800'; -@mm-icon__list: '\e801'; -@mm-icon__datatrics: '\e802'; -@mm-icon__list-unordered: '\e803'; -@mm-icon__help-rounded: '\e804'; -@mm-icon__thumbs-down: '\e805'; -@mm-icon__thumbs-up: '\e806'; -@mm-icon__font-icons: '\e807'; -@mm-icon__info-rounded1: '\e808'; -@mm-icon__download: '\e809'; -@mm-icon__external-link: '\e80a'; -@mm-icon__notification-rounded: '\e80b'; -@mm-icon__envelop: '\e80c'; -@mm-icon__list-unordered-four: '\e80d'; -@mm-icon__question-circled: '\e80e'; -@mm-icon__list-thumbnails: '\e80f'; -@mm-icon__repeat: '\e810'; -@mm-icon__info-rounded0: '\e811'; - diff --git a/view/adminhtml/web/css/source/variables/_mm-colors.less b/view/adminhtml/web/css/source/variables/_mm-colors.less deleted file mode 100755 index 17e572b..0000000 --- a/view/adminhtml/web/css/source/variables/_mm-colors.less +++ /dev/null @@ -1,20 +0,0 @@ - -@mm-color__orange: #d85321; -@mm-bg__blue: #007dbd; -@mm-color__green-light: #79a12d; -@mm-color__red: #ce2525; -@mm-color__green-dark: #2e701e; -@mm-color__yellow: #d3b83a; -@mm-border-color: #4f4943; -@mm-color__dark-gray: #626262; -@mm-bg__even: #edeff1; -@mm-bg__odd: #f6f8fa; -@mm-version-btn-border: #948f8b; -@mm-version-btn-icon-bg: #96928e; - -@mm-color__1: #504944; -@mm-color__2: #514943; -@mm-color__3: #41362f; - -// -@bordercolor: @mm-color__orange; diff --git a/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.eot b/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.eot deleted file mode 100755 index 8ad52ba7cfdb9f25b9a690cbb5e3a966acc71104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10768 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 diff --git a/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.svg b/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.svg deleted file mode 100755 index 59bb859..0000000 --- a/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.svg +++ /dev/null @@ -1,46 +0,0 @@ - - - -Copyright (C) 2020 by original authors @ fontello.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.ttf b/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.ttf deleted file mode 100755 index f2ce08a530972fec6197ec68f21fbed98f3aba0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10568 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 V?eYLBoypOo5CSkgY2eBK{{Rt;_U-@x diff --git a/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.woff2 b/view/adminhtml/web/fonts/datatrics-icons/datatrics-icons.woff2 deleted file mode 100755 index 92a0265a73cf0e11f282e1a7972341ef5657c4dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5444 zcmV-K6}#$pPew8T0RR9102M?44*&oF04YcS02Jx~0RR9100000000000000000000 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 { + let elText = document.getElementById(`mm-datatrics-result_${options.buttons}`).innerText || '', + link = document.createElement('a'); + + link.setAttribute('download', `${options.buttons}-log.txt`); + link.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(elText)); + link.click(); + }, + }; - if (!$resultModal.length) return; + defaultOptions.buttons.unshift(additionalButtons); + } - var popup = modal(options, $resultModal); - $resultModal.loader({texts: ''}); + modal(defaultOptions, $(modalId)); + $(modalId).loader({texts: ''}); } var successHandlers = { /** - * @param{Object[]} result - Ajax request response data. + * @param{Object[]} obj - Ajax request response data. * @param{Object} $container - jQuery container element. + * @param{String} type - debug || error || test. */ - debug: function (result, $container) { - - if (Array.isArray(result)) { - - var lisHtml = result.map(function (err) { - return '
    • ' + err.date + '

      ' + err.msg + '

    • '; - }).join(''); - - $container.find('.result').empty().append('
        ' + lisHtml + '
      '); - } else { + logs(response, $container, type) { + if (Array.isArray(response.result)) { + if (type === 'debug' || type === 'error') { + response = `
        ${response.result.map((data) => this.tmpLogs(type, data)).join('')}
      `; + } - $container.find('.result').empty().append(result); + if (type === 'test') { + response = `${response.result.map((data) => this.tmpTest(type, data)).join('')}`; + } } - }, - - /** - * @param{Object[]} result - Ajax request response data. - * @param{Object} $container - jQuery container element. - */ - error: function (result, $container) { - - if (Array.isArray(result)) { - var lisHtml = result.map(function (err) { - return '
    • ' + err.date + '

      ' + err.msg + '

    • '; - }).join(''); - - $container.find('.result').empty().append('
        ' + lisHtml + '
      '); - } else { - - $container.find('.result').empty().append(result); - } + $container.find('.result').empty().append(response); }, - /** - * @param{Object[]} result - Ajax request response data. - * @param{Object} $container - jQuery container element. - */ - test: function (result, $container) { - - var lisHtml = result.map(function (test) { - - var supportLinkHtml = test.support_link ? - '' : ''; - var resultText = test.result_code === 'success' ? - $.mage.__('Passed') : $.mage.__('Failed'); - var resultMsg = test.result_code === 'failed' ? test.result_msg : ''; + tmpLogs(type, data) { + return `
    • + ${data.date} +

      ${data.msg}

      +
    • `; + }, - return '
    • ' + resultText + '' - + '

      ' + test.test + '

      ' + resultMsg + '

      ' - + supportLinkHtml + '
    • '; - }).join(''); + tmpTest(type, data) { + let supportLinkHtml = ''; + resultMsg = data.result_code === 'failed' ? data.result_msg : ''; + resultText = data.result_code === 'success' + ? $.mage.__('Passed') + : $.mage.__('Failed'); + + if (data.support_link) { + supportLinkHtml = ` + ${$.mage.__('More information')} + `; + } - $container.find('.result').empty().append(lisHtml); + return `
    • + ${resultText} +
      +

      ${data.test}

      +

      ${resultMsg}

      +
      + ${supportLinkHtml} +
    • `; }, /** * @param{Object[]} result - Ajax request response data. * @param{Object} $container - jQuery container element. */ - version: function (result, $container) { - - var resultHtml = ''; - var currentVersion = result.current_verion.replace(/v|version/gi, ''); - var latestVersion = result.last_version.replace(/v|version/gi, ''); - if (this.compare(latestVersion, currentVersion) <= 0) { - resultHtml = '' - + $.mage.__('Great, you are using the latest version.') - + ''; - } else { - - var translatedResult = $.mage.__('There is a new version available (%1) see .') + version(response, $container) { + let resultHTML = ''; + resultText = $.mage.__('Great, you are using the latest version.'); + resultClass = 'up'; + currentVersion = response.result.current_verion.replace(/v|version/gi, ''); + latestVersion = response.result.last_version.replace(/v|version/gi, ''); + + if (currentVersion !== latestVersion) { + resultClass = 'down'; + resultText = $.mage.__('There is a new version available (%1) see .') .replace('%1', latestVersion); - - resultHtml = '' - + translatedResult - + ''; } - $container.html(resultHtml); - }, - - compare: function(a, b) { - if (a === b) { - return 0; - } - var a_components = a.split("."); - var b_components = b.split("."); - var len = Math.min(a_components.length, b_components.length); - for (var i = 0; i < len; i++) { - if (parseInt(a_components[i]) > parseInt(b_components[i])) { - return 1; - } + resultHTML = ` + ${resultText} + `; - if (parseInt(a_components[i]) < parseInt(b_components[i])) { - return -1; - } - } - if (a_components.length > b_components.length) { - return 1; - } - if (a_components.length < b_components.length) { - return -1; - } - return 0; + $container.html(resultHTML); }, /** * @param{Object[]} result - Ajax request response data. * @param{Object} $container - jQuery container element. */ - changelog: function (result, $container) { - - var lisHtml = Object.keys(result).map(function (key) { - - var version = key; - var date = result[key].date; - var resultHtml = result[key].changelog; - - return '
    • ' - + version + '|' - + date + '
      ' - + resultHtml + '
    • '; - }).join(''); - - $container.find('.result').empty().append(lisHtml); + changelog(response, $container) { + var listHTML = Object.keys(response).map((version) => { + return `
    • + ${version} + | + ${response[version].date} +
      ${response[version].changelog}
      +
    • `; + }); + + $container.find('.result').empty().append(listHTML.join('')); }, } - // init debug modal + // init modals $(() => { - initModal('#mm-result_debug-modal', { - type: 'popup', - responsive: true, - innerScroll: true, - title: $.mage.__('last 100 debug log lines'), - buttons: [ - { - text: $.mage.__('download as .txt file'), - class: 'mm-button__download mm-icon__download-alt', - click: function () { - - var elText = document.getElementById('mm-result_debug').innerText || ''; - var link = document.createElement('a'); - - link.setAttribute('download', 'debug-log.txt'); - link.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(elText)); - link.click(); - }, - }, - { - text: $.mage.__('ok'), - class: '', - click: function () { - this.closeModal(); - }, - } - ] - }); - - // init error modal - initModal('#mm-result_error-modal', { - type: 'popup', - responsive: true, - innerScroll: true, - title: $.mage.__('last 100 error log records'), - buttons: [ - { - text: $.mage.__('download as .txt file'), - class: 'mm-button__download mm-icon__download-alt', - click: function () { - - var elText = document.getElementById('mm-result_error').innerText || ''; - var link = document.createElement('a'); - - link.setAttribute('download', 'error-log.txt'); - link.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(elText)); - link.click(); - }, - }, - { - text: $.mage.__('ok'), - class: '', - click: function () { - this.closeModal(); - }, - } - ] - }); - - // init selftest modal - initModal('#mm-result_test-modal', { - type: 'popup', - responsive: true, - innerScroll: true, - title: $.mage.__('Self-test'), - buttons: [ - { - text: $.mage.__('ok'), - class: '', - click: function () { - this.closeModal(); - }, - } - ] - }); - - // init changelog modal - initModal('#mm-result_changelog-modal', { - type: 'popup', - responsive: true, - innerScroll: true, - title: 'Changelog', - buttons: [ - { - text: $.mage.__('ok'), - class: '', - click: function () { - this.closeModal(); - }, - } - ] - }); + initModal('debug', { title: $.mage.__('last 100 debug log records'), buttons: 'debug' }); + initModal('error', { title: $.mage.__('last 100 error log records'), buttons: 'error' }); + initModal('test', { title: $.mage.__('Self-test') }); + initModal('changelog', { title: $.mage.__('Changelog') }); }); // init loader on the Check Version block - $('.mm-result_version-wrapper').loader({texts: ''}); + $('.mm-datatrics-result_version-wrapper').loader({texts: ''}); /** * Ajax request event */ - $(document).on('click', '[id^=mm-button]', function () { - var actionName = this.id.split('_')[1]; - var $modal = $('#mm-result_' + actionName + '-modal'); - var $result = $('#mm-result_' + actionName); - - if (actionName === 'version') { - $(this).fadeOut(300).addClass('mm-disabled'); - $modal = $('.mm-result_' + actionName + '-wrapper'); + $(document).on('click', '[id^=mm-datatrics-button]', function () { + let action = this.id.split('_')[1], + $modal = $(`#mm-datatrics-result_${action}-modal`), + $result = $(`#mm-datatrics-result_${action}`); + + if (action === 'version') { + $(this).fadeOut(300).addClass('mm-datatrics-disabled'); + $modal = $(`.mm-datatrics-result_${action}-wrapper`); $modal.loader('show'); } else { $modal.modal('openModal').loader('show'); @@ -270,20 +173,16 @@ require([ $result.hide(); - new Ajax.Request($modal.data('mm-endpoind-url'), { - loaderArea: false, - asynchronous: true, - onSuccess: function (response) { - - if (response.status > 200) { - var result = response.statusText; - } else { - successHandlers[actionName](response.responseJSON.result || response.responseJSON, $result); - - $result.fadeIn(); - $modal.loader('hide'); - } - } - }); + fetch($modal.attr('data-mm-datatrics-endpoind-url')) + .then((res) => res.clone().json().catch(() => res.text())) + .then((data) => { + const func = action === 'debug' || + action === 'error' || + action === 'test' ? 'logs' : action; + + successHandlers[func](data, $result, action); + $result.fadeIn(); + $modal.loader('hide'); + }); }); }); diff --git a/view/adminhtml/web/js/show-more.js b/view/adminhtml/web/js/show-more.js new file mode 100755 index 0000000..b7e80d2 --- /dev/null +++ b/view/adminhtml/web/js/show-more.js @@ -0,0 +1,41 @@ +require([ + 'jquery', + 'mage/translate', + '!domReady' +], function ($, $t) { + + let comment = $('.mm-datatrics-heading-comment'), + showMoreLessBtnHtml = ` + `; + + if(comment.length) { + comment.parent().append(showMoreLessBtnHtml); + + $(document).on('click', '.mm-datatrics-show-more-actions a', (e) => { + let button = $(e.target), + parent = $(e.target).closest('.value').find('.mm-datatrics-heading-comment'); + + if (parent.hasClass('show')) { + parent.removeClass('show'); + button.text($t('Show more.')); + } else { + parent.addClass('show'); + button.text($t('Show less.')); + } + }); + + window.addEventListener("load", isShowMore); + window.addEventListener("resize", isShowMore); + } + + function isShowMore() { + Array.from(comment).forEach((item) => { + const BTN = item.closest('td').querySelector('.mm-datatrics-show-more-actions'); + item.scrollHeight <= 55 ? BTN.classList.add('hidden') : BTN.classList.remove('hidden'); + }); + } +}); From 3e3745d510a13e6584ca5b77169fcd438a157dc3 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Tue, 22 Aug 2023 11:57:09 +0200 Subject: [PATCH 4/4] Version bump --- composer.json | 2 +- etc/config.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a60ad96..a37ca46 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "datatrics/magento2-integration", "description": "Datatrics Connect extension for Magento 2", "type": "magento2-module", - "version": "1.6.8", + "version": "1.7.0", "license": [ "BSD-2-Clause" ], diff --git a/etc/config.xml b/etc/config.xml index 58c8946..cd1dae4 100755 --- a/etc/config.xml +++ b/etc/config.xml @@ -10,7 +10,7 @@ - v1.6.8 + v1.7.0 0 Magento 2 0