<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[52604] trunk: Users: Add new hooks to filter retrieve password emails.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/52604">52604</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/52604","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>audrasjb</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2022-01-19 12:15:13 +0000 (Wed, 19 Jan 2022)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Users: Add new hooks to filter retrieve password emails.

This change introduces two new hooks to help developers to filter retrieve password emails:

- `send_retrieve_password_email` can be used to filter whether to send the retrieve password email;
- `retrieve_password_notification_email` can be used to filter the contents of the reset password notification email sent to the user.

This changesets also adds unit tests for these new filters.

Props connapptivity, costdev, audrasjb, johnbillion.
Fixes <a href="https://core.trac.wordpress.org/ticket/54690">#54690</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesuserphp">trunk/src/wp-includes/user.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsuserretrievePasswordphp">trunk/tests/phpunit/tests/user/retrievePassword.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesuserphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/user.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/user.php    2022-01-19 10:33:55 UTC (rev 52603)
+++ trunk/src/wp-includes/user.php      2022-01-19 12:15:13 UTC (rev 52604)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2938,6 +2938,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return $errors;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /**
+        * Filter whether to send the retrieve password email.
+        *
+        * @since 6.0.0
+        *
+        * @param bool $send False to prevent sending. Default: true
+        */
+       if ( ! apply_filters( 'send_retrieve_password_email', true ) ) {
+               return true;
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         // Redefining user_login ensures we return the right case in the email.
</span><span class="cx" style="display: block; padding: 0 10px">        $user_login = $user_data->user_login;
</span><span class="cx" style="display: block; padding: 0 10px">        $user_email = $user_data->user_email;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3012,11 +3023,60 @@
</span><span class="cx" style="display: block; padding: 0 10px">         */
</span><span class="cx" style="display: block; padding: 0 10px">        $message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Short-circuit on falsey $message value for backwards compatibility.
+       if ( ! $message ) {
+               return true;
+       }
+
+       // Wrap the single notification email arguments in an array to pass them to the retrieve_password_notification_email filter.
+       $defaults = array(
+               'to'      => $user_email,
+               'subject' => $title,
+               'message' => $message,
+               'headers' => '',
+       );
+
+       $data = compact( 'key', 'user_login', 'user_data' );
+
+       /**
+        * Filter the contents of the reset password notification email sent to the user.
+        *
+        * @since 6.0.0
+        *
+        * @param array $defaults {
+        *     The default notification email arguments. Used to build wp_mail().
+        *
+        *     @type string $to      The intended recipient - user email address.
+        *     @type string $subject The subject of the email.
+        *     @type string $message The body of the email.
+        *     @type string $headers The headers of the email.
+        * }
+        * @param array $data {
+        *     Additional information for extenders.
+        *
+        *     @type string  $key        The activation key.
+        *     @type string  $user_login The username for the user.
+        *     @type WP_User $user_data  WP_User object.
+        * }
+        */
+       $notification_email = apply_filters( 'retrieve_password_notification_email', $defaults, $data );
+
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( $switched_locale ) {
</span><span class="cx" style="display: block; padding: 0 10px">                restore_previous_locale();
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( $message && ! wp_mail( $user_email, wp_specialchars_decode( $title ), $message ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_array( $notification_email ) ) {
+               // Force key order and merge defaults in case any value is missing in the filtered array.
+               $notification_email = array_merge( $defaults, $notification_email );
+       } else {
+               $notification_email = $defaults;
+       }
+
+       list( $to, $subject, $message, $headers ) = array_values( $notification_email );
+
+       $subject = wp_specialchars_decode( $subject );
+
+       if ( ! wp_mail( $to, $subject, $message, $headers ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                 $errors->add(
</span><span class="cx" style="display: block; padding: 0 10px">                        'retrieve_password_email_failure',
</span><span class="cx" style="display: block; padding: 0 10px">                        sprintf(
</span></span></pre></div>
<a id="trunktestsphpunittestsuserretrievePasswordphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/user/retrievePassword.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/user/retrievePassword.php                               (rev 0)
+++ trunk/tests/phpunit/tests/user/retrievePassword.php 2022-01-19 12:15:13 UTC (rev 52604)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,60 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Test cases for the `retrieve_password()` function.
+ *
+ * @package WordPress
+ * @since 6.0.0
+ */
+
+/**
+ * Test retrieve_password(), in wp-includes/user.php.
+ *
+ * @group user
+ *
+ * @covers ::retrieve_password
+ */
+class Tests_User_RetrievePassword extends WP_UnitTestCase {
+       /**
+        * Test user.
+        *
+        * @since 6.0.0
+        *
+        * @var WP_User $user
+        */
+       protected $user;
+
+       public function set_up() {
+               parent::set_up();
+
+               // Create the user.
+               $this->user = self::factory()->user->create_and_get(
+                       array(
+                               'user_login' => 'jane',
+                               'user_email' => 'r.jane@example.com',
+                       )
+               );
+       }
+
+       /**
+        * @ticket 54690
+        */
+       public function test_retrieve_password_reset_notification_email() {
+               $message = 'Sending password reset notification email failed.';
+               $this->assertNotWPError( retrieve_password( $this->user->user_login ), $message );
+       }
+
+       /**
+        * @ticket 54690
+        */
+       public function test_retrieve_password_should_return_wp_error_on_failed_email() {
+               add_filter(
+                       'retrieve_password_notification_email',
+                       static function() {
+                               return array( 'message' => '' );
+                       }
+               );
+
+               $message = 'Sending password reset notification email succeeded.';
+               $this->assertWPError( retrieve_password( $this->user->user_login ), $message );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/user/retrievePassword.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>