Shopware: Redirect old URLs

We migrated a Magento to Shopware and one of the things we had to do is find a solution to redirect the old URLs.

Easy solution: VHost config

Most of the time you can just add them to the VHost config, so they are loaded once with the apache and used by the apache. No better and faster way. Don’t put them in the .htaccess, because it is read and processed on each request.

This works if you have a couple or URLs, maybe a couple hundred.

Too many URLs – other solutions needed

But if you have more than that, in our case we are talking millions of URLs, because 10k+ products in a dozen languages and shops it makes a lot of URLs. Dimensions and multiplications are no good friends in IT, although exponential growth is even worse!

Our solution was to copy over the core_rewrite table to Shopware and have a controller listen on anything ending in .html, because the new URLs are all without html.

This can be done in .htaccess or VHost config as well!

You might say, and you are right. Unfortunately the new URL schema has a slash in the end for categories and no slash for products. And because we don’t know wether a URL is a product or a category, we need a way to distinguish. The magento database table knows.

RedirectController

How did we do it? pretty simple. The only challenge left was to know which store id in magento we are talking about and we kept this solution easy as well, we just hardcode it. Technically this is “one time code”, once Google learned, the new URLs, we can delete the whole module.

<?php

declare(strict_types=1);

namespace Winkelwagen\SeoRedirect\Storefront\Controller;

use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Storefront\Controller\StorefrontController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Winkelwagen\SeoRedirect\Entity\SeoRedirect\SeoRedirectEntity;

#[Route(defaults: ['_routeScope' => ['storefront']])]
class RedirectController extends StorefrontController
{
    public function __construct(private EntityRepository $wwSeoRedirectRepository)
    {
    }

    #[Route('{url}.html', name: 'winkelwagen_seoredirect_storefront_redirect_seoredirect', requirements: ['url' => '.+'])]
    public function seoRedirect(string $url, Request $request, SalesChannelContext $context): Response
    {
        $storeIds = $this->getStoreIdFrom($request->getHost());
        $criteria = new Criteria();
        $criteria->addFilter(
            new EqualsFilter('requestPath', $url . '.html'),
            new EqualsAnyFilter('storeId', $storeIds),
        );
        /** @var SeoRedirectEntity $redirect */
        $redirect = $this->wwSeoRedirectRepository->search($criteria, $context->getContext())->first();
        if (!$redirect) {
            return $this->redirect($this->generateUrl('frontend.home.page'), 301);
        }

        // Redirect
        if ($redirect->getOptions() === 'RP') {
            return $this->redirect('/' . $redirect->getTargetPath(), 301);
        }

        // SEO URL for product
        if ($redirect->getProductId()) {
            return $this->redirect('/' . $url, 301);
        }
        // SEO url for category
        return $this->redirect('/' . $url . '/', 301);
    }

    /**
     * get all store ids for the old magento hosts
     *
     * @return int[]
     */
    private function getStoreIdFrom(string $host): array
    {
        return array_keys(
            array_filter([
                0 => 'example.com',
                1 => 'example.org',
                6 => 'www.example.net',
            ], static function ($value) use ($host) {
                return $value === $host;
            }, ARRAY_FILTER_USE_BOTH)
        );
    }
}

As you can see we are a little sloppy with languages, because we are sure, every shop has only one language, but we don’t know which of the IDs it is.

Leave a Reply

Discover more from Winkelwagen

Subscribe now to keep reading and get access to the full archive.

Continue reading