\d+)', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'start_job' ), 'permission_callback' => array( $this, 'can_generate' ), 'args' => $this->get_route_args(), ), array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_status' ), 'permission_callback' => array( $this, 'can_view_status' ), 'args' => $this->get_route_args(), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_audio' ), 'permission_callback' => array( $this, 'can_delete' ), 'args' => $this->get_route_args(), ), ) ); } /** * Returns the shared route argument definitions. * * @since 1.5.0 * * @return array> The route args. */ protected function get_route_args(): array { return array( 'id' => array( 'description' => esc_html__( 'The post ID.', 'ai' ), 'type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint', ), ); } /** * Checks whether the current user can trigger audio generation. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return true|\WP_Error True if permitted, WP_Error otherwise. */ public function can_generate( WP_REST_Request $request ) { $post_check = $this->check_post( $request ); if ( is_wp_error( $post_check ) ) { return $post_check; } $post_id = absint( $request['id'] ); if ( ! current_user_can( 'edit_post', $post_id ) || ! current_user_can( 'upload_files' ) ) { return new WP_Error( 'rest_forbidden', esc_html__( 'You do not have permission to generate audio for this post.', 'ai' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks whether the current user can view generation status. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return true|\WP_Error True if permitted, WP_Error otherwise. */ public function can_view_status( WP_REST_Request $request ) { $post_check = $this->check_post( $request ); if ( is_wp_error( $post_check ) ) { return $post_check; } if ( ! current_user_can( 'edit_post', absint( $request['id'] ) ) ) { return new WP_Error( 'rest_forbidden', esc_html__( 'You do not have permission to view audio generation status for this post.', 'ai' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks whether the current user can delete a post's generated audio. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return true|\WP_Error True if permitted, WP_Error otherwise. */ public function can_delete( WP_REST_Request $request ) { $post_check = $this->check_post( $request ); if ( is_wp_error( $post_check ) ) { return $post_check; } $post_id = absint( $request['id'] ); if ( ! current_user_can( 'edit_post', $post_id ) ) { return new WP_Error( 'rest_forbidden', esc_html__( 'You do not have permission to delete audio for this post.', 'ai' ), array( 'status' => rest_authorization_required_code() ) ); } $audio_id = absint( get_post_meta( $post_id, Job_Manager::META_AUDIO_ID, true ) ); if ( $audio_id && get_post( $audio_id ) && ! current_user_can( 'delete_post', $audio_id ) ) { return new WP_Error( 'rest_forbidden', esc_html__( 'You do not have permission to delete the audio file for this post.', 'ai' ), array( 'status' => rest_authorization_required_code() ) ); } return true; } /** * Checks that the requested post exists and is REST-visible. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return true|\WP_Error True if valid, WP_Error otherwise. */ protected function check_post( WP_REST_Request $request ) { $post_id = absint( $request['id'] ); $post = get_post( $post_id ); if ( ! $post ) { return new WP_Error( 'rest_post_not_found', esc_html__( 'Post not found.', 'ai' ), array( 'status' => 404 ) ); } $post_type_obj = get_post_type_object( (string) get_post_type( $post_id ) ); if ( ! $post_type_obj || empty( $post_type_obj->show_in_rest ) ) { return new WP_Error( 'rest_invalid_post_type', esc_html__( 'Audio generation is not available for this post type.', 'ai' ), array( 'status' => 400 ) ); } return true; } /** * Starts (or restarts) a background audio generation job. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return \WP_REST_Response|\WP_Error The status payload or an error. */ public function start_job( WP_REST_Request $request ) { if ( ! has_text_to_speech_support( true ) ) { return new WP_Error( 'unsupported', esc_html__( 'No connected AI provider supports text to speech.', 'ai' ), array( 'status' => 400 ) ); } $result = ( new Job_Manager() )->start_job( absint( $request['id'] ), get_current_user_id() ); if ( is_wp_error( $result ) ) { return new WP_Error( $result->get_error_code(), $result->get_error_message(), array( 'status' => 400 ) ); } return rest_ensure_response( $result ); } /** * Returns the current job/audio status for a post. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return \WP_REST_Response The status payload. */ public function get_status( WP_REST_Request $request ) { return rest_ensure_response( ( new Job_Manager() )->get_status( absint( $request['id'] ) ) ); } /** * Deletes a post's generated audio and its text to speech state. * * @since 1.5.0 * * @param \WP_REST_Request $request The request. * @return \WP_REST_Response The status payload after deletion. */ public function delete_audio( WP_REST_Request $request ) { return rest_ensure_response( ( new Job_Manager() )->delete_audio( absint( $request['id'] ) ) ); } }