From 2ec5b492f85c1f1271f44e28f1eb960786daa4eb Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 2 Nov 2026 21:41:06 +0530 Subject: [PATCH 1/3] feat: opt into the ThemeIsle SDK Support Hub Registers the visualizer_support_hub_metadata filter so the SDK shows one Help button on the Chart Library, Settings and Support screens, a setup nudge while the site has no chart, and the new deactivation window on the Plugins screen. The Settings screen now fires themeisle_internal_page like the other two screens. Visualizer Pro registers nothing: it has no admin screens of its own, so the free plugin owns the hub and reports is_pro from the Pro license. Co-Authored-By: Claude Fable 5.1 --- classes/Visualizer/Module/Admin.php | 2 + index.php | 55 ++++++++++++ tests/test-support-hub.php | 124 ++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 tests/test-support-hub.php diff --git a/classes/Visualizer/Module/Admin.php b/classes/Visualizer/Module/Admin.php index e7ef36e5..0074a3b0 100644 --- a/classes/Visualizer/Module/Admin.php +++ b/classes/Visualizer/Module/Admin.php @@ -1119,6 +1119,8 @@ public function enqueueSettingsScripts( string $hook_suffix ): void { } wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'wp-color-picker' ); + + do_action( 'themeisle_internal_page', VISUALIZER_DIRNAME, 'settings' ); } /** diff --git a/index.php b/index.php index b5f21108..3d42a87d 100644 --- a/index.php +++ b/index.php @@ -189,6 +189,7 @@ function () { ); } ); + add_filter( 'visualizer_support_hub_metadata', 'visualizer_support_hub_metadata' ); add_filter( 'visualizer_ai_connect_metadata', function () { @@ -274,6 +275,60 @@ function visualizer_action_scheduler_store_class( $class_name ) { return 'ActionScheduler_DBStore' === $class_name ? 'Visualizer_ActionScheduler_Store' : $class_name; } +/** + * Opts into the SDK Support Hub: one Help button on the plugin's own screens, + * a setup nudge while the site has no chart, and the deactivation window. + * + * Visualizer Pro registers nothing here: it has no admin screens of its own + * and the SDK shows one Help button per screen, so the free plugin owns the + * hub for both and tags the message as pro from the Pro license status. + * + * The plugin screens need no `pages` entry: each one fires + * `themeisle_internal_page`, which the SDK already treats as a product screen. + * + * @return array{is_pro: bool, links: array, setup_complete: callable} + */ +function visualizer_support_hub_metadata() { + return array( + 'is_pro' => Visualizer_Module::is_pro(), + 'links' => array( + array( + 'icon' => 'book-alt', + 'text' => __( 'Docs', 'visualizer' ), + 'url' => VISUALIZER_MAIN_DOC, + ), + array( + 'icon' => 'groups', + 'text' => __( 'Forum', 'visualizer' ), + 'url' => 'https://wordpress.org/support/plugin/visualizer/', + ), + ), + 'setup_complete' => 'visualizer_support_hub_setup_complete', + ); +} + +/** + * Whether the site has at least one chart, in any status except auto-draft + * and trash. Runs on the plugin's own screens for administrators only. + * + * @return bool + */ +function visualizer_support_hub_setup_complete() { + $charts = get_posts( + array( + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER, + 'post_status' => 'any', + 'posts_per_page' => 1, + 'fields' => 'ids', + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + ) + ); + + return ! empty( $charts ); +} + /** * Registers with the SDK * diff --git a/tests/test-support-hub.php b/tests/test-support-hub.php new file mode 100644 index 00000000..f35d77fd --- /dev/null +++ b/tests/test-support-hub.php @@ -0,0 +1,124 @@ +assertNotFalse( has_filter( 'visualizer_support_hub_metadata', 'visualizer_support_hub_metadata' ) ); + + $data = apply_filters( 'visualizer_support_hub_metadata', array() ); + + $this->assertIsArray( $data ); + $this->assertArrayHasKey( 'is_pro', $data ); + $this->assertIsBool( $data['is_pro'] ); + $this->assertArrayHasKey( 'setup_complete', $data ); + $this->assertIsCallable( $data['setup_complete'] ); + $this->assertArrayNotHasKey( 'pages', $data, 'Screens are declared through themeisle_internal_page, not pages.' ); + $this->assertArrayNotHasKey( 'primary_color', $data, 'The hub keeps the shared SDK accent.' ); + } + + /** + * Every footer link has an icon, a text and a URL. + */ + public function test_links_are_complete() { + $data = apply_filters( 'visualizer_support_hub_metadata', array() ); + + $this->assertNotEmpty( $data['links'] ); + $this->assertLessThanOrEqual( 6, count( $data['links'] ) ); + foreach ( $data['links'] as $link ) { + $this->assertNotEmpty( $link['icon'] ); + $this->assertNotEmpty( $link['text'] ); + $this->assertNotFalse( filter_var( $link['url'], FILTER_VALIDATE_URL ) ); + } + $this->assertSame( VISUALIZER_MAIN_DOC, $data['links'][0]['url'] ); + } + + /** + * `is_pro` follows the `visualizer_is_pro` filter Pro answers from its license. + */ + public function test_is_pro_follows_the_license_filter() { + $this->assertFalse( apply_filters( 'visualizer_support_hub_metadata', array() )['is_pro'] ); + + add_filter( 'visualizer_is_pro', '__return_true' ); + $this->assertTrue( apply_filters( 'visualizer_support_hub_metadata', array() )['is_pro'] ); + remove_filter( 'visualizer_is_pro', '__return_true' ); + } + + /** + * Setup counts as complete once the site has one chart, in any live status. + */ + public function test_setup_complete_needs_one_chart() { + $this->assertFalse( visualizer_support_hub_setup_complete() ); + + $chart_id = self::factory()->post->create( + array( + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER, + 'post_status' => 'draft', + ) + ); + $this->assertTrue( visualizer_support_hub_setup_complete() ); + + wp_trash_post( $chart_id ); + $this->assertFalse( visualizer_support_hub_setup_complete(), 'A trashed chart does not count.' ); + } + + /** + * Auto-drafts (the editor's placeholder posts) do not count as a chart. + */ + public function test_setup_complete_ignores_auto_drafts() { + self::factory()->post->create( + array( + 'post_type' => Visualizer_Plugin::CPT_VISUALIZER, + 'post_status' => 'auto-draft', + ) + ); + $this->assertFalse( visualizer_support_hub_setup_complete() ); + + self::factory()->post->create( + array( + 'post_type' => 'post', + 'post_status' => 'publish', + ) + ); + $this->assertFalse( visualizer_support_hub_setup_complete(), 'Other post types do not count.' ); + } + + /** + * The Settings screen declares itself a product screen like the Library and Support screens. + */ + public function test_settings_screen_fires_internal_page() { + wp_set_current_user( 1 ); + set_current_screen( 'dashboard' ); + + $admin = Visualizer_Plugin::instance()->getModule( Visualizer_Module_Admin::NAME ); + $admin->registerAdminMenu(); + + $fired = array(); + add_action( + 'themeisle_internal_page', + function ( $product, $page ) use ( &$fired ) { + $fired[] = array( $product, $page ); + }, + 10, + 2 + ); + + // The suffix add_submenu_page() returned, whatever menu state earlier tests left behind. + $admin->enqueueSettingsScripts( get_plugin_page_hookname( 'viz-settings', Visualizer_Plugin::NAME ) ); + $admin->enqueueSettingsScripts( 'edit-post' ); + + $this->assertSame( array( array( VISUALIZER_DIRNAME, 'settings' ) ), $fired ); + } +} From 054efd78de52e193fed67ab3dc06de7cc5394f01 Mon Sep 17 00:00:00 2001 From: Hardeep Asrani Date: Mon, 2 Nov 2026 22:10:29 +0530 Subject: [PATCH 2/3] fix: clarify support hub metadata types --- index.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/index.php b/index.php index 3d42a87d..5dd9b144 100644 --- a/index.php +++ b/index.php @@ -286,12 +286,13 @@ function visualizer_action_scheduler_store_class( $class_name ) { * The plugin screens need no `pages` entry: each one fires * `themeisle_internal_page`, which the SDK already treats as a product screen. * - * @return array{is_pro: bool, links: array, setup_complete: callable} + * @return array{is_pro: bool, license_namespace: string, links: array, setup_complete: callable(): bool} */ function visualizer_support_hub_metadata() { return array( - 'is_pro' => Visualizer_Module::is_pro(), - 'links' => array( + 'is_pro' => Visualizer_Module::is_pro(), + 'license_namespace' => 'visualizer', + 'links' => array( array( 'icon' => 'book-alt', 'text' => __( 'Docs', 'visualizer' ), @@ -303,7 +304,7 @@ function visualizer_support_hub_metadata() { 'url' => 'https://wordpress.org/support/plugin/visualizer/', ), ), - 'setup_complete' => 'visualizer_support_hub_setup_complete', + 'setup_complete' => 'visualizer_support_hub_setup_complete', ); } From 5191d4b12d26938045a63397b7c0dedd362e60ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vytis=20Bulkevi=C4=8Dius?= Date: Tue, 3 Nov 2026 14:23:20 +0200 Subject: [PATCH 3/3] fix: drop the support hub setup nudge for now --- index.php | 29 +++----------------------- tests/test-support-hub.php | 42 +------------------------------------- 2 files changed, 4 insertions(+), 67 deletions(-) diff --git a/index.php b/index.php index 5dd9b144..9e49ac8a 100644 --- a/index.php +++ b/index.php @@ -276,8 +276,8 @@ function visualizer_action_scheduler_store_class( $class_name ) { } /** - * Opts into the SDK Support Hub: one Help button on the plugin's own screens, - * a setup nudge while the site has no chart, and the deactivation window. + * Opts into the SDK Support Hub: one Help button on the plugin's own screens + * and the deactivation window. * * Visualizer Pro registers nothing here: it has no admin screens of its own * and the SDK shows one Help button per screen, so the free plugin owns the @@ -286,7 +286,7 @@ function visualizer_action_scheduler_store_class( $class_name ) { * The plugin screens need no `pages` entry: each one fires * `themeisle_internal_page`, which the SDK already treats as a product screen. * - * @return array{is_pro: bool, license_namespace: string, links: array, setup_complete: callable(): bool} + * @return array{is_pro: bool, license_namespace: string, links: array} */ function visualizer_support_hub_metadata() { return array( @@ -304,32 +304,9 @@ function visualizer_support_hub_metadata() { 'url' => 'https://wordpress.org/support/plugin/visualizer/', ), ), - 'setup_complete' => 'visualizer_support_hub_setup_complete', ); } -/** - * Whether the site has at least one chart, in any status except auto-draft - * and trash. Runs on the plugin's own screens for administrators only. - * - * @return bool - */ -function visualizer_support_hub_setup_complete() { - $charts = get_posts( - array( - 'post_type' => Visualizer_Plugin::CPT_VISUALIZER, - 'post_status' => 'any', - 'posts_per_page' => 1, - 'fields' => 'ids', - 'no_found_rows' => true, - 'update_post_meta_cache' => false, - 'update_post_term_cache' => false, - ) - ); - - return ! empty( $charts ); -} - /** * Registers with the SDK * diff --git a/tests/test-support-hub.php b/tests/test-support-hub.php index f35d77fd..312ca80e 100644 --- a/tests/test-support-hub.php +++ b/tests/test-support-hub.php @@ -23,8 +23,7 @@ public function test_metadata_has_the_contract_keys() { $this->assertIsArray( $data ); $this->assertArrayHasKey( 'is_pro', $data ); $this->assertIsBool( $data['is_pro'] ); - $this->assertArrayHasKey( 'setup_complete', $data ); - $this->assertIsCallable( $data['setup_complete'] ); + $this->assertArrayNotHasKey( 'setup_complete', $data, 'The setup nudge stays off for now.' ); $this->assertArrayNotHasKey( 'pages', $data, 'Screens are declared through themeisle_internal_page, not pages.' ); $this->assertArrayNotHasKey( 'primary_color', $data, 'The hub keeps the shared SDK accent.' ); } @@ -56,45 +55,6 @@ public function test_is_pro_follows_the_license_filter() { remove_filter( 'visualizer_is_pro', '__return_true' ); } - /** - * Setup counts as complete once the site has one chart, in any live status. - */ - public function test_setup_complete_needs_one_chart() { - $this->assertFalse( visualizer_support_hub_setup_complete() ); - - $chart_id = self::factory()->post->create( - array( - 'post_type' => Visualizer_Plugin::CPT_VISUALIZER, - 'post_status' => 'draft', - ) - ); - $this->assertTrue( visualizer_support_hub_setup_complete() ); - - wp_trash_post( $chart_id ); - $this->assertFalse( visualizer_support_hub_setup_complete(), 'A trashed chart does not count.' ); - } - - /** - * Auto-drafts (the editor's placeholder posts) do not count as a chart. - */ - public function test_setup_complete_ignores_auto_drafts() { - self::factory()->post->create( - array( - 'post_type' => Visualizer_Plugin::CPT_VISUALIZER, - 'post_status' => 'auto-draft', - ) - ); - $this->assertFalse( visualizer_support_hub_setup_complete() ); - - self::factory()->post->create( - array( - 'post_type' => 'post', - 'post_status' => 'publish', - ) - ); - $this->assertFalse( visualizer_support_hub_setup_complete(), 'Other post types do not count.' ); - } - /** * The Settings screen declares itself a product screen like the Library and Support screens. */