namespace, '/extra-fields/discover', [ [ 'methods' => 'GET', 'callback' => [ $this, 'discover_fields' ], 'permission_callback' => [ $this, 'manage_permissions_check' ], ], ] ); register_rest_route( $this->namespace, '/extra-fields', [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_fields' ], 'permission_callback' => [ $this, 'read_permissions_check' ], ], [ 'methods' => 'PUT', 'callback' => [ $this, 'save_fields' ], 'permission_callback' => [ $this, 'manage_permissions_check' ], 'args' => [ 'fields' => [ 'required' => true, 'type' => 'array', ], ], ], ] ); } /** * Anyone who may see the calendar may see the shape of its event form. * * @psalm-suppress PossiblyUnusedMethod -- Called by the WP REST API as a permission callback. * @psalm-suppress PossiblyUnusedParam -- The REST framework always passes $request. */ public function read_permissions_check( WP_REST_Request $request ): bool|\WP_Error { $rate_limit = $this->check_rate_limit(); if ( is_wp_error( $rate_limit ) ) { return $rate_limit; } // Public, for the same reason the tag and venue lists are: a shortcode // on a public page renders for logged-out visitors too. if ( ! is_user_logged_in() ) { return true; } return $this->check_permission( 'webcal_can_view' ); } /** * @psalm-suppress PossiblyUnusedMethod -- Called by the WP REST API as a permission callback. * @psalm-suppress PossiblyUnusedParam -- The REST framework always passes $request. */ public function manage_permissions_check( WP_REST_Request $request ): bool|\WP_Error { $rate_limit = $this->check_rate_limit(); if ( is_wp_error( $rate_limit ) ) { return $rate_limit; } return $this->check_permission( 'webcal_can_manage_categories' ); } /** * List the defined fields. * * @psalm-suppress PossiblyUnusedMethod -- Called by the WP REST API as a route callback. * @psalm-suppress PossiblyUnusedParam -- The REST framework always passes $request. */ public function get_fields( WP_REST_Request $request ): WP_REST_Response|WP_Error { return new WP_REST_Response( [ 'fields' => SiteExtraFields::all(), 'types' => SiteExtraFields::TYPES, ], 200 ); } /** * Replace the definitions. * * The whole list at once rather than one field at a time: order is part * of the definition, and a per-field endpoint would need a reordering * call anyway. * * @psalm-suppress PossiblyUnusedMethod -- Called by the WP REST API as a route callback. * @psalm-suppress PossiblyUnusedParam -- $request is read on the next line; * Psalm reports it unused for the same reason it reports * the method unused — it cannot see the callback registry. */ public function save_fields( WP_REST_Request $request ): WP_REST_Response|WP_Error { $fields = $request->get_param( 'fields' ); if ( ! is_array( $fields ) ) { return new WP_Error( 'rest_invalid_param', __( 'Fields must be a list.', 'agenticdaisy-calendar' ), [ 'status' => 400 ] ); } /** @var array $fields */ $saved = SiteExtraFields::save( $fields ); // What was stored, not what was sent: names are derived from labels and // adjusted where two labels derive the same one, unusable definitions // are dropped, and the caller needs to see what it actually has rather // than assume it round-tripped. return new WP_REST_Response( [ 'fields' => $saved, 'types' => SiteExtraFields::TYPES, ], 200 ); } /** * Field definitions implied by values already in the database. * * Returned for review rather than saved: labels and select options are * inferred, and a guess offered as a suggestion is useful where the same * guess written silently into a site's configuration is not. * * @psalm-suppress PossiblyUnusedMethod -- Called by the WP REST API as a route callback. * @psalm-suppress PossiblyUnusedParam -- The REST framework always passes $request. */ public function discover_fields( WP_REST_Request $request ): WP_REST_Response|WP_Error { return new WP_REST_Response( [ 'fields' => \WebCalendar\WP\SiteExtraDiscovery::discover() ], 200 ); } }