<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Review\Model\ResourceModel; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; /** * Rating resource model * * @api * * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Rating extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { const RATING_STATUS_APPROVED = 'Approved'; /** * Store manager * * @var \Magento\Store\Model\StoreManagerInterface */ protected $_storeManager; /** * @var \Magento\Framework\Module\Manager */ protected $moduleManager; /** * @var \Psr\Log\LoggerInterface */ protected $_logger; /** * @var ScopeConfigInterface */ private $scopeConfig; /** * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Module\Manager $moduleManager * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param Review\Summary $reviewSummary * @param string $connectionName * @param ScopeConfigInterface|null $scopeConfig */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, \Psr\Log\LoggerInterface $logger, \Magento\Framework\Module\Manager $moduleManager, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Review\Model\ResourceModel\Review\Summary $reviewSummary, $connectionName = null, ScopeConfigInterface $scopeConfig = null ) { $this->moduleManager = $moduleManager; $this->_storeManager = $storeManager; $this->_logger = $logger; $this->_reviewSummary = $reviewSummary; $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); parent::__construct($context, $connectionName); } /** * Resource initialization * * @return void */ protected function _construct() { $this->_init('rating', 'rating_id'); } /** * Initialize unique fields * * @return $this */ protected function _initUniqueFields() { $this->_uniqueFields = [['field' => 'rating_code', 'title' => '']]; return $this; } /** * Retrieve select object for load object data * * @param string $field * @param mixed $value * @param \Magento\Review\Model\Rating $object * @return \Magento\Framework\DB\Select */ protected function _getLoadSelect($field, $value, $object) { $connection = $this->getConnection(); $table = $this->getMainTable(); $storeId = (int)$this->_storeManager->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId(); $select = parent::_getLoadSelect($field, $value, $object); $codeExpr = $connection->getIfNullSql('title.value', "{$table}.rating_code"); $select->joinLeft( ['title' => $this->getTable('rating_title')], $connection->quoteInto("{$table}.rating_id = title.rating_id AND title.store_id = ?", $storeId), ['rating_code' => $codeExpr] ); return $select; } /** * Actions after load * * @param \Magento\Framework\Model\AbstractModel|\Magento\Review\Model\Rating $object * @return $this */ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $object) { parent::_afterLoad($object); if (!$object->getId()) { return $this; } $connection = $this->getConnection(); $bind = [':rating_id' => (int)$object->getId()]; // load rating titles $select = $connection->select()->from( $this->getTable('rating_title'), ['store_id', 'value'] )->where( 'rating_id=:rating_id' ); $result = $connection->fetchPairs($select, $bind); if ($result) { $object->setRatingCodes($result); } // load rating available in stores $object->setStores($this->getStores((int)$object->getId())); return $this; } /** * Retrieve store IDs related to given rating * * @param int $ratingId * @return array */ public function getStores($ratingId) { $select = $this->getConnection()->select()->from( $this->getTable('rating_store'), 'store_id' )->where( 'rating_id = ?', $ratingId ); return $this->getConnection()->fetchCol($select); } /** * Actions after save * * @param \Magento\Framework\Model\AbstractModel|\Magento\Review\Model\Rating $object * @return $this */ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) { parent::_afterSave($object); if ($object->hasRatingCodes()) { $this->processRatingCodes($object); } if ($object->hasStores()) { $this->processRatingStores($object); } return $this; } /** * Process rating codes * * @param \Magento\Framework\Model\AbstractModel $object * @return $this */ protected function processRatingCodes(\Magento\Framework\Model\AbstractModel $object) { $connection = $this->getConnection(); $ratingId = (int)$object->getId(); $table = $this->getTable('rating_title'); $select = $connection->select()->from($table, ['store_id', 'value']) ->where('rating_id = :rating_id'); $old = $connection->fetchPairs($select, [':rating_id' => $ratingId]); $new = array_filter(array_map('trim', $object->getRatingCodes())); $this->deleteRatingData($ratingId, $table, array_keys(array_diff_assoc($old, $new))); $insert = []; foreach (array_diff_assoc($new, $old) as $storeId => $title) { $insert[] = ['rating_id' => $ratingId, 'store_id' => (int)$storeId, 'value' => $title]; } $this->insertRatingData($table, $insert); return $this; } /** * Process rating stores * * @param \Magento\Framework\Model\AbstractModel $object * @return $this */ protected function processRatingStores(\Magento\Framework\Model\AbstractModel $object) { $connection = $this->getConnection(); $ratingId = (int)$object->getId(); $table = $this->getTable('rating_store'); $select = $connection->select()->from($table, ['store_id']) ->where('rating_id = :rating_id'); $old = $connection->fetchCol($select, [':rating_id' => $ratingId]); $new = $object->getStores(); $this->deleteRatingData($ratingId, $table, array_diff($old, $new)); $insert = []; foreach (array_diff($new, $old) as $storeId) { $insert[] = ['rating_id' => $ratingId, 'store_id' => (int)$storeId]; } $this->insertRatingData($table, $insert); return $this; } /** * Delete rating data * * @param int $ratingId * @param string $table * @param array $storeIds * @return void */ protected function deleteRatingData($ratingId, $table, array $storeIds) { if (empty($storeIds)) { return; } $connection = $this->getConnection(); $connection->beginTransaction(); try { $where = ['rating_id = ?' => $ratingId, 'store_id IN(?)' => $storeIds]; $connection->delete($table, $where); $connection->commit(); } catch (\Exception $e) { $this->_logger->critical($e); $connection->rollBack(); } } /** * Insert rating data * * @param string $table * @param array $data * @return void */ protected function insertRatingData($table, array $data) { if (empty($data)) { return; } $connection = $this->getConnection(); $connection->beginTransaction(); try { $connection->insertMultiple($table, $data); $connection->commit(); } catch (\Exception $e) { $this->_logger->critical($e); $connection->rollBack(); } } /** * Perform actions after object delete * * Prepare rating data for reaggregate all data for reviews * * @param \Magento\Framework\Model\AbstractModel $object * @return $this */ protected function _afterDelete(\Magento\Framework\Model\AbstractModel $object) { parent::_afterDelete($object); if (!$this->moduleManager->isEnabled('Magento_Review') && !$this->scopeConfig->getValue( \Magento\Review\Observer\PredispatchReviewObserver::XML_PATH_REVIEW_ACTIVE, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ) ) { return $this; } $data = $this->_getEntitySummaryData($object); $summary = []; foreach ($data as $row) { $clone = clone $object; $clone->addData($row); $summary[$clone->getStoreId()][$clone->getEntityPkValue()] = $clone; } $this->_reviewSummary->reAggregate($summary); return $this; } /** * Return array of rating summary * * @param \Magento\Review\Model\Rating $object * @param boolean $onlyForCurrentStore * @return array */ public function getEntitySummary($object, $onlyForCurrentStore = true) { $data = $this->_getEntitySummaryData($object); if ($onlyForCurrentStore) { foreach ($data as $row) { if ($row['store_id'] == $this->_storeManager->getStore()->getId()) { $object->addData($row); } } return $object; } $stores = $this->_storeManager->getStores(); $result = []; foreach ($data as $row) { $clone = clone $object; $clone->addData($row); $result[$clone->getStoreId()] = $clone; } $usedStoresId = array_keys($result); foreach ($stores as $store) { if (!in_array($store->getId(), $usedStoresId)) { $clone = clone $object; $clone->setCount(0); $clone->setSum(0); $clone->setStoreId($store->getId()); $result[$store->getId()] = $clone; } } return array_values($result); } /** * Return data of rating summary * * @param \Magento\Review\Model\Rating $object * @return array */ protected function _getEntitySummaryData($object) { $connection = $this->getConnection(); $sumColumn = new \Zend_Db_Expr("SUM(rating_vote.{$connection->quoteIdentifier('percent')})"); $countColumn = new \Zend_Db_Expr("COUNT(*)"); $select = $connection->select()->from( ['rating_vote' => $this->getTable('rating_option_vote')], ['entity_pk_value' => 'rating_vote.entity_pk_value', 'sum' => $sumColumn, 'count' => $countColumn] )->join( ['review' => $this->getTable('review')], 'rating_vote.review_id=review.review_id', [] )->joinLeft( ['review_store' => $this->getTable('review_store')], 'rating_vote.review_id=review_store.review_id', ['review_store.store_id'] ); if (!$this->_storeManager->isSingleStoreMode()) { $select->join( ['rating_store' => $this->getTable('rating_store')], 'rating_store.rating_id = rating_vote.rating_id AND rating_store.store_id = review_store.store_id', [] ); } $select->join( ['review_status' => $this->getTable('review_status')], 'review.status_id = review_status.status_id', [] )->where( 'review_status.status_code = :status_code' )->group( 'rating_vote.entity_pk_value' )->group( 'review_store.store_id' ); $bind = [':status_code' => self::RATING_STATUS_APPROVED]; $entityPkValue = $object->getEntityPkValue(); if ($entityPkValue) { $select->where('rating_vote.entity_pk_value = :pk_value'); $bind[':pk_value'] = $entityPkValue; } return $connection->fetchAll($select, $bind); } /** * Review summary * * @param \Magento\Review\Model\Rating $object * @param boolean $onlyForCurrentStore * @return array */ public function getReviewSummary($object, $onlyForCurrentStore = true) { $connection = $this->getConnection(); $sumColumn = new \Zend_Db_Expr("SUM(rating_vote.{$connection->quoteIdentifier('percent')})"); $countColumn = new \Zend_Db_Expr('COUNT(*)'); $select = $connection->select()->from( ['rating_vote' => $this->getTable('rating_option_vote')], ['sum' => $sumColumn, 'count' => $countColumn] )->joinLeft( ['review_store' => $this->getTable('review_store')], 'rating_vote.review_id = review_store.review_id', ['review_store.store_id'] ); if (!$this->_storeManager->isSingleStoreMode()) { $select->join( ['rating_store' => $this->getTable('rating_store')], 'rating_store.rating_id = rating_vote.rating_id AND rating_store.store_id = review_store.store_id', [] ); } $select->where( 'rating_vote.review_id = :review_id' )->group( 'rating_vote.review_id' )->group( 'review_store.store_id' ); $data = $connection->fetchAll($select, [':review_id' => $object->getReviewId()]); $currentStore = $this->_storeManager->isSingleStoreMode() ? $this->_storeManager->getStore()->getId() : null; if ($onlyForCurrentStore) { foreach ($data as $row) { if ($row['store_id'] !== $currentStore) { $object->addData($row); } } return $object; } $result = []; $stores = $this->_storeManager->getStore()->getResourceCollection()->load(); foreach ($data as $row) { $clone = clone $object; $clone->addData($row); $result[$clone->getStoreId()] = $clone; } $usedStoresId = array_keys($result); foreach ($stores as $store) { if (!in_array($store->getId(), $usedStoresId)) { $clone = clone $object; $clone->setCount(0); $clone->setSum(0); $clone->setStoreId($store->getId()); $result[$store->getId()] = $clone; } } return array_values($result); } /** * Get rating entity type id by code * * @param string $entityCode * @return int */ public function getEntityIdByCode($entityCode) { $select = $this->getConnection()->select()->from( $this->getTable('rating_entity'), ['entity_id'] )->where( 'entity_code = :entity_code' ); return $this->getConnection()->fetchOne($select, [':entity_code' => $entityCode]); } /** * Delete ratings by product id * * @param int $productId * @return $this */ public function deleteAggregatedRatingsByProductId($productId) { $entityId = $this->getEntityIdByCode(\Magento\Review\Model\Rating::ENTITY_PRODUCT_CODE); $connection = $this->getConnection(); $select = $connection->select()->from($this->getMainTable(), 'rating_id')->where('entity_id = :entity_id'); $ratingIds = $connection->fetchCol($select, [':entity_id' => $entityId]); if ($ratingIds) { $where = ['entity_pk_value = ?' => (int)$productId, 'rating_id IN(?)' => $ratingIds]; $connection->delete($this->getTable('rating_option_vote_aggregated'), $where); } return $this; } }