diff --git a/inc/functions/assets.php b/inc/functions/assets.php index 4723302d..7ac8c477 100644 --- a/inc/functions/assets.php +++ b/inc/functions/assets.php @@ -29,21 +29,33 @@ function wu_get_asset($asset, $assets_dir = 'img', $base_dir = 'assets') { } /** - * Checks if the current admin page belongs to WP Ultimo. + * Checks if the current admin page belongs to Ultimate Multisite. * - * Used to guard asset enqueues so that WP Ultimo scripts and styles are only - * loaded on WP Ultimo admin pages, not on every page in the network admin. + * Used to guard asset enqueues so that Ultimate Multisite scripts and styles are + * only loaded on Ultimate Multisite admin pages, not on every page in the network + * admin. * - * Detection relies on the hook suffix passed to `admin_enqueue_scripts`. All - * WP Ultimo admin pages register with an ID prefixed by `wp-ultimo`, which - * WordPress uses when generating the page hook (e.g., `toplevel_page_wp-ultimo`, - * `wp-ultimo_page_wp-ultimo-settings`). The hook suffix always contains the - * page slug, so checking for `wp-ultimo` is reliable. + * Detection relies on the hook suffix passed to `admin_enqueue_scripts`. Core + * Ultimate Multisite admin pages register with an ID prefixed by `wp-ultimo`, + * which WordPress embeds in the generated page hook (e.g., + * `toplevel_page_wp-ultimo`, `wp-ultimo_page_wp-ultimo-settings`). Addon pages + * typically register with an ID prefixed by `wu-` (e.g., `wu-networks`, + * `wu-sites-by-user`), and may appear either as top-level pages + * (`toplevel_page_wu-foo`) or as submenus of other menus (`*_page_wu-foo`). + * + * Recognized patterns: + * - Hook suffix contains `wp-ultimo` — core plugin pages and submenus. + * - Hook suffix contains `_page_wu-` — any page with a slug starting `wu-` + * (covers `toplevel_page_wu-*` and `{parent}_page_wu-*`). + * + * The result is passed through the `wu_is_wu_page` filter so addons can + * explicitly register their pages when they use a non-standard slug. * * @since 2.4.2 + * @since 2.6.3 Recognize pages with `wu-` slug prefix and add `wu_is_wu_page` filter. * * @param string $hook_suffix The hook suffix passed to `admin_enqueue_scripts`. - * @return bool True if the current page is a WP Ultimo admin page. + * @return bool True if the current page is an Ultimate Multisite admin page. */ function wu_is_wu_page(string $hook_suffix = ''): bool { @@ -52,5 +64,30 @@ function wu_is_wu_page(string $hook_suffix = ''): bool { $hook_suffix = $screen ? (string) $screen->id : ''; } - return str_contains($hook_suffix, 'wp-ultimo'); + $is_wu_page = str_contains($hook_suffix, 'wp-ultimo') + || str_contains($hook_suffix, '_page_wu-'); + + /** + * Filters whether the current admin page is considered an Ultimate Multisite page. + * + * Addons that register admin pages with non-standard slugs (i.e., slugs that + * do not contain `wp-ultimo` and do not start with `wu-`) should hook into + * this filter to ensure the default Ultimate Multisite admin styles and + * scripts (including wu-form modal styling) are enqueued on their pages. + * + * Example: + * + * add_filter( 'wu_is_wu_page', function ( $is_wu_page, $hook_suffix ) { + * if ( str_contains( $hook_suffix, 'my-addon-slug' ) ) { + * return true; + * } + * return $is_wu_page; + * }, 10, 2 ); + * + * @since 2.6.3 + * + * @param bool $is_wu_page Whether the page is recognized as an Ultimate Multisite page. + * @param string $hook_suffix The hook suffix for the current admin page. + */ + return (bool) apply_filters('wu_is_wu_page', $is_wu_page, $hook_suffix); } diff --git a/tests/WP_Ultimo/Functions/Assets_Functions_Test.php b/tests/WP_Ultimo/Functions/Assets_Functions_Test.php index 17956b3a..5c5035d4 100644 --- a/tests/WP_Ultimo/Functions/Assets_Functions_Test.php +++ b/tests/WP_Ultimo/Functions/Assets_Functions_Test.php @@ -67,4 +67,107 @@ public function test_wu_get_asset_custom_base_dir(): void { $this->assertStringContainsString('static/img/', $result); } + + /** + * Core top-level page hook. + */ + public function test_wu_is_wu_page_matches_core_toplevel(): void { + + $this->assertTrue(wu_is_wu_page('toplevel_page_wp-ultimo')); + } + + /** + * Core submenu page hook. + */ + public function test_wu_is_wu_page_matches_core_submenu(): void { + + $this->assertTrue(wu_is_wu_page('wp-ultimo_page_wp-ultimo-settings')); + } + + /** + * Addon page with wu- slug prefix registered as top-level (regression for #706). + * + * When the multinetwork addon adds its `wu-networks` page at top level, + * the hook suffix does not contain `wp-ultimo`. It must still be recognized + * so wu-admin.css / wu-admin.js are enqueued, otherwise wu-form modals + * on that page render un-styled. + */ + public function test_wu_is_wu_page_matches_addon_toplevel_wu_slug(): void { + + $this->assertTrue(wu_is_wu_page('toplevel_page_wu-networks')); + } + + /** + * Addon page with wu- slug prefix registered as network admin submenu. + */ + public function test_wu_is_wu_page_matches_addon_network_wu_slug(): void { + + $this->assertTrue(wu_is_wu_page('toplevel_page_wu-networks-network')); + } + + /** + * Addon page submenu of a non-wu parent with wu- slug. + */ + public function test_wu_is_wu_page_matches_addon_submenu_wu_slug(): void { + + $this->assertTrue(wu_is_wu_page('settings_page_wu-custom-addon')); + } + + /** + * Unrelated WordPress pages must not match. + */ + public function test_wu_is_wu_page_rejects_unrelated_pages(): void { + + $this->assertFalse(wu_is_wu_page('edit-post')); + $this->assertFalse(wu_is_wu_page('options-general')); + $this->assertFalse(wu_is_wu_page('plugins')); + $this->assertFalse(wu_is_wu_page('toplevel_page_other-plugin')); + } + + /** + * A page slug that merely starts with `wu` (no hyphen) must not match, to + * avoid false positives on slugs like `wunderground`. + */ + public function test_wu_is_wu_page_requires_hyphen_after_wu(): void { + + $this->assertFalse(wu_is_wu_page('toplevel_page_wunderground')); + } + + /** + * The wu_is_wu_page filter allows addons with non-standard slugs to opt in. + */ + public function test_wu_is_wu_page_filter_opt_in(): void { + + $callback = function ($is_wu_page, $hook_suffix) { + if ('toplevel_page_my-custom-addon' === $hook_suffix) { + return true; + } + return $is_wu_page; + }; + + add_filter('wu_is_wu_page', $callback, 10, 2); + + $this->assertTrue(wu_is_wu_page('toplevel_page_my-custom-addon')); + + remove_filter('wu_is_wu_page', $callback, 10); + } + + /** + * The wu_is_wu_page filter allows opting out of a core match. + */ + public function test_wu_is_wu_page_filter_opt_out(): void { + + $callback = function ($is_wu_page, $hook_suffix) { + if ('toplevel_page_wp-ultimo' === $hook_suffix) { + return false; + } + return $is_wu_page; + }; + + add_filter('wu_is_wu_page', $callback, 10, 2); + + $this->assertFalse(wu_is_wu_page('toplevel_page_wp-ultimo')); + + remove_filter('wu_is_wu_page', $callback, 10); + } } diff --git a/tests/WP_Ultimo/Scripts_Test.php b/tests/WP_Ultimo/Scripts_Test.php index f437c807..3063467d 100644 --- a/tests/WP_Ultimo/Scripts_Test.php +++ b/tests/WP_Ultimo/Scripts_Test.php @@ -250,8 +250,8 @@ public function test_init_registers_hooks(): void { $this->assertNotFalse(has_action('init', [$this->scripts, 'register_default_scripts'])); $this->assertNotFalse(has_action('init', [$this->scripts, 'register_default_styles'])); - $this->assertNotFalse(has_action('admin_init', [$this->scripts, 'enqueue_default_admin_styles'])); - $this->assertNotFalse(has_action('admin_init', [$this->scripts, 'enqueue_default_admin_scripts'])); + $this->assertNotFalse(has_action('admin_enqueue_scripts', [$this->scripts, 'enqueue_default_admin_styles'])); + $this->assertNotFalse(has_action('admin_enqueue_scripts', [$this->scripts, 'enqueue_default_admin_scripts'])); $this->assertNotFalse(has_filter('admin_body_class', [$this->scripts, 'add_body_class_container_boxed'])); }