PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.4
WooCommerce v10.9.4
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / lib / packages / GraphQL / Error / FormattedError.php
woocommerce / lib / packages / GraphQL / Error Last commit date
ClientAware.php 2 months ago CoercionError.php 2 months ago DebugFlag.php 2 months ago Error.php 2 months ago FormattedError.php 2 months ago InvariantViolation.php 2 months ago ProvidesExtensions.php 2 months ago SerializationError.php 2 months ago SyntaxError.php 2 months ago UserError.php 2 months ago Warning.php 2 months ago
FormattedError.php
338 lines
1 <?php declare(strict_types=1);
2
3 namespace Automattic\WooCommerce\Vendor\GraphQL\Error;
4
5 use Automattic\WooCommerce\Vendor\GraphQL\Executor\ExecutionResult;
6 use Automattic\WooCommerce\Vendor\GraphQL\Language\Source;
7 use Automattic\WooCommerce\Vendor\GraphQL\Language\SourceLocation;
8 use Automattic\WooCommerce\Vendor\GraphQL\Type\Definition\Type;
9 use Automattic\WooCommerce\Vendor\GraphQL\Utils\Utils;
10 use PHPUnit\Framework\Test;
11
12 /**
13 * This class is used for [default error formatting](error-handling.md).
14 * It converts PHP exceptions to [spec-compliant errors](https://facebook.github.io/graphql/#sec-Errors)
15 * and provides tools for error debugging.
16 *
17 * @see ExecutionResult
18 *
19 * @phpstan-import-type SerializableError from ExecutionResult
20 * @phpstan-import-type ErrorFormatter from ExecutionResult
21 *
22 * @see \Automattic\WooCommerce\Vendor\GraphQL\Tests\Error\FormattedErrorTest
23 */
24 class FormattedError
25 {
26 private static string $internalErrorMessage = 'Internal server error';
27
28 /**
29 * Set default error message for internal errors formatted using createFormattedError().
30 * This value can be overridden by passing 3rd argument to `createFormattedError()`.
31 *
32 * @api
33 */
34 public static function setInternalErrorMessage(string $msg): void
35 {
36 self::$internalErrorMessage = $msg;
37 }
38
39 /**
40 * Prints a GraphQLError to a string, representing useful location information
41 * about the error's position in the source.
42 */
43 public static function printError(Error $error): string
44 {
45 $printedLocations = [];
46
47 $nodes = $error->nodes;
48 if (isset($nodes) && $nodes !== []) {
49 foreach ($nodes as $node) {
50 $location = $node->loc;
51 if (isset($location)) {
52 $source = $location->source;
53 if (isset($source)) {
54 $printedLocations[] = self::highlightSourceAtLocation(
55 $source,
56 $source->getLocation($location->start)
57 );
58 }
59 }
60 }
61 } elseif ($error->getSource() !== null && $error->getLocations() !== []) {
62 $source = $error->getSource();
63 foreach ($error->getLocations() as $location) {
64 $printedLocations[] = self::highlightSourceAtLocation($source, $location);
65 }
66 }
67
68 return $printedLocations === []
69 ? $error->getMessage()
70 : implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
71 }
72
73 /**
74 * Render a helpful description of the location of the error in the Automattic\WooCommerce\Vendor\GraphQL
75 * Source document.
76 */
77 private static function highlightSourceAtLocation(Source $source, SourceLocation $location): string
78 {
79 $line = $location->line;
80 $lineOffset = $source->locationOffset->line - 1;
81 $columnOffset = self::getColumnOffset($source, $location);
82 $contextLine = $line + $lineOffset;
83 $contextColumn = $location->column + $columnOffset;
84 $prevLineNum = (string) ($contextLine - 1);
85 $lineNum = (string) $contextLine;
86 $nextLineNum = (string) ($contextLine + 1);
87 $padLen = strlen($nextLineNum);
88
89 $lines = Utils::splitLines($source->body);
90 $lines[0] = self::spaces($source->locationOffset->column - 1) . $lines[0];
91
92 $outputLines = [
93 "{$source->name} ({$contextLine}:{$contextColumn})",
94 $line >= 2 ? (self::leftPad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
95 self::leftPad($padLen, $lineNum) . ': ' . $lines[$line - 1],
96 self::spaces(2 + $padLen + $contextColumn - 1) . '^',
97 $line < count($lines) ? self::leftPad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
98 ];
99
100 return implode("\n", array_filter($outputLines));
101 }
102
103 private static function getColumnOffset(Source $source, SourceLocation $location): int
104 {
105 return $location->line === 1
106 ? $source->locationOffset->column - 1
107 : 0;
108 }
109
110 private static function spaces(int $length): string
111 {
112 return str_repeat(' ', $length);
113 }
114
115 private static function leftPad(int $length, string $str): string
116 {
117 return self::spaces($length - mb_strlen($str)) . $str;
118 }
119
120 /**
121 * Convert any exception to a Automattic\WooCommerce\Vendor\GraphQL spec compliant array.
122 *
123 * This method only exposes the exception message when the given exception
124 * implements the ClientAware interface, or when debug flags are passed.
125 *
126 * For a list of available debug flags @see \Automattic\WooCommerce\Vendor\GraphQL\Error\DebugFlag constants.
127 *
128 * @return SerializableError
129 *
130 * @api
131 */
132 public static function createFromException(\Throwable $exception, int $debugFlag = DebugFlag::NONE, ?string $internalErrorMessage = null): array
133 {
134 $internalErrorMessage ??= self::$internalErrorMessage;
135
136 $message = $exception instanceof ClientAware && $exception->isClientSafe()
137 ? $exception->getMessage()
138 : $internalErrorMessage;
139
140 $formattedError = ['message' => $message];
141
142 if ($exception instanceof Error) {
143 $locations = array_map(
144 static fn (SourceLocation $loc): array => $loc->toSerializableArray(),
145 $exception->getLocations()
146 );
147 if ($locations !== []) {
148 $formattedError['locations'] = $locations;
149 }
150
151 if ($exception->path !== null && $exception->path !== []) {
152 $formattedError['path'] = $exception->path;
153 }
154 }
155
156 if ($exception instanceof ProvidesExtensions) {
157 $extensions = $exception->getExtensions();
158 if (is_array($extensions) && $extensions !== []) {
159 $formattedError['extensions'] = $extensions;
160 }
161 }
162
163 if ($debugFlag !== DebugFlag::NONE) {
164 $formattedError = self::addDebugEntries($formattedError, $exception, $debugFlag);
165 }
166
167 return $formattedError;
168 }
169
170 /**
171 * Decorates spec-compliant $formattedError with debug entries according to $debug flags.
172 *
173 * @param SerializableError $formattedError
174 * @param int $debugFlag For available flags @see \Automattic\WooCommerce\Vendor\GraphQL\Error\DebugFlag
175 *
176 * @throws \Throwable
177 *
178 * @return SerializableError
179 */
180 public static function addDebugEntries(array $formattedError, \Throwable $e, int $debugFlag): array
181 {
182 if ($debugFlag === DebugFlag::NONE) {
183 return $formattedError;
184 }
185
186 if (($debugFlag & DebugFlag::RETHROW_INTERNAL_EXCEPTIONS) !== 0) {
187 if (! $e instanceof Error) {
188 throw $e;
189 }
190
191 if ($e->getPrevious() !== null) {
192 throw $e->getPrevious();
193 }
194 }
195
196 $isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
197
198 if (($debugFlag & DebugFlag::RETHROW_UNSAFE_EXCEPTIONS) !== 0 && $isUnsafe && $e->getPrevious() !== null) {
199 throw $e->getPrevious();
200 }
201
202 if (($debugFlag & DebugFlag::INCLUDE_DEBUG_MESSAGE) !== 0 && $isUnsafe) {
203 $formattedError['extensions']['debugMessage'] = $e->getMessage();
204 }
205
206 if (($debugFlag & DebugFlag::INCLUDE_TRACE) !== 0) {
207 $actualError = $e->getPrevious() ?? $e;
208 if ($e instanceof \ErrorException || $e instanceof \Error) {
209 $formattedError['extensions']['file'] = $e->getFile();
210 $formattedError['extensions']['line'] = $e->getLine();
211 } else {
212 $formattedError['extensions']['file'] = $actualError->getFile();
213 $formattedError['extensions']['line'] = $actualError->getLine();
214 }
215
216 $isTrivial = $e instanceof Error && $e->getPrevious() === null;
217
218 if (! $isTrivial) {
219 $formattedError['extensions']['trace'] = static::toSafeTrace($actualError);
220 }
221 }
222
223 return $formattedError;
224 }
225
226 /**
227 * Prepares final error formatter taking in account $debug flags.
228 *
229 * If initial formatter is not set, FormattedError::createFromException is used.
230 *
231 * @phpstan-param ErrorFormatter|null $formatter
232 */
233 public static function prepareFormatter(?callable $formatter, int $debug): callable
234 {
235 return $formatter === null
236 ? static fn (\Throwable $e): array => static::createFromException($e, $debug)
237 : static fn (\Throwable $e): array => static::addDebugEntries($formatter($e), $e, $debug);
238 }
239
240 /**
241 * Returns error trace as serializable array.
242 *
243 * @return array<int, array{
244 * file?: string,
245 * line?: int,
246 * function?: string,
247 * call?: string,
248 * }>
249 *
250 * @api
251 */
252 public static function toSafeTrace(\Throwable $error): array
253 {
254 $trace = $error->getTrace();
255
256 if (
257 isset($trace[0]['function']) && isset($trace[0]['class'])
258 // Remove invariant entries as they don't provide much value:
259 && ($trace[0]['class'] . '::' . $trace[0]['function'] === 'Automattic\WooCommerce\Vendor\GraphQL\Utils\Utils::invariant')
260 ) {
261 array_shift($trace);
262 } elseif (! isset($trace[0]['file'])) {
263 // Remove root call as it's likely error handler trace:
264 array_shift($trace);
265 }
266
267 $formatted = [];
268 foreach ($trace as $err) {
269 $safeErr = [];
270
271 if (isset($err['file'])) {
272 $safeErr['file'] = $err['file'];
273 }
274
275 if (isset($err['line'])) {
276 $safeErr['line'] = $err['line'];
277 }
278
279 $func = $err['function'];
280 $args = array_map([self::class, 'printVar'], $err['args'] ?? []);
281 $funcStr = $func . '(' . implode(', ', $args) . ')';
282
283 if (isset($err['class'])) {
284 $safeErr['call'] = $err['class'] . '::' . $funcStr;
285 } else {
286 $safeErr['function'] = $funcStr;
287 }
288
289 $formatted[] = $safeErr;
290 }
291
292 return $formatted;
293 }
294
295 /** @param mixed $var */
296 public static function printVar($var): string
297 {
298 if ($var instanceof Type) {
299 return 'GraphQLType: ' . $var->toString();
300 }
301
302 if (is_object($var)) {
303 // Calling `count` on instances of `PHPUnit\Framework\Test` triggers an unintended side effect - see https://github.com/sebastianbergmann/phpunit/issues/5866#issuecomment-2172429263
304 $count = ! $var instanceof Test && $var instanceof \Countable
305 ? '(' . count($var) . ')'
306 : '';
307
308 return 'instance of ' . get_class($var) . $count;
309 }
310
311 if (is_array($var)) {
312 return 'array(' . count($var) . ')';
313 }
314
315 if ($var === '') {
316 return '(empty string)';
317 }
318
319 if (is_string($var)) {
320 return "'" . addcslashes($var, "'") . "'";
321 }
322
323 if (is_bool($var)) {
324 return $var ? 'true' : 'false';
325 }
326
327 if (is_scalar($var)) {
328 return (string) $var;
329 }
330
331 if ($var === null) {
332 return 'null';
333 }
334
335 return gettype($var);
336 }
337 }
338