From e34ff09f9a6caa9c27d1ea4d6af5a1ffb5ca7f97 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 18 Jun 2026 19:41:28 +0800 Subject: [PATCH 1/2] Abilities API: Plugin Settings: Broadcasts --- .../class-convertkit-settings-broadcasts.php | 102 ++++++++ includes/class-convertkit-settings.php | 2 +- includes/mcp/class-convertkit-mcp.php | 1 + .../Integration/MCPSettingsBroadcastsTest.php | 233 ++++++++++++++++++ 4 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/MCPSettingsBroadcastsTest.php diff --git a/includes/class-convertkit-settings-broadcasts.php b/includes/class-convertkit-settings-broadcasts.php index 10fc8f4be..5d724fa7a 100644 --- a/includes/class-convertkit-settings-broadcasts.php +++ b/includes/class-convertkit-settings-broadcasts.php @@ -217,6 +217,108 @@ public function restrict_content() { } + /** + * Returns this settings group's programmatic name. + * + * @since 3.4.0 + * + * @return string + */ + public function get_name() { + + return 'broadcasts'; + + } + + /** + * Returns the title of this settings group. + * + * @since 3.4.0 + * + * @return string + */ + public function get_title() { + + return __( 'Broadcasts Settings', 'convertkit' ); + + } + + /** + * Returns the keys in this settings group that hold credentials or other + * sensitive values. + * + * @since 3.4.0 + * + * @return array + */ + public function get_secret_keys() { + + return array(); + + } + + /** + * Returns the JSON Schema describing this settings group, in the shape + * stored by save() / returned by get(), excluding secret keys. + * + * @since 3.4.0 + * + * @return array + */ + public function get_schema() { + + return array( + 'type' => 'object', + 'additionalProperties' => false, + 'properties' => array( + 'enabled' => array( + 'type' => 'string', + 'enum' => array( '', 'on' ), + 'description' => __( 'Whether importing Broadcasts from Kit to WordPress Posts is enabled.', 'convertkit' ), + ), + 'author_id' => array( + 'type' => 'integer', + 'minimum' => 1, + 'description' => __( 'WordPress User ID to assign as the Post author when importing Broadcasts.', 'convertkit' ), + ), + 'post_status' => array( + 'type' => 'string', + 'description' => __( 'WordPress Post status to assign to Posts created from imported Broadcasts (e.g. publish, draft).', 'convertkit' ), + ), + 'category_id' => array( + 'type' => array( 'integer', 'string' ), + 'description' => __( 'WordPress Category ID to assign to Posts created from imported Broadcasts. Blank for none.', 'convertkit' ), + ), + 'import_thumbnail' => array( + 'type' => 'string', + 'enum' => array( '', 'on' ), + 'description' => __( 'Whether to import the Broadcast thumbnail as the Post\'s Featured Image.', 'convertkit' ), + ), + 'import_images' => array( + 'type' => 'string', + 'enum' => array( '', 'on' ), + 'description' => __( 'Whether to import images referenced in the Broadcast\'s content into the WordPress Media Library.', 'convertkit' ), + ), + 'published_at_min_date' => array( + 'type' => 'string', + 'format' => 'date', + 'description' => __( 'Earliest published_at date (YYYY-MM-DD) of Broadcasts to import.', 'convertkit' ), + ), + 'enabled_export' => array( + 'type' => 'string', + 'enum' => array( '', 'on' ), + 'description' => __( 'Whether exporting WordPress Posts to Kit Broadcasts is enabled.', 'convertkit' ), + ), + 'no_styles' => array( + 'type' => 'string', + 'enum' => array( '', 'on' ), + 'description' => __( 'Whether inline styles on imported Broadcast content should be stripped.', 'convertkit' ), + ), + ), + ); + + } + /** * The default settings, used when the ConvertKit Broadcasts Settings haven't been saved * e.g. on a new installation. diff --git a/includes/class-convertkit-settings.php b/includes/class-convertkit-settings.php index c1f69423c..8832a46f6 100644 --- a/includes/class-convertkit-settings.php +++ b/includes/class-convertkit-settings.php @@ -600,7 +600,7 @@ public function get_title() { * * @since 3.4.0 * - * @return string[] + * @return array */ public function get_secret_keys() { diff --git a/includes/mcp/class-convertkit-mcp.php b/includes/mcp/class-convertkit-mcp.php index 5a736a38f..a20e0009a 100644 --- a/includes/mcp/class-convertkit-mcp.php +++ b/includes/mcp/class-convertkit-mcp.php @@ -100,6 +100,7 @@ public function register_settings_abilities( $abilities ) { // Settings instances to register with MCP. $groups = array( new ConvertKit_Settings(), + new ConvertKit_Settings_Broadcasts(), ); // Iterate through settings groups, registering the get and update abilities. diff --git a/tests/Integration/MCPSettingsBroadcastsTest.php b/tests/Integration/MCPSettingsBroadcastsTest.php new file mode 100644 index 000000000..d00089f59 --- /dev/null +++ b/tests/Integration/MCPSettingsBroadcastsTest.php @@ -0,0 +1,233 @@ + \ConvertKit_MCP_Ability_Settings_Get::class, + 'kit/settings-broadcasts-update' => \ConvertKit_MCP_Ability_Settings_Update::class, + ); + + // Assert that the abilities are registered and are instances of the expected classes. + foreach ( $expected as $name => $class ) { + $this->assertArrayHasKey($name, $abilities); + $this->assertInstanceOf($class, $abilities[ $name ]); + } + } + + /** + * Test that the permission_callback() rejects a user who cannot manage options. + * + * @since 3.4.0 + */ + public function testPermissionCallbackDeniesWithoutManageOptionsCapability() + { + // Become a Subscriber (no manage_options capability). + $subscriber_id = static::factory()->user->create([ 'role' => 'subscriber' ]); + wp_set_current_user($subscriber_id); + + // Resolve the abilities array via the same helper the MCP server uses. + $abilities = convertkit_get_abilities(); + + // Assert that the abilities are permission denied. + foreach ( self::ABILITY_NAMES as $name ) { + // Execute the ability. + $result = $abilities[ $name ]->permission_callback([]); + + // Assert that the result is a WP_Error. + $this->assertInstanceOf(\WP_Error::class, $result); + } + } + + /** + * Test that kit/settings-broadcasts-get returns the current settings. + * + * @since 3.4.0 + */ + public function testGetSettings() + { + // Populate settings. + $this->populateSettings(); + + // Resolve the abilities array via the same helper the MCP server uses. + $abilities = convertkit_get_abilities(); + + // Execute the ability. + $result = $abilities['kit/settings-broadcasts-get']->execute_callback([]); + + // Confirm expected settings are returned. + $this->assertArrayHasKey('enabled', $result); + $this->assertEquals('on', $result['enabled']); + $this->assertArrayHasKey('author_id', $result); + $this->assertArrayHasKey('post_status', $result); + $this->assertEquals('draft', $result['post_status']); + $this->assertArrayHasKey('category_id', $result); + $this->assertArrayHasKey('import_thumbnail', $result); + $this->assertArrayHasKey('import_images', $result); + $this->assertArrayHasKey('published_at_min_date', $result); + $this->assertArrayHasKey('enabled_export', $result); + $this->assertArrayHasKey('no_styles', $result); + } + + /** + * Test that kit/settings-broadcasts-update updates the settings. + * + * @since 3.4.0 + */ + public function testUpdateSettings() + { + // Resolve the abilities array via the same helper the MCP server uses. + $abilities = convertkit_get_abilities(); + + // Execute the ability. + $result = $abilities['kit/settings-broadcasts-update']->execute_callback( + [ + 'enabled' => 'on', + 'post_status' => 'draft', + 'import_thumbnail' => '', + 'import_images' => 'on', + 'enabled_export' => 'on', + 'no_styles' => 'on', + ] + ); + + // Confirm expected settings are returned. + $this->assertArrayHasKey('enabled', $result); + $this->assertArrayHasKey('author_id', $result); + $this->assertArrayHasKey('post_status', $result); + $this->assertArrayHasKey('category_id', $result); + $this->assertArrayHasKey('import_thumbnail', $result); + $this->assertArrayHasKey('import_images', $result); + $this->assertArrayHasKey('published_at_min_date', $result); + $this->assertArrayHasKey('enabled_export', $result); + $this->assertArrayHasKey('no_styles', $result); + + // Confirm settings are updated. + $this->assertEquals('on', $result['enabled']); + $this->assertEquals('draft', $result['post_status']); + $this->assertEquals('', $result['import_thumbnail']); + $this->assertEquals('on', $result['import_images']); + $this->assertEquals('on', $result['enabled_export']); + $this->assertEquals('on', $result['no_styles']); + } + + /** + * Test that kit/settings-broadcasts-update returns an error if an invalid key is provided. + * + * @since 3.4.0 + */ + public function testUpdateSettingsWithInvalidKeyReturnsError() + { + // Resolve the abilities array via the same helper the MCP server uses. + $abilities = convertkit_get_abilities(); + + // Execute the ability. + $result = $abilities['kit/settings-broadcasts-update']->execute_callback([ 'invalid_key' => 'invalid_value' ]); + } + + /** + * Populate the settings with some sensible values for testing. + * + * @since 3.4.0 + */ + private function populateSettings() + { + update_option( + self::SETTINGS_NAME, + [ + 'enabled' => 'on', + 'author_id' => 1, + 'post_status' => 'draft', + 'category_id' => '', + 'import_thumbnail' => 'on', + 'import_images' => '', + 'published_at_min_date' => gmdate( 'Y-m-d', strtotime( '-30 days' ) ), + 'enabled_export' => '', + 'no_styles' => '', + ] + ); + } +} From 9b7d82b30c70e74db136f4d7e5874f4ba0c5f044 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 18 Jun 2026 19:52:20 +0800 Subject: [PATCH 2/2] Broadcast Settings: Update settings after save --- .../class-convertkit-settings-broadcasts.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/includes/class-convertkit-settings-broadcasts.php b/includes/class-convertkit-settings-broadcasts.php index 5d724fa7a..f637f7b11 100644 --- a/includes/class-convertkit-settings-broadcasts.php +++ b/includes/class-convertkit-settings-broadcasts.php @@ -369,6 +369,27 @@ public function save( $settings ) { update_option( self::SETTINGS_NAME, array_merge( $this->get(), $settings ) ); + // Reload settings in class, to reflect changes. + $this->refresh_settings(); + + } + + /** + * Reloads settings from the options table so this instance has the latest values. + * + * @since 3.3.4 + */ + private function refresh_settings() { + + $settings = get_option( self::SETTINGS_NAME ); + + if ( ! $settings ) { + $this->settings = $this->get_defaults(); + return; + } + + $this->settings = array_merge( $this->get_defaults(), $settings ); + } }