Your IP : 127.0.0.1


Current Path : /home/dev2.destoffenstraat.com/app/Firebear/ImportExport/Model/Import/
Upload File :
Current File : /home/dev2.destoffenstraat.com/app/Firebear/ImportExport/Model/Import/Review.php

<?php
/**
 * @copyright: Copyright © 2019 Firebear Studio. All rights reserved.
 * @author : Firebear Studio <fbeardev@gmail.com>
 */

namespace Firebear\ImportExport\Model\Import;

use Firebear\ImportExport\Traits\Import\Entity as ImportTrait;
use InvalidArgumentException;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\ImportExport\Helper\Data;
use Magento\ImportExport\Model\Import;
use Magento\ImportExport\Model\Import\AbstractEntity;
use Magento\ImportExport\Model\Import\AbstractSource;
use Magento\ImportExport\Model\ImportFactory;
use Magento\Review\Model\Rating\Option\VoteFactory;
use Magento\ImportExport\Model\ResourceModel\Helper;
use Magento\Review\Model\RatingFactory;
use Magento\Review\Model\ResourceModel\Rating\CollectionFactory as RatingCollectionFactory;
use Magento\Review\Model\Review as ReviewModel;
use Magento\Review\Model\ReviewFactory;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable;
use Magento\Bundle\Model\Product\Type as BundleProduct;
use Magento\Catalog\Model\ResourceModel\Product;
use Magento\Review\Model\ResourceModel\Review\Product\CollectionFactory as ReviewProductCollectionFactory;

/**
 * Review import
 */
class Review extends AbstractEntity implements ImportAdapterInterface
{
    use ImportTrait;

    /**
     * Sku column name
     */
    const COLUMN_SKU = 'sku';

    /**
     * Review id column name
     */
    const COLUMN_REVIEW_ID = 'review_id';

    /**
     * Created at column name
     */
    const COLUMN_CREATED_AT = 'created_at';

    /**
     * Entity pk value column name
     */
    const COLUMN_ENTITY_PK_VALUE = 'entity_pk_value';

    /**
     * Entity Id column name
     */
    const COLUMN_ENTITY_ID = 'entity_id';

    /**
     * Status column name
     */
    const COLUMN_STATUS = 'status';

    /**
     * Status id column name
     */
    const COLUMN_STATUS_ID = 'status_id';

    /**
     * Stores column name
     */
    const COLUMN_STORES = 'stores';
    /**
     * Error codes
     */
    const ERROR_SKU_NOT_FOUND = 'reviewSkuNotFound';
    const ERROR_REVIEW_ID_IS_EMPTY = 'reviewIdIsEmpty';
    /**
     * Source model
     *
     * @var AbstractSource
     */
    protected $_source;
    /**
     * Source model
     *
     * @var Helper
     */
    protected $resourceHelper;
    /**
     * Json helper
     *
     * @var \Magento\Framework\Json\Helper\Data
     */
    protected $jsonHelper;
    /**
     * Import export data
     *
     * @var Data
     */
    protected $importExportData;
    /**
     * Product instance
     *
     * @var \Magento\Catalog\Model\Product
     */
    protected $product;

    /**
     * @var Configurable
     */
    protected $configurableProductResource;

    /**
     * @var Product
     */
    protected $productResource;

    /**
     * @var BundleProduct
     */
    protected $bundleProduct;

    /**
     * Reviews factory
     *
     * @var ReviewFactory
     */
    protected $reviewsFactory;
    /**
     * Rating factory
     *
     * @var RatingFactory
     */
    protected $ratingFactory;
    /**
     * Rating collection factory
     *
     * @var RatingCollectionFactory
     */
    protected $ratingCollectionFactory;
    /**
     * Rating fields
     *
     * @var array
     */
    protected $ratingFields;
    /**
     * Rating option fields
     *
     * @var array
     */
    protected $optionFields;
    /**
     * Status map
     *
     * @var array
     */
    protected $statusMap;
    /**
     * Store manager
     *
     * @var StoreManagerInterface
     */
    protected $storeManager;
    /**
     * All stores code-ID pairs.
     *
     * @var array
     */
    protected $storeCodeToId = [];
    /**
     * Validation failure message template definitions
     *
     * @var array
     */
    protected $_messageTemplates = [
        self::ERROR_SKU_NOT_FOUND => 'Product with specified SKU not found',
        self::ERROR_REVIEW_ID_IS_EMPTY => 'Review id is empty',
    ];

    /**
     * Permanent entity columns
     *
     * @var string[]
     */
    protected $_permanentAttributes = [
        self::COLUMN_SKU,
        'nickname',
        'title',
        'detail',
        self::COLUMN_STATUS,
    ];

    protected $validColumnNames = [
        self::COLUMN_REVIEW_ID,
        'created_at',
        'vote:Quality',
        'vote:Value',
        'vote:Price',
        'vote:Rating'
    ];

    /**
     * @var VoteFactory
     */
    protected $_ratingOptionVoteF;

    protected $_availableBehaviors = [
        \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE,
        \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE,
        \Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE,
    ];

    /**
     * @var ReviewProductCollectionFactory
     */
    protected $reviewProductCollectionFactory;

    /**
     * Initialize import
     *
     * @param Context $context
     * @param ScopeConfigInterface $scopeConfig
     * @param ImportFactory $importFactory
     * @param ProductInterface $product
     * @param ReviewFactory $reviewsFactory
     * @param RatingFactory $ratingFactory
     * @param RatingCollectionFactory $ratingCollectionFactory
     * @param StoreManagerInterface $storeManager
     * @param VoteFactory $ratingOptionVoteF
     * @param array $data
     */
    public function __construct(
        Context $context,
        ScopeConfigInterface $scopeConfig,
        ImportFactory $importFactory,
        ProductInterface $product,
        ReviewFactory $reviewsFactory,
        RatingFactory $ratingFactory,
        RatingCollectionFactory $ratingCollectionFactory,
        StoreManagerInterface $storeManager,
        VoteFactory $ratingOptionVoteF,
        Configurable $configurableProductResource,
        Product $productResource,
        BundleProduct $bundleProduct,
        ReviewProductCollectionFactory $reviewProductCollectionFactory,
        array $data = []
    ) {
        $this->_logger = $context->getLogger();
        $this->output = $context->getOutput();
        $this->importExportData = $context->getImportExportData();
        $this->resourceHelper = $context->getResourceHelper();
        $this->jsonHelper = $context->getJsonHelper();
        $this->product = $product;
        $this->reviewsFactory = $reviewsFactory;
        $this->ratingFactory = $ratingFactory;
        $this->ratingCollectionFactory = $ratingCollectionFactory;
        $this->storeManager = $storeManager;
        $this->_ratingOptionVoteF = $ratingOptionVoteF;
        $this->configurableProductResource = $configurableProductResource;
        $this->productResource = $productResource;
        $this->bundleProduct = $bundleProduct;
        $this->reviewProductCollectionFactory = $reviewProductCollectionFactory;

        parent::__construct(
            $context->getStringUtils(),
            $scopeConfig,
            $importFactory,
            $context->getResourceHelper(),
            $context->getResource(),
            $context->getErrorAggregator(),
            $data
        );

        $this->initRating();
        $this->initStatus();
        $this->initStores();
    }

    /**
     * Init rating
     *
     * @return void
     */
    protected function initRating()
    {
        if (null === $this->ratingFields) {
            $collection = $this->ratingCollectionFactory->create()
                ->addEntityFilter(ReviewModel::ENTITY_PRODUCT_CODE);

            foreach ($collection as $rating) {
                $this->ratingFields[$rating->getId()] = $rating->getRatingCode();
                $options = [];
                foreach ($rating->getOptions() as $option) {
                    $options[$option->getValue()] = $option->getId();
                }
                $this->optionFields[$rating->getId()] = $options;
            }
        }
    }

    /**
     * Init rating
     *
     * @return void
     */
    protected function initStatus()
    {
        if (null === $this->statusMap) {
            $collection = $this->reviewsFactory->create()->getStatusCollection();
            foreach ($collection as $status) {
                $this->statusMap[$status->getId()] = $status->getStatusCode();
            }
        }
    }

    /**
     * Initialize stores data
     *
     * @param bool $withDefault
     * @return $this
     */
    protected function initStores($withDefault = false)
    {
        /** @var $store Store */
        foreach ($this->storeManager->getStores($withDefault) as $store) {
            $this->storeCodeToId[$store->getCode()] = $store->getId();
        }
        return $this;
    }

    /**
     * Retrieve All Fields Source
     *
     * @return array
     */
    public function getAllFields()
    {
        $options = array_merge($this->getValidColumnNames());
        $options = array_merge($options, $this->_permanentAttributes);

        return array_unique($options);
    }

    /**
     * Import data rows
     *
     * @return boolean
     * @throws \Exception
     */
    protected function _importData()
    {
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            foreach ($bunch as $rowNumber => $rowData) {
                /* validate data */
                if (!$rowData || !$this->validateRow($rowData, $rowNumber)) {
                    continue;
                }

                if ($this->getErrorAggregator()->hasToBeTerminated()) {
                    $this->getErrorAggregator()->addRowToSkip($rowNumber);
                    continue;
                }

                /* behavior selector */
                switch ($this->getBehavior()) {
                    case Import::BEHAVIOR_DELETE:
                        $this->delete($rowData);
                        break;
                    case Import::BEHAVIOR_REPLACE:
                        if ($this->isReviewId($rowData)) {
                            $this->save($rowData);
                        }
                        break;
                    case Import::BEHAVIOR_ADD_UPDATE:
                        $this->save($rowData);
                        break;
                }
            }
        }
        return true;
    }

    /**
     * Validate data row
     *
     * @param array $rowData
     * @param int $rowNumber
     * @return bool
     */
    public function validateRow(array $rowData, $rowNumber)
    {
        if (isset($this->_validatedRows[$rowNumber])) {
            /* check that row is already validated */
            return !$this->getErrorAggregator()->isRowInvalid($rowNumber);
        }

        $this->_validatedRows[$rowNumber] = true;
        $this->_processedEntitiesCount++;

        /* behavior selector */
        switch ($this->getBehavior()) {
            case Import::BEHAVIOR_DELETE:
                $this->validateRowForDelete($rowData, $rowNumber);
                break;
            case Import::BEHAVIOR_REPLACE:
                $this->validateRowForReplace($rowData, $rowNumber);
                break;
            case Import::BEHAVIOR_ADD_UPDATE:
                $this->validateRowForUpdate($rowData, $rowNumber);
                break;
        }
        return !$this->getErrorAggregator()->isRowInvalid($rowNumber);
    }

    /**
     * Validate row data for delete behaviour
     *
     * @param array $rowData
     * @param int $rowNumber
     * @return void
     */
    public function validateRowForDelete(array $rowData, $rowNumber)
    {
        if (empty($rowData[self::COLUMN_REVIEW_ID])) {
            $this->addRowError(self::ERROR_REVIEW_ID_IS_EMPTY, $rowNumber);
        }
    }

    /**
     * Validate row data for replace behaviour
     *
     * @param array $rowData
     * @param int $rowNumber
     * @return void
     */
    public function validateRowForReplace(array $rowData, $rowNumber)
    {
        $this->validateRowForDelete($rowData, $rowNumber);
        $this->validateRowForUpdate($rowData, $rowNumber);
    }

    /**
     * Validate row data for update behaviour
     *
     * @param array $rowData
     * @param int $rowNumber
     * @return void
     */
    public function validateRowForUpdate(array $rowData, $rowNumber)
    {
        if (!empty($rowData[self::COLUMN_SKU])) {
            $pkValue = $this->product->getIdBySku($rowData[self::COLUMN_SKU]);
            if (!$pkValue) {
                $this->addRowError(self::ERROR_SKU_NOT_FOUND, $rowNumber);
            }
        }
    }

    /**
     * Delete row
     *
     * @param array $rowData
     * @return $this
     * @throws \Exception
     */
    protected function delete(array $rowData)
    {
        $review = $this->reviewsFactory->create();
        $review->load($rowData[self::COLUMN_REVIEW_ID]);

        if ($review->getId()) {
            $review->delete();
            $this->countItemsDeleted++;
        }
        return $this;
    }

    /**
     * Update entity
     *
     * @param array $rowData
     * @return $this
     * @throws \Exception
     */
    public function save(array $rowData)
    {
        $productId = $this->product->getIdBySku($rowData[self::COLUMN_SKU]);
        $productsData[] = [
            'entity_id' => $productId,
            'sku' => $rowData[self::COLUMN_SKU]
        ];
        $parameters = $this->getParameters();
        $parentIds = [];
        if (!empty($parameters['associate_child_review_to_configurable_parent_product'])) {
            $configurableProductParentIds = $this->configurableProductResource->getParentIdsByChild($productId);
            $parentIds = array_merge($parentIds, $configurableProductParentIds);
        }
        if (!empty($parameters['associate_child_review_to_bundle_parent_product'])) {
            $bundleProductParentIds = $this->bundleProduct->getParentIdsByChild($productId);
            $parentIds = array_merge($parentIds, $bundleProductParentIds);
        }
        if (!empty($parentIds)) {
            $productsData = $this->productResource->getProductsSku($parentIds);
        }
        foreach ($productsData as $productData) {
            $reviewData = $rowData;
            $review = $this->reviewsFactory->create();
            if (!empty($reviewData[self::COLUMN_REVIEW_ID])) {
                $reviewId = $reviewData[self::COLUMN_REVIEW_ID];
                unset($rowData[self::COLUMN_REVIEW_ID]);
            } else {
                $reviewId = $this->getExistingReviewId($rowData, $productData['sku']);
                $reviewData[self::COLUMN_REVIEW_ID] = $reviewId;
            }
            if (!empty($reviewId)) {
                $review->load($reviewId);
            }

            if ($review->getId()) {
                $this->countItemsUpdated++;
            } else {
                $this->countItemsCreated++;
                $reviewData[self::COLUMN_REVIEW_ID] = null;
            }

            if (!empty($productData['entity_id'])) {
                $reviewData[self::COLUMN_ENTITY_PK_VALUE] = $productData['entity_id'];
            }

            if (empty($reviewData[self::COLUMN_ENTITY_ID])) {
                $reviewData[self::COLUMN_ENTITY_ID] = $review->getEntityIdByCode(
                    ReviewModel::ENTITY_PRODUCT_CODE
                );
            }

            $rating = [];
            foreach ($reviewData as $field => $value) {
                if (false === strpos($field, ':')) {
                    continue;
                }
                unset($reviewData[$field]);
                list($fieldPrefix, $ratingCode) = explode(':', $field);
                if ($fieldPrefix == 'vote') {
                    $ratingId = array_search($ratingCode, $this->ratingFields);
                    if (false === $ratingId || '' === $value || !isset($this->optionFields[$ratingId][$value])) {
                        continue;
                    }
                    $rating[$ratingId] = $this->optionFields[$ratingId][$value];
                }
            }
            $reviewData['ratings'] = $rating;

            if (!empty($reviewData[self::COLUMN_STATUS])) {
                $statusId = array_search($reviewData[self::COLUMN_STATUS], $this->statusMap);
                if (false === $statusId) {
                    $statusId = ReviewModel::STATUS_PENDING;
                }
                $reviewData[self::COLUMN_STATUS_ID] = $statusId;
            }

            if (!empty($reviewData[self::COLUMN_STORES]) && !is_array($reviewData[self::COLUMN_STORES])) {
                $reviewData[self::COLUMN_STORES] = explode(',', $reviewData[self::COLUMN_STORES]);
            } else {
                $reviewData[self::COLUMN_STORES] = array_values($this->storeCodeToId);
            }

            unset(
                $reviewData[self::COLUMN_SKU],
                $reviewData[self::COLUMN_STATUS]
            );
            $review->addData($reviewData);
            $review->save();
            $this->updateDateFromDump($review->getId(), $reviewData);

            $votes = $this->_ratingOptionVoteF->create()
                ->getResourceCollection()
                ->setReviewFilter($review->getId())
                ->addOptionInfo()
                ->load()
                ->addRatingOptions();

            foreach ($rating as $ratingId => $optionId) {
                $ratingFac = $this->ratingFactory->create()->setReviewId($review->getId());
                if ($vote = $votes->getItemByColumnValue('rating_id', $ratingId)) {
                    $ratingFac->setVoteId($vote->getId())
                        ->updateOptionVote($optionId);
                } else {
                    $ratingFac->setRatingId($ratingId)
                        ->addOptionVote($optionId, $reviewData[self::COLUMN_ENTITY_PK_VALUE]);
                }
            }
            $review->aggregate();
        }
        return $this;
    }

    /**
     * Get an existing review id by row data
     *
     * @param $rowData
     * @param $sku
     * @return array|mixed|null
     */
    protected function getExistingReviewId($rowData, $sku)
    {
        $reviewItem = $this->reviewProductCollectionFactory
            ->create()
            ->addAttributeToFilter('sku', $sku)
            ->addAttributeToFilter('rt.created_at', $rowData['created_at'])
            ->addAttributeToFilter('rdt.nickname', $rowData['nickname'])
            ->addAttributeToFilter('rdt.title', $rowData['title'])
            ->getLastItem();

        return $reviewItem->getData('review_id') ?? null;
    }

    /**
     * Save Validated Bunches
     *
     * @return $this
     * @throws LocalizedException
     */
    protected function _saveValidatedBunches()
    {
        $source = $this->_getSource();
        $currentDataSize = 0;
        $bunchRows = [];
        $startNewBunch = false;
        $nextRowBackup = [];
        $maxDataSize = $this->resourceHelper->getMaxDataSize();
        $bunchSize = $this->importExportData->getBunchSize();

        $source->rewind();
        $this->_dataSourceModel->cleanBunches();
        $file = null;
        $jobId = null;
        if (isset($this->_parameters['file'])) {
            $file = $this->_parameters['file'];
        }
        if (isset($this->_parameters['job_id'])) {
            $jobId = $this->_parameters['job_id'];
        }

        while ($source->valid() || $bunchRows) {
            if ($startNewBunch || !$source->valid()) {
                $this->_dataSourceModel->saveBunches(
                    $this->getEntityTypeCode(),
                    $this->getBehavior(),
                    $jobId,
                    $file,
                    $bunchRows
                );
                $bunchRows = $nextRowBackup;
                $currentDataSize = strlen($this->jsonHelper->jsonEncode($bunchRows));
                $startNewBunch = false;
                $nextRowBackup = [];
            }

            if ($source->valid()) {
                try {
                    $rowData = $source->current();
                } catch (InvalidArgumentException $e) {
                    $this->addRowError($e->getMessage(), $this->_processedRowsCount);
                    $this->_processedRowsCount++;
                    $source->next();
                    continue;
                }
                $rowData = $this->customBunchesData($rowData);
                $rowData = $this->validateCreatedAt($rowData, $source->key());
                $this->_processedRowsCount++;
                if ($this->validateRow($rowData, $source->key())) {
                    $rowSize = strlen($this->jsonHelper->jsonEncode($rowData));

                    $isBunchSizeExceeded = $bunchSize > 0 && count($bunchRows) >= $bunchSize;

                    if ($currentDataSize + $rowSize >= $maxDataSize || $isBunchSizeExceeded) {
                        $startNewBunch = true;
                        $nextRowBackup = [$source->key() => $rowData];
                    } else {
                        $bunchRows[$source->key()] = $rowData;
                        $currentDataSize += $rowSize;
                    }
                }
                $source->next();
            }
        }
        return $this;
    }

    /**
     * @param $rowData
     * @param $rowNum
     * @return mixed
     */
    protected function validateCreatedAt($rowData, $rowNum)
    {
        $createdAt = $rowData[self::COLUMN_CREATED_AT] ?? false;
        if ($createdAt) {
            $format = 'Y-m-d H:i:s';
            $date = \DateTime::createFromFormat($format, $createdAt);
            if ($date) {
                $rowData[self::COLUMN_CREATED_AT] = $date->format($format);
            } else {
                if (isset($rowData[self::COLUMN_REVIEW_ID])) {
                    $message = 'review with review_id: %1 not imported because the created_at is not correct.';
                    $this->addLogWriteln(__($message, $rowData[self::COLUMN_REVIEW_ID]), $this->output, 'error');
                    $this->addRowError(__($message, $rowData[self::COLUMN_REVIEW_ID]), $rowNum);
                } else {
                    $message = 'review not imported because the created_at is not correct.';
                    $this->addLogWriteln(__($message), $this->output, 'error');
                    $this->addRowError(__($message), $rowNum);
                }
            }
        }

        return $rowData;
    }

    /**
     * @param $reviewId
     * @param $rowData
     */
    protected function updateDateFromDump($reviewId, $rowData)
    {
        if ($reviewId) {
            $this->_connection->insertOnDuplicate(
                $this->_connection->getTableName('review'),
                [self::COLUMN_REVIEW_ID => $reviewId, self::COLUMN_CREATED_AT => $rowData[self::COLUMN_CREATED_AT]],
                [self::COLUMN_CREATED_AT]
            );
        }
    }

    /**
     * @param array $rowData
     * @return bool
     */
    protected function isReviewId(array $rowData)
    {
        $review = $this->reviewsFactory->create();
        $review->load($rowData[self::COLUMN_REVIEW_ID]);
        if ($review->getId()) {
            $result = true;
        } else {
            $result = false;
        }
        return $result;
    }

    /**
     * Inner source object getter
     *
     * @return AbstractSource
     * @throws LocalizedException
     */
    protected function _getSource()
    {
        if (!$this->_source) {
            throw new LocalizedException(__('Please specify a source.'));
        }
        return $this->_source;
    }

    /**
     * Imported entity type code getter
     *
     * @return string
     */
    public function getEntityTypeCode()
    {
        return 'review';
    }
}