<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\AdminBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestMatcherInterface;

/**
 * Selects the `ferienpass_admin` firewall for both real admin URLs and for
 * `/_fragment` sub-requests dispatching an admin-bundle controller.
 *
 * The pattern-based form used before (`^/([a-z]{2,3}/)?admin($|/)`) works
 * for main requests but not for ESI sub-requests generated by
 * `render_esi(controller('Ferienpass\\AdminBundle\\Controller\\Fragment\\…'))`
 * — those URLs are `/_fragment?_path=…&_hash=…`, which the old pattern
 * doesn't cover. Without a matching firewall, Symfony treats the sub-request
 * as anonymous and the admin `UserNavigationController` silently renders an
 * empty dropdown for every logged-in admin.
 *
 * Safety: Symfony's `FragmentListener` (priority 48) validates the HMAC
 * signature on `/_fragment` URIs before this matcher runs at firewall time
 * (priority 8). An attacker cannot craft a `/_fragment` URL that dispatches
 * an admin controller without `kernel.secret`, so allowing the admin
 * firewall to match such URLs does not widen the attack surface.
 *
 * @see \Ferienpass\CmsBundle\EventListener\FragmentFrontendScopeListener
 *      For the mirror mechanism on the CMS (`contao_frontend`) side.
 */
final class AdminRequestMatcher implements RequestMatcherInterface
{
    private const string ADMIN_PATH_PATTERN = '#^/([a-z]{2,3}/)?admin($|/)#';
    private const string FRAGMENT_PATH = '/_fragment';
    private const ADMIN_CONTROLLER_PREFIX = 'Ferienpass\\AdminBundle\\Controller\\';

    public function matches(Request $request): bool
    {
        $path = $request->getPathInfo();

        if (1 === preg_match(self::ADMIN_PATH_PATTERN, $path)) {
            return true;
        }

        // ESI / inline sub-request dispatching an admin-bundle controller.
        if (self::FRAGMENT_PATH === $path) {
            $controller = (string) $request->attributes->get('_controller');

            return str_starts_with($controller, self::ADMIN_CONTROLLER_PREFIX);
        }

        return false;
    }
}
