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;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\Release;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||
use LIT\MCP\Integrations\Divi\Design\FontCatalog;
|
||||
use LIT\MCP\Integrations\Divi\Design\FontInstaller;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\ThemeOptionsAdapter;
|
||||
@@ -35,6 +36,7 @@ final class DiviAdapters
|
||||
GlobalVariablesAdapter::class,
|
||||
ThemeOptionsAdapter::class,
|
||||
FontCatalog::class,
|
||||
FontInstaller::class,
|
||||
DiviModules::class,
|
||||
DynamicContentMap::class,
|
||||
];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"customizer-typography": "0264fa5a2ba604542e1e1ad0c0339f04c8f4f67297087a0f92954ee63d343fd0",
|
||||
"font-install": "a6b7c0f26437f9ea63186138e130f3c226c0cc017d424807e1030b44c165ea88",
|
||||
"library-submit-layout": "594b3843d4c42e1a91e5eda06f108c79583a0036e46c92dd749f4f541e362d02",
|
||||
"preset-sync": "9586f8005b8065893c0350977a58ee9171bf715fff21ac0c84663cc512f45522",
|
||||
"sig:ET\\Builder\\Framework\\UserRole\\UserRole::can_current_user_use_visual_builder": "76d199c13adc6600c8a456225a8eb9db8037064f20eca9db597c4e45ccecf8d5",
|
||||
@@ -22,6 +23,10 @@
|
||||
"sig:ET\\Builder\\Packages\\ModuleLibrary\\ModuleRegistration::get_default_attrs": "728c9cddae0184ffccaff5b22a392b7a4e9c5f078b5d12d4c9717aca6af70e69",
|
||||
"sig:ET\\Builder\\Packages\\ModuleLibrary\\ModuleRegistration::get_module_settings": "f61b3693b0df17be1f068b8be424dcf56a67324372a92f576724cd564d992036",
|
||||
"sig:ET\\Builder\\Packages\\Module\\Layout\\Components\\DynamicContent\\DynamicContentOptions::get_options": "6d3932cbf7921be4f353ab71d2bd7200c2bf500577b401b32e9ca31809f07489",
|
||||
"sig:ET\\Builder\\VisualBuilder\\Fonts\\FontsUtility::font_add": "028c32cd79e1f33dceebf23c4c08d39c7b8062da01fb3006318ce21597d55103",
|
||||
"sig:ET\\Builder\\VisualBuilder\\Fonts\\FontsUtility::font_remove": "ab573a0776e9ba79e9606d3df9b1d2adb77e00e6eaf4120710a7649bcaf68ba0",
|
||||
"sig:ET\\Builder\\VisualBuilder\\Fonts\\FontsUtility::mime_types_font": "dfbf01f6750e3fa6c309f1200effcfda539d75c3404732cd5df939a8bff399ae",
|
||||
"sig:ET\\Builder\\VisualBuilder\\Hooks\\HooksRegistration::check_filetype_and_ext_font": "d98fd02bab7ba06af60ac0a99fbc717f916dd126d19c8f2e69779ab83d99902f",
|
||||
"sig:ET_Core_PageResource::remove_static_resources": "eaa39fd2c037c2d399578f71862da5c04b68d58a30657916482f95e94e5d8a96",
|
||||
"sig:et_": "ae7b0ec701e388b03d29d972195cb727fe1c8fd75dba4657ad71f1a963a32966",
|
||||
"sig:et_builder_enabled_for_post_type": "a6aed8043b2c07ef1082488fde706816cf0146847f5bac8acca79fb3fe5275fc",
|
||||
|
||||
Reference in New Issue
Block a user