• 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

90.83
/src/Data/Cursor/PostObjectCursor.php
1
<?php
2

3
namespace WPGraphQL\Data\Cursor;
4

5
/**
6
 * Post Cursor
7
 *
8
 * This class generates the SQL AND operators for cursor based pagination for posts
9
 *
10
 * @package WPGraphQL\Data\Cursor
11
 */
12
class PostObjectCursor extends AbstractCursor {
13
        /**
14
         * @var ?\WP_Post
15
         */
16
        public $cursor_node;
17

18
        /**
19
         * Counter for meta value joins
20
         *
21
         * @var int
22
         */
23
        public $meta_join_alias = 0;
24

25
        /**
26
         * {@inheritDoc}
27
         */
28
        public function __construct( $query_vars, $cursor = 'after' ) {
177✔
29
                // Handle deprecated use of $query.
30
                if ( $query_vars instanceof \WP_Query ) {
177✔
31
                        _doing_it_wrong( __METHOD__, 'The first argument should be an array of $query_vars, not the WP_Query object', '1.9.0' );
×
32
                        $query_vars = $query_vars->query_vars;
×
33
                }
34

35
                // Initialize the class properties.
36
                parent::__construct( $query_vars, $cursor );
177✔
37

38
                // Set ID key.
39
                $this->id_key = "{$this->wpdb->posts}.ID";
177✔
40
        }
41

42
        /**
43
         * {@inheritDoc}
44
         *
45
         * @return ?\WP_Post
46
         */
47
        public function get_cursor_node() {
177✔
48
                // Bail if no offset.
49
                if ( ! $this->cursor_offset ) {
177✔
50
                        return null;
173✔
51
                }
52

53
                /**
54
                 * If pre-hooked, return filtered node.
55
                 *
56
                 * @param \WP_Post|null                           $pre_post The pre-filtered post node.
57
                 * @param int                                     $offset   The cursor offset.
58
                 * @param \WPGraphQL\Data\Cursor\PostObjectCursor $node     The cursor instance.
59
                 *
60
                 * @return \WP_Post|null
61
                 */
62
                $pre_post = apply_filters( 'graphql_pre_post_cursor_node', null, $this->cursor_offset, $this );
38✔
63
                if ( null !== $pre_post ) {
38✔
64
                        return $pre_post;
×
65
                }
66

67
                // Get cursor node.
68
                $post = \WP_Post::get_instance( $this->cursor_offset );
38✔
69

70
                return false !== $post ? $post : null;
38✔
71
        }
72

73
        /**
74
         * @deprecated 1.9.0
75
         *
76
         * @return ?\WP_Post
77
         */
78
        public function get_cursor_post() {
×
79
                _deprecated_function( __METHOD__, '1.9.0', self::class . '::get_cursor_node()' );
×
80

81
                return $this->cursor_node;
×
82
        }
83

84
        /**
85
         * {@inheritDoc}
86
         */
87
        public function to_sql() {
38✔
88
                $orderby = isset( $this->query_vars['orderby'] ) ? $this->query_vars['orderby'] : null;
38✔
89

90
                $orderby_should_not_convert_to_sql = isset( $orderby ) && in_array(
38✔
91
                        $orderby,
38✔
92
                        [
38✔
93
                                'post__in',
38✔
94
                                'post_name__in',
38✔
95
                                'post_parent__in',
38✔
96
                        ],
38✔
97
                        true
38✔
98
                );
38✔
99

100
                if ( true === $orderby_should_not_convert_to_sql ) {
38✔
101
                        return '';
4✔
102
                }
103

104
                $sql = $this->builder->to_sql();
34✔
105

106
                if ( empty( $sql ) ) {
34✔
107
                        return '';
×
108
                }
109

110
                return ' AND ' . $sql;
34✔
111
        }
112

113
        /**
114
         * {@inheritDoc}
115
         */
116
        public function get_where() {
38✔
117
                // If we have a bad cursor, just skip.
118
                if ( ! $this->is_valid_offset_and_node() ) {
38✔
119
                        return '';
×
120
                }
121

122
                $orderby = $this->get_query_var( 'orderby' );
38✔
123
                $order   = $this->get_query_var( 'order' );
38✔
124

125
                if ( 'menu_order' === $orderby ) {
38✔
126
                        if ( '>' === $this->compare ) {
3✔
127
                                $order         = 'DESC';
2✔
128
                                $this->compare = '<';
2✔
129
                        } elseif ( '<' === $this->compare ) {
2✔
130
                                $this->compare = '>';
2✔
131
                                $order         = 'ASC';
2✔
132
                        }
133
                }
134

135
                if ( ! empty( $orderby ) && is_array( $orderby ) ) {
38✔
136

137
                        /**
138
                         * Loop through all order keys if it is an array
139
                         */
140
                        foreach ( $orderby as $by => $order ) {
13✔
141
                                $this->compare_with( $by, $order );
13✔
142
                        }
143
                } elseif ( ! empty( $orderby ) && is_string( $orderby ) ) {
25✔
144

145
                        /**
146
                         * If $orderby is just a string just compare with it directly as DESC
147
                         */
148
                        $this->compare_with( $orderby, $order );
17✔
149
                }
150

151
                /**
152
                 * If there's no orderby specified yet, compare with the following fields.
153
                 */
154
                if ( ! $this->builder->has_fields() ) {
38✔
155
                        $this->compare_with_cursor_fields(
13✔
156
                                [
13✔
157
                                        [
13✔
158
                                                'key'   => "{$this->wpdb->posts}.post_date",
13✔
159
                                                'value' => $this->cursor_node ? $this->cursor_node->post_date : null,
13✔
160
                                                'type'  => 'DATETIME',
13✔
161
                                        ],
13✔
162
                                ]
13✔
163
                        );
13✔
164
                }
165

166
                $this->compare_with_id_field();
38✔
167

168
                return $this->to_sql();
38✔
169
        }
170

171
        /**
172
         * Get AND operator for given order by key
173
         *
174
         * @param string $by    The order by key
175
         * @param string $order The order direction ASC or DESC
176
         */
177
        private function compare_with( $by, $order ): void {
30✔
178
                // Bail early, if "key" and "value" provided in query_vars.
179
                $key   = $this->get_query_var( "graphql_cursor_compare_by_{$by}_key" );
30✔
180
                $value = $this->get_query_var( "graphql_cursor_compare_by_{$by}_value" );
30✔
181
                if ( ! empty( $key ) && ! empty( $value ) ) {
30✔
182
                        $this->builder->add_field( $key, $value, null, $order );
×
183
                        return;
×
184
                }
185

186
                /**
187
                 * Find out whether this is a post field
188
                 */
189
                $orderby_post_fields = [
30✔
190
                        'post_author',
30✔
191
                        'post_title',
30✔
192
                        'post_type',
30✔
193
                        'post_name',
30✔
194
                        'post_modified',
30✔
195
                        'post_date',
30✔
196
                        'post_parent',
30✔
197
                        'menu_order',
30✔
198
                ];
30✔
199
                if ( in_array( $by, $orderby_post_fields, true ) ) {
30✔
200
                        $key   = "{$this->wpdb->posts}.{$by}";
15✔
201
                        $value = $this->cursor_node->{$by} ?? null;
15✔
202
                }
203

204
                /**
205
                 * If key or value are null, check whether this is a meta key based ordering before bailing.
206
                 */
207
                if ( null === $key || null === $value ) {
30✔
208
                        $meta_key = $this->get_meta_key( $by );
16✔
209
                        if ( $meta_key ) {
16✔
210
                                $this->compare_with_meta_field( $meta_key, $order );
11✔
211
                        }
212
                        return;
16✔
213
                }
214

215
                // Add field to build.
216
                $this->builder->add_field( $key, $value, null, $order );
15✔
217
        }
218

219
        /**
220
         * Compare with meta key field
221
         *
222
         * @param string $meta_key post meta key
223
         * @param string $order    The comparison string
224
         */
225
        private function compare_with_meta_field( string $meta_key, string $order ): void {
11✔
226
                $meta_type  = $this->get_query_var( 'meta_type' );
11✔
227
                $meta_value = get_post_meta( $this->cursor_offset, $meta_key, true );
11✔
228

229
                $key = "{$this->wpdb->postmeta}.meta_value";
11✔
230

231
                /**
232
                 * WP uses mt1, mt2 etc. style aliases for additional meta value joins.
233
                 */
234
                $meta_query = $this->get_query_var( 'meta_query' );
11✔
235
                if ( ! empty( $meta_query ) && is_array( $meta_query ) ) {
11✔
236
                        if ( ! empty( $meta_query['relation'] ) ) {
4✔
237
                                unset( $meta_query['relation'] );
1✔
238
                        }
239

240
                        $meta_keys = array_column( $meta_query, 'key' );
4✔
241
                        $index     = array_search( $meta_key, $meta_keys, true );
4✔
242

243
                        if ( $index && 1 < count( $meta_query ) ) {
4✔
244
                                $key = "mt{$index}.meta_value";
1✔
245
                        }
246
                }
247

248
                /**
249
                 * Allow filtering the meta key used for cursor based pagination
250
                 *
251
                 * @param string $key       The meta key to use for cursor based pagination
252
                 * @param string $meta_key  The original meta key
253
                 * @param string $meta_type The meta type
254
                 * @param string $order     The order direction
255
                 * @param object $cursor    The PostObjectCursor instance
256
                 */
257
                $key = apply_filters( 'graphql_post_object_cursor_meta_key', $key, $meta_key, $meta_type, $order, $this );
11✔
258

259
                $this->builder->add_field( $key, $meta_value, $meta_type, $order, $this );
11✔
260
        }
261

262
        /**
263
         * Get the actual meta key if any
264
         *
265
         * @param string $by The order by key
266
         *
267
         * @return string|null
268
         */
269
        private function get_meta_key( $by ) {
16✔
270
                if ( 'meta_value' === $by || 'meta_value_num' === $by ) {
16✔
271
                        return $this->get_query_var( 'meta_key' );
8✔
272
                }
273

274
                /**
275
                 * Check for the WP 4.2+ style meta clauses
276
                 * https://make.wordpress.org/core/2015/03/30/query-improvements-in-wp-4-2-orderby-and-meta_query/
277
                 */
278
                if ( ! isset( $this->query_vars['meta_query'][ $by ] ) ) {
8✔
279
                        return null;
5✔
280
                }
281

282
                $clause = $this->query_vars['meta_query'][ $by ];
3✔
283

284
                return empty( $clause['key'] ) ? null : $clause['key'];
3✔
285
        }
286
}
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