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
11 changes: 11 additions & 0 deletions event/listener.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,20 @@ public static function getSubscribedEvents()
'core.text_formatter_s9e_configure_after' => [['configure_iframe_embeds', -10]],
'core.text_formatter_s9e_renderer_setup' => 'configure_iframe_renderer',
'core.page_header_after' => 'inject_frontend',
'core.ucp_delete_cookies' => 'clear_browser_storage',
];
}

/**
* Mark Consent Manager browser storage for deletion after confirmation.
*
* @return void
*/
public function clear_browser_storage()
{
$this->template->assign_var('S_CONSENTMANAGER_CLEAR_STORAGE', true);
}

/**
* Transform s9e-rendered iframe output into consent-aware placeholders.
*
Expand Down
2 changes: 1 addition & 1 deletion language/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
'CONSENTMANAGER_DEFAULT_BANNER_TITLE' => 'We value your privacy',
'CONSENTMANAGER_DEFAULT_BANNER_TEXT' => 'This forum uses cookies to keep you signed in, secure your account, and ensure the site works properly. With your consent, we may also use optional cookies and similar technologies for analytics, marketing, and embedded media.',
'CONSENTMANAGER_DEFAULT_BANNER_SUBTEXT' => 'You can change your preferences at any time in the Privacy Settings.',
'CONSENTMANAGER_PRIVACY_POLICY_LINK' => 'Read our %s here.',
'CONSENTMANAGER_PRIVACY_POLICY_LINK' => 'See our <strong>%s</strong> for more information.',
'CONSENTMANAGER_CATEGORY_NECESSARY' => 'Necessary',
'CONSENTMANAGER_CATEGORY_NECESSARY_EXPLAIN' => 'Required for forum security, authentication, and essential site functionality.',
'CONSENTMANAGER_CATEGORY_ANALYTICS' => 'Analytics',
Expand Down
36 changes: 31 additions & 5 deletions service/consent_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@

class consent_manager implements consent_manager_interface
{
public const STORAGE_KEY = 'phpbb_consent_manager';
public const COOKIE_NAME = 'phpbb_consent_manager';
public const STORAGE_KEY = 'consent_manager';
public const COOKIE_NAME = 'consent_manager';

/** @var consent_cache */
protected $consent_cache;
Expand Down Expand Up @@ -320,6 +320,8 @@ public function build_frontend_payload($log_url, $log_hash)
return [
'storageKey' => $this->get_storage_key(),
'cookieName' => $this->get_cookie_name(),
'cookiePath' => $this->config['cookie_path'],
'cookieDomain' => $this->get_cookie_domain(),
'version' => $this->get_version(),
'requiredCategories' => $this->get_required_category_ids($categories),
'enabledCategories' => $this->get_enabled_category_ids($categories),
Expand Down Expand Up @@ -548,7 +550,7 @@ public function normalize_categories(array $categories)
*/
public function get_storage_key()
{
return self::STORAGE_KEY;
return $this->get_prefixed_storage_name(self::STORAGE_KEY);
}

/**
Expand All @@ -558,7 +560,7 @@ public function get_storage_key()
*/
public function get_cookie_name()
{
return self::COOKIE_NAME;
return $this->get_prefixed_storage_name(self::COOKIE_NAME);
}

/**
Expand Down Expand Up @@ -651,7 +653,7 @@ protected function get_server_consent_state()
$this->server_consent_state_loaded = true;
$this->server_consent_state = null;

$raw = $this->request->raw_variable(self::COOKIE_NAME, '', request_interface::COOKIE);
$raw = $this->request->raw_variable($this->get_cookie_name(), '', request_interface::COOKIE);
if (!is_string($raw) || $raw === '')
{
return null;
Expand All @@ -674,6 +676,30 @@ protected function get_server_consent_state()
return $this->server_consent_state;
}

/**
* Prefix browser storage names consistently with phpBB cookies.
*
* @param string $name Unprefixed storage name
*
* @return string
*/
protected function get_prefixed_storage_name($name)
{
return $this->config['cookie_name'] . '_' . $name;
}

/**
* Return the cookie domain phpBB will use when deleting board cookies.
*
* @return string
*/
protected function get_cookie_domain()
{
$domain = $this->config['cookie_domain'];

return (!$domain || $domain === '127.0.0.1' || strpos($domain, '.') === false) ? '' : $domain;
}

/**
* Allow extensions to register consent-aware integrations.
*
Expand Down
11 changes: 11 additions & 0 deletions styles/all/template/event/overall_header_head_append.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
const googleConsentTypes = googleConsentMode.types || {};

window.phpbbConsentManagerPayload = payload;
window.phpbbConsentManagerDeleteCookiesUrl = '{{ U_DELETE_COOKIES|e('js') }}';
window.phpbbConsentManagerLang = {
mediaPlaceholderLabel: '{{ lang('CONSENTMANAGER_MEDIA_PLACEHOLDER')|e('js') }}'
};
Expand Down Expand Up @@ -44,6 +45,16 @@
return '';
}

{% if S_CONSENTMANAGER_CLEAR_STORAGE %}
try
{
window.localStorage.removeItem(payload.storageKey);
}
catch (error)
{
}
{% endif %}

function loadState()
{
let raw = '';
Expand Down
41 changes: 40 additions & 1 deletion styles/all/template/js/consentmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@

function setCookie(name, value, maxAge)
{
let cookie = name + '=' + encodeURIComponent(value) + '; path=/; SameSite=Lax';
let cookie = name + '=' + encodeURIComponent(value) + '; path=' + payload.cookiePath + '; SameSite=Lax';

if (payload.cookieDomain)
{
cookie += '; domain=' + payload.cookieDomain;
}

if (window.location.protocol === 'https:')
{
Expand Down Expand Up @@ -275,6 +280,39 @@
return state;
}

function sameRequestUrl(left, right)
{
if (!left || !right)
{
return false;
}

const leftLink = document.createElement('a');
const rightLink = document.createElement('a');
leftLink.href = left;
rightLink.href = right;

return leftLink.pathname === rightLink.pathname;
}

function bindDeleteCookiesCleanup()
{
if (!window.jQuery || !window.phpbbConsentManagerDeleteCookiesUrl)
{
return;
}

window.jQuery(document).ajaxSuccess(function(event, request, settings, response) {
if (sameRequestUrl(settings.url, window.phpbbConsentManagerDeleteCookiesUrl)
&& response
&& typeof response.S_CONFIRM_ACTION === 'undefined'
&& response.REFRESH_DATA)
{
removeStoredState();
}
});
}

function unique(items)
{
const deduplicated = [];
Expand Down Expand Up @@ -1190,6 +1228,7 @@
};

window.consentManager = api;
bindDeleteCookiesCleanup();

applyGoogleConsentMode();

Expand Down
11 changes: 11 additions & 0 deletions tests/event/listener_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,20 @@ public function test_get_subscribed_events()
'core.text_formatter_s9e_configure_after' => [['configure_iframe_embeds', -10]],
'core.text_formatter_s9e_renderer_setup' => 'configure_iframe_renderer',
'core.page_header_after' => 'inject_frontend',
'core.ucp_delete_cookies' => 'clear_browser_storage',
], \phpbb\consentmanager\event\listener::getSubscribedEvents());
}

public function test_clear_browser_storage_assigns_template_flag()
{
$template = $this->createMock('\phpbb\template\template');
$template->expects(self::once())
->method('assign_var')
->with('S_CONSENTMANAGER_CLEAR_STORAGE', true);

$this->create_listener(null, null, $template)->clear_browser_storage();
}

public function test_configure_iframe_embeds_delegates_to_media_manager()
{
$configurator = new \s9e\TextFormatter\Configurator();
Expand Down
3 changes: 2 additions & 1 deletion tests/functional/frontend_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public function test_frontend_markup_is_injected_on_board_pages()
$this->assertContainsLang('CONSENTMANAGER_SETTINGS_TITLE', $crawler->filter('#consent-manager-link')->text());
$this->assertSame(2, $crawler->filter('.consent-manager-policy-link')->count());
$this->assertSame(1, $payload['version']);
$this->assertSame('phpbb_consent_manager', $payload['storageKey']);
$this->assertStringEndsWith('_consent_manager', $payload['storageKey']);
$this->assertSame($payload['storageKey'], $payload['cookieName']);
$this->assertSame($this->lang('CONSENTMANAGER_MEDIA_PLACEHOLDER'), $this->extract_media_placeholder_label($content));
$this->assertSame(array('necessary'), $payload['requiredCategories']);
$this->assertContains('analytics', $payload['optionalCategories']);
Expand Down
35 changes: 34 additions & 1 deletion tests/javascript/consentmanager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ function createPayload(overrides) {
version: '2026-04-28',
storageKey: 'phpbb-consent-state',
cookieName: 'phpbb_consent_state',
cookiePath: '/',
cookieDomain: '',
logEndpoint: '/app.php/consent/log',
logHash: 'test-hash',
categories: [
Expand Down Expand Up @@ -114,6 +116,7 @@ function setupConsentManager(options) {
const { window } = dom;
const requests = [];
const gtagCalls = settings.gtagCalls || [];
let ajaxSuccessCallback = null;

Object.defineProperty(window.document, 'readyState', {
configurable: true,
Expand Down Expand Up @@ -175,6 +178,17 @@ function setupConsentManager(options) {
window.document.cookie = payload.cookieName + '=' + encodeURIComponent(JSON.stringify(settings.cookieState));
}

if (settings.withJquery) {
window.jQuery = function() {
return {
ajaxSuccess: function(callback) {
ajaxSuccessCallback = callback;
}
};
};
window.phpbbConsentManagerDeleteCookiesUrl = '/delete_cookies';
}

if (!settings.withoutPayload) {
window.phpbbConsentManagerPayload = payload;
}
Expand All @@ -192,7 +206,10 @@ function setupConsentManager(options) {
payload,
requests,
gtagCalls,
jsdomErrors
jsdomErrors,
triggerAjaxSuccess: function(url, response) {
ajaxSuccessCallback({}, {}, { url }, response);
}
};
}

Expand All @@ -209,6 +226,22 @@ test('exits cleanly when payload is missing', () => {
expect(jsdomErrors).toEqual([]);
});

test('successful AJAX cookie deletion clears consent browser storage', () => {
const storedState = createState([ 'necessary', 'analytics' ], '2026-04-28T00:00:00.000Z');
const { window, document, payload, triggerAjaxSuccess } = setupConsentManager({
localState: storedState,
withJquery: true
});

triggerAjaxSuccess('/delete_cookies?sid=changed-by-confirmation', {
REFRESH_DATA: { url: '/', time: 3 }
});

expect(window.localStorage.getItem(payload.storageKey)).toBeNull();
expect(getCookieValue(document, payload.cookieName)).toBe('');
expect(window.consentManager.getState()).toEqual(storedState);
});

test('prefers the newest stored state and synchronizes cookie and local storage', () => {
const localState = createState([ 'necessary', 'marketing' ], '2026-04-27T00:00:00.000Z');
const cookieState = createState([ 'necessary', 'analytics' ], '2026-04-28T00:00:00.000Z');
Expand Down
22 changes: 18 additions & 4 deletions tests/service/consent_manager_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ protected function setUp(): void
public function test_public_metadata_methods()
{
$manager = $this->get_manager(array(
'cookie_name' => 'phpbb3_test',
'cookie_path' => '/forum',
'cookie_domain' => '.example.com',
'consentmanager_analytics_enabled' => 1,
'consentmanager_marketing_enabled' => 0,
'consentmanager_media_enabled' => 1,
'consentmanager_consent_version' => 7,
));

self::assertSame('phpbb_consent_manager', $manager->get_storage_key());
self::assertSame('phpbb_consent_manager', $manager->get_cookie_name());
self::assertSame('phpbb3_test_consent_manager', $manager->get_storage_key());
self::assertSame('phpbb3_test_consent_manager', $manager->get_cookie_name());
self::assertSame(7, $manager->get_version());
self::assertTrue($manager->is_supported_category('analytics'));
self::assertTrue($manager->is_supported_category('media'));
Expand Down Expand Up @@ -605,12 +608,19 @@ public function test_build_frontend_payload_collects_registered_and_configured_i
});

$manager = $this->get_manager(array(
'cookie_name' => 'phpbb3_payload',
'cookie_path' => '/board',
'cookie_domain' => '.example.com',
'consentmanager_marketing_enabled' => 0,
'consentmanager_consent_version' => 7,
), $this->get_submitted_integrations_json(), $dispatcher);

$payload = $manager->build_frontend_payload('/app.php/consent/log', 'deadbeef');

self::assertSame('phpbb3_payload_consent_manager', $payload['storageKey']);
self::assertSame('phpbb3_payload_consent_manager', $payload['cookieName']);
self::assertSame('/board', $payload['cookiePath']);
self::assertSame('.example.com', $payload['cookieDomain']);
self::assertSame(array('necessary'), $payload['requiredCategories']);
self::assertSame(array('necessary', 'analytics', 'media'), $payload['enabledCategories']);
self::assertSame(array('analytics', 'media'), $payload['optionalCategories']);
Expand Down Expand Up @@ -967,7 +977,7 @@ public function test_has_server_consent_returns_true_for_required_category_and_f
public function test_has_server_consent_reuses_cached_cookie_state()
{
$raw_variable_args = [
\phpbb\consentmanager\service\consent_manager::COOKIE_NAME,
'phpbb3_test_consent_manager',
'',
\phpbb\request\request_interface::COOKIE,
];
Expand All @@ -981,6 +991,7 @@ public function test_has_server_consent_reuses_cached_cookie_state()
]));

$manager = $this->get_manager([
'cookie_name' => 'phpbb3_test',
'consentmanager_consent_version' => 3,
], '', null, null, null, $request);

Expand Down Expand Up @@ -1131,6 +1142,9 @@ protected function get_manager_constructor_args(array $config_values = array(),
}

$config = new \phpbb\config\config(array_merge(array(
'cookie_name' => 'phpbb3_test',
'cookie_path' => '/',
'cookie_domain' => '',
'consentmanager_analytics_enabled' => 1,
'consentmanager_marketing_enabled' => 1,
'consentmanager_media_enabled' => 1,
Expand Down Expand Up @@ -1203,7 +1217,7 @@ protected function get_cookie_request($cookie_value)
$request = $this->createMock('\phpbb\request\request_interface');
$request->method('raw_variable')
->willReturnCallback(function ($name, $default, $super_global = null) use ($cookie_value) {
if ($name === \phpbb\consentmanager\service\consent_manager::COOKIE_NAME && $super_global === \phpbb\request\request_interface::COOKIE)
if ($name === 'phpbb3_test_consent_manager' && $super_global === \phpbb\request\request_interface::COOKIE)
{
return $cookie_value;
}
Expand Down