From c5d013bf95e520bfee7d13d9fce494173cd88f9d Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Mon, 13 Aug 2018 16:31:31 +0000 Subject: [PATCH] Privacy: Ensure the user request email is sent in the requested user's locale (or the site's default locale if they are not a registered user) when the administrator creating the request uses a different locale. Props desrosj, Chouby, iandunn, lbenicio, birgire, earnjam, swissspidy, garrett-eclipse. Fixes #43985. git-svn-id: https://develop.svn.wordpress.org/trunk@43568 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/file.php | 4 +- src/wp-includes/user.php | 28 +- tests/phpunit/data/languages/de_DE.mo | Bin 556 -> 699 bytes tests/phpunit/data/languages/de_DE.po | 18 +- tests/phpunit/data/languages/es_ES.mo | Bin 725 -> 855 bytes tests/phpunit/data/languages/es_ES.po | 18 +- .../wpPrivacySendPersonalDataExportEmail.php | 13 +- .../phpunit/tests/user/wpSendUserRequest.php | 372 ++++++++++++++++++ 8 files changed, 428 insertions(+), 25 deletions(-) create mode 100644 tests/phpunit/tests/user/wpSendUserRequest.php diff --git a/src/wp-admin/includes/file.php b/src/wp-admin/includes/file.php index 3c8f9c221a..4fd93e2e60 100644 --- a/src/wp-admin/includes/file.php +++ b/src/wp-admin/includes/file.php @@ -2196,7 +2196,7 @@ function wp_privacy_send_personal_data_export_email( $request_id ) { $request = wp_get_user_request_data( $request_id ); if ( ! $request || 'export_personal_data' !== $request->action_name ) { - return new WP_Error( 'invalid', __( 'Invalid request ID when sending personal data export email.' ) ); + return new WP_Error( 'invalid_request', __( 'Invalid request ID when sending personal data export email.' ) ); } /** This filter is documented in wp-includes/functions.php */ @@ -2256,7 +2256,7 @@ All at ###SITENAME### ); if ( ! $mail_success ) { - return new WP_Error( 'error', __( 'Unable to send personal data export email.' ) ); + return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export email.' ) ); } return true; diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 4ebcf822a9..c1de6f1243 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -3357,16 +3357,25 @@ function wp_user_request_action_description( $action_name ) { * @since 4.9.6 * * @param string $request_id ID of the request created via wp_create_user_request(). - * @return WP_Error|bool Will return true/false based on the success of sending the email, or a WP_Error object. + * @return bool|WP_Error True on success, `WP_Error` on failure. */ function wp_send_user_request( $request_id ) { $request_id = absint( $request_id ); $request = wp_get_user_request_data( $request_id ); if ( ! $request ) { - return new WP_Error( 'user_request_error', __( 'Invalid request.' ) ); + return new WP_Error( 'invalid_request', __( 'Invalid user request.' ) ); } + // Localize message content for user; fallback to site default for visitors. + if ( ! empty( $request->user_id ) ) { + $locale = get_user_locale( $request->user_id ); + } else { + $locale = get_locale(); + } + + $switched_locale = switch_to_locale( $locale ); + $email_data = array( 'request' => $request, 'email' => $request->email, @@ -3454,7 +3463,17 @@ All at ###SITENAME### */ $subject = apply_filters( 'user_request_action_email_subject', $subject, $email_data['sitename'], $email_data ); - return wp_mail( $email_data['email'], $subject, $content ); + $email_sent = wp_mail( $email_data['email'], $subject, $content ); + + if ( $switched_locale ) { + restore_previous_locale(); + } + + if ( ! $email_sent ) { + return new WP_Error( 'privacy_email_error', __( 'Unable to send personal data export confirmation email.' ) ); + } + + return true; } /** @@ -3504,7 +3523,7 @@ function wp_validate_user_request_key( $request_id, $key ) { $request = wp_get_user_request_data( $request_id ); if ( ! $request ) { - return new WP_Error( 'user_request_error', __( 'Invalid request.' ) ); + return new WP_Error( 'invalid_request', __( 'Invalid request.' ) ); } if ( ! in_array( $request->status, array( 'request-pending', 'request-failed' ), true ) ) { @@ -3591,7 +3610,6 @@ final class WP_User_Request { * * @var int */ - public $user_id = 0; /** diff --git a/tests/phpunit/data/languages/de_DE.mo b/tests/phpunit/data/languages/de_DE.mo index f2c48b4b40033eb9a613cbdac26d42f89cc3d23c..b65a14e19c98eb3c4294921179ff799e95d444c2 100644 GIT binary patch delta 444 zcmXw!y-EW?5XU!~C`1HFz(&C>5>Rx{T+Sl7R56!A5lnH~h{&27!PZwL)bcdIR}3H=W}=FA^njVedp4*1Z^5wL|kMBxkR+RA+yLkvVeRc zdE_UFe-q882+5(pj%p#B$UMfw2||`ouTV3nkEmE8Ph)M9XLKep@tPFSjT^P8VxX{N0Em5n{3T2b4B|lgBal`A(wxi?eOf@8hk?O}!41d&B8Fff0n`HqAU?=J zKs|8424t}SG3&&I5z^7BhAPFe3eNd?X_-a23XaJonfZBE3aUmb#S?!d3RvhGSm+uW zD;Qc@8Cq%^7*Eb*oW*LSXQ*d5nUhJy%TljGlglSDFTFG|J=IDfwK(22m@B|PMAx|} zH4$Wzu1jJGPy(o?xI{OkC^4@%C$S{I2rL58r5lo(m}{jl*^wz-0o5^y3OR`iiOI>C Nhc`oAGvq2?A!ih6cJurV54@Rz?=u21b*oGR|T%)U(htG?;9{q#|OXSE0$}lbDxYnwXwy TrI1=2?;1RL5mWkPEoNN+9XlY7 diff --git a/tests/phpunit/data/languages/es_ES.po b/tests/phpunit/data/languages/es_ES.po index 0736e9ba8a..6e03a32de2 100644 --- a/tests/phpunit/data/languages/es_ES.po +++ b/tests/phpunit/data/languages/es_ES.po @@ -1,15 +1,18 @@ -# Translation of Development (4.4.x) in Spanish (Spain) -# This file is distributed under the same license as the Development (4.4.x) package. +# Translation of Development (4.9.x) in Spanish (Spain) +# This file is distributed under the same license as the Development (4.9.x) package. msgid "" msgstr "" -"PO-Revision-Date: 2016-10-25 18:28+0200\n" +"PO-Revision-Date: 2018-08-13 19:19+0300\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Poedit 1.8.10\n" -"Project-Id-Version: Development (4.4.x)\n" +"X-Generator: Poedit 2.1.1\n" +"Project-Id-Version: Development (4.9.x)\n" "Language: es_ES\n" +"POT-Creation-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" #. translators: 'rtl' or 'ltr'. This sets the text direction for WordPress. #: wp-includes/locale.php:201 @@ -36,3 +39,8 @@ msgstr "(Actual: %s)" msgctxt "menu" msgid "(Currently set to: %s)" msgstr "(Actualmente fijado en: %s)" + +#. translators: Privacy data request subject. 1: Site name, 2: Name of the action +#: wp-includes/user.php:3445 +msgid "[%1$s] Confirm Action: %2$s" +msgstr "[%1$s] Confirma la acción: %2$s" diff --git a/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php b/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php index 3e8ae840db..523ec76981 100644 --- a/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php +++ b/tests/phpunit/tests/privacy/wpPrivacySendPersonalDataExportEmail.php @@ -39,7 +39,7 @@ class Tests_Privacy_WpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase * * @since 4.9.6 */ - function setUp() { + public function setUp() { parent::setUp(); reset_phpmailer_instance(); } @@ -49,7 +49,7 @@ class Tests_Privacy_WpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase * * @since 4.9.6 */ - function tearDown() { + public function tearDown() { reset_phpmailer_instance(); parent::tearDown(); } @@ -95,12 +95,12 @@ class Tests_Privacy_WpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase $request_id = 0; $email_sent = wp_privacy_send_personal_data_export_email( $request_id ); $this->assertWPError( $email_sent ); - $this->assertSame( 'invalid', $email_sent->get_error_code() ); + $this->assertSame( 'invalid_request', $email_sent->get_error_code() ); $request_id = PHP_INT_MAX; $email_sent = wp_privacy_send_personal_data_export_email( $request_id ); $this->assertWPError( $email_sent ); - $this->assertSame( 'invalid', $email_sent->get_error_code() ); + $this->assertSame( 'invalid_request', $email_sent->get_error_code() ); } /** @@ -111,10 +111,9 @@ class Tests_Privacy_WpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase public function test_return_wp_error_when_send_fails() { add_filter( 'wp_mail_from', '__return_empty_string' ); // Cause `wp_mail()` to return false. $email_sent = wp_privacy_send_personal_data_export_email( self::$request_id ); - remove_filter( 'wp_mail_from', '__return_empty_string' ); $this->assertWPError( $email_sent ); - $this->assertSame( 'error', $email_sent->get_error_code() ); + $this->assertSame( 'privacy_email_error', $email_sent->get_error_code() ); } /** @@ -125,7 +124,6 @@ class Tests_Privacy_WpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase public function test_export_expiration_should_be_filterable() { add_filter( 'wp_privacy_export_expiration', array( $this, 'modify_export_expiration' ) ); wp_privacy_send_personal_data_export_email( self::$request_id ); - remove_filter( 'wp_privacy_export_expiration', array( $this, 'modify_export_expiration' ) ); $mailer = tests_retrieve_phpmailer_instance(); $this->assertContains( 'we will automatically delete the file on December 18, 2017,', $mailer->get_sent()->body ); @@ -152,7 +150,6 @@ class Tests_Privacy_WpPrivacySendPersonalDataExportEmail extends WP_UnitTestCase public function test_email_content_should_be_filterable() { add_filter( 'wp_privacy_personal_data_email_content', array( $this, 'modify_email_content' ), 10, 2 ); wp_privacy_send_personal_data_export_email( self::$request_id ); - remove_filter( 'wp_privacy_personal_data_email_content', array( $this, 'modify_email_content' ) ); $mailer = tests_retrieve_phpmailer_instance(); $this->assertContains( 'Custom content for request ID: ' . self::$request_id, $mailer->get_sent()->body ); diff --git a/tests/phpunit/tests/user/wpSendUserRequest.php b/tests/phpunit/tests/user/wpSendUserRequest.php new file mode 100644 index 0000000000..2ccbe19e28 --- /dev/null +++ b/tests/phpunit/tests/user/wpSendUserRequest.php @@ -0,0 +1,372 @@ +user->create_and_get( + array( + 'user_email' => 'admin@local.dev', + 'role' => 'administrator', + ) + ); + + self::$test_user = $factory->user->create_and_get( + array( + 'user_email' => 'export-user@local.dev', + 'role' => 'subscriber', + ) + ); + } + + /** + * Reset the mocked phpmailer instance before each test method. + * + * @since 4.9.9 + */ + public function setUp() { + parent::setUp(); + + set_current_screen( 'dashboard' ); + reset_phpmailer_instance(); + } + + /** + * Reset the mocked phpmailer instance after each test method. + * + * @since 4.9.9 + */ + public function tearDown() { + delete_option( 'WPLANG' ); + reset_phpmailer_instance(); + + unset( $GLOBALS['locale'] ); + unset( $GLOBALS['current_screen'] ); + unset( $GLOBALS['taxnow'] ); + unset( $GLOBALS['typenow'] ); + + restore_previous_locale(); + parent::tearDown(); + } + + /** + * The function should error when the request ID is invalid. + * + * @ticket 43985 + */ + public function test_should_error_when_invalid_request_id() { + $result = wp_send_user_request( null ); + + $this->assertWPError( $result ); + $this->assertSame( 'invalid_request', $result->get_error_code() ); + } + + /** + * The function should send a user request export email when the requester is a registered user. + * + * @ticket 43985 + */ + public function test_should_send_user_request_export_email_when_requester_registered_user() { + $request_id = wp_create_user_request( self::$test_user->user_email, 'export_personal_data' ); + + $result = wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertTrue( $result ); + $this->assertSame( self::$test_user->user_email, $mailer->get_recipient( 'to' )->address ); + $this->assertContains( 'Confirm Action: Export Personal Data', $mailer->get_sent()->subject ); + $this->assertContains( 'action=confirmaction&request_id=', $mailer->get_sent()->body ); + $this->assertContains( 'Export Personal Data', $mailer->get_sent()->body ); + } + + /** + * The function should send a user request erase email when the requester is a registered user. + * + * @ticket 43985 + */ + public function test_should_send_user_request_erase_email_when_requester_registered_user() { + $request_id = wp_create_user_request( self::$test_user->user_email, 'remove_personal_data' ); + + $result = wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertTrue( $result ); + $this->assertSame( self::$test_user->user_email, $mailer->get_recipient( 'to' )->address ); + $this->assertContains( 'Confirm Action: Erase Personal Data', $mailer->get_sent()->subject ); + $this->assertContains( 'action=confirmaction&request_id=', $mailer->get_sent()->body ); + $this->assertContains( 'Erase Personal Data', $mailer->get_sent()->body ); + } + + /** + * The function should send a user request export email when the requester is an un-registered user. + * + * @ticket 43985 + */ + public function test_should_send_user_request_export_email_when_user_not_registered() { + $request_id = wp_create_user_request( self::$test_user->user_email, 'export_personal_data' ); + + $result = wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertTrue( $result ); + $this->assertSame( self::$test_user->user_email, $mailer->get_recipient( 'to' )->address ); + $this->assertContains( 'Confirm Action: Export Personal Data', $mailer->get_sent()->subject ); + $this->assertContains( 'action=confirmaction&request_id=', $mailer->get_sent()->body ); + $this->assertContains( 'Export Personal Data', $mailer->get_sent()->body ); + } + + /** + * The function should send a user request erase email when the requester is an un-registered user. + * + * @ticket 43985 + */ + public function test_should_send_user_request_erase_email_when_user_not_registered() { + $request_id = wp_create_user_request( self::$test_user->user_email, 'remove_personal_data' ); + + $result = wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertTrue( $result ); + $this->assertSame( self::$test_user->user_email, $mailer->get_recipient( 'to' )->address ); + $this->assertContains( 'Confirm Action: Erase Personal Data', $mailer->get_sent()->subject ); + $this->assertContains( 'action=confirmaction&request_id=', $mailer->get_sent()->body ); + $this->assertContains( 'Erase Personal Data', $mailer->get_sent()->body ); + } + + /** + * The email subject should be filterable. + * + * @ticket 43985 + */ + public function test_email_subject_should_be_filterable() { + $request_id = wp_create_user_request( self::$test_user->user_email, 'remove_personal_data' ); + + add_filter( 'user_request_action_email_subject', array( $this, 'modify_email_subject' ) ); + $result = wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertTrue( $result ); + $this->assertSame( 'Custom Email Subject', $mailer->get_sent()->subject ); + } + + /** + * Filter callback to modify the subject of the email sent when an account action is attempted. + * + * @since 4.9.9 + * + * @param string $subject The email subject. + * @return string Filtered email subject. + */ + public function modify_email_subject( $subject ) { + return 'Custom Email Subject'; + } + + /** + * The email content should be filterable. + * + * @ticket 43985 + */ + public function test_email_content_should_be_filterable() { + $request_id = wp_create_user_request( self::$test_user->user_email, 'remove_personal_data' ); + + add_filter( 'user_request_action_email_content', array( $this, 'modify_email_content' ), 10, 2 ); + $result = wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertTrue( $result ); + $this->assertContains( 'Custom Email Content.', $mailer->get_sent()->body ); + } + + /** + * Filter callback to modify the content of the email sent when an account action is attempted. + * + * @since 4.9.9 + * + * @param string $email_text Confirmation email text. + * @return string $email_text Filtered email text. + */ + public function modify_email_content( $email_text ) { + return 'Custom Email Content.'; + } + + /** + * The function should error when the email was not sent. + * + * @ticket 43985 + */ + public function test_return_wp_error_when_sending_fails() { + $request_id = wp_create_user_request( 'erase.request.from.unregistered.user@example.com', 'remove_personal_data' ); + + add_filter( 'wp_mail_from', '__return_empty_string' ); // Cause `wp_mail()` to return false. + $result = wp_send_user_request( $request_id ); + + $this->assertWPError( $result ); + $this->assertSame( 'privacy_email_error', $result->get_error_code() ); + } + + /** + * The function should respect the user locale settings when the site uses the default locale. + * + * @ticket 43985 + * @group l10n + */ + public function test_should_send_user_request_email_in_user_locale() { + update_user_meta( self::$test_user->ID, 'locale', 'es_ES' ); + + wp_set_current_user( self::$admin_user->ID ); + $request_id = wp_create_user_request( self::$test_user->user_email, 'export_personal_data' ); + + wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertContains( 'Confirma la', $mailer->get_sent()->subject ); + } + + /** + * The function should respect the user locale settings when the site does not use en_US, the administrator + * uses the site's default locale, and the user has a different locale. + * + * @ticket 43985 + * @group l10n + */ + public function test_should_send_user_request_email_in_user_locale_when_site_is_not_en_us() { + update_option( 'WPLANG', 'es_ES' ); + switch_to_locale( 'es_ES' ); + + update_user_meta( self::$test_user->ID, 'locale', 'de_DE' ); + + wp_set_current_user( self::$admin_user->ID ); + $request_id = wp_create_user_request( self::$test_user->user_email, 'remove_personal_data' ); + + wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertContains( 'Aktion bestätigen', $mailer->get_sent()->subject ); + } + + /** + * The function should respect the user locale settings when the site is not en_US, the administrator + * has a different selected locale, and the user uses the site's default locale. + * + * @ticket 43985 + * @group l10n + */ + public function test_should_send_user_request_email_in_user_locale_when_admin_and_site_have_different_locales() { + update_option( 'WPLANG', 'es_ES' ); + switch_to_locale( 'es_ES' ); + + update_user_meta( self::$admin_user->ID, 'locale', 'de_DE' ); + wp_set_current_user( self::$admin_user->ID ); + + $request_id = wp_create_user_request( self::$test_user->user_email, 'export_personal_data' ); + + wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertContains( 'Confirma la', $mailer->get_sent()->subject ); + } + + /** + * The function should respect the user locale settings when the site is not en_US and both the + * administrator and the user use different locales. + * + * @ticket 43985 + * @group l10n + */ + public function test_should_send_user_request_email_in_user_locale_when_both_have_different_locales_than_site() { + update_option( 'WPLANG', 'es_ES' ); + switch_to_locale( 'es_ES' ); + + update_user_meta( self::$admin_user->ID, 'locale', 'de_DE' ); + update_user_meta( self::$test_user->ID, 'locale', 'en_US' ); + + wp_set_current_user( self::$admin_user->ID ); + + $request_id = wp_create_user_request( self::$test_user->user_email, 'export_personal_data' ); + + wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertContains( 'Confirm Action', $mailer->get_sent()->subject ); + } + + /** + * The function should respect the site's locale when the request is for an unregistered user and the + * administrator does not use the site's locale. + * + * @ticket 43985 + * @group l10n + */ + public function test_should_send_user_request_email_in_site_locale() { + update_user_meta( self::$admin_user->ID, 'locale', 'es_ES' ); + wp_set_current_user( self::$admin_user->ID ); + + $request_id = wp_create_user_request( 'erase-user-not-registered@example.com', 'erase_personal_data' ); + + wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertContains( 'Confirm Action', $mailer->get_sent()->subject ); + } + + /** + * The function should respect the site's locale when it is not en_US, the request is for an + * unregistered user, and the administrator does not use the site's default locale. + * + * @ticket 43985 + * @group l10n + */ + public function test_should_send_user_request_email_in_site_locale_when_not_en_us_and_admin_has_different_locale() { + update_option( 'WPLANG', 'es_ES' ); + switch_to_locale( 'es_ES' ); + + update_user_meta( self::$admin_user->ID, 'locale', 'de_DE' ); + wp_set_current_user( self::$admin_user->ID ); + + $request_id = wp_create_user_request( 'export-user-not-registered@example.com', 'erase_personal_data' ); + + wp_send_user_request( $request_id ); + $mailer = tests_retrieve_phpmailer_instance(); + + $this->assertContains( 'Confirma la', $mailer->get_sent()->subject ); + } +}