false, 'type' => false, 'proper_filename' => false]; /** @var list */ private array $files = []; protected function setUp(): void { parent::setUp(); Functions\when('get_option')->justReturn([]); Functions\when('sanitize_file_name')->alias(static fn (string $name): string => str_replace(' ', '-', $name)); } protected function tearDown(): void { foreach ($this->files as $file) { if (is_file($file)) { unlink($file); } } parent::tearDown(); } private function tempFile(string $bytes): string { $file = (string) tempnam(sys_get_temp_dir(), 'lit-font'); file_put_contents($file, $bytes); $this->files[] = $file; return $file; } public function testIsARegisteredDesignAdapterForFontUpload(): void { $manifest = FontInstaller::manifest(); self::assertContains(FontInstaller::class, DiviAdapters::CLASSES); self::assertSame(Manifest::DESIGN, $manifest->area); self::assertSame( ['FontsUtility::mime_types_font()', 'HooksRegistration::check_filetype_and_ext_font()', 'FontsUtility::font_add()', 'FontsUtility::font_remove()'], array_map(static fn (Call $call): string => $call->label(), $manifest->calls) ); self::assertSame([0, 3, 4, 1], array_map(static fn (Call $call): ?int => $call->args, $manifest->calls)); self::assertSame( [self::FONTS . '::FONT_NAME_MAX_LENGTH'], array_map(static fn (ConstantRequirement $constant): string => $constant->name, $manifest->constants) ); self::assertCount(1, $manifest->mirrors); self::assertSame('font-install', $manifest->mirrors[0]->id); self::assertSame( 'includes/builder-5/server/VisualBuilder/AiAgent/AiAgentImageUtility.php', $manifest->mirrors[0]->file ); self::assertSame('AiAgentImageUtility::install_to_custom_fonts()', $manifest->mirrors[0]->label()); self::assertSame([DiviFeature::FontUpload], $manifest->features); self::assertSame(['et_uploaded_fonts'], $manifest->keys); } public function testInstallNeedsAReleasedDivi(): void { ReleaseGate::useResolver( static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0') ); $this->expectException(DiviLocked::class); (new FontInstaller())->install('Inter', ['woff2' => '/tmp/x'], 'all', 'sans-serif'); } /** @runInSeparateProcess */ public function testFormatsAreThePluginsFourAsFarAsDiviKnowsThem(): void { Mockery::mock('alias:' . self::FONTS)->allows('mime_types_font')->andReturn([ 'otf' => ['font/otf'], 'ttf' => ['font/ttf'], 'woff' => ['font/woff'], 'eot' => ['application/vnd.ms-fontobject'], ]); self::assertSame(['woff', 'ttf', 'otf'], (new FontInstaller())->formats(), 'no woff2 in Divi, never eot'); } /** @runInSeparateProcess */ public function testTheNameLimitIsDivis(): void { Mockery::getConfiguration()->setConstantsMap([self::FONTS => ['FONT_NAME_MAX_LENGTH' => 80]]); Mockery::mock('alias:' . self::FONTS); self::assertSame(80, (new FontInstaller())->nameMaxLength()); } /** @runInSeparateProcess */ public function testAcceptsAsksDivisContentCheckAndWantsTheGivenExtensionBack(): void { $file = $this->tempFile('wOF2DATA'); $hooks = Mockery::mock('alias:' . self::HOOKS); $hooks->expects('check_filetype_and_ext_font')->with(self::UNCHECKED, $file, 'Inter.woff2') ->andReturn(['ext' => 'woff2', 'type' => 'font/woff2', 'proper_filename' => 'Inter.woff2']); $hooks->expects('check_filetype_and_ext_font')->with(self::UNCHECKED, $file, 'Inter.ttf') ->andReturn(['ext' => 'woff2', 'type' => 'font/woff2', 'proper_filename' => 'Inter.ttf']); $hooks->expects('check_filetype_and_ext_font')->with(self::UNCHECKED, $file, 'Inter.otf') ->andReturn(self::UNCHECKED); $installer = new FontInstaller(); self::assertTrue($installer->accepts($file, 'Inter.woff2')); self::assertFalse($installer->accepts($file, 'Inter.ttf'), 'Divi reports another extension'); self::assertFalse($installer->accepts($file, 'Inter.otf'), 'Divi refuses the content'); } /** @runInSeparateProcess */ public function testInstallHandsTheTempFilesToFontAddLikeDivisAiAgent(): void { $woff2 = $this->tempFile('wOF2DATA'); $ttf = $this->tempFile("\x00\x01\x00\x00TTF"); $hooks = Mockery::mock('alias:' . self::HOOKS); $hooks->allows('check_filetype_and_ext_font')->andReturnUsing( static fn (array $unchecked, string $file, string $name): array => [ 'ext' => pathinfo($name, PATHINFO_EXTENSION), 'type' => 'font/' . pathinfo($name, PATHINFO_EXTENSION), 'proper_filename' => $name, ] ); Mockery::mock('alias:' . self::FONTS)->expects('font_add')->with( [ 'woff2' => ['name' => 'Inter-Display.woff2', 'type' => 'font/woff2', 'tmp_name' => $woff2, 'error' => 0, 'size' => 8], 'ttf' => ['name' => 'Inter-Display.ttf', 'type' => 'font/ttf', 'tmp_name' => $ttf, 'error' => 0, 'size' => 7], ], 'Inter Display', ['font_weights' => '400,700', 'generic_family' => 'serif'], ['action' => 'wp_handle_upload_rest'] )->andReturn([ 'uploaded_font' => 'Inter Display', 'updated_fonts' => [ 'Andere' => ['font_file' => ['otf' => '/up/et-fonts/Andere.otf']], 'Inter Display' => ['font_file' => [ 'woff2' => '/up/et-fonts/Inter-Display.woff2', 'ttf' => '/up/et-fonts/Inter-Display.ttf', ]], ], ]); $installed = (new FontInstaller())->install( 'Inter Display', ['woff2' => $woff2, 'ttf' => $ttf], '400,700', 'serif' ); self::assertSame(['woff2', 'ttf'], $installed); } /** @runInSeparateProcess */ public function testAPartialSuccessListsOnlyTheFormatsDiviStored(): void { $woff2 = $this->tempFile('wOF2DATA'); $ttf = $this->tempFile("\x00\x01\x00\x00TTF"); Mockery::mock('alias:' . self::HOOKS)->allows('check_filetype_and_ext_font')->andReturnUsing( static fn (array $unchecked, string $file, string $name): array => [ 'ext' => pathinfo($name, PATHINFO_EXTENSION), 'type' => 'font/x', 'proper_filename' => $name, ] ); Mockery::mock('alias:' . self::FONTS)->allows('font_add')->andReturn([ 'uploaded_font' => 'Inter', 'updated_fonts' => ['Inter' => ['font_file' => ['woff2' => '/up/et-fonts/Inter.woff2']]], ]); self::assertSame( ['woff2'], (new FontInstaller())->install('Inter', ['woff2' => $woff2, 'ttf' => $ttf], 'all', 'sans-serif') ); } /** @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 */ public function testAFileDivisTypeCheckRefusesStopsTheInstallBeforeFontAdd(): void { $woff2 = $this->tempFile('wOF2DATA'); Mockery::mock('alias:' . self::HOOKS)->allows('check_filetype_and_ext_font')->andReturn(self::UNCHECKED); Mockery::mock('alias:' . self::FONTS)->expects('font_add')->never(); $this->expectException(RuntimeException::class); $this->expectExceptionMessage("font file woff2 refused by Divi's type check"); (new FontInstaller())->install('Inter', ['woff2' => $woff2], 'all', 'sans-serif'); } /** @runInSeparateProcess */ public function testARefusedFontAddIsARuntimeException(): void { $woff2 = $this->tempFile('wOF2DATA'); 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)->allows('font_add') ->andReturn(new WP_Error('upload_error_handler', 'Sorry, you are not allowed to upload this file type.')); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('font_add refused: Sorry, you are not allowed to upload this file type.'); (new FontInstaller())->install('Inter', ['woff2' => $woff2], 'all', 'sans-serif'); } /** @runInSeparateProcess */ public function testRemoveDeletesTheFontAndReportsARefusal(): void { $fonts = Mockery::mock('alias:' . self::FONTS); $fonts->expects('font_remove')->with('Inter')->andReturn(['updated_fonts' => []]); $fonts->expects('font_remove')->with('Fehlt')->andReturn(new WP_Error('font_not_exist', 'Font Does not Exist.')); (new FontInstaller())->remove('Inter'); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('font_remove refused: Font Does not Exist.'); (new FontInstaller())->remove('Fehlt'); } }