EC-CUBE4で会員登録時に会員登録ポイントを付与させる方法

EC-CUBE4ではポイント機能は標準で対応されるようになりましたが、商品購入時にしかポイントが付与されません。

EC-CUBE2系をお使いの方であれば、会員登録ポイントは付与できますので同じような機能をEC-CUBE4でも実現させてみます。

変更させる箇所は以下の通りです。

今回はDBで会員登録ポイントを管理するのではなく、設定ファイルで管理させてます。

  • app/config/eccube/packages/eccube.yaml
  eccube_point_entry_point: 200 # ポイント機能;会員登録で獲得するポイント数

記述する箇所は最下行で構いません。この例だと200ポイントを付与させるようにしています。

  • src/Eccube/Controller/EntryController.php
use Eccube\Service\PointHelper;

・
・
・
         
/**
 * @var PointHelper
 */
protected $pointHelper;

・
・
・

/**
 * EntryController constructor.
 *
 * @param CartService $cartService
 * @param CustomerStatusRepository $customerStatusRepository
 * @param MailService $mailService
 * @param BaseInfoRepository $baseInfoRepository
 * @param CustomerRepository $customerRepository
 * @param EncoderFactoryInterface $encoderFactory
 * @param ValidatorInterface $validatorInterface
 * @param TokenStorageInterface $tokenStorage
 * @param PointHelper $pointHelper
 */
public function __construct(
    CartService $cartService,
    CustomerStatusRepository $customerStatusRepository,
    MailService $mailService,
    BaseInfoRepository $baseInfoRepository,
    CustomerRepository $customerRepository,
    EncoderFactoryInterface $encoderFactory,
    ValidatorInterface $validatorInterface,
    TokenStorageInterface $tokenStorage,
    PointHelper $pointHelper
)
{
    $this->customerStatusRepository = $customerStatusRepository;
    $this->mailService = $mailService;
    $this->BaseInfo = $baseInfoRepository->get();
    $this->customerRepository = $customerRepository;
    $this->encoderFactory = $encoderFactory;
    $this->recursiveValidator = $validatorInterface;
    $this->tokenStorage = $tokenStorage;
    $this->cartService = $cartService;
    $this->pointHelper = $pointHelper;
}

・
・
・

// 会員登録ポイントの付与
case 'complete':
    log_info('会員登録開始');

    $encoder = $this->encoderFactory->getEncoder($Customer);
    $salt = $encoder->createSalt();
    $password = $encoder->encodePassword($Customer->getPassword(), $salt);
    $secretKey = $this->customerRepository->getUniqueSecretKey();

    $Customer
        ->setSalt($salt)
        ->setPassword($password)
        ->setSecretKey($secretKey)
        ->setPoint(0);

    $this->entityManager->persist($Customer);
    $this->entityManager->flush();

    // 会員登録ポイントの付与
    // 所有ポイントが更新されるので再度flushする必要あり
    $this->pointHelper->addEntryPoint($Customer);
    $this->entityManager->persist($Customer);
    $this->entityManager->flush();

PointHelperを利用して会員登録ポイントを付与させる処理を追加します。

  • src/Eccube/Service/PointHelper.php
・
・
・

/**
 * @var EccubeConfig
 */
private $eccubeConfig;

・
・
・

/**
 * PointHelper constructor.
 *
 * @param BaseInfoRepository $baseInfoRepository
 * @param EntityManagerInterface $entityManager
 */
public function __construct(BaseInfoRepository $baseInfoRepository, EntityManagerInterface $entityManager, EccubeConfig $eccubeConfig)
{
    $this->baseInfoRepository = $baseInfoRepository;
    $this->entityManager = $entityManager;
    $this->eccubeConfig = $eccubeConfig;
}

・
・
・


// 会員登録時ポイントを付与する
public function addEntryPoint(Customer $Customer)
{
    $point = $this->eccubeConfig['eccube_point_entry_point'];

    $pointHistory = new PointHistory();
    $pointHistory->setRecordType(PointHistory::TYPE_ADD);
    $pointHistory->setRecordEvent(PointHistory::EVENT_ENTRY);
    $pointHistory->setPoint($point);
    $pointHistory->setCustomer($Customer);
    $em = $this->entityManager;
    $em->persist($pointHistory);
    $em->flush($pointHistory);

    // ポイントの付与
    $Customer->setPoint($Customer->getPoint() + $point);
}

PointHelper.phpに会員登録時にポイントを付与する関数を追加します。

今回はかなり省略して変更点を記述していますので、もう少し詳しく書いて欲しければコメントしてください。

EC-CUBE4でCSVファイルを常にダブルクォーテーション付きで出力させる方法

EC-CUBE4では管理画面の商品管理や受注管理でCSV出力をする場合、項目によってはダブルクォーテーションが付いたり付かなかったりとバラバラな状態で出力されます。

理由はfputcsv関数を利用しているからです。 https://www.php.net/manual/ja/function.fputcsv.php

ただ、サイトによっては常にダブルクォーテーションをつけて欲しいという要望があります。 その時は以下の修正を行うことで対応が可能です。

説明は割愛して、修正した関数だけ載せておきます。 src/Eccube/Service/CsvExportService.phpファイルの以下の関数を修正します。

  • exportHeader関数の修正
/**
 * ヘッダ行を出力する.
 * このメソッドを使う場合は, 事前にinitCsvType($CsvType)で初期化しておく必要がある.
 */
public function exportHeader()
{
    if (is_null($this->CsvType) || is_null($this->Csvs)) {
        throw new \LogicException('init csv type incomplete.');
    }

    $row = [];
    foreach ($this->Csvs as $Csv) {
        $row[] = '"'.$Csv->getDispName().'"';
    }
    $row[] = "\r\n";

    $str = implode($this->eccubeConfig['eccube_csv_export_separator'], $row);
    $csv = mb_convert_encoding($str, $this->eccubeConfig['eccube_csv_export_encoding'], 'UTF-8');

    $this->fopen();
    fputs($this->fp, $csv);
    $this->fclose();
}
  • fputcsv関数の修正
/**
 * @param $row
 */
public function fputcsv($row)
{
    // if (is_null($this->convertEncodingCallBack)) {
    //     $this->convertEncodingCallBack = $this->getConvertEncodingCallback();
    // }

    // fputcsv($this->fp, array_map($this->convertEncodingCallBack, $row), $this->eccubeConfig['eccube_csv_export_separator']);

    $rows = [];
    foreach ($row as $r) {
        $rows[] = '"'.$r.'"';
    }
    $rows[] = "\r\n";

    $str = implode($this->eccubeConfig['eccube_csv_export_separator'], $rows);
    $csv = mb_convert_encoding($str, $this->eccubeConfig['eccube_csv_export_encoding'], 'UTF-8');

    fputs($this->fp, $csv);

}

以上の対応でCSV出力時は常にダブルクォーテーションが付くようになります。

EC-CUBE4でMySQL利用時に絵文字を利用する方法

EC-CUBE4をMySQLで利用している場合、標準のままだと絵文字をDBへ登録しようとするとシステムエラーが発生します。 それを防ぐためには文字コードを「utf8mb4」へ変更する必要があります。 前提条件としてMySQL5.7以上を対象としていますが、 MySQL5.6以下でも設定を変更する事で利用可能となります。

パッケージを解凍後EC-CUBEをインストールする前に、 最低限以下の設定をすることで絵文字が登録できるようになります。

  • app/config/eccube/packages/doctrine.yaml
parameters:
    # Adds a fallback DATABASE_URL if the env var is not set.
    # This allows you to run cache:warmup even if your
    # environment variables are not available yet.
    # You should not need to change this value.
    env(DATABASE_URL): ''
    env(DATABASE_SERVER_VERSION): ~
doctrine:
    dbal:
        driver: 'pdo_sqlite'
        server_version: "%env(DATABASE_SERVER_VERSION)%"
        charset: utf8mb4

        # for mysql only
        default_table_options:
          collate: 'utf8mb4_general_ci'

        # With Symfony 3.3, remove the `resolve:` prefix
        url: '%env(DATABASE_URL)%'

        # types
        types:
            datetime: 'Eccube\Doctrine\DBAL\Types\UTCDateTimeType'
            datetimetz: 'Eccube\Doctrine\DBAL\Types\UTCDateTimeTzType'
    orm:
        auto_generate_proxy_classes: '%kernel.debug%'
        naming_strategy: doctrine.orm.naming_strategy.underscore
        auto_mapping: true
        dql:
            string_functions:
                NORMALIZE: Eccube\Doctrine\ORM\Query\Normalize
            numeric_functions:
                EXTRACT: Eccube\Doctrine\ORM\Query\Extract
        filters:
            option_nostock_hidden:
                class: Eccube\Doctrine\Filter\NoStockHiddenFilter
                enabled: false
            incomplete_order_status_hidden:
                class: Eccube\Doctrine\Filter\OrderStatusFilter
                enabled: false

変更点は12行目と16行目にある
「utf8」「utf8_general_ci」を
「utf8mb4」「utf8mb4_general_ci」
に変更します。

  • src/Eccube/Controller/Install/InstallController.php
if (strpos($params['url'], 'mysql') !== false) {
    $params['charset'] = 'utf8mb4';
    $params['defaultTableOptions'] = [
        'collate' => 'utf8mb4_general_ci',
    ];
}

変更点は564行目と566行目にある
「utf8」「utf8_general_ci」を
「utf8mb4」「utf8mb4_general_ci」
に変更することで絵文字対応が可能となります。 前提として、MySQLから

show variables like "chara%";

を実行時に、 character_set_databaseutf8mb4 になっている必要があります。 そのため、データベースを作成時は必ず utf8mb4 で作成するようにしましょう。

create database [データベース名] default character set utf8mb4;

最後は必ず「utf8mb4」を指定してください。

もう一点環境によってはDBの型を変更する必要があります。

  • src/Eccube/Controller/Entity/Product.php

ここで4000として定義されている、 note description_list description_detai search_word

@ORM\Column(name="note", type="text", nullable=true)

というようにtext型へ変更させる必要があります。

PostgreSQLの方は試してないのですが、特に変更することなく対応できるのではないかと思います。

EC-CUBE4でログイン成功時に直前まで見ていた画面へ戻る方法

EC-CUBE4ではログインする場合、EC-CUBE2とは違いログイン画面を経由させる必要があります。

また、ログイン成功後は必ずサイトのトップページへ戻ってしまいます。

サイトの作りにもよりますが、例えばログイン後は前に見ていた画面を表示して欲しいという要望があった場合、 以下のカスタマイズを行うことで実現可能となります。

EC-CUBE3では以下の記事で対応方法を書いています。

amidaike.hatenablog.com

EC-CUBE4.0.1を対象に説明します。

まず、MypageControllerのコンストラクタに対してRequestStackクラスを宣言します。

  • src/Eccube/Controller/Mypage/MypageController.php
/**
 * @var RequestStack
 */
protected $requestStack;

/**
 * MypageController constructor.
 *
 * @param OrderRepository $orderRepository
 * @param CustomerFavoriteProductRepository $customerFavoriteProductRepository
 * @param CartService $cartService
 * @param BaseInfoRepository $baseInfoRepository
 * @param PurchaseFlow $purchaseFlow
 * @param RequestStack $requestStack
 */
public function __construct(
    OrderRepository $orderRepository,
    CustomerFavoriteProductRepository $customerFavoriteProductRepository,
    CartService $cartService,
    BaseInfoRepository $baseInfoRepository,
    PurchaseFlow $purchaseFlow,
    RequestStack $requestStack
)
{
    $this->orderRepository = $orderRepository;
    $this->customerFavoriteProductRepository = $customerFavoriteProductRepository;
    $this->BaseInfo = $baseInfoRepository->get();
    $this->cartService = $cartService;
    $this->purchaseFlow = $purchaseFlow;
    $this->requestStack = $requestStack;
}

その後、MypageControllerにあるlogin関数を修正します。

/**
 * ログイン画面.
 *
 * @Route("/mypage/login", name="mypage_login")
 * @Template("Mypage/login.twig")
 */
public function login(Request $request, AuthenticationUtils $utils)
{
    if ($this->isGranted('IS_AUTHENTICATED_FULLY')) {
        log_info('認証済のためログイン処理をスキップ');

        return $this->redirectToRoute('mypage');
    }

    /* @var $form \Symfony\Component\Form\FormInterface */
    $builder = $this->formFactory
        ->createNamedBuilder('', CustomerLoginType::class);

    $builder->get('login_memory')->setData((bool)$request->getSession()->get('_security.login_memory'));

    if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
        $Customer = $this->getUser();
        if ($Customer instanceof Customer) {
            $builder->get('login_email')
                ->setData($Customer->getEmail());
        }
    }

    $event = new EventArgs(
        [
            'builder' => $builder,
        ],
        $request
    );
    $this->eventDispatcher->dispatch(EccubeEvents::FRONT_MYPAGE_MYPAGE_LOGIN_INITIALIZE, $event);

    $form = $builder->getForm();

    // ログイン前の画面へ戻す
    $error = $utils->getLastAuthenticationError();
    if (!$error) {
        // ログインエラーがなければ戻り先をセット
        $referer = $request->headers->get('referer');

        if ($referer) {
            // refererチェック
            $referers = parse_url($referer);
            if ($referers['host'] == $request->getHost()) {
                // ホストが同一であればrefererをセット
                $requestStackUri = $this->requestStack->getMasterRequest()->getUri();

                if ($request->getUri() == $requestStackUri) {
                    // ログイン画面遷移直前のuriをセット
                    $this->setLoginTargetPath($referer);
                } else {
                    // ログイン必須画面のuriをセット
                    $this->setLoginTargetPath($requestStackUri);
                }
            }
        }
    }

    return [
        'error' => $error,
        'form' => $form->createView(),
    ];
}

修正箇所は、

// ログイン前の画面へ戻す
$error = $utils->getLastAuthenticationError();
if (!$error) {
    // ログインエラーがなければ戻り先をセット
    $referer = $request->headers->get('referer');

    if ($referer) {
        // refererチェック
        $referers = parse_url($referer);
        if ($referers['host'] == $request->getHost()) {
            // ホストが同一であればrefererをセット
            $requestStackUri = $this->requestStack->getMasterRequest()->getUri();

            if ($request->getUri() == $requestStackUri) {
                // ログイン画面遷移直前のuriをセット
                $this->setLoginTargetPath($referer);
            } else {
                // ログイン必須画面のuriをセット
                $this->setLoginTargetPath($requestStackUri);
            }
        }
    }
}

return [
    'error' => $error,
    'form' => $form->createView(),
];

になります。ログイン前のrefererを取得し、ログイン成功時に直前まで見ていた画面へ戻すようにしています。ただし、ログインが必要な画面を直接指定された場合、refererを取得するとログインに成功してもrefererで指定した画面に遷移してしまうため、

$this->requestStack->getMasterRequest()->getUri();

と本来指定されているURLを取得し、比較する事でログイン成功後の遷移先を変更するようにセットしています。

refererが取得できない環境は諦めてください。

あと、login.twigも変更する必要があります。以下のソースをlogin.twigのform内に追加します。

  • src/Eccube/Resource/template/default/Mypage/login.twig
<form name="login_mypage" id="login_mypage" method="post" action="{{ url('mypage_login') }}">
{% if app.session.flashBag.has('eccube.login.target.path') %}
    {% for targetPath in app.session.flashBag.peek('eccube.login.target.path') %}
        <input type="hidden" name="_target_path" value="{{ targetPath }}" />
    {% endfor %}
{% endif %}

以上の対応でログイン成功後は、直前まで見ていた画面へ遷移するようになります。

EC-CUBE4でメンテナンスモード有効時でも管理者でログインしていればフロント画面を表示できるようにする方法

EC-CUBE Advent Calendar 2018 22日目の記事です。

EC-CUBE4.0.1から新たにメンテナンス機能が利用できるようになりました。

github.com

今回の機能でメンテナンス管理画面が簡単に行えるようになりましたが、フロント画面の確認が出来なくなります。

そこで、管理者でログインしていればフロント画面が表示できるという機能を以下のPull Requestで作成しています。

github.com

変更点はこのPull Requestを見てもらえれば良いのですが、コメントにもあるようにこのprだと、 プラグインのインストール時等にキャッシュを再生成時に失敗する恐れがあるということなので推奨していません。

ただし、キャッシュ再生成等の仕組みを理解している方であれば便利な機能となりますので利用できる方は限られますが参考にしてください。

EC-CUBE4でページ管理から画面作成時にURLからuser_dataを無くす方法

EC-CUBE Advent Calendar 2018 18日目の記事です。

EC-CUBE4にあるページ管理より新しいページを作成した場合、URLには必ずuser_dataが含まれます。

http://example.com/user_data/hoge

というようになります。

user_dataは別にあっても問題はありませんが、今までカスタマイズしてきた要望の一つとしてuser_dataを無くしたいという方もいました。 そこでこのuser_dataをなくす方法を説明します。

EC-CUBE4.0.1を対象にします。

EC-CUBE4からはEC-CUBE3と異なり、URL定義はアノテーションへと変更されました。そのため、対象となるアノテーションを変更します。

まず、UserDataControllerにあるuser_dataを定義しているルーティングの箇所を変更します。

  • src/Eccube/Controller/UserDataController.php
/**
 * @Route("/%eccube_user_data_route%/{route}", name="user_data", requirements={"route": "([0-9a-zA-Z_\-]+\/?)+(?<!\/)"})
 */

から

/**
 * @Route("/{route}", name="user_data", requirements={"route": "^(?=([0-9a-zA-Z_\-]+\/?)+(?<!\/))(?!logout|%eccube_admin_route%/logout|install).*$"})
 */

%eccube_user_data_route% を無くし、ログアウトも除外できるように追加します。

また、管理画面でのURL表記を変更するため、page_edit.twigも修正します。

  • Resource/template/admin/Content/page_edit.twig
・91行目
{{ url('homepage') }}{{ eccube_config.eccube_user_data_route }}/
↓
{{ url('homepage') }}

こちらはuser_dataを表示上からなくしているだけです。 以上の作業を行うことで、ページ管理から新たなページを作成した場合、URLからuser_dataはなくなります。

[追記] もう一箇所修正する箇所がありました。プラグイン利用時にプラグイン側でルーティングの定義をされていた場合、 先にUserDataControllerが参照されてしまい正常に動作しません。

そのため、Kernel.phpにあるconfigureRoutes関数の内容を一部変更します。

  • src/Eccube/Kernel.php
// 有効なプラグインのルーティングをインポートする.
$plugins = $container->getParameter('eccube.plugins.enabled');
$pluginDir = $this->getProjectDir().'/app/Plugin';
foreach ($plugins as $plugin) {
    $dir = $pluginDir.'/'.$plugin.'/Controller';
    if (file_exists($dir)) {
        $builder = $routes->import($dir, '/', 'annotation');
        $builder->setSchemes($scheme);
    }
}

と記述されている箇所を152行目に移動させて以下の様にします。

  • src/Eccube/Kernel.phpのconfigureRoutes変更後
protected function configureRoutes(RouteCollectionBuilder $routes)
{
    $container = $this->getContainer();

    $scheme = ['https', 'http'];
    $forceSSL = $container->getParameter('eccube_force_ssl');
    if ($forceSSL) {
        $scheme = 'https';
    }
    $routes->setSchemes($scheme);

    $confDir = $this->getProjectDir().'/app/config/eccube';
    if (is_dir($confDir.'/routes/')) {
        $builder = $routes->import($confDir.'/routes/*'.self::CONFIG_EXTS, '/', 'glob');
        $builder->setSchemes($scheme);
    }
    if (is_dir($confDir.'/routes/'.$this->environment)) {
        $builder = $routes->import($confDir.'/routes/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
        $builder->setSchemes($scheme);
    }

    // 有効なプラグインのルーティングをインポートする.
    $plugins = $container->getParameter('eccube.plugins.enabled');
    $pluginDir = $this->getProjectDir().'/app/Plugin';
    foreach ($plugins as $plugin) {
        $dir = $pluginDir.'/'.$plugin.'/Controller';
        if (file_exists($dir)) {
            $builder = $routes->import($dir, '/', 'annotation');
            $builder->setSchemes($scheme);
        }
    }

    $builder = $routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/', 'glob');
    $builder->setSchemes($scheme);
    $builder = $routes->import($confDir.'/routes_'.$this->environment.self::CONFIG_EXTS, '/', 'glob');
    $builder->setSchemes($scheme);

}

上記のようにルーティングの読み出す順序を変更することで対応可能です。

EC-CUBE3の時は色々とめんどくさいことをしなければいけませんでしたが、EC-CUBE4では簡単にできます。

amidaike.hatenablog.com

上記対応を行っても反映されないなどがあれば、[ECCUBEROOT]/var/cache ディレクトリを削除してからお試しください。

必要な方には必要なカスタマイズだと思いますので、user_dataをURLから無くしてしまいたい!という方は是非ご利用ください。

ローカル環境でEC-CUBEをhttpsで動かす方法

EC-CUBE Advent Calendar 2018 15日目の記事です。

前回の記事で、EC-CUBE4のインストール方法について書きました。

EC-CUBE4をMAMPにインストールする方法とインストール画面の設定方法 - AmidaikeBlog

インストール方法は慣れてしまえば簡単なのですが、ローカルでEC-CUBE3、4を動かすにはどのようにすれば良いのか悩みます。

DockerやVagrantMAMP、XAMMPやビルトインウェブサーバー等があり、EC-CUBE4からは

bin/console server:run

というコマンドが用意されています。

それぞれ慣れている方法で実行してもらえれば良いのですが、SSLを利用したいとなった時にはめんどくさい思いが必要になります。

ローカルで開発時にはSSLなんて必要ないと思われますが、決済や他サービスとの連携時にはhttpsでないと連携できないサービスも増えてきていますので、どうしてもhttpsを利用せざるを得ないという状況が発生してしまいます。

その場合、どうすれば良いかとなりますが、つい先日以下のスライドが発表がされました。

speakerdeck.com

Symfonyが新たにSymfony Cloudと呼ばれるサービスを開始し、その一つしてSymfony CLIというものが提供されています。 それを利用することでローカルでもSSL環境が簡単に利用できるようになります。

実際の利用方法は以下となります。

1.SymfoyCLIツールのダウンロード

OSの環境に合わせて下記URLよりダウンロードします。

symfony.com

2.SSLの設定

証明書をインストールするため、下記コマンドを実行します。

symfony server:ca:install

実行後、

ls ~/.symfony/certs/

default.p12 rootCA-key.pem  rootCA.pem

が含まれていれば問題ありません。

3.サーバを起動

環境が整いましたので、以下のコマンドを実行してサーバを起動します。

  • EC-CUBE3の場合

[ECCUBEROOT]/html

まで移動し、

symfony server:start

を実行します。

  • EC-CUBE4の場合

[ECCUBEROOT]

まで移動し、

symfony server:start

を実行します。

4.ブラウザで確認

https://127.0.0.1:8000

にアクセスし、EC-CUBEの画面が表示されれば成功です。

以上がローカルでhttpsを動かす方法となります。

httpsがローカル環境で必要な方は是非お使いください。