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
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,51 @@ jobs:
- name: Check out repository
uses: actions/checkout@v7

- name: Lint WordPress plugin PHP
run: find wordpress-plugins/calorieapp-identity-bridge -type f -name '*.php' -print0 | xargs -0 -n1 php -l

- name: Test legal footer compatibility
shell: bash
run: |
php <<'PHP'
<?php
define('ABSPATH', __DIR__);
require 'wordpress-plugins/calorieapp-identity-bridge/includes/class-calorieapp-identity-bridge-legal-footer-compatibility.php';

use CalorieApp\IdentityBridge\LegalFooterCompatibility;

$input = '<p>Chamber of Commerce KVK: 84216352</p>'
. '<p>Calorie Token • KvK 84216352</p>'
. '<p>ICTHendrikse &bull; KvK 73774693</p>'
. '<p>Operator: ICTHendrikse</p>'
. '<p>© 2023 Calorie Token</p>'
. '<p>&copy;&nbsp;2026&nbsp;CalorieToken</p>'
. '<p>Calorie aims to be the World&apos;s food token</p>'
. '<p>Unrelated content remains unchanged.</p>';
$output = LegalFooterCompatibility::replace_legacy_footer_html($input);
$operator = 'Operator: ICTHendrikse · KVK 73774693';
$copyright = '© 2026 ICTHendrikse (owned content only) · CalorieToken® trade mark: Pieter Hendrikse';

if (substr_count($output, $operator) !== 4) {
throw new RuntimeException('Known operator footer variants were not normalized.');
}
if (substr_count($output, $copyright) !== 2) {
throw new RuntimeException('Known copyright footer variants were not normalized.');
}
if (!str_contains($output, 'Calorie aims to be the world’s food token')) {
throw new RuntimeException('The legacy footer tagline was not normalized.');
}
if (!str_contains($output, 'Unrelated content remains unchanged.')) {
throw new RuntimeException('Unrelated rendered content changed.');
}

$current = '<footer><p>Calorie aims to be the world’s food token</p>'
. '<p>' . $operator . '</p><p>' . $copyright . '</p></footer>';
if (LegalFooterCompatibility::replace_legacy_footer_html($current) !== $current) {
throw new RuntimeException('The current footer is not idempotent.');
}
PHP

- name: Set up Python
uses: actions/setup-python@v7
with:
Expand Down
2 changes: 1 addition & 1 deletion wordpress-plugins/calorieapp-identity-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The browser endpoint is intentionally not REST. XUMM Login establishes a normal

Details are in SECURITY.md and CONFIGURATION.md.

Version 0.1.3 adds a narrowly scoped public-HTML compatibility layer that replaces obsolete legal footer labels left in cached Brizy output. It does not modify stored page, product, Tokenomics, payment, or authentication data. Version 0.1.2 adds a normal WordPress browser authorization handler for XUMM-authenticated sessions while retaining the REST exchange contract. Version 0.1.1 aligned state validation with the backend's signed v1 protocol
Version 0.1.4 completes the narrowly scoped public-HTML footer compatibility layer by normalizing the known legacy Brizy footer variants to the current operator, KVK, copyright, trademark, and tagline text. It does not modify stored page, product, Tokenomics, payment, or authentication data. Version 0.1.3 introduced the compatibility layer. Version 0.1.2 adds a normal WordPress browser authorization handler for XUMM-authenticated sessions while retaining the REST exchange contract. Version 0.1.1 aligned state validation with the backend's signed v1 protocol
(timestamp, nonce, and HMAC-SHA256 signature) and supports an exact allowlisted
external callback without requiring a separate WordPress redirect-host filter.

Expand Down
5 changes: 3 additions & 2 deletions wordpress-plugins/calorieapp-identity-bridge/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ Covered scenarios:
20. backend state validation uses timestamp/nonce/HMAC headers
21. backend state validation does not transmit the shared secret
22. canonical WordPress REST root is documented for `/index.php/wp-json/` deployments
23. obsolete cached legal footer labels are replaced in rendered HTML
24. unrelated rendered HTML remains unchanged
23. known obsolete cached legal footer variants are normalized in rendered HTML
24. the current footer remains unchanged when the filter runs repeatedly
25. unrelated rendered HTML remains unchanged

## Files

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: CalorieApp Identity Bridge
* Plugin URI: https://calorietoken.net
* Description: Companion bridge plugin that maps authenticated WordPress/XUMM sessions to short-lived CalorieApp authorization codes.
* Version: 0.1.3
* Version: 0.1.4
* Author: CalorieApp
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,22 @@
* been rebuilt.
*/
class LegalFooterCompatibility {
private const LEGACY_OPERATOR_PATTERN = '/Chamber of Commerce KVK:\\s*[0-9]{8}/';
private const CURRENT_OPERATOR = 'Operator: ICTHendrikse';
private const LEGACY_COPYRIGHT = '© 2023 Calorie Token';
private const FLEXIBLE_SPACE = '(?:\\s|&nbsp;|&#160;|\\x{00A0})*';
private const LEGACY_OPERATOR_PATTERNS = [
'/Chamber' . self::FLEXIBLE_SPACE . 'of' . self::FLEXIBLE_SPACE . 'Commerce' . self::FLEXIBLE_SPACE . 'KVK' . self::FLEXIBLE_SPACE . ':' . self::FLEXIBLE_SPACE . '[0-9]{8}/iu',
'/Calorie' . self::FLEXIBLE_SPACE . 'Token' . self::FLEXIBLE_SPACE . '(?:•|·|&bull;|&middot;)' . self::FLEXIBLE_SPACE . 'KvK' . self::FLEXIBLE_SPACE . ':?' . self::FLEXIBLE_SPACE . '[0-9]{8}/iu',
'/Operator:' . self::FLEXIBLE_SPACE . 'ICTHendrikse' . self::FLEXIBLE_SPACE . '(?:•|·|&bull;|&middot;)' . self::FLEXIBLE_SPACE . 'KvK' . self::FLEXIBLE_SPACE . ':?' . self::FLEXIBLE_SPACE . '73774693/iu',
'/(?<!Operator: )ICTHendrikse' . self::FLEXIBLE_SPACE . '(?:•|·|&bull;|&middot;)' . self::FLEXIBLE_SPACE . 'KvK' . self::FLEXIBLE_SPACE . ':?' . self::FLEXIBLE_SPACE . '73774693/iu',
'/Operator:' . self::FLEXIBLE_SPACE . 'ICTHendrikse(?!' . self::FLEXIBLE_SPACE . '(?:•|·|&bull;|&middot;)' . self::FLEXIBLE_SPACE . 'KVK)/iu',
];
private const CURRENT_OPERATOR = 'Operator: ICTHendrikse · KVK 73774693';
private const LEGACY_COPYRIGHT_PATTERNS = [
'/(?:©|&copy;)' . self::FLEXIBLE_SPACE . '2023' . self::FLEXIBLE_SPACE . 'Calorie' . self::FLEXIBLE_SPACE . 'Token/iu',
'/(?:©|&copy;)' . self::FLEXIBLE_SPACE . '2026' . self::FLEXIBLE_SPACE . 'CalorieToken(?!®)/iu',
];
private const CURRENT_COPYRIGHT = '© 2026 ICTHendrikse (owned content only) · CalorieToken® trade mark: Pieter Hendrikse';
private const LEGACY_TAGLINE_PATTERN = '/Calorie' . self::FLEXIBLE_SPACE . 'aims' . self::FLEXIBLE_SPACE . 'to' . self::FLEXIBLE_SPACE . 'be' . self::FLEXIBLE_SPACE . 'the' . self::FLEXIBLE_SPACE . 'World(?:\'|’|&apos;|&rsquo;|&#39;|&#8217;)' . self::FLEXIBLE_SPACE . 's' . self::FLEXIBLE_SPACE . 'food' . self::FLEXIBLE_SPACE . 'token/iu';
private const CURRENT_TAGLINE = 'Calorie aims to be the world’s food token';

public function register_hooks(): void {
add_action('template_redirect', [$this, 'start_output_buffer'], PHP_INT_MAX);
Expand All @@ -43,12 +55,18 @@ public function start_output_buffer(): void {
}

public static function replace_legacy_footer_html(string $html): string {
$html = preg_replace(
self::LEGACY_OPERATOR_PATTERN,
self::CURRENT_OPERATOR,
foreach (self::LEGACY_OPERATOR_PATTERNS as $pattern) {
$html = preg_replace($pattern, self::CURRENT_OPERATOR, $html) ?? $html;
}

foreach (self::LEGACY_COPYRIGHT_PATTERNS as $pattern) {
$html = preg_replace($pattern, self::CURRENT_COPYRIGHT, $html) ?? $html;
}

return preg_replace(
self::LEGACY_TAGLINE_PATTERN,
self::CURRENT_TAGLINE,
$html
) ?? $html;

return str_replace(self::LEGACY_COPYRIGHT, self::CURRENT_COPYRIGHT, $html);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,41 @@
use CalorieApp\IdentityBridge\LegalFooterCompatibility;

class Test_CalorieApp_Legal_Footer_Compatibility extends WP_UnitTestCase {
public function test_replaces_only_obsolete_legal_footer_labels(): void {
$html = '<p>Chamber of Commerce KVK: 12345678</p>'
public function test_replaces_known_obsolete_legal_footer_variants(): void {
$html = '<p>Chamber of Commerce KVK: 84216352</p>'
. '<p>Calorie Token • KvK 84216352</p>'
. '<p>ICTHendrikse &bull; KvK 73774693</p>'
. '<p>Operator: ICTHendrikse</p>'
. '<p>© 2023 Calorie Token</p>'
. '<p>&copy;&nbsp;2026&nbsp;CalorieToken</p>'
. '<p>Calorie aims to be the World&apos;s food token</p>'
. '<p>Tokenomics content remains unchanged.</p>';

$result = LegalFooterCompatibility::replace_legacy_footer_html($html);

$this->assertStringContainsString('Operator: ICTHendrikse', $result);
$this->assertSame(4, substr_count($result, 'Operator: ICTHendrikse · KVK 73774693'));
$this->assertSame(
2,
substr_count(
$result,
'© 2026 ICTHendrikse (owned content only) · CalorieToken® trade mark: Pieter Hendrikse'
)
);
$this->assertStringContainsString(
'© 2026 ICTHendrikse (owned content only) · CalorieToken® trade mark: Pieter Hendrikse',
'Calorie aims to be the world’s food token',
$result
);
$this->assertStringContainsString('Tokenomics content remains unchanged.', $result);
$this->assertStringNotContainsString('12345678', $result);
$this->assertStringNotContainsString('84216352', $result);
}

public function test_current_footer_is_idempotent(): void {
$html = '<footer><p>Calorie aims to be the world’s food token</p>'
. '<p>Operator: ICTHendrikse · KVK 73774693</p>'
. '<p>© 2026 ICTHendrikse (owned content only) · CalorieToken® trade mark: Pieter Hendrikse</p>'
. '</footer>';

$this->assertSame($html, LegalFooterCompatibility::replace_legacy_footer_html($html));
}

public function test_leaves_unrelated_html_unchanged(): void {
Expand Down