<?php
namespace App\Controller;
use App\Component\ApiTrait;
use App\Component\CartService;
use App\Component\OptionTrait;
use App\Component\RefererTrait;
use App\Entity\Category;
use App\Entity\Menu;
use App\Entity\Post;
use App\Entity\Shop;
use App\Entity\ShopPlan;
use App\Entity\User;
use App\Form\Type\ContactType;
use App\Form\Type\RegisterType;
use App\Form\Type\UserRegisterInputType;
use App\Form\Type\UserRegisterType;
use App\Form\Type\UserRegisterVerifyType;
use App\Security\OTPService;
use App\Service\GoogleRecaptcha;
use Doctrine\ORM\EntityManagerInterface;
use GuzzleHttp\Client;
use Knp\Bundle\PaginatorBundle\Pagination\SlidingPagination;
use Knp\Component\Pager\PaginatorInterface;
use Mailgun\Exception\HttpClientException;
use Mailgun\Mailgun;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* @author Kazuyuki Hayashi <kaz@noop.co.jp>
*/
class IndexController extends AbstractController
{
use RefererTrait;
use OptionTrait;
use ApiTrait;
/**
* トップページ
* @Route("/", name="index")
*
* @param EntityManagerInterface $em
* @return Response
*/
public function index(EntityManagerInterface $em)
{
// クーポン非表示に伴いトップを店舗一覧に変更
return $this->redirectToRoute('search');
$locations = $em->getRepository('App:Location')
->createQueryBuilder('l')
->leftJoin('l.area', 'a')
->join('l.shops', 's', 'WITH', 's.public = 1 and s.ready = 1')
->orderBy('a.position', 'ASC')
->addOrderBy('l.position', 'ASC')
->getQuery()
->getResult();
$areas = $em->getRepository('App:Area')
->createQueryBuilder('a')
->join('a.locations', 'l')
->join('l.shops', 's', 'WITH', 's.public = 1 and s.ready = 1')
->orderBy('a.position', 'ASC')
->getQuery()
->getResult();
$categories = $em->getRepository('App:Category')
->createQueryBuilder('c')
->join('c.shops', 's', 'WITH', 's.public = 1 and s.ready = 1')
->orderBy('c.position', 'ASC')
->getQuery()
->getResult();
return $this->render('index/index.html.twig', [
'areas' => $areas,
'locations' => $locations,
'categories' => $categories
]);
}
/**
* @Route("/search", name="search")
* @param EntityManagerInterface $em
* @param PaginatorInterface $paginator
* @param Request $request
* @return Response
*/
public function search(EntityManagerInterface $em, PaginatorInterface $paginator, Request $request)
{
$seed = date('Ymd');
$areas = $em->getRepository('App:Area')->findBy([], ['position' => 'ASC']);
$categories = $em->getRepository('App:Category')->findBy([], ['position' => 'ASC']);
$area = null;
$location = null;
$category = null;
$nextParam = [];
if ($request->get('a')) {
$area = $em->getRepository('App:Area')->find($request->get('a'));
$nextParam['a'] = $request->get('a');
}
if ($request->get('l')) {
$location = $em->getRepository('App:Location')->find($request->get('l'));
$nextParam['l'] = $request->get('l');
}
if ($request->get('c')) {
$category = $em->getRepository('App:Category')->find($request->get('c'));
$nextParam['c'] = $request->get('c');
}
$builder = $this->createShopListBuilder($em, $request->get('a'), $request->get('l'), $request->get('c'), $request->get('pp'), $request->get('d'), $request->get('sa'));
/* @var SlidingPagination $pagination */
$pagination = $paginator->paginate($builder, $request->get('page', 1), 20);
$pageInfo = $pagination->getPaginationData();
/* @var SlidingPagination $pagination */
$hasNext = ($pageInfo['pageCount'] + 0) > $request->get('page', 1);
$nextParam['page'] = $pagination->getCurrentPageNumber() + 1;
$totalShopCount = $pagination->getTotalItemCount();
$cart = null;
$user = $this->getUser();
if ($user) {
$cart = $em->getRepository('App:Cart')->getAvailableCartByUser($user);
}
return $this->render('index/search.html.twig', [
'areas' => $areas,
'categories' => $categories,
'area' => $area,
'location' => $location,
'category' => $category,
'pagination' => $pagination,
'pageInfo' => $pageInfo,
'nextParam' => $nextParam,
'hasNext' => $hasNext,
'totalShopCount' => $totalShopCount,
'cart' => $cart,
]);
}
/**
* @Route("/explore", name="explore")
* @return Response
*/
public function explore(EntityManagerInterface $em)
{
$locations = $em->getRepository('App:Location')
->createQueryBuilder('l')
->leftJoin('l.area', 'a')
->orderBy('a.position', 'ASC')
->addOrderBy('l.position', 'ASC')
->getQuery()
->getResult();
$categories = $em->getRepository('App:Category')->findBy([], ['position' => 'ASC']);
return $this->render('index/explore.html.twig', [
'locations' => $locations,
'categories' => $categories
]);
}
/**
* @Route("/navi", name="navi")
* @return Response
*/
public function navi()
{
return $this->render('index/navi.html.twig', [
]);
}
/**
* LP
* @Route("/lp", name="lp")
*
* @param EntityManagerInterface $em
* @return Response
*/
public function lp(EntityManagerInterface $em)
{
return $this->render('index/lp/index.html.twig', [
]);
}
/**
* @Route("/law", name="law")
* @return Response
*/
public function law(Request $request)
{
return $this->render('index/law.html.twig', [
'fromApp' => $request->query->has('app'),
]);
}
/**
* @Route("/shop/{id}", name="shop")
* @param Shop $shop
* @return Response
*/
public function shop(Shop $shop)
{
return $this->render('index/shop.html.twig', [
'shop' => $shop
]);
}
/**
* @Route("/shop/{id}/law", name="shop_law")
* @param Shop $shop
* @return Response
*/
public function shopLaw(Request $request, Shop $shop)
{
return $this->render('index/shop_law.html.twig', [
'fromApp' => $request->query->has('app'),
'shop' => $shop,
'lawOptions' => $this->getLawOptionValues(),
]);
}
/**
* @Route("/shop/menu/{id}", name="shop_menu_show")
* @param Menu $menu
* @return Response
*/
public function shopMenuShow(Menu $menu)
{
return $this->render('index/menu_show.html.twig', [
'shop' => $menu->getShop(),
'menu' => $menu,
]);
}
/**
* @Route("/user-register", name="user_register")
* @return Response
*/
public function userRegister(Request $request, GoogleRecaptcha $googleRecaptcha, RateLimiterFactory $otpApiLimiter, OTPService $OTPService, Security $security)
{
if ($this->getUser()) {
$token = $security->getToken();
$_user = $token->getUser();
if ($_user instanceof User) {
return $this->redirectToRoute('user_dashboard');
}
}
$params = $this->getRefererParams();
$this->container->get('session')->set('user-register-referer', $params);
$form = $this->createForm(UserRegisterType::class);
$form->handleRequest($request);
if ($form->isSubmitted()){
if (! $googleRecaptcha->siteVerify($request)) {
$googleRecaptchaError = new FormError('reCAPTCHAの認証に失敗しました。再度送信をお願いいたします。', '', [], null, 'grecaptcha');
$form->addError($googleRecaptchaError);
// return $this->redirectToRoute('register');
}
if ($form->isValid()) {
$data = $form->getData();
// create a limiter based on a unique identifier of the client
// (e.g. the client's IP address, a username/email, an API key, etc.)
$limiter = $otpApiLimiter->create($request->getClientIp());
// the argument of consume() is the number of tokens to consume
// and returns an object of type Limit
if (false === $limiter->consume(1)->isAccepted()) {
throw new TooManyRequestsHttpException();
}
if ($this->getParameter('kernel.environment') !== 'dev') {
$OTPService->generateOTP($data['tel']);
}
$this->container->get('session')->set('user_register_tel', $data['tel']);
return $this->redirectToRoute('user_register_verify');
}
}
$this->container->get('session')->remove('user_register_tel');
return $this->render('index/user_register/index.html.twig', [
'form' => $form->createView(),
]);
}
/**
* AJAXで呼び出される想定
*
* @Route("/user-register/re-send", name="user_register_re_send")
* @return Response
*/
public function userRegisterReSend(Request $request, RateLimiterFactory $otpApiLimiter, OTPService $OTPService)
{
// create a limiter based on a unique identifier of the client
// (e.g. the client's IP address, a username/email, an API key, etc.)
$limiter = $otpApiLimiter->create($request->getClientIp());
// the argument of consume() is the number of tokens to consume
// and returns an object of type Limit
if (false === $limiter->consume(1)->isAccepted()) {
throw new TooManyRequestsHttpException();
}
$tel = $this->container->get('session')->get('user_register_tel');
if (! $tel) {
return $this->redirectToRoute('user_register');
}
$OTPService->generateOTP($tel);
$this->container->get('session')->set('user_register_tel', $tel);
return $this->json([]);
}
/**
* @Route("/user-register/verify", name="user_register_verify")
* @return Response
*/
public function userRegisterVerify(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $em, Request $request, OTPService $OTPService, CartService $cartService)
{
$form = $this->createForm(UserRegisterVerifyType::class);
$tel = $this->container->get('session')->get('user_register_tel');
if (! $tel) {
return $this->redirectToRoute('user_register');
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$isValid = false;
if ($this->getParameter('kernel.environment') === 'dev') {
$isValid = true;
} else {
$isValid = $OTPService->isValidOTP($data['otp'], $tel);
}
if (! $isValid) {
$form->get('otp')->addError(new FormError('PINが正しくありません。'));
// 入力エラーがある場合はスルーしてverify.html.twigを表示
} else {
$user = $em->getRepository('App:User')->createQueryBuilder('u')
->andWhere('u.deletedAt is null')
->andWhere('u.tel = :tel')
->setParameter('tel', $tel)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
if ($user) {
// 登録済みの場合、ログインして元のページにリダイレクト
$token = new UsernamePasswordToken($user, 'user', $user->getRoles());
$this->container->get('security.token_storage')->setToken($token);
$this->container->get('session')->set('_security_main', serialize($token));
$loginEvent = new InteractiveLoginEvent($request, $token);
$eventDispatcher->dispatch($loginEvent, 'security.interactive_login');
// $cart = $em->getRepository('App:Cart')->getAvailableCartByUser($user);
// if ($cart) {
// // 有効なカートがある場合はセッションにカートIDを追加
// $this->container->get('session')->set(CartSessionStorage::CART_KEY_NAME, $cart->getId());
// }
// dump($this->getUser());
if (! $this->container->get('session')->has('auth_adding_item')) {
// dd('auth_adding');
// カート追加以外の登録またはログイン=元のページに戻す
$pathInfo = $this->container->get('session')->get('user-register-referer');
$params = [];
foreach ($pathInfo as $key => $value) {
if ($key != '_route' && $key != '_controller') {
$params[$key] = $value;
}
}
return $this->redirectToRoute($pathInfo['_route'], $params);
} else {
// dd('to cart_index');
$cart = $cartService->getOrCreateCart($user);
if ($cart->getShop()) {
return $this->redirectToRoute('shop', [
'id' => $cart->getShop()->getId(),
]);
}
return $this->redirectToRoute('cart_index');
}
} else {
// 未登録の場合、入力画面へ
return $this->redirectToRoute('user_register_input');
}
}
}
return $this->render('index/user_register/verify.html.twig', [
'form' => $form->createView(),
]);
}
/**
* @Route("/user-register/input", name="user_register_input")
* @param EntityManagerInterface $em
* @param Request $request
* @param UserPasswordEncoderInterface $passwordEncoder
* @param OTPService $OTPService
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function userRegisterInput(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $em, Request $request, UserPasswordEncoderInterface $passwordEncoder, OTPService $OTPService)
{
$user = new User();
$form = $this->createForm(UserRegisterInputType::class, $user);
$tel = $this->container->get('session')->get('user_register_tel');
if (! $tel) {
return $this->redirectToRoute('user_register');
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$user->setTel($tel);
$user->setPassword($passwordEncoder->encodePassword($user, random_bytes(32)));
$em->persist($user);
$em->flush();
$this->container->get('session')->remove('user_register_tel');
$token = new UsernamePasswordToken($user, 'user', $user->getRoles());
$this->container->get('security.token_storage')->setToken($token);
$this->container->get('session')->set('_security_main', serialize($token));
$loginEvent = new InteractiveLoginEvent($request, $token);
$eventDispatcher->dispatch($loginEvent, 'security.interactive_login');
return $this->redirectToRoute('user_register_done');
}
return $this->render('index/user_register/input.html.twig', [
'form' => $form->createView(),
'tel' => $tel,
]);
}
/**
* @Route("/user-register/done", name="user_register_done")
* @return Response
*/
public function userRegisterDone(EntityManagerInterface $em)
{
$user = $this->getUser();
$cart = $em->getRepository('App:Cart')->getAvailableCartByUser($user);
$cartItemLength = 0;
$shopId = null;
if ($cart) {
$cartItemLength = count($cart->getCartItems());
if ($cart->getShop()) {
$shopId = $cart->getShop()->getId();
}
}
return $this->render('index/user_register/done.html.twig', [
'cartItemLength' => $cartItemLength,
'shopId' => $shopId,
]);
}
/**
* @Route("/area", name="area")
* @return Response
*/
public function area()
{
return $this->render('index/area.html.twig', [
]);
}
// /**
// * @Route("/coupon", name="coupon")
// * @return Response
// */
// public function coupon()
// {
// return $this->render('index/coupon.html.twig', [
// ]);
// }
/**
* @Route("/faq", name="faq")
* @return Response
*/
public function faq(EntityManagerInterface $em)
{
$questionCategories = $em->getRepository('App:QuestionCategory')
->createQueryBuilder('qc')
->andWhere('qc.public = 1')
->andWhere('qc.toUser = 1')
->orderBy('qc.position')
->getQuery()
->getResult();
return $this->render('index/faq.html.twig', [
'questionCategories' => $questionCategories,
]);
}
/**
* @Route("/news", name="news")
* @return Response
*/
public function news(EntityManagerInterface $em, Request $request, PaginatorInterface $paginator)
{
$builder = $em->getRepository('App:Post')->createQueryBuilder('p')
->andWhere('p.toUser = 1')
->andWhere('p.publishedAt <= :now')
->setParameter('now', new \DateTime())
->orderBy('p.publishedAt', 'DESC');
;
/* @var SlidingPagination $pagination */
$pagination = $paginator->paginate($builder, $request->get('page', 1), 20);
$pageInfo = $pagination->getPaginationData();
return $this->render('index/news/index.html.twig', [
'pagination' => $pagination,
'pageInfo' => $pageInfo,
]);
}
/**
* @Route("/news/{id}", name="news_show")
* @return Response
*/
public function newsShow(Post $post, Request $request)
{
return $this->render('index/news/show.html.twig', [
'post' => $post,
'fromApp' => $request->query->has('app'),
]);
}
/**
* @Route("/map", name="map")
* @param EntityManagerInterface $em
* @return Response
*/
public function map(EntityManagerInterface $em)
{
$shops = $em->getRepository('App:Shop')->findBy(['public' => true, 'ready' => true]);
return $this->render('index/map.html.twig', [
'shops' => $shops
]);
}
/**
* @Route("/pins", name="pins")
* @param Request $request
* @param EntityManagerInterface $em
*/
public function pins(Request $request, EntityManagerInterface $em)
{
$lat1 = (float) $request->get('lat1');
$lng1 = (float) $request->get('lng1');
$lat2 = (float) $request->get('lat2');
$lng2 = (float) $request->get('lng2');
/* @var Shop[] $shops */
$shops = $em->getRepository('App:Shop')->createQueryBuilder('s')
->andWhere('s.coordinate.latitude > :lat1')
->andWhere('s.coordinate.longitude > :lng1')
->andWhere('s.coordinate.latitude < :lat2')
->andWhere('s.coordinate.longitude < :lng2')
->andWhere('s.public = 1')
->andWhere('s.ready = 1')
// 富山駅(初期値)を表示する場合はこの行を削除してください。
->andWhere('(s.coordinate.latitude <> 36.7018268 and s.coordinate.longitude <> 137.2126258)')
->setParameter('lat1', $lat1)
->setParameter('lng1', $lng1)
->setParameter('lat2', $lat2)
->setParameter('lng2', $lng2)
->getQuery()
->getResult();
$json = [];
foreach ($shops as $shop) {
$json[$shop->getId()] = [
'id' => $shop->getId(),
'latitude' => $shop->getCoordinate()->getLatitude(),
'longitude' => $shop->getCoordinate()->getLongitude(),
'name' => $shop->getName(),
'category' => implode(' / ', array_map(function (Category $category) {
return $category->getName();
}, $shop->getCategories()->toArray())),
'path' => $this->generateUrl('shop', ['id' => $shop->getId()]),
'minutes' => $shop->getEstimatedMinutes() ?: '-',
'toGo' => $shop->getToGo(),
'delivery' => $shop->getDelivery(),
'menus' => array_map(function (Menu $menu) {
return [
'image' => $menu->getImage() ?: '/img/no-image.jpeg',
'name' => $menu->getName(),
];
}, $shop->getMenus()->toArray()),
'html' => $this->renderView('index/map_panel.html.twig', ['shop' => $shop])
];
}
return $this->json($json);
}
/**
* @Route("/about", name="about")
* @return Response
*/
public function about()
{
return $this->render('index/about.html.twig', []);
}
/**
* @Route("/campaign", name="campaign")
* @return Response
*/
public function campaign()
{
return $this->render('index/campaign.html.twig', []);
}
/**
* @Route("/terms", name="terms")
* @return Response
*/
public function terms(Request $request)
{
return $this->render('index/terms.html.twig', [
'fromApp' => $request->query->has('app'),
]);
}
/**
* @Route("/privacy", name="privacy")
* @return Response
*/
public function privacy(Request $request)
{
return $this->render('index/privacy.html.twig', [
'fromApp' => $request->query->has('app'),
]);
}
/**
* @Route("/links", name="links")
* @return Response
*/
public function links()
{
return $this->render('index/links.html.twig', []);
}
/**
* @Route("/register", name="register")
* @see \App\Controller\Api\ShopController::shopRegister()
* @param EntityManagerInterface $em
* @param UserPasswordEncoderInterface $passwordEncoder
* @param Request $request
* @return Response
*/
public function register(GoogleRecaptcha $googleRecaptcha, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder, Request $request)
{
$shop = new Shop();
$shop->setReady(false);
$form = $this->createForm(RegisterType::class, $shop);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if (! $googleRecaptcha->siteVerify($request)) {
$googleRecaptchaError = new FormError('reCAPTCHAの認証に失敗しました。再度送信をお願いいたします。', '', [], null, 'grecaptcha');
$form->addError($googleRecaptchaError);
// return $this->redirectToRoute('register');
}
if ($form->isValid()) {
$shopPlan = $em->getRepository('App:ShopPlan')->createQueryBuilder('sp')
->andWhere('sp.slug = :plan')
->setParameter('plan', ShopPlan::SLUG_STANDARD)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
$shop->setShopPlan($shopPlan);
$shop->setPassword($passwordEncoder->encodePassword($shop, $shop->getPlainPassword()));
$em->persist($shop);
$em->flush();
try {
$mailgun = Mailgun::create($_ENV['MAILGUN_API_KEY']);
$mailgun->messages()->send($_ENV['MAILGUN_DOMAIN'], array(
'from' => $_ENV['MAIL_FROM_ADDRESS'],
'to' => $shop->getEmail(),
'bcc' => $_ENV['MAIL_TO_ADDRESS'],
'subject' => '掲載申し込み',
//'text' => $this->renderView('index/register.eml.twig', ['shop' => $shop])
'template' => 'registration',
'h:X-Mailgun-Variables' => json_encode([
'url' => $this->generateUrl('shop_dashboard', [], UrlGeneratorInterface::ABSOLUTE_URL),
'name' => $shop->getName(),
'homepage' => 'https://uchideri.com'
])
));
} catch (HttpClientException $e) {
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
return $this->redirectToRoute('register_complete');
}
}
return $this->render('index/register.html.twig', [
'form' => $form->createView()
]);
}
/**
* @Route("/register/complete", name="register_complete")
* @return Response
*/
public function registerComplete()
{
return $this->render('index/register_complete.html.twig', []);
}
/**
* @Route("/contact", name="contact")
* @return Response
*/
public function contact(Request $request)
{
$form = $this->createForm(ContactType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
// to-do email
$mailgun = Mailgun::create($_ENV['MAILGUN_API_KEY']);
try {
$mailgun->messages()->send($_ENV['MAILGUN_DOMAIN'], array(
'from' => $_ENV['MAIL_FROM_ADDRESS'],
'to' => $_ENV['MAIL_TO_ADDRESS'],
// 'bcc' => 'kaz@noop.co.jp',
'subject' => 'ホームページからのお問い合わせ',
//'text' => $this->renderView('index/register.eml.twig', ['shop' => $shop])
'template' => 'contact.notification',
'h:X-Mailgun-Variables' => json_encode([
'name' => $data['name'],
'tel' => $data['tel'],
'email' => $data['email'],
'content' => $data['content'],
'date' => date('Y/m/d H:i:s')
])
));
} catch (HttpClientException $e) {
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
try {
$mailgun->messages()->send($_ENV['MAILGUN_DOMAIN'], array(
'from' => $_ENV['MAIL_FROM_ADDRESS'],
'to' => $data['email'],
'subject' => 'お問い合わせありがとうございます',
//'text' => $this->renderView('index/register.eml.twig', ['shop' => $shop])
'template' => 'contact',
'h:X-Mailgun-Variables' => json_encode([
'url' => $this->generateUrl('index', [], UrlGeneratorInterface::ABSOLUTE_URL),
'name' => $data['name']
])
));
} catch (HttpClientException $e) {
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
return $this->redirectToRoute('contact_complete');
}
return $this->render('index/contact.html.twig', [
'form' => $form->createView()
]);
}
/**
* @Route("/contact/complete", name="contact_complete")
* @return Response
*/
public function contactComplete()
{
return $this->render('index/contact_complete.html.twig', []);
}
}