repository = $repository; $this->renderer = $renderer; $this->feed_query = $feed_query; $this->cache = $cache; } /** * Registers the REST route. */ public function register_routes(): void { register_rest_route( 'mapsemble/v1', '/feature', array( 'methods' => 'GET', 'callback' => array( $this, 'handle' ), 'permission_callback' => '__return_true', 'args' => array( 'mapId' => array( 'type' => 'string' ), 'id' => array( 'type' => 'string' ), 'language' => array( 'type' => 'string' ), 'location' => array( 'type' => 'string' ), 'filters' => array( 'type' => 'string' ), ), ) ); } /** * Answers the request, from the cache when it can. * * Everything that can say 404 — the refusal, the post's existence, * status, type and membership — runs in front of the cache, so an * unpublished post vanishes at once rather than after a minute. Only * the rendering is cached. * * @param WP_REST_Request $request The request. * @return WP_REST_Response|WP_Error */ public function handle( WP_REST_Request $request ) { $config = $this->repository->find_by_map_id( (string) $request->get_param( 'mapId' ) ); if ( null === $config ) { return $this->respond( '', array(), array() ); } if ( $config->is_hosted() ) { // Cards for a hosted map are rendered by Mapsemble from its own // rows. Rendering one from a post here would answer about content // the map does not show. return $this->hosted_map_refusal(); } $id = (string) $request->get_param( 'id' ); if ( '' === $id || ! ctype_digit( $id ) ) { return new WP_Error( 'mapsemble_not_found', __( 'Feature not found.', 'mapsemble' ), array( 'status' => 404 ) ); } $filters = $this->feed_query->parse_filters( (string) $request->get_param( 'filters' ) ); $card_post = get_post( (int) $id ); if ( null === $card_post || $card_post->post_type !== $config->post_type || 'publish' !== $card_post->post_status // A post the feed would not list must not render a card either: // same 404 as a missing post, so excluded content cannot be probed. || ! $this->feed_query->post_matches( $config, (int) $card_post->ID, $filters ) ) { return new WP_Error( 'mapsemble_not_found', __( 'Feature not found.', 'mapsemble' ), array( 'status' => 404 ) ); } $raw_location = (string) $request->get_param( 'location' ); $location = in_array( $raw_location, self::ALLOWED_LOCATIONS, true ) ? $raw_location : null; // `filters` is not part of the key: it decides membership, which was // settled above, and plays no part in the rendering. return $this->cache->remember( 'feature', $config, $request, array( 'id' => (int) $card_post->ID, 'language' => (string) $request->get_param( 'language' ), 'location' => (string) $location, ), fn (): WP_REST_Response => $this->build( $config, $card_post, $location ) ); } /** * Renders the card response. * * @param MapConfig $config The matched, site-fed map. * @param \WP_Post $card_post The admitted post. * @param string|null $location Validated card location, or null. */ private function build( MapConfig $config, \WP_Post $card_post, ?string $location ): WP_REST_Response { $html = $this->renderer->render( $card_post, $location, $config ); $css_urls = array( add_query_arg( 'ver', MAPSEMBLE_VERSION, plugins_url( 'assets/css/card.css', MAPSEMBLE_PLUGIN_FILE ) ), ); /** * Filters the stylesheet URLs the widget adopts for this card. * * @param string[] $css_urls Absolute CSS URLs. * @param \WP_Post $card_post The post being rendered. * @param \Mapsemble\MapConfig $config The map config. */ $css_urls = (array) apply_filters( 'mapsemble_card_css_urls', $css_urls, $card_post, $config ); /** * Filters the free-form data object returned with the card. * * @param array $data Arbitrary data for custom widget code. * @param \WP_Post $card_post The post being rendered. * @param \Mapsemble\MapConfig $config The map config. */ $data = (array) apply_filters( 'mapsemble_card_data', array(), $card_post, $config ); return $this->respond( $html, array_values( $css_urls ), $data ); } /** * Wraps the payload in the card response shape. * * @param string $html HTML markup ('' when the map is unknown). * @param string[] $css Stylesheet URLs. * @param array $data Free-form data. */ private function respond( string $html, array $css, array $data ): WP_REST_Response { $response = new WP_REST_Response( array( 'html' => $html, 'css' => $css, 'js' => array( 'head' => '', 'foot' => '', ), 'data' => (object) $data, ) ); $response->header( 'Access-Control-Allow-Origin', '*' ); return $response; } }