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

wp-graphql / wp-graphql / 13316763745

13 Feb 2025 08:45PM UTC coverage: 82.712% (-0.3%) from 83.023%
13316763745

push

github

web-flow
Merge pull request #3307 from wp-graphql/release/v2.0.0

release: v2.0.0

195 of 270 new or added lines in 20 files covered. (72.22%)

180 existing lines in 42 files now uncovered.

13836 of 16728 relevant lines covered (82.71%)

299.8 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
use function sprintf;
15

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

23
        /**
24
         * The max query depth allowed.
25
         *
26
         * @var int
27
         */
28
        private $maxQueryDepth;
29

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

39
        /**
40
         * {@inheritDoc}
41
         *
42
         * @param \GraphQL\Validator\QueryValidationContext $context
43
         *
44
         * @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))>
45
         */
46
        public function getVisitor( \GraphQL\Validator\QueryValidationContext $context ): array {
746✔
47
                return $this->invokeIfNeeded(
746✔
48
                        $context,
746✔
49
                        [
746✔
50
                                NodeKind::OPERATION_DEFINITION => [
746✔
51
                                        'leave' => function ( Node $node ) use ( $context ): void {
746✔
NEW
52
                                                if ( ! $node instanceof OperationDefinitionNode ) {
×
NEW
53
                                                        return;
×
54
                                                }
55

NEW
56
                                                $maxDepth = $this->fieldDepth( $node );
×
57

58
                                                if ( $maxDepth <= $this->getMaxQueryDepth() ) {
×
59
                                                        return;
×
60
                                                }
61

62
                                                $context->reportError(
×
63
                                                        new Error( $this->errorMessage( $this->getMaxQueryDepth(), $maxDepth ) )
×
64
                                                );
×
65
                                        },
746✔
66
                                ],
746✔
67
                        ]
746✔
68
                );
746✔
69
        }
70

71
        /**
72
         * Determine field depth
73
         *
74
         * @param mixed $node The node being analyzed
75
         * @param int   $depth The depth of the field
76
         * @param int   $maxDepth The max depth allowed
77
         *
78
         * @return int|mixed
79
         */
UNCOV
80
        private function fieldDepth( $node, $depth = 0, $maxDepth = 0 ) {
×
81
                if ( isset( $node->selectionSet ) && $node->selectionSet instanceof SelectionSetNode ) {
×
82
                        foreach ( $node->selectionSet->selections as $childNode ) {
×
83
                                $maxDepth = $this->nodeDepth( $childNode, $depth, $maxDepth );
×
84
                        }
85
                }
86

87
                return $maxDepth;
×
88
        }
89

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

112
                        case $node instanceof InlineFragmentNode:
×
113
                                // node has children?
114
                                $maxDepth = $this->fieldDepth( $node, $depth, $maxDepth );
×
115
                                break;
×
116

117
                        case $node instanceof FragmentSpreadNode:
×
118
                                $fragment = $this->getFragment( $node );
×
119

120
                                if ( null !== $fragment ) {
×
121
                                        $maxDepth = $this->fieldDepth( $fragment, $depth, $maxDepth );
×
122
                                }
123
                                break;
×
124
                }
125

126
                return $maxDepth;
×
127
        }
128

129
        /**
130
         * Return the maxQueryDepth allowed
131
         *
132
         * @return int
133
         */
UNCOV
134
        public function getMaxQueryDepth() {
×
135
                return $this->maxQueryDepth;
×
136
        }
137

138
        /**
139
         * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
140
         *
141
         * @param int $maxQueryDepth The max query depth to allow for GraphQL operations
142
         *
143
         * @return void
144
         */
145
        public function setMaxQueryDepth( int $maxQueryDepth ) {
754✔
146
                $this->checkIfGreaterOrEqualToZero( 'maxQueryDepth', $maxQueryDepth );
754✔
147

148
                $this->maxQueryDepth = $maxQueryDepth;
754✔
149
        }
150

151
        /**
152
         * Return the max query depth error message
153
         *
154
         * @param int $max The max number of levels to allow in GraphQL operation
155
         * @param int $count The number of levels in the current operation
156
         *
157
         * @return string
158
         */
UNCOV
159
        public function errorMessage( $max, $count ) {
×
160
                return sprintf( 'The server administrator has limited the max query depth to %d, but the requested query has %d levels.', $max, $count );
×
161
        }
162

163
        /**
164
         * Determine whether the rule should be enabled
165
         */
166
        protected function isEnabled(): bool {
746✔
167
                $is_enabled = false;
746✔
168

169
                $enabled = get_graphql_setting( 'query_depth_enabled', 'off' );
746✔
170

171
                if ( 'on' === $enabled && absint( $this->getMaxQueryDepth() ) && 1 <= $this->getMaxQueryDepth() ) {
746✔
172
                        $is_enabled = true;
×
173
                }
174

175
                return $is_enabled;
746✔
176
        }
177
}
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

© 2026 Coveralls, Inc