【EC-CUBE4.1】Akismet APIを利用して商品レビュー管理プラグインのコメントスパム対策をする方法

Akismet APIを利用して EC-CUBE 公式プラグインの 商品レビュー管理プラグインのコメントスパム対策をする方法です。

Symfonyのドキュメントを参考に作っているので最初に Symfony ドキュメントの API でスパム対策をする をご確認ください。

以下、サンプルコードです。

Akismet に登録する

akismet.com の無料アカウントに登録して Akismet APIキーを取得してください。

Symfony HTTPClient コンポーネントをインストール

API の呼び出しをするために、Symfony の HTTPClient コンポーネントを使用します。

composer req http-client

スパムチェッカークラスを作成

app/Customize/Service 以下に、SpamChecker クラスを作成します。

<?php

namespace Customize\Service;

use Plugin\ProductReview4\Entity\ProductReview;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class SpamChecker
{
    /**
     * @var HttpClientInterface
     */
    private $client;

    /**
     * @var string
     */
    private $endpoint;

    public function __construct(HttpClientInterface $client, string $akismetKey)
    {
        $this->client = $client;
        $this->endpoint = sprintf('https://%s.rest.akismet.com/1.1/comment-check', $akismetKey);
    }

    /**
     * @param ProductReview $review
     * @param array $context
     * @return int Spam score: 0: not spam, 1: maybe spam, 2: blatant spam
     * @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
     * @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
     */
    public function getSpamScore(ProductReview $review, array $context): int
    {
        $response = $this->client->request('POST', $this->endpoint, [
            'body' => array_merge($context, [
                'blog' => 'https://a-zumi.net',
                'comment_type' => 'comment',
                'comment_author' => $review->getReviewerName(),
                'comment_content' => $review->getComment(),
                'blog_lang' => 'ja',
                'blog_charset' => 'UTF-8',
                'is_test' => true // trueにするとテストモード。本番環境では外す

            ]),
        ]);

        $headers = $response->getHeaders();
        if ('discard' === ($headers['x-akismet-pro-tip'][0] ?? '')) {
            return 2;
        }

        $content = $response->getContent();
        if (isset($headers['x-akismet-debug-help'][0])) {
            throw new \RuntimeException(sprintf('Unable to check for spam: %s (%s).', $content, $headers['x-akismet-debug-help'][0]));
        }

        return 'true' === $content ? 1 : 0;
    }
}

getSpamScore() メソッドは API 呼び出しのレスポンスに応じて3つの値を返します。

  • 2: コメントが露骨なスパム
  • 1: コメントがスパムの可能性がある
  • 0: コメントがスパムではない

SpamChecker クラスを登録する

app/Customize/Resource/config 内に、以下の内容のservices.yamlを作成します。

services:
  Customize\Service\SpamChecker:
    arguments:
      - '@http_client'
      - "%env(AKISMET_KEY)%"

そして、.env にAkismet APIキーをセットしてください。

AKISMET_KEY=abcdefg

コメントがスパムかチェックする

ProductReviewTypeを拡張してコメントがスパムか判定する処理を追加します。

app/Customize/Form/Extension 以下に、ProductReviewTypeExtension を作成します。

<?php

namespace Customize\Form\Extension;

use Customize\Service\SpamChecker;
use Plugin\ProductReview4\Entity\ProductReview;
use Plugin\ProductReview4\Form\Type\ProductReviewType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\HttpFoundation\RequestStack;

class ProductReviewTypeExtension extends AbstractTypeExtension
{
    /**
     * @var SpamChecker
     */
    private $spamChecker;

    /**
     * @var RequestStack
     */
    private $requestStack;

    public function __construct(SpamChecker $spamChecker, RequestStack $requestStack)
    {
        $this->spamChecker = $spamChecker;
        $this->requestStack = $requestStack;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($request = $this->requestStack->getMasterRequest()) {
            $builder
                ->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use ($request) {
                    /** @var ProductReview $data */
                    $data = $event->getData();

                    $context = [
                        'user_ip' => $request->getClientIp(),
                        'user_agent' => $request->headers->get('user-agent'),
                        'referrer' => $request->headers->get('referer'),
                        'permalink' => $request->getUri()
                    ];
                    if (2 === $this->spamChecker->getSpamScore($data, $context)) {
                        throw new \RuntimeException('Blatant spam, go away!');
                    }
                });
        }
    }

    public static function getExtendedTypes(): iterable
    {
        yield ProductReviewType::class;
    }
}

以上で完成です。

お気軽にコメントをどうぞ

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください