80 lines
3.1 KiB
PHP
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=');
|