Commit dfbcb234 by lmf

增加评论模块

parent a5258e68
<?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->create('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>
<?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
<style>
#we-accept{
text-align: center;
background: #ffffff;
padding: 10px 20px;
}
#we-accept .title{
font-size: 24px;
font-weight: bold;
margin-bottom: 10px;
}
</style>
<div id="we-accept">
<div class="title">We Accept</div>
<img src="/media/wysiwyg/we_accept.png" alt="">
</div>
\ No newline at end of file
define([
'jquery',
'uiComponent',
'Magento_Checkout/js/model/checkout-data-resolver'
], function ($, Component,checkoutDataResolver) {
'use strict';
var input_city = $('#co-shipping-form input[name="city"]');
if (input_city.is(':visible') && !input_city.closest('.field').hasClass('_required')) {
input_city.attr('required',true);
input_city.attr('_required',true);
}
return Component.extend({
initialize: function () {
this.initStep();
this._super();
return this;
},
initStep: function () {
$(document).on('click', '#next-shipping>span', function () {
var loginForm = $('.form.form-login');
var addressForm = $('#checkout-step-shipping>#co-shipping-form');
if (loginForm.length > 0 && !loginForm.validate().form()) {
return false;
}
if (addressForm.length > 0) {
if (!$('#co-shipping-form input[name="region"]').closest('.field').hasClass('_required')) {
$('#co-shipping-form input[name="region"]').removeAttr('required');
}
if (!$('#co-shipping-form input[name="street[1]"]').closest('.field').hasClass('_required')) {
$('#co-shipping-form input[name="street[1]"]').removeAttr('required');
}
if (!$('#co-shipping-form input[name="company"]').closest('.field').hasClass('_required')) {
$('#co-shipping-form input[name="company"]').removeAttr('required');
}
var region_select = $('#co-shipping-form select[name="region_id"]');
if (region_select.is(':visible') && region_select.closest('.field').hasClass('_required')) {
region_select.attr('required',true);
}
if (!addressForm.validate().form()) {
return false;
}
var email = $('#customer-email').val() || '';
var firstname = $('#co-shipping-form input[name="firstname"]').val();
var lastname = $('#co-shipping-form input[name="lastname"]').val();
var street = $('#co-shipping-form input[name="street[0]"]').val();
var city = $('#co-shipping-form input[name="city"]').val();
var postcode = $('#co-shipping-form input[name="postcode"]').val();
var telephone = $('#co-shipping-form input[name="telephone"]').val();
var country = $('#co-shipping-form select[name="country_id"]').find(":selected").text();
var regin;
if ($('#co-shipping-form input[name="region"]').is(':visible')) {
regin = $('#co-shipping-form input[name="region"]').val();
} else {
regin = region_select.find(":selected").text();
}
$('#checkout-step-shipping').hide();
$('#shipping-text').show();
$('#shipping-text .main-address.shipping-address').html(firstname + ' ' + lastname + '<br>' + street + '<br>' + city + ', ' + regin + ', ' + country + '<br>' + postcode + ', ' + telephone + '<br>' + email);
}
$('#next-shipping').hide();
$('#checkout-step-shipping_method').show();
$('#checkout-payment-method-load').show();
$('#checkout-step-payment,#opc-shipping_method').find('.step-title').removeClass('no-border');
if ($(window).width() < 768) {
var scrollTop = $('#opc-shipping_method').offset().top - 100;
$('html,body').animate({scrollTop: scrollTop}, 'fast');
}
checkoutDataResolver.resolveEstimationAddress();
});
$(document).on('click', '#shipping-text .address-edit', function () {
$('#shipping-text .main-address.shipping-address').html('');
$('#shipping-text').hide();
$('#checkout-step-shipping').show();
$('#next-shipping').show();
$('#checkout-step-shipping_method').hide();
$('#checkout-payment-method-load').hide();
$('#checkout-step-payment,#opc-shipping_method').find('.step-title').addClass('no-border');
});
}
});
}
);
<style>
.billing-box{
width: 50%;
padding: 20px 22px;
border: 1px solid #d6d6d6;
font-size: 14px;
min-height: 200px;
position: relative;
box-sizing: border-box;
}
.billing-address-box{
padding-bottom: 1rem;
border-bottom: 1px dashed #ddd;
}
.billing-handle .action-edit-address {
border: none;
background: 0 0;
position: absolute;
bottom: 0;
right: 10px;
outline: none;
text-align: center;
box-sizing: border-box;
}
.billing-handle .action-edit-address svg {
width: 12px;
height: 12px;
fill: #666;
}
.billing-address-details {
padding: 0.5rem 0px 0.5rem 1.5rem;
}
@media (max-width: 1023px){
.action-update-cancel{
height: 120px;
}
.action-update,.action-cancel{
margin-bottom: 13%;
margin-right: 20%;
}
}
@media (min-width: 768px) {
.billing-box:before {
width: 5px;
height: 100%;
display: block;
content: "";
position: absolute;
left: 0;
top: 0;
background-size: contain;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAACECAYAAABVnZHfAAAA+0lEQVQ4je3UsUrDUBTG8X9s6yZOeYMOLejgS7iIUDCrS0uH0oI+QUGdHZSsjnZw6dB3ENK1U8eCQqBjoWvkaE/4SpHgYjv0bj9yc5J7v3tukN5GGT9jcICMreFDEQFLRwK0gCyIXtLvKfGkd5aDwr+uKkbAsaMGvAElf+cc6GuB6m7szo4hU9w5ysA9UNdpTWDsS7BMn3U9mZ3EtdIlxZOiC3T8O6yeTr1axU7JPp8iJIor7zvDJ9CwjLTNwj+02aEiVrSBGyS5R03OEr/e51OEd4UFNXPMgUtgkTdT7zQONzrrd1woXoETh92UQyD0AnaXPmi1o3/ZA+ALpYA1JaK/rAEAAAAASUVORK5CYII=) repeat-y;
}
}
@media (max-width: 768px){
.billing-box{
width: 100%;
}
.billing-address-details {
padding: 0;
}
.billing-box:before{
width: 100%;
height: 5px;
bottom: 0;
left:0;
display: block;
content: "";
position: absolute;
background-size: contain;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAu4AAAAGCAYAAACclRsMAAABWklEQVR4nO3aMUrEQBiG4VfxAnoHwWZ7e1GsrAQ9gkIKDxPQI2hrJSvb29sIXsBKLyAoWbbLDCZuZvlD3qfaIgn59vsnTDFbHzfnP5TxBVxWs3re8em7wD1wUuh9SlnmBOb1azWJnD3uGXVWZ7dlKrM7+pzVrO56j7Mb26Rm129ui/uF+DY+uzuF/pJ34Kya1W8dr98HHoGDQu9TyjInYM62UWd1dlvMGdvk1qidtvjNjc25zXN2e9geMsHKAjjsUdoR8DLCwsyZZ6exmTPNNRqfnaaZMzZz5tlpT0Nv3O+AU+Cz4/VXwBOwN/B7lGbOPDuNzZxprtH47DTNnLGZM89O/2Gojfs30BzYul79/ktzRKc5hHlb8LhOCebMs9PYzJnmGo3PTtPMGZs58+x0DUM8qDmYfwE8d7y+OZj/ABwPFWJDzJlnp7GZM801Gp+dppkzNnPm2ek6gF8O5wurSKfLHQAAAABJRU5ErkJggg==) repeat-x;
}
}
</style>
<div class="checkout-billing-address cclt">
<div class="billing-address-same-as-shipping-block field choice" data-bind="visible: canUseShippingAddress()">
<input type="checkbox" name="billing-address-same-as-shipping"
data-bind="checked: isAddressSameAsShipping, click: useShippingAddress, attr: {id: 'billing-address-same-as-shipping-' + getCode($parent)}"/>
<label data-bind="attr: {for: 'billing-address-same-as-shipping-' + getCode($parent)}"><span
data-bind="i18n: 'My billing and shipping address are the same'"></span></label>
</div>
<!-- ko ifnot: isAddressSameAsShipping -->
<!-- ko template: 'Magento_Checkout/billing-address/details' --><!-- /ko -->
<!-- /ko -->
<fieldset class="fieldset" data-bind="visible: !isAddressDetailsVisible()">
<!-- ko template: 'Magento_Checkout/billing-address/list' --><!-- /ko -->
<!-- ko template: 'Magento_Checkout/billing-address/form' --><!-- /ko -->
<div class="actions-toolbar action-update-cancel" style="margin-left: 0%;">
<div class="primary">
<button class="action action-update" type="button" data-bind="click: updateAddress">
<span data-bind="i18n: 'Update'"></span>
</button>
<button class="action action-cancel" type="button" data-bind="click: cancelAddressEdit">
<span data-bind="i18n: 'Cancel'"></span>
</button>
</div>
</div>
</fieldset>
</div>
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
/**
* Product view template
*
* @var $block \Magento\Catalog\Block\Product\View
*/
?>
<?php $_helper = $this->helper(Magento\Catalog\Helper\Output::class); ?>
<?php $_product = $block->getProduct(); ?>
<?php echo $block->getLayout()->createBlock('Magento\Cms\Block\Block')->setBlockId('product_info_addtocar_top_slogan')->toHtml();?>
<div class="product-add-form">
<form data-product-sku="<?= $block->escapeHtml($_product->getSku()) ?>"
action="<?= $block->escapeUrl($block->getSubmitUrl($_product)) ?>" method="post"
id="product_addtocart_form"<?php if ($_product->getOptions()) :?> enctype="multipart/form-data"<?php endif; ?>>
<input type="hidden" name="product" value="<?= (int)$_product->getId() ?>" />
<input type="hidden" name="selected_configurable_option" value="" />
<input type="hidden" name="related_product" id="related-products-field" value="" />
<input type="hidden" name="item" value="<?= (int)$block->getRequest()->getParam('id') ?>" />
<?= $block->getBlockHtml('formkey') ?>
<?= $block->getChildHtml('form_top') ?>
<?php if (!$block->hasOptions()) :?>
<?= $block->getChildHtml('product_info_form_content') ?>
<?php else :?>
<?php if ($_product->isSaleable() && $block->getOptionsContainer() == 'container1') :?>
<?= $block->getChildChildHtml('options_container') ?>
<?php endif;?>
<?php endif; ?>
<?php if ($_product->isSaleable() && $block->hasOptions() && $block->getOptionsContainer() == 'container2') :?>
<?= $block->getChildChildHtml('options_container') ?>
<?php endif;?>
<?= $block->getChildHtml('form_bottom') ?>
</form>
</div>
<script type="text/x-magento-init">
{
"[data-role=priceBox][data-price-box=product-id-<?= $block->escapeHtml($_product->getId()) ?>]": {
"priceBox": {
"priceConfig": <?= /* @noEscape */ $block->getJsonConfig() ?>
}
}
}
</script>
/**
* Created by Renk on 2017/4/19.
*/
var config = {
deps: [
"jquery"
],
paths: {
'jquery.owlCarousel': "OwlCarousel2/dist/owl.carousel.min",
},
shim: {
'jquery.owlCarousel': {
deps: ["jquery"],
exports: 'jQuery.fn.owlCarousel'
}
}
};
\ No newline at end of file
Copyright (c) 2014 Owl
Modified work Copyright 2016 David Deutsch
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
# OwlCarousel2 is currently being transferred to a new owner
Stay tuned while the new owner sorts through some stuff. (Oh, hi, I'm [David](https://github.com/daviddeutsch)!)
## Owl Carousel 2
Touch enabled [jQuery](https://jquery.com/) plugin that lets you create a beautiful, responsive carousel slider. **To get started, check out https://owlcarousel2.github.io/OwlCarousel2/.**
## Quick start
### Install
This package can be installed with:
- [npm](https://www.npmjs.com/package/owl.carousel): `npm install --save owl.carousel`
- [bower](http://bower.io/search/?q=owl.carousel): `bower install --save owl.carousel`
Or download the [latest release](https://github.com/OwlCarousel2/OwlCarousel2/releases).
### Load
#### Webpack
Load the required stylesheet and JS:
```js
import 'owl.carousel/dist/assets/owl.carousel.css';
import $ from 'jquery';
import 'imports?jQuery=jquery!owl.carousel';
```
#### Static HTML
Put the required stylesheet at the [top](https://developer.yahoo.com/performance/rules.html#css_top) of your markup:
```html
<link rel="stylesheet" href="/node_modules/owl.carousel/dist/assets/owl.carousel.min.css" />
```
```html
<link rel="stylesheet" href="/bower_components/owl.carousel/dist/assets/owl.carousel.min.css" />
```
**NOTE:** If you want to use the default navigation styles, you will also need to include `owl.theme.default.css`.
Put the script at the [bottom](https://developer.yahoo.com/performance/rules.html#js_bottom) of your markup right after jQuery:
```html
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script src="/node_modules/owl.carousel/dist/owl.carousel.min.js"></script>
```
```html
<script src="/bower_components/jquery/dist/jquery.js"></script>
<script src="/bower_components/owl.carousel/dist/owl.carousel.min.js"></script>
```
### Usage
Wrap your items (`div`, `a`, `img`, `span`, `li` etc.) with a container element (`div`, `ul` etc.). Only the class `owl-carousel` is mandatory to apply proper styles:
```html
<div class="owl-carousel owl-theme">
<div> Your Content </div>
<div> Your Content </div>
<div> Your Content </div>
<div> Your Content </div>
<div> Your Content </div>
<div> Your Content </div>
<div> Your Content </div>
</div>
```
**NOTE:** The `owl-theme` class is optional, but without it, you will need to style navigation features on your own.
Call the [plugin](https://learn.jquery.com/plugins/) function and your carousel is ready.
```javascript
$(document).ready(function(){
$('.owl-carousel').owlCarousel();
});
```
## Documentation
The documentation, included in this repo in the root directory, is built with [Assemble](http://assemble.io/) and publicly available at https://owlcarousel2.github.io/OwlCarousel2/. The documentation may also be run locally.
## Building
This package comes with [Grunt](http://gruntjs.com/) and [Bower](http://bower.io/). The following tasks are available:
* `default` compiles the CSS and JS into `/dist` and builds the doc.
* `dist` compiles the CSS and JS into `/dist` only.
* `watch` watches source files and builds them automatically whenever you save.
* `test` runs [JSHint](http://www.jshint.com/) and [QUnit](http://qunitjs.com/) tests headlessly in [PhantomJS](http://phantomjs.org/).
To define which plugins are build into the distribution just edit `/_config.json` to fit your needs.
## Contributing
Please read [CONTRIBUTING.md](CONTRIBUTING.md).
## License
The code and the documentation are released under the [MIT License](LICENSE).
/**
* Owl Carousel v2.2.1
* Copyright 2013-2017 David Deutsch
* Licensed under ()
*/
/*
* Owl Carousel - Core
*/
.owl-carousel {
display: none;
width: 100%;
-webkit-tap-highlight-color: transparent;
/* position relative and z-index fix webkit rendering fonts issue */
position: relative;
z-index: 1; }
.owl-carousel .owl-stage {
position: relative;
-ms-touch-action: pan-Y;
-moz-backface-visibility: hidden;
/* fix firefox animation glitch */ }
.owl-carousel .owl-stage:after {
content: ".";
display: block;
clear: both;
visibility: hidden;
line-height: 0;
height: 0; }
.owl-carousel .owl-stage-outer {
position: relative;
overflow: hidden;
/* fix for flashing background */
-webkit-transform: translate3d(0px, 0px, 0px); }
.owl-carousel .owl-wrapper,
.owl-carousel .owl-item {
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-ms-backface-visibility: hidden;
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0); }
.owl-carousel .owl-item {
position: relative;
min-height: 1px;
float: left;
-webkit-backface-visibility: hidden;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none; }
.owl-carousel .owl-item img {
display: block;
width: 100%; }
.owl-carousel .owl-nav.disabled,
.owl-carousel .owl-dots.disabled {
display: none; }
.owl-carousel .owl-nav .owl-prev,
.owl-carousel .owl-nav .owl-next,
.owl-carousel .owl-dot {
cursor: pointer;
cursor: hand;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
.owl-carousel.owl-loaded {
display: block; }
.owl-carousel.owl-loading {
opacity: 0;
display: block; }
.owl-carousel.owl-hidden {
opacity: 0; }
.owl-carousel.owl-refresh .owl-item {
visibility: hidden; }
.owl-carousel.owl-drag .owl-item {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
.owl-carousel.owl-grab {
cursor: move;
cursor: grab; }
.owl-carousel.owl-rtl {
direction: rtl; }
.owl-carousel.owl-rtl .owl-item {
float: right; }
/* No Js */
.no-js .owl-carousel {
display: block; }
/*
* Owl Carousel - Animate Plugin
*/
.owl-carousel .animated {
animation-duration: 1000ms;
animation-fill-mode: both; }
.owl-carousel .owl-animated-in {
z-index: 0; }
.owl-carousel .owl-animated-out {
z-index: 1; }
.owl-carousel .fadeOut {
animation-name: fadeOut; }
@keyframes fadeOut {
0% {
opacity: 1; }
100% {
opacity: 0; } }
/*
* Owl Carousel - Auto Height Plugin
*/
.owl-height {
transition: height 500ms ease-in-out; }
/*
* Owl Carousel - Lazy Load Plugin
*/
.owl-carousel .owl-item .owl-lazy {
opacity: 0;
transition: opacity 400ms ease; }
.owl-carousel .owl-item img.owl-lazy {
transform-style: preserve-3d; }
/*
* Owl Carousel - Video Plugin
*/
.owl-carousel .owl-video-wrapper {
position: relative;
height: 100%;
background: #000; }
.owl-carousel .owl-video-play-icon {
position: absolute;
height: 80px;
width: 80px;
left: 50%;
top: 50%;
margin-left: -40px;
margin-top: -40px;
background: url("owl.video.play.png") no-repeat;
cursor: pointer;
z-index: 1;
-webkit-backface-visibility: hidden;
transition: transform 100ms ease; }
.owl-carousel .owl-video-play-icon:hover {
-ms-transform: scale(1.3, 1.3);
transform: scale(1.3, 1.3); }
.owl-carousel .owl-video-playing .owl-video-tn,
.owl-carousel .owl-video-playing .owl-video-play-icon {
display: none; }
.owl-carousel .owl-video-tn {
opacity: 0;
height: 100%;
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
transition: opacity 400ms ease; }
.owl-carousel .owl-video-frame {
position: relative;
z-index: 1;
height: 100%;
width: 100%; }
/**
* Owl Carousel v2.2.1
* Copyright 2013-2017 David Deutsch
* Licensed under ()
*/
.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;position:relative}.owl-carousel{display:none;width:100%;z-index:1}.owl-carousel .owl-stage{position:relative;-ms-touch-action:pan-Y;-moz-backface-visibility:hidden}.owl-carousel .owl-stage:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}.owl-carousel .owl-stage-outer{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0)}.owl-carousel .owl-item,.owl-carousel .owl-wrapper{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}.owl-carousel .owl-item{min-height:1px;float:left;-webkit-backface-visibility:hidden;-webkit-touch-callout:none}.owl-carousel .owl-item img{display:block;width:100%}.owl-carousel .owl-dots.disabled,.owl-carousel .owl-nav.disabled{display:none}.no-js .owl-carousel,.owl-carousel.owl-loaded{display:block}.owl-carousel .owl-dot,.owl-carousel .owl-nav .owl-next,.owl-carousel .owl-nav .owl-prev{cursor:pointer;cursor:hand;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-loading{opacity:0;display:block}.owl-carousel.owl-hidden{opacity:0}.owl-carousel.owl-refresh .owl-item{visibility:hidden}.owl-carousel.owl-drag .owl-item{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-grab{cursor:move;cursor:grab}.owl-carousel.owl-rtl{direction:rtl}.owl-carousel.owl-rtl .owl-item{float:right}.owl-carousel .animated{animation-duration:1s;animation-fill-mode:both}.owl-carousel .owl-animated-in{z-index:0}.owl-carousel .owl-animated-out{z-index:1}.owl-carousel .fadeOut{animation-name:fadeOut}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.owl-height{transition:height .5s ease-in-out}.owl-carousel .owl-item .owl-lazy{opacity:0;transition:opacity .4s ease}.owl-carousel .owl-item img.owl-lazy{transform-style:preserve-3d}.owl-carousel .owl-video-wrapper{position:relative;height:100%;background:#000}.owl-carousel .owl-video-play-icon{position:absolute;height:80px;width:80px;left:50%;top:50%;margin-left:-40px;margin-top:-40px;background:url(owl.video.play.png) no-repeat;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;transition:transform .1s ease}.owl-carousel .owl-video-play-icon:hover{-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.owl-carousel .owl-video-playing .owl-video-play-icon,.owl-carousel .owl-video-playing .owl-video-tn{display:none}.owl-carousel .owl-video-tn{opacity:0;height:100%;background-position:center center;background-repeat:no-repeat;background-size:contain;transition:opacity .4s ease}.owl-carousel .owl-video-frame{position:relative;z-index:1;height:100%;width:100%}
\ No newline at end of file
/*
*
*/
.owl-carousel.owl-theme.owl-loaded .item {
width: auto!important;
}
.owl-carousel.owl-theme .owl-prev.disabled svg,.owl-carousel.owl-theme .owl-next.disabled svg{
fill: #999999;
}
.owl-carousel.owl-theme .owl-nav .owl-prev{
padding: 10px;
box-sizing: content-box;
position: absolute;
z-index: 10;
cursor: pointer;
position: absolute;
left: 0;
}
.owl-carousel.owl-theme .owl-nav .owl-next{
padding: 10px;
box-sizing: content-box;
position: absolute;
z-index: 10;
cursor: pointer;
position: absolute;
right:0;
}
@media (min-width: 768px) {
.owl-carousel.owl-theme {
margin:0 auto;
padding:0px 50px;
}
.owl-carousel.owl-theme .item-action{
line-height: 33px;
width: 50%;
margin: 0 auto 20px;
background: #fff;
border: 1px solid #000;
border-radius: 3px;
}
.owl-carousel.owl-theme .item {
width: 196px;
margin-bottom: 20px;
text-align: center;
box-sizing: border-box;
border: 1px solid #fff;
}
.owl-carousel.owl-theme .item .product-item-photo {
display: block;
position: relative;
}
.owl-carousel.owl-theme .owl-nav *{
top: 30%;
}
}
@media (max-width: 768px) {
.owl-carousel.owl-theme .owl-nav *{
top: 25%;
}
.owl-carousel.owl-theme .owl-nav .owl-prev{
padding:10px 10px 10px 0px;
}
.owl-carousel.owl-theme .owl-nav .owl-next{
padding:10px 0px 10px 10px;
}
.owl-carousel.owl-theme .owl-prev svg,.owl-carousel.owl-theme .owl-next svg{
width:16px;
height: 16px;
fill: #666666;
}
}
\ No newline at end of file
/**
* Owl Carousel v2.2.1
* Copyright 2013-2017 David Deutsch
* Licensed under ()
*/
/*
* Default theme - Owl Carousel CSS File
*/
.owl-theme .owl-nav {
margin-top: 10px;
text-align: center;
-webkit-tap-highlight-color: transparent; }
.owl-theme .owl-nav [class*='owl-'] {
color: #FFF;
font-size: 14px;
margin: 5px;
padding: 4px 7px;
background: #D6D6D6;
display: inline-block;
cursor: pointer;
border-radius: 3px; }
.owl-theme .owl-nav [class*='owl-']:hover {
background: #869791;
color: #FFF;
text-decoration: none; }
.owl-theme .owl-nav .disabled {
opacity: 0.5;
cursor: default; }
.owl-theme .owl-nav.disabled + .owl-dots {
margin-top: 10px; }
.owl-theme .owl-dots {
text-align: center;
-webkit-tap-highlight-color: transparent; }
.owl-theme .owl-dots .owl-dot {
display: inline-block;
zoom: 1;
*display: inline; }
.owl-theme .owl-dots .owl-dot span {
width: 10px;
height: 10px;
margin: 5px 7px;
background: #D6D6D6;
display: block;
-webkit-backface-visibility: visible;
transition: opacity 200ms ease;
border-radius: 30px; }
.owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span {
background: #869791; }
/**
* Owl Carousel v2.2.1
* Copyright 2013-2017 David Deutsch
* Licensed under ()
*/
.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791}
\ No newline at end of file
/**
* Owl Carousel v2.2.1
* Copyright 2013-2017 David Deutsch
* Licensed under ()
*/
/*
* Green theme - Owl Carousel CSS File
*/
.owl-theme .owl-nav {
margin-top: 10px;
text-align: center;
-webkit-tap-highlight-color: transparent; }
.owl-theme .owl-nav [class*='owl-'] {
color: #FFF;
font-size: 14px;
margin: 5px;
padding: 4px 7px;
background: #D6D6D6;
display: inline-block;
cursor: pointer;
border-radius: 3px; }
.owl-theme .owl-nav [class*='owl-']:hover {
background: #4DC7A0;
color: #FFF;
text-decoration: none; }
.owl-theme .owl-nav .disabled {
opacity: 0.5;
cursor: default; }
.owl-theme .owl-nav.disabled + .owl-dots {
margin-top: 10px; }
.owl-theme .owl-dots {
text-align: center;
-webkit-tap-highlight-color: transparent; }
.owl-theme .owl-dots .owl-dot {
display: inline-block;
zoom: 1;
*display: inline; }
.owl-theme .owl-dots .owl-dot span {
width: 10px;
height: 10px;
margin: 5px 7px;
background: #D6D6D6;
display: block;
-webkit-backface-visibility: visible;
transition: opacity 200ms ease;
border-radius: 30px; }
.owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span {
background: #4DC7A0; }
/**
* Owl Carousel v2.2.1
* Copyright 2013-2017 David Deutsch
* Licensed under ()
*/
.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#4DC7A0;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#4DC7A0}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd">
<suite name="RemoteStorageAwsS3EnabledPageBuilderSuite">
<before>
<magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.enable_options}}" stepKey="enableRemoteStorage"/>
<magentoCLI command="config:set cms/pagebuilder/enabled 1" stepKey="enablePageBuilder"/>
<magentoCLI command="config:set cms/wysiwyg/enabled enabled" stepKey="enableWYSIWYG"/>
<actionGroup ref="CliEnableTinyMCEActionGroup" stepKey="enableTinyMCE" >
<argument name="TinyMCEValue" value="{{EnableTinyMCE.value}}"/>
</actionGroup>
<magentoCLI command="config:set cms/pagebuilder/google_maps_api_key ''" stepKey="setEmptyGoogleMapsAPIKey"/>
<magentoCLI command="config:set web/default_layouts/default_cms_layout cms-full-width" stepKey="setPageBuilderDefaultCmsLayout"/>
<magentoCLI command="config:set web/default_layouts/default_category_layout category-full-width" stepKey="setPageBuilderDefaultCategoryLayout"/>
<magentoCLI command="config:set web/default_layouts/default_product_layout product-full-width" stepKey="setPageBuilderDefaultProductLayout"/>
<magentoCLI command="cache:clean config" stepKey="flushCache"/>
</before>
<after>
<magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.disable_options}}" stepKey="disableRemoteStorage"/>
<magentoCLI command="config:set cms/pagebuilder/enabled 1" stepKey="enablePageBuilder"/>
<actionGroup ref="CliEnableTinyMCEActionGroup" stepKey="enableTinyMCE" >
<argument name="TinyMCEValue" value="{{EnableTinyMCE.value}}"/>
</actionGroup>
<magentoCLI command="config:set cms/wysiwyg/enabled disabled" stepKey="disableWYSIWYG"/>
<magentoCLI command="config:set cms/pagebuilder/google_maps_api_key ''" stepKey="setEmptyGoogleMapsAPIKey"/>
<magentoCLI command="cache:clean config" stepKey="flushCache"/>
</after>
<include>
<group name="remote_storage_aws_s3_pagebuilder"/>
</include>
</suite>
</suites>
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