fix: FontInstaller prüft Divis Schriftenliste vor font_add; leere Installation ist ein sauberer Rollback
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
901179de62
commit
2b45f1bdd3
@@ -411,6 +411,13 @@ final class DiviFontsUploadAbility implements Ability, RequiresDiviFeatures
|
|||||||
throw WriteFailed::rolledBack(self::REASON, $e, 'font upload: ' . $e->getMessage());
|
throw WriteFailed::rolledBack(self::REASON, $e, 'font upload: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($installed === []) {
|
||||||
|
throw WriteFailed::rolledBack(
|
||||||
|
self::REASON . ' Divi hat nichts gespeichert.',
|
||||||
|
detail: 'font upload: Divi stored nothing of ' . implode(',', array_keys($paths))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$missing = array_values(array_diff(array_keys($paths), $installed));
|
$missing = array_values(array_diff(array_keys($paths), $installed));
|
||||||
if ($missing === []) {
|
if ($missing === []) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ class FontInstaller implements DiviAdapter
|
|||||||
new Call(FontsUtility::class . '::font_remove', 1),
|
new Call(FontsUtility::class . '::font_remove', 1),
|
||||||
],
|
],
|
||||||
constants: [FontsUtility::class . '::FONT_NAME_MAX_LENGTH'],
|
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: [
|
mirrors: [
|
||||||
new Mirror(
|
new Mirror(
|
||||||
'font-install',
|
'font-install',
|
||||||
@@ -79,11 +81,17 @@ class FontInstaller implements DiviAdapter
|
|||||||
* @param array<string, string> $files Extension => temp file path (deleted or moved by WordPress).
|
* @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.
|
* @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 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.
|
* @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
|
public function install(string $name, array $files, string $weights, string $family): array
|
||||||
{
|
{
|
||||||
ReleaseGate::assertFeature(DiviFeature::FontUpload);
|
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 = [];
|
$uploads = [];
|
||||||
foreach ($files as $extension => $path) {
|
foreach ($files as $extension => $path) {
|
||||||
$filename = sanitize_file_name($name . '.' . $extension);
|
$filename = sanitize_file_name($name . '.' . $extension);
|
||||||
|
|||||||
@@ -464,6 +464,24 @@ final class DiviFontsUploadAbilityTest extends TestCase
|
|||||||
$this->assertTempFilesRemoved();
|
$this->assertTempFilesRemoved();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAnInstallThatStoredNothingIsACleanRollback(): void
|
||||||
|
{
|
||||||
|
$installer = self::installer();
|
||||||
|
$installer->allows('install')->andReturn([]);
|
||||||
|
$installer->expects('remove')->never();
|
||||||
|
$log = FakeChangeLog::partialsOnly();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->ability($installer, $log)->execute(self::input(['confirm' => true]));
|
||||||
|
self::fail('WriteFailed expected');
|
||||||
|
} catch (WriteFailed $e) {
|
||||||
|
self::assertTrue($e->rolledBack);
|
||||||
|
$error = self::assertToolError($e->toError(), 'write_failed');
|
||||||
|
self::assertStringContainsString('nichts gespeichert', $error['message']);
|
||||||
|
}
|
||||||
|
self::assertSame([], $log->partials);
|
||||||
|
}
|
||||||
|
|
||||||
public function testAPartialInstallThatCannotBeRemovedIsAPartialWrite(): void
|
public function testAPartialInstallThatCannotBeRemovedIsAPartialWrite(): void
|
||||||
{
|
{
|
||||||
$installer = self::installer();
|
$installer = self::installer();
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ final class FontInstallerTest extends TestCase
|
|||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
Functions\when('get_option')->justReturn([]);
|
||||||
Functions\when('sanitize_file_name')->alias(static fn (string $name): string => str_replace(' ', '-', $name));
|
Functions\when('sanitize_file_name')->alias(static fn (string $name): string => str_replace(' ', '-', $name));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ final class FontInstallerTest extends TestCase
|
|||||||
);
|
);
|
||||||
self::assertSame('AiAgentImageUtility::install_to_custom_fonts()', $manifest->mirrors[0]->label());
|
self::assertSame('AiAgentImageUtility::install_to_custom_fonts()', $manifest->mirrors[0]->label());
|
||||||
self::assertSame([DiviFeature::FontUpload], $manifest->features);
|
self::assertSame([DiviFeature::FontUpload], $manifest->features);
|
||||||
|
self::assertSame(['et_uploaded_fonts'], $manifest->keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInstallNeedsAReleasedDivi(): void
|
public function testInstallNeedsAReleasedDivi(): void
|
||||||
@@ -204,6 +206,21 @@ final class FontInstallerTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @runInSeparateProcess */
|
||||||
|
public function testACorruptFontListStopsTheInstallBeforeFontAdd(): void
|
||||||
|
{
|
||||||
|
$woff2 = $this->tempFile('wOF2DATA');
|
||||||
|
Functions\when('get_option')->justReturn('kaputt');
|
||||||
|
Mockery::mock('alias:' . self::HOOKS)->allows('check_filetype_and_ext_font')
|
||||||
|
->andReturn(['ext' => 'woff2', 'type' => 'font/woff2', 'proper_filename' => 'Inter.woff2']);
|
||||||
|
Mockery::mock('alias:' . self::FONTS)->expects('font_add')->never();
|
||||||
|
|
||||||
|
$this->expectException(RuntimeException::class);
|
||||||
|
$this->expectExceptionMessage('et_uploaded_fonts');
|
||||||
|
|
||||||
|
(new FontInstaller())->install('Inter', ['woff2' => $woff2], 'all', 'sans-serif');
|
||||||
|
}
|
||||||
|
|
||||||
/** @runInSeparateProcess */
|
/** @runInSeparateProcess */
|
||||||
public function testAFileDivisTypeCheckRefusesStopsTheInstallBeforeFontAdd(): void
|
public function testAFileDivisTypeCheckRefusesStopsTheInstallBeforeFontAdd(): void
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user