repository = $repository;
$this->coordinates = $coordinates;
$this->field_groups = $field_groups ?? 'acf_get_field_groups';
$this->fields_of = $fields_of ?? 'acf_get_fields';
}
/**
* Whether ACF is active.
*/
public static function is_available(): bool {
return function_exists( 'get_field' ) && function_exists( 'acf_get_field_groups' );
}
/**
* Hooks the integration when ACF is present.
*/
public function register(): void {
if ( ! self::is_available() ) {
return;
}
add_filter(
'mapsemble_location_sources',
static function ( array $sources ): array {
$sources[ MapConfig::SOURCE_ACF ] = __( 'Advanced Custom Fields (a Google Map field)', 'mapsemble' );
return $sources;
}
);
add_action( 'mapsemble_map_form_location_fields', array( $this, 'render_field_select' ) );
// Priority 20: after ACF has written its own meta for the save.
add_action( 'acf/save_post', array( $this, 'mirror_on_save' ), 20 );
}
/**
* Renders the ACF field picker in the map config form.
*
* @param MapConfig $config The config being edited.
*/
public function render_field_select( MapConfig $config ): void {
$fields = $this->get_map_fields();
// The data attribute lets the edit screen script disable and hide
// the picker while another source is chosen; without the script it
// stays usable.
?>
Field key => label.
*/
private function get_map_fields(): array {
$options = array();
foreach ( (array) call_user_func( $this->field_groups ) as $group ) {
$group_fields = (array) call_user_func( $this->fields_of, $group );
foreach ( $group_fields as $field ) {
if ( is_array( $field ) && 'google_map' === ( $field['type'] ?? '' ) ) {
$options[ (string) $field['key'] ] = sprintf(
'%s — %s',
(string) ( $group['title'] ?? '' ),
(string) ( $field['label'] ?? $field['name'] ?? $field['key'] )
);
}
}
}
return $options;
}
/**
* Mirrors the configured ACF field into the canonical meta on save.
*
* @param int|string $post_id ACF save target (posts give an int/numeric string).
*/
public function mirror_on_save( $post_id ): void {
if ( ! is_numeric( $post_id ) ) {
// Options pages, terms, users — not ours.
return;
}
$post_id = (int) $post_id;
$config = $this->coordinates->find_config_for_post( $post_id );
if ( null === $config
|| MapConfig::SOURCE_ACF !== $config->location_source
|| '' === $config->acf_field ) {
return;
}
$this->coordinates->write( $post_id, $this->read_field_coords( $post_id, $config->acf_field ) );
}
/**
* Reads lat/lng from an ACF google_map field value.
*
* @param int $post_id Post ID.
* @param string $field_key ACF field key.
* @return array{lat: float, lng: float}|null
*/
public function read_field_coords( int $post_id, string $field_key ): ?array {
$value = get_field( $field_key, $post_id, false );
if ( ! is_array( $value ) || ! isset( $value['lat'], $value['lng'] )
|| ! is_numeric( $value['lat'] ) || ! is_numeric( $value['lng'] ) ) {
return null;
}
return array(
'lat' => (float) $value['lat'],
'lng' => (float) $value['lng'],
);
}
/**
* Resolves a post's coordinates from its config's source.
*
* Used by the reindex loop; safe to call whether or not ACF is
* active (an ACF-sourced config simply resolves to null then).
*
* @param int $post_id Post ID.
* @param MapConfig $config The post's map config.
* @return array{lat: float, lng: float}|null
*/
public function resolve( int $post_id, MapConfig $config ): ?array {
if ( MapConfig::SOURCE_ACF === $config->location_source ) {
return self::is_available() && '' !== $config->acf_field
? $this->read_field_coords( $post_id, $config->acf_field )
: null;
}
if ( MapConfig::SOURCE_META_KEYS === $config->location_source ) {
return '' !== $config->lat_meta_key && '' !== $config->lng_meta_key
? $this->coordinates->read_from_meta_keys( $post_id, $config->lat_meta_key, $config->lng_meta_key )
: null;
}
return $this->coordinates->get( $post_id );
}
}