• 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

53.3
/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
                // Inject the type registry into the app context.
162
                $this->app_context->type_registry = $this->type_registry;
754✔
163

164
                $this->query_analyzer = new QueryAnalyzer( $this );
754✔
165

166
                // The query analyzer tracks nodes, models, list types and more
167
                // to return in headers and debug messages to help developers understand
168
                // what was resolved, how to cache it, etc.
169
                $this->query_analyzer->init();
754✔
170
        }
171

172
        /**
173
         * Get the instance of the Query Analyzer
174
         */
175
        public function get_query_analyzer(): QueryAnalyzer {
11✔
176
                return $this->query_analyzer;
11✔
177
        }
178

179
        /**
180
         * @return callable|null
181
         */
182
        protected function get_field_resolver() {
754✔
183
                return $this->field_resolver;
754✔
184
        }
185

186
        /**
187
         * Return the validation rules to use in the request
188
         *
189
         * @return array<int|string,\GraphQL\Validator\Rules\ValidationRule>
190
         */
191
        protected function get_validation_rules(): array {
754✔
192
                $validation_rules = GraphQL::getStandardValidationRules();
754✔
193

194
                $validation_rules['require_authentication'] = new RequireAuthentication();
754✔
195
                $validation_rules['disable_introspection']  = new DisableIntrospection();
754✔
196
                $validation_rules['query_depth']            = new QueryDepth();
754✔
197

198
                /**
199
                 * Return the validation rules to use in the request
200
                 *
201
                 * @param array<int|string,\GraphQL\Validator\Rules\ValidationRule> $validation_rules The validation rules to use in the request
202
                 * @param \WPGraphQL\Request                                        $request          The Request instance
203
                 */
204
                return apply_filters( 'graphql_validation_rules', $validation_rules, $this );
754✔
205
        }
206

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

218
                /**
219
                 * Return the filtered root value
220
                 *
221
                 * @param mixed              $root_value The root value the Schema should use to resolve with. Default null.
222
                 * @param \WPGraphQL\Request $request    The Request instance
223
                 */
224
                return apply_filters( 'graphql_root_value', $root_value, $this );
754✔
225
        }
226

227
        /**
228
         * Apply filters and do actions before GraphQL execution
229
         *
230
         * @throws \GraphQL\Error\Error
231
         */
232
        private function before_execute(): void {
748✔
233

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

245
                if ( ! empty( $GLOBALS['wp_query'] ) ) {
748✔
246
                        $this->global_wp_the_query = clone $GLOBALS['wp_the_query'];
748✔
247
                }
248

249
                /**
250
                 * If the request is a batch request it will come back as an array
251
                 */
252
                if ( is_array( $this->params ) ) {
748✔
253

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

260
                        $batch_limit = get_graphql_setting( 'batch_limit', 10 );
×
261
                        $batch_limit = absint( $batch_limit ) ? absint( $batch_limit ) : 10;
×
262

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

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

277
                        // Process the batched requests
278
                        array_walk( $this->params, [ $this, 'do_action' ] );
×
279
                } else {
280
                        $this->do_action( $this->params );
748✔
281
                }
282

283
                // Get the Schema
284
                $this->schema = \WPGraphQL::get_schema();
747✔
285

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

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

317
                /**
318
                 * Access the global $wp_rest_auth_cookie
319
                 */
320
                global $wp_rest_auth_cookie;
×
321

322
                /**
323
                 * Default state of the authentication errors
324
                 */
325
                $authentication_errors = false;
×
326

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

335
                        /**
336
                         * Return filtered authentication errors
337
                         */
338
                        return $this->filtered_authentication_errors( $authentication_errors );
×
339

340
                        /**
341
                         * If the user is not logged in, determine if there's a nonce
342
                         */
343
                } else {
344
                        $nonce = null;
×
345

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

352
                        if ( null === $nonce ) {
×
353
                                // No nonce at all, so act as if it's an unauthenticated request.
354
                                wp_set_current_user( 0 );
×
355

356
                                return $this->filtered_authentication_errors( $authentication_errors );
×
357
                        }
358

359
                        // Check the nonce.
360
                        $result = wp_verify_nonce( $nonce, 'wp_rest' );
×
361

362
                        if ( ! $result ) {
×
363
                                throw new Exception( esc_html__( 'Cookie nonce is invalid', 'wp-graphql' ) );
×
364
                        }
365
                }
366

367
                /**
368
                 * Return the filtered authentication errors
369
                 */
370
                return $this->filtered_authentication_errors( $authentication_errors );
×
371
        }
372

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

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

393
        /**
394
         * Performs actions and runs filters after execution completes
395
         *
396
         * @param mixed|array<string,mixed>|object $response The response from execution. Array for batch requests, single object for individual requests.
397
         *
398
         * @return mixed[]
399
         *
400
         * @throws \Exception
401
         */
402
        private function after_execute( $response ) {
747✔
403

404
                /**
405
                 * If there are authentication errors, prevent execution and throw an exception.
406
                 */
407
                if ( false !== $this->has_authentication_errors() ) {
747✔
408
                        throw new Exception( esc_html__( 'Authentication Error', 'wp-graphql' ) );
×
409
                }
410

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

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

437
                if ( ! empty( $this->global_wp_the_query ) ) {
747✔
438
                        $GLOBALS['wp_the_query'] = $this->global_wp_the_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
747✔
439
                        wp_reset_query(); // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query
747✔
440
                }
441

442
                if ( ! empty( $this->global_post ) ) {
747✔
443
                        $GLOBALS['post'] = $this->global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
237✔
444
                        setup_postdata( $this->global_post );
237✔
445
                }
446

447
                /**
448
                 * Run an action after GraphQL Execution
449
                 *
450
                 * @param mixed[] $filtered_response The response of the entire operation. Could be a single operation or a batch operation
451
                 * @param \WPGraphQL\Request  $request Instance of the Request being executed
452
                 */
453
                do_action( 'graphql_after_execute', $filtered_response, $this );
747✔
454

455
                /**
456
                 * Return the filtered response
457
                 */
458
                return $filtered_response;
747✔
459
        }
460

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

471
                /**
472
                 * Determine which params (batch or single request) to use when passing through to the actions
473
                 */
474
                $query     = null;
747✔
475
                $operation = null;
747✔
476
                $variables = null;
747✔
477
                $query_id  = null;
747✔
478

479
                if ( $this->params instanceof OperationParams ) {
747✔
480
                        $operation = $this->params->operation;
747✔
481
                        $query     = $this->params->query;
747✔
482
                        $query_id  = $this->params->queryId;
747✔
483
                        $variables = $this->params->variables;
747✔
484
                } elseif ( is_array( $this->params ) ) {
×
485
                        $operation = $this->params[ $key ]->operation ?? '';
×
486
                        $query     = $this->params[ $key ]->query ?? '';
×
487
                        $query_id  = $this->params[ $key ]->queryId ?? null;
×
488
                        $variables = $this->params[ $key ]->variables ?? null;
×
489
                }
490

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

505
                /**
506
                 * Add the debug log to the request
507
                 */
508
                if ( ! empty( $response ) ) {
747✔
509
                        if ( is_array( $response ) ) {
747✔
510
                                $response['extensions']['debug'] = $this->debug_log->get_logs();
747✔
511
                        } else {
512
                                $response->extensions['debug'] = $this->debug_log->get_logs();
×
513
                        }
514
                }
515

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

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

557
                /**
558
                 * Filter "is_graphql_request" back to false.
559
                 */
560
                \WPGraphQL::set_is_graphql_request( false );
747✔
561

562
                return $filtered_response;
747✔
563
        }
564

565
        /**
566
         * Run action for a request.
567
         *
568
         * @param \GraphQL\Server\OperationParams $params OperationParams for the request.
569
         */
570
        private function do_action( OperationParams $params ): void {
748✔
571

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

584
        /**
585
         * Execute an internal request (graphql() function call).
586
         *
587
         * @return array<string,mixed>
588
         * @throws \Exception
589
         */
590
        public function execute() {
748✔
591
                $helper = new WPHelper();
748✔
592

593
                if ( ! $this->data instanceof OperationParams ) {
748✔
594
                        $this->params = $helper->parseRequestParams( 'POST', $this->data, [] );
748✔
595
                } else {
596
                        $this->params = $this->data;
2✔
597
                }
598

599
                if ( is_array( $this->params ) ) {
748✔
600
                        return array_map(
2✔
601
                                function ( $data ) {
2✔
602
                                        $this->data = $data;
2✔
603
                                        return $this->execute();
2✔
604
                                },
2✔
605
                                $this->params
2✔
606
                        );
2✔
607
                }
608

609
                // If $this->params isnt an array or an OperationParams instance, then something probably went wrong.
610
                if ( ! $this->params instanceof OperationParams ) {
748✔
611
                        throw new \Exception( 'Invalid request params.' );
×
612
                }
613

614
                /**
615
                 * Initialize the GraphQL Request
616
                 */
617
                $this->before_execute();
748✔
618
                $response = apply_filters( 'pre_graphql_execute_request', null, $this );
747✔
619

620
                if ( null === $response ) {
747✔
621

622
                        /**
623
                         * Allow the query string to be determined by a filter. Ex, when params->queryId is present, query can be retrieved.
624
                         */
625
                        $query = apply_filters(
747✔
626
                                'graphql_execute_query_params',
747✔
627
                                isset( $this->params->query ) ? $this->params->query : '',
747✔
628
                                $this->params
747✔
629
                        );
747✔
630

631
                        $result = GraphQL::executeQuery(
747✔
632
                                $this->schema,
747✔
633
                                $query,
747✔
634
                                $this->root_value,
747✔
635
                                $this->app_context,
747✔
636
                                isset( $this->params->variables ) ? $this->params->variables : null,
747✔
637
                                isset( $this->params->operation ) ? $this->params->operation : null,
747✔
638
                                $this->field_resolver,
747✔
639
                                $this->validation_rules
747✔
640
                        );
747✔
641

642
                        /**
643
                         * Return the result of the request
644
                         */
645
                        $response = $result->toArray( $this->get_debug_flag() );
747✔
646
                }
647

648
                /**
649
                 * Ensure the response is returned as a proper, populated array. Otherwise add an error.
650
                 */
651
                if ( empty( $response ) || ! is_array( $response ) ) {
747✔
652
                        $response = [
×
653
                                'errors' => __( 'The GraphQL request returned an invalid response', 'wp-graphql' ),
×
654
                        ];
×
655
                }
656

657
                /**
658
                 * If the request is a batch request it will come back as an array
659
                 */
660
                return $this->after_execute( $response );
747✔
661
        }
662

663
        /**
664
         * Execute an HTTP request.
665
         *
666
         * @return array<string,mixed>
667
         * @throws \Exception
668
         */
669
        public function execute_http() {
×
670
                if ( ! $this->is_valid_http_content_type() ) {
×
671
                        return $this->get_invalid_content_type_response();
×
672
                }
673

674
                /**
675
                 * Parse HTTP request.
676
                 */
677
                $helper       = new WPHelper();
×
678
                $this->params = $helper->parseHttpRequest();
×
679

680
                /**
681
                 * Initialize the GraphQL Request
682
                 */
683
                $this->before_execute();
×
684

685
                /**
686
                 * Get the response.
687
                 */
688
                $response = apply_filters( 'pre_graphql_execute_request', null, $this );
×
689

690
                /**
691
                 * If no cached response, execute the query
692
                 */
693
                if ( null === $response ) {
×
694
                        $server   = $this->get_server();
×
695
                        $response = $server->executeRequest( $this->params );
×
696
                }
697

698
                return $this->after_execute( $response );
×
699
        }
700

701
        /**
702
         * Validates the content type for HTTP POST requests
703
         */
704
        private function is_valid_http_content_type(): bool {
×
705
                if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
×
706
                        return true;
×
707
                }
708

709
                $content_type = $this->get_content_type();
×
710
                if ( empty( $content_type ) ) {
×
711
                        return false;
×
712
                }
713

714
                $is_valid = 0 === stripos( $content_type, 'application/json' );
×
715

716
                /**
717
                 * Allow graphql to validate custom content types for HTTP POST requests
718
                 *
719
                 * @param bool $is_valid Whether the content type is valid
720
                 * @param string $content_type The content type header value that was received
721
                 *
722
                 * @since 2.1.0
723
                 */
724
                return (bool) apply_filters( 'graphql_is_valid_http_content_type', $is_valid, $content_type );
×
725
        }
726

727
        /**
728
         * Gets the content type from the request headers
729
         */
730
        private function get_content_type(): string {
×
731
                if ( isset( $_SERVER['CONTENT_TYPE'] ) ) {
×
732
                        return sanitize_text_field( $_SERVER['CONTENT_TYPE'] );
×
733
                }
734

735
                if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) ) {
×
736
                        return sanitize_text_field( $_SERVER['HTTP_CONTENT_TYPE'] );
×
737
                }
738

739
                return '';
×
740
        }
741

742
        /**
743
         * Returns the error response for invalid content type
744
         *
745
         * @return array<string,mixed>
746
         */
747
        private function get_invalid_content_type_response(): array {
×
748
                $content_type = $this->get_content_type();
×
749

750
                /**
751
                 * Filter the status code to return when the content type is invalid
752
                 *
753
                 * @param int    $status_code The status code to return
754
                 * @param string $content_type The content type header value that was received
755
                 */
756
                $filtered_status_code = apply_filters( 'graphql_invalid_content_type_status_code', 415, $content_type );
×
757

758
                // validate that the status code is in valid http status code ranges (100-599)
759
                if ( is_numeric( $filtered_status_code ) && ( $filtered_status_code > 100 && $filtered_status_code < 599 ) ) {
×
760
                        // Set status code to 415 (Unsupported Media Type)
761
                        Router::$http_status_code = $filtered_status_code;
×
762
                }
763

764
                return [
×
765
                        'errors' => [
×
766
                                [
×
767
                                        // translators: %s is the content type header value that was received
768
                                        'message' => sprintf( esc_html__( 'HTTP POST requests must have Content-Type: application/json header. Received: %s', 'wp-graphql' ), $content_type ),
×
769
                                ],
×
770
                        ],
×
771
                ];
×
772
        }
773

774
        /**
775
         * Get the operation params for the request.
776
         *
777
         * @return \GraphQL\Server\OperationParams|\GraphQL\Server\OperationParams[]
778
         */
779
        public function get_params() {
1✔
780
                return $this->params;
1✔
781
        }
782

783
        /**
784
         * Returns the debug flag value
785
         *
786
         * @return int
787
         */
788
        public function get_debug_flag() {
747✔
789
                $flag = DebugFlag::INCLUDE_DEBUG_MESSAGE;
747✔
790
                if ( 0 !== get_current_user_id() ) {
747✔
791
                        // Flag 2 shows the trace data, which should require user to be logged in to see by default
792
                        $flag = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
277✔
793
                }
794

795
                return true === \WPGraphQL::debug() ? $flag : DebugFlag::NONE;
747✔
796
        }
797

798
        /**
799
         * Determines if batch queries are enabled for the server.
800
         *
801
         * Default is to have batch queries enabled.
802
         */
803
        private function is_batch_queries_enabled(): bool {
×
804
                $batch_queries_enabled = true;
×
805

806
                $batch_queries_setting = get_graphql_setting( 'batch_queries_enabled', 'on' );
×
807
                if ( 'off' === $batch_queries_setting ) {
×
808
                        $batch_queries_enabled = false;
×
809
                }
810

811
                /**
812
                 * Filter whether batch queries are supported or not
813
                 *
814
                 * @param bool         $batch_queries_enabled Whether Batch Queries should be enabled
815
                 * @param \GraphQL\Server\OperationParams $params Request operation params
816
                 */
817
                return (bool) apply_filters( 'graphql_is_batch_queries_enabled', $batch_queries_enabled, $this->params );
×
818
        }
819

820
        /**
821
         * Create the GraphQL server that will process the request.
822
         */
823
        private function get_server(): StandardServer {
×
824
                $debug_flag = $this->get_debug_flag();
×
825

826
                $config = new ServerConfig();
×
827
                $config
×
828
                        ->setDebugFlag( $debug_flag )
×
829
                        ->setSchema( $this->schema )
×
830
                        ->setContext( $this->app_context )
×
831
                        ->setValidationRules( $this->validation_rules )
×
832
                        ->setQueryBatching( $this->is_batch_queries_enabled() );
×
833

834
                if ( ! empty( $this->root_value ) ) {
×
835
                        $config->setFieldResolver( $this->root_value );
×
836
                }
837

838
                if ( ! empty( $this->field_resolver ) ) {
×
839
                        $config->setFieldResolver( $this->field_resolver );
×
840
                }
841

842
                /**
843
                 * Run an action when the server config is created. The config can be acted
844
                 * upon directly to override default values or implement new features, e.g.,
845
                 * $config->setValidationRules().
846
                 *
847
                 * @param \GraphQL\Server\ServerConfig $config Server config
848
                 * @param \GraphQL\Server\OperationParams $params Request operation params
849
                 *
850
                 * @since 0.2.0
851
                 */
852
                do_action( 'graphql_server_config', $config, $this->params );
×
853

854
                return new StandardServer( $config );
×
855
        }
856
}
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