<?php
namespace Customize\Repository\Extension;
use Eccube\Repository\ProductRepository;
use Eccube\Repository\QueryKey;
use Eccube\Entity\Product;
use Plugin\Maker42\Entity\Maker;
use Eccube\Util\StringUtil;
use Eccube\Entity\Master\ProductStatus;
use Eccube\Common\EccubeConfig;
use Eccube\Doctrine\Query\Queries;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
class ProductRepositoryExtension extends ProductRepository
{
/**
* @var CacheInterface
*/
private CacheInterface $cache;
/**
* カテゴリ別商品数のキャッシュ有効期間(秒)
*/
private const CATEGORY_COUNT_CACHE_LIFETIME = 3600;
public function __construct(
ManagerRegistry $registry,
Queries $queries,
EccubeConfig $eccubeConfig,
CacheInterface $cache
) {
parent::__construct($registry, $queries, $eccubeConfig);
$this->cache = $cache;
}
/**
* get query builder.
*
* @param array $searchData
*
* @return \Doctrine\ORM\QueryBuilder
*/
public function customizeGetQueryBuilderBySearchData($searchData)
{
$qb = $this->createQueryBuilder('p')
->andwhere('p.Status = :Disp')
->setParameter('Disp', ProductStatus::DISPLAY_SHOW);
// category
$categoryJoin = false;
if (!empty($searchData['category_id']) && $searchData['category_id']) {
$Categories = $searchData['category_id']->getSelfAndDescendants();
if ($Categories) {
$qb
->innerJoin('p.ProductCategories', 'pct')
->innerJoin('pct.Category', 'c')
->andWhere($qb->expr()->in('pct.Category', ':Categories'))
->setParameter('Categories', $Categories);
$categoryJoin = true;
}
}
//maker
$qb
->leftJoin('p.Maker', 'm');
if (!empty($searchData['maker_id']) && $searchData['maker_id']) {
$qb
->andWhere('m = :id')
->setParameter('id', $searchData['maker_id']);
}
//stock又は新商品のみ
if (empty($searchData['nostock']) || (!empty($searchData['new']) && $searchData['new'])) {
$qb
->leftJoin('p.ProductClasses', 'pcl');
}
//stock
if (empty($searchData['nostock'])) {
$qb
->andWhere('pcl.stock > 0');
}
//新商品のみ(2週間以内の発売日)
if (!empty($searchData['new'])) {
$today = date("Y-m-d");
//eccubeで登録される時間が日本時間の6時間前になる為、2週間+6時間前をの日付を取得
$two_week_ago = date("Y-m-d", strtotime("{$today} -2 week -6 hour"));
$qb
->andWhere(" '{$two_week_ago}' <= pcl.release_date and pcl.release_date < '{$today}' ");
}
//タグ検索 TOPページスライダー
if (!empty($searchData['tag_slider']) && $searchData['tag_slider']) {
$qb
->innerJoin('p.ProductTag', 'pt')
->innerJoin('pt.Tag', 'tag')
->andWhere("tag.name = '{$searchData['tag_slider']}'");
}
// name
if (isset($searchData['name']) && StringUtil::isNotBlank($searchData['name'])) {
//スペース、全角スペースで分割
$keywords = preg_split('/[\s ]+/u', str_replace(['%', '_'], ['\\%', '\\_'], $searchData['name']), -1, PREG_SPLIT_NO_EMPTY);
foreach ($keywords as $index => $keyword) {
$key = sprintf('keyword%s', $index);
$qb
->andWhere(sprintf(
'NORMALIZE(m.name) LIKE NORMALIZE(:%s) OR
REPLACE(REPLACE(NORMALIZE(p.name), \'゙\', \'\'), \'゚\', \'\') LIKE REPLACE(REPLACE(NORMALIZE(:%s), \'゙\', \'\'), \'゚\', \'\') OR
NORMALIZE(p.search_word) LIKE NORMALIZE(:%s) OR
NORMALIZE(p.description_detail) LIKE NORMALIZE(:%s) OR
NORMALIZE(p.free_area) LIKE NORMALIZE(:%s) OR
EXISTS (SELECT wpc%d FROM \Eccube\Entity\ProductClass wpc%d WHERE p = wpc%d.Product AND NORMALIZE(wpc%d.code) LIKE NORMALIZE(:%s))',
$key,
$key,
$key,
$key,
$key,
$index,
$index,
$index,
$index,
$key
))
->setParameter($key, '%' . $keyword . '%');
}
}
// Order By
// 価格低い順
$config = $this->eccubeConfig;
if (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_price_lower']) {
// @see http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html
$qb->addSelect('MIN(pc.price02) as HIDDEN price02_min');
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
$qb->groupBy('p.id');
$qb->orderBy('price02_min', 'ASC');
$qb->addOrderBy('p.id', 'ASC');
// 価格高い順
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_price_higher']) {
$qb->addSelect('MAX(pc.price02) as HIDDEN price02_max');
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
$qb->groupBy('p.id');
$qb->orderBy('price02_max', 'DESC');
$qb->addOrderBy('p.id', 'ASC');
// 新着順
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_newer']) {
// 在庫切れ商品非表示の設定が有効時対応
// @see https://github.com/EC-CUBE/ec-cube/issues/1998
if ($this->getEntityManager()->getFilters()->isEnabled('option_nostock_hidden') == true) {
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
}
$qb->orderBy('p.create_date', 'DESC');
$qb->addOrderBy('p.id', 'ASC');
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_best_seller']) {
//売れ筋順
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_japanese_asc']) {
// 五十音順(昇順)
if ($this->getEntityManager()->getFilters()->isEnabled('option_nostock_hidden') == true) {
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
}
$qb->addOrderBy('p.kana', 'ASC');
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_japanese_desc']) {
// 五十音順(降順)
if ($this->getEntityManager()->getFilters()->isEnabled('option_nostock_hidden') == true) {
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
}
$qb->addOrderBy('p.kana', 'DESC');
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_maker_part_number_asc']) {
// 品番順(昇順)
if ($this->getEntityManager()->getFilters()->isEnabled('option_nostock_hidden') == true) {
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
}
$qb->addOrderBy('p.makerPartNumber', 'ASC');
} elseif (!empty($searchData['orderby']) && $searchData['orderby']->getId() == $config['eccube_product_order_maker_part_number_desc']) {
// 品番順(降順)
if ($this->getEntityManager()->getFilters()->isEnabled('option_nostock_hidden') == true) {
$qb->innerJoin('p.ProductClasses', 'pc');
$qb->andWhere('pc.visible = true');
}
$qb->addOrderBy('p.makerPartNumber', 'DESC');
} else {
if ($categoryJoin === false) {
$qb
->leftJoin('p.ProductCategories', 'pct')
->leftJoin('pct.Category', 'c');
}
$qb
->addOrderBy('p.id', 'ASC');
}
return $this->queries->customize(QueryKey::PRODUCT_SEARCH, $qb, $searchData);
}
public function findProductsWithCategoriesCount()
{
return $this->cache->get('categories_product_count', function (ItemInterface $item) {
$item->expiresAfter(self::CATEGORY_COUNT_CACHE_LIFETIME);
$qb = $this->createQueryBuilder('p');
$result = $qb
->select('c.id,COUNT(p.id) as cnt')
->where('p.Status = :Disp')
->setParameter('Disp', ProductStatus::DISPLAY_SHOW)
->innerJoin('p.ProductCategories', 'pct')
->innerJoin('pct.Category', 'c')
->groupBy('c.id')
->getQuery()
->getResult();
$counts = [];
foreach ($result as $r) {
$counts[$r['id']] = $r['cnt'];
}
return $counts;
});
}
/**
* カテゴリ別の商品数を取得(在庫切れ商品を除外)
*
* @return array カテゴリID => 商品数 の連想配列
*/
public function findProductsWithCategoriesCountInStock()
{
return $this->cache->get('categories_product_count_in_stock', function (ItemInterface $item) {
$item->expiresAfter(self::CATEGORY_COUNT_CACHE_LIFETIME);
$qb = $this->createQueryBuilder('p');
$result = $qb
->select('c.id, COUNT(DISTINCT p.id) as cnt')
->where('p.Status = :Disp')
->setParameter('Disp', ProductStatus::DISPLAY_SHOW)
->innerJoin('p.ProductCategories', 'pct')
->innerJoin('pct.Category', 'c')
->innerJoin('p.ProductClasses', 'pc')
->andWhere('pc.stock > 0')
->groupBy('c.id')
->getQuery()
->getResult();
$counts = [];
foreach ($result as $r) {
$counts[$r['id']] = $r['cnt'];
}
return $counts;
});
}
public function findProductsWithMakersCount($maker_id)
{
$qb = $this->createQueryBuilder('p');
// maker
if ($maker_id) {
return $qb
->where('p.Status = :Disp')
->setParameter('Disp', ProductStatus::DISPLAY_SHOW) #公開になっているもの
->leftJoin('p.Maker', 'm')
->select('COUNT(m.id)')
->andWhere('m.id =' . $maker_id)
->getQuery() #作成したクエリを取得
->getSingleScalarResult(); #実行 getSingleScalarResultはcountした時、数値が取得できます。
}
}
public function findProductsStockCount($id)
{
$qb = $this->createQueryBuilder('p');
if ($id) {
return $qb
->where('p.Status = :Disp')
->setParameter('Disp', ProductStatus::DISPLAY_SHOW) #公開になっているもの
->leftJoin('p.ProductClasses', 'pc')
->select('pc.stock')
->andWhere('p.id =' . $id)
->getQuery() #作成したクエリを取得
->getSingleScalarResult(); #実行 getSingleScalarResultはcountした時、数値が取得できます。
}
}
/**
* Find the Products with sorted ClassCategories.
*
* @param array $ids Product in ids
* @param string $indexBy The index for the from.
*
* @return ArrayCollection|array
*/
public function findProductsWithSortedClassCategories(array $ids, $indexBy = null)
{
if (count($ids) < 1) {
return [];
}
$qb = $this->createQueryBuilder('p', $indexBy);
$qb->addSelect(['pc', 'cc1', 'cc2', 'pi', 'pt', 'tr', 'ps'])
->innerJoin('p.ProductClasses', 'pc')
// XXX Joined 'TaxRule' and 'ProductStock' to prevent lazy loading
->leftJoin('pc.TaxRule', 'tr')
->leftJoin('pc.ProductStock', 'ps')
->leftJoin('pc.ClassCategory1', 'cc1')
->leftJoin('pc.ClassCategory2', 'cc2')
->leftJoin('p.ProductImage', 'pi')
->leftJoin('p.ProductTag', 'pt')
->where($qb->expr()->in('p.id', $ids))
->andWhere('pc.visible = :visible')
->setParameter('visible', true)
->orderBy('cc1.sort_no', 'DESC')
->addOrderBy('cc2.sort_no', 'DESC');
$products = $qb
->getQuery()
->useResultCache(true, $this->eccubeConfig['eccube_result_cache_lifetime_short'])
->getResult();
return $products;
}
}