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

wp-graphql / wp-graphql / 13659882478

04 Mar 2025 05:56PM UTC coverage: 82.702% (-0.01%) from 82.712%
13659882478

push

github

jasonbahl
- fix changelog and readme.txt

13822 of 16713 relevant lines covered (82.7%)

299.99 hits per line

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

54.38
/src/Request.php
1
<?php
2

3
namespace WPGraphQL;
4

5
use Exception;
6
use GraphQL\Error\DebugFlag;
7
use GraphQL\Error\Error;
8
use GraphQL\GraphQL;
9
use GraphQL\Server\OperationParams;
10
use GraphQL\Server\ServerConfig;
11
use GraphQL\Server\StandardServer;
12
use WPGraphQL\Server\ValidationRules\DisableIntrospection;
13
use WPGraphQL\Server\ValidationRules\QueryDepth;
14
use WPGraphQL\Server\ValidationRules\RequireAuthentication;
15
use WPGraphQL\Server\WPHelper;
16
use WPGraphQL\Utils\DebugLog;
17
use WPGraphQL\Utils\QueryAnalyzer;
18

19
/**
20
 * Class Request
21
 *
22
 * Proxies a request to graphql-php, applying filters and transforming request
23
 * data as needed.
24
 *
25
 * @package WPGraphQL
26
 */
27
class Request {
28

29
        /**
30
         * App context for this request.
31
         *
32
         * @var \WPGraphQL\AppContext
33
         */
34
        public $app_context;
35

36
        /**
37
         * Request data.
38
         *
39
         * @var mixed|array<string,mixed>|\GraphQL\Server\OperationParams
40
         */
41
        public $data;
42

43
        /**
44
         * Cached global post.
45
         *
46
         * @var ?\WP_Post
47
         */
48
        public $global_post;
49

50
        /**
51
         * Cached global wp_the_query.
52
         *
53
         * @var ?\WP_Query
54
         */
55
        private $global_wp_the_query;
56

57
        /**
58
         * GraphQL operation parameters for this request. Can also be an array of
59
         * OperationParams.
60
         *
61
         * @var mixed|mixed[]|\GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[]
62
         */
63
        public $params;
64

65
        /**
66
         * Schema for this request.
67
         *
68
         * @var \WPGraphQL\WPSchema
69
         */
70
        public $schema;
71

72
        /**
73
         * Debug log for WPGraphQL Requests
74
         *
75
         * @var \WPGraphQL\Utils\DebugLog
76
         */
77
        public $debug_log;
78

79
        /**
80
         * The Type Registry the Schema is built with
81
         *
82
         * @var \WPGraphQL\Registry\TypeRegistry
83
         */
84
        public $type_registry;
85

86
        /**
87
         * Validation rules for execution.
88
         *
89
         * @var array<int|string,\GraphQL\Validator\Rules\ValidationRule>
90
         */
91
        protected $validation_rules;
92

93
        /**
94
         * The default field resolver function. Default null
95
         *
96
         * @var callable|null
97
         */
98
        protected $field_resolver;
99

100
        /**
101
         * The root value of the request. Default null;
102
         *
103
         * @var mixed
104
         */
105
        protected $root_value;
106

107
        /**
108
         * @var \WPGraphQL\Utils\QueryAnalyzer
109
         */
110
        protected $query_analyzer;
111

112
        /**
113
         * Constructor
114
         *
115
         * @param array<string,mixed> $data The request data.
116
         *
117
         * @return void
118
         *
119
         * @throws \Exception
120
         */
121
        public function __construct( array $data = [] ) {
754✔
122

123
                /**
124
                 * Whether it's a GraphQL Request (http or internal)
125
                 *
126
                 * @since 0.0.5
127
                 */
128
                if ( ! defined( 'GRAPHQL_REQUEST' ) ) {
754✔
129
                        define( 'GRAPHQL_REQUEST', true );
×
130
                }
131

132
                /**
133
                 * Filter "is_graphql_request" to return true
134
                 */
135
                \WPGraphQL::set_is_graphql_request( true );
754✔
136

137
                /**
138
                 * Action – intentionally with no context – to indicate a GraphQL Request has started.
139
                 * This is a great place for plugins to hook in and modify things that should only
140
                 * occur in the context of a GraphQL Request. The base class hooks into this action to
141
                 * kick off the schema creation, so types are not set up until this action has run!
142
                 */
143
                do_action( 'init_graphql_request' );
754✔
144

145
                // Start tracking debug log messages
146
                $this->debug_log = new DebugLog();
754✔
147

148
                // Set request data for passed-in (non-HTTP) requests.
149
                $this->data = $data;
754✔
150

151
                // Get the Type Registry
152
                $this->type_registry = \WPGraphQL::get_type_registry();
754✔
153

154
                // Get the App Context
155
                $this->app_context = \WPGraphQL::get_app_context();
754✔
156

157
                $this->root_value       = $this->get_root_value();
754✔
158
                $this->validation_rules = $this->get_validation_rules();
754✔
159
                $this->field_resolver   = $this->get_field_resolver();
754✔
160

161
                /**
162
                 * Configure the app_context which gets passed down to all the resolvers.
163
                 *
164
                 * @since 0.0.4
165
                 */
166
                $app_context                = new AppContext();
754✔
167
                $app_context->viewer        = wp_get_current_user();
754✔
168
                $app_context->root_url      = get_bloginfo( 'url' );
754✔
169
                $app_context->request       = ! empty( $_REQUEST ) ? $_REQUEST : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
754✔
170
                $app_context->type_registry = $this->type_registry;
754✔
171
                $this->app_context          = $app_context;
754✔
172

173
                $this->query_analyzer = new QueryAnalyzer( $this );
754✔
174

175
                // The query analyzer tracks nodes, models, list types and more
176
                // to return in headers and debug messages to help developers understand
177
                // what was resolved, how to cache it, etc.
178
                $this->query_analyzer->init();
754✔
179
        }
180

181
        /**
182
         * Get the instance of the Query Analyzer
183
         */
184
        public function get_query_analyzer(): QueryAnalyzer {
11✔
185
                return $this->query_analyzer;
11✔
186
        }
187

188
        /**
189
         * @return callable|null
190
         */
191
        protected function get_field_resolver() {
754✔
192
                return $this->field_resolver;
754✔
193
        }
194

195
        /**
196
         * Return the validation rules to use in the request
197
         *
198
         * @return array<int|string,\GraphQL\Validator\Rules\ValidationRule>
199
         */
200
        protected function get_validation_rules(): array {
754✔
201
                $validation_rules = GraphQL::getStandardValidationRules();
754✔
202

203
                $validation_rules['require_authentication'] = new RequireAuthentication();
754✔
204
                $validation_rules['disable_introspection']  = new DisableIntrospection();
754✔
205
                $validation_rules['query_depth']            = new QueryDepth();
754✔
206

207
                /**
208
                 * Return the validation rules to use in the request
209
                 *
210
                 * @param array<int|string,\GraphQL\Validator\Rules\ValidationRule> $validation_rules The validation rules to use in the request
211
                 * @param \WPGraphQL\Request                                        $request          The Request instance
212
                 */
213
                return apply_filters( 'graphql_validation_rules', $validation_rules, $this );
754✔
214
        }
215

216
        /**
217
         * Returns the root value to use in the request.
218
         *
219
         * @return mixed|null
220
         */
221
        protected function get_root_value() {
754✔
222
                /**
223
                 * Set the root value based on what was passed to the request
224
                 */
225
                $root_value = isset( $this->data['root_value'] ) && ! empty( $this->data['root_value'] ) ? $this->data['root_value'] : null;
754✔
226

227
                /**
228
                 * Return the filtered root value
229
                 *
230
                 * @param mixed              $root_value The root value the Schema should use to resolve with. Default null.
231
                 * @param \WPGraphQL\Request $request    The Request instance
232
                 */
233
                return apply_filters( 'graphql_root_value', $root_value, $this );
754✔
234
        }
235

236
        /**
237
         * Apply filters and do actions before GraphQL execution
238
         *
239
         * @throws \GraphQL\Error\Error
240
         */
241
        private function before_execute(): void {
748✔
242

243
                /**
244
                 * Store the global post so that it can be reset after GraphQL execution
245
                 *
246
                 * This allows for a GraphQL query to be used in the middle of post content, such as in a Shortcode
247
                 * without disrupting the flow of the post as the global POST before and after GraphQL execution will be
248
                 * the same.
249
                 */
250
                if ( ! empty( $GLOBALS['post'] ) ) {
748✔
251
                        $this->global_post = $GLOBALS['post'];
237✔
252
                }
253

254
                if ( ! empty( $GLOBALS['wp_query'] ) ) {
748✔
255
                        $this->global_wp_the_query = clone $GLOBALS['wp_the_query'];
748✔
256
                }
257

258
                /**
259
                 * If the request is a batch request it will come back as an array
260
                 */
261
                if ( is_array( $this->params ) ) {
748✔
262

263
                        // If the request is a batch request, but batch requests are disabled,
264
                        // bail early
265
                        if ( ! $this->is_batch_queries_enabled() ) {
×
266
                                throw new Error( esc_html__( 'Batch Queries are not supported', 'wp-graphql' ) );
×
267
                        }
268

269
                        $batch_limit = get_graphql_setting( 'batch_limit', 10 );
×
270
                        $batch_limit = absint( $batch_limit ) ? absint( $batch_limit ) : 10;
×
271

272
                        // If batch requests are enabled, but a limit is set and the request exceeds the limit
273
                        // fail now
274
                        if ( $batch_limit < count( $this->params ) ) {
×
275
                                // translators: First placeholder is the max number of batch operations allowed in a GraphQL request. The 2nd placeholder is the number of operations requested in the current request.
276
                                throw new Error( sprintf( esc_html__( 'Batch requests are limited to %1$d operations. This request contained %2$d', 'wp-graphql' ), absint( $batch_limit ), count( $this->params ) ) );
×
277
                        }
278

279
                        /**
280
                         * Execute batch queries
281
                         *
282
                         * @param \GraphQL\Server\OperationParams[] $params The operation params of the batch request
283
                         */
284
                        do_action( 'graphql_execute_batch_queries', $this->params );
×
285

286
                        // Process the batched requests
287
                        array_walk( $this->params, [ $this, 'do_action' ] );
×
288
                } else {
289
                        $this->do_action( $this->params );
748✔
290
                }
291

292
                // Get the Schema
293
                $this->schema = \WPGraphQL::get_schema();
747✔
294

295
                /**
296
                 * This action runs before execution of a GraphQL request (regardless if it's a single or batch request)
297
                 *
298
                 * @param \WPGraphQL\Request $request The instance of the Request being executed
299
                 */
300
                do_action( 'graphql_before_execute', $this );
747✔
301
        }
302

303
        /**
304
         * Checks authentication errors.
305
         *
306
         * False will mean there are no detected errors and
307
         * execution will continue.
308
         *
309
         * Anything else (true, WP_Error, thrown exception, etc) will prevent execution of the GraphQL
310
         * request.
311
         *
312
         * @return bool
313
         * @throws \Exception
314
         */
315
        protected function has_authentication_errors() {
747✔
316
                /**
317
                 * Bail if this is not an HTTP request.
318
                 *
319
                 * Auth for internal requests will happen
320
                 * via WordPress internals.
321
                 */
322
                if ( ! is_graphql_http_request() ) {
747✔
323
                        return false;
747✔
324
                }
325

326
                /**
327
                 * Access the global $wp_rest_auth_cookie
328
                 */
329
                global $wp_rest_auth_cookie;
×
330

331
                /**
332
                 * Default state of the authentication errors
333
                 */
334
                $authentication_errors = false;
×
335

336
                /**
337
                 * Is cookie authentication NOT being used?
338
                 *
339
                 * If we get an auth error, but the user is still logged in, another auth mechanism
340
                 * (JWT, oAuth, etc) must have been used.
341
                 */
342
                if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
×
343

344
                        /**
345
                         * Return filtered authentication errors
346
                         */
347
                        return $this->filtered_authentication_errors( $authentication_errors );
×
348

349
                        /**
350
                         * If the user is not logged in, determine if there's a nonce
351
                         */
352
                } else {
353
                        $nonce = null;
×
354

355
                        if ( isset( $_REQUEST['_wpnonce'] ) ) {
×
356
                                $nonce = $_REQUEST['_wpnonce']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
×
357
                        } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
×
358
                                $nonce = $_SERVER['HTTP_X_WP_NONCE']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
×
359
                        }
360

361
                        if ( null === $nonce ) {
×
362
                                // No nonce at all, so act as if it's an unauthenticated request.
363
                                wp_set_current_user( 0 );
×
364

365
                                return $this->filtered_authentication_errors( $authentication_errors );
×
366
                        }
367

368
                        // Check the nonce.
369
                        $result = wp_verify_nonce( $nonce, 'wp_rest' );
×
370

371
                        if ( ! $result ) {
×
372
                                throw new Exception( esc_html__( 'Cookie nonce is invalid', 'wp-graphql' ) );
×
373
                        }
374
                }
375

376
                /**
377
                 * Return the filtered authentication errors
378
                 */
379
                return $this->filtered_authentication_errors( $authentication_errors );
×
380
        }
381

382
        /**
383
         * Filter Authentication errors. Allows plugins that authenticate to hook in and prevent
384
         * execution if Authentication errors exist.
385
         *
386
         * @param bool $authentication_errors Whether there are authentication errors with the request.
387
         *
388
         * @return bool
389
         */
390
        protected function filtered_authentication_errors( $authentication_errors = false ) {
×
391

392
                /**
393
                 * If false, there are no authentication errors. If true, execution of the
394
                 * GraphQL request will be prevented and an error will be thrown.
395
                 *
396
                 * @param bool $authentication_errors Whether there are authentication errors with the request
397
                 * @param \WPGraphQL\Request $request Instance of the Request
398
                 */
399
                return apply_filters( 'graphql_authentication_errors', $authentication_errors, $this );
×
400
        }
401

402
        /**
403
         * Performs actions and runs filters after execution completes
404
         *
405
         * @param mixed|array<string,mixed>|object $response The response from execution. Array for batch requests, single object for individual requests.
406
         *
407
         * @return mixed[]
408
         *
409
         * @throws \Exception
410
         */
411
        private function after_execute( $response ) {
747✔
412

413
                /**
414
                 * If there are authentication errors, prevent execution and throw an exception.
415
                 */
416
                if ( false !== $this->has_authentication_errors() ) {
747✔
417
                        throw new Exception( esc_html__( 'Authentication Error', 'wp-graphql' ) );
×
418
                }
419

420
                /**
421
                 * If the params and the $response are both arrays
422
                 * treat this as a batch request and map over the array to apply the
423
                 * after_execute_actions, otherwise apply them to the current response
424
                 */
425
                if ( is_array( $this->params ) && is_array( $response ) ) {
747✔
426
                        $filtered_response = [];
×
427
                        foreach ( $response as $key => $resp ) {
×
428
                                $filtered_response[] = $this->after_execute_actions( $resp, (int) $key );
×
429
                        }
430
                } else {
431
                        $filtered_response = $this->after_execute_actions( $response, null );
747✔
432
                }
433

434
                /**
435
                 * Reset the global post after execution
436
                 *
437
                 * This allows for a GraphQL query to be used in the middle of post content, such as in a Shortcode
438
                 * without disrupting the flow of the post as the global POST before and after GraphQL execution will be
439
                 * the same.
440
                 *
441
                 * We cannot use wp_reset_postdata here because it just resets the post from the global query which can
442
                 * be anything the because the resolvers themself can set it to whatever. So we just manually reset the
443
                 * post with setup_postdata we cached before this request.
444
                 */
445

446
                if ( ! empty( $this->global_wp_the_query ) ) {
747✔
447
                        $GLOBALS['wp_the_query'] = $this->global_wp_the_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
747✔
448
                        wp_reset_query(); // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query
747✔
449
                }
450

451
                if ( ! empty( $this->global_post ) ) {
747✔
452
                        $GLOBALS['post'] = $this->global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
237✔
453
                        setup_postdata( $this->global_post );
237✔
454
                }
455

456
                /**
457
                 * Run an action after GraphQL Execution
458
                 *
459
                 * @param mixed[] $filtered_response The response of the entire operation. Could be a single operation or a batch operation
460
                 * @param \WPGraphQL\Request  $request Instance of the Request being executed
461
                 */
462
                do_action( 'graphql_after_execute', $filtered_response, $this );
747✔
463

464
                /**
465
                 * Return the filtered response
466
                 */
467
                return $filtered_response;
747✔
468
        }
469

470
        /**
471
         * Apply filters and do actions after GraphQL execution
472
         *
473
         * @param mixed|array<string,mixed>|object $response The response for your GraphQL request
474
         * @param mixed|int|null                   $key      The array key of the params for batch requests
475
         *
476
         * @return mixed|array<string,mixed>|object
477
         */
478
        private function after_execute_actions( $response, $key = null ) {
747✔
479

480
                /**
481
                 * Determine which params (batch or single request) to use when passing through to the actions
482
                 */
483
                $query     = null;
747✔
484
                $operation = null;
747✔
485
                $variables = null;
747✔
486
                $query_id  = null;
747✔
487

488
                if ( $this->params instanceof OperationParams ) {
747✔
489
                        $operation = $this->params->operation;
747✔
490
                        $query     = $this->params->query;
747✔
491
                        $query_id  = $this->params->queryId;
747✔
492
                        $variables = $this->params->variables;
747✔
493
                } elseif ( is_array( $this->params ) ) {
×
494
                        $operation = $this->params[ $key ]->operation ?? '';
×
495
                        $query     = $this->params[ $key ]->query ?? '';
×
496
                        $query_id  = $this->params[ $key ]->queryId ?? null;
×
497
                        $variables = $this->params[ $key ]->variables ?? null;
×
498
                }
499

500
                /**
501
                 * Run an action. This is a good place for debug tools to hook in to log things, etc.
502
                 *
503
                 * @param mixed|array<string,mixed>|object $response  The response your GraphQL request
504
                 * @param \WPGraphQL\WPSchema              $schema The schema object for the root request
505
                 * @param mixed|string|null                $operation The name of the operation
506
                 * @param string                           $query     The query that GraphQL executed
507
                 * @param mixed[]|null                     $variables Variables to passed to your GraphQL query
508
                 * @param \WPGraphQL\Request               $request Instance of the Request
509
                 *
510
                 * @since 0.0.4
511
                 */
512
                do_action( 'graphql_execute', $response, $this->schema, $operation, $query, $variables, $this );
747✔
513

514
                /**
515
                 * Add the debug log to the request
516
                 */
517
                if ( ! empty( $response ) ) {
747✔
518
                        if ( is_array( $response ) ) {
747✔
519
                                $response['extensions']['debug'] = $this->debug_log->get_logs();
747✔
520
                        } else {
521
                                $response->extensions['debug'] = $this->debug_log->get_logs();
×
522
                        }
523
                }
524

525
                /**
526
                 * Filter the $response of the GraphQL execution. This allows for the response to be filtered
527
                 * before it's returned, allowing granular control over the response at the latest point.
528
                 *
529
                 * POSSIBLE USAGE EXAMPLES:
530
                 * This could be used to ensure that certain fields never make it to the response if they match
531
                 * certain criteria, etc. For example, this filter could be used to check if a current user is
532
                 * allowed to see certain things, and if they are not, the $response could be filtered to remove
533
                 * the data they should not be allowed to see.
534
                 *
535
                 * Or, perhaps some systems want the response to always include some additional piece of data in
536
                 * every response, regardless of the request that was sent to it, this could allow for that
537
                 * to be hooked in and included in the $response.
538
                 *
539
                 * @param mixed|array<string,mixed>|object $response  The response for your GraphQL query
540
                 * @param \WPGraphQL\WPSchema              $schema    The schema object for the root query
541
                 * @param string                           $operation The name of the operation
542
                 * @param string                           $query     The query that GraphQL executed
543
                 * @param mixed[]|null                     $variables Variables to passed to your GraphQL request
544
                 * @param \WPGraphQL\Request               $request   Instance of the Request
545
                 * @param ?string                          $query_id  The query id that GraphQL executed
546
                 *
547
                 * @since 0.0.5
548
                 */
549
                $filtered_response = apply_filters( 'graphql_request_results', $response, $this->schema, $operation, $query, $variables, $this, $query_id );
747✔
550

551
                /**
552
                 * Run an action after the response has been filtered, as the response is being returned.
553
                 * This is a good place for debug tools to hook in to log things, etc.
554
                 *
555
                 * @param mixed|array<string,mixed>|object $filtered_response The filtered response for the GraphQL request
556
                 * @param mixed|array<string,mixed>|object $response          The response for your GraphQL request
557
                 * @param \WPGraphQL\WPSchema              $schema            The schema object for the root request
558
                 * @param string                           $operation         The name of the operation
559
                 * @param string                           $query             The query that GraphQL executed
560
                 * @param mixed[]|null                     $variables         Variables to passed to your GraphQL query
561
                 * @param \WPGraphQL\Request               $request           Instance of the Request
562
                 * @param ?string                          $query_id          The query id that GraphQL executed
563
                 */
564
                do_action( 'graphql_return_response', $filtered_response, $response, $this->schema, $operation, $query, $variables, $this, $query_id );
747✔
565

566
                /**
567
                 * Filter "is_graphql_request" back to false.
568
                 */
569
                \WPGraphQL::set_is_graphql_request( false );
747✔
570

571
                return $filtered_response;
747✔
572
        }
573

574
        /**
575
         * Run action for a request.
576
         *
577
         * @param \GraphQL\Server\OperationParams $params OperationParams for the request.
578
         *
579
         * @return void
580
         */
581
        private function do_action( OperationParams $params ) {
748✔
582

583
                /**
584
                 * Run an action for each request.
585
                 *
586
                 * @param ?string          $query     The GraphQL query
587
                 * @param ?string          $operation The name of the operation
588
                 * @param ?array          $variables Variables to be passed to your GraphQL request
589
                 * @param \GraphQL\Server\OperationParams $params The Operation Params. This includes any extra params,
590
                 * such as extensions or any other modifications to the request body
591
                 */
592
                do_action( 'do_graphql_request', $params->query, $params->operation, $params->variables, $params );
748✔
593
        }
594

595
        /**
596
         * Execute an internal request (graphql() function call).
597
         *
598
         * @return array<string,mixed>
599
         * @throws \Exception
600
         */
601
        public function execute() {
748✔
602
                $helper = new WPHelper();
748✔
603

604
                if ( ! $this->data instanceof OperationParams ) {
748✔
605
                        $this->params = $helper->parseRequestParams( 'POST', $this->data, [] );
748✔
606
                } else {
607
                        $this->params = $this->data;
2✔
608
                }
609

610
                if ( is_array( $this->params ) ) {
748✔
611
                        return array_map(
2✔
612
                                function ( $data ) {
2✔
613
                                        $this->data = $data;
2✔
614
                                        return $this->execute();
2✔
615
                                },
2✔
616
                                $this->params
2✔
617
                        );
2✔
618
                }
619

620
                // If $this->params isnt an array or an OperationParams instance, then something probably went wrong.
621
                if ( ! $this->params instanceof OperationParams ) {
748✔
622
                        throw new \Exception( 'Invalid request params.' );
×
623
                }
624

625
                /**
626
                 * Initialize the GraphQL Request
627
                 */
628
                $this->before_execute();
748✔
629
                $response = apply_filters( 'pre_graphql_execute_request', null, $this );
747✔
630

631
                if ( null === $response ) {
747✔
632

633
                        /**
634
                         * Allow the query string to be determined by a filter. Ex, when params->queryId is present, query can be retrieved.
635
                         */
636
                        $query = apply_filters(
747✔
637
                                'graphql_execute_query_params',
747✔
638
                                isset( $this->params->query ) ? $this->params->query : '',
747✔
639
                                $this->params
747✔
640
                        );
747✔
641

642
                        $result = GraphQL::executeQuery(
747✔
643
                                $this->schema,
747✔
644
                                $query,
747✔
645
                                $this->root_value,
747✔
646
                                $this->app_context,
747✔
647
                                isset( $this->params->variables ) ? $this->params->variables : null,
747✔
648
                                isset( $this->params->operation ) ? $this->params->operation : null,
747✔
649
                                $this->field_resolver,
747✔
650
                                $this->validation_rules
747✔
651
                        );
747✔
652

653
                        /**
654
                         * Return the result of the request
655
                         */
656
                        $response = $result->toArray( $this->get_debug_flag() );
747✔
657
                }
658

659
                /**
660
                 * Ensure the response is returned as a proper, populated array. Otherwise add an error.
661
                 */
662
                if ( empty( $response ) || ! is_array( $response ) ) {
747✔
663
                        $response = [
×
664
                                'errors' => __( 'The GraphQL request returned an invalid response', 'wp-graphql' ),
×
665
                        ];
×
666
                }
667

668
                /**
669
                 * If the request is a batch request it will come back as an array
670
                 */
671
                return $this->after_execute( $response );
747✔
672
        }
673

674
        /**
675
         * Execute an HTTP request.
676
         *
677
         * @return array<string,mixed>
678
         * @throws \Exception
679
         */
680
        public function execute_http() {
×
681
                if ( ! $this->is_valid_http_content_type() ) {
×
682
                        return $this->get_invalid_content_type_response();
×
683
                }
684

685
                /**
686
                 * Parse HTTP request.
687
                 */
688
                $helper       = new WPHelper();
×
689
                $this->params = $helper->parseHttpRequest();
×
690

691
                /**
692
                 * Initialize the GraphQL Request
693
                 */
694
                $this->before_execute();
×
695

696
                /**
697
                 * Get the response.
698
                 */
699
                $response = apply_filters( 'pre_graphql_execute_request', null, $this );
×
700

701
                /**
702
                 * If no cached response, execute the query
703
                 */
704
                if ( null === $response ) {
×
705
                        $server   = $this->get_server();
×
706
                        $response = $server->executeRequest( $this->params );
×
707
                }
708

709
                return $this->after_execute( $response );
×
710
        }
711

712
        /**
713
         * Validates the content type for HTTP POST requests
714
         */
715
        private function is_valid_http_content_type(): bool {
×
716
                if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
×
717
                        return true;
×
718
                }
719

720
                $content_type = $this->get_content_type();
×
721
                if ( empty( $content_type ) ) {
×
722
                        return false;
×
723
                }
724

725
                $is_valid = 0 === stripos( $content_type, 'application/json' );
×
726

727
                /**
728
                 * Allow graphql to validate custom content types for HTTP POST requests
729
                 *
730
                 * @param bool $is_valid Whether the content type is valid
731
                 * @param string $content_type The content type header value that was received
732
                 *
733
                 * @since todo
734
                 */
735
                return (bool) apply_filters( 'graphql_is_valid_http_content_type', $is_valid, $content_type );
×
736
        }
737

738
        /**
739
         * Gets the content type from the request headers
740
         */
741
        private function get_content_type(): string {
×
742
                if ( isset( $_SERVER['CONTENT_TYPE'] ) ) {
×
743
                        return sanitize_text_field( $_SERVER['CONTENT_TYPE'] );
×
744
                }
745

746
                if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
×
747
                        return sanitize_text_field( $_SERVER['HTTP_CONTENT_TYPE'] );
×
748
                }
749

750
                return '';
×
751
        }
752

753
        /**
754
         * Returns the error response for invalid content type
755
         *
756
         * @return array<string,mixed>
757
         */
758
        private function get_invalid_content_type_response(): array {
×
759
                $content_type = $this->get_content_type();
×
760

761
                /**
762
                 * Filter the status code to return when the content type is invalid
763
                 *
764
                 * @param int    $status_code The status code to return
765
                 * @param string $content_type The content type header value that was received
766
                 */
767
                $filtered_status_code = apply_filters( 'graphql_invalid_content_type_status_code', 415, $content_type );
×
768

769
                // validate that the status code is in valid http status code ranges (100-599)
770
                if ( is_numeric( $filtered_status_code ) && ( $filtered_status_code > 100 && $filtered_status_code < 599 ) ) {
×
771
                        // Set status code to 415 (Unsupported Media Type)
772
                        Router::$http_status_code = $filtered_status_code;
×
773
                }
774

775
                return [
×
776
                        'errors' => [
×
777
                                [
×
778
                                        // translators: %s is the content type header value that was received
779
                                        'message' => sprintf( esc_html__( 'HTTP POST requests must have Content-Type: application/json header. Received: %s', 'wp-graphql' ), $content_type ),
×
780
                                ],
×
781
                        ],
×
782
                ];
×
783
        }
784

785
        /**
786
         * Get the operation params for the request.
787
         *
788
         * @return \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[]
789
         */
790
        public function get_params() {
1✔
791
                return $this->params;
1✔
792
        }
793

794
        /**
795
         * Returns the debug flag value
796
         *
797
         * @return int
798
         */
799
        public function get_debug_flag() {
747✔
800
                $flag = DebugFlag::INCLUDE_DEBUG_MESSAGE;
747✔
801
                if ( 0 !== get_current_user_id() ) {
747✔
802
                        // Flag 2 shows the trace data, which should require user to be logged in to see by default
803
                        $flag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
277✔
804
                }
805

806
                return true === \WPGraphQL::debug() ? $flag : DebugFlag::NONE;
747✔
807
        }
808

809
        /**
810
         * Determines if batch queries are enabled for the server.
811
         *
812
         * Default is to have batch queries enabled.
813
         *
814
         * @return bool
815
         */
816
        private function is_batch_queries_enabled() {
×
817
                $batch_queries_enabled = true;
×
818

819
                $batch_queries_setting = get_graphql_setting( 'batch_queries_enabled', 'on' );
×
820
                if ( 'off' === $batch_queries_setting ) {
×
821
                        $batch_queries_enabled = false;
×
822
                }
823

824
                /**
825
                 * Filter whether batch queries are supported or not
826
                 *
827
                 * @param bool         $batch_queries_enabled Whether Batch Queries should be enabled
828
                 * @param \GraphQL\Server\OperationParams $params Request operation params
829
                 */
830
                return apply_filters( 'graphql_is_batch_queries_enabled', $batch_queries_enabled, $this->params );
×
831
        }
832

833
        /**
834
         * Create the GraphQL server that will process the request.
835
         *
836
         * @return \GraphQL\Server\StandardServer
837
         */
838
        private function get_server() {
×
839
                $debug_flag = $this->get_debug_flag();
×
840

841
                $config = new ServerConfig();
×
842
                $config
×
843
                        ->setDebugFlag( $debug_flag )
×
844
                        ->setSchema( $this->schema )
×
845
                        ->setContext( $this->app_context )
×
846
                        ->setValidationRules( $this->validation_rules )
×
847
                        ->setQueryBatching( $this->is_batch_queries_enabled() );
×
848

849
                if ( ! empty( $this->root_value ) ) {
×
850
                        $config->setFieldResolver( $this->root_value );
×
851
                }
852

853
                if ( ! empty( $this->field_resolver ) ) {
×
854
                        $config->setFieldResolver( $this->field_resolver );
×
855
                }
856

857
                /**
858
                 * Run an action when the server config is created. The config can be acted
859
                 * upon directly to override default values or implement new features, e.g.,
860
                 * $config->setValidationRules().
861
                 *
862
                 * @param \GraphQL\Server\ServerConfig $config Server config
863
                 * @param \GraphQL\Server\OperationParams $params Request operation params
864
                 *
865
                 * @since 0.2.0
866
                 */
867
                do_action( 'graphql_server_config', $config, $this->params );
×
868

869
                return new StandardServer( $config );
×
870
        }
871
}
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

© 2026 Coveralls, Inc