app/Plugin/ECCUBE4LineIntegration42/Controller/LineIntegrationController.php line 67

Open in your IDE?
  1. <?php
  2. namespace Plugin\ECCUBE4LineIntegration42\Controller;
  3. use Plugin\ECCUBE4LineIntegration42\Consts\ApiUrl;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  6. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  7. use Eccube\Controller\AbstractController;
  8. use Eccube\Entity\Master\CustomerStatus;
  9. use Eccube\Repository\CustomerRepository;
  10. use Plugin\ECCUBE4LineIntegration42\Entity\LineIntegration;
  11. use Plugin\ECCUBE4LineIntegration42\Controller\Admin\LineIntegrationAdminController;
  12. use Plugin\ECCUBE4LineIntegration42\Repository\LineIntegrationSettingRepository;
  13. use Plugin\ECCUBE4LineIntegration42\Repository\LineIntegrationRepository;
  14. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  15. use Symfony\Component\Security\Http\SecurityEvents;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. class LineIntegrationController extends AbstractController
  18. {
  19.     private $lineChannelId;
  20.     private $lineChannelSecret;
  21.     private $lineIntegrationSettingRepository;
  22.     private $lineIntegrationRepository;
  23.     private $customerRepository;
  24.     private $tokenStorage;
  25.     protected $apiUrl;
  26.     const PLUGIN_LINE_INTEGRATION_SSO_USERID 'plugin.line_integration.sso.userid';
  27.     const PLUGIN_LINE_INTEGRATION_SSO_STATE 'plugin.line_integration.sso.state';
  28.     public function __construct(
  29.         LineIntegrationSettingRepository $lineIntegrationSettingRepository,
  30.         LineIntegrationRepository $lineIntegrationRepository,
  31.         CustomerRepository $customerRepository,
  32.         TokenStorageInterface $tokenStorage,
  33.         ApiUrl $apiUrl
  34.     ) {
  35.         $this->lineIntegrationSettingRepository $lineIntegrationSettingRepository;
  36.         $this->lineIntegrationRepository $lineIntegrationRepository;
  37.         $lineIntegrationSetting $this->getLineIntegrationSetting();
  38.         $this->lineChannelId $lineIntegrationSetting->getLineChannelId();
  39.         $this->lineChannelSecret $lineIntegrationSetting->getLineChannelSecret();
  40.         $this->customerRepository $customerRepository;
  41.         $this->tokenStorage $tokenStorage;
  42.         $this->apiUrl $apiUrl;
  43.     }
  44.     /**
  45.      * ログイン画面の表示
  46.      *
  47.      * @Route("/plugin_line_login", name="plugin_line_login")
  48.      * @param Request $request
  49.      * @return \Symfony\Component\HttpFoundation\RedirectResponse
  50.      */
  51.     public function login(Request $request)
  52.     {
  53.         $url $this->generateUrl('plugin_line_login_callback',array(),0);
  54.         $state uniqid();
  55.         $session $request->getSession();
  56.         $session->set(self::PLUGIN_LINE_INTEGRATION_SSO_STATE$state);
  57.         $previousUrl parse_url(
  58.             $request->headers->get('referer'),PHP_URL_PATH);
  59.         $session->set('$previousUrl' ,$previousUrl);
  60.         // bot_prompt
  61.         // bot_prompt=normal or aggressive
  62.         // https://developers.line.me/ja/docs/line-login/web/link-a-bot/
  63.         $lineAuthUrl $this->apiUrl->getAccessUrl() . '/oauth2/v2.1/authorize?response_type=code&client_id=' $this->lineChannelId '&redirect_uri=' rawurlencode($url) . '&state=' $state '&scope=profile&bot_prompt=aggressive';
  64.         return $this->redirect($lineAuthUrl);
  65.     }
  66.     /**
  67.      * ログインのコールバック処理
  68.      *
  69.      * @Route("/plugin_line_login_callback", name="plugin_line_login_callback")
  70.      * @param Request $request
  71.      *
  72.      * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
  73.      */
  74.     public function loginCallback(Request $request)
  75.     {
  76.         $code $request->get('code');
  77.         $state $request->get('state');
  78.         $session $request->getSession();
  79.         $originalState $session->get(self::PLUGIN_LINE_INTEGRATION_SSO_STATE);
  80.         $session->remove(self::PLUGIN_LINE_INTEGRATION_SSO_STATE);
  81.         $shopLoginUrl '/shopping/login';
  82.         $shopLoginUrlLength strlen($shopLoginUrl);
  83.         // APIアクセスの為のパラメータ検証
  84.         $results $this->validateParameter($code$state$originalState);
  85.         if($results !== null) {
  86.             return $results;
  87.         }
  88.         // アクセストークン発行
  89.         $tokenJson $this->publishAccessToken($code);
  90.         if (isset($tokenJson['error'])) {
  91.             //errorレスポンスはないため、この処理は起こり得ない(コード記述ミス?)
  92.             log_error('LINE API エラー(4)' $tokenJson['error'] . ' ' $tokenJson['error_description']);
  93.             return $this->render('error.twig', [
  94.                 'error_title'   => 'エラーが発生しました(エラーコード:4)',
  95.                 'error_message' => 'サイト運営者にお問い合わせください',
  96.             ]);
  97.         }
  98.         if (!array_key_exists("access_token"$tokenJson)) {
  99.             log_error('LINE API エラー(5)');
  100.             return $this->render('error.twig', [
  101.                 'error_title'   => 'エラーが発生しました(エラーコード:5)',
  102.                 'error_message' => 'サイト運営者にお問い合わせください',
  103.             ]);
  104.         }
  105.         // LineId取得
  106.         $profile $this->getProfile($tokenJson['access_token']);
  107.         if (!array_key_exists("userId"$profile)) {
  108.             log_error('LINE API エラー(6): LINE IDの取得失敗');
  109.             return $this->render('error.twig', [
  110.                 'error_title'   => 'エラーが発生しました(エラーコード:6)',
  111.                 'error_message' => 'サイト運営者にお問い合わせください',
  112.             ]);
  113.         }
  114.         if (empty($profile['userId'])) {
  115.             //LINE API エラー(6)とほぼ同じ(コード記述ミス?)
  116.             log_error('LINE API エラー(7): LINE IDが不正');
  117.             return $this->render('error.twig', [
  118.                 'error_title'   => 'エラーが発生しました(エラーコード:7)',
  119.                 'error_message' => 'サイト運営者にお問い合わせください',
  120.             ]);
  121.         }
  122.         $lineUserId $profile['userId'];
  123.         $session->set(self::PLUGIN_LINE_INTEGRATION_SSO_USERID$lineUserId);
  124.         $this->setSession($session);
  125.         // LINE連携レコードを取得
  126.         $lineIntegration $this->lineIntegrationRepository->
  127.         findOneBy(['line_user_id' => $lineUserId]);
  128.         // LINE連携レコードの顧客IDを取得
  129.         isset($lineIntegration['customer_id']) ?
  130.             $customerId $lineIntegration['customer_id'] :
  131.             $customerId null;
  132.         // 顧客レコードから顧客取得
  133.         $this->customerRepository->findOneBy(['id' => $customerId]) ?
  134.             $customer =
  135.                 $this->customerRepository->findOneBy(['id' => $customerId]) :
  136.             $customer null;
  137.         // LINE連携レコードがあり、LINE連携レコードに紐づく顧客レコードが見つからない場合、LINE連携レコード削除
  138.         if (!is_null($lineIntegration)) {
  139.             // DB上にLINE IDの登録はあるが、Customerオブジェクトが未発見の場合、LINE IDの削除
  140.             if (is_null($customer)) {
  141.                 log_info('削除されたユーザ(customer_id:' $customerId ')とのLINE IDのレコードを削除します');
  142.                 $this->lineIntegrationRepository->deleteLineAssociation($lineIntegration);
  143.                 // DB上にLINE IDの登録はあるが、Customerが退会済み扱いのときも、LINE IDを削除する
  144.             } else if ($customer->getStatus()['id'] == CustomerStatus::WITHDRAWING) {
  145.                 log_info('退会しているユーザ(customer_id:' $customerId ')とのLINE IDのレコードを削除します');
  146.                 $this->lineIntegrationRepository->deleteLineAssociation($lineIntegration);
  147.                 $customer null// 会員を存在しなかった扱いにすることで、新規登録フローに流す
  148.             }
  149.             // 削除後はそのままスルーし、普通のフローに
  150.         }
  151.         // EC-CUBEにログインしているとき(会員情報編集からの遷移)、LINE連携レコードと紐付け
  152.         if ($this->isGranted('ROLE_USER')) {
  153.             log_info('LINEコールバック: ログイン済み');
  154.             if (is_null($customer)) {
  155.                 $this->associationCustomerAndLineid($lineUserId);
  156.             } else {
  157.                 // 既にDBにLINE IDと紐づけられている顧客ID
  158.                 $registeredCustomerId $customer->getId();
  159.                 // 新たにLINE IDと紐付けようと申請する顧客ID
  160.                 $nowLoggedInCustomerId $this->getUser()->getId();
  161.                 if ($nowLoggedInCustomerId != $registeredCustomerId) {
  162.                     log_info('すでに連携済みのLINE IDを別のアカウントの連携に使おうとしました', [$nowLoggedInCustomerId$registeredCustomerId]);
  163.                     return $this->render('error.twig', [
  164.                         'error_title' => '重複したLINE IDです',
  165.                         'error_message' => "既に別のアカウントで、同じLINE IDが登録されています。",
  166.                     ]);
  167.                 }
  168.             }
  169.             return $this->redirectToRoute('mypage_change');
  170.         }
  171.         // EC-CUBEに未ログインであるとき
  172.         else {
  173.             log_info('LINEコールバック: 未ログイン');
  174.             // LINE連携レコードがなかったら、会員登録へ
  175.             log_info('未ログイン');
  176.             if (is_null($lineIntegration)) {
  177.                 log_info('LINE連携レコードなし');
  178.                 return $this->redirectToRoute('entry_contact');
  179.             }
  180.             // LINE連携レコードがあっても、顧客レコードがない場合は会員登録へ
  181.             if (is_null($customer)) {
  182.                 log_info('顧客レコードが取得できなかった為、会員登録へ');
  183.                 return $this->redirectToRoute('entry_contact');
  184.             }
  185.             // 仮会員の場合ログインへ
  186.             if ($customer->getStatus()->getId() == 1) {
  187.                 log_info('仮会員のため、ログインへ customer_id:'.$customerId);
  188.                 if (substr($session->get('$previousUrl'), -$shopLoginUrlLength) === $shopLoginUrl) {
  189.                     return $this->redirectToRoute('shopping_login');
  190.                 }
  191.                 return $this->redirectToRoute('mypage_login');
  192.             }
  193.             // 本会員かつ、LINE連携レコード・顧客レコードが存在するのでログイン処理
  194.             if ($customer->getStatus()->getId() == 2) {
  195.                 $token = new UsernamePasswordToken($customernull'customer',
  196.                     array('ROLE_USER'));
  197.                 $this->tokenStorage->setToken($token);
  198.                 log_info('ログイン済に変更。dtb_customer.id:'.$this->getUser()->getId());
  199.                 // カートのマージなどの処理
  200.                 $loginEvent = new InteractiveLoginEvent($request$token);
  201.                 $this->eventDispatcher->dispatch(
  202.                     $loginEvent,
  203.                     SecurityEvents::INTERACTIVE_LOGIN
  204.                 );
  205.                 // 遷移元がカート経由のログインだった場合、購入画面へ
  206.                 if (substr($session->get('$previousUrl'), -$shopLoginUrlLength) === $shopLoginUrl) {
  207.                     return $this->redirectToRoute('shopping');
  208.                 }
  209.                 // かご落ちメッセージ経由のログインだった場合、カート画面(セッションに保存されているURL)へ
  210.                 if ($session->get('dropped-cart-notifier-redirect') !== null) {
  211.                     return $this->redirect($session->get('dropped-cart-notifier-redirect'));
  212.                 }
  213.                 // そうでない場合マイページへ遷移
  214.                 return $this->redirectToRoute('mypage');
  215.             }
  216.             // 例外としてログインページに戻す
  217.             return $this->redirectToRoute('login');
  218.         }
  219.     }
  220.     /**
  221.      * 設定レコードを取得します
  222.      * @return string
  223.      */
  224.     private function getLineIntegrationSetting()
  225.     {
  226.         $lineIntegrationSetting $this->lineIntegrationSettingRepository
  227.             ->find(LineIntegrationAdminController::LINE_INTEGRATION_SETTING_TABLE_ID);
  228.         return $lineIntegrationSetting;
  229.     }
  230.     /**
  231.      * LINE APIからアクセストークンを取得する為の、パラメータを検証します
  232.      * @param $code
  233.      * @param $state
  234.      * @param $originalState
  235.      *
  236.      * @return \Symfony\Component\HttpFoundation\Response|null
  237.      */
  238.     private function validateParameter($code$state$originalState){
  239.         if (empty($code)) {
  240.             log_error('LINE API エラー(0): 認可コードが空');
  241.             $config $this->lineIntegrationSettingRepository->find(1);
  242.             if (is_null($config) || is_null($config->getLineAddCancelRedirectUrl())) {
  243.                 log_error("[LineIntegration] 設定を取得できませんでした");
  244.                 return $this->render('error.twig', [
  245.                     'error_title'   => 'エラーが発生しました(エラーコード:0)',
  246.                     'error_message' => 'サイト運営者にお問い合わせください',
  247.                 ]);
  248.             } else {
  249.                 return $this->redirect($config->getLineAddCancelRedirectUrl());
  250.             }
  251.         }
  252.         if (empty($state)) {
  253.             log_error('LINE API エラー(1): CSRF防止用の固有な英数字の文字列が空');
  254.             return $this->render('error.twig', [
  255.                 'error_title'   => 'エラーが発生しました(エラーコード:1)',
  256.                 'error_message' => 'サイト運営者にお問い合わせください',
  257.             ]);
  258.         }
  259.         if (empty($originalState)) {
  260.             log_error('LINE API エラー(2): セッションタイムアウト');
  261.             return $this->render('error.twig', [
  262.                 'error_title'   => 'エラーが発生しました(エラーコード:2)',
  263.                 'error_message' => 'セッションタイムアウトしました。再度ログインしてください。',
  264.             ]);
  265.         }
  266.         if ($state != $originalState) {
  267.             log_error('LINE API エラー(3): CSRF防止用の固有な英数字の文字列がセッションのものと異なる');
  268.             return $this->render('error.twig', [
  269.                 'error_title'   => 'エラーが発生しました(エラーコード:3)',
  270.                 'error_message' => 'サイト運営者にお問い合わせください',
  271.             ]);
  272.         }
  273.         return null;
  274.     }
  275.     /**
  276.      * LINE APIでアクセストークンを発行します
  277.      * @param $code
  278.      *
  279.      * @return mixed
  280.      */
  281.     private function publishAccessToken($code){
  282.         $url $this->generateUrl('plugin_line_login_callback',array(),0);
  283.         $accessTokenUrl $this->apiUrl->getApiUrl() . "/oauth2/v2.1/token";
  284.         $accessTokenData = array(
  285.             "grant_type" => "authorization_code",
  286.             "code" => $code,
  287.             "redirect_uri" => $url,
  288.             "client_id" => $this->lineChannelId,
  289.             "client_secret" => $this->lineChannelSecret,
  290.         );
  291.         $accessTokenData http_build_query($accessTokenData"""&");
  292.         $header = array(
  293.             "Content-Type: application/x-www-form-urlencoded",
  294.             "Content-Length: " strlen($accessTokenData)
  295.         );
  296.         $context = array(
  297.             "http" => array(
  298.                 "method" => "POST",
  299.                 "header" => implode("\r\n"$header),
  300.                 "content" => $accessTokenData
  301.             )
  302.         );
  303.         $response file_get_contents($accessTokenUrlfalsestream_context_create($context));
  304.         $tokenJson json_decode($responsetrue);
  305.         return $tokenJson;
  306.     }
  307.     /**
  308.      * LINE APIからLINE IDを取得します
  309.      * @param $accessToken
  310.      *
  311.      * @return mixed
  312.      */
  313.     private function getProfile($accessToken){
  314.         $lineProfileUrl $this->apiUrl->getApiUrl() . "/v2/profile";
  315.         $context = array(
  316.             "http" => array(
  317.                 "method" => "GET",
  318.                 "header" => "Authorization: Bearer " $accessToken
  319.             )
  320.         );
  321.         $response file_get_contents($lineProfileUrlfalsestream_context_create($context));
  322.         $profileJson json_decode($responsetrue);
  323.         return $profileJson;
  324.     }
  325.     /**
  326.      * 顧客とLINE連携レコードの紐付けを行います
  327.      * @param $customer
  328.      * @param $lineUserId
  329.      *
  330.      * @return \Symfony\Component\HttpFoundation\Response
  331.      */
  332.     private function associationCustomerAndLineid($lineUserId){
  333.         log_info('plg_line_integrationレコードなし');
  334.         $lineIntegration = new LineIntegration();
  335.         $lineIntegration->setLineUserId($lineUserId);
  336.         $lineIntegration->setCustomerId($this->getUser()->getId());
  337.         $lineIntegration->setLineNotificationFlg(1);
  338.         $lineIntegration->setDelFlg(0);
  339.         $this->entityManager->persist($lineIntegration);
  340.         $this->entityManager->flush();
  341.         log_info('LINE IDとユーザーの関連付け終了');
  342.     }
  343. }