WP_REST_Server::CREATABLE, 'callback' => [ self::class, 'save' ], 'permission_callback' => [ self::class, 'check_permission' ], 'args' => [ 'seen' => [ 'type' => 'boolean', 'required' => false, ], 'provider' => [ 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field', 'enum' => [ 'openai', 'claude', 'gemini' ], ], 'api_keys' => [ 'type' => 'object', 'required' => false, 'additionalProperties' => [ 'type' => 'string', ], 'sanitize_callback' => static function ( $value ) { if ( ! is_array( $value ) ) { return []; } return array_map( 'sanitize_text_field', $value ); }, ], 'image_provider' => [ 'type' => 'string', 'required' => false, 'sanitize_callback' => 'sanitize_text_field', 'enum' => [ 'openai', 'gemini' ], ], ], ] ); } /** * Handles the onboarding POST endpoint. * * Saves provider, API keys, and onboarding-seen state from the request. * API keys are accepted on every tier (WP.org Guideline 5 — bringing your * own key is never plan-gated). * * For proxy-tier users (no own_api_key capability), also registers the site with * the proxy now rather than waiting for the next admin_init or a failed chat * request — the response's `registered` field reflects the outcome so the * client can set expectations before the user reaches Chat. * * @since 1.0.0 * @since 1.12.0 Removed the own_api_key tier gate. * @param WP_REST_Request $request Incoming REST request. * @return WP_REST_Response|\WP_Error 200 on success (with `registered` bool). */ public static function save( WP_REST_Request $request ): WP_REST_Response|\WP_Error { $seen = $request->get_param( 'seen' ); if ( true === $seen ) { update_option( 'plume_onboarding_seen', true ); } elseif ( false === $seen ) { delete_option( 'plume_onboarding_seen' ); } $api_keys = $request->get_param( 'api_keys' ); $provider = $request->get_param( 'provider' ); if ( $provider ) { update_option( 'plume_default_provider', sanitize_text_field( $provider ) ); } if ( $api_keys && is_array( $api_keys ) ) { $valid = ProviderFactory::PROXY_CAPABLE; $settings = static::make_provider_settings(); foreach ( $api_keys as $p => $key ) { if ( in_array( $p, $valid, true ) && ! empty( $key ) ) { $settings->set_api_key( $p, sanitize_text_field( (string) $key ) ); } } } $image_provider = $request->get_param( 'image_provider' ); if ( $image_provider ) { update_option( 'plume_image_provider', sanitize_text_field( $image_provider ) ); } // Proxy-tier users (no own_api_key) need a site token before their first chat request — // register now, synchronously, rather than leaving it to the next admin_init or a failed chat call. // BYOK users never need a site token, so `registered` stays true (nothing to wait for). $registered = true; if ( 'pro_byok' !== TierManager::get_user_tier() ) { $registered = SiteRegistration::is_registered(); if ( ! $registered ) { SiteRegistration::maybe_register(); $registered = SiteRegistration::is_registered(); } } return new WP_REST_Response( [ 'success' => true, 'registered' => $registered, ], 200 ); } /** * Checks that the current user has the `manage_options` capability. * * @return bool|\WP_Error */ public static function check_permission(): bool|\WP_Error { if ( ! current_user_can( 'manage_options' ) ) { return new \WP_Error( 'rest_forbidden', __( 'You do not have permission to manage plugin settings.', 'plume' ), [ 'status' => 403 ] ); } return true; } /** * Factory method for ProviderSettings — overridable in tests. * * @return ProviderSettings */ protected static function make_provider_settings(): ProviderSettings { return new ProviderSettings(); } }