Prerender.php 4.17 KB
<?php

namespace FootyRoom\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use FootyRoom\PrerenderService;

class Prerender
{
    /**
     * List of crawler user agents that will be.
     *
     * @var array
     */
    protected $crawlerUserAgents = [
        'Googlebot',
        'bingbot',
        'Yahoo',
        'YandexBot',
        'Baiduspider',
        'Twitterbot',
        'facebookexternalhit',
    ];

    /**
     * URI whitelist for prerendering pages only on this list.
     *
     * Whitelist paths or patterns. You can use asterix syntax, or regular
     * expressions (without start and end markers). If a whitelist is supplied,
     * only url's containing a whitelist path will be prerendered. An empty
     * array means that all URIs will pass this filter. Note that this is the
     * full request URI, so including starting slash and query parameter string.
     * See github.com/JeroenNoten/Laravel-Prerender for an example.
     *
     * @var array
     */
    protected $whitelist = [
        '/matches/*',
        '/lemix/*'
    ];

    /**
     * URI blacklist for prerendering pages that are not on the list.
     *
     * Blacklist paths to exclude. You can use asterix syntax, or regular
     * expressions (without start and end markers). If a blacklist is supplied,
     * all url's will be prerendered except ones containing a blacklist path. By
     * default, a set of asset extentions are included (this is actually only
     * necessary when you dynamically provide assets via routes). Note that this
     * is the full request URI, so including starting slash and query parameter
     * string. See github.com/JeroenNoten/Laravel-Prerender for an example.
     *
     * @var array
     */
    protected $blacklist = [];

    /**
     * @var \FootyRoom\PrerenderService
     */
    protected $prerenderService;

    public function __construct(PrerenderService $prerenderService)
    {
        $this->prerenderService = $prerenderService;
    }

    /**
     * Handles a request and prerender if it should, otherwise call the next middleware.
     *
     * @return Response
     */
    public function handle(Request $request, Closure $next)
    {
        if ($this->shouldShowPrerenderedPage($request)) {
            $response = $this->prerenderService->prerender($request->fullUrl(), false);

            if ($response) {
                return $response;
            }
        }

        // If prerender could not prerender it properly then let it handle as usual instead.
        return $next($request);
    }

    /**
     * Returns whether the request must be prerendered.
     */
    protected function shouldShowPrerenderedPage(Request $request): bool
    {
        if (!$request->isMethod('GET')) {
            return false;
        }

        $userAgent = $request->userAgent();

        // Prerender if a crawler is detected.
        if (!$userAgent || !$this->isUserAgentListed($userAgent)) {
            return false;
        }

        $requestUri = $request->getRequestUri();

        // Only check whitelist if it is not empty.
        if ($this->whitelist) {
            if (!$this->isListed([$requestUri], $this->whitelist)) {
                return false;
            }
        }

        // Only check blacklist if it is not empty.
        if ($this->blacklist) {
            if ($this->isListed([$requestUri], $this->blacklist)) {
                return false;
            }
        }

        // Okay! Prerender please.
        return true;
    }

    /**
     * Determine if given user agent needs prerendered response.
     */
    protected function isUserAgentListed($userAgent): bool
    {
        foreach ($this->crawlerUserAgents as $crawlerUserAgent) {
            if (str_contains($userAgent, $crawlerUserAgent)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check whether one or more needles are in the given list.
     */
    protected function isListed(array $needles, array $list): bool
    {
        foreach ($list as $pattern) {
            foreach ($needles as $needle) {
                if (str_is($pattern, $needle)) {
                    return true;
                }
            }
        }

        return false;
    }
}