diff --git a/CLAUDE.md b/CLAUDE.md index d9aee75..c0ffc77 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,4 +125,19 @@ Update the changelog whenever you: Also update the version number in: - `mayo-events-manager.php` (line 23: `MAYO_VERSION` constant) - `readme.txt` (line 8: `Stable tag`) -- `package.json` (line 3: `version`) \ No newline at end of file +- `package.json` (line 3: `version`) + +## Encoding Guidelines for REST API Data + +This codebase has had multiple encoding bugs (#41, #84, #109, #222, #249). Follow these rules when returning data via REST API: + +### URLs +- **REST API/JSON context**: Use `esc_url_raw($url)` - preserves `&` characters +- **HTML output context**: Use `esc_url($url)` - encodes `&` to `&` + +### Text/Titles +- **REST API/JSON context**: Use `html_entity_decode($text, ENT_QUOTES, 'UTF-8')` +- **HTML output context**: Use `esc_html($text)` or `esc_attr($text)` + +### Why? +WordPress escaping functions encode special characters for HTML safety. When this data goes through REST API → JSON → React, the HTML entities cause display issues or broken functionality. \ No newline at end of file diff --git a/composer.json b/composer.json index 6e4c7ef..2c064e3 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,9 @@ "test": "phpunit", "test:unit": "phpunit --testsuite Unit", "lint": "phpcs *.php", - "lint:fix": "phpcbf *.php" + "lint:fix": "phpcbf *.php", + "lint:esc-url": "bash scripts/check-esc-url.sh", + "lint:all": ["@lint", "@lint:esc-url"] }, "config": { "allow-plugins": { diff --git a/includes/Announcement.php b/includes/Announcement.php index 5af223e..dd54e51 100644 --- a/includes/Announcement.php +++ b/includes/Announcement.php @@ -844,7 +844,7 @@ public static function resolve_event_ref($ref) { return [ 'id' => $ref['id'] ?? 0, 'title' => sanitize_text_field($ref['title']), - 'permalink' => esc_url($ref['url']), + 'permalink' => esc_url_raw($ref['url']), 'icon' => isset($ref['icon']) ? sanitize_text_field($ref['icon']) : 'external', 'source' => [ 'type' => 'custom', diff --git a/mayo-events-manager.php b/mayo-events-manager.php index 1cbd96a..1f55baa 100644 --- a/mayo-events-manager.php +++ b/mayo-events-manager.php @@ -3,7 +3,7 @@ /** * Plugin Name: Mayo Events Manager * Description: A plugin for managing and displaying events. - * Version: 1.8.5 + * Version: 1.8.6 * Author: bmlt-enabled * License: GPLv2 or later * Author URI: https://bmlt.app @@ -20,7 +20,7 @@ exit; // Exit if accessed directly } -define('MAYO_VERSION', '1.8.5'); +define('MAYO_VERSION', '1.8.6'); require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/includes/Admin.php'; diff --git a/package.json b/package.json index af17a52..301c540 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mayo", - "version": "1.8.5", + "version": "1.8.6", "description": "", "main": "index.js", "scripts": { diff --git a/readme.txt b/readme.txt index 6760dd5..f1544f6 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Tags: events, bmlt, narcotics anonymous, na Requires PHP: 8.2 Requires at least: 6.7 Tested up to: 6.9 -Stable tag: 1.8.5 +Stable tag: 1.8.6 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -187,6 +187,9 @@ This project is licensed under the GPL v2 or later. == Changelog == += 1.8.6 = +* Fixed custom URLs in announcements having ampersands incorrectly HTML-encoded, causing links with query parameters to fail. [#249] + = 1.8.5 = * Fixed external feed events not displaying service body names correctly. [#234] * Fixed events and announcements remaining visible after their scheduled end time passes. Previously only the end date was checked, ignoring the end time. [#237] diff --git a/scripts/check-esc-url.sh b/scripts/check-esc-url.sh new file mode 100755 index 0000000..c93cdf0 --- /dev/null +++ b/scripts/check-esc-url.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Check for esc_url() usage in REST API contexts (should use esc_url_raw() instead) +# See issue #249 for context + +ERRORS=0 + +# Check in includes/Rest directory +if grep -rn "esc_url(" includes/Rest/ 2>/dev/null; then + echo "WARNING: Found esc_url() in includes/Rest/ - use esc_url_raw() for REST API contexts" + ERRORS=1 +fi + +# Check in Announcement.php (has REST-consumed methods) +if grep -n "esc_url(" includes/Announcement.php 2>/dev/null | grep -v "esc_url_raw"; then + echo "WARNING: Found esc_url() in includes/Announcement.php - verify it's not used for REST API data" + ERRORS=1 +fi + +if [ $ERRORS -eq 0 ]; then + echo "No esc_url() issues found in REST contexts" +fi + +exit $ERRORS diff --git a/tests/Unit/AnnouncementTest.php b/tests/Unit/AnnouncementTest.php index d71b412..1a6e218 100644 --- a/tests/Unit/AnnouncementTest.php +++ b/tests/Unit/AnnouncementTest.php @@ -429,6 +429,33 @@ public function testResolveEventRefHandlesCustomLinks(): void { $this->assertEquals('custom', $result['source']['type']); } + /** + * Test resolve_event_ref preserves URL query parameters (issue #249) + * + * Custom URLs with ampersands in query strings must not be HTML-encoded + * when returned via REST API. + */ + public function testResolveEventRefPreservesUrlQueryParameters(): void { + $ref = [ + 'type' => 'custom', + 'url' => 'https://example.com/link?id=123&key=GRP&app=resvlink', + 'title' => 'Book Room', + 'icon' => 'external' + ]; + + $result = Announcement::resolve_event_ref($ref); + + $this->assertNotNull($result); + // URL should preserve literal & characters, not HTML entities + $this->assertEquals( + 'https://example.com/link?id=123&key=GRP&app=resvlink', + $result['permalink'] + ); + // Explicitly verify no HTML encoding + $this->assertStringNotContainsString('&', $result['permalink']); + $this->assertStringNotContainsString('&', $result['permalink']); + } + /** * Test resolve_event_ref returns null for custom link without url */