Skip to content

Commit df6d96f

Browse files
JeroenDeDauwclaude
andauthored
Expand the MediaWiki:Maps config page beyond Leaflet layers (#927)
* Expand the MediaWiki:Maps config page beyond Leaflet layers Fixes #926 Follows-up to #925 Generalizes the MediaWiki:Maps config page (added in #925 for Leaflet layers) to expose most Maps settings, driven by a single declarative schema so that exposing a setting is one schema entry rather than bespoke code. ## Design Maps\Config\ConfigSchema lists every exposed setting as a ConfigSetting: page group + key, the egMaps*/smgQP* setting it overrides, its value type, and its merge strategy. It drives both save-time validation and read-time application: * ConfigValidator walks the schema on save (EditFilter hook), rejecting unknown groups/keys and per-value type errors. * EffectiveSettings is the one effective-settings lookup wrapping the PHP settings with the schema-translated wiki overlay. It reads the page lazily at parse time, memoizes the result, falls back to the PHP settings on any Throwable or when disabled, and ignores wiki values that fail their type validation. All exposed-setting consumption sites read through it instead of $GLOBALS. Value types are small reusable objects: boolean, integer, string, enum, enum list, string list, dimension, positive-number map, pattern, plus the layer-definitions and availability specials. Wiki values replace the PHP value, except layerDefinitions, availableLayers and availableOverlays, which merge per name via a MergeStrategy attribute of those entries. The #925 Leaflet-specific classes were reshaped into this general concept: LeafletConfigValidator -> ConfigValidator, WikiLeafletConfigSource -> WikiPageConfigSource (now reads the whole page), CombiningLeafletConfigLookup -> EffectiveSettings; LeafletConfig/LeafletConfigLookup dissolved into LeafletService reading the effective settings directly. LeafletLayerContract and AttributionSanitizer are unchanged. ## Exposed and excluded Groups: general, coordinates, geocoding, semanticMediaWiki, leaflet, googleMaps (see the README for the full key list). Secrets/API keys, script-injection primitives, the caches and setup-time switches stay PHP-only, as decided in the issue. Two settings from the issue's list are deviated from: * defaultService is excluded. egMapsDefaultService is consumed when MappingServices is constructed and when SMW result-format aliases are registered, both during extension setup before the config page can be read. Routing it would force a wiki read at setup, defeating the lazy design, so it stays PHP-only. * internationalDirectionLabels is excluded. egMapsInternatDirectionLabels is not read anywhere in the code, so exposing it would have no effect. distanceUnit is validated as a plain string rather than against the effective units: an unknown default unit self-heals to the first available unit at runtime and cannot hard-fail. ## Security mapWidth/mapHeight use a strict dimension pattern (they reach inline styles), googleMaps.language a strict pattern (it reaches the Google API URL), and distance unit names are restricted to alphanumerics (they reach a regex and rendered output). Layer definitions keep the #925 hardening unchanged. ## Tests Table-driven unit tests per value type, schema and validator tests, EffectiveSettings merge, fallback and memoization tests, and integration spot checks that a wiki-set value lands end to end. All #925 tests are preserved through the renames. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Reject a trailing newline in the config value patterns The dimension, distance-unit-name and Google Maps language patterns anchored with $, which matches just before a trailing newline. Add the D modifier so a value with a trailing newline is rejected, keeping these strict patterns fully strict before their style, URL and regex sinks. Harmless in practice since the sinks escape or encode, found in review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Correct README wording about config page key naming Several page keys are deliberately renamed from their PHP settings (e.g. pagesWithMapsCategory for egMapsEnableCategory), so the reference list should not claim the names are the same. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d3c8bdf commit df6d96f

72 files changed

Lines changed: 2336 additions & 1182 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DefaultSettings.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@
2828

2929
// Boolean. Whether Maps configuration may also be set on the MediaWiki:Maps JSON config page,
3030
// which is editable only by users with the editinterface and editsitejson rights. When false,
31-
// that page is never read and only LocalSettings.php applies. The wiki page currently exposes
32-
// the Leaflet layer settings; its values take precedence over LocalSettings.php.
31+
// that page is never read and only LocalSettings.php applies. The wiki page exposes most Maps
32+
// settings, grouped by service and topic (see the README); its values take precedence over
33+
// LocalSettings.php. Secrets, script-injection primitives and setup-time switches stay PHP-only.
3334
'egMapsEnableInWikiConfig' => true,
3435

3536

README.md

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,63 @@ via [Professional.Wiki](https://professional.wiki/). Discounts for work that is
4444

4545
## On-wiki configuration
4646

47-
Most settings are configured in `LocalSettings.php` (see the
48-
[configuration documentation](https://maps.extension.wiki/wiki/Configuration)). Wiki administrators
49-
without server access can also configure the Leaflet layers on the `MediaWiki:Maps` page. It holds
50-
JSON and, like other site configuration, is editable only by users with the `editinterface` and
51-
`editsitejson` rights. For example, to add a custom tile layer that authors can then select with the
52-
`layers` parameter:
47+
Most settings can be configured in `LocalSettings.php` (see the
48+
[configuration documentation](https://maps.extension.wiki/wiki/Configuration)) or, for wiki
49+
administrators without server access, on the `MediaWiki:Maps` page. It holds JSON and, like other
50+
site configuration, is editable only by users with the `editinterface` and `editsitejson` rights.
51+
Settings are grouped by service and topic:
5352

5453
```json
5554
{
55+
"general": {
56+
"mapWidth": "100%",
57+
"mapHeight": "500px",
58+
"distanceUnit": "km"
59+
},
60+
"coordinates": {
61+
"notation": "float",
62+
"directional": false
63+
},
64+
"geocoding": {
65+
"service": "google"
66+
},
5667
"leaflet": {
68+
"defaultZoom": 5,
5769
"layerDefinitions": {
5870
"Historic 1904": {
5971
"url": "https://tiles.example.org/historic1904/{z}/{x}/{y}.png",
6072
"options": { "attribution": "Historic map tiles", "maxZoom": 18 }
6173
}
6274
}
75+
},
76+
"googleMaps": {
77+
"zoom": 5,
78+
"type": "satellite"
6379
}
6480
}
6581
```
6682

67-
The page is validated when saved and combined with `LocalSettings.php`, with the wiki page taking
68-
precedence. Changes take effect the next time a page with a map is parsed.
83+
The available groups and keys correspond to `LocalSettings.php` settings:
84+
85+
* **general**: `mapWidth`, `mapHeight`, `defaultTitle`, `defaultLabel`, `resizableByDefault`,
86+
`rezoomForKml`, `pagesWithMapsCategory`, `distanceUnits`, `distanceUnit`, `distanceDecimals`
87+
* **coordinates**: `availableNotations`, `notation`, `directional`
88+
* **geocoding**: `service` (`geonames`, `google` or `nominatim`)
89+
* **semanticMediaWiki**: `showTitle`, `hideNamespace`, `template`, `coordinateFormat`,
90+
`coordinateDirectional`
91+
* **leaflet**: `layerDefinitions`, `defaultLayers`, `defaultOverlays`, `availableLayers`,
92+
`availableOverlays`, `defaultZoom`
93+
* **googleMaps**: `zoom`, `type`, `types`, `controls`, `typeControlStyle`, `zoomControlStyle`,
94+
`autoInfoWindows`, `layers`, `showPoi`, `language`
95+
96+
The page is validated when saved and combined with `LocalSettings.php`, with the wiki value taking
97+
precedence. Most settings are replaced wholesale; only `layerDefinitions`, `availableLayers` and
98+
`availableOverlays` are merged per name, so the wiki only needs to list the entries it changes.
99+
Changes take effect the next time a page with a map is parsed.
100+
101+
Secrets (API keys), settings that run before a wiki page can be read, and other server-only options
102+
stay exclusive to `LocalSettings.php`. Set `$egMapsEnableInWikiConfig` to `false` to ignore the page
103+
entirely.
69104

70105
## Project status
71106

RELEASE-NOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ different releases and which versions of PHP and MediaWiki they support, see the
77

88
Released on TBD.
99

10-
* Added an on-wiki JSON configuration page at `MediaWiki:Maps` for the Leaflet layer settings (custom layer definitions, the default and available base layers and overlays). It is editable only by administrators and interface administrators, is validated on save, and is combined with `LocalSettings.php` with the wiki page taking precedence. Set `$egMapsEnableInWikiConfig` to `false` to disable it.
10+
* Added an on-wiki JSON configuration page at `MediaWiki:Maps` exposing most Maps settings, grouped by service and topic (general map, coordinate, geocoding, Semantic MediaWiki, Leaflet and Google Maps options), including the Leaflet custom layer definitions and available layers. It is editable only by administrators and interface administrators, is validated on save, and is combined with `LocalSettings.php` with the wiki page taking precedence. Secrets, script-injection primitives and setup-time settings stay exclusive to `LocalSettings.php`. Set `$egMapsEnableInWikiConfig` to `false` to disable it.
1111
* Hardened the custom Leaflet layer definitions used by `$egMapsLeafletLayerDefinitions` and the config page (breaking change): the `url` and `errorTileUrl` must be `http(s)` URLs, only an allowlist of Leaflet `options` is kept, and `attribution` is sanitized to plain text and `http(s)` links. Definitions relying on raw HTML attribution or non-allowlisted options need updating.
1212

1313
## Maps 13.1.0

i18n/en.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,15 @@
270270
"maps-config-error-invalid-json": "The configuration must be a JSON object.",
271271
"maps-config-error-not-object": "The value of \"$1\" must be a JSON object.",
272272
"maps-config-error-unknown-key": "Unknown configuration key \"$1\".",
273+
"maps-config-error-invalid-boolean": "\"$1\" must be true or false.",
274+
"maps-config-error-invalid-integer": "\"$1\" must be a whole number.",
275+
"maps-config-error-integer-too-small": "\"$1\" must be $2 or greater.",
276+
"maps-config-error-invalid-string": "\"$1\" must be text.",
277+
"maps-config-error-invalid-enum": "\"$1\" must be one of: $2.",
278+
"maps-config-error-invalid-enum-list": "\"$1\" must be a list containing only: $2.",
279+
"maps-config-error-invalid-dimension": "\"$1\" must be a size such as 400, \"400px\" or \"auto\".",
280+
"maps-config-error-invalid-number-map": "\"$1\" must be a map of names to positive numbers.",
281+
"maps-config-error-invalid-language": "\"$1\" must be a valid language code, such as \"en\" or \"en-GB\".",
273282
"maps-config-error-invalid-layer-name": "\"$1\" is not a valid layer name.",
274283
"maps-config-error-unknown-layer-key": "Layer \"$1\" has an unknown property \"$2\".",
275284
"maps-config-error-invalid-url": "Layer \"$1\" must have a \"url\" that starts with http:// or https://.",

i18n/qqq.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,17 @@
246246
"maps-validator-message-nodesc": "Indicates that no parameter description is available",
247247
"maps-config-invalid": "Heading shown above the list of errors when an edit to the MediaWiki:Maps JSON config page is rejected.",
248248
"maps-config-error-invalid-json": "Error shown when the MediaWiki:Maps config is not a JSON object.",
249-
"maps-config-error-not-object": "Error shown when a value that must be a JSON object is not. $1 is the location, e.g. \"leaflet\" or \"layerDefinitions\".",
250-
"maps-config-error-unknown-key": "Error shown when the config contains an unrecognized key. $1 is the unknown key.",
249+
"maps-config-error-not-object": "Error shown when a value that must be a JSON object is not. $1 is the location, e.g. \"general\" or \"leaflet.layerDefinitions\".",
250+
"maps-config-error-unknown-key": "Error shown when the config contains an unrecognized group or key. $1 is the unknown key, e.g. \"general\" or \"general.mapWidth\".",
251+
"maps-config-error-invalid-boolean": "Error shown when a setting that must be a boolean is not. $1 is the location, e.g. \"general.resizableByDefault\".",
252+
"maps-config-error-invalid-integer": "Error shown when a setting that must be a whole number is not. $1 is the location, e.g. \"leaflet.defaultZoom\".",
253+
"maps-config-error-integer-too-small": "Error shown when a whole-number setting is below its minimum. $1 is the location, $2 is the minimum.",
254+
"maps-config-error-invalid-string": "Error shown when a setting that must be text is not. $1 is the location, e.g. \"general.defaultTitle\".",
255+
"maps-config-error-invalid-enum": "Error shown when a setting is not one of its allowed values. $1 is the location, $2 is the comma-separated list of allowed values.",
256+
"maps-config-error-invalid-enum-list": "Error shown when a setting is not a list of allowed values. $1 is the location, $2 is the comma-separated list of allowed values.",
257+
"maps-config-error-invalid-dimension": "Error shown when a map size setting is not a valid dimension. $1 is the location, e.g. \"general.mapWidth\".",
258+
"maps-config-error-invalid-number-map": "Error shown when the distance units setting is not a map of names to positive numbers. $1 is the location.",
259+
"maps-config-error-invalid-language": "Error shown when the Google Maps language setting is not a valid language code. $1 is the location.",
251260
"maps-config-error-invalid-layer-name": "Error shown when a custom Leaflet layer has an invalid name. $1 is the name.",
252261
"maps-config-error-unknown-layer-key": "Error shown when a custom Leaflet layer definition has an unrecognized property. $1 is the layer name, $2 is the property.",
253262
"maps-config-error-invalid-url": "Error shown when a custom Leaflet layer has no valid url. $1 is the layer name.",

src/CombiningLeafletConfigLookup.php

Lines changed: 0 additions & 115 deletions
This file was deleted.

src/Config/AvailabilityType.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace Maps\Config;
6+
7+
/**
8+
* A map of layer names to booleans, used for the available base layers and overlays. Merged per
9+
* name with the PHP settings rather than replaced, so the wiki only needs to list the layers it
10+
* changes.
11+
*/
12+
class AvailabilityType implements ConfigType {
13+
14+
public function validate( mixed $value, string $location ): array {
15+
return $this->isAvailabilityMap( $value ) ? [] : [ [ 'maps-config-error-invalid-availability', $location ] ];
16+
}
17+
18+
private function isAvailabilityMap( mixed $value ): bool {
19+
if ( !is_array( $value ) || ( $value !== [] && array_is_list( $value ) ) ) {
20+
return false;
21+
}
22+
23+
foreach ( $value as $enabled ) {
24+
if ( !is_bool( $enabled ) ) {
25+
return false;
26+
}
27+
}
28+
29+
return true;
30+
}
31+
32+
}

src/Config/BooleanType.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace Maps\Config;
6+
7+
class BooleanType implements ConfigType {
8+
9+
public function validate( mixed $value, string $location ): array {
10+
return is_bool( $value ) ? [] : [ [ 'maps-config-error-invalid-boolean', $location ] ];
11+
}
12+
13+
}

0 commit comments

Comments
 (0)