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

wp-graphql / wp-graphql / 17562196906

08 Sep 2025 07:44PM UTC coverage: 84.575% (+0.4%) from 84.17%
17562196906

push

github

web-flow
Merge pull request #3389 from wp-graphql/develop

release: next version 📦

238 of 308 new or added lines in 13 files covered. (77.27%)

6 existing lines in 6 files now uncovered.

15884 of 18781 relevant lines covered (84.57%)

261.69 hits per line

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

62.65
/src/Data/Cursor/UserCursor.php
1
<?php
2

3
namespace WPGraphQL\Data\Cursor;
4

5
use WP_User_Query;
6

7
/**
8
 * User Cursor
9
 *
10
 * This class generates the SQL AND operators for cursor based pagination for users
11
 *
12
 * @package WPGraphQL\Data\Cursor
13
 */
14
class UserCursor extends AbstractCursor {
15

16
        /**
17
         * @var ?\WP_User
18
         */
19
        public $cursor_node;
20

21
        /**
22
         * Counter for meta value joins
23
         *
24
         * @var int
25
         */
26
        public $meta_join_alias = 0;
27

28
        /**
29
         * {@inheritDoc}
30
         *
31
         * @param array<string,mixed>|\WP_User_Query $query_vars The query vars to use when building the SQL statement.
32
         */
33
        public function __construct( $query_vars, $cursor = 'after' ) {
71✔
34
                // @todo remove in 3.0.0
35
                if ( $query_vars instanceof WP_User_Query ) {
71✔
NEW
36
                        _doing_it_wrong(
×
NEW
37
                                __METHOD__,
×
NEW
38
                                esc_html__( 'The first argument should be an array of $query_vars, not the WP_Query object. This will throw an error in the next major release', 'wp-graphql' ),
×
NEW
39
                                '1.9.0'
×
NEW
40
                        );
×
UNCOV
41
                        $query_vars = $query_vars->query_vars;
×
42
                }
43

44
                // Initialize the class properties.
45
                parent::__construct( $query_vars, $cursor );
71✔
46

47
                // Set ID key.
48
                $this->id_key = "{$this->wpdb->users}.ID";
71✔
49
        }
50

51
        /**
52
         * {@inheritDoc}
53
         *
54
         * Unlike most queries, users by default are in ascending order.
55
         */
56
        public function get_cursor_compare() {
71✔
57
                if ( 'before' === $this->cursor ) {
71✔
58
                        return '<';
7✔
59
                }
60
                return '>';
71✔
61
        }
62

63
        /**
64
         * {@inheritDoc}
65
         *
66
         * @return ?\WP_User
67
         */
68
        public function get_cursor_node() {
71✔
69
                // Bail if no offset.
70
                if ( ! $this->cursor_offset ) {
71✔
71
                        return null;
71✔
72
                }
73

74
                /**
75
                 * If pre-hooked, return filtered node.
76
                 *
77
                 * @param \WP_User|null                        $pre_user The pre-filtered user node.
78
                 * @param int                                  $offset   The cursor offset.
79
                 * @param \WPGraphQL\Data\Cursor\UserCursor    $node     The cursor instance.
80
                 *
81
                 * @return \WP_User|null
82
                 */
83
                $pre_user = apply_filters( 'graphql_pre_user_cursor_node', null, $this->cursor_offset, $this );
30✔
84
                if ( null !== $pre_user ) {
30✔
85
                        return $pre_user;
×
86
                }
87

88
                // Get cursor node.
89
                $user = get_user_by( 'id', $this->cursor_offset );
30✔
90

91
                return false !== $user ? $user : null;
30✔
92
        }
93

94
        /**
95
         * {@inheritDoc}
96
         */
97
        public function to_sql() {
30✔
98
                return ' AND ' . $this->builder->to_sql();
30✔
99
        }
100

101
        /**
102
         * {@inheritDoc}
103
         */
104
        public function get_where() {
30✔
105
                // If we have a bad cursor, just skip.
106
                if ( ! $this->is_valid_offset_and_node() ) {
30✔
107
                        return '';
×
108
                }
109

110
                $orderby = $this->get_query_var( 'orderby' );
30✔
111
                $order   = $this->get_query_var( 'order' );
30✔
112

113
                if ( ! empty( $orderby ) && is_array( $orderby ) ) {
30✔
114

115
                        /**
116
                         * Loop through all order keys if it is an array
117
                         */
118
                        foreach ( $orderby as $by => $order ) {
×
119
                                $this->compare_with( $by, $order );
×
120
                        }
121
                } elseif ( ! empty( $orderby ) && is_string( $orderby ) && 'login' !== $orderby ) {
30✔
122

123
                        /**
124
                         * If $orderby is just a string just compare with it directly as DESC
125
                         */
126
                        $this->compare_with( $orderby, $order );
9✔
127
                }
128

129
                /**
130
                 * If there's no orderby specified yet, compare with the following fields.
131
                 */
132
                if ( ! $this->builder->has_fields() ) {
30✔
133
                        $this->compare_with_cursor_fields(
21✔
134
                                [
21✔
135
                                        [
21✔
136
                                                'key'   => "{$this->wpdb->users}.user_login",
21✔
137
                                                'value' => $this->cursor_node ? $this->cursor_node->user_login : null,
21✔
138
                                                'type'  => 'CHAR',
21✔
139
                                        ],
21✔
140
                                ]
21✔
141
                        );
21✔
142
                }
143

144
                $this->compare_with_id_field();
30✔
145

146
                return $this->to_sql();
30✔
147
        }
148

149
        /**
150
         * Get AND operator for given order by key
151
         *
152
         * @param string $by    The order by key
153
         * @param string $order The order direction ASC or DESC
154
         */
155
        private function compare_with( $by, $order ): void {
9✔
156
                // Bail early, if "key" and "value" provided in query_vars.
157
                $key   = $this->get_query_var( "graphql_cursor_compare_by_{$by}_key" );
9✔
158
                $value = $this->get_query_var( "graphql_cursor_compare_by_{$by}_value" );
9✔
159
                if ( ! empty( $key ) && ! empty( $value ) ) {
9✔
160
                        $this->builder->add_field( $key, $value, null, $order );
×
161
                        return;
×
162
                }
163

164
                /**
165
                 * Find out whether this is a user field
166
                 */
167
                $orderby_user_fields = [
9✔
168
                        'user_email',
9✔
169
                        'user_login',
9✔
170
                        'user_nicename',
9✔
171
                        'user_registered',
9✔
172
                        'user_url',
9✔
173
                ];
9✔
174
                if ( in_array( $by, $orderby_user_fields, true ) ) {
9✔
175
                        $key   = "{$this->wpdb->users}.{$by}";
9✔
176
                        $value = $this->cursor_node->{$by} ?? null;
9✔
177
                }
178

179
                /**
180
                 * If key or value are null, check whether this is a meta key based ordering before bailing.
181
                 */
182
                if ( null === $key || null === $value ) {
9✔
183
                        $meta_key = $this->get_meta_key( $by );
×
184
                        if ( $meta_key ) {
×
185
                                $this->compare_with_meta_field( $meta_key, $order );
×
186
                        }
187
                        return;
×
188
                }
189

190
                // Add field to build.
191
                $this->builder->add_field( $key, $value, null, $order );
9✔
192
        }
193

194
        /**
195
         * Compare with meta key field
196
         *
197
         * @param string $meta_key user meta key
198
         * @param string $order    The comparison string
199
         */
200
        private function compare_with_meta_field( string $meta_key, string $order ): void {
×
201
                $meta_type  = $this->get_query_var( 'meta_type' );
×
202
                $meta_value = get_user_meta( $this->cursor_offset, $meta_key, true );
×
203

204
                $key = "{$this->wpdb->usermeta}.meta_value";
×
205

206
                /**
207
                 * WP uses mt1, mt2 etc. style aliases for additional meta value joins.
208
                 */
209
                if ( 0 !== $this->meta_join_alias ) {
×
210
                        $key = "mt{$this->meta_join_alias}.meta_value";
×
211
                }
212

213
                ++$this->meta_join_alias;
×
214

215
                $this->builder->add_field( $key, $meta_value, $meta_type, $order );
×
216
        }
217

218
        /**
219
         * Get the actual meta key if any
220
         *
221
         * @param string $by The order by key
222
         *
223
         * @return string|null
224
         */
225
        private function get_meta_key( $by ) {
×
226
                if ( 'meta_value' === $by ) {
×
227
                        return $this->get_query_var( 'meta_key' );
×
228
                }
229

230
                /**
231
                 * Check for the WP 4.2+ style meta clauses
232
                 * https://make.wordpress.org/core/2015/03/30/query-improvements-in-wp-4-2-orderby-and-meta_query/
233
                 */
234
                if ( ! isset( $this->query_vars['meta_query'][ $by ] ) ) {
×
235
                        return null;
×
236
                }
237

238
                $clause = $this->query_vars['meta_query'][ $by ];
×
239

240
                return empty( $clause['key'] ) ? null : $clause['key'];
×
241
        }
242

243
        /**
244
         * @todo remove in 3.0.0
245
         * @deprecated 1.9.0
246
         * @codeCoverageIgnore
247
         *
248
         * @return ?\WP_User
249
         */
250
        public function get_cursor_user() {
251
                _doing_it_wrong(
252
                        __METHOD__,
253
                        sprintf(
254
                                // translators: %s is the method name
255
                                esc_html__( 'This method will be removed in the next major release. Use %s instead.', 'wp-graphql' ),
256
                                self::class . '::get_cursor_node()'
257
                        ),
258
                        '1.9.0'
259
                );
260

261
                return $this->cursor_node;
262
        }
263
}
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