diff --git a/adm/style/consentmanager_acp.html b/adm/style/consentmanager_acp.html index edc2f3d..a345bdd 100644 --- a/adm/style/consentmanager_acp.html +++ b/adm/style/consentmanager_acp.html @@ -41,21 +41,8 @@

{{ lang('WARNING') }}

{{ lang('ACP_CONSENTMANAGER_INTEGRATIONS') }}

{{ lang('ACP_CONSENTMANAGER_INTEGRATIONS_EXPLAIN') }}

- {{ lang('EXAMPLE') ~ lang('COLON') }} -
-					
-[
-  {
-    "id": "board.analytics",
-    "category": "analytics",
-    "label": "Board Analytics",
-    "description": "Loads a simple analytics library after consent.",
-    "src": "https://cdn.example.com/analytics.js",
-    "async": true
-  }
-]
-					
-				
+ {{ lang('EXAMPLE') ~ lang('COLON') }}
+
{{ CONSENTMANAGER_INTEGRATIONS_EXAMPLE }}
diff --git a/controller/acp_controller.php b/controller/acp_controller.php index 8db9104..d3895b8 100644 --- a/controller/acp_controller.php +++ b/controller/acp_controller.php @@ -123,12 +123,12 @@ public function handle() */ public function handle_logs() { - add_form_key('phpbb_consentmanager_export'); + add_form_key('phpbb_consentmanager_logs'); $form_data = $this->get_logs_form_data(); if ($this->request->is_set_post('download_csv')) { - $this->validate_form_key('phpbb_consentmanager_export'); + $this->validate_form_key('phpbb_consentmanager_logs'); $errors = []; $filters = $this->parse_export_filters($form_data, $errors); @@ -144,6 +144,8 @@ public function handle_logs() } else if ($this->request->is_set_post('delete_logs')) { + $this->validate_form_key('phpbb_consentmanager_logs'); + $errors = []; $filters = $this->parse_export_filters($form_data, $errors); @@ -168,7 +170,7 @@ public function handle_logs() build_hidden_fields(array_merge([ 'mode' => 'export', 'delete_logs' => 1, - ], $form_data)) + ], $form_data, $this->get_current_form_token_fields())) ); } } @@ -277,6 +279,19 @@ protected function get_logs_form_data() ]; } + protected function get_current_form_token_fields() + { + if (!$this->request->is_set_post('creation_time') || !$this->request->is_set_post('form_token')) + { + return []; + } + + return [ + 'creation_time' => $this->request->variable('creation_time', 0), + 'form_token' => $this->request->variable('form_token', ''), + ]; + } + protected function assign_template_vars(array $errors = []) { $this->template->assign_vars(array_merge( diff --git a/language/en/acp_consentmanager.php b/language/en/acp_consentmanager.php index 4314b91..dc415b9 100644 --- a/language/en/acp_consentmanager.php +++ b/language/en/acp_consentmanager.php @@ -28,7 +28,9 @@ 'ACP_CONSENTMANAGER_MEDIA' => 'Enable embedded media category', 'ACP_CONSENTMANAGER_MEDIA_EXPLAIN' => 'Allows videos, players, widgets, and other iframe-based external media to be loaded after consent.', 'ACP_CONSENTMANAGER_INTEGRATIONS' => 'ACP-managed integrations', - 'ACP_CONSENTMANAGER_INTEGRATIONS_EXPLAIN' => 'Use this to add simple third-party analytics, marketing, or scripts directly from the ACP instead of through an extension. These entries appear in the consent UI and are only loaded after consent.

Provide a JSON array of integrations. Each object must include: id, category, src. The id may only use letters, numbers, dots, underscores, colons, and hyphens. category must be necessary, analytics, or marketing. src must be a valid http, https, or relative script URL. Optional fields: label, description, async, defer.', + 'ACP_CONSENTMANAGER_INTEGRATIONS_EXPLAIN' => 'Use this to add simple third-party analytics, marketing, or scripts directly from the ACP instead of through an extension. These entries appear in the consent UI and are only loaded after consent.

Provide a JSON array of integrations.

Each object must include: id, category, src. The id may only use letters, numbers, dots, underscores, colons, and hyphens. The category must be necessary, analytics, or marketing. The src must be a valid http, https, or relative script URL.

Optional fields: label, description, async, defer.', + 'ACP_CONSENTMANAGER_INTEGRATIONS_EXAMPLE_LABEL' => 'Example Analytics', + 'ACP_CONSENTMANAGER_INTEGRATIONS_EXAMPLE_DESC' => 'Loads a simple analytics library after consent.', 'ACP_CONSENTMANAGER_VERSION' => 'Current consent version', 'ACP_CONSENTMANAGER_VERSION_EXPLAIN' => 'Increase the version to force a fresh prompt for every visitor when the consent text or integrations materially change.', 'ACP_CONSENTMANAGER_FORCE_REPROMPT' => 'Force re-prompt', diff --git a/service/acp_manager.php b/service/acp_manager.php index a39148d..12d67e2 100644 --- a/service/acp_manager.php +++ b/service/acp_manager.php @@ -99,8 +99,9 @@ public function get_settings_template_data() 'S_CONSENTMANAGER_ANALYTICS' => (bool) $this->config['consentmanager_analytics_enabled'], 'S_CONSENTMANAGER_MARKETING' => (bool) $this->config['consentmanager_marketing_enabled'], 'S_CONSENTMANAGER_MEDIA' => (bool) $this->config['consentmanager_media_enabled'], - 'CONSENTMANAGER_INTEGRATIONS' => $this->get_integrations_json(), 'CONSENTMANAGER_VERSION' => (int) $this->config['consentmanager_consent_version'], + 'CONSENTMANAGER_INTEGRATIONS' => $this->get_integrations_json(), + 'CONSENTMANAGER_INTEGRATIONS_EXAMPLE' => $this->get_integrations_example_json(), ]; } @@ -368,6 +369,27 @@ protected function get_integrations_json() return $pretty_json; } + /** + * Return example ACP integrations JSON formatted for template output. + * + * @return string + */ + protected function get_integrations_example_json() + { + $example = [[ + 'id' => 'example.analytics', + 'category' => consent_manager_interface::ANALYTICS_CATEGORY, + 'label' => $this->language->lang('ACP_CONSENTMANAGER_INTEGRATIONS_EXAMPLE_LABEL'), + 'description' => $this->language->lang('ACP_CONSENTMANAGER_INTEGRATIONS_EXAMPLE_DESC'), + 'src' => 'https://cdn.example.com/analytics.js', + 'async' => true, + ]]; + + $json = json_encode($example, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + + return $json === false ? '' : $json; + } + /** * Build a WHERE clause for consent log queries. * diff --git a/service/consent_manager.php b/service/consent_manager.php index 4ae3edc..8f2fbbb 100644 --- a/service/consent_manager.php +++ b/service/consent_manager.php @@ -188,9 +188,9 @@ public function get_frontend_template_data($log_url, $log_hash) $vars = [ 'S_CONSENTMANAGER_ENABLED' => $has_optional_categories, - 'S_CONSENTMANAGER_ANALYTICS_ENABLED' => !empty($categories['analytics']['enabled']), - 'S_CONSENTMANAGER_MARKETING_ENABLED' => !empty($categories['marketing']['enabled']), - 'S_CONSENTMANAGER_MEDIA_ENABLED' => !empty($categories['media']['enabled']), + 'S_CONSENTMANAGER_ANALYTICS_ENABLED' => !empty($categories[self::ANALYTICS_CATEGORY]['enabled']), + 'S_CONSENTMANAGER_MARKETING_ENABLED' => !empty($categories[self::MARKETING_CATEGORY]['enabled']), + 'S_CONSENTMANAGER_MEDIA_ENABLED' => !empty($categories[self::MEDIA_CATEGORY]['enabled']), 'CONSENTMANAGER_PAYLOAD' => $has_optional_categories ? json_encode($payload, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) : '', ]; @@ -332,10 +332,10 @@ public function build_frontend_payload($log_url, $log_hash) public function get_categories() { $lang_keys = [ - 'necessary' => ['CONSENTMANAGER_CATEGORY_NECESSARY', 'CONSENTMANAGER_CATEGORY_NECESSARY_EXPLAIN'], - 'analytics' => ['CONSENTMANAGER_CATEGORY_ANALYTICS', 'CONSENTMANAGER_CATEGORY_ANALYTICS_EXPLAIN'], - 'marketing' => ['CONSENTMANAGER_CATEGORY_MARKETING', 'CONSENTMANAGER_CATEGORY_MARKETING_EXPLAIN'], - 'media' => ['CONSENTMANAGER_CATEGORY_MEDIA', 'CONSENTMANAGER_CATEGORY_MEDIA_EXPLAIN'], + self::NECESSARY_CATEGORY => ['CONSENTMANAGER_CATEGORY_NECESSARY', 'CONSENTMANAGER_CATEGORY_NECESSARY_EXPLAIN'], + self::ANALYTICS_CATEGORY => ['CONSENTMANAGER_CATEGORY_ANALYTICS', 'CONSENTMANAGER_CATEGORY_ANALYTICS_EXPLAIN'], + self::MARKETING_CATEGORY => ['CONSENTMANAGER_CATEGORY_MARKETING', 'CONSENTMANAGER_CATEGORY_MARKETING_EXPLAIN'], + self::MEDIA_CATEGORY => ['CONSENTMANAGER_CATEGORY_MEDIA', 'CONSENTMANAGER_CATEGORY_MEDIA_EXPLAIN'], ]; $categories = []; @@ -519,12 +519,12 @@ public function normalize_integrations($input, array &$errors = []) */ public function normalize_categories(array $categories) { - $normalized = ['necessary']; + $normalized = [self::NECESSARY_CATEGORY]; foreach ($categories as $category) { $category = trim((string) $category); - if ($category !== 'necessary' && $this->is_category_enabled($category)) + if ($category !== self::NECESSARY_CATEGORY && $this->is_category_enabled($category)) { $normalized[] = $category; } @@ -625,7 +625,7 @@ public function has_server_consent($category) */ public function is_supported_category($category) { - return in_array($category, ['necessary', 'analytics', 'marketing', 'media'], true); + return in_array($category, [self::NECESSARY_CATEGORY, self::ANALYTICS_CATEGORY, self::MARKETING_CATEGORY, self::MEDIA_CATEGORY], true); } /** @@ -792,23 +792,23 @@ protected function get_frontend_payload_categories(array $categories) protected function get_category_config() { return $this->category_config ?? ($this->category_config = [ - 'necessary' => [ - 'id' => 'necessary', + self::NECESSARY_CATEGORY => [ + 'id' => self::NECESSARY_CATEGORY, 'required' => true, 'enabled' => true, ], - 'analytics' => [ - 'id' => 'analytics', + self::ANALYTICS_CATEGORY => [ + 'id' => self::ANALYTICS_CATEGORY, 'required' => false, 'enabled' => (bool) $this->config['consentmanager_analytics_enabled'], ], - 'marketing' => [ - 'id' => 'marketing', + self::MARKETING_CATEGORY => [ + 'id' => self::MARKETING_CATEGORY, 'required' => false, 'enabled' => (bool) $this->config['consentmanager_marketing_enabled'], ], - 'media' => [ - 'id' => 'media', + self::MEDIA_CATEGORY => [ + 'id' => self::MEDIA_CATEGORY, 'required' => false, 'enabled' => (bool) $this->config['consentmanager_media_enabled'], ], diff --git a/service/consent_manager_interface.php b/service/consent_manager_interface.php index da96d36..770b91a 100644 --- a/service/consent_manager_interface.php +++ b/service/consent_manager_interface.php @@ -12,6 +12,11 @@ interface consent_manager_interface { + public const NECESSARY_CATEGORY = 'necessary'; + public const ANALYTICS_CATEGORY = 'analytics'; + public const MARKETING_CATEGORY = 'marketing'; + public const MEDIA_CATEGORY = 'media'; + /** * Register a consent-aware service definition. * diff --git a/service/media_manager.php b/service/media_manager.php index 9a4b3df..7651f85 100644 --- a/service/media_manager.php +++ b/service/media_manager.php @@ -15,7 +15,6 @@ class media_manager { - public const MEDIA_CATEGORY = 'media'; public const MEDIA_ALLOWED_PARAMETER = 'S_CONSENTMANAGER_MEDIA_ALLOWED'; public const XSL_NAMESPACE = 'http://www.w3.org/1999/XSL/Transform'; @@ -44,7 +43,7 @@ public function __construct(consent_manager_interface $consent_manager) */ public function configure_iframe_embeds(Configurator $configurator) { - if (!$this->consent_manager->is_category_enabled(self::MEDIA_CATEGORY)) + if (!$this->consent_manager->is_category_enabled(consent_manager_interface::MEDIA_CATEGORY)) { return; } @@ -81,7 +80,7 @@ public function configure_iframe_renderer($renderer) { $renderer->get_renderer()->setParameter( self::MEDIA_ALLOWED_PARAMETER, - $this->consent_manager->has_server_consent(self::MEDIA_CATEGORY) ? '1' : '' + $this->consent_manager->has_server_consent(consent_manager_interface::MEDIA_CATEGORY) ? '1' : '' ); } @@ -242,7 +241,7 @@ protected function wrap_media_root(\DOMDocument $dom, \DOMElement $media_root) { $container = $dom->createElement('span'); $container->setAttribute('data-consent-media-container', '1'); - $container->setAttribute('data-consent-category', self::MEDIA_CATEGORY); + $container->setAttribute('data-consent-category', consent_manager_interface::MEDIA_CATEGORY); $placeholder = $dom->createElement('span'); $placeholder->setAttribute('data-consent-media-placeholder', '1'); diff --git a/tests/controller/acp_controller_test.php b/tests/controller/acp_controller_test.php index cdee330..1f7e43a 100644 --- a/tests/controller/acp_controller_test.php +++ b/tests/controller/acp_controller_test.php @@ -14,6 +14,9 @@ class acp_controller_test extends \phpbb_test_case { + protected const ACP_URL = 'adm.php?i=test'; + protected const EXPORT_URL = 'adm.php?i=test&mode=export'; + /** @var bool */ public static $valid_form = true; @@ -41,6 +44,7 @@ class acp_controller_test extends \phpbb_test_case protected function setUp(): void { parent::setUp(); + $_POST = []; global $phpbb_root_path, $phpEx; @@ -74,7 +78,7 @@ protected function setUp(): void self::$confirm_hidden_fields = null; } - protected function create_controller($request, $u_action = 'adm.php?i=test') + protected function create_controller($request, $u_action = self::ACP_URL) { global $phpbb_root_path, $phpEx; $controller = new \phpbb\consentmanager\controller\acp_controller( @@ -95,16 +99,18 @@ public function test_handle_assigns_existing_template_data() ->method('get_settings_template_data') ->willReturn([ 'S_CONSENTMANAGER_ANALYTICS' => true, + 'CONSENTMANAGER_INTEGRATIONS_EXAMPLE' => '[{}]', 'CONSENTMANAGER_VERSION' => 1, ]); $this->template->expects(self::once()) ->method('assign_vars') ->with([ 'S_CONSENTMANAGER_ANALYTICS' => true, + 'CONSENTMANAGER_INTEGRATIONS_EXAMPLE' => '[{}]', 'CONSENTMANAGER_VERSION' => 1, 'S_ERROR' => false, 'ERROR_MSG' => '', - 'U_ACTION' => 'adm.php?i=test', + 'U_ACTION' => self::ACP_URL, ]); $this->create_controller($this->create_request_mock())->handle(); @@ -134,7 +140,7 @@ public function test_handle_submit_validation_errors_reassigns_form_data() $args = [self::callback(static function ($vars) { return $vars['S_ERROR'] && $vars['ERROR_MSG'] === 'Invalid integrations' - && $vars['U_ACTION'] === 'adm.php?i=test' + && $vars['U_ACTION'] === self::ACP_URL && isset($vars['CONSENTMANAGER_VERSION']); })]; @@ -185,25 +191,31 @@ public function test_handle_reset_consent_logs_and_triggers_success_notice() $this->create_controller($this->create_request_mock(['reset_consent' => 1]))->handle(); } - public function test_handle_rejects_invalid_form_key() + /** + * @dataProvider handle_invalid_form_key_data + */ + public function test_handle_invalid_form_key($action, $manager_method, $log_action = null) { self::$valid_form = false; - $this->acp_manager->expects(self::never())->method('save_settings'); + $this->acp_manager->expects(self::never())->method($manager_method); + + if ($log_action !== null) + { + $this->acp_manager->expects(self::never())->method('log_admin_action')->with($log_action); + } + $this->setExpectedTriggerError(E_USER_WARNING, $this->language->lang('FORM_INVALID')); - $this->create_controller($this->create_request_mock(['submit' => 1]))->handle(); + $this->create_controller($this->create_request_mock([$action => 1]))->handle(); } - public function test_handle_reset_consent_rejects_invalid_form_key() + public function handle_invalid_form_key_data() { - self::$valid_form = false; - - $this->acp_manager->expects(self::never())->method('reset_consent_version'); - $this->acp_manager->expects(self::never())->method('log_admin_action')->with('LOG_CONSENTMANAGER_REPROMPT'); - $this->setExpectedTriggerError(E_USER_WARNING, $this->language->lang('FORM_INVALID')); - - $this->create_controller($this->create_request_mock(['reset_consent' => 1]))->handle(); + return [ + 'submit' => ['submit', 'save_settings'], + 'reset consent' => ['reset_consent', 'reset_consent_version', 'LOG_CONSENTMANAGER_REPROMPT'], + ]; } public function test_handle_logs_export_shows_empty_form() @@ -218,57 +230,67 @@ public function test_handle_logs_export_shows_empty_form() 'EXPORT_USERNAME' => '', 'EXPORT_CONSENT_VER' => 0, 'U_FIND_USERNAME' => 'u_find_username', - 'U_ACTION' => 'adm.php?i=test&mode=export', + 'U_ACTION' => self::EXPORT_URL, ]); - $this->create_controller($this->create_request_mock(), 'adm.php?i=test&mode=export')->handle_logs(); + $this->create_controller($this->create_request_mock(), self::EXPORT_URL)->handle_logs(); } - public function test_handle_logs_export_rejects_invalid_form_key() + /** + * @dataProvider handle_logs_invalid_form_key_data + */ + public function test_handle_logs_invalid_form_key_before_processing(array $request_values, $confirm_result) { self::$valid_form = false; + self::$confirm_result = $confirm_result; + $this->acp_manager->expects(self::never())->method('stream_logs_csv'); + $this->acp_manager->expects(self::never())->method('delete_logs'); + $this->acp_manager->expects(self::never())->method('log_admin_action'); + $this->acp_manager->expects(self::never())->method('get_user_id_by_username'); + $this->acp_manager->expects(self::never())->method('parse_date_filter'); + $this->template->expects(self::never())->method('assign_vars'); $this->setExpectedTriggerError(E_USER_WARNING, $this->language->lang('FORM_INVALID')); - $this->create_controller($this->create_request_mock(['download_csv' => 1]), 'adm.php?i=test&mode=export')->handle_logs(); + $this->create_controller($this->create_request_mock($request_values), self::EXPORT_URL)->handle_logs(); } - public function test_handle_logs_delete_requests_confirmation_without_form_key_validation() + public function handle_logs_invalid_form_key_data() { - self::$valid_form = false; - self::$confirm_result = false; - - $this->acp_manager->expects(self::never())->method('delete_logs'); - $this->acp_manager->expects(self::never())->method('log_admin_action'); - $this->acp_manager->expects(self::once())->method('get_user_id_by_username')->with('Alice')->willReturn(42); - $this->acp_manager->method('parse_date_filter') - ->willReturnOnConsecutiveCalls(1704067200, 1735689599); - $this->template->expects(self::once()) - ->method('assign_vars') - ->with([ - 'S_ERROR' => false, - 'ERROR_MSG' => '', - 'EXPORT_DATE_FROM' => '2024-01-01', - 'EXPORT_DATE_TO' => '2024-12-31', - 'EXPORT_USERNAME' => 'Alice', - 'EXPORT_CONSENT_VER' => 2, - 'U_FIND_USERNAME' => 'u_find_username', - 'U_ACTION' => 'adm.php?i=test&mode=export', - ]); - - $request = $this->create_request_mock([ - 'delete_logs' => 1, - 'export_date_from' => '2024-01-01', - 'export_date_to' => '2024-12-31', - 'export_username' => 'Alice', - 'export_consent_version' => 2, - ]); - $this->create_controller($request, 'adm.php?i=test&mode=export')->handle_logs(); - - self::assertSame($this->language->lang('ACP_CONSENTMANAGER_DELETE_CONFIRM'), self::$confirm_title); + return [ + 'download csv' => [ + ['download_csv' => 1], + false, + ], + 'delete logs before confirmation' => [ + [ + 'delete_logs' => 1, + 'export_date_from' => '2024-01-01', + 'export_date_to' => '2024-12-31', + 'export_username' => 'Alice', + 'export_consent_version' => 2, + ], + false, + ], + 'delete logs after confirmation' => [ + [ + 'delete_logs' => 1, + 'export_date_from' => '2024-01-01', + 'export_date_to' => '2024-12-31', + 'export_username' => 'Alice', + 'export_consent_version' => 2, + 'creation_time' => 1234567890, + 'form_token' => 'form-token-value', + ], + true, + ], + ]; } - public function test_handle_logs_delete_requests_confirmation_with_current_filters() + /** + * @dataProvider handle_logs_delete_confirmation_data + */ + public function test_handle_logs_delete_requests_confirmation_with_current_filters(array $request_overrides, array $expected_token_fields) { self::$valid_form = true; self::$confirm_result = false; @@ -288,41 +310,65 @@ public function test_handle_logs_delete_requests_confirmation_with_current_filte 'EXPORT_USERNAME' => 'Alice', 'EXPORT_CONSENT_VER' => 2, 'U_FIND_USERNAME' => 'u_find_username', - 'U_ACTION' => 'adm.php?i=test&mode=export', + 'U_ACTION' => self::EXPORT_URL, ]); - $request = $this->create_request_mock([ + $request = $this->create_request_mock(array_merge([ 'delete_logs' => 1, 'export_date_from' => '2024-01-01', 'export_date_to' => '2024-12-31', 'export_username' => 'Alice', 'export_consent_version' => 2, - ]); - $this->create_controller($request, 'adm.php?i=test&mode=export')->handle_logs(); + ], $request_overrides)); + $this->create_controller($request, self::EXPORT_URL)->handle_logs(); self::assertSame($this->language->lang('ACP_CONSENTMANAGER_DELETE_CONFIRM'), self::$confirm_title); - self::assertSame([ + self::assertSame(array_merge([ 'mode' => 'export', 'delete_logs' => 1, 'export_date_from' => '2024-01-01', 'export_date_to' => '2024-12-31', 'export_username' => 'Alice', 'export_consent_version' => 2, - ], self::$confirm_hidden_fields); + ], $expected_token_fields), self::$confirm_hidden_fields); } - public function test_handle_logs_delete_cancel_returns_to_form_and_next_request_confirms_again() + public function handle_logs_delete_confirmation_data() { - self::$valid_form = false; + return [ + 'with current csrf tokens' => [ + [ + 'creation_time' => 1234567890, + 'form_token' => 'form-token-value', + ], + [ + 'creation_time' => 1234567890, + 'form_token' => 'form-token-value', + ], + ], + 'without current csrf tokens' => [ + [], + [], + ], + ]; + } + + public function test_handle_logs_delete_cancel_returns_to_form_without_invalid_form_error() + { + self::$valid_form = true; self::$confirm_result = false; + $_POST = [ + 'cancel' => 1, + 'confirm_key' => 'existing-confirm-key', + ]; $this->acp_manager->expects(self::never())->method('delete_logs'); $this->acp_manager->expects(self::never())->method('log_admin_action'); - $this->acp_manager->expects(self::exactly(2))->method('get_user_id_by_username')->with('Alice')->willReturn(42); - $this->acp_manager->expects(self::exactly(4)) + $this->acp_manager->expects(self::once())->method('get_user_id_by_username')->with('Alice')->willReturn(42); + $this->acp_manager->expects(self::exactly(2)) ->method('parse_date_filter') - ->willReturnOnConsecutiveCalls(1704067200, 1735689599, 1704067200, 1735689599); - $this->template->expects(self::exactly(2)) + ->willReturnOnConsecutiveCalls(1704067200, 1735689599); + $this->template->expects(self::once()) ->method('assign_vars') ->with([ 'S_ERROR' => false, @@ -332,33 +378,23 @@ public function test_handle_logs_delete_cancel_returns_to_form_and_next_request_ 'EXPORT_USERNAME' => 'Alice', 'EXPORT_CONSENT_VER' => 2, 'U_FIND_USERNAME' => 'u_find_username', - 'U_ACTION' => 'adm.php?i=test&mode=export', + 'U_ACTION' => self::EXPORT_URL, ]); - $cancel_request = $this->create_request_mock([ + $reopened_request = $this->create_request_mock([ 'delete_logs' => 1, 'confirm_key' => 'existing-confirm-key', 'export_date_from' => '2024-01-01', 'export_date_to' => '2024-12-31', 'export_username' => 'Alice', 'export_consent_version' => 2, + 'creation_time' => 1234567890, + 'form_token' => 'form-token-value', ]); - $this->create_controller($cancel_request, 'adm.php?i=test&mode=export')->handle_logs(); - - self::$confirm_title = ''; - self::$confirm_hidden_fields = null; + $this->create_controller($reopened_request, self::EXPORT_URL)->handle_logs(); - $fresh_request = $this->create_request_mock([ - 'delete_logs' => 1, - 'export_date_from' => '2024-01-01', - 'export_date_to' => '2024-12-31', - 'export_username' => 'Alice', - 'export_consent_version' => 2, - ]); - - $this->create_controller($fresh_request, 'adm.php?i=test&mode=export')->handle_logs(); - - self::assertSame($this->language->lang('ACP_CONSENTMANAGER_DELETE_CONFIRM'), self::$confirm_title); + self::assertSame('', self::$confirm_title); + self::assertNull(self::$confirm_hidden_fields); } /** @@ -382,6 +418,7 @@ public function test_handle_logs_invalid_filters_show_error($action, array $requ ->willReturnOnConsecutiveCalls(...$parse_results); } + $error_substring = $this->language->lang($error_substring); $args = [self::callback(static function ($vars) use ($error_substring) { return $vars['S_ERROR'] === true && strpos($vars['ERROR_MSG'], $error_substring) !== false; })]; @@ -390,7 +427,7 @@ public function test_handle_logs_invalid_filters_show_error($action, array $requ ->with(...$args); $request_values[$action] = 1; - $this->create_controller($this->create_request_mock($request_values), 'adm.php?i=test&mode=export')->handle_logs(); + $this->create_controller($this->create_request_mock($request_values), self::EXPORT_URL)->handle_logs(); } public function handle_logs_invalid_filter_data() @@ -404,7 +441,7 @@ public function handle_logs_invalid_filter_data() 'export_consent_version' => 0, ], [false], - 'Date from', + 'ACP_CONSENTMANAGER_EXPORT_DATE_FROM', ], 'invalid date to' => [ [ @@ -414,7 +451,7 @@ public function handle_logs_invalid_filter_data() 'export_consent_version' => 0, ], [false], - 'Date to', + 'ACP_CONSENTMANAGER_EXPORT_DATE_TO', ], 'reversed date range' => [ [ @@ -424,7 +461,7 @@ public function handle_logs_invalid_filter_data() 'export_consent_version' => 0, ], [1735603200, 1704067200], - 'Date from', + 'ACP_CONSENTMANAGER_EXPORT_DATE_FROM', ], ]; @@ -467,7 +504,7 @@ public function test_handle_logs_export_success_logs_and_passes_filters_to_downl $phpbb_root_path, $phpEx ); - $controller->set_page_url('adm.php?i=test&mode=export'); + $controller->set_page_url(self::EXPORT_URL); $controller->handle_logs(); self::assertSame([ @@ -480,7 +517,7 @@ public function test_handle_logs_export_success_logs_and_passes_filters_to_downl public function test_handle_logs_delete_confirmed_logs_and_triggers_success_notice() { - self::$valid_form = false; + self::$valid_form = true; self::$confirm_result = true; $this->acp_manager->expects(self::once())->method('get_user_id_by_username')->with('Alice')->willReturn(42); @@ -504,8 +541,10 @@ public function test_handle_logs_delete_confirmed_logs_and_triggers_success_noti 'export_date_to' => '2024-12-31', 'export_username' => 'Alice', 'export_consent_version' => 2, + 'creation_time' => 1234567890, + 'form_token' => 'form-token-value', ]); - $this->create_controller($request, 'adm.php?i=test&mode=export')->handle_logs(); + $this->create_controller($request, self::EXPORT_URL)->handle_logs(); } /** @@ -522,7 +561,7 @@ public function test_handle_logs_unknown_username_shows_error($action) $this->acp_manager->method('parse_date_filter') ->willReturn(false); - $args = [self::callback(function ($vars) { + $args = [self::callback(static function ($vars) { return $vars['S_ERROR'] === true && strpos($vars['ERROR_MSG'], 'MissingUser') !== false && $vars['EXPORT_USERNAME'] === 'MissingUser' @@ -535,7 +574,7 @@ public function test_handle_logs_unknown_username_shows_error($action) $this->create_controller($this->create_request_mock([ $action => 1, 'export_username' => 'MissingUser', - ]), 'adm.php?i=test&mode=export')->handle_logs(); + ]), self::EXPORT_URL)->handle_logs(); } public function handle_logs_unknown_username_data() @@ -621,9 +660,19 @@ function confirm_box($check, $title = '', $hidden = null) { if ($check) { + if (isset($_POST['cancel'])) + { + return false; + } + return \phpbb\consentmanager\tests\controller\acp_controller_test::$confirm_result; } + if (!empty($_POST['confirm_key'])) + { + return false; + } + \phpbb\consentmanager\tests\controller\acp_controller_test::$confirm_title = $title; \phpbb\consentmanager\tests\controller\acp_controller_test::$confirm_hidden_fields = $hidden; return false; diff --git a/tests/functional/acp_test.php b/tests/functional/acp_test.php index 7e2694c..2194c2d 100644 --- a/tests/functional/acp_test.php +++ b/tests/functional/acp_test.php @@ -15,6 +15,13 @@ */ class acp_test extends functional_base { + protected function setUp(): void + { + parent::setUp(); + + $this->add_lang_ext('phpbb/consentmanager', 'acp_consentmanager'); + } + public function test_acp_page_renders_consent_manager_settings() { $this->login(); @@ -22,9 +29,9 @@ public function test_acp_page_renders_consent_manager_settings() $crawler = self::request('GET', $this->get_module_url()); - $this->assertStringContainsString('Consent categories', $crawler->filter('#main')->text()); - $this->assertStringContainsString('ACP-managed integrations', $crawler->filter('#main')->text()); - $this->assertStringContainsString('Current consent version', $crawler->filter('#main')->text()); + $this->assertContainsLang('ACP_CONSENTMANAGER_CATEGORIES', $crawler->filter('#main')->text()); + $this->assertContainsLang('ACP_CONSENTMANAGER_INTEGRATIONS', $crawler->filter('#main')->text()); + $this->assertContainsLang('ACP_CONSENTMANAGER_VERSION', $crawler->filter('#main')->text()); } public function test_acp_form_saves_settings_and_integrations() @@ -77,10 +84,10 @@ public function test_acp_force_reprompt_increments_version() $before = $this->get_consent_version(); $crawler = self::request('GET', $this->get_module_url()); - $form = $crawler->selectButton('Force re-prompt')->form(); + $form = $crawler->selectButton($this->lang('ACP_CONSENTMANAGER_FORCE_REPROMPT'))->form(); $crawler = self::submit($form); - $this->assertStringContainsString('Consent version increased. Visitors will be asked to review their settings again.', $crawler->text()); + $this->assertContainsLang('ACP_CONSENTMANAGER_REPROMPT_SUCCESS', $crawler->text()); $this->assertSame($before + 1, $this->get_consent_version()); } diff --git a/tests/functional/frontend_test.php b/tests/functional/frontend_test.php index d5fced6..28e23e7 100644 --- a/tests/functional/frontend_test.php +++ b/tests/functional/frontend_test.php @@ -31,7 +31,7 @@ public function test_frontend_markup_is_injected_on_board_pages() $payload = $this->extract_payload($content); $this->assertStringContainsString('consent-manager-root', $content); - $this->assertStringContainsString('Privacy settings', $crawler->filter('#consent-manager-link')->text()); + $this->assertContainsLang('CONSENTMANAGER_SETTINGS_TITLE', $crawler->filter('#consent-manager-link')->text()); $this->assertSame(1, $payload['version']); $this->assertSame('phpbb_consent_manager', $payload['storageKey']); $this->assertSame($this->lang('CONSENTMANAGER_MEDIA_PLACEHOLDER'), $this->extract_media_placeholder_label($content)); diff --git a/tests/service/acp_manager_test.php b/tests/service/acp_manager_test.php index a4fc58a..e3bff43 100644 --- a/tests/service/acp_manager_test.php +++ b/tests/service/acp_manager_test.php @@ -30,6 +30,16 @@ protected function setUp(): void global $db, $phpbb_root_path, $phpEx; $lang_loader = new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx); + $lang_loader->set_extension_manager(new \phpbb_mock_extension_manager( + $phpbb_root_path, + [ + 'phpbb/consentmanager' => [ + 'ext_name' => 'phpbb/consentmanager', + 'ext_active' => '1', + 'ext_path' => 'ext/phpbb/consentmanager/', + ], + ] + )); $this->language = new \phpbb\language\language($lang_loader); $this->language->add_lang('common', 'phpbb/consentmanager'); $this->language->add_lang('acp_consentmanager', 'phpbb/consentmanager'); @@ -155,6 +165,7 @@ public function test_get_settings_template_data_pretty_prints_stored_integration $template_data = $manager->get_settings_template_data(); self::assertSame($this->get_pretty_integrations_json(), $template_data['CONSENTMANAGER_INTEGRATIONS']); + self::assertSame($this->get_example_integrations_json(), $template_data['CONSENTMANAGER_INTEGRATIONS_EXAMPLE']); self::assertSame(1, $template_data['CONSENTMANAGER_VERSION']); } @@ -164,6 +175,7 @@ public function test_get_settings_template_data_returns_empty_integrations_when_ $template_data = $manager->get_settings_template_data(); self::assertSame('', $template_data['CONSENTMANAGER_INTEGRATIONS']); + self::assertSame($this->get_example_integrations_json(), $template_data['CONSENTMANAGER_INTEGRATIONS_EXAMPLE']); } public function test_get_settings_template_data_keeps_invalid_json_verbatim() @@ -797,6 +809,22 @@ protected function get_pretty_integrations_json() JSON; } + protected function get_example_integrations_json() + { + return <<<'JSON' +[ + { + "id": "example.analytics", + "category": "analytics", + "label": "Example Analytics", + "description": "Loads a simple analytics library after consent.", + "src": "https://cdn.example.com/analytics.js", + "async": true + } +] +JSON; + } + protected function get_language_messages(array $message_specs) { return array_map(function ($message_spec) {