• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql / 17334288077

29 Aug 2025 09:07PM UTC coverage: 84.593% (+0.4%) from 84.169%
17334288077

push

github

actions-user
chore: update changeset for PR #3410

15884 of 18777 relevant lines covered (84.59%)

260.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.58
/src/Mutation/SendPasswordResetEmail.php
1
<?php
2

3
namespace WPGraphQL\Mutation;
4

5
use GraphQL\Error\UserError;
6

7
class SendPasswordResetEmail {
8

9
        /**
10
         * Registers the sendPasswordResetEmail Mutation
11
         *
12
         * @return void
13
         * @throws \Exception
14
         */
15
        public static function register_mutation() {
601✔
16
                register_graphql_mutation(
601✔
17
                        'sendPasswordResetEmail',
601✔
18
                        [
601✔
19
                                'description'         => static function () {
601✔
20
                                        return __( 'Send password reset email to user', 'wp-graphql' );
68✔
21
                                },
601✔
22
                                'inputFields'         => self::get_input_fields(),
601✔
23
                                'outputFields'        => self::get_output_fields(),
601✔
24
                                'mutateAndGetPayload' => self::mutate_and_get_payload(),
601✔
25
                        ]
601✔
26
                );
601✔
27
        }
28

29
        /**
30
         * Defines the mutation input field configuration.
31
         *
32
         * @return array<string,array<string,mixed>>
33
         */
34
        public static function get_input_fields(): array {
601✔
35
                return [
601✔
36
                        'username' => [
601✔
37
                                'type'        => [
601✔
38
                                        'non_null' => 'String',
601✔
39
                                ],
601✔
40
                                'description' => static function () {
601✔
41
                                        return __( 'A string that contains the user\'s username or email address.', 'wp-graphql' );
15✔
42
                                },
601✔
43
                        ],
601✔
44
                ];
601✔
45
        }
46

47
        /**
48
         * Defines the mutation output field configuration.
49
         *
50
         * @return array<string,array<string,mixed>>
51
         */
52
        public static function get_output_fields(): array {
601✔
53
                return [
601✔
54
                        'success' => [
601✔
55
                                'type'        => 'Boolean',
601✔
56
                                'description' => static function () {
601✔
57
                                        return __( 'Whether the mutation completed successfully. This does NOT necessarily mean that an email was sent.', 'wp-graphql' );
15✔
58
                                },
601✔
59
                        ],
601✔
60
                ];
601✔
61
        }
62

63
        /**
64
         * Defines the mutation data modification closure.
65
         *
66
         * @return callable(array<string,mixed>$input,\WPGraphQL\AppContext $context,\GraphQL\Type\Definition\ResolveInfo $info):array<string,mixed>
67
         */
68
        public static function mutate_and_get_payload(): callable {
601✔
69
                return static function ( $input ) {
601✔
70
                        if ( ! self::was_username_provided( $input ) ) {
7✔
71
                                throw new UserError( esc_html__( 'Enter a username or email address.', 'wp-graphql' ) );
×
72
                        }
73

74
                        // We obsfucate the actual success of this mutation to prevent user enumeration.
75
                        $payload = [
7✔
76
                                'success' => true,
7✔
77
                                'id'      => null,
7✔
78
                        ];
7✔
79

80
                        $user_data = self::get_user_data( $input['username'] );
7✔
81

82
                        if ( ! $user_data ) {
7✔
83
                                graphql_debug( self::get_user_not_found_error_message( $input['username'] ) );
1✔
84

85
                                return $payload;
1✔
86
                        }
87

88
                        // Get the password reset key.
89
                        $key = get_password_reset_key( $user_data );
6✔
90
                        if ( is_wp_error( $key ) ) {
6✔
91
                                graphql_debug( __( 'Unable to generate a password reset key.', 'wp-graphql' ) );
×
92

93
                                return $payload;
×
94
                        }
95

96
                        // Mail the reset key.
97
                        $subject = self::get_email_subject( $user_data );
6✔
98
                        $message = self::get_email_message( $user_data, $key );
6✔
99

100
                        $email_sent = wp_mail( // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_mail_wp_mail
6✔
101
                                $user_data->user_email,
6✔
102
                                wp_specialchars_decode( $subject ),
6✔
103
                                $message
6✔
104
                        );
6✔
105

106
                        // wp_mail can return a wp_error, but the docblock for it in WP Core is incorrect.
107
                        if ( is_wp_error( $email_sent ) ) {
6✔
108
                                graphql_debug( __( 'The email could not be sent.', 'wp-graphql' ) . "<br />\n" . __( 'Possible reason: your host may have disabled the mail() function.', 'wp-graphql' ) );
×
109

110
                                return $payload;
×
111
                        }
112

113
                        /**
114
                         * Return the ID of the user
115
                         */
116
                        return [
6✔
117
                                'id'      => $user_data->ID,
6✔
118
                                'success' => true,
6✔
119
                        ];
6✔
120
                };
601✔
121
        }
122

123
        /**
124
         * Was a username or email address provided?
125
         *
126
         * @param array<string,mixed> $input The input args.
127
         */
128
        private static function was_username_provided( $input ): bool {
7✔
129
                return ! empty( $input['username'] ) && is_string( $input['username'] );
7✔
130
        }
131

132
        /**
133
         * Get WP_User object representing this user
134
         *
135
         * @param string $username The user's username or email address.
136
         *
137
         * @return \WP_User|false WP_User object on success, false on failure.
138
         */
139
        private static function get_user_data( $username ) {
7✔
140
                if ( self::is_email_address( $username ) ) {
7✔
141
                        $username = wp_unslash( $username );
3✔
142

143
                        if ( ! is_string( $username ) ) {
3✔
144
                                return false;
×
145
                        }
146

147
                        return get_user_by( 'email', trim( $username ) );
3✔
148
                }
149

150
                return get_user_by( 'login', trim( $username ) );
4✔
151
        }
152

153
        /**
154
         * Get the error message indicating why the user wasn't found
155
         *
156
         * @param string $username The user's username or email address.
157
         */
158
        private static function get_user_not_found_error_message( string $username ): string {
1✔
159
                if ( self::is_email_address( $username ) ) {
1✔
160
                        return __( 'There is no user registered with that email address.', 'wp-graphql' );
×
161
                }
162

163
                return __( 'Invalid username.', 'wp-graphql' );
1✔
164
        }
165

166
        /**
167
         * Is the provided username arg an email address?
168
         *
169
         * @param string $username The user's username or email address.
170
         */
171
        private static function is_email_address( string $username ): bool {
7✔
172
                return (bool) strpos( $username, '@' );
7✔
173
        }
174

175
        /**
176
         * Get the subject of the password reset email
177
         *
178
         * @param \WP_User $user_data User data
179
         */
180
        private static function get_email_subject( $user_data ): string {
6✔
181
                /* translators: Password reset email subject. %s: Site name */
182
                $title = sprintf( __( '[%s] Password Reset', 'wp-graphql' ), self::get_site_name() );
6✔
183

184
                /**
185
                 * Filters the subject of the password reset email.
186
                 *
187
                 * @param string   $title      Default email title.
188
                 * @param string   $user_login The username for the user.
189
                 * @param \WP_User $user_data WP_User object.
190
                 */
191
                return (string) apply_filters( 'retrieve_password_title', $title, $user_data->user_login, $user_data );
6✔
192
        }
193

194
        /**
195
         * Get the site name.
196
         */
197
        private static function get_site_name(): string {
6✔
198
                if ( is_multisite() ) {
6✔
199
                        $network = get_network();
6✔
200
                        if ( isset( $network->site_name ) ) {
6✔
201
                                return $network->site_name;
6✔
202
                        }
203
                }
204

205
                /*
206
                * The blogname option is escaped with esc_html on the way into the database
207
                * in sanitize_option we want to reverse this for the plain text arena of emails.
208
                */
209

210
                return wp_specialchars_decode( (string) get_option( 'blogname' ), ENT_QUOTES );
×
211
        }
212

213
        /**
214
         * Get the message body of the password reset email
215
         *
216
         * @param \WP_User $user_data User data
217
         * @param string   $key       Password reset key
218
         */
219
        private static function get_email_message( $user_data, $key ): string {
6✔
220
                $message = __( 'Someone has requested a password reset for the following account:', 'wp-graphql' ) . "\r\n\r\n";
6✔
221
                /* translators: %s: site name */
222
                $message .= sprintf( __( 'Site Name: %s', 'wp-graphql' ), self::get_site_name() ) . "\r\n\r\n";
6✔
223
                /* translators: %s: user login */
224
                $message .= sprintf( __( 'Username: %s', 'wp-graphql' ), $user_data->user_login ) . "\r\n\r\n";
6✔
225
                $message .= __( 'If this was a mistake, just ignore this email and nothing will happen.', 'wp-graphql' ) . "\r\n\r\n";
6✔
226
                $message .= __( 'To reset your password, visit the following address:', 'wp-graphql' ) . "\r\n\r\n";
6✔
227
                $message .= '<' . network_site_url( "wp-login.php?action=rp&key={$key}&login=" . rawurlencode( $user_data->user_login ), 'login' ) . ">\r\n";
6✔
228

229
                /**
230
                 * Filters the message body of the password reset mail.
231
                 *
232
                 * If the filtered message is empty, the password reset email will not be sent.
233
                 *
234
                 * @param string   $message    Default mail message.
235
                 * @param string   $key        The activation key.
236
                 * @param string   $user_login The username for the user.
237
                 * @param \WP_User $user_data WP_User object.
238
                 */
239
                return (string) apply_filters( 'retrieve_password_message', $message, $key, $user_data->user_login, $user_data );
6✔
240
        }
241
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc