Files
html/api/wem-screen-thumb.php
2026-04-17 22:35:01 +02:00

80 lines
3.1 KiB
PHP

<?php
// ============================================================
// wem-screen-thumb.php — Thumbnail cache for weval-enterprise-management.html
// Doctrine 84 : Premium UX tile previews (thumb + HTTP badge + real title)
// Created 17avr 2026 — Opus session
// ============================================================
// Pipeline :
// GET /api/wem-screen-thumb.php?path=ethica-chatbot.html
// → hash(path) → /var/www/html/api/screenshots/wem/<hash>.png
// → si cache fresh (< 6h) → serve PNG
// → sinon → background trigger Playwright script → serve placeholder OR queue
// → fallback 404 → return 204 empty PNG (so img tag shows nothing, not broken)
// ============================================================
// SAFETY : read-only shell_exec with hash, zéro injection, whitelist paths, cache 6h
// ============================================================
header('Cache-Control: public, max-age=21600'); // 6h browser cache too
$path = $_GET['path'] ?? '';
if (!$path || strlen($path) > 250) { http_response_code(400); exit; }
// Security: path must be safe (alnum + dash + dot + slash + underscore only)
if (!preg_match('/^[a-zA-Z0-9\/_\-\.]+$/', $path)) { http_response_code(400); exit; }
// Reject path traversal
if (strpos($path, '..') !== false) { http_response_code(400); exit; }
$path = ltrim($path, '/');
// Detect domain prefix (wevads/, ethica/) → build full URL for worker
$worker_arg = $path;
if (strpos($path, 'wevads/') === 0) {
$worker_arg = 'https://wevads.weval-consulting.com/' . substr($path, 7);
} elseif (strpos($path, 'ethica/') === 0) {
$worker_arg = 'https://ethica.wevup.app/' . substr($path, 7);
}
// else: bare path → worker treats as S204-local
// Hash uses the key (= path with prefix) so it matches worker
$hash = md5($path);
$thumb = "/var/www/html/api/screenshots/wem/{$hash}.png";
$now = time();
$ttl = 21600; // 6h
$fresh = file_exists($thumb) && ($now - filemtime($thumb)) < $ttl;
if ($fresh) {
header('Content-Type: image/png');
header('X-Cache: HIT');
header('X-Thumb-Age: ' . ($now - filemtime($thumb)));
readfile($thumb);
exit;
}
// Cache miss OR stale — trigger background generation, return placeholder or old cache
$exists_stale = file_exists($thumb);
$lockfile = "/tmp/wem-thumb-{$hash}.lock";
// Only trigger if no lock (prevent stampede)
if (!file_exists($lockfile) || (time() - filemtime($lockfile)) > 120) {
@touch($lockfile);
// Fire-and-forget Playwright capture (worker handles URL resolution)
$cmd = "nohup python3 /var/www/html/api/wem-thumb-worker.py " . escapeshellarg($worker_arg) . " > /tmp/wem-thumb.log 2>&1 &";
@shell_exec($cmd);
}
if ($exists_stale) {
// Serve stale version while fresh one generates
header('Content-Type: image/png');
header('X-Cache: STALE-REFRESHING');
readfile($thumb);
exit;
}
// No cache at all — serve 1x1 transparent PNG placeholder (tile will show skeleton)
header('Content-Type: image/png');
header('X-Cache: MISS-QUEUED');
// 1x1 transparent PNG
echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=');