From 9e0b4cef360fcebe1b80e37beb0a91efc985c807 Mon Sep 17 00:00:00 2001 From: "Daniel L. Iser" Date: Thu, 13 Aug 2026 23:56:02 -0400 Subject: [PATCH] perf(editor): reduce popup selector and stylesheet overhead --- classes/Admin/Shortcode/UI.php | 85 ++++++++++++- classes/Controllers/Assets.php | 21 +++- .../Block_Editor_Assets_Controller_Test.php | 113 ++++++++++++++++++ .../php/tests/PUM_Admin_Shortcode_UI_Test.php | 102 ++++++++++++++++ 4 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 tests/php/tests/Block_Editor_Assets_Controller_Test.php create mode 100644 tests/php/tests/PUM_Admin_Shortcode_UI_Test.php diff --git a/classes/Admin/Shortcode/UI.php b/classes/Admin/Shortcode/UI.php index 4b8169bcd..07391ed64 100644 --- a/classes/Admin/Shortcode/UI.php +++ b/classes/Admin/Shortcode/UI.php @@ -66,8 +66,9 @@ public static function init_editor() { add_filter( 'mce_buttons', [ __CLASS__, 'mce_buttons' ] ); add_filter( 'mce_external_plugins', [ __CLASS__, 'mce_external_plugins' ] ); - // Add core site styles for form previews. - add_editor_style( Popup_Maker::$URL . 'dist/assets/site.css' ); + // Load plugin editor styles without registering remote theme styles. + add_filter( 'mce_css', [ __CLASS__, 'mce_css' ] ); + add_filter( 'block_editor_settings_all', [ __CLASS__, 'block_editor_settings' ], 10, 2 ); // Process live previews. add_action( 'wp_ajax_pum_do_shortcode', [ __CLASS__, 'do_shortcode' ] ); @@ -99,9 +100,6 @@ public static function mce_buttons( $buttons ) { * Enqueues needed assets. */ public static function enqueue_scripts() { - // Register editor styles. - add_editor_style( PUM_Admin_Assets::$css_url . 'admin-editor-styles.css' ); - wp_enqueue_style( 'pum-admin-shortcode-ui' ); wp_enqueue_script( 'pum-admin-shortcode-ui' ); wp_localize_script( @@ -123,6 +121,83 @@ public static function enqueue_scripts() { ); } + /** + * Add Popup Maker styles to TinyMCE and Classic blocks. + * + * @param string $stylesheets Comma-separated stylesheet URLs. + * @return string + */ + public static function mce_css( $stylesheets = '' ) { + $stylesheets = is_string( $stylesheets ) ? array_filter( array_map( 'trim', explode( ',', $stylesheets ) ) ) : []; + + foreach ( self::editor_stylesheets() as $stylesheet ) { + $stylesheets[] = $stylesheet['url']; + } + + return implode( ',', array_unique( $stylesheets ) ); + } + + /** + * Add Popup Maker styles to the block editor from local files. + * + * WordPress fetches absolute URLs registered through add_editor_style() + * over HTTP while rendering the editor. Reading plugin-owned files directly + * avoids that request while retaining the same CSS in editor settings. + * + * @param array $settings Block editor settings. + * @param mixed $context Block editor context. + * @return array + */ + public static function block_editor_settings( $settings, $context = null ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + if ( ! is_array( $settings ) ) { + return $settings; + } + + if ( ! isset( $settings['styles'] ) || ! is_array( $settings['styles'] ) ) { + $settings['styles'] = []; + } + + foreach ( self::editor_stylesheets() as $stylesheet ) { + if ( ! is_readable( $stylesheet['path'] ) ) { + continue; + } + + $css = file_get_contents( $stylesheet['path'] ); + + if ( ! is_string( $css ) || '' === $css ) { + continue; + } + + $settings['styles'][] = [ + 'css' => $css, + '__unstableType' => 'theme', + 'isGlobalStyles' => false, + ]; + } + + return $settings; + } + + /** + * Get Popup Maker editor stylesheet paths and URLs. + * + * @return array + */ + private static function editor_stylesheets() { + $suffix = is_rtl() ? '-rtl' : ''; + + return [ + [ + 'path' => Popup_Maker::$DIR . "dist/assets/site{$suffix}.css", + 'url' => Popup_Maker::$URL . "dist/assets/site{$suffix}.css", + ], + [ + 'path' => Popup_Maker::$DIR . "dist/assets/admin-editor-styles{$suffix}.css", + 'url' => Popup_Maker::$URL . "dist/assets/admin-editor-styles{$suffix}.css", + ], + ]; + } + /** * Generates a json object variable to pass to the Shortcode UI front end. * diff --git a/classes/Controllers/Assets.php b/classes/Controllers/Assets.php index d17171f75..fb45a127e 100644 --- a/classes/Controllers/Assets.php +++ b/classes/Controllers/Assets.php @@ -105,7 +105,8 @@ public function get_packages() { 'vars' => function () { return [ 'cta_types' => $this->container->get( 'cta_types' )->get_as_array(), - 'popups' => pum_get_all_popups(), + // Preserve the original models for extensions that filter these variables. + 'popups' => false !== has_filter( 'popup_maker/block-editor_localized_vars' ) ? \pum_get_all_popups() : $this->get_block_editor_popup_choices(), 'homeUrl' => home_url(), 'previewNonce' => wp_create_nonce( 'popup-preview' ), 'popupTriggerExcludedBlocks' => apply_filters( @@ -280,6 +281,24 @@ public function get_packages() { return $packages; } + /** + * Adapt the shared popup title map for block editor select controls. + * + * @return array + */ + private function get_block_editor_popup_choices() { + $choices = []; + + foreach ( \PUM_Helpers::popup_selectlist( [ 'post_status' => [ 'publish', 'private' ] ] ) as $popup_id => $post_title ) { + $choices[] = [ + 'ID' => (int) $popup_id, + 'post_title' => (string) $post_title, + ]; + } + + return $choices; + } + /** * Register all package scripts & styles. */ diff --git a/tests/php/tests/Block_Editor_Assets_Controller_Test.php b/tests/php/tests/Block_Editor_Assets_Controller_Test.php new file mode 100644 index 000000000..9bc79662a --- /dev/null +++ b/tests/php/tests/Block_Editor_Assets_Controller_Test.php @@ -0,0 +1,113 @@ +post->create( + [ + 'post_type' => 'popup', + 'post_status' => 'publish', + ] + ); + + $expected = pum_get_all_popups(); + $filter = function ( $vars ) { + return $vars; + }; + + add_filter( 'popup_maker/block-editor_localized_vars', $filter, 0 ); + + $assets = \PopupMaker\plugin()->get_controller( 'Assets' ); + $packages = $assets->get_packages(); + $vars = call_user_func( $packages['block-editor']['vars'] ); + + remove_filter( 'popup_maker/block-editor_localized_vars', $filter, 0 ); + + $this->assertSame( $expected, $vars['popups'] ); + } + + /** + * Authorized editors retain private popups in the viewer title data. + * + * @return void + */ + public function test_block_editor_preserves_private_popup_choices_for_authorized_editors() { + $previous_user_id = get_current_user_id(); + $admin_user_id = self::factory()->user->create( [ 'role' => 'administrator' ] ); + $private_id = self::factory()->post->create( + [ + 'post_type' => 'popup', + 'post_status' => 'private', + 'post_title' => 'Private popup', + ] + ); + + wp_set_current_user( $admin_user_id ); + + try { + $assets = \PopupMaker\plugin()->get_controller( 'Assets' ); + $packages = $assets->get_packages(); + $vars = call_user_func( $packages['block-editor']['vars'] ); + } finally { + wp_set_current_user( $previous_user_id ); + } + + $this->assertContains( + [ + 'ID' => $private_id, + 'post_title' => 'Private popup', + ], + $vars['popups'] + ); + } + + /** + * Shared title-choice filters are retained in lightweight popup choices. + * + * @return void + */ + public function test_block_editor_preserves_filtered_popup_titles() { + $popup_id = self::factory()->post->create( + [ + 'post_type' => 'popup', + 'post_status' => 'publish', + 'post_title' => 'Stored popup title', + ] + ); + + $filter = function ( $titles ) use ( $popup_id ) { + $titles[ $popup_id ] = 'Filtered popup title'; + + return $titles; + }; + + add_filter( 'popup_maker/popup_title_choices', $filter ); + + $assets = \PopupMaker\plugin()->get_controller( 'Assets' ); + $packages = $assets->get_packages(); + $vars = call_user_func( $packages['block-editor']['vars'] ); + + remove_filter( 'popup_maker/popup_title_choices', $filter ); + + $this->assertContains( + [ + 'ID' => $popup_id, + 'post_title' => 'Filtered popup title', + ], + $vars['popups'] + ); + } +} diff --git a/tests/php/tests/PUM_Admin_Shortcode_UI_Test.php b/tests/php/tests/PUM_Admin_Shortcode_UI_Test.php new file mode 100644 index 000000000..06c54c280 --- /dev/null +++ b/tests/php/tests/PUM_Admin_Shortcode_UI_Test.php @@ -0,0 +1,102 @@ +user->create( [ 'role' => 'administrator' ] ); + + wp_set_current_user( $user_id ); + update_user_option( $user_id, 'rich_editing', 'true' ); + } + + /** + * TinyMCE retains both Popup Maker editor stylesheets. + * + * @return void + */ + public function test_mce_css_includes_popup_maker_styles() { + PUM_Admin_Shortcode_UI::init_editor(); + + $stylesheets = apply_filters( 'mce_css', 'https://example.com/theme.css' ); + + $this->assertStringContainsString( 'https://example.com/theme.css', $stylesheets ); + $this->assertStringContainsString( Popup_Maker::$URL . 'dist/assets/site.css', $stylesheets ); + $this->assertStringContainsString( Popup_Maker::$URL . 'dist/assets/admin-editor-styles.css', $stylesheets ); + } + + /** + * RTL editors load only the RTL Popup Maker stylesheets. + * + * @return void + */ + public function test_mce_css_selects_rtl_popup_maker_styles() { + global $wp_locale; + + $original_direction = $wp_locale->text_direction; + $wp_locale->text_direction = 'rtl'; + + try { + PUM_Admin_Shortcode_UI::init_editor(); + + $stylesheets = apply_filters( 'mce_css', '' ); + } finally { + $wp_locale->text_direction = $original_direction; + } + + $this->assertStringContainsString( Popup_Maker::$URL . 'dist/assets/site-rtl.css', $stylesheets ); + $this->assertStringContainsString( Popup_Maker::$URL . 'dist/assets/admin-editor-styles-rtl.css', $stylesheets ); + $this->assertStringNotContainsString( Popup_Maker::$URL . 'dist/assets/site.css', $stylesheets ); + $this->assertStringNotContainsString( Popup_Maker::$URL . 'dist/assets/admin-editor-styles.css', $stylesheets ); + } + + /** + * Block editor settings receive local CSS without an HTTP request. + * + * @return void + */ + public function test_block_editor_settings_include_local_styles() { + $site_styles_path = Popup_Maker::$DIR . 'dist/assets/site.css'; + $admin_styles_path = Popup_Maker::$DIR . 'dist/assets/admin-editor-styles.css'; + + if ( ! is_readable( $site_styles_path ) || ! is_readable( $admin_styles_path ) ) { + $this->markTestSkipped( 'Dist assets not built in test environment.' ); + } + + $http_requests = 0; + $filter = function ( $response ) use ( &$http_requests ) { + ++$http_requests; + + return $response; + }; + + add_filter( 'pre_http_request', $filter ); + + PUM_Admin_Shortcode_UI::init_editor(); + + $settings = apply_filters( 'block_editor_settings_all', [ 'styles' => [] ], null ); + + remove_filter( 'pre_http_request', $filter ); + + $this->assertSame( 0, $http_requests ); + + $styles = array_column( $settings['styles'], 'css' ); + + $this->assertContains( file_get_contents( $site_styles_path ), $styles ); + $this->assertContains( file_get_contents( $admin_styles_path ), $styles ); + } +}