name = 'open_street_map'; /* * label (string) Multiple words, can include spaces, visible when selecting a field type */ $this->label = __("OpenStreetMap",'acf-openstreetmap-field'); $this->show_in_rest = true; /* * category (string) basic | content | choice | relational | jquery | layout | CUSTOM GROUP NAME */ $this->category = 'jquery'; $this->default_values = [ // hamburg 'lat' => 53.55064, 'lng' => 10.00065, 'zoom' => 12, 'layers' => [ 'OpenStreetMap.Mapnik' ], 'markers' => [], // gm compatibility 'address' => '', 'version' => '', ]; /* * defaults (array) Array of default settings which are merged into the field object. These are used later in settings */ $this->defaults = [ 'center_lat' => $this->default_values['lat'], 'center_lng' => $this->default_values['lng'], 'zoom' => $this->default_values['zoom'], 'height' => 400, 'return_format' => 'leaflet', 'allow_map_layers' => 1, 'max_markers' => '', 'layers' => $this->default_values['layers'], ]; /* * l10n (array) Array of strings that are used in JavaScript. This allows JS strings to be translated in PHP and loaded via: * var message = acf._e('FIELD_NAME', 'error'); */ $this->l10n = []; add_action( 'print_media_templates', [ $this, 'print_media_templates' ] ); } /* * render_field_settings() * * Create extra settings for your field. These are visible when editing a field * * @type action * @since 3.6 * @date 23/01/13 * * @param $field (array) the $field being edited * @return n/a */ function render_field_settings( $field ) { $templates = Core\Templates::instance(); $field = $this->sanitize_field( $field ); $return_choices = $templates->get_templates(); $return_choices = array_map( function( $template ) { return $template['name']; }, $return_choices ); $is_legacy = version_compare( acf()->version, '6.0.0', '<' ); // return_format acf_render_field_setting( $field, [ 'label' => __('Return Format','acf'), 'instructions' => '', 'type' => 'radio', 'name' => 'return_format', 'choices' => [ 'raw' => __("Raw Data",'acf-openstreetmap-field'), ] + $return_choices, 'layout' => 'horizontal', ]); if ( $is_legacy ) { $this->render_field_presentation_settings( $field ); $this->render_field_validation_settings( $field ); } } /** * Renders the field settings used in the "Validation" tab. * * @since 6.0 * * @param array $field The field settings array. * @return void */ function render_field_validation_settings( $field ) { // allow_layer selection acf_render_field_setting( $field, [ 'label' => __( 'Max. number of Markers', 'acf-openstreetmap-field' ), 'instructions' => __( 'Leave empty for infinite markers', 'acf-openstreetmap-field' ), 'name' => 'max_markers', 'type' => 'number', 'ui' => 1, 'min' => 0, 'step' => 1, ]); } /** * Renders the field settings used in the "Presentation" tab. * * @since 6.0 * * @param array $field The field settings array. * @return void */ function render_field_presentation_settings( $field ) { $leafletProviders = Core\LeafletProviders::instance(); $osmProviders = Core\OSMProviders::instance(); acf_render_field_setting( $field, [ 'label' => __( 'Map Appearance', 'acf-openstreetmap-field' ), 'instructions' => __( 'Set zoom, center and select layers being displayed.', 'acf-openstreetmap-field' ), 'type' => 'open_street_map', 'name' => 'leaflet_map', 'return_format' => 'admin', 'attr' => [ 'data-editor-config' => [ 'allow_providers' => true, 'restrict_providers' => false, 'max_markers' => 0, // no markers 'name_prefix' => $field['prefix'], ], 'data-map-layers' => $field['layers'], ], 'value' => [ 'lat' => $field['center_lat'], 'lng' => $field['center_lng'], 'zoom' => $field['zoom'], 'layers' => $field['layers'], 'markers' => [], ], 'wrapper' => [ 'data-name' => 'wrapper', 'class' => 'acf-field-setting-wrapper', ], ] ); // lat acf_render_field_setting( $field, [ 'label' => __('Map Position','acf-openstreetmap-field'), 'instructions' => __('Center the initial map','acf-openstreetmap-field'), 'type' => 'number', 'name' => 'center_lat', 'prepend' => __('lat','acf-openstreetmap-field'), 'placeholder' => $this->default_values['lat'], // 'step' => 0.1, ]); // lng acf_render_field_setting( $field, [ 'label' => __( 'Center', 'acf-openstreetmap-field' ), 'instructions' => __( 'Center the initial map', 'acf-openstreetmap-field' ), 'type' => 'number', 'name' => 'center_lng', 'prepend' => __('lng','acf-openstreetmap-field'), 'placeholder' => $this->default_values['lng'], '_append' => 'center_lat', // 'step' => 0.1, ]); // zoom acf_render_field_setting( $field, [ 'label' => __( 'Zoom', 'acf-openstreetmap-field' ), 'instructions' => __( 'Set the initial zoom level', 'acf-openstreetmap-field' ), 'type' => 'number', 'name' => 'zoom', 'min' => 1, 'max' => 22, 'prepend' => __('zoom','acf-openstreetmap-field'), 'placeholder' => $this->default_values['zoom'], '_append' => 'center_lat', ]); // allow_layer selection acf_render_field_setting( $field, [ 'label' => __( 'Allow layer selection', 'acf-openstreetmap-field' ), 'instructions' => '', 'name' => 'allow_map_layers', 'type' => 'true_false', 'ui' => 1, ]); // Leaflet layers acf_render_field_setting( $field, [ 'label' => __( 'Leaflet Layers', 'acf-openstreetmap-field' ), 'instructions' => '', 'name' => 'layers', 'type' => 'select', 'multiple' => 1, 'choices' => $leafletProviders->get_layers(), 'wrapper' => [ 'class' => 'acf-hidden', ], ]); // map height acf_render_field_setting( $field, [ 'label' => __('Height','acf'), 'instructions' => __('Customize the map height','acf-openstreetmap-field'), 'type' => 'text', 'name' => 'height', 'append' => 'px', ]); } /* * render_field() * * Create the HTML interface for your field * * @param $field (array) the $field being rendered * * @type action * @since 3.6 * @date 23/01/13 * * @param $field (array) the $field being edited * @return n/a */ function render_field( $field ) { $core = Core\Core::instance(); if ( is_null( $field['value'] ) ) { $field['value'] = $this->sanitize_value( [], $field, 'display' ); } // json_encoded value acf_hidden_input([ 'id' => $field['id'], 'name' => $field['name'], 'value' => json_encode( $field['value'] ), 'class' => 'osm-json', ]); $restrict_providers = isset($field['return_format']) && $field['return_format'] === 'osm' ? array_values( Core\OSMProviders::instance()->get_layers() ) : false; $max_markers = $field['max_markers'] === '' ? false : intval( $field['max_markers'] ); if ( 'osm' === $field['return_format'] ) { if ( $max_markers === false ) { // no restriction > max one marker $max_markers = 1; } // only one marker max $max_markers = min( $max_markers, 1 ); } $map_args = [ 'field' => $field + [ 'attr' => [ 'data-editor-config' => [ 'allow_providers' => $field['allow_map_layers'], 'restrict_providers' => $restrict_providers, 'max_markers' => $max_markers, 'name_prefix' => $field['name'], ], ], ], 'map' => $field['value'], ]; if ( Core\Templates::is_supported() ) { get_template_part( 'osm-maps/admin', null, $map_args ); } else { // legacy $attr = [ 'data-editor-config' => json_encode([ 'allow_providers' => $field['allow_map_layers'], 'restrict_providers' => array_values( $providers ), 'max_markers' => $max_markers, 'name_prefix' => $field['name'], ]), 'class' => 'leaflet-map', 'data-height' => $field['height'], 'data-map' => 'leaflet', 'data-map-lng' => $field['value']['lng'], 'data-map-lat' => $field['value']['lat'], 'data-map-zoom' => $field['value']['zoom'], 'data-map-layers' => $field['value']['layers'], 'data-map-markers' => $field['value']['markers'], ]; ?>
>

sanitize_value( $value, $field, 'display' ); return $value; } /** * Sanitize lat, lng, convert legacy properties */ private function sanitize_geodata( $value, $default_latlng = null ) { // convert settings from <= 1.0.1 > display only? if ( isset( $value['center_lat'] ) ) { if ( ( ! isset( $value['lat'] ) || empty( $value['lat'] ) ) && ! empty( $value['center_lat'] ) ) { $value['lat'] = $value['center_lat']; } unset( $value['center_lat'] ); } if ( isset( $value['center_lng'] ) ) { if ( ( ! isset( $value['lng'] ) || empty( $value['lng'] ) ) && ! empty( $value['center_lng'] ) ) { $value['lng'] = $value['center_lng']; } unset( $value['center_lng'] ); } // apply defaults if ( ! is_null( $default_latlng ) ) { $value = wp_parse_args( $value, $default_latlng ); } // typecast values $value['lat'] = floatval( $value['lat'] ); $value['lng'] = floatval( $value['lng'] ); // maybe sanitize zoom if ( isset( $value['zoom'] )) { // boundaries $value['zoom'] = min( 22, max( 0, intval( $value['zoom'] ) ) ); } return $value; } /** * @param array $value array( 'lat' => float, 'lng => float, 'zoom' => int, 'address' => string, 'markers' => array, 'layers' => array ) * @param array $field * @param string $context edit|dispaly|update * @return array Sanitized $value */ private function sanitize_value( $value, $field, $context = '' ) { if ( is_string( $value ) ) { // try to json-decode $value = json_decode( $value ); if ( is_null( $value ) ) { $value = []; } } $value = (array) $value; // sanitize field $field = $this->sanitize_field( $field ); // // Markers // if ( ! isset( $value['markers']) || ! is_array( $value['markers'] ) ) { $value['markers'] = []; } // make sure its an indexed array $value['markers'] = array_values( $value['markers'] ); // Maybe get marker from ACF GoogleMaps data if ( 'display' === $context ) { // display + edit $value = $this->sanitize_geodata( $value, [ 'lat' => $field['center_lat'], 'lng' => $field['center_lng'], 'zoom' => $field['zoom'], ] ); if ( ! empty( $value[ 'address' ] ) ) { // create marker from GM field address if ( $field['max_markers'] !== 0 && ! count( $value[ 'markers' ] ) ) { $value['markers'][] = [ 'label' => wp_kses_post( $value['address'] ), 'default_label' => '', 'lat' => $value['lat'], 'lng' => $value['lng'], ]; } } else { if ( count( $value['markers'] ) ) { // update address from first marker $value['address'] = $value['markers'][0]['label']; } else { $value['address'] = ''; } } } // typecast foreach ( $value['markers'] as &$marker ) { // typecast values $marker['lat'] = floatval( $marker['lat'] ); $marker['lng'] = floatval( $marker['lng'] ); $marker['label'] = wp_kses_post( $marker[ 'label' ], [], $allowed_protocols = '' ); $marker['default_label'] = wp_kses_post( $marker[ 'default_label' ], [], $allowed_protocols = '' ); } // store data to be used by ACF GM Field if ( 'update' === $context ) { $value[ 'version' ] = Core\Core::instance()->get_version(); if ( count( $value['markers'] ) ) { // update address from first marker $value['address'] = $value['markers'][0]['label']; } else { $value['address'] = ''; } } // Sanitize HTML from address $value[ 'address' ] = wp_kses_post( $value[ 'address' ] ); // // Layers // if ( ! isset( $value['layers'] ) || ! is_array( $value['layers'] ) || ! count( $value['layers'] ) || ! $field['allow_map_layers'] ) { $value['layers'] = $field['layers']; } else { // normalize layers $value['layers'] = $this->sanitize_layers( $value['layers'] ); } return array_intersect_key( $value, $this->default_values ); } /** * Sanitize layers */ private function sanitize_layers( $layers ) { $layers = (array) $layers; $layers = array_map( function( $layer ) { if ( 'OpenStreetMap' === $layer ) { $layer = 'OpenStreetMap.Mapnik'; } return $layer; }, $layers ); $layers = array_filter( $layers ); $layers = array_unique( $layers ); $layers = array_values( $layers ); return $layers; } /* * update_value() * * This filter is applied to the $value before it is saved in the db * * @type filter * @since 3.6 * @date 23/01/13 * * @param $value (mixed) the value found in the database * @param $post_id (mixed) the $post_id from which the value was loaded * @param $field (array) the field array holding all the field options * @return $value */ function update_value( $value, $post_id, $field ) { // sanitize data from UI! // normalize markers if ( is_string( $value ) ) { $value = json_decode( stripslashes($value), true ); } if ( ! is_array( $value ) ) { $value = $this->defaults; } $value = $this->sanitize_value( $value, $field, 'update' ); return $value; } /* * format_value() * * This filter is appied to the $value after it is loaded from the db and before it is returned to the template * * @type filter * @since 3.6 * @date 23/01/13 * * @param $value (mixed) the value which was loaded from the database * @param $post_id (mixed) the $post_id from which the value was loaded * @param $field (array) the field array holding all the field options * * @return $value (mixed) the modified value */ function format_value( $value, $post_id, $field ) { // bail early if no value if ( empty( $value ) ) { return $value; } $value = $this->sanitize_value( $value, $field, 'display' ); if ( 'raw' === $field['return_format'] ) { // ensure backwards compatibility <= 1.0.1 $value['center_lat'] = $value['lat']; $value['center_lng'] = $value['lng']; } else if ( Core\Templates::is_supported() ) { if ( 'osm' === $field['return_format'] && has_filter( 'osm_map_iframe_template' ) ) { _deprecated_hook( 'osm_map_iframe_template', '1.3.0', 'theme overrides', 'The filter is no longer in effect.' ); } ob_start(); get_template_part( 'osm-maps/' . $field['return_format'], null, [ 'field' => $field, 'map' => $value, ] ); $value = ob_get_clean(); } else if ( $field['return_format'] === 'admin' ) { // wp < 5.5 $attr = $field['attr'] + [ 'class' => 'leaflet-map', 'data-height' => $field['height'], 'data-map' => 'leaflet', 'data-map-lng' => $value['lng'], 'data-map-lat' => $value['lat'], 'data-map-zoom' => $value['zoom'], 'data-map-layers' => $value['layers'], 'data-map-markers' => $value['markers'], ]; $value = sprintf( '
', acf_esc_attr( $attr ) ); } else if ( $field['return_format'] === 'osm' ) { // wp < 5.5 // features: one marker max. four maps to choose from $osm_providers = Core\OSMProviders::instance(); $iframe_atts = [ 'height' => $field['height'], 'width' => '425', 'frameborder' => 0, 'scrolling' => 'no', 'marginheight' => 0, 'marginwidth' => 0, ]; $html = '
%4$s'; /** * Filter iframe HTML. * * @param string $html Template String. Placeholders: %1$s: iFrame Source, %2$s: iframe attributes, %3$s: URL to bigger map, %4$s: Link-Text. */ $html = apply_filters( 'osm_map_iframe_template', $html ); $value = sprintf( $html, $osm_providers->get_iframe_url( $value ), acf_esc_attr( $iframe_atts ), esc_url( $osm_providers->get_link_url( $value ) ), esc_html__( 'View Larger Map','acf-openstreetmap-field' ) ); } else if ( $field['return_format'] === 'leaflet' ) { // features: multiple markers. lots of maps to choose from $map_attr = [ 'class' => 'leaflet-map', 'data-height' => $field['height'], 'data-map' => 'leaflet', 'data-map-lng' => $value['lng'], 'data-map-lat' => $value['lat'], 'data-map-zoom' => $value['zoom'], 'data-map-layers' => $value['layers'], 'data-map-markers' => $value['markers'], ]; if ( isset( $field['attr'] ) ) { $map_attr = $field['attr'] + $map_attr; } $html = sprintf('
', acf_esc_attr( $map_attr ) ); $value = $html; wp_enqueue_style( 'leaflet' ); } // return return $value; } /** * Apply basic formatting to prepare the value for default REST output. * * @param mixed $value * @param int|string $post_id * @param array $field * @return array|mixed */ public function format_value_for_rest( $value, $post_id, array $field ) { if ( ! $value ) { return null; } return acf_format_numerics( $value ); } //*/ /* * validate_value() * * This filter is used to perform validation on the value prior to saving. * All values are validated regardless of the field's required setting. This allows you to validate and return * messages to the user if the value is not correct * * @type filter * @date 11/02/2014 * @since 5.0.0 * * @param $valid (boolean) validation status based on the value and the field's required setting * @param $value (mixed) the $_POST value * @param $field (array) the field array holding all the field options * @param $input (string) the corresponding input name for $_POST value * @return $valid */ function validate_value( $valid, $value, $field, $input ){ // bail early if not required if( ! $field['required'] || $field['max_markers'] === 0 ) { return $valid; } $value = json_decode( stripslashes( $value ), true ); if ( ! count( $value['markers'] ) ) { return __('Please set a marker on the map.','acf-openstreetmap-field'); } // return return $valid; } /* * load_field() * * This filter is applied to the $field after it is loaded from the database * * @type filter * @date 23/01/2013 * @since 3.6.0 * * @param $field (array) the field array holding all the field options * @return $field */ function load_field( $field ) { return $this->sanitize_field( $field, 'display' ); } /* * update_field() * * This filter is applied to the $field before it is saved to the database * * @type filter * @date 23/01/2013 * @since 3.6.0 * * @param $field (array) the field array holding all the field options * @return $field */ function update_field( $field ) { return $this->sanitize_field( $field, 'update' ); } /** * @param array $field * @param string $context * @return array Sanitized $field */ private function sanitize_field( $field, $context = '' ) { $field = wp_parse_args( $field, $this->defaults ); // typecast and restrict values $field['center_lat'] = floatval( $field['center_lat'] ); $field['center_lng'] = floatval( $field['center_lng'] ); $field['zoom'] = min( 22, max( 1, intval( $field['zoom'] ) ) ); // layers $field['layers'] = $this->sanitize_layers( $field['layers'] ); return $field; } /** * @action wp_footer */ public function maybe_print_media_templates() { if ( ! did_action( 'print_media_templates' ) ) { $this->print_media_templates(); } } /** * @action print_media_templates */ public function print_media_templates() { ?>