api = $api; $this->repository = $repository; } /** * Registers the REST route. */ public function register_routes(): void { register_rest_route( 'mapsemble/v1', '/map-filters', array( 'methods' => 'GET', 'callback' => array( $this, 'handle' ), 'permission_callback' => static fn (): bool => current_user_can( 'edit_posts' ), 'args' => array( 'mapId' => array( 'type' => 'string', 'required' => true, // The embed's own test of a map id: anything else // is no map, and never reaches Mapsemble. 'pattern' => MapId::PATTERN, // Named explicitly: a custom sanitizer would otherwise // switch the schema checks above off. 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'sanitize_text_field', ), ), ) ); } /** * Lists the map's filters. * * @param WP_REST_Request $request The request. */ public function handle( WP_REST_Request $request ): WP_REST_Response { $map_id = (string) $request->get_param( 'mapId' ); // The sample lives on Mapsemble's own account, which this site's // credentials cannot read: asking would only spend a request on a 404. if ( 'sample' === $map_id || Embed::SAMPLE_MAP_ID === $map_id ) { return $this->error( 'mapsemble_sample_map', __( 'The sample map\'s filters are not listed here.', 'mapsemble' ), 404 ); } if ( ! $this->api->has_credentials() ) { return $this->error( 'mapsemble_not_connected', __( 'Connect this site to Mapsemble to list the map\'s filters.', 'mapsemble' ), 503 ); } $known = $this->is_known( $map_id ); if ( null === $known ) { return $this->error( 'mapsemble_unavailable', __( 'Mapsemble could not be reached.', 'mapsemble' ), 502 ); } if ( ! $known ) { return $this->error( 'mapsemble_not_found', __( 'That map is not on the connected Mapsemble account.', 'mapsemble' ), 404 ); } // Three-way, as get_remote_map() documents: the map, [] for a map // Mapsemble does not know, null for a question it could not answer // (unreachable, rate-limited, credentials refused). $remote = $this->api->get_remote_map( $map_id ); if ( null === $remote ) { return $this->error( 'mapsemble_unavailable', __( 'Mapsemble could not be reached.', 'mapsemble' ), 502 ); } if ( array() === $remote ) { return $this->error( 'mapsemble_not_found', __( 'That map no longer exists on the connected Mapsemble account.', 'mapsemble' ), 404 ); } $filters = array(); foreach ( Client::catalog( $remote, 'filters' ) as $slug => $entry ) { $label = is_scalar( $entry['label'] ?? null ) ? trim( (string) $entry['label'] ) : ''; $filters[] = array( 'slug' => (string) $slug, 'label' => '' !== $label ? $label : (string) $slug, ); } return new WP_REST_Response( $filters ); } /** * Whether this site has a reason to ask Mapsemble about a map. * * A site-fed binding is checked first: it costs no request, and it * answers even while Mapsemble is unreachable. * * @param string $map_id The map id. * @return bool|null Null when the account list could not be read, so * the answer is not known. */ private function is_known( string $map_id ): ?bool { $config = $this->repository->find_by_map_id( $map_id ); if ( null !== $config && ! $config->is_hosted() ) { return true; } $maps = $this->api->get_remote_maps(); if ( isset( $maps[ $map_id ] ) ) { return true; } // An empty list is either an empty account or a failed read; only // the second leaves an error behind. return array() === $maps && '' !== $this->api->get_last_error() ? null : false; } /** * An error response. * * @param string $code REST error code. * @param string $message What went wrong, in words. * @param int $status HTTP status. */ private function error( string $code, string $message, int $status ): WP_REST_Response { return rest_convert_error_to_response( new WP_Error( $code, $message, array( 'status' => $status ) ) ); } }