<?php 
 
/* 
 * This file is part of Customize of EC-CUBE by Kenji Nakanishi 
 * 
 * Copyright(c) 2021 Kenji Nakanishi. All Rights Reserved. 
 * 
 * https://www.facebook.com/web.kenji.nakanishi 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Customize\Controller; 
 
use Doctrine\ORM\EntityManagerInterface; 
use Doctrine\ORM\Query\Expr; 
use Doctrine\ORM\QueryBuilder; 
use Eccube\Controller\AbstractController; 
use Eccube\Entity\Category; 
use Eccube\Entity\Customer; 
use Eccube\Entity\Master\OrderItemType; 
use Eccube\Entity\Master\OrderStatus; 
use Eccube\Entity\Master\ProductStatus; 
use Eccube\Entity\Master\SaleType; 
use Eccube\Entity\Order; 
use Eccube\Entity\OrderItem; 
use Eccube\Entity\Product; 
use Eccube\Entity\ProductClass; 
use Eccube\Entity\ProductCategory; 
use Eccube\Repository\OrderItemRepository; 
use Eccube\Repository\OrderRepository; 
use Plugin\SheebDlc4\Entity\Config; 
use Plugin\SheebDlc4\Entity\DownloadContent; 
use Plugin\SheebDlc4\PluginManager; 
use Plugin\SheebDlc4\Service\SaveFile\AbstractSaveFile; 
use Plugin\SheebDlc4\Service\SaveFile\Modules\Local; 
use Plugin\SheebDlc4\Service\SaveFile\SaveFileModuleFactory; 
use Plugin\SheebDlc4\Service\SheebUtils; 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; 
use Symfony\Component\Asset\Packages; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Eccube\Common\EccubeConfig; 
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; 
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; 
use Symfony\Component\Routing\Annotation\Route; 
use Knp\Component\Pager\PaginatorInterface; 
use Eccube\Form\Type\SearchProductType; 
use Eccube\Form\Type\Master\ProductListMaxType; 
use Eccube\Form\Type\Master\ProductListOrderByType; 
use Plugin\CMBlogPro\Form\Type\Admin\BlogType; 
use Plugin\CMBlogPro\Repository\BlogRepository; 
use Eccube\Doctrine\Query\Queries; 
use Plugin\CMBlogPro\Repository\ConfigRepository; 
use Plugin\CMBlogPro\Repository\CategoryRepository; 
use Eccube\Util\StringUtil; 
use Eccube\Repository\PageRepository; 
 
class CustomBlogController extends AbstractController 
{ 
    const BLOG_CATEGORY = array( 
        'MEMBER' => 1, 
        'NEWS' => 2, 
    ); 
 
    /** 
     * @var BlogRepository $blogRepository 
     */ 
    private $blogRepository; 
 
 
    private $queries; 
 
    /** 
     * @var ConfigRepository 
     */ 
    protected $configRepository; 
 
    /** 
     * @var CategoryRepository 
     */ 
    protected $categoryRepository; 
 
    /** 
     * @var PageRepository 
     */ 
    protected $pageRepository; 
 
    public function __construct(BlogRepository $blogRepository, Queries $queries, ConfigRepository $configRepository, CategoryRepository $categoryRepository, PageRepository $pageRepository) 
    { 
        $this->blogRepository = $blogRepository; 
        $this->queries = $queries; 
        $this->configRepository = $configRepository; 
        $this->categoryRepository = $categoryRepository; 
        $this->pageRepository = $pageRepository; 
    } 
 
    /** 
     * ブログ一覧 
     * 
     * @Route("/blog_list/", name="blog_list", methods={"GET"}) 
     * @Template("blog/list.twig") 
     * 
     * @param Request $request 
     * @param PaginatorInterface $paginator 
     * @return array 
     */ 
    public function blog_list(Request $request, PaginatorInterface $paginator) 
    { 
        // ログイン時はニュースのみ、非ログイン時は会員カテゴリも除外する 
        if ($this->isGranted('ROLE_USER')) { 
            return $this->blog_list_response($request, $paginator, [self::BLOG_CATEGORY['NEWS']]); 
        } else { 
            return $this->blog_list_response($request, $paginator, [self::BLOG_CATEGORY['MEMBER'], self::BLOG_CATEGORY['NEWS']]); 
        } 
    } 
 
    /** 
     * ブログ詳細ページのブログ遷移先の振り分け 
     * ログイン済み→デフォルト, 未ログイン→カスタム 
     * 
     * @Route("/blog_detail/{id}", name="blog_detail/{id}", methods={"GET"}) 
     * @Template("blog/detail.twig") 
     * 
     * @param Request $request 
     * @param string $id 
     * @return \Symfony\Component\HttpFoundation\RedirectResponse 
     * 
     * @throws \Exception 
     */ 
    public function blog_detail(Request $request, $id) 
    { 
        $blogDetail = $this->getBlogDetailByIdOrSlug($id); 
        if ($blogDetail === null) { 
            throw new NotFoundHttpException(); 
        } 
        // お知らせを含む場合は404 
        if ($this->hasCategoryId($blogDetail, self::BLOG_CATEGORY['NEWS']) === true) { 
            throw new NotFoundHttpException(); 
        } 
 
        if ($this->isGranted('ROLE_USER')) { 
            // 会員の場合は無条件 
            return $this->forwardToRoute('cm_blog_pro_page_detail', ['id' => $id], $request->query->all()); 
        } else { 
            // 非会員の場合は会員カテゴリが設定されている場合のみ一覧画面に遷移 
            if ($this->hasCategoryId($blogDetail, self::BLOG_CATEGORY['MEMBER']) === true) { 
                return $this->redirectToRoute('blog_list', $request->query->all()); 
            } 
             
            return $this->forwardToRoute('cm_blog_pro_page_detail', ['id' => $id], $request->query->all()); 
        } 
    } 
 
    /** 
     * 会員限定のニュース一覧ページ 
     * 
     * @Route("/news_list/", name="news_list", methods={"GET"}) 
     * @Template("blog/news_list.twig") 
     * 
     * @param Request $request 
     * @param PaginatorInterface $paginator 
     * @return array|RedirectResponse 
     */ 
    public function news_list(Request $request, PaginatorInterface $paginator) 
    { 
        if ($this->isGranted('ROLE_USER')) { 
            return $this->news_list_response($request, $paginator, null, [self::BLOG_CATEGORY['NEWS']]); 
            // return $this->redirectToRoute('news_entry', $request->query->all()); 
        } else { 
            return $this->news_list_response($request, $paginator, [self::BLOG_CATEGORY['MEMBER']], [self::BLOG_CATEGORY['NEWS']]); 
        } 
    } 
 
    /** 
     * ニュース詳細ページのブログ遷移先の振り分け 
     * ログイン済み→デフォルト, 未ログイン→カスタム 
     * 
     * @Route("/news_detail/{id}", name="news_detail/{id}", methods={"GET"}) 
     * @Template("blog/news_detail.twig") 
     * 
     * @param Request $request 
     * @param int|string $id 
     * @return \Symfony\Component\HttpFoundation\RedirectResponse 
     * 
     * @throws \Exception 
     */ 
    public function news_detail(Request $request, $id) 
    { 
        $blog = $this->getBlogDetailByIdOrSlug($id); 
        if ($blog === null) { 
            throw new NotFoundHttpException(); 
        } 
        // お知らせカテゴリは必須 
        if ($this->hasCategoryId($blog, self::BLOG_CATEGORY['NEWS']) === false) { 
            throw new NotFoundHttpException(); 
        } 
 
        // 非会員∧会員カテゴリ 
        if (!$this->isGranted('ROLE_USER')) { 
            // 非会員の場合は会員カテゴリが設定されている場合のみ一覧画面に遷移 
            if ($this->hasCategoryId($blog, 1) === true) { 
                return $this->redirectToRoute('blog_list', $request->query->all()); 
            } 
        } 
 
        $config = $this->configRepository->get(); 
 
        $form = $this->createForm(BlogType::class, $blog); 
 
        $cmPage = $this->pageRepository->findOneBy(['url'  => 'cm_blog_pro_page_detail']); 
 
        if ($blog->getAuthor() != null) { 
            $Page["author"] = $blog->getAuthor(); 
        } else { 
            $Page["author"] = $cmPage->getAuthor(); 
        } 
             
 
        if ($blog->getDescription() != null) { 
            $Page["description"] = $blog->getDescription(); 
        } else { 
            $Page["description"] = $cmPage->getDescription(); 
        }; 
         
        if ($blog->getKeyword() != null) { 
            $Page["keyword"] = $blog->getKeyword(); 
        } else { 
            $Page["keyword"] = $cmPage->getKeyword(); 
        }; 
         
 
        if ($blog->getRobot() != null) { 
            $Page["meta_robots"] = $blog->getRobot(); 
        } else { 
            $Page["meta_robots"] = $cmPage->getMetaRobots(); 
        } 
         
 
        if ($blog->getMetatag() != null) { 
            $Page["meta_tags"] = $blog->getMetatag(); 
        } else { 
            $Page["meta_tags"] = $cmPage->getMetaTags(); 
        } 
 
        $tagArr = []; 
        if ($blog->getTag() != null) { 
            $tagArr = preg_split('/[;, 、]+/u', $blog->getTag()); 
        } 
        $Page["edit_type"] = 0; 
        return [ 
            'form' => $form->createView(), 
            'blog' => $blog, 
            'Page' => $Page, 
            'subtitle' => $blog->getTitle(), 
            'tags' => $tagArr, 
            'monthArr' => $this->setMonthArchive() 
        ]; 
    } 
 
    /** 
     * ブログ一覧(会員, ゲスト)のレスポンスを生成する。 
     * 検索条件のみ若干異なるためパラメータに応じて処理を分岐する。 
     * 
     * @param Request $request 
     * @param PaginatorInterface $paginator 
     * @param array|null $excludeCategoryIds 
     * @param array|null $includeCategoryIds 
     * @return array 
     */ 
    public function blog_list_response(Request $request, PaginatorInterface $paginator, $excludeCategoryIds = [], $includeCategoryIds = []) 
    { 
        $form = $this->createForm(BlogType::class); 
        $search = $request->query->all(); 
        $search["status"] = 1; 
        $qb = $this->getCustomQuery($search, $excludeCategoryIds, $includeCategoryIds); 
 
        $config = $this->configRepository->get(); 
 
        $pagination = $paginator->paginate( 
            $qb, 
            !empty($search['pageno']) ? $search['pageno'] : 1, 
            !empty($search['disp_number']) ? $search['disp_number'] : $config->getDisplayPage() 
        ); 
 
        return [ 
            'form' => $form->createView(), 
            'categories' => $this->categoryRepository->getFrontCategoryList(), 
            'pagination' => $pagination, 
            'monthArr' => $this->setMonthArchive($search), 
            'excludeCategoryIds' => $excludeCategoryIds, 
            'includeCategoryIds' => $includeCategoryIds, 
            'searchData' => $search, 
        ]; 
    } 
 
    /** 
     * ニュース一覧(会員, ゲスト)のレスポンスを生成する。 
     * 検索条件のみ若干異なるためパラメータに応じて処理を分岐する。 
     * 
     * @param Request $request 
     * @param PaginatorInterface $paginator 
     * @param array|null $excludeCategoryIds 
     * @param array|null $includeCategoryIds 
     * @return array 
     */ 
    public function news_list_response(Request $request, PaginatorInterface $paginator, $excludeCategoryIds = [], $includeCategoryIds = []) 
    { 
        $form = $this->createForm(BlogType::class); 
        $search = $request->query->all(); 
        $search["status"] = 1; 
        $qb = $this->getCustomQuery($search, $excludeCategoryIds, $includeCategoryIds); 
 
        $config = $this->configRepository->get(); 
 
        $pagination = $paginator->paginate( 
            $qb, 
            !empty($search['pageno']) ? $search['pageno'] : 1, 
            !empty($search['disp_number']) ? $search['disp_number'] : $config->getDisplayPage() 
        ); 
 
        return [ 
            'form' => $form->createView(), 
            'categories' => $this->categoryRepository->getFrontCategoryList(), 
            'pagination' => $pagination, 
            'monthArr' => $this->setMonthArchive($search), 
            'excludeCategoryIds' => $excludeCategoryIds, 
            'includeCategoryIds' => $includeCategoryIds, 
            'searchData' => $search, 
        ]; 
    } 
 
    /** 
     * 検索クエリを生成する 
     * 
     * @param array $searchData 
     * @param array|null $excludeCategoryIds 除外するカテゴリID 
     * @param array|null $includeCategoryIds 含む必要のあるカテゴリ 
     * @return \Doctrine\ORM\QueryBuilder 
     */ 
    public function getCustomQuery($searchData, $excludeCategoryIds = [], $includeCategoryIds = []) 
    { 
        $currentDate = new \DateTime(); 
        $qb = $this->get_publish_query_builder($currentDate); 
 
        // 除外カテゴリ設定がある場合は対象のブログIDを取得する 
        $excludeMemberBlogs = null; 
        $excludeMemberBlogIds = null; 
        if (!empty($excludeCategoryIds)) { 
            $excludeQb = $this->get_publish_query_builder($currentDate, 'o.id'); 
            $excludeQb 
                ->innerJoin('o.BlogCategories', 'bct') 
                ->innerJoin('bct.Category', 'c', 'WITH', 'c.id = bct.Category'); 
 
            $excludeQb->andWhere($excludeQb->expr()->in('bct.Category', $excludeCategoryIds)); 
            $excludeMemberBlogs = $excludeQb->getQuery()->getResult(); 
         
            if (!empty($excludeMemberBlogs)) { 
                $excludeMemberBlogIds = array_column($excludeMemberBlogs, 'id'); 
            } 
        } 
 
        // 指定カテゴリがある場合は対象のブログIDを取得する 
        $includeBlogs = null; 
        $includeBlogIds = null; 
        if (!empty($includeCategoryIds)) { 
            $includeQb = $this->get_publish_query_builder($currentDate, 'o.id'); 
            $includeQb 
                ->innerJoin('o.BlogCategories', 'bct') 
                ->innerJoin('bct.Category', 'c', 'WITH', 'c.id = bct.Category'); 
 
            $includeQb->andWhere($includeQb->expr()->in('bct.Category', $includeCategoryIds)); 
            $includeBlogs = $includeQb->getQuery()->getResult(); 
 
            if (!empty($includeBlogs)) { 
                $includeBlogIds = array_column($includeBlogs, 'id'); 
            } 
        } 
 
        // カテゴリ 
        if (isset($searchData['categories']) && StringUtil::isNotBlank($searchData['categories'])) { 
            $qb 
                ->innerJoin('o.BlogCategories', 'bct') 
                ->innerJoin('bct.Category', 'c') 
                ->andWhere($qb->expr()->in('bct.Category', ':categories')) 
                ->setParameter('categories', $searchData['categories']); 
        } else { 
            // NULL許容 
            $qb 
                ->leftJoin('o.BlogCategories', 'bct') 
                ->leftJoin('bct.Category', 'c', 'WITH', 'c.id = bct.Category'); 
        } 
 
        // タグで検索 
        if (isset($searchData['tag']) && StringUtil::isNotBlank($searchData['tag'])) { 
            $qb 
                ->andWhere('o.tag LIKE :tag') 
                ->setParameter('tag', '%'. $searchData['tag'] .'%'); 
        } 
 
        // タグで検索 
        if (isset($searchData['date']) && StringUtil::isNotBlank($searchData['date'])) { 
             
            $currentDate = new \DateTime(); 
            $qb 
                ->andWhere('o.release_date >= :max_date AND o.release_date <= :min_date AND o.release_date <= :currentDate') 
                ->setParameter('max_date', date('Y-m-01 00:00:00', strtotime($searchData['date']))) 
                ->setParameter('min_date', date('Y-m-t 23:59:59', strtotime($searchData['date']))) 
                ->setParameter('currentDate', $currentDate); 
        } 
 
        // カテゴリを指定する場合 
        // if (!empty($includeCategoryIds)) { 
        //     $qb->andWhere($qb->expr()->in('bct.Category', ':includeCategories')) 
        //         ->setParameter('includeCategories', $includeCategoryIds); 
        // } 
        if (!empty($includeBlogIds)) { 
            $qb->andWhere($qb->expr()->in('o.id', ':includeIds')) 
                ->setParameter('includeIds', $includeBlogIds); 
        } 
 
        // 特定カテゴリの記事IDを結果から除外する 
        if (!empty($excludeMemberBlogIds)) { 
            $qb->andWhere($qb->expr()->notIn('o.id', ':excludeIds')) 
                ->setParameter('excludeIds', $excludeMemberBlogIds); 
        } 
 
        // Order By 
        $qb->orderBy('o.create_date', 'DESC'); 
 
        return $this->queries->customize('Blog.getQueryBuilderBySearchData', $qb, $searchData); 
    } 
 
    /** 
     * 公開状態の記事を抽出するクエリビルダを返却する 
     * 
     * @param \Datetime $currentDate 
     * @param string $select selectする対象 
     * @return \Doctrine\ORM\QueryBuilder 
     */ 
    public function get_publish_query_builder($currentDate = null, $select = 'o') 
    { 
        if (empty($currentDate)) { 
            $currentDate = new \DateTime(); 
        } 
        /** @var \Doctrine\ORM\QueryBuilder $qb */ 
        $qb = $this->blogRepository->createQueryBuilder('o')->select($select) 
            ->andWhere('o.release_date < :date OR o.release_date is NULL AND o.create_date < :date') 
            ->setParameter('date', $currentDate) 
            ->andWhere('o.Status = 1'); 
        return $qb; 
    } 
 
    /** 
     * パラメータからブログ詳細を取得する 
     * 
     * @param int|string $id 
     * @return Blog|null 
     */ 
    public function getBlogDetailByIdOrSlug($id) 
    { 
        $blogDetail = null; 
        //postgresql→int の最大値:2147483647だから、最大値を超えるとSlug として判断 
        if (is_numeric($id) && $id <= 2147483647) { 
            $blogDetail = $this->blogRepository->find($id); 
            // 数字slugの対応 
            if (!$blogDetail) { 
                $blogDetail = $this->blogRepository->findOneBy(['slug' => $id]); 
            } 
        } else { 
            $blogDetail = $this->blogRepository->findOneBy(['slug' => $id]); 
        } 
 
        if ($blogDetail) { 
            return $blogDetail; 
        } else { 
            return null; 
        } 
    } 
 
    /** 
     * 記事に指定したカテゴリが含まれるか判別する 
     * 
     * @param Blog $entries 
     * @param int $targetCategoryId 
     * @return boolean 
     */ 
    public function hasCategoryId($entry, $targetCategoryId) 
    { 
        foreach ($entry->getBlogCategories() as $entryCategory) { 
            if ($entryCategory->getCategoryId() == $targetCategoryId) { 
                return true; 
            } 
        } 
        return false; 
    } 
 
    /** 
     * 月別アーカイブ表示のため取得 
     * 
     * @param array $search 
     * 
     * @return array 月別配列 
     */ 
    public function setMonthArchive($search = []) 
    { 
        $releaseDate = $this->blogRepository->getReleaseDate($search); 
 
        $monthArr = []; 
        foreach ($releaseDate as $val) { 
            if (is_null($val['release_date'])) { 
                continue; 
            } 
            $key = $val['release_date']->format('Y-m'); 
            if (isset($monthArr[$key])) { 
                continue; 
            } 
            $monthArr[$key] = $val['release_date']->format('Y年m月'); 
        } 
        return $monthArr; 
    } 
}