sanitizeInvalidUTF8($text); return $adapter->sanitizeInvalidUTF8($adapter->substr($clean, 0, max(0, $maxChars))); } /** * Encode a record as JSON. Invalid UTF-8 becomes U+FFFD rather than * failing the encode. When the record still cannot be encoded, the failure * is logged as a durable WARN (record name plus the JSON error) and '' * is returned, so the caller drops the record knowing it was reported. * * @param mixed $value The record. * @param string $context What this record is, for the warning ("phase timeline"). * @param int $flags Extra json_encode flags (for example JSON_UNESCAPED_SLASHES). * @return string The JSON, or '' when the record could not be encoded. */ public static function encode($value, string $context, int $flags = 0): string { try { return json_encode($value, $flags | JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR); } catch (JsonException $e) { self::reportEncodeFailure($context, $e); return ''; } } /** * @param string $context * @param JsonException $failure * @return void */ private static function reportEncodeFailure(string $context, JsonException $failure): void { $message = $context . ' could not be JSON-encoded and was dropped (json error ' . $failure->getCode() . ': ' . $failure->getMessage() . ')'; try { $logger = function_exists('abj_service') ? abj_service('logging') : null; if (is_object($logger) && method_exists($logger, 'warnCaught')) { $logger->warnCaught($message, $failure); return; } } catch (Throwable $e) { $message .= '; the logger failed too (' . get_class($e) . ' ' . $e->getCode() . '): ' . $e->getMessage(); } if (function_exists('abj404_logPhpFallback')) { abj404_logPhpFallback('diagnostic-record-encode', $message); } } }