MapTiler maps in Wordpress widget

WordPress is probably the most famous Content Management System, with many great features, such as widgets or plugins. I’d like to show you how to use WordPress together with MapTiler and give your users the option to display MapTiler maps in any widget area on their sites.

Step 1: Create your Widget Plugin

First things first, we need to create a plugin that will be the base for our widget code. The best place to start is the official WordPress plugin Handbook.

We will start with creating a folder with the plugin and the main plugin file where our code will be.

wordpress $ cd wp-content
wp-content $ cd plugins
plugins $ mkdir maptiler
plugins $ cd maptiler
maptiler $ vi maptiler.php

In the example above, vi the name of the text editor is used. Use whichever editor that is comfortable for you.

According to the WordPress Handbook, there are some header requirements. This is how the header should look like:

/**
 * Plugin Name:       My Basics Plugin
 * Plugin URI:        https://example.com/plugins/the-basics/
 * Description:       Handle the basics with this plugin.
 * Version:           1.10.3
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            John Smith
 * Author URI:        https://author.example.com/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Update URI:        https://example.com/my-plugin/
 * Text Domain:       my-basics-plugin
 * Domain Path:       /languages
 */

So we will follow the instructions and modify the comments as we need:

/**
* Plugin Name:       MapTiler maps in widget
* Plugin URI:        https://maptiler.com/docs/
* Description:       Use the MapTiler maps in your widget area
* Version:           1.0.0
* Requires at least: 5.2
* Requires PHP:      7.2
* Author:            MapTiler
* Author URI:        https://maptiler.com/
* License:           GPL v2 or later
* License URI:       https://www.gnu.org/licenses/gpl-2.0.html
*/

Widget skeleton

We are now going to create the widget itself. This widget will be a PHP class extending the core WordPress class WP_Widget. Basically, our widget will be defined this way:

// The widget class
class MapTiler extends WP_Widget {

	// Main constructor
	public function __construct() {
		/* ... */
	}

	// The widget form (for the backend )
	public function form( $instance ) {	
		/* ... */
	}

	// Update widget settings
	public function update( $new_instance, $old_instance ) {
		/* ... */
	}

	// Display the widget
	public function widget( $args, $instance ) {
		/* ... */
	}

}

// Register the widget
function register_maptiler_widget() {
	register_widget( 'MapTiler' );
}
add_action( 'widgets_init', 'register_maptiler_widget' );

This code gives WordPress all the information the system needs to be able to use the widget:

  1. The constructor, to initiate the widget
  2. The form() function to create the widget form in the administration
  3. The update() function to save widget data during the edition
  4. And the widget() function to display the widget content on the front-end

Constructor

// Widget constructor
public function __construct() {
    parent::__construct(
        'maptiler_widget',
        __( 'Maps by MapTiler', 'text_domain' ),
        array(
            'customize_selective_refresh' => true,
        )
    );
}

‘customize_selective_refresh’ parameter allows the widget to be refreshed under Appearance > Customize when editing the widget so instead of refreshing the entire page only the widget is refreshed when making changes.

The form() function

The main responsibility of the form() function is to output the options form to the admin. Let’s take a breath now and try to figure out, what we will need as options for our plugin. Of course, there should be some TITLE of the widget. Also, if we need to display the map, we must provide a MapTiler API KEY; we should know where is the location we want to focus on (coordinates). This brings us to other parameters, the longitude as LON and the latitude as LAT. It would be also nice if we will be able to ZOOM the map based on our needing. I can also imagine some of you would like to provide the possibility of showing different map styles. Finally, if there is a map in the widget area, there might be additional info below the map in a separate TEXTAREA.

So we have some defaults we need to use:

// Set widget defaults
$defaults = array(
    'title'    => '',
    'key' => '',
    'lat'     => '',
    'lon'     => '',
    'textarea' => '',
    'style' => '',
    'zoom'   => '',
);

// Parse current settings with defaults
extract( wp_parse_args( ( array ) $instance, $defaults ) ); ?>

And now, let’s complete the form of our widget:

<?php // Widget TITLE ?>
<p>
    <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title', 'text_domain' ); ?></label>
    <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
</p>
  
<?php // API Key Field ?>  
<p>  
<label for="<?php echo esc_attr( $this->get_field_id( 'key' ) ); ?>"><?php _e( 'API key:', 'text_domain' ); ?></label>  
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'key' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'key' ) ); ?>" type="text" value="<?php echo esc_attr( $key ); ?>" />  
</p>  

<?php // LAT Field ?>
<p>
    <label for="<?php echo esc_attr( $this->get_field_id( 'lat' ) ); ?>"><?php _e( 'Lat:', 'text_domain' ); ?></label>
    <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'lat' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'lat' ) ); ?>" type="text" value="<?php echo esc_attr( $lat ); ?>" />
</p>

<?php // LON Field ?>
<p>
    <label for="<?php echo esc_attr( $this->get_field_id( 'lon' ) ); ?>"><?php _e( 'Lon:', 'text_domain' ); ?></label>
    <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'lon' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'lon' ) ); ?>" type="text" value="<?php echo esc_attr( $lon ); ?>" />
</p>

<?php // ZOOM ?>
<p>
    <label for="<?php echo $this->get_field_id( 'zoom' ); ?>"><?php _e( 'Zoom', 'text_domain' ); ?></label>
    <select name="<?php echo $this->get_field_name( 'zoom' ); ?>" id="<?php echo $this->get_field_id( 'zoom' ); ?>" class="widefat">
        <?php
        // Your options array
        $options = array(
            ''        => __( 'Select zoom level', 'text_domain' ),
            '0' => __( '0 (Whole world)', 'text_domain' ),
            '1' => __( '1', 'text_domain' ),
            '2' => __( '2', 'text_domain' ),
            '3' => __( '3 (States)', 'text_domain' ),
            '4' => __( '4', 'text_domain' ),
            '5' => __( '5', 'text_domain' ),
            '6' => __( '6 (Big Cities)', 'text_domain' ),
            '7' => __( '7', 'text_domain' ),
            '8' => __( '8', 'text_domain' ),
            '9' => __( '9 (Region)', 'text_domain' ),
            '10' => __( '10', 'text_domain' ),
            '11' => __( '11', 'text_domain' ),
            '12' => __( '12 (Main streets)', 'text_domain' ),
            '13' => __( '13', 'text_domain' ),
            '14' => __( '14 (Detail)', 'text_domain' ),
            '15' => __( '15', 'text_domain' ),
            '16' => __( '16', 'text_domain' ),
            '17' => __( '17 (Max zoom in)', 'text_domain' ),
        );

        // Loop through options and add each one to the select dropdown
        foreach ( $options as $key => $name ) {
            echo '<option value="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '" '. selected( $zoom, $key, false ) . '>'. $name . '</option>';

        } ?>
    </select>
</p>  
  
<?php // STYLE ?>  
<p>  
<label for="<?php echo $this->get_field_id( 'style' ); ?>"><?php _e( 'Style', 'text_domain' ); ?></label>  
<select name="<?php echo $this->get_field_name( 'style' ); ?>" id="<?php echo $this->get_field_id( 'style' ); ?>" class="widefat">  
  <?php  
  // Your options array  
  $options = array(  
    'streets-v2' => __( 'Select the map style', 'text_domain' ),  
    'streets-v2' => __( 'Streets', 'text_domain' ),  
    'satellite' => __( 'Satellite', 'text_domain' ),  
    'outdoor-v2' => __( 'Outdoor', 'text_domain' ),  
    'winter-v2' => __( 'Winter', 'text_domain' ),  
    'dataviz-light' => __( 'Dataviz Light', 'text_domain' ),  
    'dataviz-dark' => __( 'Dataviz Dark', 'text_domain' ),  
    'aquarelle' => __( 'Aquarelle', 'text_domain' ),  
  );  
  
  // Loop through options and add each one to the select dropdown
  foreach ( $options as $key => $name ) {  
    echo '<option value="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '" '. selected( $style, $key, false ) . '>'. $name . '</option>';  
  } ?>  
</select>  
</p>  

<?php // Textarea INFO Field ?>
<p>
    <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"><?php _e( 'Aditional info:', 'text_domain' ); ?></label>
    <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php echo wp_kses_post( $textarea ); ?></textarea>
</p>

And this is how the form looks like in WordPress admin:

Screenshot from 2024-08-05 12-44-08.png

But there are still some steps we need to make to see the map on the front end.

The update() function

This function will help us update whatever we enter in the widget form. Basically, we will check every setting, and if it’s not empty, we will store the value in the database.

// Update widget settings
public function update( $new_instance, $old_instance ) {
    $instance = $old_instance;
    $instance['title']    = isset( $new_instance['title'] ) ? wp_strip_all_tags( $new_instance['title'] ) : '';
    $instance['key'] = isset( $new_instance['key'] ) ? wp_strip_all_tags( $new_instance['key'] ) : '';  
    $instance['lat']     = isset( $new_instance['lat'] ) ? wp_strip_all_tags( $new_instance['lat'] ) : '';
    $instance['lon']     = isset( $new_instance['lon'] ) ? wp_strip_all_tags( $new_instance['lon'] ) : '';
    $instance['zoom']   = isset( $new_instance['zoom'] ) ? wp_strip_all_tags( $new_instance['zoom'] ) : '';
    $instance['textarea'] = isset( $new_instance['textarea'] ) ? wp_kses_post( $new_instance['textarea'] ) : '';
    $instance['style'] = isset( $new_instance['style'] ) ? wp_strip_all_tags( $new_instance['style'] ) : '';
    return $instance;
}

The widget() function

The widget function will display our widget on the front end. First, we will get the settings from the database and display them.

// Display the widget
public function widget( $args, $instance ) {

    extract( $args );

    // Check the widget options
    $title    = isset( $instance['title'] ) ? apply_filters( 'widget_title', $instance['title'] ) : '';
    $key = isset( $instance['key'] ) ? $instance['key'] : '';  
    $lat     = isset( $instance['lat'] ) ? $instance['lat'] : '';
    $lon     = isset( $instance['lon'] ) ? $instance['lon'] : '';
    $textarea = isset( $instance['textarea'] ) ?$instance['textarea'] : '';
    $zoom   = isset( $instance['zoom'] ) ? $instance['zoom'] : '';
    $style = isset( $instance['style'] ) ? $instance['style'] : '';
    $w_id     = isset( $args['widget_id'] ) ? $args['widget_id'] : '';

    // WordPress core before_widget hook (always include )
    echo $before_widget;

    // Display the widget
    echo '<div class="widget widget-text wp_widget_plugin_box">';

    // Display widget title if defined
    if ( $title ) {
        echo $before_title . $title . $after_title;
    }

    // Display the map (container)
    echo '<div id="map_'.$w_id.'" style="width: 400px; height: 300px;"></div>';
  
    // Set KEY field  
    if ( $key ) {  
        echo '<input type="hidden" id="key_map_'.$w_id.'" value="'.$key.'">';  
    }  

    // Set LAT field
    if ( $lat ) {
        echo '<input type="hidden" id="lat_map_'.$w_id.'" value="'.$lat.'">';
    }

    // Set LON field
    if ( $lon ) {
        echo '<input type="hidden" id="lon_map_'.$w_id.'" value="'.$lon.'">';
    }

    // Set ZOOM field
    if ( $zoom == 0 || ($zoom > 0 && $zoom < 18)) {
        echo '<input type="hidden" id="zoom_map_'.$w_id.'" value="'.$zoom.'">';
    } else {
        echo '<input type="hidden" id="zoom_map_'.$w_id.'" value="7">'; //default
    }
  
    // Set STYLE field  
    if ( $style ) {  
        echo '<input type="hidden" id="style_map_'.$w_id.'" value="'.$style.'">';  
    } else {  
        echo '<input type="hidden" id="style_map_'.$w_id.'" value="streets-v2">'; //default  
    }  

    // Display textarea field
    if ( $textarea ) {
        echo '<p>' . $textarea . '</p>';
    }

    echo '</div>';

    // WordPress core after_widget hook (always include )
    echo $after_widget;

}

So far so good, widget is in its place and if not, just make sure you already placed it by moving the widget to the footer in the WordPress admin area (either under Appearance > Widgets or Appearance > Customize > Widgets). But MAP is still missing, right?

So let’s create another file, this time a javascript file in our maptiler plugin folder:

maptiler $ vi maptiler_maps.js

MapTiler Maps

We are in maptiler_maps.js file and this is where the magic happen:

(function( $ ) {
    'use strict';

    // 1. DO the stuff when DOM ready
    $(function() {

        console.log('DOM Ready and plugin js works');

        // Go and find every widget where the map is expected
        $('div[id^="map_maptiler_widget"]').each(function( index ) {

            // Get the settings we stored by widget  
            var key = $("#key_" + this.id).val();
            var lat = $("#lat_" + this.id).val();
            var lon = $("#lon_" + this.id).val();
            var zoom = $("#zoom_" + this.id).val();
            var style = $("#style_" + this.id).val();

            // Just in case, give me defaults if there is no location saved
            if (typeof lat === "undefined" || typeof lon === "undefined") {
                lat = 55.665957;
                lon = 12.550343;
            }

            // I am interested in ZOOM between 0 ~ 17
            if (zoom && (zoom == 0 || (zoom > 0 && zoom <18))) {
                //good
            } else {
                zoom = 7;
            }

            // Create map using the settings
            var map = new maptilersdk.Map({
                container: this.id,
                style: `https://api.maptiler.com/maps/${style}/style.json?key=${key}`, // stylesheet location
                center: [lon, lat], // starting position [lng, lat]
                zoom: zoom, // starting zoom
            });

            // If there is a complete location (LAT/LON), add marker to map
            if ( lat  &&  lon ) {
                var marker = new maptilersdk.Marker()
                    .setLngLat([lon, lat])
                    .addTo(map);
            }

        });

    });

})( jQuery );

Now, the only final step is to include this script together with MapTiler SDK library in the widget main file:

// Include file with MapTiler maps
function add_maptiler_maps() {
    // Styles
    wp_enqueue_style( 'maptilersdk-style', 'https://cdn.maptiler.com/maptiler-sdk-js/v2.0.3/maptiler-sdk.css', null, '1.0.0', 'all' );

    // Scripts
    wp_enqueue_script('maptilersdk-scripts', 'https://cdn.maptiler.com/maptiler-sdk-js/v2.0.3/maptiler-sdk.umd.min.js', null , '1.0.0', false );
    wp_enqueue_script( 'script', plugin_dir_url( __FILE__ ) . '/maptiler_map.js', array ( 'jquery' ), 1.1, true);
}
add_action( 'wp_enqueue_scripts', 'add_maptiler_maps' );

So now we have two files:

maptiler.php

<?php  
/**  
* Plugin Name: MapTiler maps in widget  
* Plugin URI: https://maptiler.com/docs/  
* Description: Use the MapTiler maps in your widget area  
* Version: 1.0.0  
* Requires at least: 5.2  
* Requires PHP: 7.2  
* Author: MapTiler  
* Author URI: https://maptiler.com/  
* License: GPL v2 or later  
* License URI: https://www.gnu.org/licenses/gpl-2.0.html  
*/  
  
// The widget class  
class MapTiler extends WP_Widget {  
  
  // Main constructor  
  public function __construct() {  
    parent::__construct(  
      'maptiler_widget',  
      __( 'Maps by MapTiler', 'text_domain' ),  
      array(  
        'customize_selective_refresh' => true,  
      )  
    );  
  }  
  
  // The widget form (for the backend )  
  public function form( $instance ) {   
    // Set widget defaults  
    $defaults = array(  
      'title' => '',  
      'key' => '',  
      'lat' => '',  
      'lon' => '',  
      'textarea' => '',  
      'style' => '',  
      'zoom' => '',  
    );  
  
    // Parse current settings with defaults  
    extract( wp_parse_args( ( array ) $instance, $defaults ) ); ?>  
  
    <?php // Widget TITLE ?>  
    <p>  
      <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title', 'text_domain' ); ?></label>  
      <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />  
    </p>  
  
    <?php // API Key Field ?>  
    <p>  
      <label for="<?php echo esc_attr( $this->get_field_id( 'key' ) ); ?>"><?php _e( 'API key:', 'text_domain' ); ?></label>  
      <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'key' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'key' ) ); ?>" type="text" value="<?php echo esc_attr( $key ); ?>" />  
    </p>  
  
    <?php // LAT Field ?>  
    <p>  
      <label for="<?php echo esc_attr( $this->get_field_id( 'lat' ) ); ?>"><?php _e( 'Lat:', 'text_domain' ); ?></label>  
      <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'lat' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'lat' ) ); ?>" type="text" value="<?php echo esc_attr( $lat ); ?>" />  
    </p>  
  
    <?php // LON Field ?>  
    <p>  
      <label for="<?php echo esc_attr( $this->get_field_id( 'lon' ) ); ?>"><?php _e( 'Lon:', 'text_domain' ); ?></label>  
      <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'lon' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'lon' ) ); ?>" type="text" value="<?php echo esc_attr( $lon ); ?>" />  
    </p>  
  
    <?php // ZOOM ?>  
    <p>  
      <label for="<?php echo $this->get_field_id( 'zoom' ); ?>"><?php _e( 'Zoom', 'text_domain' ); ?></label>  
      <select name="<?php echo $this->get_field_name( 'zoom' ); ?>" id="<?php echo $this->get_field_id( 'zoom' ); ?>" class="widefat">  
        <?php  
        // Your options array  
        $options = array(  
          '' => __( 'Select zoom level', 'text_domain' ),  
          '0' => __( '0 (Whole world)', 'text_domain' ),  
          '1' => __( '1', 'text_domain' ),  
          '2' => __( '2', 'text_domain' ),  
          '3' => __( '3 (States)', 'text_domain' ),  
          '4' => __( '4', 'text_domain' ),  
          '5' => __( '5', 'text_domain' ),  
          '6' => __( '6 (Big Cities)', 'text_domain' ),  
          '7' => __( '7', 'text_domain' ),  
          '8' => __( '8', 'text_domain' ),  
          '9' => __( '9 (Region)', 'text_domain' ),  
          '10' => __( '10', 'text_domain' ),  
          '11' => __( '11', 'text_domain' ),  
          '12' => __( '12 (Main streets)', 'text_domain' ),  
          '13' => __( '13', 'text_domain' ),  
          '14' => __( '14 (Detail)', 'text_domain' ),  
          '15' => __( '15', 'text_domain' ),  
          '16' => __( '16', 'text_domain' ),  
          '17' => __( '17 (Max zoom in)', 'text_domain' ),  
        );  
  
        // Loop through options and add each one to the select dropdown  
        foreach ( $options as $key => $name ) {  
          echo '<option value="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '" '. selected( $zoom, $key, false ) . '>'. $name . '</option>';  
  
        } ?>  
      </select>  
    </p>  
  
    <?php // STYLE ?>  
    <p>  
      <label for="<?php echo $this->get_field_id( 'style' ); ?>"><?php _e( 'Style', 'text_domain' ); ?></label>  
      <select name="<?php echo $this->get_field_name( 'style' ); ?>" id="<?php echo $this->get_field_id( 'style' ); ?>" class="widefat">  
      <?php  
        // Your options array  
        $options = array(  
          'streets-v2' => __( 'Select the map style', 'text_domain' ),  
          'streets-v2' => __( 'Streets', 'text_domain' ),  
          'satellite' => __( 'Satellite', 'text_domain' ),  
          'outdoor-v2' => __( 'Outdoor', 'text_domain' ),  
          'winter-v2' => __( 'Winter', 'text_domain' ),  
          'dataviz-light' => __( 'Dataviz Light', 'text_domain' ),  
          'dataviz-dark' => __( 'Dataviz Dark', 'text_domain' ),  
          'aquarelle' => __( 'Aquarelle', 'text_domain' ),  
        );  
  
        // Loop through options and add each one to the select dropdown  
        foreach ( $options as $key => $name ) {  
          echo '<option value="' . esc_attr( $key ) . '" id="' . esc_attr( $key ) . '" '. selected( $style, $key, false ) . '>'. $name . '</option>';  
        } ?>  
      </select>  
    </p>  
  
    <?php // Textarea INFO Field ?>  
    <p>  
      <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"><?php _e( 'Aditional info:', 'text_domain' ); ?></label>  
      <textarea class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php echo wp_kses_post( $textarea ); ?></textarea>  
    </p>  
  
  <?php }  
  
  
  // Update widget settings  
  public function update( $new_instance, $old_instance ) {  
    $instance = $old_instance;  
    $instance['title'] = isset( $new_instance['title'] ) ? wp_strip_all_tags( $new_instance['title'] ) : '';  
    $instance['key'] = isset( $new_instance['key'] ) ? wp_strip_all_tags( $new_instance['key'] ) : '';  
    $instance['lat'] = isset( $new_instance['lat'] ) ? wp_strip_all_tags( $new_instance['lat'] ) : '';  
    $instance['lon'] = isset( $new_instance['lon'] ) ? wp_strip_all_tags( $new_instance['lon'] ) : '';  
    $instance['zoom'] = isset( $new_instance['zoom'] ) ? wp_strip_all_tags( $new_instance['zoom'] ) : '';  
    $instance['textarea'] = isset( $new_instance['textarea'] ) ? wp_kses_post( $new_instance['textarea'] ) : '';  
    $instance['style'] = isset( $new_instance['style'] ) ? wp_strip_all_tags( $new_instance['style'] ) : '';  
    return $instance;  
  }  
  
  // Display the widget  
  public function widget( $args, $instance ) {  
    extract( $args );  
  
    // Check the widget options  
    $title = isset( $instance['title'] ) ? apply_filters( 'widget_title', $instance['title'] ) : '';  
    $key = isset( $instance['key'] ) ? $instance['key'] : '';  
    $lat = isset( $instance['lat'] ) ? $instance['lat'] : '';  
    $lon = isset( $instance['lon'] ) ? $instance['lon'] : '';  
    $textarea = isset( $instance['textarea'] ) ?$instance['textarea'] : '';  
    $zoom = isset( $instance['zoom'] ) ? $instance['zoom'] : '';  
    $style = isset( $instance['style'] ) ? $instance['style'] : '';  
    $w_id = isset( $args['widget_id'] ) ? $args['widget_id'] : '';  
  
    // WordPress core before_widget hook (always include )  
    echo $before_widget;  
  
    // Display the widget  
    echo '<div class="widget widget-text wp_widget_plugin_box">';  
  
    // Display widget title if defined  
    if ( $title ) {  
      echo $before_title . $title . $after_title;  
    }  
  
    // Display the map (container)  
    echo '<div id="map_'.$w_id.'" style="width: 400px; height: 300px;"></div>';  
  
    // Set KEY field  
    if ( $key ) {  
      echo '<input type="hidden" id="key_map_'.$w_id.'" value="'.$key.'">';  
    }  
  
    // Set LAT field  
    if ( $lat ) {  
     echo '<input type="hidden" id="lat_map_'.$w_id.'" value="'.$lat.'">';  
    }  
  
    // Set LON field  
    if ( $lon ) {  
      echo '<input type="hidden" id="lon_map_'.$w_id.'" value="'.$lon.'">';  
    }  
  
    // Set ZOOM field  
    if ( $zoom == 0 || ($zoom > 0 && $zoom < 18)) {  
      echo '<input type="hidden" id="zoom_map_'.$w_id.'" value="'.$zoom.'">';  
    } else {  
      echo '<input type="hidden" id="zoom_map_'.$w_id.'" value="7">'; //default  
    }   
  
    // Set STYLE field  
    if ( $style ) {  
      echo '<input type="hidden" id="style_map_'.$w_id.'" value="'.$style.'">';  
    } else {  
      echo '<input type="hidden" id="style_map_'.$w_id.'" value="streets-v2">'; //default  
    }  
  
    // Display textarea field  
    if ( $textarea ) {  
      echo '<p>' . $textarea . '</p>';  
    }  
  
    echo '</div>';  
  
    // WordPress core after_widget hook (always include )  
    echo $after_widget;  
  
  }  
  
}  
  
// Include file with MapTiler maps  
function add_maptiler_maps() {  
  // Styles  
  wp_enqueue_style( 'maptilersdk-style', 'https://cdn.maptiler.com/maptiler-sdk-js/v2.0.3/maptiler-sdk.css', null, '1.0.0', 'all' );  
  
  // Scripts  
  wp_enqueue_script('maptilersdk-scripts', 'https://cdn.maptiler.com/maptiler-sdk-js/v2.0.3/maptiler-sdk.umd.min.js', null , '1.0.0', false );  
  wp_enqueue_script( 'script', plugin_dir_url( __FILE__ ) . '/maptiler_map.js', array ( 'jquery' ), 1.1, true);  
}  
add_action( 'wp_enqueue_scripts', 'add_maptiler_maps' );  
  
// Register the widget  
function register_maptiler_widget() {  
  register_widget( 'MapTiler' );  
}  
add_action( 'widgets_init', 'register_maptiler_widget' );

maptiler_map.js

(function( $ ) {  
  'use strict';  
  
  // 1. DO the stuff when DOM ready  
  $(function() {  
  
    console.log('DOM Ready and plugin js works');  
  
    // Go and find every widget where the map is expected  
    $('div[id^="map_maptiler_widget"]').each(function( index ) {  
  
      // Get the settings we stored by widget  
      var key = $("#key_" + this.id).val();  
      var lat = $("#lat_" + this.id).val();  
      var lon = $("#lon_" + this.id).val();  
      var zoom = $("#zoom_" + this.id).val();  
      var style = $("#style_" + this.id).val();  
  
      // Just in case, give me defaults if there is no location saved  
      if (typeof lat === "undefined" || typeof lon === "undefined") {  
        lat = 55.665957;  
        lon = 12.550343;  
      }  
  
      // I am interested in ZOOM between 0 ~ 17  
      if (!zoom || zoom < 0 || zoom > 17) {  
        zoom = 7;  
      }  
  
      // Create map using the settings  
      var map = new maptilersdk.Map({  
        container: this.id,  
        style: `https://api.maptiler.com/maps/${style}/style.json?key=${key}`, // stylesheet location  
        center: [lon, lat], // starting position [lng, lat]  
        zoom: zoom, // starting zoom  
      });  
  
      // If there is a complete location (LAT/LON), add marker to map  
      if ( lat && lon ) {  
        var marker = new maptilersdk.Marker()  
        .setLngLat([lon, lat])  
        .addTo(map);  
      }  
    });  
  
  });  
  
})( jQuery );

The result might look like this (I used the basic Twenty Twenty-One theme included in WP installation)

Congratulation! Now you are using open-source maps and open-source CMS altogether.