From 99e360768569f2601d1e0548d1597f232212b297 Mon Sep 17 00:00:00 2001 From: Jan-CoreBunch Date: Mon, 24 Aug 2026 12:32:24 +0200 Subject: [PATCH] fix(wp): escape Oxygen Classic custom fonts --- packages/wp/Tests/OxygenCustomFontsTest.php | 149 ++++++++++++++++++++ packages/wp/wp/App/Oxygen/Functions.php | 3 +- 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 packages/wp/Tests/OxygenCustomFontsTest.php diff --git a/packages/wp/Tests/OxygenCustomFontsTest.php b/packages/wp/Tests/OxygenCustomFontsTest.php new file mode 100644 index 0000000..43c7c29 --- /dev/null +++ b/packages/wp/Tests/OxygenCustomFontsTest.php @@ -0,0 +1,149 @@ +prepared_args = $args; + return $query; + } + + public function get_row( $query ) { + $this->last_query = $query; + return (object) array( + 'data' => json_encode( $GLOBALS['cf_test_preset'] ), + ); + } +} + +final class OxygenCustomFontsTest extends TestCase { + protected function setUp(): void { + global $wpdb; + + $GLOBALS['cf_test_options'] = array( + 'core_framework_main' => array( + 'selected_id' => 'preset-id', + 'disable_fonts' => false, + ), + ); + $GLOBALS['cf_test_preset'] = $this->createPreset( array() ); + $wpdb = new CoreFrameworkOxygenTestDatabase(); + } + + private function createPreset( array $families ): array { + return array( + 'modulesData' => array( + 'FONTS' => array( + 'fonts' => array_map( + fn( string $family ): array => array( 'family' => $family ), + $families + ), + ), + ), + ); + } + + private function renderCustomFonts( array $families ): string { + $GLOBALS['cf_test_preset'] = $this->createPreset( $families ); + + $reflection = new ReflectionClass( OxygenFunctions::class ); + $functions = $reflection->newInstanceWithoutConstructor(); + + ob_start(); + try { + $functions->elegant_custom_fonts(); + return ob_get_clean(); + } catch ( Throwable $throwable ) { + ob_end_clean(); + throw $throwable; + } + } + + private function parseNgInitAttribute( string $hook_output ): string { + $document = new DOMDocument(); + $previous_use_errors = libxml_use_internal_errors( true ); + + $document->loadHTML( + '
', + LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD + ); + + libxml_clear_errors(); + libxml_use_internal_errors( $previous_use_errors ); + + return $document->getElementsByTagName( 'div' )->item( 0 )->getAttribute( 'ng-init' ); + } + + public function testFontFamiliesAreEscapedForOxygenNgInitAttribute(): void { + $families = array( 'ABeeZee', 'IBM Plex Sans' ); + $output = $this->renderCustomFonts( $families ); + + $this->assertSame( + 'elegantCustomFonts=["ABeeZee","IBM Plex Sans"];', + $output + ); + $this->assertSame( + 'elegantCustomFonts=' . json_encode( $families ) . ';', + $this->parseNgInitAttribute( $output ) + ); + } + + public function testSpecialCharactersRoundTripThroughHtmlAttribute(): void { + $families = array( + 'A " B', + 'O"Brien & Sons', + '', + ); + $output = $this->renderCustomFonts( $families ); + + $this->assertStringNotContainsString( '"', $output ); + $this->assertStringContainsString( '&quot;', $output ); + $this->assertStringContainsString( '<script>', $output ); + $this->assertSame( + 'elegantCustomFonts=' . json_encode( $families ) . ';', + $this->parseNgInitAttribute( $output ) + ); + } + + public function testEmptyFontListRemainsValid(): void { + $output = $this->renderCustomFonts( array() ); + + $this->assertSame( 'elegantCustomFonts=[];', $output ); + $this->assertSame( 'elegantCustomFonts=[];', $this->parseNgInitAttribute( $output ) ); + } + + public function testDisabledFontIntegrationEmitsNothing(): void { + $GLOBALS['cf_test_options']['core_framework_main']['disable_fonts'] = true; + + $this->assertSame( '', $this->renderCustomFonts( array( 'ABeeZee' ) ) ); + } +} diff --git a/packages/wp/wp/App/Oxygen/Functions.php b/packages/wp/wp/App/Oxygen/Functions.php index 9a515f7..e966beb 100644 --- a/packages/wp/wp/App/Oxygen/Functions.php +++ b/packages/wp/wp/App/Oxygen/Functions.php @@ -196,7 +196,8 @@ public function elegant_custom_fonts() { if ( $output === false ) { $output = '[]'; } - // The value is JSON-encoded specifically for this Oxygen JavaScript hook. + $output = \htmlspecialchars( $output, \ENT_QUOTES ); + // The JSON is HTML-escaped for Oxygen's double-quoted ng-init attribute. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo \sprintf( 'elegantCustomFonts=%s;', $output ); }