Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## 0.3.0 (May 6, 2026)

### Added
- **Geolocation Radius** setting on both the content plugin and site module — dedicated field for geolocation search radius. Positive integer = fixed radius in miles (or km per server settings). Negative integer = BMLT auto-radius (e.g. `-50` finds ~50 nearby meetings).
- `geolocation_radius` shortcode attribute on the content plugin to override the radius per article (`{crumb geolocation_radius="-50"}`).

## 0.2.0 (May 5, 2026)

### Added
Expand Down
3 changes: 3 additions & 0 deletions packages/module/language/en-GB/mod_crumb.ini
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ MOD_CRUMB_FIELD_CSS_TEMPLATE_NONE="— None —"
MOD_CRUMB_FIELD_CSS_TEMPLATE_FW="Full Width"
MOD_CRUMB_FIELD_CSS_TEMPLATE_FW_FORCE="Full Width (Force Viewport)"

MOD_CRUMB_FIELD_GEOLOCATION_RADIUS_LABEL="Geolocation Radius"
MOD_CRUMB_FIELD_GEOLOCATION_RADIUS_DESC="Optional. Geolocation search radius. Positive integer = fixed radius in miles (or km per server settings). Negative integer = BMLT auto-radius (e.g. -50 finds ~50 nearby meetings). Leave empty to use the widget default (-50). Ignored if geolocationRadius is already set in Widget Configuration JSON."

MOD_CRUMB_FIELD_BASE_PATH_LABEL="Base Path for Pretty URLs"
MOD_CRUMB_FIELD_BASE_PATH_DESC="Optional. The path where the widget lives (e.g. <code>meetings</code>). Enables clean URLs without the # fragment."

Expand Down
11 changes: 10 additions & 1 deletion packages/module/mod_crumb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<license>GPL v2 or later</license>
<authorEmail>bmlt-enabled@googlegroups.com</authorEmail>
<authorUrl>https://github.com/bmlt-enabled/crumb-joomla</authorUrl>
<version>0.2.0</version>
<version>0.3.0</version>
<description>MOD_CRUMB_XML_DESCRIPTION</description>
<namespace path="src">BmltEnabled\Module\Crumb\Site</namespace>

Expand Down Expand Up @@ -74,6 +74,15 @@
<option value="full_width">MOD_CRUMB_FIELD_CSS_TEMPLATE_FW</option>
<option value="full_width_force">MOD_CRUMB_FIELD_CSS_TEMPLATE_FW_FORCE</option>
</field>
<field
name="geolocation_radius"
type="text"
label="MOD_CRUMB_FIELD_GEOLOCATION_RADIUS_LABEL"
description="MOD_CRUMB_FIELD_GEOLOCATION_RADIUS_DESC"
hint="-50"
filter="string"
default=""
/>
<field
name="base_path"
type="text"
Expand Down
38 changes: 27 additions & 11 deletions packages/module/src/Helper/CrumbHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ class CrumbHelper
public function getSettings(Registry $params): array
{
return [
'server' => trim((string) $params->get('server', '')),
'service_body' => (string) $params->get('service_body', ''),
'format_ids' => (string) $params->get('format_ids', ''),
'view' => (string) $params->get('view', ''),
'css_template' => (string) $params->get('css_template', ''),
'base_path' => (string) $params->get('base_path', ''),
'widget_config' => (string) $params->get('widget_config', ''),
'server' => trim((string) $params->get('server', '')),
'service_body' => (string) $params->get('service_body', ''),
'format_ids' => (string) $params->get('format_ids', ''),
'view' => (string) $params->get('view', ''),
'css_template' => (string) $params->get('css_template', ''),
'base_path' => (string) $params->get('base_path', ''),
'geolocation_radius' => (string) $params->get('geolocation_radius', ''),
'widget_config' => (string) $params->get('widget_config', ''),
];
}

Expand Down Expand Up @@ -84,16 +85,31 @@ public function render(array $settings): string

$widget = '<div' . $attrString . '></div>';

$configScript = '';
// Build CrumbWidgetConfig array from widget_config JSON plus settings.
$configArray = [];
if ($widgetConf !== '') {
$decoded = json_decode($widgetConf, true);
if (\is_array($decoded)) {
$configScript = '<script>window.CrumbWidgetConfig = '
. json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP)
. ';</script>';
$configArray = $decoded;
}
}

// Merge geolocation_radius admin setting if not already set in widget_config JSON.
$geoRadiusSetting = (string) ($settings['geolocation_radius'] ?? '');
if ($geoRadiusSetting !== '' && !isset($configArray['geolocationRadius'])) {
$radius = (int) $geoRadiusSetting;
if ($radius !== 0) {
$configArray['geolocationRadius'] = $radius;
}
}

$configScript = '';
if (!empty($configArray)) {
$configScript = '<script>window.CrumbWidgetConfig = '
. json_encode($configArray, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP)
. ';</script>';
}

$loader = '<script src="https://cdn.aws.bmlt.app/crumb-widget.js" defer></script>';
$output = $configScript . $widget . $loader;

Expand Down
11 changes: 10 additions & 1 deletion packages/plugin/crumb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<license>GPL v2 or later</license>
<authorEmail>bmlt-enabled@googlegroups.com</authorEmail>
<authorUrl>https://github.com/bmlt-enabled/crumb-joomla</authorUrl>
<version>0.2.0</version>
<version>0.3.0</version>
<description>PLG_CONTENT_CRUMB_XML_DESCRIPTION</description>
<namespace path="src">BmltEnabled\Plugin\Content\Crumb</namespace>

Expand Down Expand Up @@ -73,6 +73,15 @@
<option value="full_width">PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_FW</option>
<option value="full_width_force">PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_FW_FORCE</option>
</field>
<field
name="geolocation_radius"
type="text"
label="PLG_CONTENT_CRUMB_FIELD_GEOLOCATION_RADIUS_LABEL"
description="PLG_CONTENT_CRUMB_FIELD_GEOLOCATION_RADIUS_DESC"
hint="-50"
filter="string"
default=""
/>
<field
name="base_path"
type="text"
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin/language/en-GB/plg_content_crumb.ini
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_NONE="— None —"
PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_FW="Full Width"
PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_FW_FORCE="Full Width (Force Viewport)"

PLG_CONTENT_CRUMB_FIELD_GEOLOCATION_RADIUS_LABEL="Geolocation Radius"
PLG_CONTENT_CRUMB_FIELD_GEOLOCATION_RADIUS_DESC="Optional. Geolocation search radius. Positive integer = fixed radius in miles (or km per server settings). Negative integer = BMLT auto-radius (e.g. -50 finds ~50 nearby meetings). Leave empty to use the widget default (-50). Can be overridden per shortcode via geolocation_radius=&quot;-50&quot;. Ignored if geolocationRadius is already set in Widget Configuration JSON."

PLG_CONTENT_CRUMB_FIELD_BASE_PATH_LABEL="Base Path for Pretty URLs"
PLG_CONTENT_CRUMB_FIELD_BASE_PATH_DESC="Optional. The path where the widget lives (e.g. <code>meetings</code>). Enables clean URLs like <code>/meetings/monday-night-meeting-42</code> instead of hash-based routing."

Expand Down
15 changes: 8 additions & 7 deletions packages/plugin/src/Extension/Crumb.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ public function onContentPrepare(string $context, &$article = null, &$params = n
}

$settings = [
'server' => (string) $this->params->get('server', ''),
'service_body' => (string) $this->params->get('service_body', ''),
'format_ids' => (string) $this->params->get('format_ids', ''),
'view' => (string) $this->params->get('view', ''),
'css_template' => (string) $this->params->get('css_template', ''),
'base_path' => (string) $this->params->get('base_path', ''),
'widget_config' => (string) $this->params->get('widget_config', ''),
'server' => (string) $this->params->get('server', ''),
'service_body' => (string) $this->params->get('service_body', ''),
'format_ids' => (string) $this->params->get('format_ids', ''),
'view' => (string) $this->params->get('view', ''),
'css_template' => (string) $this->params->get('css_template', ''),
'base_path' => (string) $this->params->get('base_path', ''),
'geolocation_radius' => (string) $this->params->get('geolocation_radius', ''),
'widget_config' => (string) $this->params->get('widget_config', ''),
];

$article->text = preg_replace_callback(
Expand Down
33 changes: 28 additions & 5 deletions packages/plugin/src/Helper/CrumbRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,40 @@ public static function render(array $settings, array $overrides = []): string

$widget = '<div' . $attrString . '></div>';

// Inline config script: only emit if the JSON is valid.
$configScript = '';
// Build CrumbWidgetConfig array from widget_config JSON plus settings/overrides.
$configArray = [];
if ($widgetConf !== '') {
$decoded = json_decode($widgetConf, true);
if (\is_array($decoded)) {
$configScript = '<script>window.CrumbWidgetConfig = '
. json_encode($decoded, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP)
. ';</script>';
$configArray = $decoded;
}
}

// Merge geolocation_radius admin setting if not already set in widget_config JSON.
$geoRadiusSetting = (string) ($settings['geolocation_radius'] ?? '');
if ($geoRadiusSetting !== '' && !isset($configArray['geolocationRadius'])) {
$radius = (int) $geoRadiusSetting;
if ($radius !== 0) {
$configArray['geolocationRadius'] = $radius;
}
}

// Per-shortcode override takes precedence.
$geoRadiusOverride = isset($overrides['geolocation_radius']) ? (string) $overrides['geolocation_radius'] : null;
if ($geoRadiusOverride !== null && $geoRadiusOverride !== '') {
$radius = (int) $geoRadiusOverride;
if ($radius !== 0) {
$configArray['geolocationRadius'] = $radius;
}
}

$configScript = '';
if (!empty($configArray)) {
$configScript = '<script>window.CrumbWidgetConfig = '
. json_encode($configArray, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP)
. ';</script>';
}

// Loader script (CDN). Pinned to a major-minor channel so cache stays warm
// across patches; switch to a fully-pinned URL to lock the version.
$loader = '<script src="https://cdn.aws.bmlt.app/crumb-widget.js" defer></script>';
Expand Down
2 changes: 1 addition & 1 deletion pkg_crumb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<authorEmail>bmlt-enabled@googlegroups.com</authorEmail>
<authorUrl>https://github.com/bmlt-enabled/crumb-joomla</authorUrl>
<packagename>crumb</packagename>
<version>0.2.0</version>
<version>0.3.0</version>
<url>https://github.com/bmlt-enabled/crumb-joomla</url>
<packager>bmlt-enabled</packager>
<packagerurl>https://github.com/bmlt-enabled</packagerurl>
Expand Down
57 changes: 57 additions & 0 deletions tests/Unit/CrumbRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,61 @@ public function testHtmlSpecialCharsEscapedInAttributes(): void
]);
$this->assertStringNotContainsString('<script>alert(1)</script>', $output);
}

public function testGeolocationRadiusSettingMergesAsInteger(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'geolocation_radius' => '-50',
]);
$this->assertStringContainsString('window.CrumbWidgetConfig', $output);
$this->assertStringContainsString('"geolocationRadius":-50', $output);
}

public function testGeolocationRadiusSettingPreservesIntegerType(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'geolocation_radius' => '25',
]);
preg_match('/window\.CrumbWidgetConfig\s*=\s*(\{.*?\});/', $output, $m);
$config = json_decode($m[1], true);
$this->assertSame(25, $config['geolocationRadius'], 'geolocationRadius must be an integer, not a string.');
}

public function testGeolocationRadiusOverrideTakesPrecedenceOverSetting(): void
{
$output = CrumbRenderer::render(
['server' => 'https://x/main_server', 'geolocation_radius' => '-50'],
['geolocation_radius' => '30']
);
$this->assertStringContainsString('"geolocationRadius":30', $output);
$this->assertStringNotContainsString('"geolocationRadius":-50', $output);
}

public function testWidgetConfigGeolocationRadiusTakesPrecedenceOverSetting(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'geolocation_radius' => '-50',
'widget_config' => '{"geolocationRadius":10}',
]);
$this->assertStringContainsString('"geolocationRadius":10', $output);
$this->assertStringNotContainsString('"geolocationRadius":-50', $output);
}

public function testZeroGeolocationRadiusIsIgnored(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'geolocation_radius' => '0',
]);
$this->assertStringNotContainsString('CrumbWidgetConfig', $output);
}

public function testEmptyGeolocationRadiusProducesNoConfigScript(): void
{
$output = CrumbRenderer::render(['server' => 'https://x/main_server']);
$this->assertStringNotContainsString('CrumbWidgetConfig', $output);
}
}
Loading