__( 'Editorial Notes', 'ai' ), 'description' => __( 'Adds editorial suggestions to posts block-by-block, covering Accessibility, Readability, Grammar, and SEO. Requires an AI connector that includes support for text generation models.', 'ai' ), 'category' => Experiment_Category::EDITOR, ); } /** * {@inheritDoc} */ public function register(): void { add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) ); add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_assets' ) ); add_filter( 'rest_pre_insert_comment', array( $this, 'maybe_set_ai_author' ), 10, 2 ); register_meta( 'comment', 'wpai_note', array( 'type' => 'boolean', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function ( $allowed, $meta_key, $comment_id ): bool { $comment = get_comment( $comment_id ); if ( ! $comment instanceof \WP_Comment ) { return false; } return current_user_can( 'edit_post', (int) $comment->comment_post_ID ); }, ) ); } /** * Registers any needed abilities. * * @since 0.4.0 */ public function register_abilities(): void { wp_register_ability( 'ai/' . $this->get_id(), array( 'label' => $this->get_label(), 'description' => $this->get_description(), 'ability_class' => Editorial_Notes_Ability::class, ), ); } /** * 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.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. * * @since 0.4.0 * * @param array|\WP_Error $prepared_comment The prepared comment data for wp_insert_comment(). * @param \WP_REST_Request> $request The REST API request. * @return array|\WP_Error Modified comment data, original on non-AI requests, or WP_Error if unauthorized. */ public function maybe_set_ai_author( $prepared_comment, \WP_REST_Request $request ) { if ( is_wp_error( $prepared_comment ) ) { return $prepared_comment; } $meta = $request->get_param( 'meta' ); if ( ! is_array( $meta ) || empty( $meta['wpai_note'] ) ) { return $prepared_comment; } // Ensure the user has permission to edit the post. $post_id = (int) ( $prepared_comment['comment_post_ID'] ?? $request->get_param( 'post' ) ); if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { return new \WP_Error( 'ai_note_forbidden', __( 'Sorry, you are not allowed to create AI Notes.', 'ai' ), array( 'status' => rest_authorization_required_code() ) ); } // The AI author identity is only ever applied to Notes. if ( 'note' !== ( $prepared_comment['comment_type'] ?? '' ) ) { return $prepared_comment; } $prepared_comment['comment_author'] = __( 'WordPress AI', 'ai' ); $prepared_comment['comment_author_email'] = ''; $prepared_comment['comment_author_url'] = ''; $prepared_comment['user_id'] = 0; return $prepared_comment; } /** * Enqueues and localizes the block editor script. * * @since 0.4.0 */ public function enqueue_assets(): void { Asset_Loader::enqueue_script( 'editorial_notes', 'experiments/editorial-notes', array( 'include_core_abilities' => true ) ); Asset_Loader::localize_script( 'editorial_notes', 'EditorialNotesData', array( 'enabled' => $this->is_enabled(), 'minContentLength' => get_min_content_length( 'editorial-notes', 75 ), ) ); } }