From 571cfdfe1dc76651ad2f5e58b48129f4ab41a8b5 Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Mon, 4 May 2026 13:24:51 +0600 Subject: [PATCH 1/3] feat(plugin-repo): add privacy policy content check Add a new Privacy_Policy_Check that warns when a plugin uses personal-data-handling APIs but does not call wp_add_privacy_policy_content(). WordPress.org guidelines require plugins that collect, store, or transmit personal data to a third party to suggest privacy policy text to site administrators via this function. The check scans PHP files for signals indicating potential personal data handling: - wp_remote_post() / wp_remote_get() (external data transmission) - setcookie() / $_COOKIE (cookie-based tracking) - wp_set_auth_cookie() (authentication cookies) If any signal is detected and wp_add_privacy_policy_content() is not called anywhere in the plugin, a single warning is emitted on the plugin's main file pointing to the official WordPress privacy developer documentation. Plugins with no signals are completely unaffected by this check. Fixes #1249 --- docs/checks.md | 1 + .../Plugin_Repo/Privacy_Policy_Check.php | 139 ++++++++++++++++++ includes/Checker/Default_Check_Repository.php | 1 + .../load.php | 27 ++++ .../load.php | 30 ++++ .../load.php | 41 ++++++ .../Checks/Privacy_Policy_Check_Tests.php | 71 +++++++++ 7 files changed, 310 insertions(+) create mode 100644 includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-privacy-policy-no-signals/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php create mode 100644 tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php diff --git a/docs/checks.md b/docs/checks.md index 11662a607..c85188147 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -35,3 +35,4 @@ | enqueued_styles_scope | performance | Checks whether any stylesheets are loaded on all pages, which is usually not desirable and can lead to performance issues. | [Learn more](https://developer.wordpress.org/plugins/) | | enqueued_scripts_scope | performance | Checks whether any scripts are loaded on all pages, which is usually not desirable and can lead to performance issues. | [Learn more](https://developer.wordpress.org/plugins/) | | non_blocking_scripts | performance | Checks whether scripts and styles are enqueued using a recommended loading strategy. | [Learn more](https://developer.wordpress.org/plugins/) | +| privacy_policy | plugin_repo | Checks that plugins handling personal data call wp_add_privacy_policy_content() to suggest privacy policy text to site administrators. | [Learn more](https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/) | diff --git a/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php b/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php new file mode 100644 index 000000000..a5922f721 --- /dev/null +++ b/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php @@ -0,0 +1,139 @@ + + */ + const PERSONAL_DATA_PATTERNS = array( + 'wp_remote_post\s*\(' => 'wp_remote_post()', + 'wp_remote_get\s*\(' => 'wp_remote_get()', + 'setcookie\s*\(' => 'setcookie()', + '\$_COOKIE\b' => '$_COOKIE', + 'wp_set_auth_cookie\s*\(' => 'wp_set_auth_cookie()', + ); + + /** + * Gets the categories for the check. + * + * Every check must have at least one category. + * + * @since 1.7.0 + * + * @return array The categories for the check. + */ + public function get_categories() { + return array( Check_Categories::CATEGORY_PLUGIN_REPO ); + } + + /** + * Amends the given result by running the check on the given list of files. + * + * @since 1.7.0 + * + * @param Check_Result $result The check result to amend, including the plugin context to check. + * @param array $files List of absolute file paths. + */ + protected function check_files( Check_Result $result, array $files ) { + $php_files = self::filter_files_by_extension( $files, 'php' ); + + if ( empty( $php_files ) ) { + return; + } + + // First, detect whether the plugin already calls wp_add_privacy_policy_content(). + $has_privacy_call = (bool) self::file_preg_match( + '#\bwp_add_privacy_policy_content\s*\(#', + $php_files + ); + + // If the plugin already registers privacy policy content, nothing to warn about. + if ( $has_privacy_call ) { + return; + } + + // Check for each personal-data-handling pattern. + foreach ( self::PERSONAL_DATA_PATTERNS as $pattern => $label ) { + $matches = array(); + $matched_file = self::file_preg_match( '#' . $pattern . '#', $php_files, $matches ); + + if ( $matched_file ) { + $this->add_result_warning_for_file( + $result, + sprintf( + /* translators: %s: The detected function or variable name indicating personal data usage. */ + __( 'Missing privacy policy content registration.
The plugin uses %s which may involve handling personal data, but does not call wp_add_privacy_policy_content(). Plugins that collect, store, or transmit personal data should suggest privacy policy text to site administrators.', 'plugin-check' ), + '' . esc_html( $label ) . '' + ), + 'missing_privacy_policy_content', + $result->plugin()->main_file(), + 0, + 0, + 'https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/', + 5 + ); + + // One warning per plugin is sufficient — avoid duplicate messages. + return; + } + } + } + + /** + * Gets the description for the check. + * + * Every check must have a short description explaining what the check does. + * + * @since 1.7.0 + * + * @return string Description. + */ + public function get_description(): string { + return __( 'Checks that plugins handling personal data call wp_add_privacy_policy_content() to suggest privacy policy text to site administrators.', 'plugin-check' ); + } + + /** + * Gets the documentation URL for the check. + * + * Every check must have a URL with further information about the check. + * + * @since 1.7.0 + * + * @return string The documentation URL. + */ + public function get_documentation_url(): string { + return __( 'https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/', 'plugin-check' ); + } +} diff --git a/includes/Checker/Default_Check_Repository.php b/includes/Checker/Default_Check_Repository.php index c22371044..a8844bec1 100644 --- a/includes/Checker/Default_Check_Repository.php +++ b/includes/Checker/Default_Check_Repository.php @@ -102,6 +102,7 @@ private function register_default_checks() { 'direct_file_access' => new Checks\Plugin_Repo\Direct_File_Access_Check(), 'external_admin_menu_links' => new Checks\Plugin_Repo\External_Admin_Menu_Links_Check(), 'wp_functions_compatibility' => new Checks\Plugin_Repo\WP_Functions_Compatibility_Check(), + 'privacy_policy' => new Checks\Plugin_Repo\Privacy_Policy_Check(), ) ); diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-no-signals/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-no-signals/load.php new file mode 100644 index 000000000..f0300534b --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-no-signals/load.php @@ -0,0 +1,27 @@ +' . esc_html__( 'Hello from Test Plugin!', 'test-plugin-privacy-policy-no-signals' ) . '

'; +} + +add_action( 'admin_footer', 'test_plugin_privacy_no_signals_greet' ); diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php new file mode 100644 index 000000000..795c6430e --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php @@ -0,0 +1,30 @@ + array( + 'email' => get_option( 'admin_email' ), + ), + ) + ); + + return $response; +} diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php new file mode 100644 index 000000000..34f399b59 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php @@ -0,0 +1,41 @@ + array( + 'site_url' => get_site_url(), + ), + ) + ); + + return $response; +} diff --git a/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php new file mode 100644 index 000000000..91b29f6fe --- /dev/null +++ b/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php @@ -0,0 +1,71 @@ +run( $check_result ); + + $warnings = $check_result->get_warnings(); + + $this->assertNotEmpty( $warnings ); + + // Warning must be on the plugin's main file. + $this->assertArrayHasKey( 'load.php', $warnings ); + + // Verify the expected warning code is present. + $this->assertCount( 1, wp_list_filter( $warnings['load.php'][0][0], array( 'code' => 'missing_privacy_policy_content' ) ) ); + } + + /** + * Tests that a plugin using wp_remote_post() WITH wp_add_privacy_policy_content() + * does not receive any warnings. + */ + public function test_run_without_errors() { + $check = new Privacy_Policy_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-privacy-policy-without-errors/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $warnings = $check_result->get_warnings(); + $errors = $check_result->get_errors(); + + $this->assertEmpty( $warnings ); + $this->assertEmpty( $errors ); + } + + /** + * Tests that a plugin with no personal-data-handling patterns does not receive + * any warnings, even if it does not call wp_add_privacy_policy_content(). + */ + public function test_run_with_no_signals() { + $check = new Privacy_Policy_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-privacy-policy-no-signals/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $warnings = $check_result->get_warnings(); + $errors = $check_result->get_errors(); + + $this->assertEmpty( $warnings ); + $this->assertEmpty( $errors ); + } +} From 47e42263826385d865f63e50c3b413f197141da2 Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Mon, 4 May 2026 13:35:48 +0600 Subject: [PATCH 2/3] fix(tests): remove function name from test plugin description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin description contained wp_add_privacy_policy_content() with parentheses, which caused the check's detection regex to match the comment string and return early as if the function was already implemented — producing no warning and failing the test. The description now reads 'does not register privacy policy content' which avoids the false positive without changing the intent of the test fixture. --- .../plugins/test-plugin-privacy-policy-with-errors/load.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php index 795c6430e..d32c6f211 100644 --- a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-with-errors/load.php @@ -2,7 +2,7 @@ /** * Plugin Name: Test Plugin Privacy Policy With Errors * Plugin URI: https://github.com/WordPress/plugin-check - * Description: A test plugin that handles personal data but does not call wp_add_privacy_policy_content(). + * Description: A test plugin that handles personal data but does not register privacy policy content. * Requires at least: 6.0 * Requires PHP: 7.4 * Version: 1.0.0 From d025f52cf90a19e18934ca4edf0dee82b9e62b80 Mon Sep 17 00:00:00 2001 From: Faisal Ahammad Date: Mon, 15 Jun 2026 18:26:04 +0600 Subject: [PATCH 3/3] Use token parsing for privacy policy detection Replace regex scanning with token_get_all() so function names and variables in comments and string literals no longer trigger false positives or hide real signals. Add fixtures and tests for comment-only and string-only mentions. --- .../Plugin_Repo/Privacy_Policy_Check.php | 262 +++++++++++++++--- .../load.php | 34 +++ .../load.php | 34 +++ .../load.php | 2 +- .../Checks/Privacy_Policy_Check_Tests.php | 42 +++ 5 files changed, 338 insertions(+), 36 deletions(-) create mode 100644 tests/phpunit/testdata/plugins/test-plugin-privacy-policy-comment-only/load.php create mode 100644 tests/phpunit/testdata/plugins/test-plugin-privacy-policy-string-only/load.php diff --git a/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php b/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php index a5922f721..e39554e75 100644 --- a/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php +++ b/includes/Checker/Checks/Plugin_Repo/Privacy_Policy_Check.php @@ -21,6 +21,9 @@ * administrators via wp_add_privacy_policy_content(). This check detects common * personal-data-handling patterns and warns if that function is not used. * + * Uses token-based parsing to avoid false positives from function names appearing + * in comments or string literals. + * * @since 1.7.0 */ class Privacy_Policy_Check extends Abstract_File_Check { @@ -29,20 +32,29 @@ class Privacy_Policy_Check extends Abstract_File_Check { use Stable_Check; /** - * Regex patterns that indicate a plugin may handle personal data. + * Function names that indicate a plugin may handle personal data. * - * Each pattern is accompanied by a human-readable label used in the + * Each function name is accompanied by a human-readable label used in the * warning message to help plugin authors understand why the check fired. * * @since 1.7.0 * @var array */ - const PERSONAL_DATA_PATTERNS = array( - 'wp_remote_post\s*\(' => 'wp_remote_post()', - 'wp_remote_get\s*\(' => 'wp_remote_get()', - 'setcookie\s*\(' => 'setcookie()', - '\$_COOKIE\b' => '$_COOKIE', - 'wp_set_auth_cookie\s*\(' => 'wp_set_auth_cookie()', + const PERSONAL_DATA_FUNCTIONS = array( + 'wp_remote_post' => 'wp_remote_post()', + 'wp_remote_get' => 'wp_remote_get()', + 'setcookie' => 'setcookie()', + 'wp_set_auth_cookie' => 'wp_set_auth_cookie()', + ); + + /** + * Variable names that indicate a plugin may handle personal data. + * + * @since 1.7.0 + * @var array + */ + const PERSONAL_DATA_VARIABLES = array( + '$_COOKIE' => '$_COOKIE', ); /** @@ -74,41 +86,221 @@ protected function check_files( Check_Result $result, array $files ) { } // First, detect whether the plugin already calls wp_add_privacy_policy_content(). - $has_privacy_call = (bool) self::file_preg_match( - '#\bwp_add_privacy_policy_content\s*\(#', - $php_files - ); + $has_privacy_call = $this->find_function_in_files( $php_files, array( 'wp_add_privacy_policy_content' => 'wp_add_privacy_policy_content()' ) ); // If the plugin already registers privacy policy content, nothing to warn about. if ( $has_privacy_call ) { return; } - // Check for each personal-data-handling pattern. - foreach ( self::PERSONAL_DATA_PATTERNS as $pattern => $label ) { - $matches = array(); - $matched_file = self::file_preg_match( '#' . $pattern . '#', $php_files, $matches ); - - if ( $matched_file ) { - $this->add_result_warning_for_file( - $result, - sprintf( - /* translators: %s: The detected function or variable name indicating personal data usage. */ - __( 'Missing privacy policy content registration.
The plugin uses %s which may involve handling personal data, but does not call wp_add_privacy_policy_content(). Plugins that collect, store, or transmit personal data should suggest privacy policy text to site administrators.', 'plugin-check' ), - '' . esc_html( $label ) . '' - ), - 'missing_privacy_policy_content', - $result->plugin()->main_file(), - 0, - 0, - 'https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/', - 5 - ); - - // One warning per plugin is sufficient — avoid duplicate messages. - return; + // Check for personal-data-handling function calls. + $matched_label = $this->find_function_in_files( $php_files, self::PERSONAL_DATA_FUNCTIONS ); + + // If no function call found, check for personal-data-handling variable usage. + if ( ! $matched_label ) { + $matched_label = $this->find_variable_in_files( $php_files, self::PERSONAL_DATA_VARIABLES ); + } + + if ( $matched_label ) { + $this->add_result_warning_for_file( + $result, + sprintf( + /* translators: %s: The detected function or variable name indicating personal data usage. */ + __( 'Missing privacy policy content registration.
The plugin uses %s which may involve handling personal data, but does not call wp_add_privacy_policy_content(). Plugins that collect, store, or transmit personal data should suggest privacy policy text to site administrators.', 'plugin-check' ), + '' . esc_html( $matched_label ) . '' + ), + 'missing_privacy_policy_content', + $result->plugin()->main_file(), + 0, + 0, + 'https://developer.wordpress.org/plugins/privacy/suggesting-text-for-the-site-privacy-policy/', + 5 + ); + } + } + + /** + * Searches PHP files for a global function call using token-based parsing. + * + * Uses token_get_all() to avoid false positives from function names appearing + * in comments or string literals. + * + * @since 1.7.0 + * + * @param array $files List of absolute file paths. + * @param array $function_names Map of function names (lowercase) to human-readable labels. + * @return string|false Human-readable label of the first matched function, or false if none found. + */ + private function find_function_in_files( array $files, array $function_names ) { + $lowercase_names = array(); + foreach ( $function_names as $name => $label ) { + $lowercase_names[ strtolower( $name ) ] = $label; + } + + foreach ( $files as $file ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $source = file_get_contents( $file ); + if ( false === $source || '' === $source ) { + continue; + } + + $tokens = token_get_all( $source ); + + foreach ( $tokens as $index => $token ) { + if ( ! is_array( $token ) || T_STRING !== $token[0] ) { + continue; + } + + $name = strtolower( $token[1] ); + if ( ! isset( $lowercase_names[ $name ] ) ) { + continue; + } + + $next_index = $this->get_next_significant_token_index( $tokens, $index ); + if ( null === $next_index || '(' !== $tokens[ $next_index ] ) { + continue; + } + + if ( ! $this->is_global_function_call( $tokens, $index ) ) { + continue; + } + + return $lowercase_names[ $name ]; + } + } + + return false; + } + + /** + * Checks whether a tokenized T_STRING is a global function call. + * + * Rejects method calls (->method), static calls (Class::method), + * function definitions (function name), instantiation (new name), + * and namespace-qualified calls (\NS\name or NS\name). + * + * @since 1.7.0 + * + * @param array $tokens Token stream. + * @param int $index Current token index. + * @return bool True if the token represents a global function call. + */ + private function is_global_function_call( array $tokens, int $index ): bool { + $previous_index = $this->get_previous_significant_token_index( $tokens, $index ); + if ( null === $previous_index ) { + return true; + } + + $previous_token = $tokens[ $previous_index ]; + + if ( is_array( $previous_token ) ) { + $disqualifying_tokens = array( T_FUNCTION, T_NEW, T_OBJECT_OPERATOR, T_DOUBLE_COLON ); + + // T_NULLSAFE_OBJECT_OPERATOR (?->) only exists on PHP 8.0+. + if ( defined( 'T_NULLSAFE_OBJECT_OPERATOR' ) ) { + $disqualifying_tokens[] = constant( 'T_NULLSAFE_OBJECT_OPERATOR' ); + } + + if ( in_array( $previous_token[0], $disqualifying_tokens, true ) ) { + return false; + } + + if ( T_NS_SEPARATOR === $previous_token[0] ) { + $before_namespace_index = $this->get_previous_significant_token_index( $tokens, $previous_index ); + if ( null === $before_namespace_index ) { + return true; + } + + $before_namespace_token = $tokens[ $before_namespace_index ]; + if ( is_array( $before_namespace_token ) && in_array( $before_namespace_token[0], array( T_STRING, T_NAMESPACE ), true ) ) { + return false; + } + } + } + + return true; + } + + /** + * Searches PHP files for a variable usage using token-based parsing. + * + * Uses token_get_all() to avoid false positives from variable names appearing + * in comments or string literals. + * + * @since 1.7.0 + * + * @param array $files List of absolute file paths. + * @param array $variable_names Map of variable names to human-readable labels. + * @return string|false Human-readable label of the first matched variable, or false if none found. + */ + private function find_variable_in_files( array $files, array $variable_names ) { + foreach ( $files as $file ) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $source = file_get_contents( $file ); + if ( false === $source || '' === $source ) { + continue; + } + + $tokens = token_get_all( $source ); + + foreach ( $tokens as $token ) { + if ( ! is_array( $token ) || T_VARIABLE !== $token[0] ) { + continue; + } + + if ( isset( $variable_names[ $token[1] ] ) ) { + return $variable_names[ $token[1] ]; + } + } + } + + return false; + } + + /** + * Finds the next significant token index, skipping whitespace. + * + * @since 1.7.0 + * + * @param array $tokens Token stream. + * @param int $index Current token index. + * @return int|null Index of the next significant token, or null if end of stream. + */ + private function get_next_significant_token_index( array $tokens, int $index ): ?int { + $count = count( $tokens ); + for ( $i = $index + 1; $i < $count; $i++ ) { + $token = $tokens[ $i ]; + if ( is_array( $token ) && T_WHITESPACE === $token[0] ) { + continue; + } + + return $i; + } + + return null; + } + + /** + * Finds the previous significant token index, skipping whitespace and comments. + * + * @since 1.7.0 + * + * @param array $tokens Token stream. + * @param int $index Current token index. + * @return int|null Index of the previous significant token, or null if start of stream. + */ + private function get_previous_significant_token_index( array $tokens, int $index ): ?int { + for ( $i = $index - 1; $i >= 0; $i-- ) { + $token = $tokens[ $i ]; + + if ( is_array( $token ) && in_array( $token[0], array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT ), true ) ) { + continue; } + + return $i; } + + return null; } /** diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-comment-only/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-comment-only/load.php new file mode 100644 index 000000000..a9cd50554 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-comment-only/load.php @@ -0,0 +1,34 @@ + array( + 'email' => get_option( 'admin_email' ), + ), + ) + ); +} diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-string-only/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-string-only/load.php new file mode 100644 index 000000000..90f230688 --- /dev/null +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-string-only/load.php @@ -0,0 +1,34 @@ + array( + 'email' => get_option( 'admin_email' ), + ), + ) + ); +} diff --git a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php index 34f399b59..93a3b05b1 100644 --- a/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php +++ b/tests/phpunit/testdata/plugins/test-plugin-privacy-policy-without-errors/load.php @@ -2,7 +2,7 @@ /** * Plugin Name: Test Plugin Privacy Policy Without Errors * Plugin URI: https://github.com/WordPress/plugin-check - * Description: A test plugin that handles personal data AND correctly calls wp_add_privacy_policy_content(). + * Description: A test plugin that handles personal data AND correctly registers privacy policy content. * Requires at least: 6.0 * Requires PHP: 7.4 * Version: 1.0.0 diff --git a/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php b/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php index 91b29f6fe..d0910ccd4 100644 --- a/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php +++ b/tests/phpunit/tests/Checker/Checks/Privacy_Policy_Check_Tests.php @@ -68,4 +68,46 @@ public function test_run_with_no_signals() { $this->assertEmpty( $warnings ); $this->assertEmpty( $errors ); } + + /** + * Tests that function names appearing only in comments do not satisfy the + * wp_add_privacy_policy_content() check. The fixture contains a real + * wp_remote_post() signal plus a comment-only mention of the privacy + * function; the check must still emit a warning because the real code does + * not register privacy policy content. + */ + public function test_run_with_comment_only_signals() { + $check = new Privacy_Policy_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-privacy-policy-comment-only/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $warnings = $check_result->get_warnings(); + + $this->assertNotEmpty( $warnings ); + $this->assertArrayHasKey( 'load.php', $warnings ); + $this->assertCount( 1, wp_list_filter( $warnings['load.php'][0][0], array( 'code' => 'missing_privacy_policy_content' ) ) ); + } + + /** + * Tests that function names and variable names appearing only inside + * string literals do not satisfy the wp_add_privacy_policy_content() check. + * The fixture contains a real wp_remote_post() signal plus a string-only + * mention of the privacy function; the check must still emit a warning + * because the real code does not register privacy policy content. + */ + public function test_run_with_string_only_signals() { + $check = new Privacy_Policy_Check(); + $check_context = new Check_Context( UNIT_TESTS_PLUGIN_DIR . 'test-plugin-privacy-policy-string-only/load.php' ); + $check_result = new Check_Result( $check_context ); + + $check->run( $check_result ); + + $warnings = $check_result->get_warnings(); + + $this->assertNotEmpty( $warnings ); + $this->assertArrayHasKey( 'load.php', $warnings ); + $this->assertCount( 1, wp_list_filter( $warnings['load.php'][0][0], array( 'code' => 'missing_privacy_policy_content' ) ) ); + } }