Improves argument handling and script loading

This commit is contained in:
nine committed 2026-10-18 21:29:09 +02:00
1 parent 177b70631b
commit 46add3f003
4 files changed
+181 -53

No files matched your search

+5 -3
View File
@@ -5,8 +5,6 @@ A [WordPress plugin](https://wordpress.org/plugins) that renders [OpenFreeMap](h
## Installation ## Installation
1. Upload the plugin to your `/wp-plugins/` directory 1. Upload the plugin to your `/wp-plugins/` directory
2. Activate the plugin through the 'Plugins' menu in WordPress 2. Activate the plugin through the 'Plugins' menu in WordPress
3. Go to "Settings > OpenFreemap" to configure the plugin
## Basic Usage ## Basic Usage
Once activated you can render [OpenFreeMap](https://openfreemap.org) map widgets using the shortcode Once activated you can render [OpenFreeMap](https://openfreemap.org) map widgets using the shortcode
@@ -21,9 +19,13 @@ The options are as follows
- `zoom` - `zoom`
Initial zoom of the map (default: `16.5`) Initial zoom of the map (default: `16.5`)
- `color` - `color`
Marker color in CSS notation (default: `#ff0000`) Marker color in hex notation, e.g. `#ff0000` (default: `#ff0000`)
- `height` - `height`
Height of the map widget in CSS notation (default: `450px`) Height of the map widget in CSS notation (default: `450px`)
- `popup_text`
HTML content shown in a popup attached to the marker (default: none)
- `style`
Map style, one of `bright`, `dark`, `fiord`, `liberty`, `positron` (default: `bright`)
## Links ## Links
- https://openfreemap.org - https://openfreemap.org
+18
View File
@@ -0,0 +1,18 @@
var openfreemapModuleUrl;
var openfreemapStyleUrl;
if (document.querySelector('.openfreemap') && typeof openfreemapModuleUrl === 'string') {
if (typeof openfreemapStyleUrl === 'string') {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = openfreemapStyleUrl;
link.onerror = function () {
console.error('[OpenFreeMap] Failed to load the map stylesheet.');
};
document.head.appendChild(link);
}
import(openfreemapModuleUrl).catch(function (error) {
console.error('[OpenFreeMap] Failed to load the map library.', error);
});
}
+31 -10
View File
@@ -1,23 +1,45 @@
import * as maplibregl from '../vendor/maplibre-gl/maplibre-gl.mjs'; import * as maplibregl from '../vendor/maplibre-gl/maplibre-gl.mjs';
const NO_WEBGL_MESSAGE =
'The map could not be displayed. Please enable WebGL or use a different browser.';
function readNumber(value, fallback) {
const parsed = parseFloat(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
document.querySelectorAll('.openfreemap').forEach((container) => { document.querySelectorAll('.openfreemap').forEach((container) => {
const center = [ const center = [
parseFloat(container.dataset.lon), readNumber(container.dataset.lon, 12.3453356),
parseFloat(container.dataset.lat) readNumber(container.dataset.lat, 47.1092664)
]; ];
const zoom = parseFloat(container.dataset.zoom); const zoom = readNumber(container.dataset.zoom, 16.5);
const map = new maplibregl.Map({ const popupText = container.dataset.popup;
style: container.dataset.style,
center: center, let map;
zoom: zoom, try {
container: container map = new maplibregl.Map({
style: container.dataset.style,
center: center,
zoom: zoom,
container: container
});
} catch (error) {
// MapLibre throws when WebGL is unavailable, leaving an empty box behind.
console.error('[OpenFreeMap] Could not initialise the map.', error);
container.textContent = NO_WEBGL_MESSAGE;
return;
}
map.on('error', (event) => {
console.warn('[OpenFreeMap]', event.error);
}); });
const marker = new maplibregl.Marker({ const marker = new maplibregl.Marker({
color: container.dataset.color color: container.dataset.color
}) })
.setLngLat(center) .setLngLat(center)
.addTo(map); .addTo(map);
const popupText = container.dataset.popup;
if (popupText) { if (popupText) {
const popup = new maplibregl.Popup({ const popup = new maplibregl.Popup({
closeOnClick: false closeOnClick: false
@@ -27,4 +49,3 @@ document.querySelectorAll('.openfreemap').forEach((container) => {
} }
map.addControl(new maplibregl.NavigationControl()); map.addControl(new maplibregl.NavigationControl());
}); });
+127 -40
View File
@@ -2,9 +2,13 @@
/* /*
Plugin Name: OpenFreeMap Plugin Name: OpenFreeMap
Description: Add an OpenFreeMap widget via shortcode [openfreemap] Description: Add an OpenFreeMap widget via shortcode [openfreemap]
Version: 0.2.0 Version: 0.3.0
Author: Erwin Nindl Author: Erwin Nindl
Author URI: https://nindl.net Author URI: https://nindl.net
License: MIT
Requires at least: 5.9
Requires PHP: 7.4
Text Domain: openfreemap
*/ */
if (!defined('ABSPATH')) if (!defined('ABSPATH'))
@@ -12,6 +16,79 @@ if (!defined('ABSPATH'))
exit; // Exit if accessed directly exit; // Exit if accessed directly
} }
// Clamp a numeric shortcode attribute, falling back to $default when it is not a number
function openfreemap_clamp_float($value, $default, $min, $max)
{
if (!is_numeric($value)) {
return $default;
}
return max($min, min($max, (float) $value));
}
// Allow only plain CSS lengths so the value can be interpolated into a style attribute
function openfreemap_sanitize_css_length($value, $default)
{
$value = trim((string) $value);
if (preg_match('/^\d+(?:\.\d+)?(?:px|pt|em|rem|ch|%|vh|vw|vmin|vmax)$/i', $value)) {
return $value;
}
return $default;
}
// Read the release version from the plugin header, keeping the header authoritative
function openfreemap_plugin_version()
{
static $version = null;
if (null === $version) {
$header = get_file_data(__FILE__, array('Version' => 'Version'), 'plugin');
$version = isset($header['Version']) && $header['Version'] ? $header['Version'] : null;
}
return $version;
}
// Append a cache busting query argument to a bundled asset URL. Every bundled
// asset (vendored or not) shares the plugin's Version header, so bumping that
// header on release is what invalidates caches -- including for vendored
// files (e.g. a new MapLibre or OpenFreeMap style release) that don't carry
// their own version number.
function openfreemap_versioned_url($relative_path)
{
$url = plugin_dir_url(__FILE__) . $relative_path;
$version = openfreemap_plugin_version();
return $version ? add_query_arg('ver', $version, $url) : $url;
}
// URL of a bundled OpenFreeMap style, versioned like every other shipped asset
function openfreemap_style_url($style)
{
return openfreemap_versioned_url('vendor/openfreemap/styles/' . sanitize_key($style));
}
// Styles bundled under vendor/openfreemap/styles/. Kept as an explicit allow-list
// so the shortcode can't be used to probe for arbitrary files on disk.
function openfreemap_available_styles()
{
return array('bright', 'dark', 'fiord', 'liberty', 'positron');
}
// Restrict the shortcode's style attribute to a known, bundled style
function openfreemap_sanitize_style($value, $default)
{
$value = sanitize_key($value);
if (in_array($value, openfreemap_available_styles(), true)) {
return $value;
}
return $default;
}
// Register the shortcode // Register the shortcode
function openfreemap_handler($atts) function openfreemap_handler($atts)
{ {
@@ -21,16 +98,19 @@ function openfreemap_handler($atts)
'popup_text' => '', 'popup_text' => '',
'color' => '#ff0000', 'color' => '#ff0000',
'height' => '450px', 'height' => '450px',
'zoom' => '16.5' 'zoom' => '16.5',
'style' => 'bright'
), $atts, 'openfreemap'); ), $atts, 'openfreemap');
$lat = is_numeric($atts['lat']) ? (float) $atts['lat'] : 47.1092664; $lat = openfreemap_clamp_float($atts['lat'], 47.1092664, -90, 90);
$lon = is_numeric($atts['lon']) ? (float) $atts['lon'] : 12.3453356; $lon = openfreemap_clamp_float($atts['lon'], 12.3453356, -180, 180);
$zoom = is_numeric($atts['zoom']) ? (float) $atts['zoom'] : 16.5; $zoom = openfreemap_clamp_float($atts['zoom'], 16.5, 0, 22);
$color = sanitize_hex_color($atts['color']); $color = sanitize_hex_color($atts['color']);
if (!$color) { if (!$color) {
$color = '#ff0000'; $color = '#ff0000';
} }
$height = sanitize_text_field($atts['height']); $height = openfreemap_sanitize_css_length($atts['height'], '450px');
$popup_text = wp_kses_post($atts['popup_text']);
$style = openfreemap_sanitize_style($atts['style'], 'bright');
$map_id = wp_unique_id('openfreemap-'); $map_id = wp_unique_id('openfreemap-');
ob_start(); ob_start();
@@ -43,8 +123,8 @@ function openfreemap_handler($atts)
data-lon="<?php echo esc_attr($lon); ?>" data-lon="<?php echo esc_attr($lon); ?>"
data-zoom="<?php echo esc_attr($zoom); ?>" data-zoom="<?php echo esc_attr($zoom); ?>"
data-color="<?php echo esc_attr($color); ?>" data-color="<?php echo esc_attr($color); ?>"
data-popup="<?php echo esc_attr($atts['popup_text']); ?>" data-popup="<?php echo esc_attr($popup_text); ?>"
data-style="<?php echo esc_url(plugin_dir_url(__FILE__) . 'vendor/openfreemap/styles/bright'); ?>" data-style="<?php echo esc_url(openfreemap_style_url($style)); ?>"
></div> ></div>
<?php <?php
@@ -53,45 +133,52 @@ function openfreemap_handler($atts)
add_shortcode('openfreemap', 'openfreemap_handler'); add_shortcode('openfreemap', 'openfreemap_handler');
// Detect shortcode presence in post content // Enqueue the map stylesheet and a small loader
function openfreemap_detect_shortcode($posts) function openfreemap_enqueue_assets()
{ {
if (empty($posts) || !is_array($posts)) { static $enqueued = false;
return $posts; if ($enqueued) {
return;
} }
foreach ($posts as $post) {
if (has_shortcode($post->post_content, 'openfreemap')) { // Skip entirely on singular posts/pages that have neither the shortcode
add_action('wp_enqueue_scripts', 'openfreemap_enqueue_shortcode_assets'); // in their content nor a shortcode block. Widgets, template parts, and
break; // Only need to enqueue once // secondary loops aren't covered by this check, so it's a partial
// optimization, not a guarantee.
if (is_singular()) {
$post = get_post();
if ($post && !has_shortcode($post->post_content, 'openfreemap') && !has_block('core/shortcode', $post)) {
return;
} }
} }
return $posts;
}
add_filter('the_posts', 'openfreemap_detect_shortcode');
$enqueued = true;
// Enqueue scripts and styles only if shortcode is used // The loader is a classic script: it conditionally dynamic-imports the ES
function openfreemap_enqueue_shortcode_assets() // module below and injects the stylesheet, so both the ~1 MB of MapLibre JS
{ // and its CSS are only fetched on pages that actually render a map.
$plugin_url = plugin_dir_url(__FILE__); // Detection happens in the DOM rather than in post content, which also
wp_enqueue_style( // covers widgets, blocks, template parts and secondary loops. The
'openfreemap-style', // is_singular() check above is just a cheap best-effort early exit that
$plugin_url . 'vendor/maplibre-gl/maplibre-gl.css' // skips even the tiny loader request on obviously map-free singular pages;
); // it is not relied on for correctness.
$load_in_footer = false;
wp_enqueue_script( wp_enqueue_script(
'openfreemap-script', 'openfreemap-loader',
$plugin_url . 'assets/openfreemap.js', plugin_dir_url(__FILE__) . 'assets/loader.js',
array(), array(),
null, openfreemap_plugin_version(),
$load_in_footer true
); );
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if ($handle === 'openfreemap-script') {
return '<script type="module" src="' . esc_url($src) . '"></script>';
}
return $tag;
}, 10, 3);
}
add_action('wp_enqueue_scripts', 'openfreemap_enqueue_shortcode_assets');
$module_url = openfreemap_versioned_url('assets/openfreemap.js');
$style_href = openfreemap_versioned_url('vendor/maplibre-gl/maplibre-gl.css');
// JSON_HEX_TAG keeps the values from terminating the inline script early.
wp_add_inline_script(
'openfreemap-loader',
'var openfreemapModuleUrl = ' . wp_json_encode($module_url, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES) . ';' .
'var openfreemapStyleUrl = ' . wp_json_encode($style_href, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES) . ';',
'before'
);
}
add_action('wp_enqueue_scripts', 'openfreemap_enqueue_assets');