Files
WP-LIT-MCP/src/Integrations/Divi/Design/FontInstaller.php
T

148 lines
6.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
use ET\Builder\VisualBuilder\Fonts\FontsUtility;
use ET\Builder\VisualBuilder\Hooks\HooksRegistration;
use LIT\MCP\Integrations\Divi\Release\Call;
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\Manifest;
use LIT\MCP\Integrations\Divi\Release\Mirror;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use RuntimeException;
defined('ABSPATH') || exit;
/**
* Adapter "Designsystem" (spec §3) for uploaded fonts (ruling W8): installs server-side temp files through
* FontsUtility::font_add() in-process with the upload action wp_handle_upload_rest – the way Divi's own AI agent
* does it (AiAgentImageUtility::install_to_custom_fonts(), mirrored as calling convention) – after checking every
* file with Divi's content check (check_filetype_and_ext_font). font_add() reports a partial success as success;
* install() therefore returns the formats Divi really stored, the caller compares and removes the font again.
* Not final so the upload tool can be tested with a fake.
*/
class FontInstaller implements DiviAdapter
{
/** Formats the plugin allows (ruling W8, no eot), in the order the tool lists them. */
public const FORMATS = ['woff2', 'woff', 'ttf', 'otf'];
private const UNCHECKED = ['ext' => false, 'type' => false, 'proper_filename' => false];
public static function manifest(): Manifest
{
return new Manifest(
Manifest::DESIGN,
calls: [
new Call(FontsUtility::class . '::mime_types_font', 0),
new Call(HooksRegistration::class . '::check_filetype_and_ext_font', 3),
new Call(FontsUtility::class . '::font_add', 4),
new Call(FontsUtility::class . '::font_remove', 1),
],
constants: [FontsUtility::class . '::FONT_NAME_MAX_LENGTH'],
// Read before font_add(): a corrupt list would make Divi lose the files it moved.
keys: ['et_uploaded_fonts'],
mirrors: [
new Mirror(
'font-install',
'includes/builder-5/server/VisualBuilder/AiAgent/AiAgentImageUtility.php',
'install_to_custom_fonts',
'AiAgentImageUtility',
'Aufrufkonvention von font_add() mit serverseitiger Temp-Datei (check_filetype_and_ext_font '
. 'vorab, wp_tempnam, action wp_handle_upload_rest)'
),
],
features: [DiviFeature::FontUpload]
);
}
/** @return list<string> FORMATS that Divi's font MIME list knows too. */
public function formats(): array
{
return array_values(array_intersect(self::FORMATS, array_keys(FontsUtility::mime_types_font())));
}
/** Divi's bound for a font name (FontNameRules::check() takes it). */
public function nameMaxLength(): int
{
return (int) FontsUtility::FONT_NAME_MAX_LENGTH;
}
/** Whether Divi's content check (finfo) takes $path as a font of $filename's extension. */
public function accepts(string $path, string $filename): bool
{
return $this->mimeOf($path, $filename) !== null;
}
/**
* @param array<string, string> $files Extension => temp file path (deleted or moved by WordPress).
* @return list<string> The extensions Divi stored – fewer than $files on a partial success.
* @throws DiviLocked When font-upload is not released – before anything is written.
* @throws RuntimeException When Divi's font list is corrupt or Divi refuses a file or the whole font; nothing is stored then.
*/
public function install(string $name, array $files, string $weights, string $family): array
{
ReleaseGate::assertFeature(DiviFeature::FontUpload);
if (! is_array(get_option('et_uploaded_fonts', []))) {
throw new RuntimeException(
'Divis Schriftenliste (et_uploaded_fonts) ist beschädigt; nichts wurde gespeichert. '
. 'Die Option in Divi bzw. der Datenbank prüfen.'
);
}
$uploads = [];
foreach ($files as $extension => $path) {
$filename = sanitize_file_name($name . '.' . $extension);
$mime = $this->mimeOf($path, $filename);
if ($mime === null) {
throw new RuntimeException(sprintf("font file %s refused by Divi's type check", $extension));
}
clearstatcache(true, $path);
$uploads[$extension] = [
'name' => $filename,
'type' => $mime,
'tmp_name' => $path,
'error' => 0,
'size' => (int) filesize($path),
];
}
$result = FontsUtility::font_add(
$uploads,
$name,
['font_weights' => $weights, 'generic_family' => $family],
['action' => 'wp_handle_upload_rest']
);
if (is_wp_error($result)) {
throw new RuntimeException('font_add refused: ' . $result->get_error_message());
}
$stored = is_array($result) ? ($result['updated_fonts'][$name]['font_file'] ?? null) : null;
return is_array($stored) ? array_map('strval', array_keys($stored)) : [];
}
/** @throws RuntimeException When Divi does not remove the font (e.g. it does not exist). */
public function remove(string $name): void
{
$result = FontsUtility::font_remove($name);
if (is_wp_error($result)) {
throw new RuntimeException('font_remove refused: ' . $result->get_error_message());
}
}
/** The MIME type Divi detected, or null when it refuses the content or names another extension. */
private function mimeOf(string $path, string $filename): ?string
{
$checked = HooksRegistration::check_filetype_and_ext_font(self::UNCHECKED, $path, $filename);
$extension = strtolower((string) ($checked['ext'] ?? ''));
$type = (string) ($checked['type'] ?? '');
return $type !== '' && $extension !== '' && $extension === strtolower(pathinfo($filename, PATHINFO_EXTENSION))
? $type
: null;
}
}