• 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

88.03
/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
class Post extends Model {
83

84
        /**
85
         * Stores the incoming post data
86
         *
87
         * @var \WP_Post $data
88
         */
89
        protected $data;
90

91
        /**
92
         * Store the global post to reset during model tear down
93
         *
94
         * @var \WP_Post
95
         */
96
        protected $global_post;
97

98
        /**
99
         * Stores the incoming post type object for the post being modeled
100
         *
101
         * @var \WP_Post_Type|null $post_type_object
102
         */
103
        protected $post_type_object;
104

105
        /**
106
         * Store the instance of the WP_Query
107
         *
108
         * @var \WP_Query
109
         */
110
        protected $wp_query;
111

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

123
        /**
124
         * Post constructor.
125
         *
126
         * @param \WP_Post $post The incoming WP_Post object that needs modeling.
127
         *
128
         * @return void
129
         */
130
        public function __construct( WP_Post $post ) {
319✔
131

132
                /**
133
                 * Set the data as the Post object
134
                 */
135
                $this->data             = $post;
319✔
136
                $this->post_type_object = get_post_type_object( $post->post_type );
319✔
137

138
                /**
139
                 * If the post type is 'revision', we need to get the post_type_object
140
                 * of the parent post type to determine capabilities from
141
                 */
142
                if ( 'revision' === $post->post_type && ! empty( $post->post_parent ) ) {
319✔
143
                        $parent = get_post( absint( $post->post_parent ) );
17✔
144
                        if ( ! empty( $parent ) ) {
17✔
145
                                $this->post_type_object = get_post_type_object( $parent->post_type );
17✔
146
                        }
147
                }
148

149
                /**
150
                 * Mimic core functionality for templates, as seen here:
151
                 * https://github.com/WordPress/WordPress/blob/6fd8080e7ee7599b36d4528f72a8ced612130b8c/wp-includes/template-loader.php#L56
152
                 */
153
                if ( 'attachment' === $this->data->post_type ) {
319✔
154
                        remove_filter( 'the_content', 'prepend_attachment' );
40✔
155
                }
156

157
                $allowed_restricted_fields = [
319✔
158
                        'databaseId',
319✔
159
                        'enqueuedScriptsQueue',
319✔
160
                        'enqueuedStylesheetsQueue',
319✔
161
                        'hasPassword',
319✔
162
                        'id',
319✔
163
                        'isFrontPage',
319✔
164
                        'isPostsPage',
319✔
165
                        'isPrivacyPage',
319✔
166
                        'isRestricted',
319✔
167
                        'link',
319✔
168
                        'post_status',
319✔
169
                        'post_type',
319✔
170
                        'slug',
319✔
171
                        'status',
319✔
172
                        'titleRendered',
319✔
173
                        'uri',
319✔
174
                ];
319✔
175

176
                if ( isset( $this->post_type_object->graphql_single_name ) ) {
319✔
177
                        $allowed_restricted_fields[] = $this->post_type_object->graphql_single_name . 'Id';
319✔
178
                }
179

180
                $restricted_cap = $this->get_restricted_cap();
319✔
181

182
                parent::__construct( $restricted_cap, $allowed_restricted_fields, (int) $post->post_author );
319✔
183
        }
184

185
        /**
186
         * {@inheritDoc}
187
         */
188
        public function setup() {
288✔
189
                global $wp_query, $post;
288✔
190

191
                /**
192
                 * Store the global post before overriding
193
                 */
194
                $this->global_post = $post;
288✔
195

196
                /**
197
                 * Set the resolving post to the global $post. That way any filters that
198
                 * might be applied when resolving fields can rely on global post and
199
                 * post data being set up.
200
                 */
201
                if ( $this->data instanceof WP_Post ) {
288✔
202
                        $id        = $this->data->ID;
288✔
203
                        $post_type = $this->data->post_type;
288✔
204
                        $post_name = $this->data->post_name;
288✔
205
                        $data      = $this->data;
288✔
206

207
                        if ( 'revision' === $this->data->post_type ) {
288✔
208
                                $id     = $this->data->post_parent;
16✔
209
                                $parent = get_post( $this->data->post_parent );
16✔
210
                                if ( empty( $parent ) ) {
16✔
211
                                        $this->fields = [];
×
212
                                        return;
×
213
                                }
214
                                $post_type = $parent->post_type;
16✔
215
                                $post_name = $parent->post_name;
16✔
216
                                $data      = $parent;
16✔
217
                        }
218

219
                        /**
220
                         * Clear out existing postdata
221
                         */
222
                        $wp_query->reset_postdata();
288✔
223

224
                        /**
225
                         * Parse the query to tell WordPress how to
226
                         * setup global state
227
                         */
228
                        if ( 'post' === $post_type ) {
288✔
229
                                $wp_query->parse_query(
202✔
230
                                        [
202✔
231
                                                'page' => '',
202✔
232
                                                'p'    => $id,
202✔
233
                                        ]
202✔
234
                                );
202✔
235
                        } elseif ( 'page' === $post_type ) {
141✔
236
                                $wp_query->parse_query(
76✔
237
                                        [
76✔
238
                                                'page'     => '',
76✔
239
                                                'pagename' => $post_name,
76✔
240
                                        ]
76✔
241
                                );
76✔
242
                        } elseif ( 'attachment' === $post_type ) {
73✔
243
                                $wp_query->parse_query(
39✔
244
                                        [
39✔
245
                                                'attachment' => $post_name,
39✔
246
                                        ]
39✔
247
                                );
39✔
248
                        } else {
249
                                $wp_query->parse_query(
34✔
250
                                        [
34✔
251
                                                $post_type  => $post_name,
34✔
252
                                                'post_type' => $post_type,
34✔
253
                                                'name'      => $post_name,
34✔
254
                                        ]
34✔
255
                                );
34✔
256
                        }
257

258
                        $wp_query->setup_postdata( $data );
288✔
259
                        $GLOBALS['post']             = $data; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
288✔
260
                        $wp_query->queried_object    = get_post( $this->data->ID );
288✔
261
                        $wp_query->queried_object_id = $this->data->ID;
288✔
262
                }
263
        }
264

265
        /**
266
         * Retrieve the cap to check if the data should be restricted for the post
267
         *
268
         * @return string
269
         */
270
        protected function get_restricted_cap() {
319✔
271
                if ( ! empty( $this->data->post_password ) ) {
319✔
272
                        return isset( $this->post_type_object->cap->edit_others_posts ) ? $this->post_type_object->cap->edit_others_posts : 'edit_others_posts';
4✔
273
                }
274

275
                switch ( $this->data->post_status ) {
316✔
276
                        case 'trash':
316✔
277
                                $cap = isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts';
3✔
278
                                break;
3✔
279
                        case 'draft':
315✔
280
                        case 'future':
307✔
281
                        case 'pending':
304✔
282
                                $cap = isset( $this->post_type_object->cap->edit_others_posts ) ? $this->post_type_object->cap->edit_others_posts : 'edit_others_posts';
23✔
283
                                break;
23✔
284
                        default:
285
                                $cap = '';
298✔
286
                                break;
298✔
287
                }
288

289
                return $cap;
316✔
290
        }
291

292
        /**
293
         * {@inheritDoc}
294
         */
295
        public function is_private() {
319✔
296

297
                /**
298
                 * If the post is of post_type "revision", we need to access the parent of the Post
299
                 * so that we can check access rights of the parent post. Revision access is inherit
300
                 * to the Parent it is a revision of.
301
                 */
302
                if ( 'revision' === $this->data->post_type ) {
319✔
303

304
                        // Get the post
305
                        $parent_post = get_post( $this->data->post_parent );
17✔
306

307
                        // If the parent post doesn't exist, the revision should be considered private
308
                        if ( ! $parent_post instanceof WP_Post ) {
17✔
309
                                return true;
×
310
                        }
311

312
                        // Determine if the revision is private using capabilities relative to the parent
313
                        return $this->is_post_private( $parent_post );
17✔
314
                }
315

316
                /**
317
                 * Media Items (attachments) are all public. Once uploaded to the media library
318
                 * they are exposed with a public URL on the site.
319
                 *
320
                 * The WP REST API sets media items to private if they don't have a `post_parent` set, but
321
                 * this has broken production apps, because media items can be uploaded directly to the
322
                 * media library and published as a featured image, published inline within content, or
323
                 * within a Gutenberg block, etc, but then a consumer tries to ask for data of a published
324
                 * image and REST returns nothing because the media item is treated as private.
325
                 *
326
                 * Currently, we're treating all media items as public because there's nothing explicit in
327
                 * how WP Core handles privacy of media library items. By default they're publicly exposed.
328
                 */
329
                if ( 'attachment' === $this->data->post_type ) {
319✔
330
                        return false;
40✔
331
                }
332

333
                /**
334
                 * Published content is public, not private
335
                 */
336
                if ( 'publish' === $this->data->post_status && $this->post_type_object && ( true === $this->post_type_object->public || true === $this->post_type_object->publicly_queryable ) ) {
295✔
337
                        return false;
270✔
338
                }
339

340
                return $this->is_post_private( $this->data );
32✔
341
        }
342

343
        /**
344
         * Method for determining if the data should be considered private or not
345
         *
346
         * @param \WP_Post $post_object The object of the post we need to verify permissions for
347
         *
348
         * @return bool
349
         */
350
        protected function is_post_private( $post_object = null ) {
47✔
351
                $post_type_object = $this->post_type_object;
47✔
352

353
                if ( ! $post_type_object ) {
47✔
354
                        return true;
×
355
                }
356

357
                if ( ! $post_object ) {
47✔
358
                        $post_object = $this->data;
×
359
                }
360

361
                /**
362
                 * If the status is NOT publish and the user does NOT have capabilities to edit posts,
363
                 * consider the post private.
364
                 */
365
                if ( ! isset( $post_type_object->cap->edit_posts ) || ! current_user_can( $post_type_object->cap->edit_posts ) ) {
47✔
366
                        return true;
12✔
367
                }
368

369
                /**
370
                 * If the owner of the content is the current user
371
                 */
372
                if ( ( true === $this->owner_matches_current_user() ) && 'revision' !== $post_object->post_type ) {
39✔
373
                        return false;
22✔
374
                }
375

376
                /**
377
                 * If the post_type isn't (not registered) or is not allowed in WPGraphQL,
378
                 * mark the post as private
379
                 */
380

381
                if ( empty( $post_type_object->name ) || ! in_array( $post_type_object->name, \WPGraphQL::get_allowed_post_types(), true ) ) {
17✔
382
                        return true;
×
383
                }
384

385
                if ( 'private' === $this->data->post_status && ( ! isset( $post_type_object->cap->read_private_posts ) || ! current_user_can( $post_type_object->cap->read_private_posts ) ) ) {
17✔
386
                        return true;
×
387
                }
388

389
                if ( 'revision' === $this->data->post_type || 'auto-draft' === $this->data->post_status ) {
17✔
390
                        $parent = get_post( (int) $this->data->post_parent );
9✔
391

392
                        if ( empty( $parent ) ) {
9✔
393
                                return true;
×
394
                        }
395

396
                        $parent_post_type_obj = $post_type_object;
9✔
397

398
                        if ( 'private' === $parent->post_status ) {
9✔
399
                                $cap = isset( $parent_post_type_obj->cap->read_private_posts ) ? $parent_post_type_obj->cap->read_private_posts : 'read_private_posts';
×
400
                        } else {
401
                                $cap = isset( $parent_post_type_obj->cap->edit_post ) ? $parent_post_type_obj->cap->edit_post : 'edit_post';
9✔
402
                        }
403

404
                        if ( ! current_user_can( $cap, $parent->ID ) ) {
9✔
405
                                return true;
×
406
                        }
407
                }
408

409
                return false;
17✔
410
        }
411

412
        /**
413
         * {@inheritDoc}
414
         */
415
        protected function init() {
312✔
416
                if ( empty( $this->fields ) ) {
312✔
417
                        $this->fields = [
312✔
418
                                'authorDatabaseId'          => function () {
312✔
419
                                        if ( true === $this->isPreview ) {
26✔
420
                                                $parent_post = get_post( $this->data->post_parent );
8✔
421

422
                                                return ! empty( $parent_post->post_author ) ? (int) $parent_post->post_author : null;
8✔
423
                                        }
424

425
                                        return ! empty( $this->data->post_author ) ? (int) $this->data->post_author : null;
26✔
426
                                },
312✔
427
                                'authorId'                  => function () {
312✔
428
                                        return ! empty( $this->authorDatabaseId ) ? Relay::toGlobalId( 'user', (string) $this->authorDatabaseId ) : null;
×
429
                                },
312✔
430
                                'commentCount'              => function () {
312✔
431
                                        return ! empty( $this->data->comment_count ) ? absint( $this->data->comment_count ) : 0;
5✔
432
                                },
312✔
433
                                'commentStatus'             => function () {
312✔
434
                                        return ! empty( $this->data->comment_status ) ? $this->data->comment_status : null;
10✔
435
                                },
312✔
436
                                'contentRaw'                => [
312✔
437
                                        'callback'   => function () {
312✔
438
                                                return ! empty( $this->data->post_content ) ? $this->data->post_content : null;
1✔
439
                                        },
312✔
440
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
312✔
441
                                ],
312✔
442
                                'contentRendered'           => function () {
312✔
443
                                        $content = ! empty( $this->data->post_content ) ? $this->data->post_content : null;
53✔
444

445
                                        return ! empty( $content ) ? $this->html_entity_decode( apply_filters( 'the_content', $content ), 'contentRendered', false ) : null;
53✔
446
                                },
312✔
447
                                'databaseId'                => function () {
312✔
448
                                        return ! empty( $this->data->ID ) ? absint( $this->data->ID ) : null;
288✔
449
                                },
312✔
450
                                'date'                      => function () {
312✔
451
                                        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✔
452
                                },
312✔
453
                                'dateGmt'                   => function () {
312✔
454
                                        return ! empty( $this->data->post_date_gmt ) ? Utils::prepare_date_response( $this->data->post_date_gmt ) : null;
12✔
455
                                },
312✔
456
                                'editLastId'                => function () {
312✔
457
                                        $edit_last = get_post_meta( $this->data->ID, '_edit_last', true );
3✔
458

459
                                        return ! empty( $edit_last ) ? absint( $edit_last ) : null;
3✔
460
                                },
312✔
461
                                'editLock'                  => function () {
312✔
462
                                        if ( ! function_exists( 'wp_check_post_lock' ) ) {
3✔
463
                                                // @phpstan-ignore requireOnce.fileNotFound
464
                                                require_once ABSPATH . 'wp-admin/includes/post.php';
×
465
                                        }
466

467
                                        if ( ! wp_check_post_lock( $this->data->ID ) ) {
3✔
468
                                                return null;
3✔
469
                                        }
470

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

474
                                        return ! empty( $edit_lock_parts ) ? $edit_lock_parts : null;
×
475
                                },
312✔
476
                                'enclosure'                 => function () {
312✔
477
                                        $enclosure = get_post_meta( $this->data->ID, 'enclosure', true );
4✔
478

479
                                        return ! empty( $enclosure ) ? $enclosure : null;
4✔
480
                                },
312✔
481
                                'enqueuedScriptsQueue'      => static function () {
312✔
482
                                        global $wp_scripts;
17✔
483
                                        do_action( 'wp_enqueue_scripts' );
17✔
484
                                        $queue = $wp_scripts->queue;
17✔
485
                                        $wp_scripts->reset();
17✔
486
                                        $wp_scripts->queue = [];
17✔
487

488
                                        return $queue;
17✔
489
                                },
312✔
490
                                'enqueuedStylesheetsQueue'  => static function () {
312✔
491
                                        global $wp_styles;
17✔
492
                                        do_action( 'wp_enqueue_scripts' );
17✔
493
                                        $queue = $wp_styles->queue;
17✔
494
                                        $wp_styles->reset();
17✔
495
                                        $wp_styles->queue = [];
17✔
496

497
                                        return $queue;
17✔
498
                                },
312✔
499
                                'excerptRaw'                => [
312✔
500
                                        'callback'   => function () {
312✔
501
                                                return ! empty( $this->data->post_excerpt ) ? $this->data->post_excerpt : null;
1✔
502
                                        },
312✔
503
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
312✔
504
                                ],
312✔
505
                                'excerptRendered'           => function () {
312✔
506
                                        $excerpt = ! empty( $this->data->post_excerpt ) ? $this->data->post_excerpt : '';
17✔
507
                                        $excerpt = apply_filters( 'get_the_excerpt', $excerpt, $this->data );
17✔
508

509
                                        return $this->html_entity_decode( apply_filters( 'the_excerpt', $excerpt ), 'excerptRendered' );
17✔
510
                                },
312✔
511
                                'featuredImageDatabaseId'   => function () {
312✔
512
                                        if ( $this->isRevision ) {
10✔
513
                                                $id = $this->parentDatabaseId;
6✔
514
                                        } else {
515
                                                $id = $this->data->ID;
10✔
516
                                        }
517

518
                                        $thumbnail_id = get_post_thumbnail_id( $id );
10✔
519

520
                                        return ! empty( $thumbnail_id ) ? absint( $thumbnail_id ) : null;
10✔
521
                                },
312✔
522
                                'featuredImageId'           => function () {
312✔
523
                                        return ! empty( $this->featuredImageDatabaseId ) ? Relay::toGlobalId( 'post', (string) $this->featuredImageDatabaseId ) : null;
×
524
                                },
312✔
525
                                'guid'                      => function () {
312✔
526
                                        return ! empty( $this->data->guid ) ? $this->data->guid : null;
3✔
527
                                },
312✔
528
                                'hasPassword'               => function () {
312✔
529
                                        return ! empty( $this->data->post_password );
2✔
530
                                },
312✔
531
                                'id'                        => function () {
312✔
532
                                        return ! empty( $this->data->post_type && ! empty( $this->databaseId ) ) ? Relay::toGlobalId( 'post', (string) $this->databaseId ) : null;
286✔
533
                                },
312✔
534
                                'isFrontPage'               => function () {
312✔
535
                                        if ( 'page' !== $this->data->post_type || 'page' !== get_option( 'show_on_front' ) ) {
29✔
536
                                                return false;
26✔
537
                                        }
538
                                        if ( absint( get_option( 'page_on_front', 0 ) ) === $this->data->ID ) {
4✔
539
                                                return true;
3✔
540
                                        }
541

542
                                        return false;
2✔
543
                                },
312✔
544
                                'isPostsPage'               => function () {
312✔
545
                                        if ( 'page' !== $this->data->post_type ) {
28✔
546
                                                return false;
18✔
547
                                        }
548
                                        if ( 'posts' !== get_option( 'show_on_front', 'posts' ) && absint( get_option( 'page_for_posts', 0 ) ) === $this->data->ID ) {
15✔
549
                                                return true;
×
550
                                        }
551

552
                                        return false;
15✔
553
                                },
312✔
554
                                'isPreview'                 => function () {
312✔
555
                                        if ( $this->isRevision ) {
72✔
556
                                                $revisions = wp_get_post_revisions(
12✔
557
                                                        $this->data->post_parent,
12✔
558
                                                        [
12✔
559
                                                                'posts_per_page' => 1,
12✔
560
                                                                'fields'         => 'ids',
12✔
561
                                                                'check_enabled'  => false,
12✔
562
                                                        ]
12✔
563
                                                );
12✔
564

565
                                                if ( in_array( $this->data->ID, array_values( $revisions ), true ) ) {
12✔
566
                                                        return true;
12✔
567
                                                }
568
                                        }
569

570
                                        if ( ! post_type_supports( $this->data->post_type, 'revisions' ) && 'draft' === $this->data->post_status ) {
72✔
571
                                                return true;
2✔
572
                                        }
573

574
                                        return false;
70✔
575
                                },
312✔
576
                                'isPrivacyPage'             => function () {
312✔
577
                                        if ( 'page' !== $this->data->post_type ) {
1✔
578
                                                return false;
×
579
                                        }
580
                                        if ( absint( get_option( 'wp_page_for_privacy_policy', 0 ) ) === $this->data->ID ) {
1✔
581
                                                return true;
1✔
582
                                        }
583

584
                                        return false;
1✔
585
                                },
312✔
586
                                'isRevision'                => function () {
312✔
587
                                        return 'revision' === $this->data->post_type;
85✔
588
                                },
312✔
589
                                'isSticky'                  => function () {
312✔
590
                                        return is_sticky( $this->data->ID );
1✔
591
                                },
312✔
592
                                'link'                      => function () {
312✔
593
                                        $link = get_permalink( $this->data->ID );
31✔
594

595
                                        if ( $this->isPreview ) {
31✔
596
                                                $link = get_preview_post_link( $this->parentDatabaseId );
×
597
                                        } elseif ( $this->isRevision ) {
31✔
598
                                                $link = get_permalink( $this->data->ID );
×
599
                                        }
600

601
                                        return ! empty( $link ) ? urldecode( $link ) : null;
31✔
602
                                },
312✔
603
                                'menuOrder'                 => function () {
312✔
604
                                        return ! empty( $this->data->menu_order ) ? absint( $this->data->menu_order ) : null;
×
605
                                },
312✔
606
                                'modified'                  => function () {
312✔
607
                                        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✔
608
                                },
312✔
609
                                'modifiedGmt'               => function () {
312✔
610
                                        return ! empty( $this->data->post_modified_gmt ) ? Utils::prepare_date_response( $this->data->post_modified_gmt ) : null;
7✔
611
                                },
312✔
612
                                'pageTemplate'              => function () {
312✔
613
                                        $slug = get_page_template_slug( $this->data->ID );
×
614

615
                                        return ! empty( $slug ) ? $slug : null;
×
616
                                },
312✔
617
                                'parentDatabaseId'          => function () {
312✔
618
                                        return ! empty( $this->data->post_parent ) ? absint( $this->data->post_parent ) : null;
20✔
619
                                },
312✔
620
                                'parentId'                  => function () {
312✔
621
                                        return ( ! empty( $this->data->post_type ) && ! empty( $this->parentDatabaseId ) ) ? Relay::toGlobalId( 'post', (string) $this->parentDatabaseId ) : null;
1✔
622
                                },
312✔
623
                                'password'                  => function () {
312✔
624
                                        return ! empty( $this->data->post_password ) ? $this->data->post_password : null;
2✔
625
                                },
312✔
626
                                'pinged'                    => function () {
312✔
627
                                        $punged = get_pung( $this->data->ID );
1✔
628

629
                                        return empty( $punged ) ? null : implode( ',', (array) $punged );
1✔
630
                                },
312✔
631
                                'pingStatus'                => function () {
312✔
632
                                        return ! empty( $this->data->ping_status ) ? $this->data->ping_status : null;
×
633
                                },
312✔
634
                                'post_type'                 => function () {
312✔
635
                                        return ! empty( $this->data->post_type ) ? $this->data->post_type : null;
163✔
636
                                },
312✔
637
                                'previewRevisionDatabaseId' => [
312✔
638
                                        'callback'   => function () {
312✔
639
                                                $revisions = wp_get_post_revisions(
15✔
640
                                                        $this->data->ID,
15✔
641
                                                        [
15✔
642
                                                                'posts_per_page' => 1,
15✔
643
                                                                'fields'         => 'ids',
15✔
644
                                                                'check_enabled'  => false,
15✔
645
                                                        ]
15✔
646
                                                );
15✔
647

648
                                                return is_array( $revisions ) && ! empty( $revisions ) ? array_values( $revisions )[0] : null;
15✔
649
                                        },
312✔
650
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
312✔
651
                                ],
312✔
652
                                'previewRevisionId'         => function () {
312✔
653
                                        return ! empty( $this->previewRevisionDatabaseId ) ? Relay::toGlobalId( 'post', (string) $this->previewRevisionDatabaseId ) : null;
×
654
                                },
312✔
655
                                'slug'                      => function () {
312✔
656
                                        return ! empty( $this->data->post_name ) ? urldecode( $this->data->post_name ) : null;
22✔
657
                                },
312✔
658
                                'status'                    => function () {
312✔
659
                                        return ! empty( $this->data->post_status ) ? $this->data->post_status : null;
40✔
660
                                },
312✔
661
                                'template'                  => function () {
312✔
662
                                        $registered_templates = wp_get_theme()->get_page_templates( null, $this->data->post_type );
5✔
663

664
                                        $template = [
5✔
665
                                                '__typename'   => 'DefaultTemplate',
5✔
666
                                                'templateName' => 'Default',
5✔
667
                                        ];
5✔
668

669
                                        if ( true === $this->isPreview ) {
5✔
670
                                                $parent_post = get_post( $this->parentDatabaseId );
×
671

672
                                                if ( empty( $parent_post ) ) {
×
673
                                                        return $template;
×
674
                                                }
675

676
                                                /** @var \WP_Post $parent_post */
677
                                                $registered_templates = wp_get_theme()->get_page_templates( $parent_post );
×
678

679
                                                if ( empty( $registered_templates ) ) {
×
680
                                                        return $template;
×
681
                                                }
682
                                                $set_template  = get_post_meta( $parent_post->ID, '_wp_page_template', true );
×
683
                                                $template_name = get_page_template_slug( $parent_post->ID );
×
684

685
                                                if ( empty( $set_template ) ) {
×
686
                                                        $set_template = get_post_meta( $this->data->ID, '_wp_page_template', true );
×
687
                                                }
688

689
                                                if ( empty( $template_name ) ) {
×
690
                                                        $template_name = get_page_template_slug( $this->data->ID );
×
691
                                                }
692

693
                                                $template_name = ! empty( $template_name ) ? $template_name : 'Default';
×
694
                                        } else {
695
                                                if ( empty( $registered_templates ) ) {
5✔
696
                                                        return $template;
1✔
697
                                                }
698

699
                                                $set_template  = get_post_meta( $this->data->ID, '_wp_page_template', true );
4✔
700
                                                $template_name = get_page_template_slug( $this->data->ID );
4✔
701

702
                                                $template_name = ! empty( $template_name ) ? $template_name : 'Default';
4✔
703
                                        }
704

705
                                        if ( ! empty( $registered_templates[ $set_template ] ) ) {
4✔
706
                                                $name = Utils::format_type_name_for_wp_template( $registered_templates[ $set_template ], $set_template );
3✔
707

708
                                                // If the name is empty, fallback to DefaultTemplate
709
                                                if ( empty( $name ) ) {
3✔
710
                                                        $name = 'DefaultTemplate';
1✔
711
                                                }
712

713
                                                $template = [
3✔
714
                                                        '__typename'   => $name,
3✔
715
                                                        'templateName' => ucwords( $registered_templates[ $set_template ] ),
3✔
716
                                                ];
3✔
717
                                        }
718

719
                                        return $template;
4✔
720
                                },
312✔
721
                                'titleRaw'                  => [
312✔
722
                                        'callback'   => function () {
312✔
723
                                                return ! empty( $this->data->post_title ) ? $this->data->post_title : null;
1✔
724
                                        },
312✔
725
                                        'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
312✔
726
                                ],
312✔
727
                                'titleRendered'             => function () {
312✔
728
                                        $id    = ! empty( $this->data->ID ) ? $this->data->ID : null;
129✔
729
                                        $title = ! empty( $this->data->post_title ) ? $this->data->post_title : '';
129✔
730

731
                                        $processedTitle = ! empty( $title ) ? $this->html_entity_decode( apply_filters( 'the_title', $title, $id ), 'titleRendered', true ) : '';
129✔
732

733
                                        return empty( $processedTitle ) ? null : $processedTitle;
129✔
734
                                },
312✔
735
                                'toPing'                    => function () {
312✔
736
                                        $to_ping = get_to_ping( $this->data->ID );
1✔
737

738
                                        return ! empty( $to_ping ) ? implode( ',', (array) $to_ping ) : null;
1✔
739
                                },
312✔
740
                                'uri'                       => function () {
312✔
741
                                        $uri = $this->link;
28✔
742

743
                                        if ( true === $this->isFrontPage ) {
28✔
744
                                                return '/';
2✔
745
                                        }
746

747
                                        // if the page is set as the posts page
748
                                        // the page node itself is not identifiable
749
                                        // by URI. Instead, the uri would return the
750
                                        // Post content type as that uri
751
                                        // represents the blog archive instead of a page
752
                                        if ( true === $this->isPostsPage ) {
26✔
753
                                                return null;
×
754
                                        }
755

756
                                        return ! empty( $uri ) ? str_ireplace( home_url(), '', $uri ) : null;
26✔
757
                                },
312✔
758

759
                                // Aliases.
760
                                'ID'                        => function () {
312✔
761
                                        return $this->databaseId;
86✔
762
                                },
312✔
763
                                'post_author'               => function () {
312✔
764
                                        return $this->authorDatabaseId;
×
765
                                },
312✔
766
                                'post_status'               => function () {
312✔
767
                                        return $this->status;
×
768
                                },
312✔
769
                        ];
312✔
770

771
                        if ( 'attachment' === $this->data->post_type ) {
312✔
772
                                $attachment_fields = [
40✔
773
                                        'altText'             => function () {
40✔
774
                                                return get_post_meta( $this->data->ID, '_wp_attachment_image_alt', true );
7✔
775
                                        },
40✔
776
                                        'captionRaw'          => [
40✔
777
                                                'callback'   => function () {
40✔
778
                                                        return ! empty( $this->data->post_excerpt ) ? $this->data->post_excerpt : null;
×
779
                                                },
40✔
780
                                                'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
40✔
781
                                        ],
40✔
782
                                        'captionRendered'     => function () {
40✔
783
                                                $caption = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $this->data->post_excerpt, $this->data ) );
7✔
784

785
                                                return ! empty( $caption ) ? $caption : null;
7✔
786
                                        },
40✔
787
                                        'descriptionRaw'      => [
40✔
788
                                                'callback'   => function () {
40✔
789
                                                        return ! empty( $this->data->post_content ) ? $this->data->post_content : null;
×
790
                                                },
40✔
791
                                                'capability' => isset( $this->post_type_object->cap->edit_posts ) ? $this->post_type_object->cap->edit_posts : 'edit_posts',
40✔
792
                                        ],
40✔
793
                                        'descriptionRendered' => function () {
40✔
794
                                                return ! empty( $this->data->post_content ) ? apply_filters( 'the_content', $this->data->post_content ) : null;
8✔
795
                                        },
40✔
796
                                        'mediaDetails'        => function () {
40✔
797
                                                $media_details = wp_get_attachment_metadata( $this->data->ID );
10✔
798
                                                if ( ! empty( $media_details ) ) {
10✔
799
                                                        $media_details['ID'] = $this->data->ID;
10✔
800

801
                                                        return $media_details;
10✔
802
                                                }
803

804
                                                return null;
×
805
                                        },
40✔
806
                                        'mediaItemUrl'        => function () {
40✔
807
                                                return wp_get_attachment_url( $this->data->ID ) ?: null;
3✔
808
                                        },
40✔
809
                                        'mediaType'           => function () {
40✔
810
                                                return wp_attachment_is_image( $this->data->ID ) ? 'image' : 'file';
4✔
811
                                        },
40✔
812
                                        'mimeType'            => function () {
40✔
813
                                                return ! empty( $this->data->post_mime_type ) ? $this->data->post_mime_type : null;
9✔
814
                                        },
40✔
815
                                        'sourceUrl'           => function () {
40✔
816
                                                return $this->get_source_url_by_size( 'full' );
7✔
817
                                        },
40✔
818
                                        'sourceUrlsBySize'    => function () {
40✔
819
                                                _doing_it_wrong(
×
820
                                                        __METHOD__,
×
821
                                                        '`sourceUrlsBySize` is deprecated. Use the `sourceUrlBySize` callable instead.',
×
822
                                                        '1.29.1'
×
823
                                                );
×
824

825
                                                /**
826
                                                 * This returns an empty array on the VIP Go platform.
827
                                                 */
828
                                                $sizes = get_intermediate_image_sizes(); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.get_intermediate_image_sizes_get_intermediate_image_sizes
×
829
                                                $urls  = [];
×
830
                                                if ( ! empty( $sizes ) && is_array( $sizes ) ) {
×
831
                                                        foreach ( $sizes as $size ) {
×
832
                                                                $urls[ $size ] = $this->get_source_url_by_size( $size );
×
833
                                                        }
834
                                                }
835

836
                                                return $urls;
×
837
                                        },
40✔
838
                                ];
40✔
839

840
                                $this->fields = array_merge( $this->fields, $attachment_fields );
40✔
841
                        }
842

843
                        // Deprecated.
844
                        if ( isset( $this->post_type_object ) && isset( $this->post_type_object->graphql_single_name ) ) {
312✔
845
                                $type_id                  = $this->post_type_object->graphql_single_name . 'Id';
312✔
846
                                $this->fields[ $type_id ] = function () {
312✔
847
                                        return absint( $this->data->ID );
×
848
                                };
312✔
849
                        }
850
                }
851
        }
852

853
        /**
854
         * Gets the source URL for an image attachment by size.
855
         *
856
         * This method caches the source URL for a given size to prevent multiple calls to `wp_get_attachment_image_src`.
857
         *
858
         * @param ?string $size The size of the image to get the source URL for. `full` by default.
859
         */
860
        public function get_source_url_by_size( ?string $size = 'full' ): ?string {
9✔
861
                // If size is not set, default to 'full'.
862
                if ( ! $size ) {
9✔
863
                        $size = 'full';
×
864
                }
865

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

870
                        $this->source_urls_by_size[ $size ] = ! empty( $src ) ? $src[0] : null;
9✔
871
                }
872

873
                return $this->source_urls_by_size[ $size ];
9✔
874
        }
875
}
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