$config ) {
$types[] = CPT::post_type_slug( $slug );
}
return $types;
}
// ── REST/meta registration ──────────────────────────────────────────
public function register_meta(): void {
foreach ( $this->managed_post_types() as $post_type ) {
register_post_meta( $post_type, '_mapfield_spots_lat', array(
'type' => 'number',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => function ( $v ) { return is_numeric( $v ) ? (float) $v : ''; },
'auth_callback' => function () { return current_user_can( 'edit_posts' ); },
) );
register_post_meta( $post_type, '_mapfield_spots_lng', array(
'type' => 'number',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => function ( $v ) { return is_numeric( $v ) ? (float) $v : ''; },
'auth_callback' => function () { return current_user_can( 'edit_posts' ); },
) );
register_post_meta( $post_type, '_mapfield_spots_address', array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function () { return current_user_can( 'edit_posts' ); },
) );
register_post_meta( $post_type, '_mapfield_spots_placement_mode', array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'sanitize_callback' => function ( $v ) { return 'click' === $v ? 'click' : 'manual'; },
'auth_callback' => function () { return current_user_can( 'edit_posts' ); },
) );
}
}
// ── Meta box ─────────────────────────────────────────────────────────
public function add_meta_box(): void {
foreach ( $this->managed_post_types() as $post_type ) {
add_meta_box(
'mfs-location',
__( 'Location', 'mapfield-spots' ),
array( $this, 'render' ),
$post_type,
'normal',
'high'
);
}
}
public function render( \WP_Post $post ): void {
wp_nonce_field( self::NONCE_ACTION, self::NONCE_NAME );
$lat = get_post_meta( $post->ID, '_mapfield_spots_lat', true );
$lng = get_post_meta( $post->ID, '_mapfield_spots_lng', true );
$address = get_post_meta( $post->ID, '_mapfield_spots_address', true );
$mode = get_post_meta( $post->ID, '_mapfield_spots_placement_mode', true ) ?: 'manual';
?>
managed_post_types(), true ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// Blank stays '' rather than (float) '' === 0.0 — used to silently
// save an untouched Spot as a real 0,0 (Atlantic), indistinguishable
// from an intentional value. render_map() already skips falsy lat/lng.
$lat_raw = isset( $_POST['mapfield_spots_lat'] ) ? sanitize_text_field( wp_unslash( $_POST['mapfield_spots_lat'] ) ) : '';
$lng_raw = isset( $_POST['mapfield_spots_lng'] ) ? sanitize_text_field( wp_unslash( $_POST['mapfield_spots_lng'] ) ) : '';
$lat = '' !== $lat_raw ? (float) str_replace( ',', '.', $lat_raw ) : '';
$lng = '' !== $lng_raw ? (float) str_replace( ',', '.', $lng_raw ) : '';
update_post_meta( $post_id, '_mapfield_spots_lat', $lat );
update_post_meta( $post_id, '_mapfield_spots_lng', $lng );
update_post_meta( $post_id, '_mapfield_spots_address', sanitize_text_field( wp_unslash( $_POST['mapfield_spots_address'] ?? '' ) ) );
$placement_mode = isset( $_POST['mapfield_spots-placement-mode'] ) ? sanitize_text_field( wp_unslash( $_POST['mapfield_spots-placement-mode'] ) ) : '';
update_post_meta( $post_id, '_mapfield_spots_placement_mode', 'click' === $placement_mode ? 'click' : 'manual' );
// save_post fires before the redirect, too early to print a notice —
// flag it via a query arg instead, same mechanism core uses itself.
if ( ! $lat || ! $lng ) {
add_filter( 'redirect_post_location', array( $this, 'flag_no_location_in_redirect' ) );
}
}
public function flag_no_location_in_redirect( string $location ): string {
return add_query_arg( 'mapfield_spots_no_location', '1', $location );
}
// Post-save counterpart to render()'s inline warning — repeats it as a
// dismissible notice right after Publish/Update, easy to miss otherwise.
public function render_no_location_notice(): void {
$screen = get_current_screen();
if ( ! $screen || ! in_array( $screen->post_type, $this->managed_post_types(), true ) ) {
return;
}
if ( '1' !== sanitize_text_field( wp_unslash( $_GET['mapfield_spots_no_location'] ?? '' ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display flag off core's own redirect query arg, nothing acted on.
return;
}
?>
post_type, $this->managed_post_types(), true ) ) {
return;
}
// MapLibre GL JS, bundled locally — no CDN. Address search/reverse-geocoding
// call Mapbox Search Box/Geocoding directly via fetch() in location-picker.js.
wp_enqueue_style( 'maplibre-gl', MAPFIELD_SPOTS_URL . 'assets/vendor/maplibre-gl/maplibre-gl.css', array(), '4.7.1' );
wp_enqueue_script( 'maplibre-gl', MAPFIELD_SPOTS_URL . 'assets/vendor/maplibre-gl/maplibre-gl.js', array(), '4.7.1', true );
wp_enqueue_style( 'mapfield-spots-admin', MAPFIELD_SPOTS_URL . 'assets/admin.css', array( 'mapfield-spots-palette' ), MAPFIELD_SPOTS_VERSION );
wp_enqueue_script( 'mapfield-spots-admin', MAPFIELD_SPOTS_URL . 'assets/admin.js', array(), MAPFIELD_SPOTS_VERSION, true );
wp_enqueue_script( 'mapfield-spots-mapbox-style-loader', MAPFIELD_SPOTS_URL . 'assets/mapbox-style-loader.js', array(), MAPFIELD_SPOTS_VERSION, true );
wp_enqueue_script( 'mapfield-spots-location', MAPFIELD_SPOTS_URL . 'assets/location-picker.js', array( 'maplibre-gl', 'mapfield-spots-mapbox-style-loader' ), MAPFIELD_SPOTS_VERSION, true );
wp_enqueue_style( 'pimp-scrollbar', MAPFIELD_SPOTS_URL . 'assets/pimp-scrollbar.css', array(), MAPFIELD_SPOTS_VERSION );
wp_enqueue_script( 'pimp-scrollbar', MAPFIELD_SPOTS_URL . 'assets/pimp-scrollbar.js', array(), MAPFIELD_SPOTS_VERSION, true );
$map_type = Map_Types::get_by_post_type( $screen->post_type ) ?? Map_Types::default_config();
$settings = Settings::get_mapbox_settings();
wp_localize_script( 'mapfield-spots-location', 'MapfieldSpots', array(
'token' => $settings['token'],
'style' => $map_type['map']['style_url'] ?: $settings['style_url'],
'center' => array( $map_type['map']['center'][1], $map_type['map']['center'][0] ), // [lng, lat]
'proximity' => array( $map_type['map']['center'][1], $map_type['map']['center'][0] ),
'zoom' => $map_type['map']['zoom'],
'markerSvg' => Marker_Library::resolve( $map_type['map'] )['markup'],
'i18n' => array(
'noToken' => __( 'No Mapbox token configured. Set one under Mapfield Spots → Settings.', 'mapfield-spots' ),
'searchPlaceholder' => __( 'Search for an address…', 'mapfield-spots' ),
'mapError' => __( 'Could not display the map:', 'mapfield-spots' ),
'unsavedWarning' => __( 'This location has an unsaved position change.', 'mapfield-spots' ),
),
) );
}
}