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

wp-graphql / wp-graphql / 18790791685

24 Oct 2025 08:03PM UTC coverage: 83.207% (-1.4%) from 84.575%
18790791685

push

github

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

2 of 4 new or added lines in 2 files covered. (50.0%)

189 existing lines in 10 files now uncovered.

16143 of 19401 relevant lines covered (83.21%)

257.79 hits per line

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

36.09
/src/Data/UserMutation.php
1
<?php
2

3
namespace WPGraphQL\Data;
4

5
use Exception;
6
use GraphQL\Error\UserError;
7
use GraphQL\Type\Definition\ResolveInfo;
8
use WPGraphQL\AppContext;
9

10
/**
11
 * Class UserMutation
12
 *
13
 * @package WPGraphQL\Type\User\Mutation
14
 */
15
class UserMutation {
16

17
        /**
18
         * Stores the input fields static definition
19
         *
20
         * @var array<string,array<string,mixed>>
21
         */
22
        private static $input_fields = [];
23

24
        /**
25
         * Defines the accepted input arguments
26
         *
27
         * @return array<string,array<string,mixed>>|null
28
         */
29
        public static function input_fields() {
×
30
                if ( empty( self::$input_fields ) ) {
×
31
                        $input_fields = [
×
32
                                'password'    => [
×
33
                                        'type'        => 'String',
×
34
                                        'description' => static function () {
×
35
                                                return __( 'A string that contains the plain text password for the user.', 'wp-graphql' );
×
36
                                        },
×
37
                                ],
×
38
                                'nicename'    => [
×
39
                                        'type'        => 'String',
×
40
                                        'description' => static function () {
×
41
                                                return __( 'A string that contains a URL-friendly name for the user. The default is the user\'s username.', 'wp-graphql' );
×
42
                                        },
×
43
                                ],
×
44
                                'websiteUrl'  => [
×
45
                                        'type'        => 'String',
×
46
                                        'description' => static function () {
×
47
                                                return __( 'A string containing the user\'s URL for the user\'s web site.', 'wp-graphql' );
×
48
                                        },
×
49
                                ],
×
50
                                'email'       => [
×
51
                                        'type'        => 'String',
×
52
                                        'description' => static function () {
×
53
                                                return __( 'A string containing the user\'s email address.', 'wp-graphql' );
×
54
                                        },
×
55
                                ],
×
56
                                'displayName' => [
×
57
                                        'type'        => 'String',
×
58
                                        'description' => static function () {
×
59
                                                return __( 'A string that will be shown on the site. Defaults to user\'s username. It is likely that you will want to change this, for both appearance and security through obscurity (that is if you dont use and delete the default admin user).', 'wp-graphql' );
×
60
                                        },
×
61
                                ],
×
62
                                'nickname'    => [
×
63
                                        'type'        => 'String',
×
64
                                        'description' => static function () {
×
65
                                                return __( 'The user\'s nickname, defaults to the user\'s username.', 'wp-graphql' );
×
66
                                        },
×
67
                                ],
×
68
                                'firstName'   => [
×
69
                                        'type'        => 'String',
×
70
                                        'description' => static function () {
×
71
                                                return __( 'The user\'s first name.', 'wp-graphql' );
×
72
                                        },
×
73
                                ],
×
74
                                'lastName'    => [
×
75
                                        'type'        => 'String',
×
76
                                        'description' => static function () {
×
77
                                                return __( 'The user\'s last name.', 'wp-graphql' );
×
78
                                        },
×
79
                                ],
×
80
                                'description' => [
×
81
                                        'type'        => 'String',
×
82
                                        'description' => static function () {
×
83
                                                return __( 'A string containing content about the user.', 'wp-graphql' );
×
84
                                        },
×
85
                                ],
×
86
                                'richEditing' => [
×
87
                                        'type'        => 'String',
×
88
                                        'description' => static function () {
×
89
                                                return __( 'A string for whether to enable the rich editor or not. False if not empty.', 'wp-graphql' );
×
90
                                        },
×
91
                                ],
×
92
                                'registered'  => [
×
93
                                        'type'        => 'String',
×
94
                                        'description' => static function () {
×
95
                                                return __( 'The date the user registered. Format is Y-m-d H:i:s.', 'wp-graphql' );
×
96
                                        },
×
97
                                ],
×
98
                                'roles'       => [
×
99
                                        'type'        => [ 'list_of' => 'String' ],
×
100
                                        'description' => static function () {
×
101
                                                return __( 'An array of roles to be assigned to the user.', 'wp-graphql' );
×
102
                                        },
×
103
                                ],
×
104
                                'jabber'      => [
×
105
                                        'type'        => 'String',
×
106
                                        'description' => static function () {
×
107
                                                return __( 'User\'s Jabber account.', 'wp-graphql' );
×
108
                                        },
×
109
                                ],
×
110
                                'aim'         => [
×
111
                                        'type'        => 'String',
×
112
                                        'description' => static function () {
×
113
                                                return __( 'User\'s AOL IM account.', 'wp-graphql' );
×
114
                                        },
×
115
                                ],
×
116
                                'yim'         => [
×
117
                                        'type'        => 'String',
×
118
                                        'description' => static function () {
×
119
                                                return __( 'User\'s Yahoo IM account.', 'wp-graphql' );
×
120
                                        },
×
121
                                ],
×
122
                                'locale'      => [
×
123
                                        'type'        => 'String',
×
124
                                        'description' => static function () {
×
125
                                                return __( 'User\'s locale.', 'wp-graphql' );
×
126
                                        },
×
127
                                ],
×
128
                        ];
×
129

130
                        /**
131
                         * Filters all of the fields available for input
132
                         *
133
                         * @param array<string,array<string,mixed>> $input_fields
134
                         */
135
                        self::$input_fields = apply_filters( 'graphql_user_mutation_input_fields', $input_fields );
×
136
                }
137

138
                return ( ! empty( self::$input_fields ) ) ? self::$input_fields : null;
×
139
        }
140

141
        /**
142
         * Maps the GraphQL input to a format that the WordPress functions can use
143
         *
144
         * @param array<string,mixed> $input         Data coming from the GraphQL mutation query input
145
         * @param string              $mutation_name Name of the mutation being performed
146
         *
147
         * @return array<string,mixed>
148
         * @throws \GraphQL\Error\UserError If the passed email address is invalid.
149
         */
150
        public static function prepare_user_object( $input, $mutation_name ) {
9✔
151
                $insert_user_args = [];
9✔
152

153
                /**
154
                 * Optional fields
155
                 */
156
                if ( isset( $input['nicename'] ) ) {
9✔
157
                        $insert_user_args['user_nicename'] = $input['nicename'];
1✔
158
                }
159

160
                if ( isset( $input['websiteUrl'] ) ) {
9✔
161
                        $insert_user_args['user_url'] = esc_url( $input['websiteUrl'] );
1✔
162
                }
163

164
                if ( isset( $input['displayName'] ) ) {
9✔
165
                        $insert_user_args['display_name'] = $input['displayName'];
1✔
166
                }
167

168
                if ( isset( $input['nickname'] ) ) {
9✔
169
                        $insert_user_args['nickname'] = $input['nickname'];
1✔
170
                }
171

172
                if ( isset( $input['firstName'] ) ) {
9✔
173
                        $insert_user_args['first_name'] = $input['firstName'];
7✔
174
                }
175

176
                if ( isset( $input['lastName'] ) ) {
9✔
177
                        $insert_user_args['last_name'] = $input['lastName'];
7✔
178
                }
179

180
                if ( isset( $input['description'] ) ) {
9✔
181
                        $insert_user_args['description'] = $input['description'];
1✔
182
                }
183

184
                if ( isset( $input['richEditing'] ) ) {
9✔
185
                        $insert_user_args['rich_editing'] = $input['richEditing'];
×
186
                }
187

188
                if ( isset( $input['registered'] ) ) {
9✔
189
                        $insert_user_args['user_registered'] = $input['registered'];
1✔
190
                }
191

192
                if ( isset( $input['locale'] ) ) {
9✔
193
                        $insert_user_args['locale'] = $input['locale'];
1✔
194
                }
195

196
                /**
197
                 * Required fields
198
                 */
199
                if ( ! empty( $input['email'] ) ) {
9✔
200
                        if ( false === is_email( apply_filters( 'pre_user_email', $input['email'] ) ) ) {
7✔
201
                                throw new UserError( esc_html__( 'The email address you are trying to use is invalid', 'wp-graphql' ) );
1✔
202
                        }
203
                        $insert_user_args['user_email'] = $input['email'];
6✔
204
                }
205

206
                if ( ! empty( $input['password'] ) ) {
8✔
207
                        $insert_user_args['user_pass'] = $input['password'];
1✔
208
                } else {
209
                        $insert_user_args['user_pass'] = null;
7✔
210
                }
211

212
                if ( ! empty( $input['username'] ) ) {
8✔
213
                        $insert_user_args['user_login'] = $input['username'];
6✔
214
                }
215

216
                if ( ! empty( $input['roles'] ) ) {
8✔
217
                        /**
218
                         * Pluck the first role out of the array since the insert and update functions only
219
                         * allow one role to be set at a time. We will add all of the roles passed to the
220
                         * mutation later on after the initial object has been created or updated.
221
                         */
222
                        $insert_user_args['role'] = $input['roles'][0];
6✔
223
                }
224

225
                /**
226
                 * Filters the mappings for input to arguments
227
                 *
228
                 * @param array<string,mixed> $insert_user_args The arguments to ultimately be passed to the WordPress function
229
                 * @param array<string,mixed> $input            Input data from the GraphQL mutation
230
                 * @param string              $mutation_name    What user mutation is being performed for context
231
                 */
232
                $insert_user_args = apply_filters( 'graphql_user_insert_post_args', $insert_user_args, $input, $mutation_name );
8✔
233

234
                return $insert_user_args;
8✔
235
        }
236

237
        /**
238
         * This updates additional data related to the user object after the initial mutation has
239
         * happened
240
         *
241
         * @param int                                  $user_id       The ID of the user being mutated
242
         * @param array<string,mixed>                  $input         The input data from the GraphQL query
243
         * @param string                               $mutation_name Name of the mutation currently being run
244
         * @param \WPGraphQL\AppContext                $context The AppContext passed down the resolve tree
245
         * @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo passed down the Resolve Tree
246
         *
247
         * @return void
248
         * @throws \Exception
249
         */
250
        public static function update_additional_user_object_data( $user_id, $input, $mutation_name, AppContext $context, ResolveInfo $info ) {
6✔
251
                $roles = ! empty( $input['roles'] ) ? $input['roles'] : [];
6✔
252
                self::add_user_roles( $user_id, $roles );
6✔
253

254
                /**
255
                 * Run an action after the additional data has been updated. This is a great spot to hook into to
256
                 * update additional data related to users, such as setting relationships, updating additional usermeta,
257
                 * or sending emails to Kevin... whatever you need to do with the userObject.
258
                 *
259
                 * @param int                                  $user_id       The ID of the user being mutated
260
                 * @param array<string,mixed>                  $input         The input for the mutation
261
                 * @param string                               $mutation_name The name of the mutation (ex: create, update, delete)
262
                 * @param \WPGraphQL\AppContext                $context       The AppContext passed down the resolve tree
263
                 * @param \GraphQL\Type\Definition\ResolveInfo $info          The ResolveInfo passed down the Resolve Tree
264
                 */
265
                do_action( 'graphql_user_object_mutation_update_additional_data', $user_id, $input, $mutation_name, $context, $info );
5✔
266
        }
267

268
        /**
269
         * Method to add user roles to a user object
270
         *
271
         * @param int      $user_id The ID of the user
272
         * @param string[] $roles   List of roles that need to get added to the user
273
         *
274
         * @throws \Exception
275
         */
276
        private static function add_user_roles( $user_id, $roles ): void {
6✔
277
                if ( empty( $roles ) || ! is_array( $roles ) || ! current_user_can( 'edit_user', $user_id ) ) {
6✔
278
                        return;
3✔
279
                }
280

281
                $user = get_user_by( 'ID', $user_id );
4✔
282

283
                if ( false !== $user ) {
4✔
284
                        foreach ( $roles as $role ) {
4✔
285
                                $verified = self::verify_user_role( $role, $user_id );
4✔
286

287
                                if ( true === $verified ) {
4✔
288
                                        $user->add_role( $role );
3✔
289
                                } elseif ( is_wp_error( $verified ) ) {
1✔
290
                                        throw new Exception( esc_html( $verified->get_error_message() ) );
1✔
291
                                } elseif ( false === $verified ) {
×
292
                                        // Translators: The placeholder is the name of the user role
293
                                        throw new Exception( esc_html( sprintf( __( 'The %s role cannot be added to this user', 'wp-graphql' ), $role ) ) );
×
294
                                }
295
                        }
296
                }
297
        }
298

299
        /**
300
         * Method to check if the user role is valid, and if the current user has permission to add, or
301
         * remove it from a user.
302
         *
303
         * @param string $role    Name of the role trying to get added to a user object
304
         * @param int    $user_id The ID of the user being mutated
305
         *
306
         * @return true|\WP_Error
307
         */
308
        private static function verify_user_role( $role, $user_id ) {
4✔
309
                global $wp_roles;
4✔
310

311
                $potential_role = isset( $wp_roles->role_objects[ $role ] ) ? $wp_roles->role_objects[ $role ] : '';
4✔
312

313
                if ( empty( $wp_roles->role_objects[ $role ] ) ) {
4✔
314
                        // Translators: The placeholder is the name of the user role
315
                        return new \WP_Error( 'wpgraphql_user_invalid_role', sprintf( __( 'The role %s does not exist', 'wp-graphql' ), $role ) );
1✔
316
                }
317

318
                /*
319
                 * Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
320
                 * Multisite super admins can freely edit their blog roles -- they possess all caps.
321
                 */
322
                if (
323
                        ! ( is_multisite() && current_user_can( 'manage_sites' ) ) &&
3✔
324
                        get_current_user_id() === $user_id &&
3✔
325
                        ! $potential_role->has_cap( 'edit_users' )
3✔
326
                ) {
327
                        return new \WP_Error( 'wpgraphql_user_invalid_role', __( 'Sorry, you cannot remove user editing permissions for your own account.', 'wp-graphql' ) );
×
328
                }
329

330
                /**
331
                 * The function for this is only loaded on admin pages. See note: https://codex.wordpress.org/Function_Reference/get_editable_roles#Notes
332
                 */
333
                if ( ! function_exists( 'get_editable_roles' ) ) {
3✔
UNCOV
334
                        require_once ABSPATH . 'wp-admin/includes/admin.php';
×
335
                }
336

337
                $editable_roles = get_editable_roles();
3✔
338

339
                if ( empty( $editable_roles[ $role ] ) ) {
3✔
340
                        // Translators: %s is the name of the role that can't be added to the user.
UNCOV
341
                        return new \WP_Error( 'wpgraphql_user_invalid_role', sprintf( __( 'Sorry, you are not allowed to give this the following role: %s.', 'wp-graphql' ), $role ) );
×
342
                }
343

344
                return true;
3✔
345
        }
346
}
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