Commit a82437d6 by halweg

Merge branch 'haowei/reviewers' into developer

# Conflicts:
#	app/code/Rokanthemes/OnePageCheckout/view/frontend/web/js/action/update-item.js
#	app/code/Rokanthemes/OnePageCheckout/view/frontend/web/js/model/shipping-rate-processor/new-address.js
#	app/code/Sparsh/FreeShippingBar/view/frontend/templates/free_shipping_bar.phtml
parents bff42af5 c22d653e
......@@ -44,14 +44,14 @@ class InstallSchema implements InstallSchemaInterface
->newTable($installer->getTable('amasty_advanced_review_images'))
->addColumn(
'image_id',
Table::TYPE_INTEGER,
Table::TYPE_BIGINT,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Review Id'
)
->addColumn(
'review_id',
Table::TYPE_INTEGER,
Table::TYPE_BIGINT,
null,
['default' => 0, 'nullable' => false],
'Review table id'
......
<?php
namespace Joshine\Review\Block\Components;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\View\Element\Template;
class Pager extends \Magento\Theme\Block\Html\Pager
{
const DEFAULT_PAGE_SIZE = 3;
/**
* @var string
*/
protected $_template = 'Joshine_Review::Components/pager.phtml';
/**
* @var Json
*/
private $json;
public function __construct(
Template\Context $context,
Json $json,
array $data = []
) {
parent::__construct($context, $data);
$this->json = $json;
}
/**
* @return string
*/
public function getJsonData()
{
return $this->json->serialize($this->getData());
}
}
<?php
namespace Joshine\Review\Block;
use Joshine\Review\Model\ResourceModel\Images\Collection;
use Joshine\Review\Helper\ImageHelper;
use Magento\Framework\View\Element\Template;
use Joshine\Review\Model\ResourceModel\Images\CollectionFactory;
/**
* Class Images
* @package Amasty\AdvancedReview\Block
*/
class Images extends \Magento\Framework\View\Element\Template
{
//评论缩略图宽度
const REVIEW_COVER_WIDTH = 200;
/**
* @var string
*/
protected $_template = 'Joshine_Review::images.phtml';
private $reviewId;
private $productId;
/**
* @var int
*/
/**
* @var \Magento\Framework\Json\EncoderInterface
*/
private $jsonEncoder;
/**
* @var CollectionFactory
*/
private $collectionFactory;
/**
* @var ImageHelper
*/
private $imageHelper;
public function __construct(
Template\Context $context,
CollectionFactory $collectionFactory,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
ImageHelper $imageHelper,
array $data = []
) {
parent::__construct($context, $data);
$this->jsonEncoder = $jsonEncoder;
$this->collectionFactory = $collectionFactory;
$this->imageHelper = $imageHelper;
}
/**
* @return int
*/
public function getReviewId(): int
{
return $this->reviewId;
}
/**
* @param $reviewId
*
* @return $this
*/
public function setReviewId($reviewId): Images
{
$this->reviewId = $reviewId;
return $this;
}
public function setProductId($productId): Images
{
$this->productId = $productId;
return $this;
}
public function getProductId()
{
return $this->productId;
}
public function getCollection() {
/** @var Collection $collection */
$collection = $this->collectionFactory->create()
->addFieldToSelect('*')
->addFieldToFilter('review_id', $this->getReviewId());
return $collection;
}
public function getFullImagePath($item): string
{
return $this->imageHelper->getFullPath($item->getPath());
}
/**
* @param $item
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getResizedImagePath($item): string
{
return $this->imageHelper->resize($item->getPath(), self::REVIEW_COVER_WIDTH * 2);
}
/**
* @param $item
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getResizedImagePathByLimit($item, $width): string
{
return $this->imageHelper->resize($item->getPath(), $width);
}
}
<?php
namespace Joshine\Review\Block;
class JsInit extends \Magento\Framework\View\Element\Template {
protected $_template = 'Joshine_Review::review-js.phtml';
}
\ No newline at end of file
<?php
namespace Joshine\Review\Block\Review\Product\View;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\DataObjectFactory;
use Magento\Review\Model\ResourceModel\Review\Collection as ReviewCollection;
use Magento\Review\Model\Review\SummaryFactory;
use Joshine\Review\Block\Components\Pager;
class ListView extends \Magento\Review\Block\Product\View\ListView
{
const VOTED_CLASS_NAME = 'active';
/**
* @var \Magento\Review\Model\Review\SummaryFactory
*/
private $summaryModFactory;
private $dataObjectFactory;
/**
* @var ReviewCollection
*/
private $reviewsCollection;
private $reviewsColFactory;
private $voteRepository;
private $vote;
private $formKey;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
private $remoteAddress;
public function __construct(
DataObjectFactory $dataObjectFactory,
SummaryFactory $summaryModFactory,
\Magento\Framework\Data\Form\FormKey $formKey,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress,
\Joshine\Review\Model\Repository\VoteRepository $voteRepository,
\Magento\Review\Model\ResourceModel\Review\CollectionFactory $reviewsColFactory,
\Magento\Catalog\Block\Product\Context $context, \Magento\Framework\Url\EncoderInterface $urlEncoder, \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\Framework\Stdlib\StringUtils $string, \Magento\Catalog\Helper\Product $productHelper, \Magento\Catalog\Model\ProductTypes\ConfigInterface $productTypeConfig, \Magento\Framework\Locale\FormatInterface $localeFormat, \Magento\Customer\Model\Session $customerSession, ProductRepositoryInterface $productRepository, \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, \Magento\Review\Model\ResourceModel\Review\CollectionFactory $collectionFactory, array $data = [])
{
$this->summaryModFactory = $summaryModFactory;
$this->dataObjectFactory = $dataObjectFactory;
$this->reviewsColFactory = $reviewsColFactory;
$this->voteRepository = $voteRepository;
$this->formKey = $formKey;
$this->remoteAddress = $remoteAddress;
parent::__construct($context, $urlEncoder, $jsonEncoder, $string, $productHelper, $productTypeConfig, $localeFormat, $customerSession, $productRepository, $priceCurrency, $collectionFactory, $data);
}
protected function _prepareLayout()
{
parent::_prepareLayout();
if ($this->getReviewsCollection()) {
$pager = $this->getLayout()->createBlock(
Pager::class
)->setCollection(
$this->getLimitedReviewsCollection()
)->setData($this->getData())
->setProductId($this->getProductId());
$this->setChild('pager', $pager);
$this->getLimitedReviewsCollection()->load();
}
return $this;
}
public function setSummary($product, $displayedCollection)
{
$storeId = $this->_storeManager->getStore()->getId();
$summaryData = $this->summaryModFactory->create()
->setStoreId($storeId)
->load($product->getId());
$summary = $this->dataObjectFactory->create();
$summary->setData($summaryData->getData());
$product->setRatingSummary($summary);
}
public function getRatingSummary()
{
$result = null;
$summary = $this->getProduct()->getRatingSummary();
if ($summary) {
$result = $summary->getRatingSummary();
}
return $result;
}
function getRatingSummaryValue(): float
{
$value = $this->getRatingSummary();
$value = $value / 100 * 5;
return round($value, 1);
}
public function getDetailedSummary(): array
{
$result = [
'3' => 0,
'2' => 0,
'1' => 0
];
$reviews = $this->reviewsColFactory->create()->addStoreFilter(
$this->_storeManager->getStore()->getId()
)->addStatusFilter(
\Magento\Review\Model\Review::STATUS_APPROVED
)->addEntityFilter(
'product',
$this->getProduct()->getId()
);
foreach ($reviews as $item) {
if($item->getSizeFits() != 0 ){
$key = $item->getSizeFits();
$result[$key] = $result[$key] + 1;
}
}
return $result;
}
/**
* Get collection of reviews
*
* @return ReviewCollection
*/
public function getReviewsCollection()
{
if (null === $this->reviewsCollection) {
$this->reviewsCollection = $this->reviewsColFactory->create()->addStoreFilter(
$this->_storeManager->getStore()->getId()
)->addStatusFilter(
\Magento\Review\Model\Review::STATUS_APPROVED
)->addEntityFilter(
'product',
$this->getProduct()->getId()
)->setDateOrder();
}
return $this->reviewsCollection;
}
public function getReviewsCount() : int
{
return $this->getReviewsCollection()->getSize();
}
public function getLimitedReviewsCollection(): ReviewCollection
{
$page = $this->getRequest()->getParam('p') ?? 1;
return $this->getReviewsCollection()->clear()
->setPageSize(Pager::DEFAULT_PAGE_SIZE)
->setCurPage((int)$page)
->load()->addRateVotes();
}
public function getPagerHtml()
{
return $this->getChildHtml('pager');
}
private function getVote($reviewId)
{
return $this->voteRepository->getVotesCount($reviewId);
}
public function getPlusReview($reviewId)
{
$vote = $this->getVote($reviewId);
return $vote['plus'];
}
/**
* @return string
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getFormKey(): string
{
return $this->formKey->getFormKey();
}
/**
* @return string
*/
public function getPlusVotedClass($reviewId)
{
$result = '';
if ($this->isPlusVoted($reviewId)) {
$result = self::VOTED_CLASS_NAME;
}
return $result;
}
private function getVotedByIp($reviewId)
{
return $this->voteRepository->getVotesCount(
$reviewId,
$this->remoteAddress->getRemoteAddress()
);
}
public function isPlusVoted($reviewId)
{
$voted = $this->getVotedByIp($reviewId);
return $voted['plus'] > 0;
}
}
<?php
namespace Joshine\Review\Block\Review;
use Amasty\AdvancedReview\Model\Toolbar\Applier;
use Amasty\AdvancedReview\Model\Toolbar\UrlBuilder;
use Magento\Framework\View\Element\Template;
class ToolBar extends Template {
protected $request;
protected $_template = 'Joshine_Review::toolbar.phtml';
public function __construct(
Template\Context $context,
\Magento\Framework\App\Request\Http $request,
array $data = []
) {
parent::__construct($context, $data);
$this->request = $request;
}
}
<?php
namespace Joshine\Review\Contract;
interface ImagesInterface
{
const IMAGE_ID = 'image_id';
const REVIEW_ID = 'review_id';
const PATH = 'path';
/**
* Returns image id field
*
* @return int|null
*/
public function getImageId();
/**
* @param int $imageId
*
* @return $this
*/
public function setImageId($imageId);
/**
* Returns review id field
*
* @return int|null
*/
public function getReviewId();
/**
* @param int $reviewId
*
* @return $this
*/
public function setReviewId($reviewId);
/**
* Returns image path
*
* @return string|null
*/
public function getPath();
/**
* @param string $path
*
* @return $this
*/
public function setPath($path);
}
<?php
namespace Joshine\Review\Controller\Ajax;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Controller\Result\JsonFactory;
class Helpful extends \Magento\Framework\App\Action\Action
{
/**
* @var \Magento\Framework\Data\Form\FormKey\Validator
*/
private $formKeyValidator;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* @var \Magento\Framework\Json\EncoderInterface
*/
private $jsonEncoder;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
private $remoteAddress;
private $voteRepository;
private $voteFactory;
/**
* @var JsonFactory
*/
private $resultJsonFactory;
public function __construct(
Context $context,
\Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator,
\Psr\Log\LoggerInterface $logger,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress,
\Joshine\Review\Model\VoteFactory $voteFactory,
\Joshine\Review\Model\Repository\VoteRepository $voteRepository,
JsonFactory $resultJsonFactory
) {
parent::__construct($context);
$this->formKeyValidator = $formKeyValidator;
$this->logger = $logger;
$this->jsonEncoder = $jsonEncoder;
$this->remoteAddress = $remoteAddress;
$this->voteRepository = $voteRepository;
$this->voteFactory = $voteFactory;
$this->resultJsonFactory = $resultJsonFactory;
}
/**
* @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Json|\Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$message = [
'error' => __('Sorry. There is a problem with your Vote Request.')
];
if ($this->getRequest()->isAjax()) {
try {
if (!$this->formKeyValidator->validate($this->getRequest())) {
throw new LocalizedException(
__('Form key is not valid. Please try to reload the page.')
);
}
$type = $this->getRequest()->getParam('type');
$reviewId = (int)$this->getRequest()->getParam('review');
if ($reviewId > 0 && in_array($type, ['plus', 'minus', 'update'])) {
$ip = $this->remoteAddress->getRemoteAddress();
if ($type != 'update') {
$type = ($type == 'plus') ? '1' : '0';
$model = $this->voteRepository->getByIdAndIp($reviewId, $ip);
$modelType = $model->getType();
if ($model->getVoteId()) {
$this->voteRepository->delete($model);
}
if ($modelType === null || $modelType != $type) {
$model = $this->voteFactory->create();
$model->setIp($ip);
$model->setReviewId($reviewId);
$model->setType($type);
$this->voteRepository->save($model);
}
}
$votesForReview = $this->voteRepository->getVotesCount($reviewId);
$voted = $this->voteRepository->getVotesCount($reviewId, $ip);
$message = [
'success' => __('Success.'),
'data' => $votesForReview,
'voted' => $voted
];
}
} catch (LocalizedException $e) {
$message = ['error' => $e->getMessage()];
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
}
$resultPage = $this->resultJsonFactory->create();
$resultPage->setHttpResponseCode(200);
$resultPage->setData($message);
return $resultPage;
}
}
<?php
namespace Joshine\Review\Controller\Ajax;
use Amasty\AdvancedReview\Model\ResourceModel\Images as ImagesModel;
use Joshine\Review\Helper\BlockHelper;
use Joshine\Review\Helper\ImageHelper;
use Magento\Catalog\Model\ProductFactory;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Review\Model\ResourceModel\Review\Collection;
use Magento\Review\Model\Review;
class ReviewInfo extends \Magento\Framework\App\Action\Action {
/**
* @var \Magento\Framework\Data\Form\FormKey\Validator
*/
private $formKeyValidator;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* @var \Magento\Framework\Json\EncoderInterface
*/
private $jsonEncoder;
/**
* @var JsonFactory
*/
private $resultJsonFactory;
private $reviewFactory;
/**
* @var ProductFactory
*/
private $productFactory;
/**
* @var \Magento\Review\Model\ResourceModel\Review\CollectionFactory
*/
private $reviewsColFactory;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\RemoteAddress
*/
private $remoteAddress;
public function __construct(
Context $context,
\Psr\Log\LoggerInterface $logger,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
JsonFactory $resultJsonFactory,
\Magento\Review\Model\ReviewFactory $reviewFactory,
ProductFactory $productFactory,
\Magento\Review\Model\ResourceModel\Review\CollectionFactory $reviewsColFactory,
\Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator,
\Magento\Framework\HTTP\PhpEnvironment\RemoteAddress $remoteAddress
) {
parent::__construct($context);
$this->logger = $logger;
$this->jsonEncoder = $jsonEncoder;
$this->resultJsonFactory = $resultJsonFactory;
$this->reviewFactory = $reviewFactory;
$this->productFactory = $productFactory;
$this->reviewsColFactory = $reviewsColFactory;
$this->formKeyValidator = $formKeyValidator;
$this->remoteAddress = $remoteAddress;
}
/**
* @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Json|\Magento\Framework\Controller\ResultInterface
*/
public function execute()
{
$message = [
'error' => __('Sorry. There is a problem with your Request.')
];
if ($this->getRequest()->isAjax()) {
try {
if (!$this->formKeyValidator->validate($this->getRequest())) {
throw new LocalizedException(
__('Form key is not valid. Please try to reload the page.')
);
}
$reviewId = $this->getRequest()->getParam('review_id');
$productId = $this->getRequest()->getParam('product_id');
if (empty($reviewId) || empty($productId) ) {
throw new LocalizedException(
__('request is valid.')
);
}
$reviewInfo = $this->getReviewInfo($reviewId, $productId);
$reviews = $this->getAllReview($productId);
$message = [
'success' => __('Success.'),
'data' => $reviews,
'review' => $reviewInfo,
'total' => count($reviews),
'offset' => $this->getOffset($reviews, $reviewInfo)
];
} catch (LocalizedException $e) {
$message = ['error' => $e->getMessage()];
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
}
}
$resultPage = $this->resultJsonFactory->create();
$resultPage->setHttpResponseCode(200);
$resultPage->setData($message);
return $resultPage;
}
/**
* @param $productId
* @return \Magento\Review\Model\ResourceModel\Review\Collection
*/
public function getCollection($productId) : Collection
{
return $this->reviewsColFactory->create()->addStatusFilter(
\Magento\Review\Model\Review::STATUS_APPROVED
)->addEntityFilter(
'product',
$productId
);
}
private function getStoreId()
{
$storeManager = $this->_objectManager->get('\Magento\Store\Model\StoreManagerInterface');
return $storeManager->getStore()->getId();
}
public function getRatingPercent($reviewId)
{
$ratingCollection = $this->_objectManager->get('Magento\Review\Model\ResourceModel\Rating\Option\Vote\Collection')
->addFieldToFilter('review_id',$reviewId);
$data = $ratingCollection->getData();
return $data[0]['percent'] ?? 100;
}
public function getAllReview($productId)
{
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$db = $objectManager->get( 'Magento\Framework\App\ResourceConnection' )->getConnection();
$tblReview = $db->getTableName( 'review' );
$tblImages = $db->getTableName( 'joshine_review_images' );
$tblDetail = $db->getTableName( 'review_detail' );
$select = $db->select()
->distinct()
->from( [ 'review' => $tblReview ], ['*'] )
->join( [ 'images' => $tblImages ], 'review.review_id = images.review_id' )
->join( [ 'review_detail' => $tblDetail ], 'review.review_id = review_detail.review_id' )
->where( 'review.entity_pk_value = ?', $productId )
->where('review.status_id = ? ', Review::STATUS_APPROVED)
->group('review.review_id')->limit(50)->order('review.created_at DESC',);
$data = $db->fetchAll($select);
$res = [];
foreach ($data as $item) {
$_item['review_id'] = $item['review_id'];
$_item['size_fits'] = $item['size_fits'];
$_item['nickname'] = $item['nickname'];
$_item['title'] = $item['title'];
$_item['detail'] = $item['detail'];
$_item['created_at'] = $item['created_at'];
$_item['images'] = $this->getImages($item['review_id'], $productId);
$_item['rating'] = $this->getRatingPercent($item['review_id']);
$_item['helpful'] = $this->getVoteCount($item['review_id'])['plus'];
$_item['helpful_clicked'] = $this->getVoteCount($item['review_id'], $this->remoteAddress->getRemoteAddress())['plus'] > 0;
$res[] = $_item;
}
return $res;
}
public function getReviewInfo($reviewId, $productId)
{
$model = $this->reviewFactory->create()->load($reviewId);
$_item = [];
$_item['review_id'] = $reviewId;
$_item['size_fits'] = $model->getSize_fits();
$_item['nickname'] = $model->getNickname();
$_item['title'] = $model->getTitle();
$_item['detail'] = $model->getDetail();
$_item['created_at'] = $model->getCreatedAt();
$_item['images'] = $this->getImages($reviewId, $productId);
$_item['rating'] = $this->getRatingPercent($reviewId);
$_item['helpful'] = $this->getVoteCount($reviewId)['plus'];
$_item['helpful_clicked'] = $this->getVoteCount($reviewId, $this->remoteAddress->getRemoteAddress())['plus'] > 0;
return $_item;
}
public function getVoteCount($reviewId, $ip=null)
{
$repo = $this->_objectManager->get('Joshine\Review\Model\Repository\VoteRepository');
return $repo->getVotesCount($reviewId);
}
public function getImages($reviewId, $productId)
{
$collect = $this->_objectManager->create('Joshine\Review\Model\ResourceModel\Images\Collection')
->addFieldToSelect('*')
->addFieldToFilter('review_id', $reviewId);
/** @var $helper BlockHelper */
$helper = $this->_objectManager->get('Joshine\Review\Helper\BlockHelper');
$block = $helper->getReviewImagesBlock($reviewId, $productId);
$res = [];
foreach ($collect->getItems() as $item) {
$res['full'][] = ['url'=>$block->getFullImagePath($item), 'image_id'=>$item->getId()] ;
$res['thumb'][] = ['url'=>$block->getResizedImagePathByLimit($item, 100), 'image_id'=>$item->getId()] ; ;
}
return $res;
}
public function getOffset($allArr, $arr)
{
$offset = 0;
foreach ($allArr as $key => $value) {
$offset++;
if ($value['review_id'] == $arr['review_id']) {
break;
}
}
return $offset;
}
}
\ No newline at end of file
<?php
namespace Joshine\Review\Helper;
use Joshine\Review\Block\Images;
use Joshine\Review\Block\Review\ToolBar;
class BlockHelper implements \Magento\Framework\Data\CollectionDataSourceInterface
{
/**
* @var \Magento\Framework\View\Element\BlockFactory
*/
private $blockFactory;
/**
* @var \Magento\Framework\Stdlib\StringUtils
*/
private $stringUtils;
/**
* @var \Magento\Customer\Model\SessionFactory
*/
private $sessionFactory;
/**
* @var \Magento\Framework\Escaper
*/
private $escaper;
/**
* @var \Magento\Framework\UrlInterface
*/
private $urlBuilder;
/**
* @var \Magento\Framework\App\RequestInterface
*/
private $request;
public function __construct(
\Magento\Framework\View\Element\BlockFactory $blockFactory,
\Magento\Framework\Stdlib\StringUtils $stringUtils,
\Magento\Customer\Model\SessionFactory $sessionFactory,
\Magento\Framework\Escaper $escaper,
\Magento\Framework\UrlInterface $urlBuilder,
\Magento\Framework\App\RequestInterface $request
) {
$this->blockFactory = $blockFactory;
$this->stringUtils = $stringUtils;
$this->sessionFactory = $sessionFactory;
$this->escaper = $escaper;
$this->urlBuilder = $urlBuilder;
$this->request = $request;
}
public function getReviewImagesHtml($reviewId, $productId)
{
$html = '';
$block = $this->blockFactory
->createBlock(Images::class)
->setProductId($productId)
->setReviewId($reviewId);
if ($block) {
$html = $block->toHtml();
}
return $html;
}
public function getToolBarHtML()
{
return $this->blockFactory
->createBlock(ToolBar::class)
->toHtml();
}
public function getReviewImagesBlock($reviewId, $productId) : Images
{
return $this->blockFactory
->createBlock(Images::class)
->setProductId($productId)
->setReviewId($reviewId);
}
}
<?php
namespace Joshine\Review\Helper;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\UrlInterface;
class ImageHelper
{
const IMAGE_PATH = '/joshine/review/';
const IMAGE_TMP_PATH = '/joshine/review/tmp/';
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
private $storeManager;
/**
* @var \Magento\Framework\Filesystem
*/
private $filesystem;
/**
* @var \Magento\Framework\Image\AdapterFactory
*/
private $imageFactory;
/**
* @var \Magento\Framework\Filesystem\Io\File
*/
private $fileManager;
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\Filesystem $filesystem,
\Magento\Framework\Image\AdapterFactory $imageFactory,
\Magento\Framework\Filesystem\Io\File $fileManager
) {
$this->storeManager = $storeManager;
$this->filesystem = $filesystem;
$this->imageFactory = $imageFactory;
$this->fileManager = $fileManager;
}
/**
* @param $name
* @return string
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getFullPath($name)
{
$path = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
$path = trim($path, '/');
$name = trim($name, '/');
$path .= '/joshine/review/';
return $path . $name;
}
/**
* @param $image
* @param int $width
* @param int $height
* @return string
* @throws \Exception
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function resize($image, $width = null, $height = null)
{
$mediaUrl = $this->storeManager->getStore()->getBaseUrl(
UrlInterface::URL_TYPE_MEDIA
);
$mediaUrl = trim($mediaUrl, '/');
$resizedURL = $mediaUrl . self::IMAGE_PATH . 'resized/' . $width . '/' . $image;
$absolutePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)
->getAbsolutePath(self::IMAGE_PATH)
. $image;
$imageResized = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)
->getAbsolutePath(self::IMAGE_PATH . 'resized/' . $width . '/')
. $image;
if ($this->fileManager->fileExists($imageResized)) {
return $resizedURL;
}
if (!$this->fileManager->fileExists($absolutePath)) {
return '';
}
//create image factory...
$imageResize = $this->imageFactory->create();
$imageResize->open($absolutePath);
$imageResize->constrainOnly(true);
$imageResize->keepTransparency(true);
$imageResize->keepFrame(false);
$imageResize->backgroundColor([255, 255, 255]);
$imageResize->keepAspectRatio(true);
$imageResize->resize($width, $height);
$destination = $imageResized;
$imageResize->save($destination);
return $resizedURL;
}
}
<?php
namespace Joshine\Review\Model;
use Joshine\Review\Helper\ImageHelper;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\MediaStorage\Model\File\Uploader;
class ImageUploader
{
/**
* @var \Magento\Framework\Filesystem
*/
private $filesystem;
/**
* @var \Magento\MediaStorage\Model\File\UploaderFactory
*/
private $fileUploaderFactory;
/**
* @var \Magento\Framework\Image\AdapterFactory
*/
private $adapterFactory;
/**
* @var \Magento\Framework\Filesystem\Io\File
*/
private $ioFile;
public function __construct(
\Magento\Framework\Filesystem $filesystem,
\Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory,
\Magento\Framework\Image\AdapterFactory $adapterFactory,
\Magento\Framework\Filesystem\Io\File $ioFile
) {
$this->filesystem = $filesystem;
$this->fileUploaderFactory = $fileUploaderFactory;
$this->adapterFactory = $adapterFactory;
$this->ioFile = $ioFile;
}
public function execute(array $file, $isTmp = false)
{
$path = $this->filesystem->getDirectoryRead(
DirectoryList::MEDIA
)->getAbsolutePath(
$isTmp ? ImageHelper::IMAGE_TMP_PATH : ImageHelper::IMAGE_PATH
);
$this->ioFile->checkAndCreateFolder($path);
/** @var $uploader Uploader */
$uploader = $this->fileUploaderFactory->create(
['fileId' => $file]
);
$imageAdapter = $this->adapterFactory->create();
$uploader->addValidateCallback('catalog_product_image', $imageAdapter, 'validateUploadFile');
$uploader->setAllowedExtensions(['jpg', 'jpeg', 'gif', 'png']);
$uploader->setAllowRenameFiles(true);
$uploader->setFilesDispersion(true);
$result = $uploader->save($path);
$this->trim($result);
return $result;
}
public function copy(string $imagePath)
{
$path = $this->filesystem->getDirectoryRead(
DirectoryList::MEDIA
)->getAbsolutePath(
ImageHelper::IMAGE_TMP_PATH
);
$from = $path . $imagePath;
if ($this->ioFile->fileExists($from)) {
$realPath = $this->filesystem->getDirectoryRead(
DirectoryList::MEDIA
)->getAbsolutePath(
ImageHelper::IMAGE_PATH
);
$counter = 0;
while ($this->ioFile->fileExists($realPath . $imagePath)) {
$imagePathArray = explode('.', $imagePath);
$imagePathArray[0] .= $counter++;
$imagePath = implode('.', $imagePathArray);
}
$this->ioFile->checkAndCreateFolder($this->ioFile->dirname($realPath . $imagePath));
if ($this->ioFile->mv($from, $realPath . $imagePath)) {
return $imagePath;
}
}
return false;
}
/**
* Fix for magento 2114 - setup upgrade
* @param $result
*/
private function trim($result)
{
if (isset($result['path']) && $result['file']) {
$path = rtrim($result['path'], '/') . $result['file'];
$this->ioFile->chmod($path, 0777);
}
}
}
<?php
namespace Joshine\Review\Model;
use Joshine\Review\Contract\ImagesInterface;
use Magento\Framework\Model\AbstractModel;
/**
* Class Images
* @package Amasty\AdvancedReview\Model
*/
class Images extends AbstractModel implements ImagesInterface
{
public function _construct()
{
$this->_init(\Joshine\Review\Model\ResourceModel\Images::class);
}
/**
* Returns image id field
*
* @return int|null
*/
public function getImageId()
{
return $this->getData(self::IMAGE_ID);
}
/**
* @param int $imageId
*
* @return $this
*/
public function setImageId($imageId)
{
$this->setData(self::IMAGE_ID, $imageId);
return $this;
}
/**
* Returns review id field
*
* @return int|null
*/
public function getReviewId()
{
return $this->getData(self::REVIEW_ID);
}
/**
* @param int $reviewId
*
* @return $this
*/
public function setReviewId($reviewId)
{
$this->setData(self::REVIEW_ID, $reviewId);
return $this;
}
/**
* Returns image path
*
* @return string|null
*/
public function getPath()
{
return $this->getData(self::PATH);
}
/**
* @param string $path
*
* @return $this
*/
public function setPath($path)
{
$this->setData(self::PATH, $path);
return $this;
}
}
<?php
namespace Joshine\Review\Model\Repository;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\CouldNotDeleteException;
use Magento\Framework\Exception\CouldNotSaveException;
use Joshine\Review\Model\ResourceModel;
use Joshine\Review\Model\VoteFactory;
class VoteRepository
{
/**
* @var array
*/
private $vote = [];
/**
* @var ResourceModel\Vote
*/
private $voteResource;
/**
* @var VoteFactory
*/
private $voteFactory;
/**
* @var ResourceModel\Vote\CollectionFactory
*/
private $collectionFactory;
public function __construct(
\Joshine\Review\Model\ResourceModel\Vote $voteResource,
\Joshine\Review\Model\VoteFactory $voteFactory,
\Joshine\Review\Model\ResourceModel\Vote\CollectionFactory $collectionFactory
) {
$this->voteResource = $voteResource;
$this->voteFactory = $voteFactory;
$this->collectionFactory = $collectionFactory;
}
/**
* {@inheritdoc}
*/
public function save($vote)
{
if ($vote->getVoteId()) {
$vote = $this->get($vote->getVoteId())->addData($vote->getData());
}
try {
$this->voteResource->save($vote);
$this->vote[$vote->getVoteId()] = $vote;
} catch (\Exception $e) {
if ($vote->getVoteId()) {
throw new CouldNotSaveException(
__('Unable to save vote with ID %1. Error: %2', [$vote->getVoteId(), $e->getMessage()])
);
}
throw new CouldNotSaveException(__('Unable to save new vote. Error: %1', $e->getMessage()));
}
return $vote;
}
/**
* {@inheritdoc}
*/
public function get($voteId)
{
if (!isset($this->vote[$voteId])) {
$vote = $this->voteFactory->create();
$this->voteResource->load($vote, $voteId);
if (!$vote->getVoteId()) {
throw new NoSuchEntityException(__('Vote with specified ID "%1" not found.', $voteId));
}
$this->vote[$voteId] = $vote;
}
return $this->vote[$voteId];
}
public function getByIdAndIp($reviewId, $ip)
{
$vote = $this->voteFactory->create();
$collection = $this->collectionFactory->create()
->addFieldToFilter('review_id', $reviewId)
->addFieldToFilter('ip', $ip);
$collection->getSelect()->limit(1);
if ($collection->getSize() > 0) {
$vote = $collection->getFirstItem();
}
return $vote;
}
/**
* {@inheritdoc}
*/
public function delete($vote)
{
try {
$this->voteResource->delete($vote);
unset($this->vote[$vote->getId()]);
} catch (\Exception $e) {
if ($vote->getVoteId()) {
throw new CouldNotDeleteException(
__('Unable to remove vote with ID %1. Error: %2', [$vote->getVoteId(), $e->getMessage()])
);
}
throw new CouldNotDeleteException(__('Unable to remove vote. Error: %1', $e->getMessage()));
}
return true;
}
/**
* {@inheritdoc}
*/
public function deleteById($voteId)
{
$model = $this->get($voteId);
$this->delete($model);
return true;
}
/**
* @param $reviewId
* @param null $ip
* @return array
*/
public function getVotesCount($reviewId, $ip = null)
{
$result = [
'plus' => 0,
'minus' => 0
];
$collection = $this->collectionFactory->create()
->addFieldToFilter('review_id', $reviewId);
if ($ip) {
$collection->addFieldToFilter('ip', $ip);
}
foreach ($collection as $vote) {
if ($vote->getType() == '1') {
$result['plus'] = ++$result['plus'];
} else {
$result['minus'] = ++$result['minus'];
}
}
return $result;
}
public function getVoteModel()
{
return $this->voteFactory->create();
}
/**
* @return array
*/
public function getVoteIpKeys()
{
return $this->collectionFactory->create()->getVoteIpKeys();
}
}
<?php
namespace Joshine\Review\Model\ResourceModel;
class Images extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
const TABLE_NAME = 'joshine_review_images';
/**
* Model Initialization
*
* @return void
*/
protected function _construct()
{
$this->_init(self::TABLE_NAME, 'image_id');
}
}
<?php
namespace Joshine\Review\Model\ResourceModel\Images;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
/**
* Class Collection
* @package Amasty\AdvancedReview\Model\ResourceModel\Images
*/
class Collection extends AbstractCollection
{
public function _construct()
{
$this->_init(
\Joshine\Review\Model\Images::class,
\Joshine\Review\Model\ResourceModel\Images::class
);
}
/**
* @return array
*/
public function getImageKeys(): array
{
$this->getSelect()->columns('CONCAT(review_id,path) as image_key');
return $this->getColumnValues('image_key');
}
}
<?php
namespace Joshine\Review\Model\ResourceModel;
class Vote extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
const TABLE_NAME = 'joshine_review_vote';
protected function _construct()
{
$this->_init(self::TABLE_NAME, 'vote_id');
}
}
<?php
namespace Joshine\Review\Model\ResourceModel\Vote;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
{
public function _construct()
{
$this->_init(
\Joshine\Review\Model\Vote::class,
\Joshine\Review\Model\ResourceModel\Vote::class
);
}
/**
* @return array
*/
public function getVoteIpKeys(): array
{
$this->getSelect()->columns('CONCAT(review_id,ip) as vote_key');
return $this->getColumnValues('vote_key');
}
}
<?php
namespace Joshine\Review\Model;
use Magento\Framework\Model\AbstractModel;
class Vote extends AbstractModel
{
const VOTE_ID = 'vote_id';
const REVIEW_ID = 'review_id';
const TYPE = 'type';
const IP = 'ip';
public function _construct()
{
$this->_init(\Joshine\Review\Model\ResourceModel\Vote::class);
}
/**
* Returns vote id field
*
* @return int|null
*/
public function getVoteId()
{
return $this->getData(self::VOTE_ID);
}
/**
* @param int $voteId
*
* @return $this
*/
public function setVoteId($voteId)
{
$this->setData(self::VOTE_ID, $voteId);
return $this;
}
/**
* Returns review id field
*
* @return int|null
*/
public function getReviewId()
{
return $this->getData(self::REVIEW_ID);
}
/**
* @param int $reviewId
*
* @return $this
*/
public function setReviewId($reviewId)
{
$this->setData(self::REVIEW_ID, $reviewId);
return $this;
}
/**
* Returns vote type
*
* @return int|null
*/
public function getType()
{
return $this->getData(self::TYPE);
}
/**
* @param int $type
*
* @return $this
*/
public function setType($type)
{
$this->setData(self::TYPE, $type);
return $this;
}
/**
* Returns vote type
*
* @return string|null
*/
public function getIp()
{
return $this->getData(self::IP);
}
/**
* @param string $ip
*
* @return $this
*/
public function setIp($ip)
{
$this->setData(self::IP, $ip);
return $this;
}
}
<?php
namespace Joshine\Review\Plugin\Review\Block;
use Magento\Review\Block\Form as MagentoForm;
class Form
{
/**
* @var \Joshine\Review\Helper\BlockHelper
*/
private $blockHelper;
public function __construct(
\Joshine\Review\Helper\BlockHelper $blockHelper
) {
$this->blockHelper = $blockHelper;
}
public function afterToHtml(
MagentoForm $subject,
$result
) {
$search = '</fieldset>';
$replace = $this->getImageUploadHtml() . $search;
$result = substr_replace($result, $replace, strrpos($result, $search), strlen($search));
$searchForm = 'data-role="product-review-form"';
return str_replace($searchForm, $searchForm . ' enctype="multipart/form-data" ', $result);
}
protected function getImageUploadHtml(): string
{
return sprintf(
'<div class="field review-field-image %s">
<label class="label">%s</label><div class="control">
<input class="joshine-input" name="review_images[]" accept="image/*" multiple %s type="file" title="%s">
</div></div>',
'',
__('Add your photo'),
'',
__('Add your photo')
);
}
}
\ No newline at end of file
<?php
namespace Joshine\Review\Plugin\Review\Model;
use Joshine\Review\Model\ImageUploader;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Review\Block\Form as MagentoForm;
use Magento\Framework\Exception\LocalizedException;
use Magento\Review\Model\Review as MagentoReview;
class Review {
/**
* @var \Magento\Framework\App\RequestInterface
*/
private $request;
/**
* @var ImageUploader
*/
private $imageUploader;
/**
* @var \Joshine\Review\Model\ImagesFactory
*/
private $imagesFactory;
private $imagesResource;
private $messageManager;
public function __construct(
\Magento\Framework\App\RequestInterface $request,
ImageUploader $imageUploader,
\Joshine\Review\Model\ImagesFactory $imagesFactory,
\Magento\Framework\Message\ManagerInterface $messageManager,
\Joshine\Review\Model\ResourceModel\Images $imagesResource
)
{
$this->messageManager = $messageManager;
$this->imagesFactory = $imagesFactory;
$this->imageUploader = $imageUploader;
$this->request = $request;
$this->imagesResource = $imagesResource;
}
public function afterAggregate(
MagentoReview $subject,
$result
) {
$this->uploadReviewImages($subject);
return $result;
}
public function uploadReviewImages(MagentoReview $subject) {
$files = $this->request->getFiles('review_images');
$reviewId = $subject->getReviewId();
//todo:: 用helper方法做是否允许上传图片的判断
if ($files && $reviewId) {
foreach ($files as $fileId => $file) {
if (UPLOAD_ERR_OK == $file['error']) {
$this->uploadImage($file, $reviewId);
}
}
}
}
public function uploadImage($file, $reviewId) {
try {
$result = $this->imageUploader->execute($file);
$this->saveImage($result, $reviewId);
} catch (\Exception $e) {
$this->messageManager->addErrorMessage(__('An error occurred while uploading the image.'));
throw new LocalizedException(__($e->getMessage()));
}
return $this;
}
/**
* @throws NoSuchEntityException
* @throws CouldNotSaveException
*/
private function saveImage($result, $reviewId)
{
/** @var \Joshine\Review\Model\Images $model */
$model = $this->imagesFactory->create();
$model->setReviewId($reviewId);
$model->setPath($result['file']);
if ($model->getImageId()) {
$model = $this->getFullImage($model->getImageId())->addData($model->getData());
}
try {
$this->imagesResource->save($model);
} catch (\Exception $e) {
if ($model->getImageId()) {
throw new CouldNotSaveException(
__('Unable to save image with ID %1. Error: %2', [$model->getImageId(), $e->getMessage()])
);
}
throw new CouldNotSaveException(__('Unable to save new image. Error: %1', $e->getMessage()));
}
}
private function getFullImage($imageId) {
$image = $this->imagesFactory->create();
$this->imagesResource->load($image, $imageId);
if (!$image->getImageId()) {
throw new NoSuchEntityException(__('Rule with specified ID "%1" not found.', $imageId));
}
return $image;
}
}
<?php
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
namespace Joshine\Review\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;
/**
* Class InstallSchema
* @package Amasty\AdvancedReview\Setup
*/
class InstallSchema implements InstallSchemaInterface
{
/**
* @param SchemaSetupInterface $setup
* @param ModuleContextInterface $context
* @throws \Zend_Db_Exception
*/
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
/** 建新表 */
$this->createModuleTables($setup);
$this->addReviewColumns($setup);
$setup->endSetup();
}
/**
* @param SchemaSetupInterface $installer
* @throws \Zend_Db_Exception
*/
private function createModuleTables(SchemaSetupInterface $installer)
{
$table = $installer->getConnection()
->newTable($installer->getTable('joshine_review_images'))
->addColumn(
'image_id',
Table::TYPE_BIGINT,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Review Id'
)
->addColumn(
'review_id',
Table::TYPE_BIGINT,
null,
['default' => 0, 'nullable' => false],
'Review table id'
)
->addColumn(
'path',
Table::TYPE_TEXT,
'2M',
[],
'Image path'
)
->setComment('用户评价图片地址保存表');
$installer->getConnection()->createTable($table);
$tableVote = $installer->getConnection()
->newTable($installer->getTable('joshine_review_vote'))
->addColumn(
'vote_id',
Table::TYPE_INTEGER,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Vote Id'
)
->addColumn(
'review_id',
Table::TYPE_INTEGER,
null,
['default' => 0, 'nullable' => false],
'Review table id'
)
->addColumn(
'type',
Table::TYPE_SMALLINT,
null,
[],
'type'
)
->addColumn(
'ip',
Table::TYPE_TEXT,
256,
[],
'ip'
)
->setComment('用户评价点赞记录表');
$installer->getConnection()->createTable($tableVote);
}
/**
* review表添加字段
* @param SchemaSetupInterface $installer
*/
private function addReviewColumns(SchemaSetupInterface $installer)
{
/**
* 添加verified_buyer字段,用于后续搜索
*/
$installer->getConnection()->addColumn(
$installer->getTable('review'),
'verified_buyer',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
'nullable' => true,
'default' => 0,
'comment' => 'Verified Buyer'
]
);
}
}
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Review\Model\Review">
<plugin name="Joshine_Review::save-images" type="Joshine\Review\Plugin\Review\Model\Review" />
</type>
</config>
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Review\Block\Form">
<plugin name="Joshine_Review::add-file-upload-control" type="Joshine\Review\Plugin\Review\Block\Form" />
</type>
</config>
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="joshine_review" frontName="joshine_review">
<module name="Joshine_Review" />
</route>
</router>
</config>
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Joshine_Review" setup_version="0.0.4">
<sequence>
<module name="Magento_Review"/>
</sequence>
</module>
</config>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Joshine_Review',
__DIR__
);
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<block name="joshine.review.comments" class="Joshine\Review\Block\Comment\Container">
<block name="joshine.review.comments.messages" as="messages" class="Joshine\Review\Block\Comment\CommentsList">
<block name="joshine.review.comments.message" as="message" class="Joshine\Review\Block\Comment\Comment" />
</block>
<block name="joshine.review.comments.form" as="form" class="Amasty\AdvancedReview\Block\Comment\Form" />
</block>
</body>
</page>
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template"
template="Joshine_Review::review-js.phtml"
name="joshine.review.toolbar.js"
after="-"/>
</referenceContainer>
</body>
</page>
<?xml version="1.0"?>
<!--
/**
* @author Amasty Team
* @copyright Copyright (c) 2021 Amasty (https://www.amasty.com)
* @package Amasty_AdvancedReview
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<head>
<css src="Joshine_Review::css/_review.css"/>
</head>
</page>
\ No newline at end of file
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="product.info.product_additional_data" remove="true" />
<referenceBlock class="Magento\Theme\Block\Html\Pager" name="product_review_list.toolbar" remove="true"/>
<referenceContainer name="root">
<block class="Joshine\Review\Block\Review\Product\View\ListView"
name="Joshine.product.info.product_additional_data"
template="Joshine_Review::product/view/list.phtml"
before="product_review_list.toolbar">
<arguments>
<argument name="joshine-review-helper" xsi:type="object">Joshine\Review\Helper\BlockHelper</argument>
</arguments>
</block>
<block name="joshine.review.js" class="Joshine\Review\Block\JsInit" ifconfig="joshine_review/comments/enabled" />
</referenceContainer>
</body>
</page>
var config = {
paths: {
"joshineReviewHandler": "Joshine_Review/js/review/handler",
},
};
\ No newline at end of file
<?php
/**
* @var $block \Joshine\Review\Block\Components\Pager
*/
?>
<?php if ($block->getCollection()->getSize() && $block->getLastPageNum() > 1): ?>
<div class="joshine-reviews-page">
<div class="joshine-pagination joshine-pagination-left" joshine-review-js="reviews-pages-<?= $block->escapeHtml($block->getProductId()); ?>" >
<?php if (!$block->isFirstPage()): ?>
<span class="joshine-pagination-btn joshine-pagination-prev"
data-href="<?= $block->escapeUrl($block->getPreviousPageUrl()); ?>"
>
<
</span>
<?php endif; ?>
<?php if ($block->canShowFirst()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getFirstPageUrl()); ?>"
>
1
</span>
<?php endif; ?>
<?php if ($block->canShowPreviousJump()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getPreviousJumpUrl()); ?>"
>
...
</span>
<?php endif; ?>
<?php foreach ($block->getFramePages() as $page): ?>
<?php if ($block->isPageCurrent($page)): ?>
<span class="joshine-pagination-btn joshine-pagination-hover active">
<?= $block->escapeHtml($page); ?>
</span>
<?php else: ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getPageUrl($page)); ?>"
>
<?= $block->escapeHtml($page) ?>
</span>
<?php endif; ?>
<?php endforeach; ?>
<?php if ($block->canShowNextJump()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getNextJumpUrl()) ?>"
>
...
</span>
<?php endif; ?>
<?php if ($block->canShowLast()): ?>
<span class="joshine-pagination-btn joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getLastPageUrl()) ?>"
>
<?= $block->escapeHtml($block->getLastPageNum()) ?>
</span>
<?php endif; ?>
<?php if (!$block->isLastPage()): ?>
<span class="joshine-pagination-btn joshine-pagination-next joshine-pagination-hover"
data-href="<?= $block->escapeUrl($block->getNextPageUrl()) ?>">
>
</span>
<?php endif; ?>
</div>
</div>
<?php endif ?>
\ No newline at end of file
<?php
/** @var \Joshine\Review\Block\Images $block */
$collection = $block->getCollection();
?>
<?php if ($collection->getSize()): ?>
<?php foreach ($collection as $item): ?>
<div class="joshine-review-pic-item joshine-col-xs-4" data-img-src="<?= $block->getFullImagePath($item) ?>"
data-image-id="<?= $item->getId(); ?>"
data-product-id="<?= $block->getProductId() ?>"
data-reviews-id="<?= $block->getReviewId() ?>">
<img class="review-image rounded" src="<?= $block->getResizedImagePath($item) ?>"/>
</div>
<?php endforeach; ?>
<?php endif;?>
<?php
/** @var $block */
?>
<script type="text/x-magento-init">
{
"*": {
"Joshine_Review/js/review/js-loader": { }
}
}
</script>
<div class="joshine-review-filter joshine-clearfix">
<div class="joshine-review-tab joshine-review-tab-container">
<span class="tab active joshine-inline-block joshine-font-c-deepin review-js-with-image">All Reviews</span>
<span class="tab review-with-image joshine-inline-block review-js-with-image joshine-hidden">With Image</span>
</div>
<div class="joshine-review-select-wrap joshine-hidden">
<div class="joshine-review-select-item">
<label>Rating</label>
<select data-review-js="filter" name="rating" class="review-js-filter">
<option value="1">1 Star</option>
<option value="2">2 Star</option>
<option value="3">3 Star</option>
<option value="4">4 Star</option>
<option value="5">5 Star</option>
</select>
</div>
<div class="joshine-review-select-item">
<label>Verified Buyers</label>
<input type="checkbox" name="verified_buyers" class="checkbox review-js-filter">
</div>
<div class="joshine-review-select-item"><span>Sort by</span>
<label><?= /* @noEscape */ __('Sort By') ?></label>
<select name="sorter" class="review-js-sorter">
<option value="date">Date</option>
<option value="helpfulness">Helpfulness</option>
<option value="rating">Rating</option>
</select>
</div>
</div>
</div>
.joshine-review-container {
padding: 0;
}
.joshine-review-container .joshine-review-title {
font-size: 24px;
font-weight: 1000;
text-transform: capitalize;
}
.joshine-review-container .joshine-review-averate {
display: flex;
}
.joshine-review-container .joshine-review-item {
padding: 24px;
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-pack: center;
justify-content: center;
width: 33.33%;
}
.joshine-review-container .joshine-review-container .joshine-review-averate .add-new-box {
flex-grow:0;
flex-basis:auto;
font-size: 1.2em;
padding: .2em;
}
.joshine-review-container .joshine-review-container .joshine-review-averate .name {
margin-bottom: 15px;
}
.joshine-review-container .ave-rate {
align-items: center;
display: flex;
}
.joshine-review-container .ave-rate-total {
font-size: 22px;
font-weight: 700;
padding-left: 10px;
}
.joshine-review-container .joshine-review-item .fit-item .joshine-process {
background-color: #e4e4e4;
border-radius: 4px;
width: 190px;
}
.joshine-review-container .joshine-review-item .fit-item .joshine-process .joshine-process-active{
background-color: #222;
border-radius: 4px;
}
.joshine-review-container .joshine-review-item .fit-item {
display: flex;
align-items: center;
}
.joshine-review-container .joshine-review-item .fit-item .fit-name {
width: 80px;
}
.joshine-review-container .joshine-review-item.add-new-box .add-new-button {
font-weight: 400;
font-size: 22px;
}
.joshine-review-container .joshine-review-list {
padding: 20px 0;
}
.joshine-review-container .joshine-review-list-item {
display: flex;
position: relative;
width: 100%;
border-bottom: 1px dashed #e5e5e5;
padding: 25px 0;
}
.joshine-review-container .joshine-review-list-item-left {
width: 200px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-nickname {
width: 100%;
white-space: nowrap;
margin-bottom: 15px;
}
.joshine-review-container .joshine-review-list-item-mid {
width: calc(100% - 288px);
display: -webkit-box;
display: flex;
padding-right: 150px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-detail {
padding-right: 40px;
width: 50%;
}
.joshine-review-container .joshine-review-list-item .joshine-review-pics {
width: 50%;
display: flex;
}
.joshine-review-container .joshine-review-list-item .joshine-review-pic-item {
position: relative;
flex-shrink: 0;
width: 108px;
height: 108px;
line-height: 108px;
overflow: hidden;
margin-right: 5px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-des-title {
font-weight: 400;
margin-bottom: 5px;
}
.joshine-review-container .joshine-review-list-item .joshine-review-des {
font-size: 14px;
line-height: 1.4em;
color: #5b5b5b;
}
.joshine-review-container .joshine-review-list-item .joshine-review-date {
position: absolute;
bottom: 28px;
}
.joshine-review-container .joshine-review-list-item .rate-fit {
display: inline-flex;
-webkit-box-align: center;
align-items: center;
flex-wrap: wrap;
padding-top: 10px;
}
.joshine-review-container .joshine-review-list-item .rate-fit .rate-fit-item{
margin-bottom: 5px;
margin-right: 14px;
}
.joshine-review-container .joshine-review-helpful {
display: inline-block;
position: absolute;
right: 0;
}
.joshine-review-container .joshine-review-helpful .joshine-like{
display: inline-block;
}
/*遮罩蒙层*/
.joshine-mask {
top: 0;
right: 0;
bottom: 0;
left: 0;
position: fixed;
background: rgba(0, 0, 0, .8);
}
.joshine-review-container .joshine-review-tab-container {
margin-right: auto;
}
.joshine-review-container .joshine-review-tab {
font-size: 14px;
color: #666;
font-weight: 700;
position: relative;
display: inline-block;
}
.joshine-review-container .joshine-review-tab .tab {
padding: 25px 0;
cursor: pointer;
}
.joshine-review-container .joshine-review-tab .tab + .tab {
margin-left: 25px;
}
.joshine-review-container .joshine-review-filter {
border-bottom: 1px solid #e5e5e5;
display: flex;
align-items: center;
}
.joshine-review-container .joshine-review-select-wrap {
padding: 10px;
font-size: .9em;
}
.joshine-review-container .joshine-review-filter .joshine-review-select-item {
display: inline-block;
}
.joshine-review-container .joshine-review-filter .joshine-review-select-item + .joshine-review-select-item {
margin-left: 25px;
}
.joshine-review-container .joshine-review-select-wrap .joshine-review-select-box {
padding: 5px 0;
position: relative;
display: inline-block;
}
.joshine-review-container .joshine-review-select-wrap .joshine-review-select-box-input {
height: 34px;
line-height: 32px;
display: inline-block;
}
.joshine-review-container .joshine-review-tab .tab.active {
border-bottom: #222 solid 2px;
}
.joshine-review-container .joshine-reviews-page {
margin-top: 15px;
margin-bottom: 15px;
}
.joshine-review-container .joshine-review-select-wrap select {
width: auto;
padding: .5em 2.2em .5em .5em;
height: auto;
line-height: normal;
}
.joshine-review-pic-item img {
width: 100%;
height: 7.5em ;
object-fit: cover;
}
.joshine-review-container-mobile .joshine-review-pics {
display: flex;
flex-wrap: wrap;
}
.joshine-review-container-mobile .joshine-review-pic-item{
padding: .1em;
}
.joshine-review-container-mobile .joshine-review-item .fit-item .joshine-process {
background-color: #e4e4e4;
border-radius: 4px;
width: 190px;
}
.joshine-review-container-mobile .joshine-review-item .fit-item .joshine-process .joshine-process-active{
background-color: #222;
border-radius: 4px;
}
.modal-reviews-details__image-swiper .swiper-button-next {
cursor : url(static/frontend/Joshine/breeze/en_US/Joshine_Review/img/favicon-next.ico), auto;
}
.modal-reviews-details__image-swiper .swiper-button-prev {
cursor : url(static/frontend/Joshine/breeze/en_US/Joshine_Review/img/favicon-pre.ico), auto;
}
\ No newline at end of file
define([
'jquery',
'ko'
], function ($) {
'use strict';
$.widget('mage.joshineReviewSlider', {
_create: function () {
},
_binding : function () {
},
});
return $.mage.joshineReviewSlider
});
\ No newline at end of file
define(['jquery', 'Magento_Ui/js/modal/modal'], function ($) {
'use strict';
return {
handleReviewsImgClick : function (element, event) {
$('.joshine-model-warp').removeClass('joshine-hidden');
},
handlePage : function (element, event) {
let self = $(this);
if (element.hasClass('pagination-btn-disabled')) {
return;
}
element.siblings().removeClass('active');
element.addClass('active');
let pageLoad = {
loaderContext: $('#product-review-container'),
};
event.preventDefault();
$.ajax({
url: element.data('href'),
cache: true,
}).done(function (data) {
pageLoad.loaderContext.html(data);
});
},
}
});
\ No newline at end of file
define([
'jquery',
'ko',
'joshineReviewHandler'
], function ($, ko, handler) {
'use strict';
$.widget('mage.joshineReview', {
_create: function () {
var self = this;
self._binding();
},
_binding : function () {
$(document).on('click', '.joshine-pagination-btn', function(event){
handler.handlePage($(this), event);
});
},
});
return $.mage.joshineReview
});
define([
'jquery',
'ko',
'joshineReviewConfig'
], function ($, ko, config) {
'use strict';
return {
isApi : function (url) {
var apisArr = config.apis;
return apisArr.indexOf(url);
}
}
});
\ No newline at end of file
......@@ -13,11 +13,13 @@ define([
$(element).mage('validation', {
/** @inheritdoc */
errorPlacement: function (error, el) {
if (el.parents('#product-review-table').length) {
$('#product-review-table').siblings(this.errorElement + '.' + this.errorClass).remove();
$('#product-review-table').after(error);
} else {
} else if(el.parents('.options-list').length ) {
$('.options-list').siblings(this.errorElement + '.' + this.errorClass).remove();
$('.options-list').after(error);
} else {
el.after(error);
}
}
......
......@@ -7,7 +7,7 @@
/**
* Pager template
*
* @see \Magento\Theme\Block\Html\Pager
* @var \Magento\Theme\Block\Html\Pager $block
*/
?>
<?php if ($block->getCollection()->getSize()): ?>
......@@ -117,22 +117,6 @@
</div>
<?php endif; ?>
<?php if ($block->isShowPerPage()): ?>
<div class="limiter">
<strong class="limiter-label"><?= $block->escapeHtml(__('Show')) ?></strong>
<select id="limiter" data-mage-init='{"redirectUrl": {"event":"change"}}' class="limiter-options">
<?php foreach ($block->getAvailableLimit() as $_key => $_limit): ?>
<option value="<?= $block->escapeUrl($block->getLimitUrl($_key)) ?>"
<?php if ($block->isLimitCurrent($_key)): ?>
selected="selected"<?php endif ?>>
<?= $block->escapeHtml($_limit) ?>
</option>
<?php endforeach; ?>
</select>
<span class="limiter-text"><?= $block->escapeHtml(__('per page')) ?></span>
</div>
<?php endif ?>
<?php if ($block->getUseContainer()): ?>
</div>
<?php endif ?>
......
define([
'jquery',
'underscore',
'Rokanthemes_OnePageCheckout/js/model/url-builder',
'mage/storage',
'Magento_Checkout/js/model/error-processor',
'Magento_Checkout/js/model/shipping-service',
'Magento_Checkout/js/model/totals',
'Rokanthemes_OnePageCheckout/js/model/payment-service',
'mage/url',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/payment/method-converter',
'Magento_Checkout/js/model/payment-service',
'Rokanthemes_OnePageCheckout/js/model/update-item-service',
'Magento_Ui/js/model/messageList',
'Magento_Checkout/js/action/get-totals',
'Magento_Checkout/js/action/select-shipping-method',
'Magento_Checkout/js/checkout-data',
'Rokanthemes_OnePageCheckout/js/model/shipping-save-processor'
], function (
$,
_,
urlBuilder,
storage,
errorProcessor,
shippingService,
totals,
paymentService,
url,
quote,
methodConverter,
paymentServiceDefault,
updateItemService,
globalMessageList,
getTotalsAction,
selectShippingMethodAction,
checkoutData,
shippingSaveProcessor
) {
'use strict';
return function (item) {
var serviceUrl = urlBuilder.getUpdateQtyUrl(),
address = quote.shippingAddress();
shippingService.isLoading(true);
totals.isLoading(true);
paymentService.isLoading(true);
return storage.post(
serviceUrl,
JSON.stringify({
address: {
'region_id': address.regionId,
'region': address.region,
'country_id': address.countryId,
'postcode': address.postcode
},
itemId: parseInt(item.item_id),
qty: parseFloat(item.qty)
})
).done(function (response) {
if (response.has_error && response.status) {
globalMessageList.addSuccessMessage(response);
window.location.replace(url.build('checkout/cart/'));
} else {
if (response.status) {
globalMessageList.addSuccessMessage(response);
updateItemService.hasUpdateResult(true);
//shippingService.setShippingRates(response.shipping_methods);
paymentServiceDefault.setPaymentMethods(methodConverter(response.payment_methods));
var sh = response.shipping_methods;
var new_sh = [];
selectShippingMethodAction(sh[0]);
checkoutData.setSelectedShippingRate(sh[0]['carrier_code'] + '_' + sh[0]['method_code']);
quote.shippingMethod(sh[0]);
new_sh.push(sh[0]);
shippingService.setShippingRates(new_sh);
updateItemService.hasUpdateResult(false);
response.totals.coupon_code ? paymentService.isAppliedCoupon(true) : paymentService.isAppliedCoupon(false);
var deferred = $.Deferred();
getTotalsAction([], deferred);
$('.items-in-cart').find('[data-bind="text: getCartSummaryItemsCount()"]')
.text(response['totals']['items_qty']);
shippingSaveProcessor.saveShippingInformation(quote.shippingAddress().getType());
} else {
globalMessageList.addErrorMessage(response);
}
}
}).fail(function (response) {
errorProcessor.process(response);
}).always(function () {
shippingService.isLoading(false);
totals.isLoading(false);
paymentService.isLoading(false);
});
};
});
define([
'Magento_Checkout/js/model/resource-url-manager',
'Magento_Checkout/js/model/quote',
'mage/storage',
'Magento_Checkout/js/model/shipping-service',
'Magento_Checkout/js/model/shipping-rate-registry',
'Magento_Checkout/js/model/error-processor'
], function (resourceUrlManager, quote, storage, shippingService, rateRegistry, errorProcessor) {
'use strict';
return function (address) {
var serviceUrl, payload;
shippingService.isLoading(true);
serviceUrl = resourceUrlManager.getUrlForEstimationShippingMethodsForNewAddress(quote);
payload = JSON.stringify({
address: {
'street': address.street,
'city': address.city,
'region_id': address.regionId,
'region': address.region,
'country_id': address.countryId,
'postcode': address.postcode,
'email': address.email,
'customer_id': address.customerId,
'firstname': address.firstname,
'lastname': address.lastname,
'middlename': address.middlename,
'prefix': address.prefix,
'suffix': address.suffix,
'vat_id': address.vatId,
'company': address.company,
'telephone': address.telephone,
'fax': address.fax,
'custom_attributes': address.customAttributes,
'save_in_address_book': address.saveInAddressBook
}
}
);
return storage.post(
serviceUrl, payload, false
).done(function (result) {
var new_sh = [];
new_sh.push(result[0]);
rateRegistry.set(address.getCacheKey(), result);
shippingService.setShippingRates(new_sh);
}).fail(function (response) {
shippingService.setShippingRates([]);
errorProcessor.process(response);
}).always(function () {
shippingService.isLoading(false);
});
}
});
......@@ -4,7 +4,6 @@
<?php
$isModuleEnable = $block->getConfig('sparsh_free_shipping_bar/general/enable');
$isFreeShippingBarAvailable = null;
if ($isModuleEnable):
$layoutPosition = $block->getData('position') ? $block->getData('position') : null;
$entityId = $block->getData('entity_id') ? $block->getData('entity_id') : null;
......@@ -29,18 +28,15 @@ endif;
.sparsh-free-shipping-bar-goal-message{
width: 100%;
height: 60px;
background-image: url("<?= $block->getImgUrl($barData['background_img']) ?>");
line-height: 60px;
background-size: 100% 100%!important;
background-repeat: no-repeat;
text-align: center;
}
.sparsh-free-shipping-bar-goal-message-back-<?= $barData['entity_id']; ?>{
background-image: url('<?= $block->getImgUrl($barData['background_img']) ?>');
}
@media (max-width: 768px) {
.sparsh-free-shipping-bar-goal-message{
background-image: url("<?= $block->getImgUrl($barData['background_img_phone']) ?>");
width: 100%;
height: 40px;
line-height: 40px;
......@@ -48,17 +44,14 @@ endif;
background-repeat: no-repeat;
}
.sparsh-free-shipping-bar-goal-message-back-<?= $barData['entity_id']; ?>{
background-image: url('<?= $block->getImgUrl($barData['background_img_phone']) ?>');
}
}
</style>
<div class="sparsh-free-shipping-bar-goal-message sparsh-free-shipping-bar-goal-message-back-<?= $barData['entity_id']; ?>"
style=" background-color: <?= /* @noEscape */ $barData['bar_background_color']?>; font-size: <?= /* @noEscape */ $barData['bar_font_size'].'px'?>">
<div class="sparsh-free-shipping-bar-goal-message"
style=" background-size: 100%; text-align: center; background-color: <?= /* @noEscape */ $barData['bar_background_color']?>; font-size: <?= /* @noEscape */ $barData['bar_font_size'].'px'?>">
<a <?= /* @noEscape */ $barData['is_clickable'] ? 'href='.$barData['bar_link_url'] : null ?>
<?= /* @noEscape */ $barData['is_clickable'] ? ($barData['is_link_open_in_new_page'] ? 'target=_blank' : 'target=_self') : null ?>
style="width: 100%;height: 100%; display: block; color: <?= /* @noEscape */ $barData['bar_text_color']?>">
<div data-bind="scope: 'free-shipping-scope<?= $barData['entity_id']; ?>'">
<div data-bind="scope: 'free-shipping-scope'">
<p data-bind="html: goalMessage"></p>
</div>
</a>
......@@ -68,7 +61,7 @@ endif;
"*": {
"Magento_Ui/js/core/app": {
"components": {
"free-shipping-scope<?= $barData['entity_id']; ?>": {
"free-shipping-scope": {
"component": "Sparsh_FreeShippingBar/js/free_shipping_bar",
"goal": "<?= /* @noEscape */ $barData['goal'] ?>",
"currency": "<?= /* @noEscape */ $block->getCurrentCurrencySymbol() ?>",
......
......@@ -11,15 +11,18 @@
.field.size-fits .field.choice {
clear: both;
display: inline-block;
padding: 0 5px;
line-height: 30px;
margin-right: 20px;
}
/* 猫头鹰选择 */
.size-fits + .size-fits {
padding: 0 5px;
}
.field.size-fits .field.choice input {
margin-right: 5px;
}
</style>
<div class="block review-add">
<div class="block review-add joshine-bg-silver joshine-hidden">
<div class="block-title"><strong><?= $block->escapeHtml(__('Write Your Own Review')) ?></strong></div>
<div class="block-content">
<?php if ($block->getAllowWriteReviewFlag()):?>
......
......@@ -2225,6 +2225,8 @@ font-weight: bold;
.product.info .review-add {
float: left;
width: 100% !important;
padding: 24px;
margin-bottom: 20px;
}
@media (max-width: 768px){
......@@ -2701,7 +2703,7 @@ tr.grand.totals {
}
}
.choice.field {
.size-fits + .size-fits {
padding-left: 15px;
}
......@@ -2843,4 +2845,5 @@ strong#block-related-heading,strong#block-upsell-heading{ font-weight: 600; colo
}
/*导入Joshine工具类*/
@import "./_joshine_col";
@import "./_joshine_utils";
......@@ -733,9 +733,9 @@
}
.joshine-center-block {
display: block;
margin-right: auto;
margin-left: auto;
display: block !important;
margin-right: auto !important;
margin-left: auto !important;
}
.joshine-pull-right {
float: right !important;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment