PluginProbe
WPGraphQL / trunk
WPGraphQL vtrunk
2.22.3 2.22.2 2.22.1 2.22.0 2.21.1 2.21.0 2.20.0 2.19.0 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.1 2.14.0 2.13.0 2.2.0 2.3.0 2.3.3 2.3.6 2.3.8 2.5.0 2.5.1 2.5.2 2.5.3 All 177 releases
wp-graphql / access-functions.php

access-functions.php in WPGraphQL trunk, at access-functions.php

1,042 lines 31.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This file contains access functions for various class methods
4 *
5 * @package WPGraphQL
6 * @since 0.0.2
7 */
8
9 use GraphQL\Type\Definition\Type;
10 use WPGraphQL\Registry\TypeRegistry;
11 use WPGraphQL\Request;
12 use WPGraphQL\Router;
13 use WPGraphQL\Utils\Utils;
14
15 // Exit if accessed directly.
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * Formats a string for use as a GraphQL name.
22 *
23 * Per the GraphQL spec, characters in names are limited to Latin ASCII letter, digits, or underscores.
24 *
25 * @see http://spec.graphql.org/draft/#sec-Names
26 * @uses graphql_pre_format_name filter.
27 *
28 * @param string $name The name to format.
29 * @param string $replacement The replacement character for invalid characters. Defaults to '_'.
30 * @param string $regex The regex to use to match invalid characters. Defaults to '/[^A-Za-z0-9_]/i'.
31 *
32 * @return string The formatted name, safe for use as a GraphQL name.
33 *
34 * @since v1.17.0
35 */
36 function graphql_format_name( string $name, string $replacement = '_', string $regex = '/[^A-Za-z0-9_]/i' ): string {
37 return Utils::format_graphql_name( $name, $replacement, $regex );
38 }
39
40 /**
41 * Formats the name of a field so that it plays nice with GraphiQL
42 *
43 * @param string $field_name Name of the field
44 *
45 * @return string Name of the field
46 * @since 0.0.2
47 *
48 * @todo refactor to use Utils::format_field_name()
49 */
50 function graphql_format_field_name( $field_name ): string {
51 // Bail if empty.
52 if ( empty( $field_name ) ) {
53 return '';
54 }
55
56 // First strip out the non-alphanumeric characters.
57 $formatted_field_name = graphql_format_name( $field_name, ' ', '/[^A-Za-z0-9]/i' );
58
59 // If the field name is empty, return the original field name for the error.
60 if ( empty( $formatted_field_name ) ) {
61 return $field_name;
62 }
63
64 // Then convert string to camelCase.
65 return str_replace( ' ', '', lcfirst( ucwords( $formatted_field_name ) ) );
66 }
67
68 /**
69 * Formats the name of a Type so that it plays nice with GraphiQL
70 *
71 * @param string $type_name Name of the field
72 *
73 * @return string Name of the field
74 * @since 0.0.2
75 */
76 function graphql_format_type_name( $type_name ): string {
77 // Bail if empty.
78 if ( empty( $type_name ) ) {
79 return '';
80 }
81
82 $formatted_type_name = graphql_format_name( $type_name, ' ', '/[^A-Za-z0-9]/i' );
83
84 // If the field name is empty, return the original field name for the error.
85 if ( empty( $formatted_type_name ) ) {
86 return $type_name;
87 }
88
89 // Then convert the string to PascalCase.
90 return str_replace( ' ', '', ucfirst( ucwords( $formatted_type_name ) ) );
91 }
92
93 /**
94 * Provides a simple way to run a GraphQL query without posting a request to the endpoint.
95 *
96 * @param array<string,mixed> $request_data The GraphQL request data (query, variables, operation_name).
97 * @param bool $return_request If true, return the Request object, else return the results of the request execution
98 *
99 * @return array<string,mixed>|\WPGraphQL\Request
100 * @phpstan-return ( $return_request is true ? \WPGraphQL\Request : array<string,mixed> )
101 *
102 * @throws \Exception
103 * @since 0.2.0
104 */
105 function graphql( array $request_data = [], bool $return_request = false ) {
106 $request = new Request( $request_data );
107
108 // allow calls to graphql() to return the full Request instead of
109 // just the results of the request execution
110 if ( true === $return_request ) {
111 return $request;
112 }
113
114 return $request->execute();
115 }
116
117 /**
118 * Previous access function for running GraphQL queries directly. This function will
119 * eventually be deprecated in favor of `graphql`.
120 *
121 * @param string $query The GraphQL query to run
122 * @param string $operation_name The name of the operation
123 * @param array<string,mixed> $variables Variables to be passed to your GraphQL request
124 * @param bool $return_request If true, return the Request object, else return the results of the request execution
125 *
126 * @return array<string,mixed>|\WPGraphQL\Request
127 *
128 * @phpstan-return ( $return_request is true ? \WPGraphQL\Request : array<string,mixed> )
129 *
130 * @throws \Exception
131 * @since 0.0.2
132 */
133 function do_graphql_request( $query, $operation_name = '', $variables = [], $return_request = false ) {
134 return graphql(
135 [
136 'query' => $query,
137 'variables' => $variables,
138 'operation_name' => $operation_name,
139 ],
140 $return_request
141 );
142 }
143
144 /**
145 * Determine when to register types.
146 *
147 * @return 'graphql_register_initial_types'|'graphql_register_types'|'graphql_register_types_late'
148 *
149 * @since 0.4.3
150 */
151 function get_graphql_register_action(): string {
152 $action = 'graphql_register_types_late';
153 if ( ! did_action( 'graphql_register_initial_types' ) ) {
154 $action = 'graphql_register_initial_types';
155 } elseif ( ! did_action( 'graphql_register_types' ) ) {
156 $action = 'graphql_register_types';
157 }
158
159 return $action;
160 }
161
162 /**
163 * Given a type name and interface name, this applies the interface to the Type.
164 *
165 * Should be used at the `graphql_register_types` hook.
166 *
167 * @param string|string[] $interface_names Array of one or more names of the GraphQL Interfaces to apply to the GraphQL Types
168 * @param string|string[] $type_names Array of one or more names of the GraphQL Types to apply the interfaces to.
169 *
170 * Example:
171 * The following would register the "MyNewInterface" interface to the Post and Page type in the
172 * Schema.
173 *
174 * register_graphql_interfaces_to_types( [ 'MyNewInterface' ], [ 'Post', 'Page' ] );
175 *
176 * @since 0.9.0
177 */
178 function register_graphql_interfaces_to_types( $interface_names, $type_names ): void {
179 // Bail if no interfaces or types.
180 if ( empty( $type_names ) || empty( $interface_names ) ) {
181 return;
182 }
183
184 if ( is_string( $type_names ) ) {
185 $type_names = [ $type_names ];
186 }
187
188 if ( is_string( $interface_names ) ) {
189 $interface_names = [ $interface_names ];
190 }
191
192 // Bail if they're still not arrays.
193 if ( ! is_array( $type_names ) || ! is_array( $interface_names ) ) {
194 return;
195 }
196
197 foreach ( $type_names as $type_name ) {
198 // Filter the GraphQL Object Type Interface to apply the interface
199 add_filter(
200 'graphql_type_interfaces',
201 static function ( $interfaces, $config ) use ( $type_name, $interface_names ) {
202 $interfaces = is_array( $interfaces ) ? $interfaces : [];
203
204 if ( strtolower( $type_name ) === strtolower( $config['name'] ) ) {
205 $interfaces = array_unique( array_merge( $interfaces, $interface_names ) );
206 }
207
208 return $interfaces;
209 },
210 10,
211 2
212 );
213 }
214 }
215
216 /**
217 * Given a Type Name and a $config array, this adds a Type to the TypeRegistry
218 *
219 * @param string $type_name The name of the Type to register
220 * @param array<string,mixed> $config The Type config
221 *
222 * @since 0.1.0
223 */
224 function register_graphql_type( string $type_name, array $config ): void {
225 add_action(
226 get_graphql_register_action(),
227 static function ( TypeRegistry $type_registry ) use ( $type_name, $config ): void {
228 $type_registry->register_type( $type_name, $config );
229 },
230 10
231 );
232 }
233
234 /**
235 * Given a Type Name and a $config array, this adds an Interface Type to the TypeRegistry
236 *
237 * @param string $type_name The name of the Type to register
238 * @param array<string,mixed> $config The Type config
239 *
240 * @since 0.4.0
241 */
242 function register_graphql_interface_type( string $type_name, $config ): void {
243 add_action(
244 get_graphql_register_action(),
245 static function ( TypeRegistry $type_registry ) use ( $type_name, $config ): void {
246 $type_registry->register_interface_type( $type_name, $config );
247 },
248 10
249 );
250 }
251
252 /**
253 * Given a Type Name and a $config array, this adds an ObjectType to the TypeRegistry
254 *
255 * @param string $type_name The name of the Type to register
256 * @param array<string,mixed> $config The Type config
257 *
258 * @since 0.1.0
259 */
260 function register_graphql_object_type( string $type_name, array $config ): void {
261 $config['kind'] = 'object';
262 register_graphql_type( $type_name, $config );
263 }
264
265 /**
266 * Given a Type Name and a $config array, this adds an InputType to the TypeRegistry
267 *
268 * @param string $type_name The name of the Type to register
269 * @param array<string,mixed> $config The Type config
270 *
271 * @since 0.1.0
272 */
273 function register_graphql_input_type( string $type_name, array $config ): void {
274 $config['kind'] = 'input';
275 register_graphql_type( $type_name, $config );
276 }
277
278 /**
279 * Given a Type Name and a $config array, this adds an UnionType to the TypeRegistry
280 *
281 * @param string $type_name The name of the Type to register
282 * @param array<string,mixed> $config The Type config
283 *
284 * @throws \Exception
285 *
286 * @since 0.1.0
287 */
288 function register_graphql_union_type( string $type_name, array $config ): void {
289 add_action(
290 get_graphql_register_action(),
291 static function ( TypeRegistry $type_registry ) use ( $type_name, $config ): void {
292 $config['kind'] = 'union';
293 $type_registry->register_type( $type_name, $config );
294 },
295 10
296 );
297 }
298
299 /**
300 * Given a Type Name and a $config array, this adds an EnumType to the TypeRegistry
301 *
302 * @param string $type_name The name of the Type to register
303 * @param array<string,mixed> $config The Type config
304 *
305 * @phpstan-param array{
306 * description?: string|callable():string|null,
307 * values: array<string, array{
308 * name?: string,
309 * value?: mixed,
310 * deprecationReason?: string|callable():string|null,
311 * description?: string|callable():string|null,
312 * astNode?: \GraphQL\Language\AST\EnumValueDefinitionNode|null
313 * }>,
314 * astNode?: \GraphQL\Language\AST\EnumTypeDefinitionNode|null,
315 * extensionASTNodes?: \GraphQL\Language\AST\EnumTypeExtensionNode[]|null,
316 * kind?: 'enum'|null
317 * } $config
318 *
319 * @since 0.1.0
320 */
321 function register_graphql_enum_type( string $type_name, array $config ): void {
322 $config['kind'] = 'enum';
323 register_graphql_type( $type_name, $config );
324 }
325
326 /**
327 * Given a Type Name, Field Name, and a $config array, this adds a Field to a registered Type in
328 * the TypeRegistry
329 *
330 * @param string $type_name The name of the Type to add the field to
331 * @param string $field_name The name of the Field to add to the Type
332 * @param array<string,mixed> $config The Type config
333 *
334 * @throws \Exception
335 * @since 0.1.0
336 */
337 function register_graphql_field( string $type_name, string $field_name, array $config ): void {
338 add_action(
339 get_graphql_register_action(),
340 static function ( TypeRegistry $type_registry ) use ( $type_name, $field_name, $config ): void {
341 $type_registry->register_field( $type_name, $field_name, $config );
342 },
343 10
344 );
345 }
346
347 /**
348 * Given a Type Name and an array of field configs, this adds the fields to the registered type in
349 * the TypeRegistry
350 *
351 * @param string $type_name The name of the Type to add the fields to
352 * @param array<string,array<string,mixed>> $fields An array of field configs
353 *
354 * @throws \Exception
355 * @since 0.1.0
356 */
357 function register_graphql_fields( string $type_name, array $fields ): void {
358 add_action(
359 get_graphql_register_action(),
360 static function ( TypeRegistry $type_registry ) use ( $type_name, $fields ): void {
361 $type_registry->register_fields( $type_name, $fields );
362 },
363 10
364 );
365 }
366
367 /**
368 * Adds a field to the Connection Edge between the provided 'From' Type Name and 'To' Type Name.
369 *
370 * @param string $from_type The name of the Type the connection is coming from.
371 * @param string $to_type The name of the Type or Alias (the connection config's `FromFieldName`) the connection is going to.
372 * @param string $field_name The name of the field to add to the connection edge.
373 * @param array<string,mixed> $config The field config.
374 *
375 * @since 1.13.0
376 */
377 function register_graphql_edge_field( string $from_type, string $to_type, string $field_name, array $config ): void {
378 $connection_name = ucfirst( $from_type ) . 'To' . ucfirst( $to_type ) . 'ConnectionEdge';
379
380 add_action(
381 get_graphql_register_action(),
382 static function ( TypeRegistry $type_registry ) use ( $connection_name, $field_name, $config ): void {
383 $type_registry->register_field( $connection_name, $field_name, $config );
384 },
385 10
386 );
387 }
388
389 /**
390 * Adds several fields to the Connection Edge between the provided 'From' Type Name and 'To' Type Name.
391 *
392 * @param string $from_type The name of the Type the connection is coming from.
393 * @param string $to_type The name of the Type or Alias (the connection config's `FromFieldName`) the connection is going to.
394 * @param array<string,array<string,mixed>> $fields An array of field configs.
395 *
396 * @since 1.13.0
397 */
398 function register_graphql_edge_fields( string $from_type, string $to_type, array $fields ): void {
399 $connection_name = ucfirst( $from_type ) . 'To' . ucfirst( $to_type ) . 'ConnectionEdge';
400
401 add_action(
402 get_graphql_register_action(),
403 static function ( TypeRegistry $type_registry ) use ( $connection_name, $fields ): void {
404 $type_registry->register_fields( $connection_name, $fields );
405 },
406 10
407 );
408 }
409
410 /**
411 * Adds an input field to the Connection Where Args between the provided 'From' Type Name and 'To' Type Name.
412 *
413 * @param string $from_type The name of the Type the connection is coming from.
414 * @param string $to_type The name of the Type or Alias (the connection config's `FromFieldName`) the connection is going to.
415 * @param string $field_name The name of the field to add to the connection edge.
416 * @param array<string,mixed> $config The field config.
417 *
418 * @since 1.13.0
419 */
420 function register_graphql_connection_where_arg( string $from_type, string $to_type, string $field_name, array $config ): void {
421 $connection_name = ucfirst( $from_type ) . 'To' . ucfirst( $to_type ) . 'ConnectionWhereArgs';
422
423 add_action(
424 get_graphql_register_action(),
425 static function ( TypeRegistry $type_registry ) use ( $connection_name, $field_name, $config ): void {
426 $type_registry->register_field( $connection_name, $field_name, $config );
427 },
428 10
429 );
430 }
431
432 /**
433 * Adds several input fields to the Connection Where Args between the provided 'From' Type Name and 'To' Type Name.
434 *
435 * @param string $from_type The name of the Type the connection is coming from.
436 * @param string $to_type The name of the Type or Alias (the connection config's `FromFieldName`) the connection is going to.
437 * @param array<string,array<string,mixed>> $fields An array of field configs.
438 *
439 * @since 1.13.0
440 */
441 function register_graphql_connection_where_args( string $from_type, string $to_type, array $fields ): void {
442 $connection_name = ucfirst( $from_type ) . 'To' . ucfirst( $to_type ) . 'ConnectionWhereArgs';
443
444 add_action(
445 get_graphql_register_action(),
446 static function ( TypeRegistry $type_registry ) use ( $connection_name, $fields ): void {
447 $type_registry->register_fields( $connection_name, $fields );
448 },
449 10
450 );
451 }
452
453 /**
454 * Renames a GraphQL field.
455 *
456 * @param string $type_name Name of the Type to rename a field on.
457 * @param string $field_name Field name to be renamed.
458 * @param string $new_field_name New field name.
459 *
460 * @since 1.3.4
461 */
462 function rename_graphql_field( string $type_name, string $field_name, string $new_field_name ): void {
463 // Rename fields on the type.
464 add_filter(
465 "graphql_{$type_name}_fields",
466 static function ( $fields ) use ( $field_name, $new_field_name ) {
467 // Bail if the field doesn't exist.
468 if ( ! isset( $fields[ $field_name ] ) ) {
469 return $fields;
470 }
471
472 $fields[ $new_field_name ] = $fields[ $field_name ];
473 unset( $fields[ $field_name ] );
474
475 return $fields;
476 }
477 );
478
479 // Rename fields registered to the type by connections.
480 add_filter(
481 "graphql_wp_connection_{$type_name}_from_field_name",
482 static function ( $old_field_name ) use ( $field_name, $new_field_name ) {
483 // Bail if the field name doesn't match.
484 if ( $old_field_name !== $field_name ) {
485 return $old_field_name;
486 }
487
488 return $new_field_name;
489 }
490 );
491 }
492
493 /**
494 * Renames a GraphQL Type in the Schema.
495 *
496 * @param string $type_name The name of the Type in the Schema to rename.
497 * @param string $new_type_name The new name to give the Type.
498 *
499 * @throws \Exception
500 *
501 * @since 1.3.4
502 */
503 function rename_graphql_type( string $type_name, string $new_type_name ): void {
504 add_filter(
505 'graphql_type_name',
506 static function ( $name ) use ( $type_name, $new_type_name ) {
507 if ( $name === $type_name ) {
508 return $new_type_name;
509 }
510 return $name;
511 }
512 );
513
514 // Add the new type to the registry referencing the original Type instance.
515 // This allows for both the new type name and the old type name to be
516 // referenced as the type when registering fields.
517 add_action(
518 'graphql_register_types_late',
519 static function ( TypeRegistry $type_registry ) use ( $type_name, $new_type_name ): void {
520 $type = $type_registry->get_type( $type_name );
521 if ( ! $type instanceof Type ) {
522 return;
523 }
524 $type_registry->register_type( $new_type_name, $type );
525 }
526 );
527 }
528
529 /**
530 * Given a config array for a connection, this registers a connection by creating all appropriate
531 * fields and types for the connection
532 *
533 * @param array<string,mixed> $config Array to configure the connection
534 *
535 * @since 0.1.0
536 */
537 function register_graphql_connection( array $config ): void {
538 add_action(
539 get_graphql_register_action(),
540 static function ( TypeRegistry $type_registry ) use ( $config ): void {
541 $type_registry->register_connection( $config );
542 },
543 20
544 );
545 }
546
547 /**
548 * Given a Mutation Name and Config array, this adds a Mutation to the Schema
549 *
550 * @param string $mutation_name The name of the Mutation to register
551 * @param array<string,mixed> $config The config for the mutation
552 *
553 * @since 0.1.0
554 */
555 function register_graphql_mutation( string $mutation_name, array $config ): void {
556 add_action(
557 get_graphql_register_action(),
558 static function ( TypeRegistry $type_registry ) use ( $mutation_name, $config ): void {
559 $type_registry->register_mutation( $mutation_name, $config );
560 },
561 10
562 );
563 }
564
565 /**
566 * Given a config array for a custom Scalar, this registers a Scalar for use in the Schema
567 *
568 * @param string $type_name The name of the Type to register
569 * @param array<string,mixed> $config The config for the scalar type to register
570 *
571 * @phpstan-param array{
572 * description?: string|callable():string|null,
573 * serialize?: callable(mixed): mixed,
574 * parseValue?: callable(mixed): mixed,
575 * parseLiteral?: callable(\GraphQL\Language\AST\ValueNode&\GraphQL\Language\AST\Node, array<string, mixed>|null): mixed,
576 * astNode?: \GraphQL\Language\AST\ScalarTypeDefinitionNode|null,
577 * extensionASTNodes?: array<\GraphQL\Language\AST\ScalarTypeDefinitionNode>|null
578 * } $config
579 *
580 * @since 0.8.4
581 */
582 function register_graphql_scalar( string $type_name, array $config ): void {
583 $config['kind'] = 'scalar';
584 register_graphql_type( $type_name, $config );
585 }
586
587 /**
588 * Given a Type Name, this removes the type from the entire schema
589 *
590 * @param string $type_name The name of the Type to remove.
591 *
592 * @since 1.13.0
593 */
594 function deregister_graphql_type( string $type_name ): void {
595 // Prevent the type from being registered to the scheme directly.
596 add_filter(
597 'graphql_excluded_types',
598 static function ( $excluded_types ) use ( $type_name ): array {
599 // Normalize the types to prevent case sensitivity issues.
600 $type_name = strtolower( $type_name );
601 // If the type isn't already excluded, add it to the array.
602 if ( ! in_array( $type_name, $excluded_types, true ) ) {
603 $excluded_types[] = $type_name;
604 }
605
606 return $excluded_types;
607 },
608 10
609 );
610
611 // Prevent the type from being inherited as an interface.
612 add_filter(
613 'graphql_type_interfaces',
614 static function ( $interfaces ) use ( $type_name ): array {
615 // Normalize the needle and haystack to prevent case sensitivity issues.
616 $key = array_search(
617 strtolower( $type_name ),
618 array_map( 'strtolower', $interfaces ),
619 true
620 );
621 // If the type is found, unset it.
622 if ( false !== $key ) {
623 unset( $interfaces[ $key ] );
624 }
625
626 return $interfaces;
627 },
628 10
629 );
630 }
631
632 /**
633 * Given a Type Name and Field Name, this removes the field from the TypeRegistry
634 *
635 * @param string $type_name The name of the Type to remove the field from
636 * @param string $field_name The name of the field to remove
637 *
638 * @since 0.1.0
639 */
640 function deregister_graphql_field( string $type_name, string $field_name ): void {
641 add_action(
642 get_graphql_register_action(),
643 static function ( TypeRegistry $type_registry ) use ( $type_name, $field_name ): void {
644 $type_registry->deregister_field( $type_name, $field_name );
645 },
646 10
647 );
648 }
649
650 /**
651 * Given a Connection Name, this removes the connection from the Schema
652 *
653 * @param string $connection_name The name of the Connection to remove
654 *
655 * @since 1.14.0
656 */
657 function deregister_graphql_connection( string $connection_name ): void {
658 add_action(
659 get_graphql_register_action(),
660 static function ( TypeRegistry $type_registry ) use ( $connection_name ): void {
661 $type_registry->deregister_connection( $connection_name );
662 },
663 10
664 );
665 }
666
667 /**
668 * Given a Mutation Name, this removes the mutation from the Schema
669 *
670 * @param string $mutation_name The name of the Mutation to remove
671 *
672 * @since 1.14.0
673 */
674 function deregister_graphql_mutation( string $mutation_name ): void {
675 add_action(
676 get_graphql_register_action(),
677 static function ( TypeRegistry $type_registry ) use ( $mutation_name ): void {
678 $type_registry->deregister_mutation( $mutation_name );
679 },
680 10
681 );
682 }
683
684 /**
685 * Whether a GraphQL request is in action or not. This is determined by the WPGraphQL Request
686 * class being initiated. True while a request is in action, false after a request completes.
687 *
688 * This should be used when a condition needs to be checked for ALL GraphQL requests, such
689 * as filtering WP_Query for GraphQL requests, for example.
690 *
691 * Default false.
692 *
693 * @return bool True while a GraphQL request is in action, false otherwise.
694 *
695 * @since 0.4.1
696 */
697 function is_graphql_request(): bool {
698 return WPGraphQL::is_graphql_request();
699 }
700
701 /**
702 * Whether a GraphQL HTTP request is in action or not. This is determined by
703 * checking if the request is occurring on the route defined for the GraphQL endpoint.
704 *
705 * This conditional should only be used for features that apply to HTTP requests. If you are going
706 * to apply filters to underlying WordPress core functionality that should affect _all_ GraphQL
707 * requests, you should use "is_graphql_request" but if you need to apply filters only if the
708 * GraphQL request is an HTTP request, use this conditional.
709 *
710 * Default false.
711 *
712 * @return bool True when the current request is an HTTP request against the GraphQL endpoint, false otherwise.
713 *
714 * @since 0.4.1
715 */
716 function is_graphql_http_request(): bool {
717 if ( ! class_exists( '\WPGraphQL\Router' ) ) {
718 return false;
719 }
720 return Router::is_graphql_http_request();
721 }
722
723 /**
724 * Generate a WPGraphQL nonce for cookie-based authentication.
725 *
726 * This nonce is required for authenticated GraphQL HTTP requests that use
727 * WordPress cookie authentication. It provides CSRF protection by proving
728 * the request originated from a legitimate WordPress-generated page.
729 *
730 * Example usage in JavaScript:
731 * ```javascript
732 * fetch('/graphql', {
733 * headers: {
734 * 'Content-Type': 'application/json',
735 * 'X-WP-Nonce': wpGraphQLSettings.nonce
736 * },
737 * body: JSON.stringify({ query: '{ viewer { name } }' })
738 * });
739 * ```
740 *
741 * Example usage in PHP for localizing to JavaScript:
742 * ```php
743 * wp_localize_script( 'my-script', 'wpGraphQLSettings', [
744 * 'nonce' => graphql_get_nonce(),
745 * 'endpoint' => graphql_get_endpoint(),
746 * ] );
747 * ```
748 *
749 * @since 2.6.0
750 *
751 * @return string The WPGraphQL nonce string.
752 *
753 * @see https://github.com/wp-graphql/wp-graphql/issues/3447
754 */
755 function graphql_get_nonce(): string {
756 return wp_create_nonce( 'wp_graphql' );
757 }
758
759 /**
760 * Registers a GraphQL Settings Section
761 *
762 * @param string $slug The slug of the group being registered
763 * @param array<string,mixed> $config Array configuring the section. Should include: title
764 *
765 * @since 0.13.0
766 */
767 function register_graphql_settings_section( string $slug, array $config ): void {
768 add_action(
769 'graphql_init_settings',
770 static function ( \WPGraphQL\Admin\Settings\SettingsRegistry $registry ) use ( $slug, $config ): void {
771 $registry->register_section( $slug, $config );
772 }
773 );
774 }
775
776 /**
777 * Registers a GraphQL Settings Field
778 *
779 * @param string $group The name of the group to register a setting field to
780 * @param array<string,mixed> $config The config for the settings field being registered
781 *
782 * @since 0.13.0
783 */
784 function register_graphql_settings_field( string $group, array $config ): void {
785 add_action(
786 'graphql_init_settings',
787 static function ( \WPGraphQL\Admin\Settings\SettingsRegistry $registry ) use ( $group, $config ): void {
788 $registry->register_field( $group, $config );
789 }
790 );
791 }
792
793 /**
794 * Given a message and an optional config array
795 *
796 * @param mixed|string|mixed[] $message The debug message
797 * @param array<string,mixed> $config The debug config. Should be an associative array of keys and values.
798 * $config['type'] will set the "type" of the log, default type is GRAPHQL_DEBUG.
799 * Other fields added to $config will be merged into the debug entry.
800 *
801 * @since 0.14.0
802 */
803 function graphql_debug( $message, $config = [] ): void {
804
805 // Bail if debug is disabled.
806 if ( ! WPGraphQL::debug() ) {
807 return;
808 }
809
810 $debug_backtrace = debug_backtrace(); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
811 $config['backtrace'] = ! empty( $debug_backtrace )
812 ?
813 array_values(
814 array_map(
815 static function ( $trace ) {
816 $line = isset( $trace['line'] ) ? absint( $trace['line'] ) : 0;
817 return sprintf( '%s:%d', $trace['file'], $line );
818 },
819 array_filter( // Filter out steps without files
820 $debug_backtrace,
821 static function ( $step ) {
822 return ! empty( $step['file'] );
823 }
824 )
825 )
826 )
827 :
828 [];
829
830 add_action(
831 'graphql_get_debug_log',
832 static function ( \WPGraphQL\Utils\DebugLog $debug_log ) use ( $message, $config ): void {
833 $debug_log->add_log_entry( $message, $config );
834 }
835 );
836 }
837
838 /**
839 * Check if the name is valid for use in GraphQL
840 *
841 * @param string $type_name The name of the type to validate
842 *
843 * @return bool True if the name is valid for use in GraphQL, false otherwise.
844 *
845 * @since 0.14.0
846 */
847 function is_valid_graphql_name( string $type_name ): bool {
848 if ( preg_match( '/^\d/', $type_name ) ) {
849 return false;
850 }
851
852 return true;
853 }
854
855 /**
856 * Registers a series of GraphQL Settings Fields
857 *
858 * @param string $group The name of the settings group to register fields to
859 * @param array<string,mixed>[] $fields Array of field configs to register to the group
860 *
861 * @since 0.13.0
862 */
863 function register_graphql_settings_fields( string $group, array $fields ): void {
864 add_action(
865 'graphql_init_settings',
866 static function ( \WPGraphQL\Admin\Settings\SettingsRegistry $registry ) use ( $group, $fields ): void {
867 $registry->register_fields( $group, $fields );
868 }
869 );
870 }
871
872 /**
873 * Get an option value from GraphQL settings
874 *
875 * @param string $option_name The key of the option to return
876 * @param mixed $default_value The default value the setting should return if no value is set
877 * @param string $section_name The settings group section that the option belongs to
878 *
879 * @return mixed|string|int|bool
880 * @since 0.13.0
881 */
882 function get_graphql_setting( string $option_name, $default_value = '', $section_name = 'graphql_general_settings' ) {
883 $section_fields = get_option( $section_name, [] );
884
885 /**
886 * Filter the section fields
887 *
888 * @param array<string,mixed> $section_fields The values of the fields stored for the section
889 * @param string $section_name The name of the section
890 * @param mixed $default_value The default value for the option being retrieved
891 *
892 * @hookGroup settings
893 * @since 0.13.0
894 */
895 $section_fields = apply_filters( 'graphql_get_setting_section_fields', $section_fields, $section_name, $default_value );
896
897 // ensure the filtered sections fields are an array before proceeding
898 $section_fields = is_array( $section_fields ) ? $section_fields : [];
899
900 /**
901 * Get the value from the stored data, or return the default
902 */
903 $value = $section_fields[ $option_name ] ?? $default_value;
904
905 /**
906 * Filter the value before returning it
907 *
908 * @param mixed $value The value of the field
909 * @param mixed $default_value The default value if there is no value set
910 * @param string $option_name The name of the option
911 * @param array<string,mixed> $section_fields The setting values within the section
912 * @param string $section_name The name of the section the setting belongs to
913 *
914 * @hookGroup settings
915 * @since 0.13.0
916 */
917 return apply_filters( 'graphql_get_setting_section_field_value', $value, $default_value, $option_name, $section_fields, $section_name );
918 }
919
920 /**
921 * Get the endpoint route for the WPGraphQL API
922 *
923 * @return string The relative endpoint path where the GraphQL API can be accessed.
924 *
925 * @since 1.12.0
926 */
927 function graphql_get_endpoint(): string {
928
929 // get the endpoint from the settings. default to 'graphql'
930 $endpoint = get_graphql_setting( 'graphql_endpoint', 'graphql' );
931
932 /**
933 * Filter the relative endpoint path where GraphQL can be accessed.
934 *
935 * @param string $endpoint The relative endpoint path that GraphQL can be accessed at.
936 * @hookGroup settings
937 * @since 0.0.6
938 */
939 $filtered_endpoint = apply_filters( 'graphql_endpoint', $endpoint );
940
941 // If the filtered endpoint has a value (not filtered to a falsy value), use it. else return the default endpoint
942 return is_string( $filtered_endpoint ) && ! empty( $filtered_endpoint ) ? $filtered_endpoint : $endpoint;
943 }
944
945 /**
946 * Return the full url for the GraphQL Endpoint.
947 *
948 * @return string The full URL to the GraphQL endpoint.
949 *
950 * @since 1.12.0
951 */
952 function graphql_get_endpoint_url(): string {
953 return (string) site_url( graphql_get_endpoint() );
954 }
955
956 /**
957 * Polyfill for PHP versions below 8.0
958 */
959 if ( ! function_exists( 'str_starts_with' ) ) {
960
961 /**
962 * Polyfill for the PHP 8.0 str_starts_with() function.
963 *
964 * @param string $haystack The string to search in.
965 * @param string $needle The substring to search for at the start of the haystack.
966 *
967 * @internal
968 */
969 function str_starts_with( string $haystack, string $needle ): bool {
970 return 0 === strncmp( $haystack, $needle, strlen( $needle ) );
971 }
972 }
973
974 /**
975 * Polyfill for PHP versions below 8.0
976 */
977 if ( ! function_exists( 'str_ends_with' ) ) {
978
979 /**
980 * Polyfill for the PHP 8.0 str_ends_with() function.
981 *
982 * @param string $haystack The string to search in.
983 * @param string $needle The substring to search for at the end of the haystack.
984 *
985 * @internal
986 */
987 function str_ends_with( string $haystack, string $needle ): bool {
988 if ( '' === $needle || $needle === $haystack ) {
989 return true;
990 }
991
992 if ( '' === $haystack ) {
993 return false;
994 }
995
996 $needle_length = strlen( $needle );
997
998 return $needle_length <= strlen( $haystack ) && 0 === substr_compare( $haystack, $needle, -$needle_length );
999 }
1000 }
1001
1002 /**
1003 * Registers an admin notice to display on WPGraphQL plugin screens.
1004 *
1005 * @param string $slug A unique slug to identify the admin notice by
1006 * @param array<string,mixed> $config The config for the admin notice. Determines visibility, context, etc.
1007 *
1008 * @phpstan-param array{
1009 * message: string,
1010 * type?: 'error'|'warning'|'success'|'info',
1011 * is_dismissable?: bool,
1012 * conditions?: callable():bool
1013 * } $config
1014 *
1015 * @since 1.21.0
1016 */
1017 function register_graphql_admin_notice( string $slug, array $config ): void {
1018 add_action(
1019 'graphql_admin_notices_init',
1020 static function ( \WPGraphQL\Admin\AdminNotices $admin_notices ) use ( $slug, $config ): void {
1021 $admin_notices->add_admin_notice( $slug, $config );
1022 }
1023 );
1024 }
1025
1026 /**
1027 * Get the admin notices registered for the WPGraphQL plugin screens
1028 *
1029 * @return array<string,array{
1030 * message: string,
1031 * type?: 'error'|'warning'|'success'|'info',
1032 * is_dismissable?: bool,
1033 * conditions?: callable():bool,
1034 * }>
1035 *
1036 * @since 1.29.0
1037 */
1038 function get_graphql_admin_notices(): array {
1039 $admin_notices = \WPGraphQL\Admin\AdminNotices::get_instance();
1040 return $admin_notices->get_admin_notices();
1041 }
1042