Deploy Ladill Woo Manager / deploy (push) Failing after 2s
Standalone app with SSO shell, WordPress plugin connect flow, webhook ingest, fulfillment inbox, and plugin activation API — no payment processing. Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
3.5 KiB
PHP
103 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Support\Qr;
|
|
|
|
class QrScanReliability
|
|
{
|
|
/** @param array<string, mixed> $style */
|
|
public static function contrastRatio(array $style): float
|
|
{
|
|
$fg = self::relativeLuminance((string) ($style['foreground'] ?? '#000000'));
|
|
$bg = self::relativeLuminance((string) ($style['background'] ?? '#ffffff'));
|
|
|
|
$lighter = max($fg, $bg);
|
|
$darker = min($fg, $bg);
|
|
|
|
return ($lighter + 0.05) / ($darker + 0.05);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $style
|
|
* @return array{level: string, messages: list<string>}
|
|
*/
|
|
public static function assess(array $style): array
|
|
{
|
|
$style = QrStyleDefaults::merge($style);
|
|
$messages = [];
|
|
$level = 'good';
|
|
|
|
$contrast = self::contrastRatio($style);
|
|
$isClassic = self::isClassicBlackOnWhite($style);
|
|
$moduleStyle = (string) ($style['module_style'] ?? 'square');
|
|
$moduleMeta = QrModuleStyleCatalog::optionsFor($moduleStyle);
|
|
|
|
if ($moduleMeta['scan_risk'] === 'medium') {
|
|
$messages[] = 'This module style may not scan on every phone camera. Square is the safest choice.';
|
|
$level = 'fair';
|
|
}
|
|
|
|
if ($moduleMeta['scan_risk'] === 'high') {
|
|
$messages[] = 'Decorative styles like ' . $moduleMeta['label'] . ' often fail on Samsung Camera. Use square for print and signage.';
|
|
$level = 'poor';
|
|
}
|
|
|
|
if (! $isClassic) {
|
|
$messages[] = 'Colored QR codes often fail on Samsung Camera and other basic scanners. Black on white works everywhere.';
|
|
$level = $level === 'good' ? 'fair' : 'poor';
|
|
}
|
|
|
|
if ($contrast < 4.5) {
|
|
$messages[] = sprintf(
|
|
'Contrast is low (%.1f:1). Aim for at least 4.5:1 — dark foreground, white background.',
|
|
$contrast,
|
|
);
|
|
$level = 'poor';
|
|
}
|
|
|
|
if ($moduleMeta['scan_risk'] !== 'low' && ! $isClassic) {
|
|
$level = 'poor';
|
|
}
|
|
|
|
if ($messages === []) {
|
|
$messages[] = 'Black square modules on white give the best compatibility with Samsung, iPhone, and printed codes.';
|
|
}
|
|
|
|
return ['level' => $level, 'messages' => $messages];
|
|
}
|
|
|
|
/** @param array<string, mixed> $style */
|
|
public static function isClassicBlackOnWhite(array $style): bool
|
|
{
|
|
$fg = strtolower(ltrim((string) ($style['foreground'] ?? ''), '#'));
|
|
$bg = strtolower(ltrim((string) ($style['background'] ?? ''), '#'));
|
|
|
|
$fg = strlen($fg) === 3 ? $fg[0] . $fg[0] . $fg[1] . $fg[1] . $fg[2] . $fg[2] : $fg;
|
|
$bg = strlen($bg) === 3 ? $bg[0] . $bg[0] . $bg[1] . $bg[1] . $bg[2] . $bg[2] : $bg;
|
|
|
|
return in_array($fg, ['000000', '0f172a', '111111', '1a1a1a'], true)
|
|
&& in_array($bg, ['ffffff', 'fff'], true);
|
|
}
|
|
|
|
private static function relativeLuminance(string $hex): float
|
|
{
|
|
$hex = ltrim(trim($hex), '#');
|
|
if (strlen($hex) === 3) {
|
|
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
|
}
|
|
|
|
if (strlen($hex) !== 6 || ! ctype_xdigit($hex)) {
|
|
return 0.0;
|
|
}
|
|
|
|
$channels = [];
|
|
foreach ([0, 2, 4] as $offset) {
|
|
$value = hexdec(substr($hex, $offset, 2)) / 255;
|
|
$channels[] = $value <= 0.03928
|
|
? $value / 12.92
|
|
: (($value + 0.055) / 1.055) ** 2.4;
|
|
}
|
|
|
|
return (0.2126 * $channels[0]) + (0.7152 * $channels[1]) + (0.0722 * $channels[2]);
|
|
}
|
|
}
|