File: /var/www/html/wp-content/plugins/woocommerce/vendor/pelago/emogrifier/src/Utilities/Preg.php
<?php
declare(strict_types=1);
namespace Pelago\Emogrifier\Utilities;
/**
* PHP's `preg_*` functions can return `false` on failure.
* Failure is rare but may occur with a complex pattern applied to a long subject.
* Catastrophic backtracking may occur ({@see https://www.regular-expressions.info/catastrophic.html}).
* Failure may also occur due to programmer error, if an invalid pattern is provided.
*
* Catering for failure in each case clutters up the code with error handling.
* This class provides wrappers for some `preg_*` functions, with errors handled either
* - by throwing an exception, or
* - by triggering a user error and providing fallback logic (e.g. returning the subject string unmodified).
*
* @internal
*/
final class Preg
{
/**
* whether to throw exceptions on errors (or call `trigger_error` and implement fallback)
*
* @var bool
*/
private $throwExceptions = false;
/**
* Sets whether exceptions should be thrown if an error occurs.
*/
public function throwExceptions(bool $throw): self
{
$this->throwExceptions = $throw;
return $this;
}
/**
* Wraps `preg_replace`, though does not support `$subject` being an array.
* If an error occurs, and exceptions are not being thrown, the original `$subject` is returned.
*
* @param non-empty-string|non-empty-array<non-empty-string> $pattern
* @param string|non-empty-array<string> $replacement
*
* @throws \RuntimeException
*/
public function replace($pattern, $replacement, string $subject, int $limit = -1, ?int &$count = null): string
{
$result = \preg_replace($pattern, $replacement, $subject, $limit, $count);
if ($result === null) {
$this->logOrThrowPregLastError();
$result = $subject;
}
return $result;
}
/**
* Wraps `preg_replace_callback`, though does not support `$subject` being an array.
* If an error occurs, and exceptions are not being thrown, the original `$subject` is returned.
*
* Note that (unlike when calling `preg_replace_callback`), `$callback` cannot be a non-public method
* represented by an array comprising an object or class name and the method name.
* To circumvent that, use `\Closure::fromCallable([$objectOrClassName, 'method'])`.
*
* @param non-empty-string|non-empty-array<non-empty-string> $pattern
*
* @throws \RuntimeException
*/
public function replaceCallback(
$pattern,
callable $callback,
string $subject,
int $limit = -1,
?int &$count = null
): string {
$result = \preg_replace_callback($pattern, $callback, $subject, $limit, $count);
if ($result === null) {
$this->logOrThrowPregLastError();
$result = $subject;
}
return $result;
}
/**
* Wraps `preg_split`.
* If an error occurs, and exceptions are not being thrown,
* a single-element array containing the original `$subject` is returned.
* This method does not support the `PREG_SPLIT_OFFSET_CAPTURE` flag and will throw an exception if it is specified.
*
* @param non-empty-string $pattern
*
* @return array<int, string>
*
* @throws \RuntimeException
*/
public function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
{
if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) {
throw new \RuntimeException('PREG_SPLIT_OFFSET_CAPTURE is not supported by Preg::split', 1726506348);
}
$result = \preg_split($pattern, $subject, $limit, $flags);
if ($result === false) {
$this->logOrThrowPregLastError();
$result = [$subject];
}
return $result;
}
/**
* Wraps `preg_match`.
* If an error occurs, and exceptions are not being thrown,
* zero (`0`) is returned, and if the `$matches` parameter is provided, it is set to an empty array.
* This method does not currently support the `$flags` or `$offset` parameters.
*
* @param non-empty-string $pattern
* @param array<int, string> $matches
*
* @return 0|1
*
* @throws \RuntimeException
*/
public function match(string $pattern, string $subject, ?array &$matches = null): int
{
$result = \preg_match($pattern, $subject, $matches);
if ($result === false) {
$this->logOrThrowPregLastError();
$result = 0;
$matches = [];
}
return $result;
}
/**
* Wraps `preg_match_all`.
*
* If an error occurs, and exceptions are not being thrown, zero (`0`) is returned.
*
* In the error case, if the `$matches` parameter is provided, it is set to an array containing empty arrays for the
* full pattern match and any possible subpattern match that might be expected.
* The algorithm to determine the length of this array simply counts the number of opening parentheses in the
* `$pattern`, which may result in a longer array than expected, but guarantees that it is at least as long as
* expected.
*
* This method does not currently support the `$flags` or `$offset` parameters.
*
* @param non-empty-string $pattern
* @param array<int, array<int, string>> $matches
*
* @throws \RuntimeException
*/
public function matchAll(string $pattern, string $subject, ?array &$matches = null): int
{
$result = \preg_match_all($pattern, $subject, $matches);
if ($result === false) {
$this->logOrThrowPregLastError();
$result = 0;
$matches = \array_fill(0, \substr_count($pattern, '(') + 1, []);
}
return $result;
}
/**
* Obtains the name of the error constant for `preg_last_error`
* (based on code posted at {@see https://www.php.net/manual/en/function.preg-last-error.php#124124})
* and puts it into an error message which is either passed to `trigger_error`
* or used in the exception which is thrown (depending on the `$throwExceptions` property).
*
* @throws \RuntimeException
*/
private function logOrThrowPregLastError(): void
{
$pcreConstants = \get_defined_constants(true)['pcre'];
$pcreErrorConstantNames = \array_flip(\array_filter(
$pcreConstants,
static function (string $key): bool {
return \substr($key, -6) === '_ERROR';
},
ARRAY_FILTER_USE_KEY
));
$pregLastError = \preg_last_error();
$message = 'PCRE regex execution error `' . (string) ($pcreErrorConstantNames[$pregLastError] ?? $pregLastError)
. '`';
if ($this->throwExceptions) {
throw new \RuntimeException($message, 1592870147);
}
\trigger_error($message);
}
}