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

wp-graphql / wp-graphql / 19651653124

24 Nov 2025 10:39PM UTC coverage: 83.619% (+0.007%) from 83.612%
19651653124

push

github

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

16243 of 19425 relevant lines covered (83.62%)

261.09 hits per line

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

87.97
/src/Model/Post.php
1
<?php
2
/**
3
 * Model - PostObject
4
 *
5
 * @package WPGraphQL\Model
6
 */
7

8
namespace WPGraphQL\Model;
9

10
use GraphQLRelay\Relay;
11
use WPGraphQL\Utils\Utils;
12
use WP_Post;
13

14
/**
15
 * Class Post - Models data for the Post object type
16
 *
17
 * @property ?int          $authorDatabaseId
18
 * @property ?string       $authorId
19
 * @property int           $commentCount
20
 * @property ?string       $commentStatus
21
 * @property ?string       $contentRaw
22
 * @property ?string       $contentRendered
23
 * @property ?int          $databaseId
24
 * @property ?string       $date
25
 * @property ?string       $dateGmt
26
 * @property ?int          $editLastId
27
 * @property string[]|null $editLock
28
 * @property ?string       $enclosure
29
 * @property ?string       $excerptRaw
30
 * @property ?string       $excerptRendered
31
 * @property ?int          $featuredImageDatabaseId
32
 * @property ?string       $featuredImageId
33
 * @property ?string       $guid
34
 * @property bool          $hasPassword
35
 * @property ?string       $id
36
 * @property bool          $isFrontPage
37
 * @property bool          $isPostsPage
38
 * @property bool          $isPreview
39
 * @property bool          $isPrivacyPage
40
 * @property bool          $isRevision
41
 * @property bool          $isSticky
42
 * @property ?string       $link
43
 * @property ?int          $menuOrder
44
 * @property ?string       $modified
45
 * @property ?string       $modifiedGmt
46
 * @property ?string       $pageTemplate
47
 * @property ?int          $parentDatabaseId
48
 * @property ?string       $parentId
49
 * @property ?string       $password
50
 * @property ?string       $pinged
51
 * @property ?string       $pingStatus
52
 * @property ?string       $post_type
53
 * @property int           $previewRevisionDatabaseId
54
 * @property ?string       $slug
55
 * @property ?string       $status
56
 * @property array{
57
 *  __typename: string,
58
 *  templateName: string
59
 * }                       $template
60
 * @property ?string       $titleRaw
61
 * @property ?string       $titleRendered
62
 * @property ?string       $toPing
63
 * @property ?string       $uri
64
 *
65
 * Attachment specific fields:
66
 * @property string|null              $altText
67
 * @property string|null              $captionRaw
68
 * @property string|null              $captionRendered
69
 * @property string|null              $descriptionRaw
70
 * @property string|null              $descriptionRendered
71
 * @property array<string,mixed>|null $mediaDetails
72
 * @property string|null              $mediaItemUrl
73
 * @property string|null              $mediaType
74
 * @property string|null              $mimeType
75
 * @property string|null              $sourceUrl
76
 *
77
 * Aliases:
78
 * @property ?int    $ID
79
 * @property ?int    $post_author
80
 * @property ?string $post_status
81
 *
82
 * @extends \WPGraphQL\Model\Model<\WP_Post>
83
 */
84
class Post extends Model {
85
        /**
86
         * Store the global post to reset during model tear down
87
         *
88
         * @var \WP_Post
89
         */
90
        protected $global_post;
91

92
        /**
93
         * Stores the incoming post type object for the post being modeled
94
         *
95
         * @var \WP_Post_Type|null $post_type_object
96
         */
97
        protected $post_type_object;
98

99
        /**
100
         * Store the instance of the WP_Query
101
         *
102
         * @var \WP_Query
103
         */
104
        protected $wp_query;
105

106
        /**
107
         * Stores the resolved image `sourceUrl`s keyed by size.
108
         *
109
         * This is used to prevent multiple calls to `wp_get_attachment_image_src`.
110
         *
111
         * If no source URL is found for a size, the value will be `null`.
112
         *
113
         * @var array<string,?string>
114
         */
115
        protected $source_urls_by_size = [];
116

117
        /**
118
         * Post constructor.
119
         *
120
         * @param \WP_Post $post The incoming WP_Post object that needs modeling.
121
         *
122
         * @return void
123
         */
124
        public function __construct( WP_Post $post ) {
332✔
125

126
                /**
127
                 * Set the data as the Post object
128
                 */
129
                $this->data             = $post;
332✔
130
                $this->post_type_object = get_post_type_object( $post->post_type );
332✔
131

132
                /**
133
                 * If the post type is 'revision', we need to get the post_type_object
134
                 * of the parent post type to determine capabilities from
135
                 */
136
                if ( 'revision' === $post->post_type && ! empty( $post->post_parent ) ) {
332✔
137
                        $parent = get_post( absint( $post->post_parent ) );
18✔
138
                        if ( ! empty( $parent ) ) {
18✔
139
                                $this->post_type_object = get_post_type_object( $parent->post_type );
18✔
140
                        }
141
                }
142

143
                /**
144
                 * Mimic core functionality for templates, as seen here:
145
                 * https://github.com/WordPress/WordPress/blob/6fd8080e7ee7599b36d4528f72a8ced612130b8c/wp-includes/template-loader.php#L56
146
                 */
147
                if ( 'attachment' === $this->data->post_type ) {
332✔
148
                        remove_filter( 'the_content', 'prepend_attachment' );
48✔
149
                }
150

151
                $allowed_restricted_fields = [
332✔
152
                        'databaseId',
332✔
153
                        'enqueuedScriptsQueue',
332✔
154
                        'enqueuedStylesheetsQueue',
332✔
155
                        'hasPassword',
332✔
156
                        'id',
332✔
157
                        'isFrontPage',
332✔
158
                        'isPostsPage',
332✔
159
                        'isPrivacyPage',
332✔
160
                        'isRestricted',
332✔
161
                        'link',
332✔
162
                        'post_status',
332✔
163
                        'post_type',
332✔
164
                        'slug',
332✔
165
                        'status',
332✔
166
                        'titleRendered',
332✔
167
                        'uri',
332✔
168
                ];
332✔
169

170
                if ( isset( $this->post_type_object->graphql_single_name ) ) {
332✔
171
                        $allowed_restricted_fields[] = $this->post_type_object->graphql_single_name . 'Id';
332✔
172
                }
173

174
                $restricted_cap = $this->get_restricted_cap();
332✔
175

176
                parent::__construct( $restricted_cap, $allowed_restricted_fields, (int) $post->post_author );
332✔
177
        }
178

179
        /**
180
         * {@inheritDoc}
181
         */
182
        public function setup() {
301✔
183
                global $wp_query, $post;
301✔
184

185
                /**
186
                 * Store the global post before overriding
187
                 */
188
                $this->global_post = $post;
301✔
189

190
                /**
191
                 * Set the resolving post to the global $post. That way any filters that
192
                 * might be applied when resolving fields can rely on global post and
193
                 * post data being set up.
194
                 */
195
                if ( $this->data instanceof WP_Post ) {
301✔
196
                        $id        = $this->data->ID;
301✔
197
                        $post_type = $this->data->post_type;
301✔
198
                        $post_name = $this->data->post_name;
301✔
199
                        $data      = $this->data;
301✔
200

201
                        if ( 'revision' === $this->data->post_type ) {
301✔
202
                                $id     = $this->data->post_parent;
17✔
203
                                $parent = get_post( $this->data->post_parent );
17✔
204
                                if ( empty( $parent ) ) {
17✔
205
                                        $this->fields = [];
×
206
                                        return;
×
207
                                }
208
                                $post_type = $parent->post_type;
17✔
209
                                $post_name = $parent->post_name;
17✔
210
                                $data      = $parent;
17✔
211
                        }
212

213
                        /**
214
                         * Clear out existing postdata
215
                         */
216
                        $wp_query->reset_postdata();
301✔
217

218
                        /**
219
                         * Parse the query to tell WordPress how to
220
                         * setup global state
221
                         */
222
                        if ( 'post' === $post_type ) {
301✔
223
                                $wp_query->parse_query(
210✔
224
                                        [
210✔
225
                                                'page' => '',
210✔
226
                                                'p'    => $id,
210✔
227
                                        ]
210✔
228
                                );
210✔
229
                        } elseif ( 'page' === $post_type ) {
149✔
230
                                $wp_query->parse_query(
76✔
231
                                        [
76✔
232
                                                'page'     => '',
76✔
233
                                                'pagename' => $post_name,
76✔
234
                                        ]
76✔
235
                                );
76✔
236
                        } elseif ( 'attachment' === $post_type ) {
81✔
237
                                $wp_query->parse_query(
47✔
238
                                        [
47✔
239
                                                'attachment' => $post_name,
47✔
240
                                        ]
47✔
241
                                );
47✔
242
                        } else {
243
                                $wp_query->parse_query(
34✔
244
                                        [
34✔
245
                                                $post_type  => $post_name,
34✔
246
                                                'post_type' => $post_type,
34✔
247
                                                'name'      => $post_name,
34✔
248
                                        ]
34✔
249
                                );
34✔
250
                        }
251

252
                        $wp_query->setup_postdata( $data );
301✔
253
                        $GLOBALS['post']             = $data; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
301✔
254
                        $wp_query->queried_object    = get_post( $this->data->ID );
301✔
255
                        $wp_query->queried_object_id = $this->data->ID;
301✔
256
                }
257
        }
258

259
        /**
260
         * Retrieve the cap to check if the data should be restricted for the post
261
         *
262
         * @return string
263
         */
264
        protected function get_restricted_cap() {
332✔
265
                if ( ! empty( $this->data->post_password ) ) {
332✔
266
                        return isset( $this->post_type_object->cap->edit_others_posts ) ? $this->post_type_object->cap->edit_others_posts : 'edit_others_posts';
4✔
267
                }
268

269
                switch ( $this->data->post_status ) {
329✔
270
                        case 'trash':
329✔
271
                                $cap = isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts';
3✔
272
                                break;
3✔
273
                        case 'draft':
328✔
274
                        case 'future':
320✔
275
                        case 'pending':
317✔
276
                                $cap = isset( $this->post_type_object->cap->edit_others_posts ) ? $this->post_type_object->cap->edit_others_posts : 'edit_others_posts';
27✔
277
                                break;
27✔
278
                        default:
279
                                $cap = '';
311✔
280
                                break;
311✔
281
                }
282

283
                return $cap;
329✔
284
        }
285

286
        /**
287
         * {@inheritDoc}
288
         */
289
        public function is_private() {
331✔
290

291
                /**
292
                 * If the post is of post_type "revision", we need to access the parent of the Post
293
                 * so that we can check access rights of the parent post. Revision access is inherit
294
                 * to the Parent it is a revision of.
295
                 */
296
                if ( 'revision' === $this->data->post_type ) {
331✔
297

298
                        // Get the post
299
                        $parent_post = get_post( $this->data->post_parent );
18✔
300

301
                        // If the parent post doesn't exist, the revision should be considered private
302
                        if ( ! $parent_post instanceof WP_Post ) {
18✔
303
                                return true;
×
304
                        }
305

306
                        // Determine if the revision is private using capabilities relative to the parent
307
                        return $this->is_post_private( $parent_post );
18✔
308
                }
309

310
                /**
311
                 * Media Items (attachments) with "inherit" status should inherit their privacy
312
                 * from their parent post. If the parent is draft/pending/private, the attachment
313
                 * should also be considered private.
314
                 *
315
                 * Attachments without a parent or with a published parent are public. Once uploaded
316
                 * to the media library they are exposed with a public URL on the site.
317
                 *
318
                 * The WP REST API sets media items to private if they don't have a `post_parent` set, but
319
                 * this has broken production apps, because media items can be uploaded directly to the
320
                 * media library and published as a featured image, published inline within content, or
321
                 * within a Gutenberg block, etc, but then a consumer tries to ask for data of a published
322
                 * image and REST returns nothing because the media item is treated as private.
323
                 *
324
                 * For attachments with "inherit" status, we check the parent's privacy status.
325
                 * For other attachments, they are treated as public.
326
                 *
327
                 * To override this behavior and make all media items public (regardless of parent status),
328
                 * use the `graphql_pre_model_data_is_private` filter:
329
                 *
330
                 * @example
331
                 * ```php
332
                 * add_filter( 'graphql_pre_model_data_is_private', function( $is_private, $model_name, $data ) {
333
                 *     // Make all media items public
334
                 *     if ( 'PostObject' === $model_name && isset( $data->post_type ) && 'attachment' === $data->post_type ) {
335
                 *         return false; // false = not private (public)
336
                 *     }
337
                 *     return $is_private; // Return null to use default logic for other types
338
                 * }, 10, 3 );
339
                 * ```
340
                 */
341
                if ( 'attachment' === $this->data->post_type ) {
331✔
342
                        // If the attachment has "inherit" status and a parent post, check the parent's privacy
343
                        // This ensures attachments inherit visibility from their parent (e.g., draft post = private attachment)
344
                        if ( 'inherit' === $this->data->post_status && ! empty( $this->data->post_parent ) ) {
47✔
345
                                $parent_post = get_post( (int) $this->data->post_parent );
14✔
346

347
                                // If parent doesn't exist (e.g., was deleted), treat as public
348
                                // This aligns with the documented reasoning: attachments without parents are public
349
                                // because they may be used in published content, featured images, etc.
350
                                if ( ! $parent_post instanceof WP_Post ) {
14✔
351
                                        return false;
×
352
                                }
353

354
                                // Check if the parent post would be private
355
                                // Create a temporary Post model to check the parent's privacy
356
                                // This properly initializes the parent's post type object and checks its privacy status
357
                                $parent_model = new self( $parent_post );
14✔
358
                                return $parent_model->is_private();
14✔
359
                        }
360

361
                        // Attachments without inherit status or without a parent are public
362
                        // This preserves the documented behavior: media items uploaded directly to the library
363
                        // or used in published content should be publicly accessible
364
                        return false;
34✔
365
                }
366

367
                /**
368
                 * Published content is public, not private
369
                 */
370
                if ( 'publish' === $this->data->post_status && $this->post_type_object && ( true === $this->post_type_object->public || true === $this->post_type_object->publicly_queryable ) ) {
308✔
371
                        return false;
281✔
372
                }
373

374
                return $this->is_post_private( $this->data );
36✔
375
        }
376

377
        /**
378
         * Method for determining if the data should be considered private or not
379
         *
380
         * @param \WP_Post $post_object The object of the post we need to verify permissions for
381
         *
382
         * @return bool
383
         */
384
        protected function is_post_private( $post_object = null ) {
52✔
385
                $post_type_object = $this->post_type_object;
52✔
386

387
                if ( ! $post_type_object ) {
52✔
388
                        return true;
×
389
                }
390

391
                if ( ! $post_object ) {
52✔
392
                        $post_object = $this->data;
×
393
                }
394

395
                /**
396
                 * If the status is NOT publish and the user does NOT have capabilities to edit posts,
397
                 * consider the post private.
398
                 */
399
                if ( ! isset( $post_type_object->cap->edit_posts ) || ! current_user_can( $post_type_object->cap->edit_posts ) ) {
52✔
400
                        return true;
16✔
401
                }
402

403
                /**
404
                 * If the owner of the content is the current user
405
                 */
406
                if ( ( true === $this->owner_matches_current_user() ) && 'revision' !== $post_object->post_type ) {
43✔
407
                        return false;
25✔
408
                }
409

410
                /**
411
                 * If the post_type isn't (not registered) or is not allowed in WPGraphQL,
412
                 * mark the post as private
413
                 */
414

415
                if ( empty( $post_type_object->name ) || ! in_array( $post_type_object->name, \WPGraphQL::get_allowed_post_types(), true ) ) {
18✔
416
                        return true;
×
417
                }
418

419
                if ( 'private' === $this->data->post_status && ( ! isset( $post_type_object->cap->read_private_posts ) || ! current_user_can( $post_type_object->cap->read_private_posts ) ) ) {
18✔
420
                        return true;
×
421
                }
422

423
                if ( 'revision' === $this->data->post_type || 'auto-draft' === $this->data->post_status ) {
18✔
424
                        $parent = get_post( (int) $this->data->post_parent );
10✔
425

426
                        if ( empty( $parent ) ) {
10✔
427
                                return true;
×
428
                        }
429

430
                        $parent_post_type_obj = $post_type_object;
10✔
431

432
                        if ( 'private' === $parent->post_status ) {
10✔
433
                                $cap = isset( $parent_post_type_obj->cap->read_private_posts ) ? $parent_post_type_obj->cap->read_private_posts : 'read_private_posts';
×
434
                        } else {
435
                                $cap = isset( $parent_post_type_obj->cap->edit_post ) ? $parent_post_type_obj->cap->edit_post : 'edit_post';
10✔
436
                        }
437

438
                        if ( ! current_user_can( $cap, $parent->ID ) ) {
10✔
439
                                return true;
×
440
                        }
441
                }
442

443
                return false;
18✔
444
        }
445

446
        /**
447
         * {@inheritDoc}
448
         */
449
        protected function init() {
325✔
450
                if ( empty( $this->fields ) ) {
325✔
451
                        $this->fields = [
325✔
452
                                'authorDatabaseId'          => function () {
325✔
453
                                        if ( true === $this->isPreview ) {
26✔
454
                                                $parent_post = get_post( $this->data->post_parent );
8✔
455

456
                                                return ! empty( $parent_post->post_author ) ? (int) $parent_post->post_author : null;
8✔
457
                                        }
458

459
                                        return ! empty( $this->data->post_author ) ? (int) $this->data->post_author : null;
26✔
460
                                },
325✔
461
                                'authorId'                  => function () {
325✔
462
                                        return ! empty( $this->authorDatabaseId ) ? Relay::toGlobalId( 'user', (string) $this->authorDatabaseId ) : null;
×
463
                                },
325✔
464
                                'commentCount'              => function () {
325✔
465
                                        return ! empty( $this->data->comment_count ) ? absint( $this->data->comment_count ) : 0;
5✔
466
                                },
325✔
467
                                'commentStatus'             => function () {
325✔
468
                                        return ! empty( $this->data->comment_status ) ? $this->data->comment_status : null;
10✔
469
                                },
325✔
470
                                'contentRaw'                => [
325✔
471
                                        'callback'   => function () {
325✔
472
                                                return ! empty( $this->data->post_content ) ? $this->data->post_content : null;
1✔
473
                                        },
325✔
474
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
325✔
475
                                ],
325✔
476
                                'contentRendered'           => function () {
325✔
477
                                        $content = ! empty( $this->data->post_content ) ? $this->data->post_content : null;
55✔
478

479
                                        return ! empty( $content ) ? $this->html_entity_decode( apply_filters( 'the_content', $content ), 'contentRendered', false ) : null;
55✔
480
                                },
325✔
481
                                'databaseId'                => function () {
325✔
482
                                        return ! empty( $this->data->ID ) ? absint( $this->data->ID ) : null;
301✔
483
                                },
325✔
484
                                'date'                      => function () {
325✔
485
                                        return ! empty( $this->data->post_date ) && '0000-00-00 00:00:00' !== $this->data->post_date ? Utils::prepare_date_response( $this->data->post_date_gmt, $this->data->post_date ) : null;
41✔
486
                                },
325✔
487
                                'dateGmt'                   => function () {
325✔
488
                                        return ! empty( $this->data->post_date_gmt ) ? Utils::prepare_date_response( $this->data->post_date_gmt ) : null;
12✔
489
                                },
325✔
490
                                'editLastId'                => function () {
325✔
491
                                        $edit_last = get_post_meta( $this->data->ID, '_edit_last', true );
3✔
492

493
                                        return ! empty( $edit_last ) ? absint( $edit_last ) : null;
3✔
494
                                },
325✔
495
                                'editLock'                  => function () {
325✔
496
                                        if ( ! function_exists( 'wp_check_post_lock' ) ) {
3✔
497
                                                require_once ABSPATH . 'wp-admin/includes/post.php';
×
498
                                        }
499

500
                                        if ( ! wp_check_post_lock( $this->data->ID ) ) {
3✔
501
                                                return null;
3✔
502
                                        }
503

504
                                        $edit_lock       = get_post_meta( $this->data->ID, '_edit_lock', true );
×
505
                                        $edit_lock_parts = ! empty( $edit_lock ) ? explode( ':', $edit_lock ) : null;
×
506

507
                                        return ! empty( $edit_lock_parts ) ? $edit_lock_parts : null;
×
508
                                },
325✔
509
                                'enclosure'                 => function () {
325✔
510
                                        $enclosure = get_post_meta( $this->data->ID, 'enclosure', true );
4✔
511

512
                                        return ! empty( $enclosure ) ? $enclosure : null;
4✔
513
                                },
325✔
514
                                'enqueuedScriptsQueue'      => static function () {
325✔
515
                                        global $wp_scripts;
18✔
516
                                        do_action( 'wp_enqueue_scripts' );
18✔
517
                                        $queue = $wp_scripts->queue;
18✔
518
                                        $wp_scripts->reset();
18✔
519
                                        $wp_scripts->queue = [];
18✔
520

521
                                        return $queue;
18✔
522
                                },
325✔
523
                                'enqueuedStylesheetsQueue'  => static function () {
325✔
524
                                        global $wp_styles;
17✔
525
                                        do_action( 'wp_enqueue_scripts' );
17✔
526
                                        $queue = $wp_styles->queue;
17✔
527
                                        $wp_styles->reset();
17✔
528
                                        $wp_styles->queue = [];
17✔
529

530
                                        return $queue;
17✔
531
                                },
325✔
532
                                'excerptRaw'                => [
325✔
533
                                        'callback'   => function () {
325✔
534
                                                return ! empty( $this->data->post_excerpt ) ? $this->data->post_excerpt : null;
1✔
535
                                        },
325✔
536
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
325✔
537
                                ],
325✔
538
                                'excerptRendered'           => function () {
325✔
539
                                        $excerpt = ! empty( $this->data->post_excerpt ) ? $this->data->post_excerpt : '';
17✔
540
                                        $excerpt = apply_filters( 'get_the_excerpt', $excerpt, $this->data );
17✔
541

542
                                        return $this->html_entity_decode( apply_filters( 'the_excerpt', $excerpt ), 'excerptRendered' );
17✔
543
                                },
325✔
544
                                'featuredImageDatabaseId'   => function () {
325✔
545
                                        if ( $this->isRevision ) {
13✔
546
                                                $id = $this->parentDatabaseId;
6✔
547
                                        } else {
548
                                                $id = $this->data->ID;
13✔
549
                                        }
550

551
                                        $thumbnail_id = get_post_thumbnail_id( $id );
13✔
552

553
                                        return ! empty( $thumbnail_id ) ? absint( $thumbnail_id ) : null;
13✔
554
                                },
325✔
555
                                'featuredImageId'           => function () {
325✔
556
                                        return ! empty( $this->featuredImageDatabaseId ) ? Relay::toGlobalId( 'post', (string) $this->featuredImageDatabaseId ) : null;
×
557
                                },
325✔
558
                                'guid'                      => function () {
325✔
559
                                        return ! empty( $this->data->guid ) ? $this->data->guid : null;
3✔
560
                                },
325✔
561
                                'hasPassword'               => function () {
325✔
562
                                        return ! empty( $this->data->post_password );
2✔
563
                                },
325✔
564
                                'id'                        => function () {
325✔
565
                                        return ! empty( $this->data->post_type && ! empty( $this->databaseId ) ) ? Relay::toGlobalId( 'post', (string) $this->databaseId ) : null;
299✔
566
                                },
325✔
567
                                'isFrontPage'               => function () {
325✔
568
                                        if ( 'page' !== $this->data->post_type || 'page' !== get_option( 'show_on_front' ) ) {
29✔
569
                                                return false;
26✔
570
                                        }
571
                                        if ( absint( get_option( 'page_on_front', 0 ) ) === $this->data->ID ) {
4✔
572
                                                return true;
3✔
573
                                        }
574

575
                                        return false;
2✔
576
                                },
325✔
577
                                'isPostsPage'               => function () {
325✔
578
                                        if ( 'page' !== $this->data->post_type ) {
28✔
579
                                                return false;
18✔
580
                                        }
581
                                        if ( 'posts' !== get_option( 'show_on_front', 'posts' ) && absint( get_option( 'page_for_posts', 0 ) ) === $this->data->ID ) {
15✔
582
                                                return true;
×
583
                                        }
584

585
                                        return false;
15✔
586
                                },
325✔
587
                                'isPreview'                 => function () {
325✔
588
                                        if ( $this->isRevision ) {
72✔
589
                                                $revisions = wp_get_post_revisions(
12✔
590
                                                        $this->data->post_parent,
12✔
591
                                                        [
12✔
592
                                                                'posts_per_page' => 1,
12✔
593
                                                                'fields'         => 'ids',
12✔
594
                                                                'check_enabled'  => false,
12✔
595
                                                        ]
12✔
596
                                                );
12✔
597

598
                                                if ( in_array( $this->data->ID, array_values( $revisions ), true ) ) {
12✔
599
                                                        return true;
12✔
600
                                                }
601
                                        }
602

603
                                        if ( ! post_type_supports( $this->data->post_type, 'revisions' ) && 'draft' === $this->data->post_status ) {
72✔
604
                                                return true;
2✔
605
                                        }
606

607
                                        return false;
70✔
608
                                },
325✔
609
                                'isPrivacyPage'             => function () {
325✔
610
                                        if ( 'page' !== $this->data->post_type ) {
1✔
611
                                                return false;
×
612
                                        }
613
                                        if ( absint( get_option( 'wp_page_for_privacy_policy', 0 ) ) === $this->data->ID ) {
1✔
614
                                                return true;
1✔
615
                                        }
616

617
                                        return false;
1✔
618
                                },
325✔
619
                                'isRevision'                => function () {
325✔
620
                                        return 'revision' === $this->data->post_type;
89✔
621
                                },
325✔
622
                                'isSticky'                  => function () {
325✔
623
                                        return is_sticky( $this->data->ID );
1✔
624
                                },
325✔
625
                                'link'                      => function () {
325✔
626
                                        $link = get_permalink( $this->data->ID );
31✔
627

628
                                        if ( $this->isPreview ) {
31✔
629
                                                $link = get_preview_post_link( $this->parentDatabaseId );
×
630
                                        } elseif ( $this->isRevision ) {
31✔
631
                                                $link = get_permalink( $this->data->ID );
×
632
                                        }
633

634
                                        return ! empty( $link ) ? urldecode( $link ) : null;
31✔
635
                                },
325✔
636
                                'menuOrder'                 => function () {
325✔
637
                                        return ! empty( $this->data->menu_order ) ? absint( $this->data->menu_order ) : null;
×
638
                                },
325✔
639
                                'modified'                  => function () {
325✔
640
                                        return ! empty( $this->data->post_modified ) && '0000-00-00 00:00:00' !== $this->data->post_modified ? Utils::prepare_date_response( $this->data->post_modified ) : null;
7✔
641
                                },
325✔
642
                                'modifiedGmt'               => function () {
325✔
643
                                        return ! empty( $this->data->post_modified_gmt ) ? Utils::prepare_date_response( $this->data->post_modified_gmt ) : null;
7✔
644
                                },
325✔
645
                                'pageTemplate'              => function () {
325✔
646
                                        $slug = get_page_template_slug( $this->data->ID );
×
647

648
                                        return ! empty( $slug ) ? $slug : null;
×
649
                                },
325✔
650
                                'parentDatabaseId'          => function () {
325✔
651
                                        return ! empty( $this->data->post_parent ) ? absint( $this->data->post_parent ) : null;
20✔
652
                                },
325✔
653
                                'parentId'                  => function () {
325✔
654
                                        return ( ! empty( $this->data->post_type ) && ! empty( $this->parentDatabaseId ) ) ? Relay::toGlobalId( 'post', (string) $this->parentDatabaseId ) : null;
1✔
655
                                },
325✔
656
                                'password'                  => function () {
325✔
657
                                        return ! empty( $this->data->post_password ) ? $this->data->post_password : null;
2✔
658
                                },
325✔
659
                                'pinged'                    => function () {
325✔
660
                                        $punged = get_pung( $this->data->ID );
1✔
661

662
                                        return empty( $punged ) ? null : implode( ',', (array) $punged );
1✔
663
                                },
325✔
664
                                'pingStatus'                => function () {
325✔
665
                                        return ! empty( $this->data->ping_status ) ? $this->data->ping_status : null;
×
666
                                },
325✔
667
                                'post_type'                 => function () {
325✔
668
                                        return ! empty( $this->data->post_type ) ? $this->data->post_type : null;
173✔
669
                                },
325✔
670
                                'previewRevisionDatabaseId' => [
325✔
671
                                        'callback'   => function () {
325✔
672
                                                $revisions = wp_get_post_revisions(
16✔
673
                                                        $this->data->ID,
16✔
674
                                                        [
16✔
675
                                                                'posts_per_page' => 1,
16✔
676
                                                                'fields'         => 'ids',
16✔
677
                                                                'check_enabled'  => false,
16✔
678
                                                        ]
16✔
679
                                                );
16✔
680

681
                                                return is_array( $revisions ) && ! empty( $revisions ) ? array_values( $revisions )[0] : null;
16✔
682
                                        },
325✔
683
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
325✔
684
                                ],
325✔
685
                                'previewRevisionId'         => function () {
325✔
686
                                        return ! empty( $this->previewRevisionDatabaseId ) ? Relay::toGlobalId( 'post', (string) $this->previewRevisionDatabaseId ) : null;
×
687
                                },
325✔
688
                                'slug'                      => function () {
325✔
689
                                        return ! empty( $this->data->post_name ) ? urldecode( $this->data->post_name ) : null;
30✔
690
                                },
325✔
691
                                'status'                    => function () {
325✔
692
                                        return ! empty( $this->data->post_status ) ? $this->data->post_status : null;
48✔
693
                                },
325✔
694
                                'template'                  => function () {
325✔
695
                                        $registered_templates = wp_get_theme()->get_page_templates( null, $this->data->post_type );
5✔
696

697
                                        $template = [
5✔
698
                                                '__typename'   => 'DefaultTemplate',
5✔
699
                                                'templateName' => 'Default',
5✔
700
                                        ];
5✔
701

702
                                        if ( true === $this->isPreview ) {
5✔
703
                                                $parent_post = get_post( $this->parentDatabaseId );
×
704

705
                                                if ( empty( $parent_post ) ) {
×
706
                                                        return $template;
×
707
                                                }
708

709
                                                /** @var \WP_Post $parent_post */
710
                                                $registered_templates = wp_get_theme()->get_page_templates( $parent_post );
×
711

712
                                                if ( empty( $registered_templates ) ) {
×
713
                                                        return $template;
×
714
                                                }
715
                                                $set_template  = get_post_meta( $parent_post->ID, '_wp_page_template', true );
×
716
                                                $template_name = get_page_template_slug( $parent_post->ID );
×
717

718
                                                if ( empty( $set_template ) ) {
×
719
                                                        $set_template = get_post_meta( $this->data->ID, '_wp_page_template', true );
×
720
                                                }
721

722
                                                if ( empty( $template_name ) ) {
×
723
                                                        $template_name = get_page_template_slug( $this->data->ID );
×
724
                                                }
725

726
                                                $template_name = ! empty( $template_name ) ? $template_name : 'Default';
×
727
                                        } else {
728
                                                if ( empty( $registered_templates ) ) {
5✔
729
                                                        return $template;
1✔
730
                                                }
731

732
                                                $set_template  = get_post_meta( $this->data->ID, '_wp_page_template', true );
4✔
733
                                                $template_name = get_page_template_slug( $this->data->ID );
4✔
734

735
                                                $template_name = ! empty( $template_name ) ? $template_name : 'Default';
4✔
736
                                        }
737

738
                                        if ( ! empty( $registered_templates[ $set_template ] ) ) {
4✔
739
                                                $name = Utils::format_type_name_for_wp_template( $registered_templates[ $set_template ], $set_template );
3✔
740

741
                                                // If the name is empty, fallback to DefaultTemplate
742
                                                if ( empty( $name ) ) {
3✔
743
                                                        $name = 'DefaultTemplate';
1✔
744
                                                }
745

746
                                                $template = [
3✔
747
                                                        '__typename'   => $name,
3✔
748
                                                        'templateName' => ucwords( $registered_templates[ $set_template ] ),
3✔
749
                                                ];
3✔
750
                                        }
751

752
                                        return $template;
4✔
753
                                },
325✔
754
                                'titleRaw'                  => [
325✔
755
                                        'callback'   => function () {
325✔
756
                                                return ! empty( $this->data->post_title ) ? $this->data->post_title : null;
1✔
757
                                        },
325✔
758
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
325✔
759
                                ],
325✔
760
                                'titleRendered'             => function () {
325✔
761
                                        $id    = ! empty( $this->data->ID ) ? $this->data->ID : null;
134✔
762
                                        $title = ! empty( $this->data->post_title ) ? $this->data->post_title : '';
134✔
763

764
                                        $processedTitle = ! empty( $title ) ? $this->html_entity_decode( apply_filters( 'the_title', $title, $id ), 'titleRendered', true ) : '';
134✔
765

766
                                        return empty( $processedTitle ) ? null : $processedTitle;
134✔
767
                                },
325✔
768
                                'toPing'                    => function () {
325✔
769
                                        $to_ping = get_to_ping( $this->data->ID );
1✔
770

771
                                        return ! empty( $to_ping ) ? implode( ',', (array) $to_ping ) : null;
1✔
772
                                },
325✔
773
                                'uri'                       => function () {
325✔
774
                                        $uri = $this->link;
28✔
775

776
                                        if ( true === $this->isFrontPage ) {
28✔
777
                                                return '/';
2✔
778
                                        }
779

780
                                        // if the page is set as the posts page
781
                                        // the page node itself is not identifiable
782
                                        // by URI. Instead, the uri would return the
783
                                        // Post content type as that uri
784
                                        // represents the blog archive instead of a page
785
                                        if ( true === $this->isPostsPage ) {
26✔
786
                                                return null;
×
787
                                        }
788

789
                                        return ! empty( $uri ) ? str_ireplace( home_url(), '', $uri ) : null;
26✔
790
                                },
325✔
791

792
                                // Aliases.
793
                                'ID'                        => function () {
325✔
794
                                        return $this->databaseId;
91✔
795
                                },
325✔
796
                                'post_author'               => function () {
325✔
797
                                        return $this->authorDatabaseId;
×
798
                                },
325✔
799
                                'post_status'               => function () {
325✔
800
                                        return $this->status;
×
801
                                },
325✔
802
                        ];
325✔
803

804
                        if ( 'attachment' === $this->data->post_type ) {
325✔
805
                                $attachment_fields = [
48✔
806
                                        'altText'             => function () {
48✔
807
                                                return get_post_meta( $this->data->ID, '_wp_attachment_image_alt', true );
7✔
808
                                        },
48✔
809
                                        'captionRaw'          => [
48✔
810
                                                'callback'   => function () {
48✔
811
                                                        return ! empty( $this->data->post_excerpt ) ? $this->data->post_excerpt : null;
×
812
                                                },
48✔
813
                                                'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
48✔
814
                                        ],
48✔
815
                                        'captionRendered'     => function () {
48✔
816
                                                $caption = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $this->data->post_excerpt, $this->data ) );
7✔
817

818
                                                return ! empty( $caption ) ? $caption : null;
7✔
819
                                        },
48✔
820
                                        'descriptionRaw'      => [
48✔
821
                                                'callback'   => function () {
48✔
822
                                                        return ! empty( $this->data->post_content ) ? $this->data->post_content : null;
×
823
                                                },
48✔
824
                                                'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
48✔
825
                                        ],
48✔
826
                                        'descriptionRendered' => function () {
48✔
827
                                                return ! empty( $this->data->post_content ) ? apply_filters( 'the_content', $this->data->post_content ) : null;
8✔
828
                                        },
48✔
829
                                        'mediaDetails'        => function () {
48✔
830
                                                $media_details = wp_get_attachment_metadata( $this->data->ID );
10✔
831
                                                if ( ! empty( $media_details ) ) {
10✔
832
                                                        $media_details['ID'] = $this->data->ID;
10✔
833

834
                                                        return $media_details;
10✔
835
                                                }
836

837
                                                return null;
×
838
                                        },
48✔
839
                                        'mediaItemUrl'        => function () {
48✔
840
                                                return wp_get_attachment_url( $this->data->ID ) ?: null;
3✔
841
                                        },
48✔
842
                                        'mediaType'           => function () {
48✔
843
                                                return wp_attachment_is_image( $this->data->ID ) ? 'image' : 'file';
4✔
844
                                        },
48✔
845
                                        'mimeType'            => function () {
48✔
846
                                                return ! empty( $this->data->post_mime_type ) ? $this->data->post_mime_type : null;
9✔
847
                                        },
48✔
848
                                        'sourceUrl'           => function () {
48✔
849
                                                return $this->get_source_url_by_size( 'full' );
7✔
850
                                        },
48✔
851
                                        // @todo Remove in 1.30.0.
852
                                        'sourceUrlsBySize'    => function () {
48✔
853
                                                _doing_it_wrong(
×
854
                                                        self::class . '->sourceUrlsBySize',
×
855
                                                        'Use the `sourceUrlBySize` callable instead. This will be removed in the next major release.',
×
856
                                                        '1.29.1'
×
857
                                                );
×
858

859
                                                /**
860
                                                 * This returns an empty array on the VIP Go platform.
861
                                                 */
862
                                                $sizes = get_intermediate_image_sizes(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.get_intermediate_image_sizes_get_intermediate_image_sizes
×
863
                                                $urls  = [];
×
864
                                                if ( ! empty( $sizes ) && is_array( $sizes ) ) {
×
865
                                                        foreach ( $sizes as $size ) {
×
866
                                                                $urls[ $size ] = $this->get_source_url_by_size( $size );
×
867
                                                        }
868
                                                }
869

870
                                                return $urls;
×
871
                                        },
48✔
872
                                ];
48✔
873

874
                                $this->fields = array_merge( $this->fields, $attachment_fields );
48✔
875
                        }
876

877
                        // Deprecated.
878
                        if ( isset( $this->post_type_object ) && isset( $this->post_type_object->graphql_single_name ) ) {
325✔
879
                                $type_id                  = $this->post_type_object->graphql_single_name . 'Id';
325✔
880
                                $this->fields[ $type_id ] = function () {
325✔
881
                                        return absint( $this->data->ID );
×
882
                                };
325✔
883
                        }
884
                }
885
        }
886

887
        /**
888
         * Gets the source URL for an image attachment by size.
889
         *
890
         * This method caches the source URL for a given size to prevent multiple calls to `wp_get_attachment_image_src`.
891
         *
892
         * @param ?string $size The size of the image to get the source URL for. `full` by default.
893
         */
894
        public function get_source_url_by_size( ?string $size = 'full' ): ?string {
9✔
895
                // If size is not set, default to 'full'.
896
                if ( ! $size ) {
9✔
897
                        $size = 'full';
×
898
                }
899

900
                // Resolve the source URL for the size if it hasn't been resolved yet.
901
                if ( ! array_key_exists( $size, $this->source_urls_by_size ) ) {
9✔
902
                        $src = wp_get_attachment_image_src( $this->data->ID, $size );
9✔
903

904
                        $this->source_urls_by_size[ $size ] = ! empty( $src ) ? $src[0] : null;
9✔
905
                }
906

907
                return $this->source_urls_by_size[ $size ];
9✔
908
        }
909
}
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