【EC-CUBE4】1日1回のバッチ処理でsitemap.xmlを自動作成する

EC-CUBE WEB

検索エンジンのためのXMLサイトマップ。作ってサーバにアップしてますか?
主にGoogleのクローラーにサイト内にどんなページが有るか通知する役割ですが、EC-CUBEだと商品が非表示や廃盤になった時にSearchConsoleから「このID◯◯の商品ページ、ないですやん」とお叱りを受けることがあります。

その度にsitemap.xmlを作り直してサーバにアップするというのが面倒なので、1日1回自動的にsitemap.xmlを作るようにしました。
まあ、単純に特商ページなどの固定ページやユーザページ、商品一覧や商品詳細ページを検索して出力させてるだけなんですがね…

サイトマップ作成のプラグインは売られていたと思いますので、しっかりと確実性を重視するならプラグインの購入をおすすめします。
<?php

namespace Customize\Command;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

use Eccube\Common\EccubeConfig;
use Eccube\Entity\Page;
use Eccube\Repository\CategoryRepository;
use Eccube\Repository\ProductRepository;
use Eccube\Repository\PageRepository;

/**
 * sitemap.xmlを作成する。
 */
class SitemapCreateCommand extends Command
{
    protected static $defaultName = 'eccube:customize:sitemap-create';

    protected $eccubeConfig;
    protected $categoryRepository;
    protected $productRepository;
    protected $entityManager;

    protected $pageRepository;
    protected $router;

    public function __construct(
        EccubeConfig $eccubeConfig,
        CategoryRepository $categoryRepository,
        EntityManagerInterface $entityManager,
        ProductRepository $productRepository,
        PageRepository $pageRepository,
        ContainerInterface $container
    ) {
        parent::__construct();
        $this->eccubeConfig  = $eccubeConfig;
        $this->categoryRepository = $categoryRepository;
        $this->productRepository = $productRepository;
        $this->entityManager = $entityManager;
        $this->pageRepository = $pageRepository;
        $this->router = $container->get('router');
    }

    protected function configure()
    {
        $this
            ->setDescription('Sitemap create.');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {

        $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            .  "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";

        // ドメインを指定
        $homepage = "https://example.com";

        $notPage = [
            'product_list',
            'product_detail',
            'contact_complete',
            'entry_complete',
            'entry_activate',
            'shopping_login',
            'shopping_nonmember',
            '404',
        ];

        // ページ
        $qb = $this->pageRepository->createQueryBuilder('p')
            ->andWhere('p.id != :preview')
            ->andWhere('p.url NOT IN (:urls)')
            ->andWhere('p.meta_robots IS NULL')
            ->setParameter('preview', 0)
            ->setParameter('urls', $notPage);

        $Pages = $qb->getQuery()->getResult();

        foreach($Pages as $Page) {

            if ($Page->getEditType() == Page::EDIT_TYPE_DEFAULT) {

                $url = $homepage . $this->router->getRouteCollection()->get($Page->getUrl())->getPath();

            } else {

                $url = $homepage . "/" . $this->eccubeConfig["eccube_user_data_route"] . "/" . $Page->getUrl();
            }

            $priority = ($Page->getUrl() === "homepage") ? 1.0 : 0.8;

            $date = new \DateTime($Page->getUpdateDate()->format('Y-m-d H:i:s'), new \DateTimeZone('Asia/Tokyo'));
            $date->setTimezone(new \DateTimeZone('UTC'));
            $lastmod = $date->format(\DateTime::ATOM);

            $xml .= "  <url>\n"
                .   "    <loc>{$url}</loc>\n"
                .   "    <lastmod>{$lastmod}</lastmod>\n"
                .   "    <priority>{$priority}</priority>\n"
                .   "  </url>\n";
        }

        // カテゴリ
        $Categories = $this->categoryRepository->getList(null, true);

        foreach($Categories as $Category) {

            $catID = $Category->getId();

            $url = $homepage . "/products/list?category_id={$catID}";

            $date = new \DateTime($Category->getUpdateDate()->format('Y-m-d H:i:s'), new \DateTimeZone('Asia/Tokyo'));
            $date->setTimezone(new \DateTimeZone('UTC'));
            $lastmod = $date->format(\DateTime::ATOM);

            $xml .= "  <url>\n"
                .   "    <loc>{$url}</loc>\n"
                .   "    <lastmod>{$lastmod}</lastmod>\n"
                .   "    <priority>0.6</priority>\n"
                .   "  </url>\n";
        }

        // 商品
        $Products = $this->productRepository->findBy(['Status' => 1]);

        foreach($Products as $Product) {

            $ProductID = $Product->getId();

            $url = $homepage . "/products/detail/{$ProductID}";

            $date = new \DateTime($Product->getUpdateDate()->format('Y-m-d H:i:s'), new \DateTimeZone('Asia/Tokyo'));
            $date->setTimezone(new \DateTimeZone('UTC'));
            $lastmod = $date->format(\DateTime::ATOM);

            $xml .= "  <url>\n"
                .   "    <loc>{$url}</loc>\n"
                .   "    <lastmod>{$lastmod}</lastmod>\n"
                .   "    <priority>0.6</priority>\n"
                .   "  </url>\n";
        }

        $xml .= '</urlset>';

        file_put_contents($this->eccubeConfig["xml_dir"], $xml);
    }
}
※priorityなどは適当に設定してますので、この通りにしないとダメというわけではありません。

軽く解説。
通常ページは、プレビューページや「meta_robots」がNULL(空欄)のもののみ取得(noindexなどが入っていたら取得しない)。
あとは配列に取得したくないurlsを指定してあげてます。
ここに書かないと決済サービスなどのページが出力されてしまいます。

ページは、EC-CUBE固定のページ(Controller付きページ)とユーザ作成ページではURLの表示の仕方が違うので、それぞれの方法でURLを取得。
カテゴリはそのままIDを取得。
商品はステータスIDが「1」(表示中)のもののみ取得。

lastmodは更新日時を「YYYY-MM-DDThh:mmTZD」形式で作成。
(何気にここが一番苦労した…)

それを繋ぎ合わせて指定ファイルに出力するといった単純なものです。
$this->eccubeConfig[“xml_dir”] はsitemap.xmlの保存場所を予め指定しています。
(大抵ルート直下だと思いますが)

あとはcronを設定して1日1回動かしてやるだけです。
sitemap.xmlのパーミッションなどを変更したい場合は、最後に「chmod」などしてやるといいかと思います。

サイトマップを作るタイミングによってはSearchConsoleからお叱りをいただくかもしれませんが、これで手間はだいぶ省けるはずです。

タイトルとURLをコピーしました