HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/QrCodeController.php
<?php

namespace App\Http\Controllers\Refactor;

use App\Http\Controllers\Controller;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\Fill;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Renderer\Color\Rgb;
use BaconQrCode\Writer;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Validator;

/**
 * SVG QR rendering — generic host endpoint reusable across the app.
 *
 * Primary consumer: mc-deeplink-button (C5, messenger W3) renders QR
 * for Telegram bot deep-links + WhatsApp wa.me click-to-chat links.
 *
 * Returns SVG with theme-appropriate fg color hardcoded. Transparent
 * background so the QR composes against any card surface.
 *
 * Rate-limited via the `throttle:qr` middleware (60/min per session)
 * to prevent bulk-generation abuse. Behind auth — public-facing flows
 * (W3 S3.5/S3.6 polling pages) will swap to a separate signed-route
 * endpoint when those pages ship, since this one assumes authenticated
 * customer/admin context.
 */
class QrCodeController extends Controller
{
    private const MIN_SIZE = 80;
    private const MAX_SIZE = 400;
    private const MAX_URL_LENGTH = 2048;

    public function render(Request $request): Response
    {
        $validated = Validator::make($request->all(), [
            'url'   => ['required', 'string', 'max:' . self::MAX_URL_LENGTH],
            'size'  => ['nullable', 'integer', 'between:' . self::MIN_SIZE . ',' . self::MAX_SIZE],
            'theme' => ['nullable', 'in:light,dark'],
        ])->validate();

        $url  = $validated['url'];
        $size = (int) ($validated['size'] ?? 160);
        // Note: `theme` param is accepted for backwards-compat with existing
        // callers but is no longer respected. QR codes ship pure-black-on-white
        // universally (Google Auth / Stripe / banking-app convention). The
        // historical theme-aware variant produced white-on-white render bugs
        // when the OS's prefers-color-scheme disagreed with the app's
        // <html data-theme="..."> attribute (trap L72).

        $fg = new Rgb(0, 0, 0);
        $bg = new Rgb(255, 255, 255);

        $renderer = new ImageRenderer(
            (new RendererStyle($size, 1, null, null, Fill::uniformColor($bg, $fg)))
                ->withSize($size)
                ->withMargin(1),
            new SvgImageBackEnd()
        );

        $writer = new Writer($renderer);
        $svg = $writer->writeString($url, 'utf-8');

        // Keep bacon's white background rect intact — gives the QR its own
        // white frame for max scannability against ANY surrounding surface
        // (light or dark theme). Just inject a11y attributes.
        $svg = $this->injectAccessibility($svg, $url);

        return response($svg, 200, [
            'Content-Type'  => 'image/svg+xml; charset=utf-8',
            'Cache-Control' => 'public, max-age=86400, immutable',
            // No CSP frame-ancestors etc.; the SVG is fully self-contained.
        ]);
    }

    /**
     * Append `role="img"` + `aria-label="QR code linking to {url}"` to the
     * root <svg> tag for screen-reader UX. bacon doesn't emit these by default.
     */
    private function injectAccessibility(string $svg, string $url): string
    {
        $label = 'QR code linking to ' . $this->truncateUrlForLabel($url);
        return preg_replace(
            '/<svg\b([^>]*)>/i',
            '<svg$1 role="img" aria-label="' . htmlspecialchars($label, ENT_QUOTES | ENT_HTML5, 'UTF-8') . '">',
            $svg,
            1
        ) ?? $svg;
    }

    private function truncateUrlForLabel(string $url): string
    {
        if (strlen($url) <= 80) {
            return $url;
        }
        return substr($url, 0, 77) . '...';
    }
}