Checkpoint: headless JSON architecture documented + cleanup (Stufe 1+2)
Restore point before Stufe 3 (central resolver-service refactoring). Includes this session's work: - Success Story (usecase) rebuild: new fields/tabs, UsecaseSerializer, thin List/Show renderers, MM relations, inline content elements. - vitec_columns content block (two-column layout with per-item content) incl. unified header section; special-cased JSON resolution. - Container per-column flex (items[].config align/justify) + gap on parent. - Unified header section across CEs/plugins/containers; header fields in JSON. - ISO-style architecture spec: Documentation/Headless-JSON-Architecture.md. - Cleanup: removed local scratch + verified .bak backups. - Fixes: B-6 (undefined thumbnail variable in Downloadcardcollection image resolver), B-7 (unsatisfiable legacy CType OR branch in Downloadcard/Datasheets). Rollback: git reset --hard snapshot-2026-07-09-pre-stufe3 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
c6759186cb
commit
bb7c03235d
237 files changed
+6698
-1749
No files matched your search
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Fallback resolver for deleted versioned files in fileadmin/downloads/Collateral.
|
||||
*
|
||||
* Expected requested filename format:
|
||||
* [fileprefix]__[filetype]__[number]-[letter].ext
|
||||
*
|
||||
* If a matching file no longer exists, this resolver redirects to the newest
|
||||
* available version (highest number, then highest letter sequence).
|
||||
*/
|
||||
|
||||
$requestedRaw = isset($_GET['requested']) ? (string)$_GET['requested'] : '';
|
||||
$requestedRaw = str_replace('\\', '/', $requestedRaw);
|
||||
$requestedFilename = basename(rawurldecode($requestedRaw));
|
||||
|
||||
if ($requestedFilename === '' || $requestedFilename === '.' || $requestedFilename === '..') {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Not found';
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!preg_match('/^(?P<prefix>.+?)__(?P<type>.+?)__(?P<number>\\d+)-(?P<letters>[A-Za-z]+)\\.(?P<ext>[A-Za-z0-9]+)$/', $requestedFilename, $parts)) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Not found';
|
||||
exit;
|
||||
}
|
||||
|
||||
$prefix = (string)$parts['prefix'];
|
||||
$type = (string)$parts['type'];
|
||||
|
||||
$baseDir = __DIR__ . '/fileadmin/downloads/Collateral';
|
||||
if (!is_dir($baseDir) || !is_readable($baseDir)) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Not found';
|
||||
exit;
|
||||
}
|
||||
|
||||
$escapedPrefix = preg_quote($prefix, '/');
|
||||
$escapedType = preg_quote($type, '/');
|
||||
$pattern = '/^' . $escapedPrefix . '__' . $escapedType . '__(\\d+)-([A-Za-z]+)\\.([A-Za-z0-9]+)$/';
|
||||
|
||||
$bestFilename = null;
|
||||
$bestNumber = -1;
|
||||
$bestLetterRank = -1;
|
||||
|
||||
$entries = scandir($baseDir);
|
||||
if ($entries === false) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Not found';
|
||||
exit;
|
||||
}
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!is_string($entry) || $entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!preg_match($pattern, $entry, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$number = (int)$matches[1];
|
||||
$letterRank = letterSequenceToRank((string)$matches[2]);
|
||||
|
||||
if ($number > $bestNumber || ($number === $bestNumber && $letterRank > $bestLetterRank)) {
|
||||
$bestNumber = $number;
|
||||
$bestLetterRank = $letterRank;
|
||||
$bestFilename = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bestFilename === null) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Not found';
|
||||
exit;
|
||||
}
|
||||
|
||||
$bestPath = $baseDir . '/' . $bestFilename;
|
||||
if (!is_file($bestPath) || !is_readable($bestPath)) {
|
||||
http_response_code(404);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo 'Not found';
|
||||
exit;
|
||||
}
|
||||
|
||||
$clientIp = isset($_SERVER['REMOTE_ADDR']) ? (string)$_SERVER['REMOTE_ADDR'] : '-';
|
||||
$userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? (string)$_SERVER['HTTP_USER_AGENT'] : '-';
|
||||
logFallbackRecovery($requestedFilename, $bestFilename, $clientIp, $userAgent);
|
||||
|
||||
$target = '/fileadmin/downloads/Collateral/' . rawurlencode($bestFilename);
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Pragma: no-cache');
|
||||
header('Location: ' . $target, true, 302);
|
||||
exit;
|
||||
|
||||
function letterSequenceToRank(string $letters): int
|
||||
{
|
||||
$rank = 0;
|
||||
$letters = strtoupper($letters);
|
||||
$length = strlen($letters);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$char = ord($letters[$i]);
|
||||
if ($char < 65 || $char > 90) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rank = ($rank * 26) + ($char - 64);
|
||||
}
|
||||
|
||||
return $rank;
|
||||
}
|
||||
|
||||
function logFallbackRecovery(string $requested, string $resolved, string $clientIp, string $userAgent): void
|
||||
{
|
||||
$logDir = dirname(__DIR__) . '/var/log';
|
||||
if (!is_dir($logDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = gmdate('c');
|
||||
$line = sprintf(
|
||||
"%s collateral-fallback requested=\"%s\" resolved=\"%s\" ip=%s ua=\"%s\"\n",
|
||||
$timestamp,
|
||||
str_replace('"', '\\"', $requested),
|
||||
str_replace('"', '\\"', $resolved),
|
||||
$clientIp,
|
||||
str_replace('"', '\\"', $userAgent)
|
||||
);
|
||||
|
||||
@file_put_contents($logDir . '/collateral-fallback.log', $line, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
Reference in new issue
Block a user