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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## 0.4.0 (May 10, 2026)

### Added
- **Update Meeting URL** setting on both the content plugin and site module, plus matching `update_url` shortcode override (`{crumb update_url="…"}`). Powers the "Update Meeting Info" link on the meeting detail panel. Supports tokens `{meeting_id}`, `{meeting_name}`, `{server_url}`, and `{return_url}` (URL-encoded on substitution). Works with [bmlt-workflow](https://github.com/bmlt-enabled/bmlt-workflow), arbitrary hosted forms, or `mailto:` URLs. Leave empty to hide the link.

## 0.3.0 (May 6, 2026)

### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ By default, the plugin reads its settings (server URL, service body IDs, etc.) f
{crumb server="https://bmlt.example.org/main_server" service_body="42" view="map"}
```

Recognised shortcode args: `server`, `service_body`, `view` (`list`/`map`), `geolocation` (`true`/`false`).
Recognised shortcode args: `server`, `service_body`, `format_ids`, `view` (`list`/`map`), `geolocation` (`true`/`false`), `geolocation_radius`, `update_url`.

### In a module position (module)

Expand All @@ -56,6 +56,7 @@ Both the plugin and the module expose the same fields:
| **Default View** | `list` or `map`. Overridable per shortcode and via `?view=` query param. |
| **CSS Template** | `Full Width` or `Full Width (Force Viewport)` for breaking out of narrow content areas. |
| **Base Path for Pretty URLs** | e.g. `meetings` → `/meetings/monday-night-meeting-42` (History API routing). Empty = hash-based routing. |
| **Update Meeting URL** | Optional. URL template for the "Update Meeting Info" link on the meeting detail panel. Tokens: `{meeting_id}`, `{meeting_name}`, `{server_url}`, `{return_url}`. Works with bmlt-workflow, hosted forms, or `mailto:` URLs. |
| **Widget Configuration (JSON)** | Advanced. JSON for `CrumbWidgetConfig`. See the [widget docs](https://crumb.bmlt.app/) for the full schema (`darkMode`, `geolocation`, `nowOffset`, `columns`, etc.). |

## Local development
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 @@ -31,5 +31,8 @@ MOD_CRUMB_FIELD_GEOLOCATION_RADIUS_DESC="Optional. Geolocation search radius. Po
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."

MOD_CRUMB_FIELD_UPDATE_URL_LABEL="Update Meeting URL"
MOD_CRUMB_FIELD_UPDATE_URL_DESC="Optional. URL template for the &quot;Update Meeting Info&quot; link shown at the bottom of the meeting detail panel. Supported tokens (URL-encoded on substitution): <code>{meeting_id}</code>, <code>{meeting_name}</code>, <code>{server_url}</code>, <code>{return_url}</code>. Works with bmlt-workflow, any hosted form, or a <code>mailto:</code> URL. Leave empty to hide the link."

MOD_CRUMB_FIELD_WIDGET_CONFIG_LABEL="Widget Configuration (JSON)"
MOD_CRUMB_FIELD_WIDGET_CONFIG_DESC="Optional. CrumbWidgetConfig in JSON format. See https://crumb.bmlt.app/ for available options."
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.3.0</version>
<version>0.4.0</version>
<description>MOD_CRUMB_XML_DESCRIPTION</description>
<namespace path="src">BmltEnabled\Module\Crumb\Site</namespace>

Expand Down Expand Up @@ -92,6 +92,15 @@
filter="string"
default=""
/>
<field
name="update_url"
type="text"
label="MOD_CRUMB_FIELD_UPDATE_URL_LABEL"
description="MOD_CRUMB_FIELD_UPDATE_URL_DESC"
hint="https://example.org/meeting-update-form/?meeting_id={meeting_id}"
filter="raw"
default=""
/>
</fieldset>
<fieldset name="advanced">
<field
Expand Down
5 changes: 5 additions & 0 deletions packages/module/src/Helper/CrumbHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function getSettings(Registry $params): array
'css_template' => (string) $params->get('css_template', ''),
'base_path' => (string) $params->get('base_path', ''),
'geolocation_radius' => (string) $params->get('geolocation_radius', ''),
'update_url' => (string) $params->get('update_url', ''),
'widget_config' => (string) $params->get('widget_config', ''),
];
}
Expand All @@ -59,6 +60,7 @@ public function render(array $settings): string
$view = \in_array($viewRaw, self::ALLOWED_VIEWS, true) ? $viewRaw : '';
$basePath = trim((string) ($settings['base_path'] ?? ''), '/');
$template = (string) ($settings['css_template'] ?? '');
$updateUrl = trim((string) ($settings['update_url'] ?? ''));
$widgetConf = trim((string) ($settings['widget_config'] ?? ''));

$attrs = [
Expand All @@ -77,6 +79,9 @@ public function render(array $settings): string
if ($basePath !== '') {
$attrs['data-path'] = '/' . $basePath;
}
if ($updateUrl !== '') {
$attrs['data-update-url'] = $updateUrl;
}

$attrString = '';
foreach ($attrs as $k => $v) {
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.3.0</version>
<version>0.4.0</version>
<description>PLG_CONTENT_CRUMB_XML_DESCRIPTION</description>
<namespace path="src">BmltEnabled\Plugin\Content\Crumb</namespace>

Expand Down Expand Up @@ -91,6 +91,15 @@
filter="string"
default=""
/>
<field
name="update_url"
type="text"
label="PLG_CONTENT_CRUMB_FIELD_UPDATE_URL_LABEL"
description="PLG_CONTENT_CRUMB_FIELD_UPDATE_URL_DESC"
hint="https://example.org/meeting-update-form/?meeting_id={meeting_id}"
filter="raw"
default=""
/>
</fieldset>
<fieldset name="advanced">
<field
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 @@ -31,5 +31,8 @@ PLG_CONTENT_CRUMB_FIELD_GEOLOCATION_RADIUS_DESC="Optional. Geolocation search ra
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."

PLG_CONTENT_CRUMB_FIELD_UPDATE_URL_LABEL="Update Meeting URL"
PLG_CONTENT_CRUMB_FIELD_UPDATE_URL_DESC="Optional. URL template for the &quot;Update Meeting Info&quot; link shown at the bottom of the meeting detail panel. Supported tokens (URL-encoded on substitution): <code>{meeting_id}</code>, <code>{meeting_name}</code>, <code>{server_url}</code>, <code>{return_url}</code>. Works with bmlt-workflow, any hosted form, or a <code>mailto:</code> URL. Leave empty to hide the link. Can be overridden per shortcode via update_url=&quot;…&quot;."

PLG_CONTENT_CRUMB_FIELD_WIDGET_CONFIG_LABEL="Widget Configuration (JSON)"
PLG_CONTENT_CRUMB_FIELD_WIDGET_CONFIG_DESC="Optional. CrumbWidgetConfig in JSON format. See https://crumb.bmlt.app/ for available options."
1 change: 1 addition & 0 deletions packages/plugin/src/Extension/Crumb.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function onContentPrepare(string $context, &$article = null, &$params = n
'css_template' => (string) $this->params->get('css_template', ''),
'base_path' => (string) $this->params->get('base_path', ''),
'geolocation_radius' => (string) $this->params->get('geolocation_radius', ''),
'update_url' => (string) $this->params->get('update_url', ''),
'widget_config' => (string) $this->params->get('widget_config', ''),
];

Expand Down
4 changes: 4 additions & 0 deletions packages/plugin/src/Helper/CrumbRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static function render(array $settings, array $overrides = []): string
$view = \in_array($viewRaw, self::ALLOWED_VIEWS, true) ? $viewRaw : '';
$basePath = trim((string) ($settings['base_path'] ?? ''), '/');
$template = (string) ($settings['css_template'] ?? '');
$updateUrl = trim((string) ($overrides['update_url'] ?? $settings['update_url'] ?? ''));
$widgetConf = trim((string) ($settings['widget_config'] ?? ''));

$attrs = [
Expand All @@ -62,6 +63,9 @@ public static function render(array $settings, array $overrides = []): string
if ($basePath !== '') {
$attrs['data-path'] = '/' . $basePath;
}
if ($updateUrl !== '') {
$attrs['data-update-url'] = $updateUrl;
}
if (isset($overrides['geolocation']) && $overrides['geolocation'] !== null) {
$geo = filter_var($overrides['geolocation'], FILTER_VALIDATE_BOOLEAN);
$attrs['data-geolocation'] = $geo ? 'true' : 'false';
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.3.0</version>
<version>0.4.0</version>
<url>https://github.com/bmlt-enabled/crumb-joomla</url>
<packager>bmlt-enabled</packager>
<packagerurl>https://github.com/bmlt-enabled</packagerurl>
Expand Down
58 changes: 58 additions & 0 deletions tests/Unit/CrumbRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,62 @@ public function testEmptyGeolocationRadiusProducesNoConfigScript(): void
$output = CrumbRenderer::render(['server' => 'https://x/main_server']);
$this->assertStringNotContainsString('CrumbWidgetConfig', $output);
}

public function testUpdateUrlSettingEmitsDataAttribute(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'update_url' => 'https://example.org/form/?meeting_id={meeting_id}',
]);
$this->assertStringContainsString(
'data-update-url="https://example.org/form/?meeting_id={meeting_id}"',
$output
);
}

public function testUpdateUrlMailtoIsEmitted(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'update_url' => 'mailto:web@example.org?subject=Update%20{meeting_name}',
]);
$this->assertStringContainsString(
'data-update-url="mailto:web@example.org?subject=Update%20{meeting_name}"',
$output
);
}

public function testEmptyUpdateUrlOmitsAttribute(): void
{
$output = CrumbRenderer::render(['server' => 'https://x/main_server']);
$this->assertStringNotContainsString('data-update-url', $output);
}

public function testUpdateUrlOverrideBeatsSavedSetting(): void
{
$output = CrumbRenderer::render(
['server' => 'https://x/main_server', 'update_url' => 'https://saved/?meeting_id={meeting_id}'],
['update_url' => 'https://override/?meeting_id={meeting_id}']
);
$this->assertStringContainsString('data-update-url="https://override/?meeting_id={meeting_id}"', $output);
$this->assertStringNotContainsString('saved', $output);
}

public function testEmptyUpdateUrlOverrideOmitsAttribute(): void
{
$output = CrumbRenderer::render(
['server' => 'https://x/main_server', 'update_url' => 'https://saved/?meeting_id={meeting_id}'],
['update_url' => '']
);
$this->assertStringNotContainsString('data-update-url', $output);
}

public function testUpdateUrlIsHtmlEscaped(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'update_url' => '"><script>alert(1)</script>',
]);
$this->assertStringNotContainsString('<script>alert(1)</script>', $output);
}
}
Loading