|null $details Diagnostic detail. Withheld * from anyone who is not a plugin admin, since it carries query shapes, * buffered output and context. * @param bool $isPluginAdmin * @return array */ public static function build($message, $details, $isPluginAdmin) { $data = array( 'message' => $message, ); if ($isPluginAdmin && $details !== null) { $data['details'] = $details; } return array( 'success' => false, 'data' => $data, ); } /** * The envelope as JSON bytes that cannot themselves fail to encode. * * Three steps, in this order and for this reason: * * 1. Reduce to printable ASCII. This is what makes the encode below * total: the likeliest caller message on this path is a * json_last_error cause or a Throwable message, either of which can * carry the very invalid UTF-8 that put us here. * 2. Cap. On the reduced text, so no escape can straddle the boundary. * 3. Encode. json_encode() does the escaping, which is the point -- * the previous hand-rolled sprintf() template offered its callers no * escaping at all, so one quote in a caller-supplied message produced * `{"success":false,"data":{"message":"he said "boom""}}`. */ public static function encodeSafely(string $message): string { $reduced = (string)preg_replace('/[^\x20-\x7E]/', '', $message); if (strlen($reduced) > self::MAX_MESSAGE_BYTES) { $reduced = substr($reduced, 0, self::MAX_MESSAGE_BYTES); } if (trim($reduced) === '') { $reduced = self::UNKNOWN_REASON; } $json = json_encode(self::build($reduced, null, false)); return is_string($json) ? $json : self::UNENCODABLE_FALLBACK; } }