From afc29e15d5c1b947dda4a9f4876522b124ef31be Mon Sep 17 00:00:00 2001 From: Jamie Shiell Date: Sun, 8 Feb 2026 16:39:03 +0000 Subject: [PATCH 1/3] Strip namespaces from namespaced XML plugin response, so we get usuable output --- .../Plugin/Parsers/XmlResponseParser.php | 23 ++++++++++++++- tests/Feature/PluginResponseTest.php | 29 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/app/Services/Plugin/Parsers/XmlResponseParser.php b/app/Services/Plugin/Parsers/XmlResponseParser.php index b82ba80..037b3c6 100644 --- a/app/Services/Plugin/Parsers/XmlResponseParser.php +++ b/app/Services/Plugin/Parsers/XmlResponseParser.php @@ -18,7 +18,7 @@ class XmlResponseParser implements ResponseParser } try { - $xml = simplexml_load_string($response->body()); + $xml = $this->simplexml_load_string_strip_namespaces($response->body()); if ($xml === false) { throw new Exception('Invalid XML content'); } @@ -43,4 +43,25 @@ class XmlResponseParser implements ResponseParser return $array; } + + function simplexml_load_string_strip_namespaces($xml_response) { + $xml = simplexml_load_string($xml_response); + if ($xml === false) { + return false; + } + + $namespaces = array_keys($xml->getDocNamespaces(true)); + $namespaces = array_filter($namespaces, function($name) { return !empty($name); }); + if (count($namespaces) == 0) { + return $xml; + } + $namespaces = array_map(function($ns) { return "$ns:"; }, $namespaces); + + $xml_no_namespaces = str_replace( + array_merge(["xmlns="], $namespaces), + array_merge(["ns="], array_fill(0, count($namespaces), '')), + $xml_response + ); + return simplexml_load_string($xml_no_namespaces); + } } diff --git a/tests/Feature/PluginResponseTest.php b/tests/Feature/PluginResponseTest.php index 2a75c9e..bcc06f8 100644 --- a/tests/Feature/PluginResponseTest.php +++ b/tests/Feature/PluginResponseTest.php @@ -36,7 +36,7 @@ test('plugin parses JSON responses correctly', function (): void { ]); }); -test('plugin parses XML responses and wraps under rss key', function (): void { +test('plugin parses RSS XML responses and wraps under rss key', function (): void { $xmlContent = ' @@ -73,6 +73,33 @@ test('plugin parses XML responses and wraps under rss key', function (): void { expect($plugin->data_payload['rss']['channel']['item'])->toHaveCount(2); }); +test('plugin parses namespaces XML responses and wraps under root key', function (): void { + $xmlContent = ' + + + Cherry + + '; + + Http::fake([ + 'example.com/namespace.xml' => Http::response($xmlContent, 200, ['Content-Type' => 'application/xml']), + ]); + + $plugin = Plugin::factory()->create([ + 'data_strategy' => 'polling', + 'polling_url' => 'https://example.com/namespace.xml', + 'polling_verb' => 'get', + ]); + + $plugin->updateDataPayload(); + + $plugin->refresh(); + + expect($plugin->data_payload)->toHaveKey('rss'); + expect($plugin->data_payload['rss'])->toHaveKey('icing'); + expect($plugin->data_payload['rss']['icing']['ontop'])->toBe('Cherry'); +}); + test('plugin parses JSON-parsable response body as JSON', function (): void { $jsonContent = '{"title": "Test Data", "items": [1, 2, 3]}'; From 9c5b5b33f56515cc67b7a7ea095cebf9d97dfefb Mon Sep 17 00:00:00 2001 From: Jamie Shiell Date: Sun, 8 Feb 2026 16:47:09 +0000 Subject: [PATCH 2/3] Use root element name for root of array rather than "rss" --- .../Plugin/Parsers/XmlResponseParser.php | 2 +- tests/Feature/PluginResponseTest.php | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Services/Plugin/Parsers/XmlResponseParser.php b/app/Services/Plugin/Parsers/XmlResponseParser.php index 037b3c6..6556de8 100644 --- a/app/Services/Plugin/Parsers/XmlResponseParser.php +++ b/app/Services/Plugin/Parsers/XmlResponseParser.php @@ -23,7 +23,7 @@ class XmlResponseParser implements ResponseParser throw new Exception('Invalid XML content'); } - return ['rss' => $this->xmlToArray($xml)]; + return [$xml->getName() => $this->xmlToArray($xml)]; } catch (Exception $exception) { Log::warning('Failed to parse XML response: '.$exception->getMessage()); diff --git a/tests/Feature/PluginResponseTest.php b/tests/Feature/PluginResponseTest.php index bcc06f8..3d82b6b 100644 --- a/tests/Feature/PluginResponseTest.php +++ b/tests/Feature/PluginResponseTest.php @@ -95,9 +95,9 @@ test('plugin parses namespaces XML responses and wraps under root key', function $plugin->refresh(); - expect($plugin->data_payload)->toHaveKey('rss'); - expect($plugin->data_payload['rss'])->toHaveKey('icing'); - expect($plugin->data_payload['rss']['icing']['ontop'])->toBe('Cherry'); + expect($plugin->data_payload)->toHaveKey('cake'); + expect($plugin->data_payload['cake'])->toHaveKey('icing'); + expect($plugin->data_payload['cake']['icing']['ontop'])->toBe('Cherry'); }); test('plugin parses JSON-parsable response body as JSON', function (): void { @@ -191,8 +191,8 @@ test('plugin handles multiple URLs with mixed content types', function (): void expect($plugin->data_payload['IDX_0'])->toBe($jsonResponse); // Second URL should be XML wrapped under rss - expect($plugin->data_payload['IDX_1'])->toHaveKey('rss'); - expect($plugin->data_payload['IDX_1']['rss']['item'])->toBe('XML Data'); + expect($plugin->data_payload['IDX_1'])->toHaveKey('root'); + expect($plugin->data_payload['IDX_1']['root']['item'])->toBe('XML Data'); }); test('plugin handles POST requests with XML responses', function (): void { @@ -213,11 +213,11 @@ test('plugin handles POST requests with XML responses', function (): void { $plugin->refresh(); - expect($plugin->data_payload)->toHaveKey('rss'); - expect($plugin->data_payload['rss'])->toHaveKey('status'); - expect($plugin->data_payload['rss'])->toHaveKey('data'); - expect($plugin->data_payload['rss']['status'])->toBe('success'); - expect($plugin->data_payload['rss']['data'])->toBe('test'); + expect($plugin->data_payload)->toHaveKey('response'); + expect($plugin->data_payload['response'])->toHaveKey('status'); + expect($plugin->data_payload['response'])->toHaveKey('data'); + expect($plugin->data_payload['response']['status'])->toBe('success'); + expect($plugin->data_payload['response']['data'])->toBe('test'); }); test('plugin parses iCal responses and filters to recent window', function (): void { From b26792359510f09e6aa2e4d38c382c7846a469d9 Mon Sep 17 00:00:00 2001 From: Benjamin Nussbaum Date: Mon, 9 Feb 2026 13:24:04 +0100 Subject: [PATCH 3/3] feat: bump to Design Framework v2.3.0 --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 96e0079..c6f59c8 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "ext-imagick": "*", "ext-simplexml": "*", "ext-zip": "*", - "bnussbau/laravel-trmnl-blade": "2.1.1", + "bnussbau/laravel-trmnl-blade": "2.3.*", "bnussbau/trmnl-pipeline-php": "^0.6.0", "keepsuit/laravel-liquid": "^0.5.2", "laravel/fortify": "^1.30", diff --git a/composer.lock b/composer.lock index a61b132..16fcc4e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "60a7e51edd8408cffdb901e4a1c1684a", + "content-hash": "a4438c591a5d746f546eab261e9e1efc", "packages": [ { "name": "aws/aws-crt-php", @@ -214,16 +214,16 @@ }, { "name": "bnussbau/laravel-trmnl-blade", - "version": "2.1.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/bnussbau/laravel-trmnl-blade.git", - "reference": "6ad96eba917ebc30ebe550e6fce4a995e94f6b35" + "reference": "e19efc6c873816395e0989d078998f02ef4f0adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bnussbau/laravel-trmnl-blade/zipball/6ad96eba917ebc30ebe550e6fce4a995e94f6b35", - "reference": "6ad96eba917ebc30ebe550e6fce4a995e94f6b35", + "url": "https://api.github.com/repos/bnussbau/laravel-trmnl-blade/zipball/e19efc6c873816395e0989d078998f02ef4f0adf", + "reference": "e19efc6c873816395e0989d078998f02ef4f0adf", "shasum": "" }, "require": { @@ -278,7 +278,7 @@ ], "support": { "issues": "https://github.com/bnussbau/laravel-trmnl-blade/issues", - "source": "https://github.com/bnussbau/laravel-trmnl-blade/tree/2.1.1" + "source": "https://github.com/bnussbau/laravel-trmnl-blade/tree/2.3.0" }, "funding": [ { @@ -294,7 +294,7 @@ "type": "github" } ], - "time": "2026-01-29T20:40:42+00:00" + "time": "2026-02-09T12:00:32+00:00" }, { "name": "bnussbau/trmnl-pipeline-php",