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

wp-graphql / wp-graphql / 13316763745

13 Feb 2025 08:45PM UTC coverage: 82.712% (-0.3%) from 83.023%
13316763745

push

github

web-flow
Merge pull request #3307 from wp-graphql/release/v2.0.0

release: v2.0.0

195 of 270 new or added lines in 20 files covered. (72.22%)

180 existing lines in 42 files now uncovered.

13836 of 16728 relevant lines covered (82.71%)

299.8 hits per line

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

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

3
namespace WPGraphQL\Mutation;
4

5
use GraphQL\Error\UserError;
6
use WPGraphQL\AppContext;
7

8
class SendPasswordResetEmail {
9

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

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

44
        /**
45
         * Defines the mutation output field configuration.
46
         *
47
         * @return array<string,array<string,mixed>>
48
         */
49
        public static function get_output_fields(): array {
593✔
50
                return [
593✔
51
                        'user'    => [
593✔
52
                                'type'              => 'User',
593✔
53
                                'description'       => __( 'The user that the password reset email was sent to', 'wp-graphql' ),
593✔
54
                                'deprecationReason' => __( 'This field will be removed in a future version of WPGraphQL', 'wp-graphql' ),
593✔
55
                                'resolve'           => static function ( $payload, $args, AppContext $context ) {
593✔
56
                                        return ! empty( $payload['id'] ) ? $context->get_loader( 'user' )->load_deferred( $payload['id'] ) : null;
7✔
57
                                },
593✔
58
                        ],
593✔
59
                        'success' => [
593✔
60
                                'type'        => 'Boolean',
593✔
61
                                'description' => __( 'Whether the mutation completed successfully. This does NOT necessarily mean that an email was sent.', 'wp-graphql' ),
593✔
62
                        ],
593✔
63
                ];
593✔
64
        }
65

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

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

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

85
                        if ( ! $user_data ) {
7✔
86
                                graphql_debug( self::get_user_not_found_error_message( $input['username'] ) );
1✔
87

88
                                return $payload;
1✔
89
                        }
90

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

96
                                return $payload;
×
97
                        }
98

99
                        // Mail the reset key.
100
                        $subject = self::get_email_subject( $user_data );
6✔
101
                        $message = self::get_email_message( $user_data, $key );
6✔
102

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

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

113
                                return $payload;
×
114
                        }
115

116
                        /**
117
                         * Return the ID of the user
118
                         */
119
                        return [
6✔
120
                                'id'      => $user_data->ID,
6✔
121
                                'success' => true,
6✔
122
                        ];
6✔
123
                };
593✔
124
        }
125

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

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

148
                        if ( ! is_string( $username ) ) {
3✔
149
                                return false;
×
150
                        }
151

152
                        return get_user_by( 'email', trim( $username ) );
3✔
153
                }
154

155
                return get_user_by( 'login', trim( $username ) );
4✔
156
        }
157

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

170
                return __( 'Invalid username.', 'wp-graphql' );
1✔
171
        }
172

173
        /**
174
         * Is the provided username arg an email address?
175
         *
176
         * @param string $username The user's username or email address.
177
         *
178
         * @return bool
179
         */
180
        private static function is_email_address( string $username ) {
7✔
181
                return (bool) strpos( $username, '@' );
7✔
182
        }
183

184
        /**
185
         * Get the subject of the password reset email
186
         *
187
         * @param \WP_User $user_data User data
188
         *
189
         * @return string
190
         */
191
        private static function get_email_subject( $user_data ) {
6✔
192
                /* translators: Password reset email subject. %s: Site name */
193
                $title = sprintf( __( '[%s] Password Reset', 'wp-graphql' ), self::get_site_name() );
6✔
194

195
                /**
196
                 * Filters the subject of the password reset email.
197
                 *
198
                 * @param string   $title      Default email title.
199
                 * @param string   $user_login The username for the user.
200
                 * @param \WP_User $user_data WP_User object.
201
                 */
202
                return apply_filters( 'retrieve_password_title', $title, $user_data->user_login, $user_data );
6✔
203
        }
204

205
        /**
206
         * Get the site name.
207
         *
208
         * @return string
209
         */
210
        private static function get_site_name() {
6✔
211
                if ( is_multisite() ) {
6✔
212
                        $network = get_network();
6✔
213
                        if ( isset( $network->site_name ) ) {
6✔
214
                                return $network->site_name;
6✔
215
                        }
216
                }
217

218
                /*
219
                * The blogname option is escaped with esc_html on the way into the database
220
                * in sanitize_option we want to reverse this for the plain text arena of emails.
221
                */
222

223
                return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
×
224
        }
225

226
        /**
227
         * Get the message body of the password reset email
228
         *
229
         * @param \WP_User $user_data User data
230
         * @param string   $key       Password reset key
231
         *
232
         * @return string
233
         */
234
        private static function get_email_message( $user_data, $key ) {
6✔
235
                $message = __( 'Someone has requested a password reset for the following account:', 'wp-graphql' ) . "\r\n\r\n";
6✔
236
                /* translators: %s: site name */
237
                $message .= sprintf( __( 'Site Name: %s', 'wp-graphql' ), self::get_site_name() ) . "\r\n\r\n";
6✔
238
                /* translators: %s: user login */
239
                $message .= sprintf( __( 'Username: %s', 'wp-graphql' ), $user_data->user_login ) . "\r\n\r\n";
6✔
240
                $message .= __( 'If this was a mistake, just ignore this email and nothing will happen.', 'wp-graphql' ) . "\r\n\r\n";
6✔
241
                $message .= __( 'To reset your password, visit the following address:', 'wp-graphql' ) . "\r\n\r\n";
6✔
242
                $message .= '<' . network_site_url( "wp-login.php?action=rp&key={$key}&login=" . rawurlencode( $user_data->user_login ), 'login' ) . ">\r\n";
6✔
243

244
                /**
245
                 * Filters the message body of the password reset mail.
246
                 *
247
                 * If the filtered message is empty, the password reset email will not be sent.
248
                 *
249
                 * @param string   $message    Default mail message.
250
                 * @param string   $key        The activation key.
251
                 * @param string   $user_login The username for the user.
252
                 * @param \WP_User $user_data WP_User object.
253
                 */
254
                return apply_filters( 'retrieve_password_message', $message, $key, $user_data->user_login, $user_data );
6✔
255
        }
256
}
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