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 f2c48b4b40..b65a14e19c 100644 Binary files a/tests/phpunit/data/languages/de_DE.mo and b/tests/phpunit/data/languages/de_DE.mo differ diff --git a/tests/phpunit/data/languages/de_DE.po b/tests/phpunit/data/languages/de_DE.po index cd556ed401..4d66b76c10 100644 --- a/tests/phpunit/data/languages/de_DE.po +++ b/tests/phpunit/data/languages/de_DE.po @@ -1,15 +1,18 @@ -# Translation of 4.6.x in German -# This file is distributed under the same license as the 4.6.x package. +# Translation of 4.9.x in German +# This file is distributed under the same license as the 4.9.x package. msgid "" msgstr "" -"PO-Revision-Date: 2016-10-25 18:27+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: 4.6.x\n" +"X-Generator: Poedit 2.1.1\n" +"Project-Id-Version: Development (4.9.x)\n" "Language: de_DE\n" +"POT-Creation-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" #. translators: Translate this to the correct language tag for your locale, see #. https://www.w3.org/International/articles/language-tags/ for reference. Do @@ -40,3 +43,8 @@ msgstr "." #: wp-includes/script-loader.php:620 msgid "Update %s now" msgstr "Jetzt %s aktualisieren" + +#. 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] Aktion bestätigen: %2$s" diff --git a/tests/phpunit/data/languages/es_ES.mo b/tests/phpunit/data/languages/es_ES.mo index 068d37a710..d4c5767eaf 100644 Binary files a/tests/phpunit/data/languages/es_ES.mo and b/tests/phpunit/data/languages/es_ES.mo differ 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 ); + } +}