feat: FontInstaller – Schrift-Upload über FontsUtility::font_add im Prozess (Manifest, Mirror font-install, Signatur-Referenzen)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
775d611cfa
commit
ad76d5ae66
@@ -0,0 +1,139 @@
|
||||
<?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'],
|
||||
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 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);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user