diff --git a/docs/experiments/editorial-notes.md b/docs/experiments/editorial-notes.md index 33d5aaf40..33b93deec 100644 --- a/docs/experiments/editorial-notes.md +++ b/docs/experiments/editorial-notes.md @@ -119,7 +119,7 @@ Notes are `WP_Comment` objects with `comment_type = 'note'` and `status = 'hold' - **New thread**: `POST /wp/v2/comments` with `parent: 0` → response `id` stored in `block.attributes.metadata.noteId` via `updateBlockAttributes` - **Reply**: `POST /wp/v2/comments` with `parent: existingNoteId` → block metadata unchanged (association already set) -- **AI author**: All Notes created by this experiment include `meta: { ai_note: true }`. The `rest_pre_insert_comment` filter intercepts this and sets the author to "WordPress AI" with no email, URL, or user ID, so Notes are not attributed to the authenticated user's account. +- **AI author**: All Notes created by this experiment include `meta: { wpai_note: true }`. The `rest_pre_insert_comment` filter intercepts this and sets the author to "WordPress AI" with no email, URL, or user ID, so Notes are not attributed to the authenticated user's account. - **Resolved Notes**: Notes with `status = 'approve'` (resolved) cause their associated block to be skipped entirely on the next review run. ## Using the Ability via REST API diff --git a/docs/experiments/summarization.md b/docs/experiments/summarization.md index 7515703f1..376cd2589 100644 --- a/docs/experiments/summarization.md +++ b/docs/experiments/summarization.md @@ -15,7 +15,7 @@ When enabled, the Content Summarization experiment adds a "Generate AI Summary" - One-click summary generation from post content - Automatically creates a group variation block with the summary - Summary block can be regenerated from block toolbar -- Summary is saved to post meta (`ai_generated_summary`) +- Summary is saved to post meta (`wpai_generated_summary`) - Works with any post type that supports the editor ### For Developers @@ -330,7 +330,7 @@ The system instruction guides the AI to: ### Post Meta Storage -- The summary is stored in post meta as `ai_generated_summary` +- The summary is stored in post meta as `wpai_generated_summary` - This meta is registered for the `post` post type and is available in REST API - The meta is updated each time a summary is generated - The meta can be accessed programmatically for custom use cases diff --git a/docs/features/image-generation.md b/docs/features/image-generation.md index e19463fbf..d177222aa 100644 --- a/docs/features/image-generation.md +++ b/docs/features/image-generation.md @@ -43,7 +43,7 @@ All three abilities can be called directly via REST API, making them useful for ### Key Hooks & Entry Points - `WordPress\AI\Features\Image_Generation\Image_Generation::register()` wires everything once the feature is enabled: - - `register_post_meta()` → registers `ai_generated` post meta for attachment post type + - `register_post_meta()` → registers `wpai_generated` post meta for attachment post type - `wp_abilities_api_init` → registers the `ai/image-generation`, `ai/image-import`, and `ai/image-prompt-generation` abilities - `admin_enqueue_scripts` → `enqueue_assets()` loads assets on `post.php` and `post-new.php` screens for post types that support featured images - `enqueue_block_editor_assets` → `enqueue_inline_assets()` loads the same assets in the block editor for inline image generation @@ -73,7 +73,7 @@ All three abilities can be called directly via REST API, making them useful for - Updates the editor store to set the imported image as featured image - Shows a loading state on the button and a progress message (with spinner) under the button while generating; clears both on success or error - Handles error notifications via the notices store - - `AILabel` component displays a label for AI-generated images by checking the `ai_generated` meta + - `AILabel` component displays a label for AI-generated images by checking the `wpai_generated` meta 3. **React Side (Inline Image Generation):** - `inline.tsx` registers two filters for supported blocks (`core/image`, `core/cover`, `core/media-text`, `core/gallery`): @@ -103,7 +103,7 @@ All three abilities can be called directly via REST API, making them useful for - Accepts base64 image data and metadata (filename, title, description, alt_text, mime_type, meta) - Decodes base64 data and creates temporary file - Uses WordPress `media_handle_sideload()` to import into media library - - Sets attachment metadata and custom meta (like `ai_generated`) + - Sets attachment metadata and custom meta (like `wpai_generated`) - Returns attachment data (id, url, filename, title, description, alt_text) ### Input Schemas @@ -837,7 +837,7 @@ npm run test:php ### Image Metadata -- Imported images are marked with `ai_generated` post meta (set to `1`) +- Imported images are marked with `wpai_generated` post meta (set to `1`) - This meta is registered for the `attachment` post type and is available in REST API - The `AILabel` component checks this meta to display the AI-generated label - Additional custom meta can be passed via the `meta` parameter in the import ability diff --git a/includes/Abilities/Image/Import_Base64_Image.php b/includes/Abilities/Image/Import_Base64_Image.php index edab42c97..df28f195a 100644 --- a/includes/Abilities/Image/Import_Base64_Image.php +++ b/includes/Abilities/Image/Import_Base64_Image.php @@ -295,7 +295,7 @@ protected function import_image( string $data, array $args = array() ) { ); if ( $args['ai_generated'] ) { - $post_data['meta_input']['ai_generated'] = 1; + $post_data['meta_input']['wpai_generated'] = 1; } $attachment_id = media_handle_sideload( $file_array, 0, $args['description'], $post_data ); diff --git a/includes/Admin/Upgrades.php b/includes/Admin/Upgrades.php index 56cd5bced..a3cad8fce 100644 --- a/includes/Admin/Upgrades.php +++ b/includes/Admin/Upgrades.php @@ -13,6 +13,7 @@ use WordPress\AI\Admin\Upgrades\V0_5_0; use WordPress\AI\Admin\Upgrades\V0_6_0; use WordPress\AI\Admin\Upgrades\V1_0_0; +use WordPress\AI\Admin\Upgrades\V1_3_0; // Exit if accessed directly. defined( 'ABSPATH' ) || exit; @@ -52,6 +53,7 @@ final class Upgrades { V0_5_0::class, V0_6_0::class, V1_0_0::class, + V1_3_0::class, ); /** diff --git a/includes/Admin/Upgrades/V1_3_0.php b/includes/Admin/Upgrades/V1_3_0.php new file mode 100644 index 000000000..009b05c7e --- /dev/null +++ b/includes/Admin/Upgrades/V1_3_0.php @@ -0,0 +1,121 @@ +rename_post_meta_key( 'ai_generated', 'wpai_generated' ); + $this->rename_post_meta_key( 'ai_generated_summary', 'wpai_generated_summary' ); + $this->rename_comment_meta_key( 'ai_note', 'wpai_note' ); + + // Cache flush to update any stale data. + wp_cache_flush(); + } + + /** + * Renames a post meta key for every row that uses it. + * + * @since x.x.x + * + * @param string $old_key The existing meta key. + * @param string $new_key The meta key to migrate to. + */ + private function rename_post_meta_key( string $old_key, string $new_key ): void { + global $wpdb; + + // Rename the old key to the new key, but only for posts that don't already + // have the new key set. This avoids creating duplicate meta if new data was + // written under the new key before this migration ran. + $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + "UPDATE {$wpdb->postmeta} AS pm + LEFT JOIN {$wpdb->postmeta} AS existing + ON existing.post_id = pm.post_id AND existing.meta_key = %s + SET pm.meta_key = %s + WHERE pm.meta_key = %s AND existing.meta_id IS NULL", + $new_key, + $new_key, + $old_key + ) + ); + + // Any rows still using the old key are duplicates of a pre-existing new key. + // The new value is authoritative, so remove the redundant old rows. + $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->postmeta, + array( 'meta_key' => $old_key ) + ); + } + + /** + * Renames a comment meta key for every row that uses it. + * + * @since x.x.x + * + * @param string $old_key The existing meta key. + * @param string $new_key The meta key to migrate to. + */ + private function rename_comment_meta_key( string $old_key, string $new_key ): void { + global $wpdb; + + // Rename the old key to the new key, but only for comments that don't already + // have the new key set. This avoids creating duplicate meta if new data was + // written under the new key before this migration ran. + $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + "UPDATE {$wpdb->commentmeta} AS cm + LEFT JOIN {$wpdb->commentmeta} AS existing + ON existing.comment_id = cm.comment_id AND existing.meta_key = %s + SET cm.meta_key = %s + WHERE cm.meta_key = %s AND existing.meta_id IS NULL", + $new_key, + $new_key, + $old_key + ) + ); + + // Any rows still using the old key are duplicates of a pre-existing new key. + // The new value is authoritative, so remove the redundant old rows. + $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->commentmeta, + array( 'meta_key' => $old_key ) + ); + } +} diff --git a/includes/Experiments/Editorial_Notes/Editorial_Notes.php b/includes/Experiments/Editorial_Notes/Editorial_Notes.php index c5f4504b3..002c50ec9 100644 --- a/includes/Experiments/Editorial_Notes/Editorial_Notes.php +++ b/includes/Experiments/Editorial_Notes/Editorial_Notes.php @@ -59,7 +59,7 @@ public function register(): void { register_meta( 'comment', - 'ai_note', + 'wpai_note', array( 'type' => 'boolean', 'single' => true, @@ -97,7 +97,7 @@ public function register_abilities(): void { * Overrides the author fields for AI-generated Notes before they are inserted. * * Fires via the rest_pre_insert_comment filter. When the REST request includes - * meta.ai_note = true on a Note (comment_type "note") created by a user who can + * meta.wpai_note = true on a Note (comment_type "note") created by a user who can * edit the target post, replaces the authenticated user's identity with a generic * "AI" author so Notes are not attributed to a personal account. * @@ -114,7 +114,7 @@ public function maybe_set_ai_author( $prepared_comment, \WP_REST_Request $reques $meta = $request->get_param( 'meta' ); - if ( ! is_array( $meta ) || empty( $meta['ai_note'] ) ) { + if ( ! is_array( $meta ) || empty( $meta['wpai_note'] ) ) { return $prepared_comment; } diff --git a/includes/Experiments/Summarization/Summarization.php b/includes/Experiments/Summarization/Summarization.php index 55381d988..e6cdfe08d 100644 --- a/includes/Experiments/Summarization/Summarization.php +++ b/includes/Experiments/Summarization/Summarization.php @@ -89,7 +89,7 @@ public function register_bulk_action_hooks_for_screen(): void { public function register_post_meta(): void { register_meta( 'post', - 'ai_generated_summary', + 'wpai_generated_summary', array( 'type' => 'string', 'single' => true, diff --git a/includes/Features/Image_Generation/Image_Generation.php b/includes/Features/Image_Generation/Image_Generation.php index 002ca6ec0..84b4abada 100644 --- a/includes/Features/Image_Generation/Image_Generation.php +++ b/includes/Features/Image_Generation/Image_Generation.php @@ -134,7 +134,7 @@ public function inject_generate_image_button(): void { public function register_post_meta(): void { register_post_meta( 'attachment', - 'ai_generated', + 'wpai_generated', array( 'type' => 'integer', 'single' => true, diff --git a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts index 8cee1f7d1..94d5db56f 100644 --- a/src/experiments/editorial-notes/hooks/useEditorialNotes.ts +++ b/src/experiments/editorial-notes/hooks/useEditorialNotes.ts @@ -471,7 +471,7 @@ async function createNote( type: 'note', status: 'hold', parent: existingNoteId ?? 0, - meta: { ai_note: true }, + meta: { wpai_note: true }, } ) ) as NoteRecord | undefined; diff --git a/src/experiments/summarization/bulk.ts b/src/experiments/summarization/bulk.ts index 912212799..029028ce3 100644 --- a/src/experiments/summarization/bulk.ts +++ b/src/experiments/summarization/bulk.ts @@ -167,7 +167,7 @@ async function processBulkSummary(): Promise< void > { method: 'POST', data: { content: newContent, - meta: { ai_generated_summary: summary }, + meta: { wpai_generated_summary: summary }, }, } ); } catch { diff --git a/src/experiments/summarization/functions/useSummaryGeneration.ts b/src/experiments/summarization/functions/useSummaryGeneration.ts index c84876787..d783ed0d5 100644 --- a/src/experiments/summarization/functions/useSummaryGeneration.ts +++ b/src/experiments/summarization/functions/useSummaryGeneration.ts @@ -129,7 +129,7 @@ export function useSummaryGeneration() { editPost( { meta: { ...meta, - ai_generated_summary: generatedSummary, + wpai_generated_summary: generatedSummary, }, } ); diff --git a/src/features/image-generation/components/AILabel.tsx b/src/features/image-generation/components/AILabel.tsx index 1ae0f2d47..f388c19bc 100644 --- a/src/features/image-generation/components/AILabel.tsx +++ b/src/features/image-generation/components/AILabel.tsx @@ -48,7 +48,7 @@ export default function AILabel( { label }: AILabelProps ): React.JSX.Element { return ( <> - { image && image?.meta?.ai_generated === 1 && ( + { image && image?.meta?.wpai_generated === 1 && (
assertEquals( 'Custom Test Image Alt Text', get_post_meta( $result['image']['id'], '_wp_attachment_image_alt', true ), 'Attachment alt text should match' ); // Verify the AI-generated flag was saved. - $this->assertSame( '1', get_post_meta( $result['image']['id'], 'ai_generated', true ), 'AI-generated flag should be saved' ); + $this->assertSame( '1', get_post_meta( $result['image']['id'], 'wpai_generated', true ), 'AI-generated flag should be saved' ); } /** diff --git a/tests/Integration/Includes/Admin/Upgrades/V1_3_0Test.php b/tests/Integration/Includes/Admin/Upgrades/V1_3_0Test.php new file mode 100644 index 000000000..ed80134d8 --- /dev/null +++ b/tests/Integration/Includes/Admin/Upgrades/V1_3_0Test.php @@ -0,0 +1,137 @@ +post->create( array( 'post_type' => 'attachment' ) ); + update_post_meta( $attachment_id, 'ai_generated', 1 ); + + ( new V1_3_0( '1.2.0' ) )->run(); + + // Direct SQL updates bypass the in-request meta cache. + wp_cache_flush(); + + $this->assertSame( '1', get_post_meta( $attachment_id, 'wpai_generated', true ), 'ai_generated should migrate to wpai_generated.' ); + $this->assertSame( '', get_post_meta( $attachment_id, 'ai_generated', true ), 'Old ai_generated meta should be removed.' ); + } + + /** + * Tests that run() renames the ai_generated_summary post meta key. + * + * @since x.x.x + */ + public function test_run_renames_summary_meta(): void { + $post_id = self::factory()->post->create(); + update_post_meta( $post_id, 'ai_generated_summary', 'A summary.' ); + + ( new V1_3_0( '1.2.0' ) )->run(); + + wp_cache_flush(); + + $this->assertSame( 'A summary.', get_post_meta( $post_id, 'wpai_generated_summary', true ), 'ai_generated_summary should migrate to wpai_generated_summary.' ); + $this->assertSame( '', get_post_meta( $post_id, 'ai_generated_summary', true ), 'Old ai_generated_summary meta should be removed.' ); + } + + /** + * Tests that run() renames the ai_note comment meta key. + * + * @since x.x.x + */ + public function test_run_renames_note_comment_meta(): void { + $comment_id = self::factory()->comment->create(); + update_comment_meta( $comment_id, 'ai_note', true ); + + ( new V1_3_0( '1.2.0' ) )->run(); + + wp_cache_flush(); + + $this->assertSame( '1', get_comment_meta( $comment_id, 'wpai_note', true ), 'ai_note should migrate to wpai_note.' ); + $this->assertSame( '', get_comment_meta( $comment_id, 'ai_note', true ), 'Old ai_note comment meta should be removed.' ); + } + + /** + * Tests that run() returns true on success. + * + * @since x.x.x + */ + public function test_run_returns_success(): void { + $this->assertTrue( ( new V1_3_0( '1.2.0' ) )->run() ); + } + + /** + * Tests that run() skips migration when the version is already current. + * + * @since x.x.x + */ + public function test_run_skips_when_version_already_current(): void { + $post_id = self::factory()->post->create(); + update_post_meta( $post_id, 'ai_generated_summary', 'A summary.' ); + + ( new V1_3_0( '1.3.0' ) )->run(); + + wp_cache_flush(); + + $this->assertSame( 'A summary.', get_post_meta( $post_id, 'ai_generated_summary', true ), 'Old meta should be untouched when the upgrade is skipped.' ); + $this->assertSame( '', get_post_meta( $post_id, 'wpai_generated_summary', true ), 'New meta should not be written when the upgrade is skipped.' ); + } + + /** + * Tests that run() does not create duplicate post meta when the new key + * already exists, keeping the new value and removing the legacy row. + * + * @since x.x.x + */ + public function test_run_does_not_duplicate_post_meta_when_new_key_exists(): void { + $post_id = self::factory()->post->create(); + update_post_meta( $post_id, 'ai_generated_summary', 'Legacy summary.' ); + update_post_meta( $post_id, 'wpai_generated_summary', 'New summary.' ); + + ( new V1_3_0( '1.2.0' ) )->run(); + + wp_cache_flush(); + + $this->assertSame( array( 'New summary.' ), get_post_meta( $post_id, 'wpai_generated_summary', false ), 'The new key should keep its value with no duplicate row.' ); + $this->assertSame( '', get_post_meta( $post_id, 'ai_generated_summary', true ), 'The legacy post meta row should be removed.' ); + } + + /** + * Tests that run() does not create duplicate comment meta when the new key + * already exists, keeping the new value and removing the legacy row. + * + * @since x.x.x + */ + public function test_run_does_not_duplicate_comment_meta_when_new_key_exists(): void { + $comment_id = self::factory()->comment->create(); + update_comment_meta( $comment_id, 'ai_note', 'Legacy note.' ); + update_comment_meta( $comment_id, 'wpai_note', 'New note.' ); + + ( new V1_3_0( '1.2.0' ) )->run(); + + wp_cache_flush(); + + $this->assertSame( array( 'New note.' ), get_comment_meta( $comment_id, 'wpai_note', false ), 'The new key should keep its value with no duplicate row.' ); + $this->assertSame( '', get_comment_meta( $comment_id, 'ai_note', true ), 'The legacy comment meta row should be removed.' ); + } +} diff --git a/tests/Integration/Includes/Experiments/Editorial_Notes/Editorial_NotesTest.php b/tests/Integration/Includes/Experiments/Editorial_Notes/Editorial_NotesTest.php index b9255362e..8159e173a 100644 --- a/tests/Integration/Includes/Experiments/Editorial_Notes/Editorial_NotesTest.php +++ b/tests/Integration/Includes/Experiments/Editorial_Notes/Editorial_NotesTest.php @@ -116,14 +116,15 @@ public function test_register_abilities_registers_editorial_notes_ability() { } /** - * Tests that the ai_note comment meta is registered with show_in_rest. + * Tests that the wpai_note comment meta is registered with show_in_rest. * * @since 0.4.0 + * @since x.x.x Renamed test method name from `test_ai_note_comment_meta_is_registered`. */ - public function test_ai_note_comment_meta_is_registered() { + public function test_wpai_note_comment_meta_is_registered() { $registered = get_registered_meta_keys( 'comment' ); - $this->assertArrayHasKey( 'ai_note', $registered, 'ai_note meta should be registered for comments' ); - $this->assertTrue( $registered['ai_note']['show_in_rest'], 'ai_note meta should have show_in_rest enabled' ); + $this->assertArrayHasKey( 'wpai_note', $registered, 'wpai_note meta should be registered for comments' ); + $this->assertTrue( $registered['wpai_note']['show_in_rest'], 'wpai_note meta should have show_in_rest enabled' ); } /** @@ -168,7 +169,7 @@ public function test_enqueue_assets_localizes_filtered_min_content_length() { // ------------------------------------------------------------------------- /** - * Tests that maybe_set_ai_author() overrides author fields when meta.ai_note is true, + * Tests that maybe_set_ai_author() overrides author fields when meta.wpai_note is true, * the comment is a Note, and the current user can edit posts. * * @since 0.4.0 @@ -188,7 +189,7 @@ public function test_maybe_set_ai_author_overrides_author_when_ai_note_true() { ); $request = new \WP_REST_Request( 'POST', '/wp/v2/comments' ); - $request->set_param( 'meta', array( 'ai_note' => true ) ); + $request->set_param( 'meta', array( 'wpai_note' => true ) ); $result = $this->experiment->maybe_set_ai_author( $prepared, $request ); @@ -200,10 +201,10 @@ public function test_maybe_set_ai_author_overrides_author_when_ai_note_true() { } /** - * Tests that maybe_set_ai_author() returns a WP_Error when meta.ai_note is true + * Tests that maybe_set_ai_author() returns a WP_Error when meta.wpai_note is true * but the current user cannot edit posts. * - * Guards against identity spoofing: the author override and the ai_note meta + * Guards against identity spoofing: the author override and the wpai_note meta * auth_callback must enforce the same edit_posts capability, otherwise a * low-privileged user could create a comment attributed to the "WordPress AI" * identity (the comment is committed before the later meta save is rejected). @@ -220,7 +221,7 @@ public function test_maybe_set_ai_author_returns_error_for_unauthorized_user() { ); $request = new \WP_REST_Request( 'POST', '/wp/v2/comments' ); - $request->set_param( 'meta', array( 'ai_note' => true ) ); + $request->set_param( 'meta', array( 'wpai_note' => true ) ); $result = $this->experiment->maybe_set_ai_author( $prepared, $request ); @@ -230,7 +231,7 @@ public function test_maybe_set_ai_author_returns_error_for_unauthorized_user() { /** * Tests that maybe_set_ai_author() does not override the author for a non-Note - * comment, even when meta.ai_note is true and the user can edit posts. + * comment, even when meta.wpai_note is true and the user can edit posts. * * The AI identity is reserved for Notes; a regular comment must remain attributed * to its actual author. @@ -250,7 +251,7 @@ public function test_maybe_set_ai_author_passes_through_non_note_comment() { ); $request = new \WP_REST_Request( 'POST', '/wp/v2/comments' ); - $request->set_param( 'meta', array( 'ai_note' => true ) ); + $request->set_param( 'meta', array( 'wpai_note' => true ) ); $result = $this->experiment->maybe_set_ai_author( $prepared, $request ); @@ -260,7 +261,7 @@ public function test_maybe_set_ai_author_passes_through_non_note_comment() { } /** - * Tests that maybe_set_ai_author() leaves data unchanged when meta.ai_note is absent. + * Tests that maybe_set_ai_author() leaves data unchanged when meta.wpai_note is absent. * * @since 0.4.0 */ @@ -281,7 +282,7 @@ public function test_maybe_set_ai_author_passes_through_without_ai_note() { } /** - * Tests that maybe_set_ai_author() passes through when meta.ai_note is false. + * Tests that maybe_set_ai_author() passes through when meta.wpai_note is false. * * @since 0.4.0 */ @@ -292,7 +293,7 @@ public function test_maybe_set_ai_author_passes_through_when_ai_note_false() { ); $request = new \WP_REST_Request( 'POST', '/wp/v2/comments' ); - $request->set_param( 'meta', array( 'ai_note' => false ) ); + $request->set_param( 'meta', array( 'wpai_note' => false ) ); $result = $this->experiment->maybe_set_ai_author( $prepared, $request ); @@ -308,7 +309,7 @@ public function test_maybe_set_ai_author_passes_through_when_ai_note_false() { public function test_maybe_set_ai_author_returns_wp_error_unchanged() { $error = new \WP_Error( 'test_error', 'Test error message' ); $request = new \WP_REST_Request( 'POST', '/wp/v2/comments' ); - $request->set_param( 'meta', array( 'ai_note' => true ) ); + $request->set_param( 'meta', array( 'wpai_note' => true ) ); $result = $this->experiment->maybe_set_ai_author( $error, $request ); @@ -321,17 +322,18 @@ public function test_maybe_set_ai_author_returns_wp_error_unchanged() { // ------------------------------------------------------------------------- /** - * Tests that a subscriber posting a comment with meta.ai_note = true is rejected + * Tests that a subscriber posting a comment with meta.wpai_note = true is rejected * and that no comment row is persisted. * * This is the regression guard for the spoofing report: because WordPress core - * commits the comment before the ai_note meta auth_callback runs, a fix that only + * commits the comment before the wpai_note meta auth_callback runs, a fix that only * gated the meta would still leave an orphaned, spoofed comment in the database. * Aborting in the rest_pre_insert_comment filter must prevent the row entirely. * * @since 1.1.0 + * @since x.x.x Renamed test method name from `test_subscriber_ai_note_request_is_rejected_and_persists_no_comment` */ - public function test_subscriber_ai_note_request_is_rejected_and_persists_no_comment() { + public function test_subscriber_wpai_note_request_is_rejected_and_persists_no_comment() { do_action( 'rest_api_init', rest_get_server() ); $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); @@ -343,7 +345,7 @@ public function test_subscriber_ai_note_request_is_rejected_and_persists_no_comm $request = new \WP_REST_Request( 'POST', '/wp/v2/comments' ); $request->set_param( 'post', $post_id ); $request->set_param( 'content', 'This paragraph has accessibility issues and should be rewritten.' ); - $request->set_param( 'meta', array( 'ai_note' => true ) ); + $request->set_param( 'meta', array( 'wpai_note' => true ) ); $response = rest_get_server()->dispatch( $request ); @@ -355,7 +357,7 @@ public function test_subscriber_ai_note_request_is_rejected_and_persists_no_comm } /** - * Tests that an editor posting a comment with meta.ai_note = true succeeds and the + * Tests that an editor posting a comment with meta.wpai_note = true succeeds and the * persisted comment is attributed to the AI identity rather than the editor. * * @since 1.1.0 @@ -371,7 +373,7 @@ public function test_editor_ai_note_request_creates_ai_attributed_comment() { $request->set_param( 'post', $post_id ); $request->set_param( 'content', 'AI editorial suggestion.' ); $request->set_param( 'type', 'note' ); - $request->set_param( 'meta', array( 'ai_note' => true ) ); + $request->set_param( 'meta', array( 'wpai_note' => true ) ); $response = rest_get_server()->dispatch( $request ); @@ -407,7 +409,7 @@ public function test_enqueue_assets_runs_without_error() { // ------------------------------------------------------------------------- /** - * Tests that the ai_note meta auth_callback checks whether the user can edit the + * Tests that the wpai_note meta auth_callback checks whether the user can edit the * comment's post. * * @since 1.1.0 @@ -419,14 +421,14 @@ public function test_ai_note_meta_auth_callback_returns_true_when_user_can_edit_ wp_set_current_user( $user_id ); $registered = get_registered_meta_keys( 'comment' ); - $callback = $registered['ai_note']['auth_callback'] ?? null; + $callback = $registered['wpai_note']['auth_callback'] ?? null; $this->assertIsCallable( $callback, 'auth_callback should be callable' ); - $this->assertTrue( $callback( true, 'ai_note', $comment_id ), 'auth_callback should return true when the user can edit the comment post.' ); + $this->assertTrue( $callback( true, 'wpai_note', $comment_id ), 'auth_callback should return true when the user can edit the comment post.' ); } /** - * Tests that the ai_note meta auth_callback returns false when the user cannot + * Tests that the wpai_note meta auth_callback returns false when the user cannot * edit the comment's post. * * @since 1.1.0 @@ -440,9 +442,9 @@ public function test_ai_note_meta_auth_callback_returns_false_when_user_cannot_e wp_set_current_user( $other_author_id ); $registered = get_registered_meta_keys( 'comment' ); - $callback = $registered['ai_note']['auth_callback'] ?? null; + $callback = $registered['wpai_note']['auth_callback'] ?? null; $this->assertIsCallable( $callback, 'auth_callback should be callable' ); - $this->assertFalse( $callback( true, 'ai_note', $comment_id ), 'auth_callback should return false when the user cannot edit the comment post.' ); + $this->assertFalse( $callback( true, 'wpai_note', $comment_id ), 'auth_callback should return false when the user cannot edit the comment post.' ); } } diff --git a/tests/Integration/Includes/Features/Image_Generation/Image_GenerationTest.php b/tests/Integration/Includes/Features/Image_Generation/Image_GenerationTest.php index 030b64e9e..7d84712f7 100644 --- a/tests/Integration/Includes/Features/Image_Generation/Image_GenerationTest.php +++ b/tests/Integration/Includes/Features/Image_Generation/Image_GenerationTest.php @@ -127,9 +127,9 @@ public function test_feature_registers_post_meta() { // Verify post meta is registered for attachment post type. $meta = get_registered_meta_keys( 'post', 'attachment' ); - $this->assertArrayHasKey( 'ai_generated', $meta, 'ai_generated meta should be registered for attachment post type' ); - $this->assertEquals( 'integer', $meta['ai_generated']['type'], 'ai_generated meta type should be integer' ); - $this->assertTrue( $meta['ai_generated']['show_in_rest'], 'ai_generated meta should be available in REST API' ); + $this->assertArrayHasKey( 'wpai_generated', $meta, 'wpai_generated meta should be registered for attachment post type' ); + $this->assertEquals( 'integer', $meta['wpai_generated']['type'], 'wpai_generated meta type should be integer' ); + $this->assertTrue( $meta['wpai_generated']['show_in_rest'], 'wpai_generated meta should be available in REST API' ); } /** diff --git a/tests/e2e/specs/experiments/editorial-updates.spec.js b/tests/e2e/specs/experiments/editorial-updates.spec.js index 0a3b4fa24..ff8b9b070 100644 --- a/tests/e2e/specs/experiments/editorial-updates.spec.js +++ b/tests/e2e/specs/experiments/editorial-updates.spec.js @@ -82,7 +82,7 @@ test.describe( 'Editorial Updates Experiment', () => { type: 'note', status: 'hold', meta: { - ai_note: true, + wpai_note: true, }, }, } ); @@ -107,7 +107,7 @@ test.describe( 'Editorial Updates Experiment', () => { id: noteId, parent: 0, content: { rendered: '

Make this better.

' }, - meta: { ai_note: true }, + meta: { wpai_note: true }, }; // Intercept all /wp/v2/comments requests to guarantee stable results. @@ -279,7 +279,7 @@ test.describe( 'Editorial Updates Experiment', () => { type: 'note', status: 'hold', meta: { - ai_note: true, + wpai_note: true, }, }, } ); @@ -291,7 +291,7 @@ test.describe( 'Editorial Updates Experiment', () => { id: noteId, parent: 0, content: { rendered: '

Make this clearer.

' }, - meta: { ai_note: true }, + meta: { wpai_note: true }, }; await page.route( /\/wp\/v2\/comments/, async ( route ) => { @@ -433,7 +433,7 @@ test.describe( 'Editorial Updates Experiment', () => { content: 'Fix this.', type: 'note', status: 'hold', - meta: { ai_note: true }, + meta: { wpai_note: true }, }, } ); @@ -453,7 +453,7 @@ test.describe( 'Editorial Updates Experiment', () => { id: noteId, parent: 0, content: { rendered: '

Fix this.

' }, - meta: { ai_note: true }, + meta: { wpai_note: true }, }; // Intercept note queries — always return the note so the button stays visible.