• 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

39.29
/src/Server/ValidationRules/QueryDepth.php
1
<?php
2

3
namespace WPGraphQL\Server\ValidationRules;
4

5
use GraphQL\Error\Error;
6
use GraphQL\Language\AST\FieldNode;
7
use GraphQL\Language\AST\FragmentSpreadNode;
8
use GraphQL\Language\AST\InlineFragmentNode;
9
use GraphQL\Language\AST\Node;
10
use GraphQL\Language\AST\NodeKind;
11
use GraphQL\Language\AST\OperationDefinitionNode;
12
use GraphQL\Language\AST\SelectionSetNode;
13
use GraphQL\Validator\Rules\QuerySecurityRule;
14

15
/**
16
 * Class QueryDepth
17
 *
18
 * @package WPGraphQL\Server\ValidationRules
19
 */
20
class QueryDepth extends QuerySecurityRule {
21

22
        /**
23
         * The max query depth allowed.
24
         */
25
        private int $maxQueryDepth;
26

27
        /**
28
         * QueryDepth constructor.
29
         */
30
        public function __construct() {
760✔
31
                $max_query_depth = get_graphql_setting( 'query_depth_max', 10 );
760✔
32
                $max_query_depth = absint( $max_query_depth ) ?? 10;
760✔
33
                $this->setMaxQueryDepth( $max_query_depth );
760✔
34
        }
35

36
        /**
37
         * {@inheritDoc}
38
         *
39
         * @param \GraphQL\Validator\QueryValidationContext $context
40
         *
41
         * @return array<string,array<string,callable(\GraphQL\Language\AST\Node): (\GraphQL\Language\VisitorOperation|void|false|null)>|(callable(\GraphQL\Language\AST\Node): (\GraphQL\Language\VisitorOperation|void|false|null))>
42
         */
43
        public function getVisitor( \GraphQL\Validator\QueryValidationContext $context ): array {
752✔
44
                return $this->invokeIfNeeded(
752✔
45
                        $context,
752✔
46
                        [
752✔
47
                                NodeKind::OPERATION_DEFINITION => [
752✔
48
                                        'leave' => function ( Node $node ) use ( $context ): void {
752✔
49
                                                if ( ! $node instanceof OperationDefinitionNode ) {
×
50
                                                        return;
×
51
                                                }
52

53
                                                $maxDepth = $this->fieldDepth( $node );
×
54

55
                                                if ( $maxDepth <= $this->getMaxQueryDepth() ) {
×
56
                                                        return;
×
57
                                                }
58

59
                                                $context->reportError(
×
60
                                                        new Error( $this->errorMessage( $this->getMaxQueryDepth(), $maxDepth ) )
×
61
                                                );
×
62
                                        },
752✔
63
                                ],
752✔
64
                        ]
752✔
65
                );
752✔
66
        }
67

68
        /**
69
         * Determine field depth
70
         *
71
         * @param \GraphQL\Language\AST\Node $node The node being analyzed
72
         * @param int                        $depth The depth of the field. Default is 0
73
         * @param int                        $maxDepth The max depth allowed. Default is 0
74
         */
75
        private function fieldDepth( Node $node, int $depth = 0, int $maxDepth = 0 ): int {
×
76
                if ( isset( $node->selectionSet ) && $node->selectionSet instanceof SelectionSetNode ) {
×
77
                        foreach ( $node->selectionSet->selections as $childNode ) {
×
78
                                $maxDepth = $this->nodeDepth( $childNode, $depth, $maxDepth );
×
79
                        }
80
                }
81

82
                return $maxDepth;
×
83
        }
84

85
        /**
86
         * Determine node depth
87
         *
88
         * @param \GraphQL\Language\AST\Node $node The node being analyzed in the operation
89
         * @param int                        $depth The depth of the operation
90
         * @param int                        $maxDepth The Max Depth of the operation
91
         */
92
        private function nodeDepth( Node $node, int $depth, int $maxDepth ): int {
×
93
                switch ( true ) {
94
                        case $node instanceof FieldNode:
×
95
                                // node has children?
96
                                if ( isset( $node->selectionSet ) ) {
×
97
                                        // update maxDepth if needed
98
                                        if ( $depth > $maxDepth ) {
×
99
                                                $maxDepth = $depth;
×
100
                                        }
101
                                        $maxDepth = $this->fieldDepth( $node, $depth + 1, $maxDepth );
×
102
                                }
103
                                break;
×
104

105
                        case $node instanceof InlineFragmentNode:
×
106
                                // node has children?
107
                                $maxDepth = $this->fieldDepth( $node, $depth, $maxDepth );
×
108
                                break;
×
109

110
                        case $node instanceof FragmentSpreadNode:
×
111
                                $fragment = $this->getFragment( $node );
×
112

113
                                if ( null !== $fragment ) {
×
114
                                        $maxDepth = $this->fieldDepth( $fragment, $depth, $maxDepth );
×
115
                                }
116
                                break;
×
117
                }
118

119
                return $maxDepth;
×
120
        }
121

122
        /**
123
         * Return the maxQueryDepth allowed
124
         *
125
         * @return int
126
         */
127
        public function getMaxQueryDepth() {
×
128
                return $this->maxQueryDepth;
×
129
        }
130

131
        /**
132
         * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
133
         *
134
         * @param int $maxQueryDepth The max query depth to allow for GraphQL operations
135
         *
136
         * @return void
137
         */
138
        public function setMaxQueryDepth( int $maxQueryDepth ) {
760✔
139
                $this->checkIfGreaterOrEqualToZero( 'maxQueryDepth', $maxQueryDepth );
760✔
140

141
                $this->maxQueryDepth = $maxQueryDepth;
760✔
142
        }
143

144
        /**
145
         * Return the max query depth error message
146
         *
147
         * @param int $max The max number of levels to allow in GraphQL operation
148
         * @param int $count The number of levels in the current operation
149
         *
150
         * @return string
151
         */
152
        public function errorMessage( $max, $count ) {
×
153
                return sprintf( 'The server administrator has limited the max query depth to %d, but the requested query has %d levels.', $max, $count );
×
154
        }
155

156
        /**
157
         * Determine whether the rule should be enabled
158
         */
159
        protected function isEnabled(): bool {
752✔
160
                $is_enabled = false;
752✔
161

162
                $enabled = get_graphql_setting( 'query_depth_enabled', 'off' );
752✔
163

164
                if ( 'on' === $enabled && absint( $this->getMaxQueryDepth() ) && 1 <= $this->getMaxQueryDepth() ) {
752✔
165
                        $is_enabled = true;
×
166
                }
167

168
                return $is_enabled;
752✔
169
        }
170
}
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