updated plugin WP Mail SMTP version 2.0.0

This commit is contained in:
2020-05-04 15:57:12 +00:00
committed by Gitium
parent 6a7ce488aa
commit 57b2d39b8f
321 changed files with 18242 additions and 5880 deletions

View File

@ -11,6 +11,8 @@
namespace Monolog\Formatter;
use Monolog\Utils;
/**
* Class FluentdFormatter
*
@ -71,7 +73,7 @@ class FluentdFormatter implements FormatterInterface
$message['level_name'] = $record['level_name'];
}
return json_encode(array($tag, $record['datetime']->getTimestamp(), $message));
return Utils::jsonEncode(array($tag, $record['datetime']->getTimestamp(), $message));
}
public function formatBatch(array $records)

View File

@ -11,6 +11,7 @@
namespace Monolog\Formatter;
use Monolog\Logger;
use Monolog\Utils;
/**
* Formats incoming records into an HTML table
@ -133,9 +134,9 @@ class HtmlFormatter extends NormalizerFormatter
$data = $this->normalize($data);
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
return Utils::jsonEncode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, true);
}
return str_replace('\\/', '/', json_encode($data));
return str_replace('\\/', '/', Utils::jsonEncode($data, null, true));
}
}

View File

@ -145,7 +145,7 @@ class JsonFormatter extends NormalizerFormatter
return 'Over 9 levels deep, aborting normalization';
}
if (is_array($data) || $data instanceof \Traversable) {
if (is_array($data)) {
$normalized = array();
$count = 1;
@ -165,6 +165,10 @@ class JsonFormatter extends NormalizerFormatter
return $this->normalizeException($data);
}
if (is_resource($data)) {
return parent::normalize($data);
}
return $data;
}
@ -186,7 +190,7 @@ class JsonFormatter extends NormalizerFormatter
$data = array(
'class' => Utils::getClass($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'code' => (int) $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
);

View File

@ -163,7 +163,7 @@ class LineFormatter extends NormalizerFormatter
return $this->toJson($data, true);
}
return str_replace('\\/', '/', @json_encode($data));
return str_replace('\\/', '/', $this->toJson($data, true));
}
protected function replaceNewlines($str)

View File

@ -87,7 +87,7 @@ class MongoDBFormatter implements FormatterInterface
$formattedException = array(
'class' => Utils::getClass($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'code' => (int) $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
);

View File

@ -129,7 +129,7 @@ class NormalizerFormatter implements FormatterInterface
$data = array(
'class' => Utils::getClass($e),
'message' => $e->getMessage(),
'code' => $e->getCode(),
'code' => (int) $e->getCode(),
'file' => $e->getFile().':'.$e->getLine(),
);
@ -142,8 +142,8 @@ class NormalizerFormatter implements FormatterInterface
$data['faultactor'] = $e->faultactor;
}
if (isset($e->detail)) {
$data['detail'] = $e->detail;
if (isset($e->detail) && (is_string($e->detail) || is_object($e->detail) || is_array($e->detail))) {
$data['detail'] = is_string($e->detail) ? $e->detail : reset($e->detail);
}
}
@ -171,127 +171,6 @@ class NormalizerFormatter implements FormatterInterface
*/
protected function toJson($data, $ignoreErrors = false)
{
// suppress json_encode errors since it's twitchy with some inputs
if ($ignoreErrors) {
return @$this->jsonEncode($data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$json = $this->handleJsonError(json_last_error(), $data);
}
return $json;
}
/**
* @param mixed $data
* @return string JSON encoded data or null on failure
*/
private function jsonEncode($data)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return json_encode($data);
}
/**
* Handle a json_encode failure.
*
* If the failure is due to invalid string encoding, try to clean the
* input and encode again. If the second encoding attempt fails, the
* inital error is not encoding related or the input can't be cleaned then
* raise a descriptive exception.
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException if failure can't be corrected
* @return string JSON encoded data after error correction
*/
private function handleJsonError($code, $data)
{
if ($code !== JSON_ERROR_UTF8) {
$this->throwEncodeError($code, $data);
}
if (is_string($data)) {
$this->detectAndCleanUtf8($data);
} elseif (is_array($data)) {
array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
} else {
$this->throwEncodeError($code, $data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$this->throwEncodeError(json_last_error(), $data);
}
return $json;
}
/**
* Throws an exception according to a given code with a customized message
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException
*/
private function throwEncodeError($code, $data)
{
switch ($code) {
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$msg = 'Unknown error';
}
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
}
/**
* Detect invalid UTF-8 string characters and convert to valid UTF-8.
*
* Valid UTF-8 input will be left unmodified, but strings containing
* invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
* original encoding of ISO-8859-15. This conversion may result in
* incorrect output if the actual encoding was not ISO-8859-15, but it
* will be clean UTF-8 output and will not rely on expensive and fragile
* detection algorithms.
*
* Function converts the input in place in the passed variable so that it
* can be used as a callback for array_walk_recursive.
*
* @param mixed &$data Input to check and convert if needed
* @private
*/
public function detectAndCleanUtf8(&$data)
{
if (is_string($data) && !preg_match('//u', $data)) {
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) { return utf8_encode($m[0]); },
$data
);
$data = str_replace(
array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
$data
);
}
return Utils::jsonEncode($data, null, $ignoreErrors);
}
}

View File

@ -164,21 +164,22 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
private static function handleStyles($formatted)
{
$args = array(static::quote('font-weight: normal'));
$args = array();
$format = '%c' . $formatted;
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
foreach (array_reverse($matches) as $match) {
$args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
$args[] = '"font-weight: normal"';
$args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
$pos = $match[0][1];
$format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
}
array_unshift($args, static::quote($format));
$args[] = static::quote('font-weight: normal');
$args[] = static::quote($format);
return $args;
return array_reverse($args);
}
private static function handleCustomStyles($style, $string)

View File

@ -13,6 +13,7 @@ namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;
/**
* Buffers all records until closing the handler and then pass them as batch.
@ -126,4 +127,22 @@ class BufferHandler extends AbstractHandler
$this->handler->reset();
}
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->handler->setFormatter($formatter);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->handler->getFormatter();
}
}

View File

@ -13,6 +13,7 @@ namespace Monolog\Handler;
use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Logger;
use Monolog\Utils;
/**
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
@ -134,7 +135,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
}
$json = @json_encode(self::$json);
$json = Utils::jsonEncode(self::$json, null, true);
$data = base64_encode(utf8_encode($json));
if (strlen($data) > 3 * 1024) {
self::$overflowed = true;
@ -149,7 +150,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
'extra' => array(),
);
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
$json = @json_encode(self::$json);
$json = Utils::jsonEncode(self::$json, null, true);
$data = base64_encode(utf8_encode($json));
}

View File

@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Utils;
/**
* Logs to Cube.
@ -119,9 +120,9 @@ class CubeHandler extends AbstractProcessingHandler
$data['data']['level'] = $record['level'];
if ($this->scheme === 'http') {
$this->writeHttp(json_encode($data));
$this->writeHttp(Utils::jsonEncode($data));
} else {
$this->writeUdp(json_encode($data));
$this->writeUdp(Utils::jsonEncode($data));
}
}

View File

@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
/**
* Simple handler wrapper that filters records based on a list of levels
@ -45,7 +46,7 @@ class FilterHandler extends AbstractHandler
protected $bubble;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $this).
* @param callable|HandlerInterface $handler Handler or factory callable($record|null, $filterHandler).
* @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
* @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
@ -104,21 +105,13 @@ class FilterHandler extends AbstractHandler
return false;
}
// The same logic as in FingersCrossedHandler
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->handler->handle($record);
$this->getHandler($record)->handle($record);
return false === $this->bubble;
}
@ -135,6 +128,45 @@ class FilterHandler extends AbstractHandler
}
}
$this->handler->handleBatch($filtered);
if (count($filtered) > 0) {
$this->getHandler($filtered[count($filtered) - 1])->handleBatch($filtered);
}
}
/**
* Return the nested handler
*
* If the handler was provided as a factory callable, this will trigger the handler's instantiation.
*
* @return HandlerInterface
*/
public function getHandler(array $record = null)
{
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
return $this->handler;
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->getHandler()->setFormatter($formatter);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->getHandler()->getFormatter();
}
}

View File

@ -15,6 +15,7 @@ use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
use Monolog\ResettableInterface;
use Monolog\Formatter\FormatterInterface;
/**
* Buffers all records until a certain level is reached
@ -39,7 +40,7 @@ class FingersCrossedHandler extends AbstractHandler
protected $passthruLevel;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
* @param callable|HandlerInterface $handler Handler or factory callable($record|null, $fingersCrossedHandler).
* @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
* @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
@ -88,15 +89,7 @@ class FingersCrossedHandler extends AbstractHandler
if ($this->stopBuffering) {
$this->buffering = false;
}
if (!$this->handler instanceof HandlerInterface) {
$record = end($this->buffer) ?: null;
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
$this->handler->handleBatch($this->buffer);
$this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer);
$this->buffer = array();
}
@ -120,7 +113,7 @@ class FingersCrossedHandler extends AbstractHandler
$this->activate();
}
} else {
$this->handler->handle($record);
$this->getHandler($record)->handle($record);
}
return false === $this->bubble;
@ -140,8 +133,8 @@ class FingersCrossedHandler extends AbstractHandler
parent::reset();
if ($this->handler instanceof ResettableInterface) {
$this->handler->reset();
if ($this->getHandler() instanceof ResettableInterface) {
$this->getHandler()->reset();
}
}
@ -167,11 +160,48 @@ class FingersCrossedHandler extends AbstractHandler
return $record['level'] >= $level;
});
if (count($this->buffer) > 0) {
$this->handler->handleBatch($this->buffer);
$this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer);
}
}
$this->buffer = array();
$this->buffering = true;
}
/**
* Return the nested handler
*
* If the handler was provided as a factory callable, this will trigger the handler's instantiation.
*
* @return HandlerInterface
*/
public function getHandler(array $record = null)
{
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
return $this->handler;
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->getHandler()->setFormatter($formatter);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->getHandler()->getFormatter();
}
}

View File

@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Formatter\FlowdockFormatter;
use Monolog\Formatter\FormatterInterface;
@ -105,7 +106,7 @@ class FlowdockHandler extends SocketHandler
*/
private function buildContent($record)
{
return json_encode($record['formatted']['flowdock']);
return Utils::jsonEncode($record['formatted']['flowdock']);
}
/**

View File

@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Utils;
/**
* IFTTTHandler uses cURL to trigger IFTTT Maker actions
@ -53,7 +54,7 @@ class IFTTTHandler extends AbstractProcessingHandler
"value2" => $record["level_name"],
"value3" => $record["message"],
);
$postString = json_encode($postData);
$postString = Utils::jsonEncode($postData);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey);

View File

@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Formatter\NormalizerFormatter;
/**
@ -190,7 +191,7 @@ class NewRelicHandler extends AbstractProcessingHandler
if (null === $value || is_scalar($value)) {
newrelic_add_custom_parameter($key, $value);
} else {
newrelic_add_custom_parameter($key, @json_encode($value));
newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true));
}
}

View File

@ -14,6 +14,7 @@ namespace Monolog\Handler;
use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use Monolog\Utils;
use PhpConsole\Connector;
use PhpConsole\Handler;
use PhpConsole\Helper;
@ -188,7 +189,7 @@ class PHPConsoleHandler extends AbstractProcessingHandler
$tags = $this->getRecordTags($record);
$message = $record['message'];
if ($record['context']) {
$message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context'])));
$message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($record['context'])), null, true);
}
$this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']);
}

View File

@ -73,7 +73,8 @@ class RedisHandler extends AbstractProcessingHandler
protected function writeCapped(array $record)
{
if ($this->redisClient instanceof \Redis) {
$this->redisClient->multi()
$mode = defined('\Redis::MULTI') ? \Redis::MULTI : 1;
$this->redisClient->multi($mode)
->rpush($this->redisKey, $record["formatted"])
->ltrim($this->redisKey, -$this->capSize, -1)
->exec();

View File

@ -11,6 +11,8 @@
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Sampling handler
*
@ -38,7 +40,7 @@ class SamplingHandler extends AbstractHandler
protected $factor;
/**
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
* @param callable|HandlerInterface $handler Handler or factory callable($record|null, $samplingHandler).
* @param int $factor Sample factor
*/
public function __construct($handler, $factor)
@ -54,29 +56,58 @@ class SamplingHandler extends AbstractHandler
public function isHandling(array $record)
{
return $this->handler->isHandling($record);
return $this->getHandler($record)->isHandling($record);
}
public function handle(array $record)
{
if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) {
// The same logic as in FingersCrossedHandler
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->handler->handle($record);
$this->getHandler($record)->handle($record);
}
return false === $this->bubble;
}
/**
* Return the nested handler
*
* If the handler was provided as a factory callable, this will trigger the handler's instantiation.
*
* @return HandlerInterface
*/
public function getHandler(array $record = null)
{
if (!$this->handler instanceof HandlerInterface) {
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
return $this->handler;
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->getHandler()->setFormatter($formatter);
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->getHandler()->getFormatter();
}
}

View File

@ -12,6 +12,7 @@
namespace Monolog\Handler\Slack;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Formatter\NormalizerFormatter;
use Monolog\Formatter\FormatterInterface;
@ -207,13 +208,17 @@ class SlackRecord
{
$normalized = $this->normalizerFormatter->format($fields);
$prettyPrintFlag = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 128;
$flags = 0;
if (PHP_VERSION_ID >= 50400) {
$flags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
}
$hasSecondDimension = count(array_filter($normalized, 'is_array'));
$hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric'));
return $hasSecondDimension || $hasNonNumericKeys
? json_encode($normalized, $prettyPrintFlag)
: json_encode($normalized);
? Utils::jsonEncode($normalized, $prettyPrintFlag | $flags)
: Utils::jsonEncode($normalized, $flags);
}
/**

View File

@ -13,6 +13,7 @@ namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Handler\Slack\SlackRecord;
/**
@ -118,7 +119,7 @@ class SlackHandler extends SocketHandler
$dataArray['token'] = $this->token;
if (!empty($dataArray['attachments'])) {
$dataArray['attachments'] = json_encode($dataArray['attachments']);
$dataArray['attachments'] = Utils::jsonEncode($dataArray['attachments']);
}
return $dataArray;

View File

@ -13,6 +13,7 @@ namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Utils;
use Monolog\Handler\Slack\SlackRecord;
/**
@ -83,7 +84,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler
protected function write(array $record)
{
$postData = $this->slackRecord->getSlackData($record);
$postString = json_encode($postData);
$postString = Utils::jsonEncode($postData);
$ch = curl_init();
$options = array(

View File

@ -527,8 +527,13 @@ class Logger implements LoggerInterface, ResettableInterface
*/
public static function toMonologLevel($level)
{
if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) {
return constant(__CLASS__.'::'.strtoupper($level));
if (is_string($level)) {
// Contains chars of all log levels and avoids using strtoupper() which may have
// strange results depending on locale (for example, "i" will become "İ")
$upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY');
if (defined(__CLASS__.'::'.$upper)) {
return constant(__CLASS__ . '::' . $upper);
}
}
return $level;

View File

@ -22,4 +22,138 @@ class Utils
return 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
}
/**
* Return the JSON representation of a value
*
* @param mixed $data
* @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
* @param bool $ignoreErrors whether to ignore encoding errors or to throw on error, when ignored and the encoding fails, "null" is returned which is valid json for null
* @throws \RuntimeException if encoding fails and errors are not ignored
* @return string
*/
public static function jsonEncode($data, $encodeFlags = null, $ignoreErrors = false)
{
if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) {
$encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
}
if ($ignoreErrors) {
$json = @json_encode($data, $encodeFlags);
if (false === $json) {
return 'null';
}
return $json;
}
$json = json_encode($data, $encodeFlags);
if (false === $json) {
$json = self::handleJsonError(json_last_error(), $data);
}
return $json;
}
/**
* Handle a json_encode failure.
*
* If the failure is due to invalid string encoding, try to clean the
* input and encode again. If the second encoding attempt fails, the
* inital error is not encoding related or the input can't be cleaned then
* raise a descriptive exception.
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @param int $encodeFlags flags to pass to json encode, defaults to JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
* @throws \RuntimeException if failure can't be corrected
* @return string JSON encoded data after error correction
*/
public static function handleJsonError($code, $data, $encodeFlags = null)
{
if ($code !== JSON_ERROR_UTF8) {
self::throwEncodeError($code, $data);
}
if (is_string($data)) {
self::detectAndCleanUtf8($data);
} elseif (is_array($data)) {
array_walk_recursive($data, array('Monolog\Utils', 'detectAndCleanUtf8'));
} else {
self::throwEncodeError($code, $data);
}
if (null === $encodeFlags && version_compare(PHP_VERSION, '5.4.0', '>=')) {
$encodeFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
}
$json = json_encode($data, $encodeFlags);
if ($json === false) {
self::throwEncodeError(json_last_error(), $data);
}
return $json;
}
/**
* Throws an exception according to a given code with a customized message
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException
*/
private static function throwEncodeError($code, $data)
{
switch ($code) {
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$msg = 'Unknown error';
}
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
}
/**
* Detect invalid UTF-8 string characters and convert to valid UTF-8.
*
* Valid UTF-8 input will be left unmodified, but strings containing
* invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
* original encoding of ISO-8859-15. This conversion may result in
* incorrect output if the actual encoding was not ISO-8859-15, but it
* will be clean UTF-8 output and will not rely on expensive and fragile
* detection algorithms.
*
* Function converts the input in place in the passed variable so that it
* can be used as a callback for array_walk_recursive.
*
* @param mixed &$data Input to check and convert if needed
* @private
*/
public static function detectAndCleanUtf8(&$data)
{
if (is_string($data) && !preg_match('//u', $data)) {
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) { return utf8_encode($m[0]); },
$data
);
$data = str_replace(
array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
$data
);
}
}
}