- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
187 lines
5.4 KiB
PHP
187 lines
5.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\QrCode;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* QR Code Generator
|
|
*
|
|
* Main facade for generating QR codes from data.
|
|
* Orchestrates encoding, matrix generation, and rendering.
|
|
*/
|
|
final readonly class QrCodeGenerator
|
|
{
|
|
public function __construct(
|
|
private DataEncoder $dataEncoder,
|
|
private MatrixGenerator $matrixGenerator,
|
|
private SvgRenderer $svgRenderer
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Create QR code generator with default components
|
|
*/
|
|
public static function create(): self
|
|
{
|
|
return new self(
|
|
dataEncoder: new DataEncoder(),
|
|
matrixGenerator: new MatrixGenerator(),
|
|
svgRenderer: new SvgRenderer()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create QR code generator for TOTP URLs
|
|
*/
|
|
public static function forTotp(): self
|
|
{
|
|
return new self(
|
|
dataEncoder: new DataEncoder(),
|
|
matrixGenerator: new MatrixGenerator(),
|
|
svgRenderer: SvgRenderer::withModuleSize(4)->withQuietZone(2)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Generate QR code as SVG string
|
|
*/
|
|
public function generateSvg(
|
|
string $data,
|
|
?ErrorCorrectionLevel $errorLevel = null,
|
|
?QrCodeVersion $version = null
|
|
): string {
|
|
$matrix = $this->generateMatrix($data, $errorLevel, $version);
|
|
|
|
return $this->svgRenderer->render($matrix);
|
|
}
|
|
|
|
/**
|
|
* Generate QR code as SVG data URI
|
|
*/
|
|
public function generateDataUri(
|
|
string $data,
|
|
?ErrorCorrectionLevel $errorLevel = null,
|
|
?QrCodeVersion $version = null
|
|
): string {
|
|
$matrix = $this->generateMatrix($data, $errorLevel, $version);
|
|
|
|
return $this->svgRenderer->renderAsDataUri($matrix);
|
|
}
|
|
|
|
/**
|
|
* Generate QR code matrix
|
|
*/
|
|
public function generateMatrix(
|
|
string $data,
|
|
?ErrorCorrectionLevel $errorLevel = null,
|
|
?QrCodeVersion $version = null
|
|
): QrCodeMatrix {
|
|
if (empty($data)) {
|
|
throw new InvalidArgumentException('QR code data cannot be empty');
|
|
}
|
|
|
|
$errorLevel = $errorLevel ?? ErrorCorrectionLevel::forTotp();
|
|
$version = $version ?? $this->determineOptimalVersion($data, $errorLevel);
|
|
|
|
// Encode data into bit stream
|
|
$encodedData = $this->dataEncoder->encode($data, $version, $errorLevel);
|
|
|
|
// Generate matrix from encoded data
|
|
return $this->matrixGenerator->generateMatrix($encodedData, $version, $errorLevel);
|
|
}
|
|
|
|
/**
|
|
* Create generator with custom renderer
|
|
*/
|
|
public function withRenderer(SvgRenderer $renderer): self
|
|
{
|
|
return new self(
|
|
dataEncoder: $this->dataEncoder,
|
|
matrixGenerator: $this->matrixGenerator,
|
|
svgRenderer: $renderer
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get recommended configuration for data
|
|
*/
|
|
public function analyzeData(string $data): array
|
|
{
|
|
if (empty($data)) {
|
|
throw new InvalidArgumentException('Cannot analyze empty data');
|
|
}
|
|
|
|
$mode = DataMode::detectForData($data);
|
|
$errorLevel = ErrorCorrectionLevel::forTotp();
|
|
$version = $this->determineOptimalVersion($data, $errorLevel);
|
|
|
|
return [
|
|
'data_length' => strlen($data),
|
|
'detected_mode' => $mode,
|
|
'recommended_error_level' => $errorLevel,
|
|
'optimal_version' => $version->getVersion(),
|
|
'matrix_size' => $version->getModuleCount(),
|
|
'data_capacity' => $version->getDataCapacity($errorLevel),
|
|
'efficiency' => $mode->getEfficiency(),
|
|
'capacity_used' => round((strlen($data) / $version->getDataCapacity($errorLevel)) * 100, 1),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Generate QR code for TOTP URI
|
|
*/
|
|
public function generateTotpQrCode(string $totpUri): string
|
|
{
|
|
// Validate TOTP URI format
|
|
if (! str_starts_with($totpUri, 'otpauth://totp/')) {
|
|
throw new InvalidArgumentException('Invalid TOTP URI format');
|
|
}
|
|
|
|
// Use appropriate settings for TOTP (allow larger versions)
|
|
$errorLevel = ErrorCorrectionLevel::M; // Medium error correction for TOTP
|
|
|
|
return $this->generateSvg($totpUri, $errorLevel, null); // Auto-detect version
|
|
}
|
|
|
|
/**
|
|
* Determine the optimal QR code version for given data
|
|
*/
|
|
private function determineOptimalVersion(string $data, ErrorCorrectionLevel $errorLevel): QrCodeVersion
|
|
{
|
|
$dataLength = strlen($data);
|
|
|
|
// Try to find the smallest version that can fit the data
|
|
return QrCodeVersion::forDataLength($dataLength, $errorLevel);
|
|
}
|
|
|
|
/**
|
|
* Get generator configuration
|
|
*/
|
|
public function getConfiguration(): array
|
|
{
|
|
return [
|
|
'encoder' => DataEncoder::class,
|
|
'matrix_generator' => MatrixGenerator::class,
|
|
'renderer' => $this->svgRenderer->getConfiguration(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Validate data before encoding
|
|
*/
|
|
private function validateData(string $data): void
|
|
{
|
|
if (strlen($data) > 2953) { // Max capacity for version 40, error level L
|
|
throw new InvalidArgumentException('Data too large for QR code: ' . strlen($data) . ' bytes');
|
|
}
|
|
|
|
// Check for null bytes or other problematic characters
|
|
if (strpos($data, "\0") !== false) {
|
|
throw new InvalidArgumentException('Data contains null bytes');
|
|
}
|
|
}
|
|
}
|