<?php
namespace Eccube\Repository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Persistence\ManagerRegistry as RegistryInterface;
use Eccube\Common\EccubeConfig;
use Eccube\Doctrine\Query\Queries;
use Eccube\Entity\Category;
use Eccube\Entity\Master\ProductListMax;
use Eccube\Entity\Master\ProductListOrderBy;
use Eccube\Entity\Master\ProductStatus;
use Eccube\Entity\Product;
use Eccube\Entity\ProductStock;
use Eccube\Entity\Tag;
use Eccube\Util\StringUtil;
use Plugin\Recommend42\Entity\RecommendProduct;
/**
* ProductRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ProductRepository extends AbstractRepository
{
/**
* @var Queries
*/
protected $queries;
/**
* @var EccubeConfig
*/
protected $eccubeConfig;
public const COLUMNS = [
'product_id' => 'p.id', 'name' => 'p.name', 'product_code' => 'pc.code', 'stock' => 'pc.stock', 'status' => 'p.Status', 'create_date' => 'p.create_date', 'update_date' => 'p.update_date',
];
/**
* ProductRepository constructor.
*
* @param RegistryInterface $registry
* @param Queries $queries
* @param EccubeConfig $eccubeConfig
*/
public function __construct(
RegistryInterface $registry,
Queries $queries,
EccubeConfig $eccubeConfig
)
{
parent::__construct($registry, Product::class);
$this->queries = $queries;
$this->eccubeConfig = $eccubeConfig;
}
/**
* 子カテゴリー一覧をハッシュで取得
* @param integer $p_id
* @return hash
*/
public function getHashStatus()
{
$conn = $this->getEntityManager()->getConnection();
$sql = "SELECT id,product_status_id FROM dtb_product";
$stmt = $conn->prepare($sql);
$resultSet = $stmt->executeQuery();
$rows = $resultSet->fetchAllAssociative();
$hash = array();
foreach ($rows as $row) {
$hash[$row["id"]] = $row["product_status_id"];
}
return $hash;
}
/**
* Find the Product with sorted ClassCategories.
*
* @param integer $productId
*
* @return Product
*/
public function findWithSortedClassCategories($productId)
{
$qb = $this->createQueryBuilder('p');
$qb->addSelect(['pc', 'cc1', 'cc2', 'pi', 'pt'])
->innerJoin('p.ProductClasses', 'pc')
->leftJoin('pc.ClassCategory1', 'cc1')
->leftJoin('pc.ClassCategory2', 'cc2')
->leftJoin('p.ProductImage', 'pi')
->leftJoin('p.ProductTag', 'pt')
->where('p.id = :id')
->andWhere('pc.visible = :visible')
->setParameter('id', $productId)
->setParameter('visible', true)
->orderBy('cc1.sort_no', 'DESC')
->addOrderBy('cc2.sort_no', 'DESC');
$product = $qb
->getQuery()
->getSingleResult();
return $product;
}
/**
* 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')
->innerJoin('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;
}
/**
* get query builder.
*
* @param array{
* category_id?:Category,
* name?:string,
* pageno?:string,
* disp_number?:ProductListMax,
* orderby?:ProductListOrderBy
* } $searchData
*
* @return \Doctrine\ORM\QueryBuilder
*/
public function getQueryBuilderBySearchData($searchData)
{
$qb = $this->createQueryBuilder('p')
->andWhere('((p.Status = 1) OR (p.Status = 6) OR (p.Status = 8))')
->andWhere('p.del_flg = 0');
// 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;
}
}
// 複数のカテゴリーに対応
if (!empty($searchData['c_id']) and is_array($searchData['c_id']) and count($searchData['c_id']) > 0) {
dump($searchData);exit;
}
// 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(p.name) LIKE NORMALIZE(:%s) OR
NORMALIZE(p.search_word) 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, $index, $index, $index, $index, $key))
->setParameter($key, '%' . $keyword . '%');
}
}
// ============================================================
// 限定商品の除外処理
// ============================================================
// 1. カテゴリーベースの限定商品除外(カテゴリーID: 218)
// 限定カテゴリーの子カテゴリーに属する商品は除外
// ただし、限定カテゴリーを直接指定した場合は表示
$genteiCategoryId = 218;
$isGenteiCategory = false;
if (!empty($searchData['category_id'])) {
// 指定カテゴリーが限定カテゴリー(218)またはその子孫かチェック
$category = $searchData['category_id'];
if ($category->getId() == $genteiCategoryId) {
$isGenteiCategory = true;
} else {
// 親をたどって限定カテゴリーかチェック
$parent = $category->getParent();
while ($parent) {
if ($parent->getId() == $genteiCategoryId) {
$isGenteiCategory = true;
break;
}
$parent = $parent->getParent();
}
}
}
if (!$isGenteiCategory) {
// 限定カテゴリー以外の場合、限定商品を除外
$genteiProductIds = $this->getGenteiProductIds($genteiCategoryId);
if (!empty($genteiProductIds)) {
$qb->andWhere($qb->expr()->notIn('p.id', ':genteiProductIds'))
->setParameter('genteiProductIds', $genteiProductIds);
}
}
// 2. 拡張項目ベースの限定公開除外(column_id: 66)
// ProductPlusの限定公開URLに値がある商品は除外
$genteiUrlProductIds = $this->getGenteiUrlProductIds();
if (!empty($genteiUrlProductIds)) {
$qb->andWhere($qb->expr()->notIn('p.id', ':genteiUrlProductIds'))
->setParameter('genteiUrlProductIds', $genteiUrlProductIds);
}
// 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', 'DESC');
// 価格高い順
} 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', 'DESC');
// 新着順
} 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', 'DESC');
} else {
if ($categoryJoin === false) {
$qb
->leftJoin('p.ProductCategories', 'pct')
->leftJoin('pct.Category', 'c');
}
$qb
->addOrderBy('p.id', 'DESC');
}
//dump($qb->getQuery()->getSQL());exit;
//$all = $qb->getQuery()->getArrayResult();
//dump($all);exit;
return $this->queries->customize(QueryKey::PRODUCT_SEARCH, $qb, $searchData);
}
/**
* get query builder.
*
* @param array{
* id?:string|int|null,
* category_id?:Category,
* status?:ProductStatus[],
* link_status?:ProductStatus[],
* stock_status?:int,
* stock?:ProductStock::IN_STOCK|ProductStock::OUT_OF_STOCK,
* tag_id?:Tag,
* create_datetime_start?:\DateTime,
* create_datetime_end?:\DateTime,
* create_date_start?:\DateTime,
* create_date_end?:\DateTime,
* update_datetime_start?:\DateTime,
* update_datetime_end?:\DateTime,
* update_date_start?:\DateTime,
* update_date_end?:\DateTime,
* sortkey?:string,
* sorttype?:string
* } $searchData
*
* @return \Doctrine\ORM\QueryBuilder
*/
public function getQueryBuilderBySearchDataForAdmin($searchData)
{
$qb = $this->createQueryBuilder('p')
->addSelect('pc', 'pi', 'tr', 'ps')
->innerJoin('p.ProductClasses', 'pc')
->leftJoin('p.ProductImage', 'pi')
->leftJoin('pc.TaxRule', 'tr')
->leftJoin('pc.ProductStock', 'ps')
->andWhere('pc.visible = :visible')
->setParameter('visible', true)
->andWhere('p.del_flg = 0');
// id(商品ID・商品名・商品コード・検索ワード・鑑定番号)
if (isset($searchData['id']) && StringUtil::isNotBlank($searchData['id'])) {
$id = preg_match('/^\d{0,10}$/', $searchData['id']) ? $searchData['id'] : null;
if ($id && $id > '2147483647' && $this->isPostgreSQL()) {
$id = null;
}
$qb
->andWhere('p.id = :id OR p.name LIKE :likeid OR pc.code LIKE :likeid OR p.search_word LIKE :likeid OR EXISTS (SELECT ppd.id FROM Plugin\ProductPlus42\Entity\ProductData ppd INNER JOIN Plugin\ProductPlus42\Entity\ProductDataDetail ppdd WITH ppdd.ProductData = ppd WHERE ppd.Product = p AND ppd.ProductItem = 2 AND ppdd.value LIKE :likeid)')
->setParameter('id', $id)
->setParameter('likeid', '%' . str_replace(['%', '_'], ['\\%', '\\_'], $searchData['id']) . '%');
}
// 在庫・お預かり・地金型コイン
// 1. 在庫 = コインパレスの在庫
// 2. お預かり = お客さまから預かっているコイン
// 3. 地金型コイン = カテゴリー地金型を含むコイン
if (!empty($searchData["custody_flg"]) and count($searchData["custody_flg"]) == 1) {
$custody_flg = $searchData["custody_flg"][0];
if ($custody_flg == 1) {
$ids = $this->getStorageCpIds();
$qb->andWhere($qb->expr()->in('p.id', $ids));
} elseif ($custody_flg == 2) {
$ids = $this->getStorageCustIds();
$qb->andWhere($qb->expr()->in('p.id', $ids));
}elseif($custody_flg == 3){
$Categories = array();
$categoryRepository = $this->getEntityManager()->getRepository(Category::class);
$Categories[] = $categoryRepository->find("5");
$qb->innerJoin('p.ProductCategories', 'pct2')
->innerJoin('pct2.Category', 'c2')
->andWhere($qb->expr()->in('pct2.Category', ':Categories2'))
->setParameter('Categories2', $Categories);
}
}
// category
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);
}
}
// status
if (!empty($searchData['status']) && $searchData['status']) {
$qb
->andWhere($qb->expr()->in('p.Status', ':Status'))
->setParameter('Status', $searchData['status']);
}
// link_status
if (isset($searchData['link_status']) && !empty($searchData['link_status'])) {
$qb
->andWhere($qb->expr()->in('p.Status', ':Status'))
->setParameter('Status', $searchData['link_status']);
}
// stock status
if (isset($searchData['stock_status'])) {
$qb
->andWhere('pc.stock_unlimited = :StockUnlimited AND pc.stock = 0')
->setParameter('StockUnlimited', $searchData['stock_status']);
}
// stock status
if (isset($searchData['stock']) && !empty($searchData['stock'])) {
switch ($searchData['stock']) {
case [ProductStock::IN_STOCK]:
$qb->andWhere('pc.stock_unlimited = true OR pc.stock > 0');
break;
case [ProductStock::OUT_OF_STOCK]:
$qb->andWhere('pc.stock_unlimited = false AND pc.stock <= 0');
break;
default:
// 共に選択された場合は全権該当するので検索条件に含めない
}
}
// tag
if (!empty($searchData['tag_id']) && $searchData['tag_id']) {
$qb
->innerJoin('p.ProductTag', 'pt')
->andWhere('pt.Tag = :tag_id')
->setParameter('tag_id', $searchData['tag_id']);
}
// crate_date
if (!empty($searchData['create_datetime_start']) && $searchData['create_datetime_start']) {
$date = $searchData['create_datetime_start'];
$qb
->andWhere('p.create_date >= :create_date_start')
->setParameter('create_date_start', $date);
} elseif (!empty($searchData['create_date_start']) && $searchData['create_date_start']) {
$date = $searchData['create_date_start'];
$qb
->andWhere('p.create_date >= :create_date_start')
->setParameter('create_date_start', $date);
}
if (!empty($searchData['create_datetime_end']) && $searchData['create_datetime_end']) {
$date = $searchData['create_datetime_end'];
$qb
->andWhere('p.create_date < :create_date_end')
->setParameter('create_date_end', $date);
} elseif (!empty($searchData['create_date_end']) && $searchData['create_date_end']) {
$date = clone $searchData['create_date_end'];
$date = $date
->modify('+1 days');
$qb
->andWhere('p.create_date < :create_date_end')
->setParameter('create_date_end', $date);
}
// update_date
if (!empty($searchData['update_datetime_start']) && $searchData['update_datetime_start']) {
$date = $searchData['update_datetime_start'];
$qb
->andWhere('p.update_date >= :update_date_start')
->setParameter('update_date_start', $date);
} elseif (!empty($searchData['update_date_start']) && $searchData['update_date_start']) {
$date = $searchData['update_date_start'];
$qb
->andWhere('p.update_date >= :update_date_start')
->setParameter('update_date_start', $date);
}
if (!empty($searchData['update_datetime_end']) && $searchData['update_datetime_end']) {
$date = $searchData['update_datetime_end'];
$qb
->andWhere('p.update_date < :update_date_end')
->setParameter('update_date_end', $date);
} elseif (!empty($searchData['update_date_end']) && $searchData['update_date_end']) {
$date = clone $searchData['update_date_end'];
$date = $date
->modify('+1 days');
$qb
->andWhere('p.update_date < :update_date_end')
->setParameter('update_date_end', $date);
}
// Order By
if (isset($searchData['sortkey']) && !empty($searchData['sortkey'])) {
$sortOrder = (isset($searchData['sorttype']) && $searchData['sorttype'] == 'a') ? 'ASC' : 'DESC';
$qb->orderBy(self::COLUMNS[$searchData['sortkey']], $sortOrder);
$qb->addOrderBy('p.update_date', 'DESC');
$qb->addOrderBy('p.id', 'DESC');
} else {
$qb->orderBy('p.id', 'DESC');
//$qb->addOrderBy('p.update_date', 'DESC');
}
//dump($qb->getQuery()->getSQL());exit;
//$all = $qb->getQuery()->getArrayResult();
//dump($all);exit;
return $this->queries->customize(QueryKey::PRODUCT_SEARCH_ADMIN, $qb, $searchData);
}
/**
* 商品種別(クレジット可否)をハッシュで取得
* sale_type_id = 3 がクレジット不可商品
*
* @return array
*/
public function getHashType()
{
$conn = $this->getEntityManager()->getConnection();
$sql = <<<SQL
SELECT
product_id,
sale_type_id
FROM
dtb_product_class
WHERE
visible = 1
SQL;
$stmt = $conn->prepare($sql);
$resultSet = $stmt->executeQuery();
$rows = $resultSet->fetchAllAssociative();
$hash = array();
foreach ($rows as $row) {
$hash[$row['product_id']] = $row['sale_type_id'];
}
return $hash;
}
/**
* 地金型コインカテゴリーに属する商品をハッシュで取得
*
* @return array
*/
public function getHashJigane()
{
$conn = $this->getEntityManager()->getConnection();
$sql = <<<SQL
SELECT
product_id,
category_id
FROM
dtb_product_category
WHERE
category_id = 5
SQL;
$stmt = $conn->prepare($sql);
$resultSet = $stmt->executeQuery();
$rows = $resultSet->fetchAllAssociative();
$hash = array();
foreach ($rows as $row) {
$hash[$row['product_id']] = $row['category_id'];
}
return $hash;
}
/**
* コインパレスが販売中のコインID一覧を取得
* @return array
* @throws \Doctrine\DBAL\Exception
*/
public function getStorageCpIds()
{
$sql = <<<SQL
SELECT
A.id
FROM
dtb_product AS A
INNER JOIN plg_productplus_dtb_product_data AS B ON A.id = B.product_id
INNER JOIN plg_productplus_dtb_product_data_detail AS C ON B.id = C.product_data_id
WHERE
B.product_item_id = 6
AND (C.value = 1424 or C.value = 1427 or C.value = '')
SQL;
return $this->getIdsFromSQL($sql);
}
/**
* お客さまからの預りコインID一覧を取得
* @return array
* @throws \Doctrine\DBAL\Exception
*/
public function getStorageCustIds()
{
$sql = <<<SQL
SELECT
A.id
FROM
dtb_product AS A
INNER JOIN plg_productplus_dtb_product_data AS B ON A.id = B.product_id
INNER JOIN plg_productplus_dtb_product_data_detail AS C ON B.id = C.product_data_id
WHERE
B.product_item_id = 6
AND (C.value != 1424 AND C.value != 1427 AND C.value != '')
SQL;
return $this->getIdsFromSQL($sql);
}
/**
* SQL文からID一覧を取得
* @param $sql
* @return array
* @throws \Doctrine\DBAL\Exception
*/
public function getIdsFromSQL($sql){
$conn = $this->getEntityManager()->getConnection();
$stmt = $conn->prepare($sql);
$resultSet = $stmt->executeQuery();
$rows = $resultSet->fetchAllAssociative();
$ids = array();
foreach ($rows as $row) {
$ids[] = $row['id'];
}
return $ids;
}
/**
* 限定カテゴリーに属する商品IDを取得
* @param int $genteiCategoryId 限定親カテゴリーID(デフォルト: 218)
* @return array 商品IDの配列
*/
public function getGenteiProductIds(int $genteiCategoryId = 218): array
{
// 限定カテゴリー(218)とその子カテゴリーのIDを取得
$categoryIds = $this->getGenteiCategoryIds($genteiCategoryId);
if (empty($categoryIds)) {
return [];
}
// 限定カテゴリーに属する商品IDを取得
$sql = "SELECT DISTINCT product_id AS id FROM dtb_product_category WHERE category_id IN (" . implode(',', $categoryIds) . ")";
return $this->getIdsFromSQL($sql);
}
/**
* 限定カテゴリーとその子カテゴリーのIDを取得
* @param int $parentCategoryId 親カテゴリーID
* @return array カテゴリーIDの配列
*/
public function getGenteiCategoryIds(int $parentCategoryId): array
{
$conn = $this->getEntityManager()->getConnection();
// 親カテゴリーと全ての子孫カテゴリーを取得(再帰的に)
$sql = <<<SQL
SELECT id FROM dtb_category
WHERE id = :parent_id
OR parent_category_id = :parent_id
OR parent_category_id IN (SELECT id FROM dtb_category WHERE parent_category_id = :parent_id)
OR parent_category_id IN (SELECT id FROM dtb_category WHERE parent_category_id IN (SELECT id FROM dtb_category WHERE parent_category_id = :parent_id))
SQL;
$stmt = $conn->prepare($sql);
$resultSet = $stmt->executeQuery(['parent_id' => $parentCategoryId]);
$rows = $resultSet->fetchAllAssociative();
$ids = [];
foreach ($rows as $row) {
$ids[] = $row['id'];
}
return $ids;
}
/**
* 限定公開URL(column_id=66)に値がある商品IDを取得
* @return array 商品IDの配列
*/
public function getGenteiUrlProductIds(): array
{
$sql = <<<SQL
SELECT DISTINCT
ppd.product_id AS id
FROM
plg_productplus_dtb_product_data AS ppd
INNER JOIN plg_productplus_dtb_product_data_detail AS ppdd ON ppd.id = ppdd.product_data_id
WHERE
ppd.product_item_id = 66
AND ppdd.value IS NOT NULL
AND ppdd.value != ''
SQL;
return $this->getIdsFromSQL($sql);
}
}