repository = $repository; } /** * Returns a post's coordinates, or null when it has none. * * @param int $post_id Post ID. * @return array{lat: float, lng: float}|null */ public function get( int $post_id ): ?array { $lat = get_post_meta( $post_id, self::META_LAT, true ); $lng = get_post_meta( $post_id, self::META_LNG, true ); if ( '' === $lat || '' === $lng || ! is_numeric( $lat ) || ! is_numeric( $lng ) ) { return null; } return array( 'lat' => (float) $lat, 'lng' => (float) $lng, ); } /** * Reads coordinates from two arbitrary meta keys. * * The meta-keys source's twin of AcfIntegration::read_field_coords(): * both values must be present and numeric, or there is no location. * * @param int $post_id Post ID. * @param string $lat_key Latitude meta key. * @param string $lng_key Longitude meta key. * @return array{lat: float, lng: float}|null */ public function read_from_meta_keys( int $post_id, string $lat_key, string $lng_key ): ?array { $lat = get_post_meta( $post_id, $lat_key, true ); $lng = get_post_meta( $post_id, $lng_key, true ); if ( ! is_numeric( $lat ) || ! is_numeric( $lng ) ) { return null; } return array( 'lat' => (float) $lat, 'lng' => (float) $lng, ); } /** * Writes (or clears) a post's canonical coordinates. * * Runs the mapsemble_post_coordinates filter first, so custom code * can supply, override, or suppress (return null) the coordinates. * * @param int $post_id Post ID. * @param array{lat: float, lng: float}|null $coords Coordinates from the configured source. */ public function write( int $post_id, ?array $coords ): void { $config = $this->find_config_for_post( $post_id ); /** * Filters a post's coordinates before they are stored. * * @param array{lat: float, lng: float}|null $coords Coordinates, or null for none. * @param int $post_id Post ID. * @param MapConfig|null $config The map config covering this post type, if any. */ $coords = apply_filters( 'mapsemble_post_coordinates', $coords, $post_id, $config ); if ( null === $coords || ! $this->is_valid( $coords ) ) { delete_post_meta( $post_id, self::META_LAT ); delete_post_meta( $post_id, self::META_LNG ); return; } update_post_meta( $post_id, self::META_LAT, sprintf( '%.6F', (float) $coords['lat'] ) ); update_post_meta( $post_id, self::META_LNG, sprintf( '%.6F', (float) $coords['lng'] ) ); } /** * Marks a post hidden from (or visible on) maps. * * @param int $post_id Post ID. * @param bool $hidden Whether the post is excluded from maps. */ public function set_hidden( int $post_id, bool $hidden ): void { if ( $hidden ) { update_post_meta( $post_id, self::META_HIDDEN, '1' ); } else { delete_post_meta( $post_id, self::META_HIDDEN ); } } /** * Whether a coordinate pair is within valid WGS84 bounds. * * @param array $coords Candidate coordinates. */ public function is_valid( array $coords ): bool { if ( ! isset( $coords['lat'], $coords['lng'] ) || ! is_numeric( $coords['lat'] ) || ! is_numeric( $coords['lng'] ) ) { return false; } $lat = (float) $coords['lat']; $lng = (float) $coords['lng']; return $lat >= -90.0 && $lat <= 90.0 && $lng >= -180.0 && $lng <= 180.0; } /** * Returns the first WordPress-mode map config covering a post's type. * * Hosted maps are skipped even though they may still carry a post type: * switching a map to hosted deliberately keeps its source settings so * switching back is lossless, and treating that retained value as live * would have a hosted map dictate how content on this site is indexed. * * @param int $post_id Post ID. */ public function find_config_for_post( int $post_id ): ?MapConfig { $post_type = get_post_type( $post_id ); if ( false === $post_type ) { return null; } foreach ( $this->repository->all() as $config ) { if ( ! $config->is_hosted() && $config->post_type === $post_type ) { return $config; } } return null; } }