true, ); /** * Registers the REST route. * * @param ABDS_SEO_Loader $loader Hook loader. */ public function register_hooks( ABDS_SEO_Loader $loader ): void { $loader->add_action( 'rest_api_init', $this, 'register_routes' ); } /** * Registers POST /ui-preferences. */ public function register_routes(): void { register_rest_route( ABDS_SEO_Rest_Controller::NAMESPACE_V1, '/ui-preferences', array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'update' ), 'permission_callback' => array( $this, 'can_manage' ), 'args' => array( 'key' => array( 'type' => 'string', 'required' => true, ), 'value' => array( 'required' => true, ), ), ) ); } /** * Capability check — the same one that gates seeing the meta box / * editor UI these preferences belong to, not the broader plugin * management capability: these are a content editor's own choices. * * @return bool */ public function can_manage(): bool { return current_user_can( ABDS_SEO_Activator::CONTENT_CAPABILITY ); } /** * Every preference for a user, defaults filled in for anything never * set. Stored as a real array via update_user_meta() (not a bare * scalar) specifically so a false value round-trips correctly — see * update()'s docblock for the bug this avoids. * * @param int $user_id User id. * @return array */ public function get_all( int $user_id ): array { $stored = get_user_meta( $user_id, self::META_KEY, true ); $stored = is_array( $stored ) ? $stored : array(); return array_merge( self::DEFAULTS, $stored ); } /** * One named preference for a user, or its default if never set / not * a known preference name. * * @param int $user_id User id. * @param string $key Preference name. * @return mixed */ public function get( int $user_id, string $key ) { $all = $this->get_all( $user_id ); return $all[ $key ] ?? ( self::DEFAULTS[ $key ] ?? null ); } /** * POST /ui-preferences — persists one named preference for the * current user. Confirmed live bug from 1.2, avoided here by * construction: update_user_meta() with a bare PHP bool value doesn't * round-trip (WordPress only serializes arrays/objects; a raw `false` * lands in the DB as an empty string, indistinguishable from "never * set", so an explicit "off" choice would silently read back as the * default). Storing the WHOLE preferences array (never a bare scalar) * sidesteps this entirely — arrays always go through serialize(). * * @param WP_REST_Request $request Request. * @return WP_REST_Response */ public function update( WP_REST_Request $request ): WP_REST_Response { $key = (string) $request->get_param( 'key' ); if ( ! array_key_exists( $key, self::DEFAULTS ) ) { return new WP_REST_Response( array( 'error' => 'unknown_preference' ), 400 ); } $value = $request->get_param( 'value' ); $user_id = get_current_user_id(); $stored = get_user_meta( $user_id, self::META_KEY, true ); $stored = is_array( $stored ) ? $stored : array(); $stored[ $key ] = $value; update_user_meta( $user_id, self::META_KEY, $stored ); return new WP_REST_Response( array( $key => $value ), 200 ); } }