• 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

57.31
/src/Utils/Utils.php
1
<?php
2

3
namespace WPGraphQL\Utils;
4

5
use GraphQLRelay\Relay;
6

7
class Utils {
8

9
        /**
10
         * Given a GraphQL Query string, return a hash
11
         *
12
         * @param string $query The Query String to hash
13
         *
14
         * @return string|null
15
         */
16
        public static function get_query_id( string $query ) {
752✔
17

18
                /**
19
                 * Filter the hash algorithm to allow different algorithms.
20
                 *
21
                 * @string $algorithm Default is sha256. Possible values are those that work with the PHP hash() function. See: https://www.php.net/manual/en/function.hash-algos.php
22
                 */
23
                $hash_algorithm = apply_filters( 'graphql_query_id_hash_algorithm', 'sha256' );
752✔
24

25
                try {
26
                        $query_ast = \GraphQL\Language\Parser::parse( $query );
752✔
27
                        $query     = \GraphQL\Language\Printer::doPrint( $query_ast );
752✔
28
                        return hash( $hash_algorithm, $query );
752✔
29
                } catch ( \Throwable $exception ) {
1✔
30
                        return null;
1✔
31
                }
32
        }
33

34
        /**
35
         * Maps new input query args and sanitizes the input
36
         *
37
         * @param mixed[]|string $args The raw query args from the GraphQL query
38
         * @param mixed[]|string $map  The mapping of where each of the args should go
39
         * @param string[]       $skip Fields to skipped and not be added to the output array.
40
         *
41
         * @return array<string,mixed>
42
         * @since  0.5.0
43
         */
44
        public static function map_input( $args, $map, $skip = [] ) {
104✔
45
                if ( ! is_array( $args ) || ! is_array( $map ) ) {
104✔
46
                        return [];
1✔
47
                }
48

49
                $query_args = [];
104✔
50

51
                foreach ( $args as $arg => $value ) {
104✔
52
                        if ( [] !== $skip && in_array( $arg, $skip, true ) ) {
104✔
53
                                continue;
×
54
                        }
55

56
                        if ( is_array( $value ) && ! empty( $value ) ) {
104✔
57
                                $value = array_map(
52✔
58
                                        static function ( $value ) {
52✔
59
                                                if ( is_string( $value ) ) {
52✔
60
                                                        $value = sanitize_text_field( $value );
21✔
61
                                                }
62

63
                                                return $value;
52✔
64
                                        },
52✔
65
                                        $value
52✔
66
                                );
52✔
67
                        } elseif ( is_string( $value ) ) {
65✔
68
                                $value = sanitize_text_field( $value );
40✔
69
                        }
70

71
                        if ( array_key_exists( $arg, $map ) ) {
104✔
72
                                $query_args[ $map[ $arg ] ] = $value;
46✔
73
                        } else {
74
                                $query_args[ $arg ] = $value;
64✔
75
                        }
76
                }
77

78
                return $query_args;
104✔
79
        }
80

81
        /**
82
         * Checks the post_date_gmt or modified_gmt and prepare any post or
83
         * modified date for single post output.
84
         *
85
         * @param string      $date_gmt GMT publication time.
86
         * @param string|null $date Optional. Local publication time. Default null.
87
         *
88
         * @return string|null ISO8601/RFC3339 formatted datetime.
89
         * @since 4.7.0
90
         */
91
        public static function prepare_date_response( string $date_gmt, $date = null ) {
41✔
92
                // Use the date if passed.
93
                if ( isset( $date ) ) {
41✔
94
                        return mysql_to_rfc3339( $date );
41✔
95
                }
96
                // Return null if $date_gmt is empty/zeros.
97
                if ( '0000-00-00 00:00:00' === $date_gmt ) {
12✔
98
                        return null;
2✔
99
                }
100

101
                // Return the formatted datetime.
102
                return mysql_to_rfc3339( $date_gmt );
12✔
103
        }
104

105
        /**
106
         * Format a GraphQL name according to the GraphQL spec.
107
         *
108
         * Per the GraphQL spec, characters in names are limited to Latin ASCII letter, digits, or underscores.
109
         *
110
         * @see http://spec.graphql.org/draft/#sec-Names
111
         *
112
         * @param string $name The name to format.
113
         * @param string $replacement The replacement character for invalid characters. Defaults to '_'.
114
         * @param string $regex The regex to use to match invalid characters. Defaults to '/[^A-Za-z0-9_]/i'.
115
         *
116
         * @since v1.17.0
117
         */
118
        public static function format_graphql_name( string $name, string $replacement = '_', string $regex = '/[^A-Za-z0-9_]/i' ): string {
603✔
119
                if ( empty( $name ) ) {
603✔
120
                        return '';
1✔
121
                }
122

123
                /**
124
                 * Filter to manually format a GraphQL name according to custom rules.
125
                 *
126
                 * If anything other than null is returned, the result will be used for the name instead of the standard regex.
127
                 *
128
                 * Useful for providing custom transliteration rules that will convert non ASCII characters to ASCII.
129
                 *
130
                 * @param string|null $formatted_name The name to format. If not null, the result will be returned as the formatted name.
131
                 * @param string $original_name       The name to format.
132
                 * @param string $replacement         The replacement character for invalid characters. Defaults to '_'.
133
                 * @param string $regex               The regex to use to match invalid characters. Defaults to '/[^A-Za-z0-9_]/i'.
134
                 *
135
                 * @return string|null
136
                 */
137
                $pre_format_name = apply_filters( 'graphql_pre_format_name', null, $name, $replacement, $regex );
603✔
138

139
                // Check whether the filter is being used (correctly).
140
                if ( ! empty( $pre_format_name ) && is_string( $pre_format_name ) ) {
603✔
141
                        // Don't trust the filter to return a formatted string.
142
                        $name = trim( sanitize_text_field( $pre_format_name ) );
1✔
143
                } else {
144
                        // Throw a warning if someone is using the filter incorrectly.
145
                        if ( null !== $pre_format_name ) {
603✔
146
                                graphql_debug(
1✔
147
                                        esc_html__( 'The `graphql_pre_format_name` filter must return a string or null.', 'wp-graphql' ),
1✔
148
                                        [
1✔
149
                                                'type'          => 'INVALID_GRAPHQL_NAME',
1✔
150
                                                'original_name' => esc_html( $name ),
1✔
151
                                        ]
1✔
152
                                );
1✔
153
                        }
154

155
                        // Remove all non-alphanumeric characters.
156
                        $name = preg_replace( $regex, $replacement, $name );
603✔
157
                }
158

159
                if ( empty( $name ) ) {
603✔
160
                        return '';
6✔
161
                }
162

163
                // Replace multiple consecutive leading underscores with a single underscore, since those are reserved.
164
                $name = preg_replace( '/^_+/', '_', trim( $name ) );
603✔
165

166
                return ! empty( $name ) ? $name : '';
603✔
167
        }
168

169
        /**
170
         * Given a field name, formats it for GraphQL
171
         *
172
         * @param string $field_name         The field name to format
173
         * @param bool   $allow_underscores  Whether the field should be formatted with underscores allowed. Default false.
174
         */
175
        public static function format_field_name( string $field_name, bool $allow_underscores = false ): string {
599✔
176
                // Bail if empty.
177
                if ( empty( $field_name ) ) {
599✔
178
                        return '';
×
179
                }
180

181
                $formatted_field_name = graphql_format_name( $field_name, '_', '/[^a-zA-Z0-9 -]/' );
599✔
182

183
                // If the formatted name is empty, we want to return the original value so it displays in the error.
184
                if ( empty( $formatted_field_name ) ) {
599✔
185
                        return $field_name;
×
186
                }
187

188
                // underscores are allowed by GraphQL, but WPGraphQL has historically
189
                // stripped them when formatting field names.
190
                // The $allow_underscores argument allows functions to opt-in to allowing underscores
191
                if ( true !== $allow_underscores ) {
599✔
192
                        // uppercase words separated by an underscore, then replace the underscores with a space
193
                        $formatted_field_name = lcfirst( str_replace( '_', ' ', ucwords( $formatted_field_name, '_' ) ) );
599✔
194
                }
195

196
                // uppercase words separated by a dash, then replace the dashes with a space
197
                $formatted_field_name = lcfirst( str_replace( '-', ' ', ucwords( $formatted_field_name, '-' ) ) );
599✔
198

199
                // uppercace words separated by a space, and replace spaces with no space
200
                $formatted_field_name = lcfirst( str_replace( ' ', '', ucwords( $formatted_field_name, ' ' ) ) );
599✔
201

202
                // Field names should be lcfirst.
203
                return lcfirst( $formatted_field_name );
599✔
204
        }
205

206
        /**
207
         * Given a type name, formats it for GraphQL
208
         *
209
         * @param string $type_name The type name to format
210
         *
211
         * @return string
212
         */
213
        public static function format_type_name( $type_name ) {
599✔
214
                return ucfirst( self::format_field_name( $type_name ) );
599✔
215
        }
216

217
        /**
218
         * Returns a GraphQL type name for a given WordPress template name.
219
         *
220
         * If the template name has no ASCII characters, the file name will be used instead.
221
         *
222
         * @param string $name The template name.
223
         * @param string $file The file name.
224
         * @return string The formatted type name. If the name is empty, an empty string will be returned.
225
         */
226
        public static function format_type_name_for_wp_template( string $name, string $file ): string {
599✔
227
                $name = ucwords( $name );
599✔
228
                // Strip out not ASCII characters.
229
                $name = graphql_format_name( $name, '', '/[^\w]/' );
599✔
230

231
                // If replaced_name is empty, use the file name.
232
                if ( empty( $name ) ) {
599✔
233
                        $file_parts    = explode( '.', $file );
6✔
234
                        $file_name     = ! empty( $file_parts[0] ) ? self::format_type_name( $file_parts[0] ) : '';
6✔
235
                        $replaced_name = ! empty( $file_name ) ? graphql_format_name( $file_name, '', '/[^\w]/' ) : '';
6✔
236

237
                        $name = ! empty( $replaced_name ) ? $replaced_name : $name;
6✔
238
                }
239

240
                // If the name is still empty, we don't have a valid type.
241
                if ( empty( $name ) ) {
599✔
242
                        return '';
6✔
243
                }
244

245
                // Maybe prefix the name with "Template_".
246
                if ( preg_match( '/^\d/', $name ) || false === strpos( strtolower( $name ), 'template' ) ) {
599✔
247
                        $name = 'Template_' . $name;
598✔
248
                }
249

250
                return $name;
599✔
251
        }
252

253
        /**
254
         * Helper function that defines the allowed HTML to use on the Settings pages
255
         *
256
         * @return array<string,array<string,array<string>>>
257
         */
258
        public static function get_allowed_wp_kses_html() {
×
259
                $allowed_atts = [
×
260
                        'align'      => [],
×
261
                        'class'      => [],
×
262
                        'type'       => [],
×
263
                        'id'         => [],
×
264
                        'dir'        => [],
×
265
                        'lang'       => [],
×
266
                        'style'      => [],
×
267
                        'xml:lang'   => [],
×
268
                        'src'        => [],
×
269
                        'alt'        => [],
×
270
                        'href'       => [],
×
271
                        'rel'        => [],
×
272
                        'rev'        => [],
×
273
                        'target'     => [],
×
274
                        'novalidate' => [],
×
275
                        'value'      => [],
×
276
                        'name'       => [],
×
277
                        'tabindex'   => [],
×
278
                        'action'     => [],
×
279
                        'method'     => [],
×
280
                        'for'        => [],
×
281
                        'width'      => [],
×
282
                        'height'     => [],
×
283
                        'data'       => [],
×
284
                        'title'      => [],
×
285
                        'checked'    => [],
×
286
                        'disabled'   => [],
×
287
                        'selected'   => [],
×
288
                ];
×
289

290
                return [
×
291
                        'form'     => $allowed_atts,
×
292
                        'label'    => $allowed_atts,
×
293
                        'input'    => $allowed_atts,
×
294
                        'textarea' => $allowed_atts,
×
295
                        'iframe'   => $allowed_atts,
×
296
                        'script'   => $allowed_atts,
×
297
                        'select'   => $allowed_atts,
×
298
                        'option'   => $allowed_atts,
×
299
                        'style'    => $allowed_atts,
×
300
                        'strong'   => $allowed_atts,
×
301
                        'small'    => $allowed_atts,
×
302
                        'table'    => $allowed_atts,
×
303
                        'span'     => $allowed_atts,
×
304
                        'abbr'     => $allowed_atts,
×
305
                        'code'     => $allowed_atts,
×
306
                        'pre'      => $allowed_atts,
×
307
                        'div'      => $allowed_atts,
×
308
                        'img'      => $allowed_atts,
×
309
                        'h1'       => $allowed_atts,
×
310
                        'h2'       => $allowed_atts,
×
311
                        'h3'       => $allowed_atts,
×
312
                        'h4'       => $allowed_atts,
×
313
                        'h5'       => $allowed_atts,
×
314
                        'h6'       => $allowed_atts,
×
315
                        'ol'       => $allowed_atts,
×
316
                        'ul'       => $allowed_atts,
×
317
                        'li'       => $allowed_atts,
×
318
                        'em'       => $allowed_atts,
×
319
                        'hr'       => $allowed_atts,
×
320
                        'br'       => $allowed_atts,
×
321
                        'tr'       => $allowed_atts,
×
322
                        'td'       => $allowed_atts,
×
323
                        'p'        => $allowed_atts,
×
324
                        'a'        => $allowed_atts,
×
325
                        'b'        => $allowed_atts,
×
326
                        'i'        => $allowed_atts,
×
327
                ];
×
328
        }
329

330
        /**
331
         * Helper function to get the WordPress database ID from a GraphQL ID type input.
332
         *
333
         * Returns false if not a valid ID.
334
         *
335
         * @param int|string $id The ID from the input args. Can be either the database ID (as either a string or int) or the global Relay ID.
336
         *
337
         * @return int|false
338
         * @phpstan-return ( $id is int|numeric-string ? int : (int|false) )
339
         */
340
        public static function get_database_id_from_id( $id ) {
84✔
341
                // If we already have the database ID, send it back as an integer.
342
                if ( is_numeric( $id ) ) {
84✔
343
                        return absint( $id );
60✔
344
                }
345

346
                $id_parts = Relay::fromGlobalId( $id );
58✔
347

348
                return isset( $id_parts['id'] ) && is_numeric( $id_parts['id'] ) ? absint( $id_parts['id'] ) : false;
58✔
349
        }
350

351
        /**
352
         * Get the node type from the ID
353
         *
354
         * @param int|string $id The encoded Node ID.
355
         *
356
         * @return ?string
357
         * @phpstan-return ( $id is int|numeric-string ? null : ?string )
358
         */
359
        public static function get_node_type_from_id( $id ) {
494✔
360
                if ( is_numeric( $id ) ) {
494✔
361
                        return null;
×
362
                }
363

364
                $id_parts = Relay::fromGlobalId( $id );
494✔
365
                return $id_parts['type'] ?: null;
494✔
366
        }
367

368
        /**
369
         * Given a WP Post or post ID, this method attempts to resolve a preview post ID.
370
         *
371
         * @param int|\WP_Post $post The WP Post object or Post ID
372
         *
373
         * @return int A preview post ID if one exists, the current post ID if one doesn't exist.
374
         * @since 1.18.0
375
         */
376
        public static function get_post_preview_id( $post ): int {
14✔
377
                $post_id = is_object( $post ) ? $post->ID : $post;
14✔
378

379
                $revisions = wp_get_post_revisions(
14✔
380
                        $post_id,
14✔
381
                        [
14✔
382
                                'posts_per_page' => 1,
14✔
383
                                'fields'         => 'ids',
14✔
384
                                'check_enabled'  => false,
14✔
385
                        ]
14✔
386
                );
14✔
387

388
                $post_id = ! empty( $revisions ) ? array_values( $revisions )[0] : $post_id;
14✔
389

390
                return is_object( $post_id ) ? (int) $post_id->ID : (int) $post_id;
14✔
391
        }
392
}
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