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

wp-graphql / wp-graphql / 14716683875

28 Apr 2025 07:58PM UTC coverage: 84.287% (+1.6%) from 82.648%
14716683875

push

github

actions-user
release: merge develop into master for v2.3.0

15905 of 18870 relevant lines covered (84.29%)

257.23 hits per line

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

92.52
/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'         => static function () {
593✔
21
                                        return __( 'Send password reset email to user', 'wp-graphql' );
69✔
22
                                },
593✔
23
                                'inputFields'         => self::get_input_fields(),
593✔
24
                                'outputFields'        => self::get_output_fields(),
593✔
25
                                'mutateAndGetPayload' => self::mutate_and_get_payload(),
593✔
26
                        ]
593✔
27
                );
593✔
28
        }
29

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

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

76
        /**
77
         * Defines the mutation data modification closure.
78
         *
79
         * @return callable(array<string,mixed>$input,\WPGraphQL\AppContext $context,\GraphQL\Type\Definition\ResolveInfo $info):array<string,mixed>
80
         */
81
        public static function mutate_and_get_payload(): callable {
593✔
82
                return static function ( $input ) {
593✔
83
                        if ( ! self::was_username_provided( $input ) ) {
7✔
84
                                throw new UserError( esc_html__( 'Enter a username or email address.', 'wp-graphql' ) );
×
85
                        }
86

87
                        // We obsfucate the actual success of this mutation to prevent user enumeration.
88
                        $payload = [
7✔
89
                                'success' => true,
7✔
90
                                'id'      => null,
7✔
91
                        ];
7✔
92

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

95
                        if ( ! $user_data ) {
7✔
96
                                graphql_debug( self::get_user_not_found_error_message( $input['username'] ) );
1✔
97

98
                                return $payload;
1✔
99
                        }
100

101
                        // Get the password reset key.
102
                        $key = get_password_reset_key( $user_data );
6✔
103
                        if ( is_wp_error( $key ) ) {
6✔
104
                                graphql_debug( __( 'Unable to generate a password reset key.', 'wp-graphql' ) );
×
105

106
                                return $payload;
×
107
                        }
108

109
                        // Mail the reset key.
110
                        $subject = self::get_email_subject( $user_data );
6✔
111
                        $message = self::get_email_message( $user_data, $key );
6✔
112

113
                        $email_sent = wp_mail( // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_mail_wp_mail
6✔
114
                                $user_data->user_email,
6✔
115
                                wp_specialchars_decode( $subject ),
6✔
116
                                $message
6✔
117
                        );
6✔
118

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

123
                                return $payload;
×
124
                        }
125

126
                        /**
127
                         * Return the ID of the user
128
                         */
129
                        return [
6✔
130
                                'id'      => $user_data->ID,
6✔
131
                                'success' => true,
6✔
132
                        ];
6✔
133
                };
593✔
134
        }
135

136
        /**
137
         * Was a username or email address provided?
138
         *
139
         * @param array<string,mixed> $input The input args.
140
         */
141
        private static function was_username_provided( $input ): bool {
7✔
142
                return ! empty( $input['username'] ) && is_string( $input['username'] );
7✔
143
        }
144

145
        /**
146
         * Get WP_User object representing this user
147
         *
148
         * @param string $username The user's username or email address.
149
         *
150
         * @return \WP_User|false WP_User object on success, false on failure.
151
         */
152
        private static function get_user_data( $username ) {
7✔
153
                if ( self::is_email_address( $username ) ) {
7✔
154
                        $username = wp_unslash( $username );
3✔
155

156
                        if ( ! is_string( $username ) ) {
3✔
157
                                return false;
×
158
                        }
159

160
                        return get_user_by( 'email', trim( $username ) );
3✔
161
                }
162

163
                return get_user_by( 'login', trim( $username ) );
4✔
164
        }
165

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

176
                return __( 'Invalid username.', 'wp-graphql' );
1✔
177
        }
178

179
        /**
180
         * Is the provided username arg an email address?
181
         *
182
         * @param string $username The user's username or email address.
183
         */
184
        private static function is_email_address( string $username ): bool {
7✔
185
                return (bool) strpos( $username, '@' );
7✔
186
        }
187

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

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

207
        /**
208
         * Get the site name.
209
         */
210
        private static function get_site_name(): string {
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( (string) 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
        private static function get_email_message( $user_data, $key ): string {
6✔
233
                $message = __( 'Someone has requested a password reset for the following account:', 'wp-graphql' ) . "\r\n\r\n";
6✔
234
                /* translators: %s: site name */
235
                $message .= sprintf( __( 'Site Name: %s', 'wp-graphql' ), self::get_site_name() ) . "\r\n\r\n";
6✔
236
                /* translators: %s: user login */
237
                $message .= sprintf( __( 'Username: %s', 'wp-graphql' ), $user_data->user_login ) . "\r\n\r\n";
6✔
238
                $message .= __( 'If this was a mistake, just ignore this email and nothing will happen.', 'wp-graphql' ) . "\r\n\r\n";
6✔
239
                $message .= __( 'To reset your password, visit the following address:', 'wp-graphql' ) . "\r\n\r\n";
6✔
240
                $message .= '<' . network_site_url( "wp-login.php?action=rp&key={$key}&login=" . rawurlencode( $user_data->user_login ), 'login' ) . ">\r\n";
6✔
241

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

© 2025 Coveralls, Inc