This commit is contained in:
2026-01-14 01:50:50 +01:00
parent 62da2a67af
commit d49c326c90
9 changed files with 343 additions and 37 deletions

View File

@@ -1081,6 +1081,9 @@ class ApiKernel
case 'account.bridge.test':
$this->handleAccountBridgeTest();
break;
case 'account.fonts.list':
$this->handleAccountFontsList();
break;
case 'placeholders.status':
$this->handlePlaceholderStatus();
break;
@@ -2072,12 +2075,18 @@ class ApiKernel
'password_key' => (string)($this->in['config_password_key'] ?? ''),
'charset_key' => (string)($this->in['config_charset_key'] ?? ''),
];
$fonts = [
'dir' => (string)($this->in['fonts_dir'] ?? ''),
'url_base' => (string)($this->in['fonts_url_base'] ?? ''),
'urls' => (string)($this->in['fonts_urls'] ?? ''),
];
$setup = $this->sanitizeBridgeSetup([
'tables' => $tables,
'mode' => $mode,
'direct' => $direct,
'config' => $config,
'fonts' => $fonts,
]);
$stored = $this->saveBridgeSetupData($customerId, $setup);
@@ -2114,6 +2123,20 @@ class ApiKernel
]);
}
private function handleAccountFontsList(): void
{
$user = $this->requireAuth();
$customerId = (int)($user['customer_id'] ?? 0);
$setup = $this->getBridgeSetupData($customerId);
$fonts = $setup['fonts'] ?? [];
$payload = $this->buildFontCatalog($fonts);
$this->respond([
'ok' => true,
'fonts' => $payload['fonts'],
'font_face_css' => $payload['font_face_css'],
]);
}
private function handleDebugPhpInfo(): void
{
$user = $this->requireAuth();
@@ -2315,6 +2338,11 @@ class ApiKernel
'password_key' => '',
'charset_key' => '',
],
'fonts' => [
'dir' => '',
'url_base' => '',
'urls' => '',
],
];
}
@@ -2331,11 +2359,28 @@ class ApiKernel
$tables = $this->normalizeBridgeTables($input['tables'] ?? []);
$direct = $input['direct'] ?? [];
$config = $input['config'] ?? [];
$fonts = $input['fonts'] ?? [];
$sanitizePath = function ($value) {
$value = trim((string)$value);
if ($value === '') return '';
return preg_replace('/[^a-zA-Z0-9_\.\-]/', '', $value) ?: '';
};
$sanitizeDir = function ($value) {
$value = trim((string)$value);
if ($value === '') return '';
return trim(preg_replace('/[^a-zA-Z0-9_\.\-\/\\\\:\s]/', '', $value)) ?: '';
};
$sanitizeUrl = function ($value) {
$value = trim((string)$value);
if ($value === '') return '';
$value = preg_replace('/[\x00-\x1f]/', '', $value);
return $value;
};
$sanitizeText = function ($value) {
$value = (string)$value;
$value = preg_replace('/[\x00-\x08\x0b\x0c\x0e-\x1f]/', '', $value);
return $value;
};
$result = [
'tables' => $tables,
'mode' => $mode,
@@ -2357,6 +2402,11 @@ class ApiKernel
'password_key' => $sanitizePath($config['password_key'] ?? ''),
'charset_key' => $sanitizePath($config['charset_key'] ?? ''),
],
'fonts' => [
'dir' => $sanitizeDir($fonts['dir'] ?? ''),
'url_base' => $sanitizeUrl($fonts['url_base'] ?? ''),
'urls' => $sanitizeText($fonts['urls'] ?? ''),
],
];
if ($result['direct']['port'] <= 0) {
$result['direct']['port'] = 3306;
@@ -2364,6 +2414,93 @@ class ApiKernel
return $result;
}
private function buildFontCatalog(array $fonts): array
{
$items = [];
$faces = [];
$seen = [];
$groups = [];
$allowed = ['woff2', 'woff', 'ttf', 'otf'];
$addGroup = function (string $family, array $sources) use (&$items, &$faces, &$seen, $allowed) {
$family = trim($family);
if ($family === '') return;
if (!empty($seen[strtolower($family)])) return;
$srcParts = [];
foreach ($allowed as $ext) {
if (!empty($sources[$ext])) {
$url = $sources[$ext];
$srcParts[] = "url('{$url}') format('{$ext}')";
}
}
if (!$srcParts) return;
$safeFamily = str_replace("'", "\\'", $family);
$faces[] = "@font-face{font-family:'{$safeFamily}';font-style:normal;font-weight:400;src:" . implode(',', $srcParts) . ";}";
$items[] = [
'label' => $family,
'value' => "'" . $safeFamily . "', sans-serif",
];
$seen[strtolower($family)] = true;
};
$inferFamily = function (string $name): string {
$name = preg_replace('/[-_]+/', ' ', $name);
$name = preg_replace('/\s+/', ' ', $name);
return trim($name);
};
$dir = trim((string)($fonts['dir'] ?? ''));
$base = trim((string)($fonts['url_base'] ?? ''));
if ($dir !== '' && $base !== '' && is_dir($dir)) {
$base = rtrim($base, '/');
$pattern = $dir . '/*.{woff2,woff,ttf,otf,WOFF2,WOFF,TTF,OTF}';
$files = glob($pattern, GLOB_BRACE) ?: [];
foreach ($files as $file) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) continue;
$name = pathinfo($file, PATHINFO_FILENAME);
$groups[$name][$ext] = $base . '/' . basename($file);
}
}
$rawUrls = (string)($fonts['urls'] ?? '');
if ($rawUrls !== '') {
$lines = preg_split('/\r?\n/', $rawUrls) ?: [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') continue;
$family = '';
$url = '';
if (strpos($line, '|') !== false) {
[$family, $url] = array_map('trim', explode('|', $line, 2));
} elseif (strpos($line, '=') !== false) {
[$family, $url] = array_map('trim', explode('=', $line, 2));
} else {
$url = $line;
}
if ($url === '') continue;
$path = parse_url($url, PHP_URL_PATH);
if (!$path) continue;
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) continue;
$name = pathinfo($path, PATHINFO_FILENAME);
$family = $family !== '' ? $family : $inferFamily($name);
if ($family === '') continue;
$groups[$family][$ext] = $url;
}
}
foreach ($groups as $family => $sources) {
$display = $inferFamily($family);
$addGroup($display, $sources);
}
return [
'fonts' => $items,
'font_face_css' => implode("\n", $faces),
];
}
private function customerSettingsTable(): string
{
return 'emailtemplate_customer_settings';