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

wp-graphql / wp-graphql / 15710056976

17 Jun 2025 02:27PM UTC coverage: 84.17% (-0.1%) from 84.287%
15710056976

push

github

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

15925 of 18920 relevant lines covered (84.17%)

258.66 hits per line

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

53.02
/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
 * phpcs:disable -- PHPStan annotation.
28
 * @phpstan-import-type RootValueResolver from \GraphQL\Server\ServerConfig
29
 * @phpstan-import-type SerializableResult from \GraphQL\Executor\ExecutionResult
30
 * phpcs:enable
31
 */
32
class Request {
33

34
        /**
35
         * App context for this request.
36
         *
37
         * @var \WPGraphQL\AppContext
38
         */
39
        public $app_context;
40

41
        /**
42
         * Request data.
43
         *
44
         * @var array<string,mixed>|\GraphQL\Server\OperationParams
45
         */
46
        public $data;
47

48
        /**
49
         * Cached global post.
50
         *
51
         * @var ?\WP_Post
52
         */
53
        public $global_post;
54

55
        /**
56
         * Cached global wp_the_query.
57
         *
58
         * @var ?\WP_Query
59
         */
60
        private $global_wp_the_query;
61

62
        /**
63
         * GraphQL operation parameters for this request.
64
         * Will be an array of OperationParams if this is a batch request.
65
         *
66
         * @var \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[]
67
         */
68
        public $params;
69

70
        /**
71
         * Schema for this request.
72
         *
73
         * @var \WPGraphQL\WPSchema
74
         */
75
        public $schema;
76

77
        /**
78
         * Debug log for WPGraphQL Requests
79
         *
80
         * @var \WPGraphQL\Utils\DebugLog
81
         */
82
        public $debug_log;
83

84
        /**
85
         * The Type Registry the Schema is built with
86
         *
87
         * @var \WPGraphQL\Registry\TypeRegistry
88
         */
89
        public $type_registry;
90

91
        /**
92
         * Validation rules for execution.
93
         *
94
         * @var array<string,\GraphQL\Validator\Rules\ValidationRule>
95
         */
96
        protected $validation_rules;
97

98
        /**
99
         * The default field resolver function. Default null
100
         *
101
         * @var callable|null
102
         */
103
        protected $field_resolver;
104

105
        /**
106
         * The root value of the request. Default null;
107
         *
108
         * @var mixed|RootValueResolver
109
         */
110
        protected $root_value;
111

112
        /**
113
         * @var \WPGraphQL\Utils\QueryAnalyzer
114
         */
115
        protected $query_analyzer;
116

117
        /**
118
         * Constructor
119
         *
120
         * @param array<string,mixed> $data The request data (for Non-HTTP requests).
121
         *
122
         * @return void
123
         *
124
         * @throws \Exception
125
         */
126
        public function __construct( array $data = [] ) {
760✔
127

128
                /**
129
                 * Whether it's a GraphQL Request (http or internal)
130
                 *
131
                 * @since 0.0.5
132
                 */
133
                if ( ! defined( 'GRAPHQL_REQUEST' ) ) {
760✔
134
                        define( 'GRAPHQL_REQUEST', true );
×
135
                }
136

137
                /**
138
                 * Filter "is_graphql_request" to return true
139
                 */
140
                \WPGraphQL::set_is_graphql_request( true );
760✔
141

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

150
                // Start tracking debug log messages
151
                $this->debug_log = new DebugLog();
760✔
152

153
                // Set request data for passed-in (non-HTTP) requests.
154
                $this->data = $data;
760✔
155

156
                // Get the Type Registry
157
                $this->type_registry = \WPGraphQL::get_type_registry();
760✔
158

159
                // Get the App Context
160
                $this->app_context = \WPGraphQL::get_app_context();
760✔
161

162
                $this->root_value       = $this->get_root_value();
760✔
163
                $this->validation_rules = $this->get_validation_rules();
760✔
164
                $this->field_resolver   = $this->get_field_resolver();
760✔
165

166
                // Inject the type registry into the app context.
167
                $this->app_context->type_registry = $this->type_registry;
760✔
168

169
                // The query analyzer tracks nodes, models, list types and more
170
                // to return in headers and debug messages to help developers understand
171
                // what was resolved, how to cache it, etc.
172
                $this->query_analyzer = new QueryAnalyzer( $this );
760✔
173
                $this->query_analyzer->init();
760✔
174
        }
175

176
        /**
177
         * Get the instance of the Query Analyzer
178
         */
179
        public function get_query_analyzer(): QueryAnalyzer {
11✔
180
                return $this->query_analyzer;
11✔
181
        }
182

183
        /**
184
         * @return callable|null
185
         */
186
        protected function get_field_resolver() {
760✔
187
                return $this->field_resolver;
760✔
188
        }
189

190
        /**
191
         * Return the validation rules to use in the request
192
         *
193
         * @return array<string,\GraphQL\Validator\Rules\ValidationRule>
194
         */
195
        protected function get_validation_rules(): array {
760✔
196
                $validation_rules = GraphQL::getStandardValidationRules();
760✔
197

198
                $validation_rules['require_authentication'] = new RequireAuthentication();
760✔
199
                $validation_rules['disable_introspection']  = new DisableIntrospection();
760✔
200
                $validation_rules['query_depth']            = new QueryDepth();
760✔
201

202
                /**
203
                 * Return the validation rules to use in the request
204
                 *
205
                 * @param array<string,\GraphQL\Validator\Rules\ValidationRule> $validation_rules The validation rules to use in the request
206
                 * @param \WPGraphQL\Request                                        $request          The Request instance
207
                 */
208
                return apply_filters( 'graphql_validation_rules', $validation_rules, $this );
760✔
209
        }
210

211
        /**
212
         * Returns the root value to use in the request.
213
         *
214
         * @return mixed|RootValueResolver|null
215
         */
216
        protected function get_root_value() {
760✔
217
                /**
218
                 * Set the root value based on what was passed to the request
219
                 */
220
                $root_value = is_array( $this->data ) && ! empty( $this->data['root_value'] ) ? $this->data['root_value'] : null;
760✔
221

222
                /**
223
                 * Return the filtered root value
224
                 *
225
                 * @param mixed|RootValueResolver $root_value The root value the Schema should use to resolve with. Default null.
226
                 * @param \WPGraphQL\Request      $request    The Request instance
227
                 */
228
                return apply_filters( 'graphql_root_value', $root_value, $this );
760✔
229
        }
230

231
        /**
232
         * Apply filters and do actions before GraphQL execution
233
         *
234
         * @throws \GraphQL\Error\Error
235
         */
236
        private function before_execute(): void {
754✔
237

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

249
                if ( ! empty( $GLOBALS['wp_query'] ) ) {
754✔
250
                        $this->global_wp_the_query = clone $GLOBALS['wp_the_query'];
754✔
251
                }
252

253
                /**
254
                 * If the request is a batch request it will come back as an array
255
                 */
256
                if ( is_array( $this->params ) ) {
754✔
257

258
                        // If the request is a batch request, but batch requests are disabled,
259
                        // bail early
260
                        if ( ! $this->is_batch_queries_enabled() ) {
×
261
                                throw new Error( esc_html__( 'Batch Queries are not supported', 'wp-graphql' ) );
×
262
                        }
263

264
                        $batch_limit = get_graphql_setting( 'batch_limit', 10 );
×
265
                        $batch_limit = absint( $batch_limit ) ? absint( $batch_limit ) : 10;
×
266

267
                        // If batch requests are enabled, but a limit is set and the request exceeds the limit
268
                        // fail now
269
                        if ( $batch_limit < count( $this->params ) ) {
×
270
                                // 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.
271
                                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 ) ) );
×
272
                        }
273

274
                        /**
275
                         * Execute batch queries
276
                         *
277
                         * @param \GraphQL\Server\OperationParams[] $params The operation params of the batch request
278
                         */
279
                        do_action( 'graphql_execute_batch_queries', $this->params );
×
280

281
                        // Process the batched requests
282
                        array_walk( $this->params, [ $this, 'do_action' ] );
×
283
                } else {
284
                        $this->do_action( $this->params );
754✔
285
                }
286

287
                // Get the Schema
288
                $this->schema = \WPGraphQL::get_schema();
753✔
289

290
                /**
291
                 * This action runs before execution of a GraphQL request (regardless if it's a single or batch request)
292
                 *
293
                 * @param \WPGraphQL\Request $request The instance of the Request being executed
294
                 */
295
                do_action( 'graphql_before_execute', $this );
753✔
296
        }
297

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

321
                /**
322
                 * Access the global $wp_rest_auth_cookie
323
                 */
324
                global $wp_rest_auth_cookie;
×
325

326
                /**
327
                 * Default state of the authentication errors
328
                 */
329
                $authentication_errors = false;
×
330

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

339
                        /**
340
                         * Return filtered authentication errors
341
                         */
342
                        return $this->filtered_authentication_errors( $authentication_errors );
×
343
                }
344

345
                /**
346
                 * If the user is not logged in, determine if there's a nonce
347
                 */
348
                $nonce = null;
×
349

350
                if ( isset( $_REQUEST['_wpnonce'] ) ) {
×
351
                        $nonce = $_REQUEST['_wpnonce']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
×
352
                } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
×
353
                        $nonce = $_SERVER['HTTP_X_WP_NONCE']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
×
354
                }
355

356
                if ( null === $nonce ) {
×
357
                        // No nonce at all, so act as if it's an unauthenticated request.
358
                        wp_set_current_user( 0 );
×
359

360
                        return $this->filtered_authentication_errors( $authentication_errors );
×
361
                }
362

363
                // Check the nonce.
364
                $result = wp_verify_nonce( $nonce, 'wp_rest' );
×
365

366
                if ( ! $result ) {
×
367
                        throw new Exception( esc_html__( 'Cookie nonce is invalid', 'wp-graphql' ) );
×
368
                }
369

370
                /**
371
                 * Return the filtered authentication errors
372
                 */
373
                return $this->filtered_authentication_errors( $authentication_errors );
×
374
        }
375

376
        /**
377
         * Filter Authentication errors. Allows plugins that authenticate to hook in and prevent
378
         * execution if Authentication errors exist.
379
         *
380
         * @param bool $authentication_errors Whether there are authentication errors with the request.
381
         *
382
         * @return bool
383
         */
384
        protected function filtered_authentication_errors( $authentication_errors = false ) {
×
385

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

396
        /**
397
         * Performs actions and runs filters after execution completes
398
         *
399
         * @template T from (SerializableResult|SerializableResult[])|(\GraphQL\Executor\ExecutionResult|array<int,\GraphQL\Executor\ExecutionResult>)
400
         *
401
         * @param T $response The response from execution.  Array for batch requests, single object for individual requests.
402
         *
403
         * @return T
404
         *
405
         * @throws \Exception
406
         */
407
        private function after_execute( $response ) {
753✔
408

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

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

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

442
                if ( ! empty( $this->global_wp_the_query ) ) {
753✔
443
                        $GLOBALS['wp_the_query'] = $this->global_wp_the_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
753✔
444
                        wp_reset_query(); // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query
753✔
445
                }
446

447
                if ( ! empty( $this->global_post ) ) {
753✔
448
                        $GLOBALS['post'] = $this->global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
237✔
449
                        setup_postdata( $this->global_post );
237✔
450
                }
451

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

460
                /**
461
                 * Return the filtered response
462
                 */
463
                return $filtered_response;
753✔
464
        }
465

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

476
                /**
477
                 * Determine which params (batch or single request) to use when passing through to the actions
478
                 */
479
                $query     = null;
753✔
480
                $operation = null;
753✔
481
                $variables = null;
753✔
482
                $query_id  = null;
753✔
483

484
                if ( $this->params instanceof OperationParams ) {
753✔
485
                        $operation = $this->params->operation;
753✔
486
                        $query     = $this->params->query;
753✔
487
                        $query_id  = $this->params->queryId;
753✔
488
                        $variables = $this->params->variables;
753✔
489
                } elseif ( is_array( $this->params ) ) {
×
490
                        $operation = $this->params[ $key ]->operation ?? '';
×
491
                        $query     = $this->params[ $key ]->query ?? '';
×
492
                        $query_id  = $this->params[ $key ]->queryId ?? null;
×
493
                        $variables = $this->params[ $key ]->variables ?? null;
×
494
                }
495

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

510
                /**
511
                 * Add the debug log to the request
512
                 */
513
                if ( ! empty( $response ) ) {
753✔
514
                        if ( is_array( $response ) ) {
753✔
515
                                $response['extensions']['debug'] = $this->debug_log->get_logs();
753✔
516
                        } else {
517
                                $response->extensions['debug'] = $this->debug_log->get_logs();
×
518
                        }
519
                }
520

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

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

562
                /**
563
                 * Filter "is_graphql_request" back to false.
564
                 */
565
                \WPGraphQL::set_is_graphql_request( false );
753✔
566

567
                return $filtered_response;
753✔
568
        }
569

570
        /**
571
         * Run action for a request.
572
         *
573
         * @param \GraphQL\Server\OperationParams $params OperationParams for the request.
574
         */
575
        private function do_action( OperationParams $params ): void {
754✔
576

577
                /**
578
                 * Run an action for each request.
579
                 *
580
                 * @param ?string                         $query     The GraphQL query
581
                 * @param ?string                         $operation The name of the operation
582
                 * @param ?array<string,mixed>            $variables Variables to be passed to your GraphQL request
583
                 * @param \GraphQL\Server\OperationParams $params    The Operation Params. This includes any extra params,
584
                 *                                                   such as extensions or any other modifications to the request body
585
                 */
586
                do_action( 'do_graphql_request', $params->query, $params->operation, $params->variables, $params );
754✔
587
        }
588

589
        /**
590
         * Execute an internal request (graphql() function call).
591
         *
592
         * @return mixed[]
593
         * @phpstan-return SerializableResult|SerializableResult[]|mixed[]
594
         * @throws \Exception
595
         */
596
        public function execute() {
754✔
597
                $helper = new WPHelper();
754✔
598

599
                if ( ! $this->data instanceof OperationParams ) {
754✔
600
                        $this->params = $helper->parseRequestParams( 'POST', $this->data, [] );
754✔
601
                } else {
602
                        $this->params = $this->data;
2✔
603
                }
604

605
                if ( is_array( $this->params ) ) {
754✔
606
                        return array_map(
2✔
607
                                function ( $data ) {
2✔
608
                                        $this->data = $data;
2✔
609
                                        return $this->execute();
2✔
610
                                },
2✔
611
                                $this->params
2✔
612
                        );
2✔
613
                }
614

615
                // If $this->params isn't an array or an OperationParams instance, then something probably went wrong.
616
                if ( ! $this->params instanceof OperationParams ) {
754✔
617
                        throw new \Exception( 'Invalid request params.' );
×
618
                }
619

620
                /**
621
                 * Initialize the GraphQL Request
622
                 */
623
                $this->before_execute();
754✔
624

625
                /**
626
                 * Filter this to be anything other than null to short-circuit the request.
627
                 *
628
                 * @param ?SerializableResult $response
629
                 * @param self               $request
630
                 */
631
                $response = apply_filters( 'pre_graphql_execute_request', null, $this );
753✔
632

633
                if ( null === $response ) {
753✔
634
                        /**
635
                         * @var \GraphQL\Server\OperationParams $params
636
                         */
637
                        $params = $this->params;
753✔
638

639
                        /**
640
                         * Allow the query string to be determined by a filter. Ex, when params->queryId is present, query can be retrieved.
641
                         *
642
                         * @param string                          $query
643
                         * @param \GraphQL\Server\OperationParams $params
644
                         */
645
                        $query = apply_filters(
753✔
646
                                'graphql_execute_query_params',
753✔
647
                                $params->query ?? '',
753✔
648
                                $params
753✔
649
                        );
753✔
650

651
                        $result = GraphQL::executeQuery(
753✔
652
                                $this->schema,
753✔
653
                                $query,
753✔
654
                                $this->root_value,
753✔
655
                                $this->app_context,
753✔
656
                                $params->variables ?? null,
753✔
657
                                $params->operation ?? null,
753✔
658
                                $this->field_resolver,
753✔
659
                                $this->validation_rules
753✔
660
                        );
753✔
661

662
                        /**
663
                         * Return the result of the request
664
                         */
665
                        $response = $result->toArray( $this->get_debug_flag() );
753✔
666
                }
667

668
                /**
669
                 * Ensure the response is returned as a proper, populated array. Otherwise add an error.
670
                 */
671
                if ( empty( $response ) || ! is_array( $response ) ) {
753✔
672
                        $response = [
×
673
                                'errors' => __( 'The GraphQL request returned an invalid response', 'wp-graphql' ),
×
674
                        ];
×
675
                }
676

677
                /**
678
                 * If the request is a batch request it will come back as an array
679
                 */
680
                return $this->after_execute( $response );
753✔
681
        }
682

683
        /**
684
         * Execute an HTTP request.
685
         *
686
         * @return SerializableResult|(\GraphQL\Executor\ExecutionResult|array<int,\GraphQL\Executor\ExecutionResult>)
687
         * @throws \Exception
688
         */
689
        public function execute_http() {
×
690
                if ( ! $this->is_valid_http_content_type() ) {
×
691
                        return $this->get_invalid_content_type_response();
×
692
                }
693

694
                /**
695
                 * Parse HTTP request.
696
                 */
697
                $helper       = new WPHelper();
×
698
                $this->params = $helper->parseHttpRequest();
×
699

700
                /**
701
                 * Initialize the GraphQL Request
702
                 */
703
                $this->before_execute();
×
704

705
                /**
706
                 * Get the response.
707
                 */
708
                $response = apply_filters( 'pre_graphql_execute_request', null, $this );
×
709

710
                /**
711
                 * If no cached response, execute the query
712
                 */
713
                if ( null === $response ) {
×
714
                        $server   = $this->get_server();
×
715
                        $response = $server->executeRequest( $this->params );
×
716
                }
717

718
                return $this->after_execute( $response );
×
719
        }
720

721
        /**
722
         * Validates the content type for HTTP POST requests
723
         */
724
        private function is_valid_http_content_type(): bool {
×
725
                if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
×
726
                        return true;
×
727
                }
728

729
                $content_type = $this->get_content_type();
×
730
                if ( empty( $content_type ) ) {
×
731
                        return false;
×
732
                }
733

734
                $is_valid = 0 === stripos( $content_type, 'application/json' );
×
735

736
                /**
737
                 * Allow graphql to validate custom content types for HTTP POST requests
738
                 *
739
                 * @param bool $is_valid Whether the content type is valid
740
                 * @param string $content_type The content type header value that was received
741
                 *
742
                 * @since 2.1.0
743
                 */
744
                return (bool) apply_filters( 'graphql_is_valid_http_content_type', $is_valid, $content_type );
×
745
        }
746

747
        /**
748
         * Gets the content type from the request headers
749
         */
750
        private function get_content_type(): string {
×
751
                if ( isset( $_SERVER['CONTENT_TYPE'] ) ) {
×
752
                        return sanitize_text_field( $_SERVER['CONTENT_TYPE'] );
×
753
                }
754

755
                if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
×
756
                        return sanitize_text_field( $_SERVER['HTTP_CONTENT_TYPE'] );
×
757
                }
758

759
                return '';
×
760
        }
761

762
        /**
763
         * Returns the error response for invalid content type
764
         *
765
         * @return array{errors: array{array{message: string}}}
766
         */
767
        private function get_invalid_content_type_response(): array {
×
768
                $content_type = $this->get_content_type();
×
769

770
                /**
771
                 * Filter the status code to return when the content type is invalid
772
                 *
773
                 * @param int    $status_code The status code to return. Default 415.
774
                 * @param string $content_type The content type header value that was received.
775
                 */
776
                $filtered_status_code = apply_filters( 'graphql_invalid_content_type_status_code', 415, $content_type );
×
777

778
                // Set the status code to the filtered value if it's a valid status code.
779
                if ( is_numeric( $filtered_status_code ) ) {
×
780
                        $filtered_status_code = (int) $filtered_status_code;
×
781

782
                        if ( $filtered_status_code > 100 && $filtered_status_code < 599 ) {
×
783
                                Router::$http_status_code = $filtered_status_code;
×
784
                        }
785
                }
786

787
                return [
×
788
                        'errors' => [
×
789
                                [
×
790
                                        // translators: %s is the content type header value that was received
791
                                        'message' => sprintf( esc_html__( 'HTTP POST requests must have Content-Type: application/json header. Received: %s', 'wp-graphql' ), $content_type ),
×
792
                                ],
×
793
                        ],
×
794
                ];
×
795
        }
796

797
        /**
798
         * Get the operation params for the request.
799
         *
800
         * @return \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[]
801
         */
802
        public function get_params() {
1✔
803
                return $this->params;
1✔
804
        }
805

806
        /**
807
         * Returns the debug flag value
808
         *
809
         * @return int
810
         */
811
        public function get_debug_flag() {
753✔
812
                $flag = DebugFlag::INCLUDE_DEBUG_MESSAGE;
753✔
813
                if ( 0 !== get_current_user_id() ) {
753✔
814
                        // Flag 2 shows the trace data, which should require user to be logged in to see by default
815
                        $flag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
277✔
816
                }
817

818
                return true === \WPGraphQL::debug() ? $flag : DebugFlag::NONE;
753✔
819
        }
820

821
        /**
822
         * Determines if batch queries are enabled for the server.
823
         *
824
         * Default is to have batch queries enabled.
825
         */
826
        private function is_batch_queries_enabled(): bool {
×
827
                $batch_queries_enabled = true;
×
828

829
                $batch_queries_setting = get_graphql_setting( 'batch_queries_enabled', 'on' );
×
830
                if ( 'off' === $batch_queries_setting ) {
×
831
                        $batch_queries_enabled = false;
×
832
                }
833

834
                /**
835
                 * Filter whether batch queries are supported or not
836
                 *
837
                 * @param bool                                                              $batch_queries_enabled Whether Batch Queries should be enabled
838
                 * @param \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[] $params Request operation params
839
                 */
840
                return (bool) apply_filters( 'graphql_is_batch_queries_enabled', $batch_queries_enabled, $this->params );
×
841
        }
842

843
        /**
844
         * Create the GraphQL server that will process the request.
845
         */
846
        private function get_server(): StandardServer {
×
847
                $debug_flag = $this->get_debug_flag();
×
848

849
                $config = new ServerConfig();
×
850
                $config
×
851
                        ->setDebugFlag( $debug_flag )
×
852
                        ->setSchema( $this->schema )
×
853
                        ->setContext( $this->app_context )
×
854
                        ->setValidationRules( $this->validation_rules )
×
855
                        ->setQueryBatching( $this->is_batch_queries_enabled() );
×
856

857
                if ( ! empty( $this->root_value ) ) {
×
858
                        $config->setRootValue( $this->root_value );
×
859
                }
860

861
                if ( ! empty( $this->field_resolver ) ) {
×
862
                        $config->setFieldResolver( $this->field_resolver );
×
863
                }
864

865
                /**
866
                 * Run an action when the server config is created. The config can be acted
867
                 * upon directly to override default values or implement new features, e.g.,
868
                 * $config->setValidationRules().
869
                 *
870
                 * @param \GraphQL\Server\ServerConfig                                      $config Server config
871
                 * @param \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[] $params Request operation params
872
                 *
873
                 * @since 0.2.0
874
                 */
875
                do_action( 'graphql_server_config', $config, $this->params );
×
876

877
                return new StandardServer( $config );
×
878
        }
879
}
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