Keep line breaks in values, handle scalar and string-array JSON
- esc() helper escapes free-text values and converts newlines to <br>; used for commit messages, SMS text, annotations, and all JSON values - JSON parser accepts scalar bodies (string, number, bool, null) and renders them as a one-row table instead of falling back to the default template - Arrays of plain values render as one table under their path breadcrumb, so nested string lists are no longer anonymous - GitHub commits render as cards with a sha pill linked to the commit
This commit is contained in:
+4
-2
@@ -66,10 +66,12 @@ HTML;
|
||||
if ($commitCount > 0) {
|
||||
$eventContent .= '<div class="section"><div class="section-title">Commits</div>';
|
||||
foreach (array_slice($commits, 0, 10) as $commit) {
|
||||
$message = htmlspecialchars($commit['message'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$message = esc($commit['message'] ?? '');
|
||||
$author = htmlspecialchars($commit['author']['name'] ?? 'Unknown', ENT_QUOTES, 'UTF-8');
|
||||
$sha = htmlspecialchars(substr($commit['id'] ?? '', 0, 7), ENT_QUOTES, 'UTF-8');
|
||||
$eventContent .= "<div class=\"commit-item\"><strong>$sha</strong> $message <em>by $author</em></div>";
|
||||
$url = htmlspecialchars($commit['url'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$shaHtml = $url ? "<a class=\"sha\" href=\"$url\">$sha</a>" : "<span class=\"sha\">$sha</span>";
|
||||
$eventContent .= "<div class=\"commit-item\">$shaHtml <span class=\"who\">$author</span><div class=\"msg\">$message</div></div>";
|
||||
}
|
||||
$eventContent .= '</div>';
|
||||
}
|
||||
|
||||
+5
-5
@@ -95,7 +95,7 @@ HTML;
|
||||
$labelsContent .= '<div class="subsection-title">Group Labels</div><table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($groupLabels as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$labelsContent .= <<<HTML
|
||||
<tr>
|
||||
<td class="metadata-label">$keySafe</td>
|
||||
@@ -110,7 +110,7 @@ HTML;
|
||||
$labelsContent .= '<div class="subsection-title">Common Labels</div><table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($commonLabels as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$labelsContent .= <<<HTML
|
||||
<tr>
|
||||
<td class="metadata-label">$keySafe</td>
|
||||
@@ -130,7 +130,7 @@ HTML;
|
||||
$annotationsContent .= '<div class="section"><div class="section-title">Common Annotations</div><table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($commonAnnotations as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$annotationsContent .= <<<HTML
|
||||
<tr>
|
||||
<td class="metadata-label">$keySafe</td>
|
||||
@@ -180,7 +180,7 @@ HTML;
|
||||
$alertsContent .= '</table><div class="subsection-title">Labels</div><table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($alertLabels as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$alertsContent .= <<<HTML
|
||||
<tr>
|
||||
<td class="metadata-label">$keySafe</td>
|
||||
@@ -196,7 +196,7 @@ HTML;
|
||||
$alertsContent .= '</table><div class="subsection-title">Annotations</div><table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($alertAnnotations as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$alertsContent .= <<<HTML
|
||||
<tr>
|
||||
<td class="metadata-label">$keySafe</td>
|
||||
|
||||
+15
-4
@@ -3,6 +3,13 @@
|
||||
* Helper functions for webhook parsers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Escape a free-text value for an HTML cell, keeping line breaks.
|
||||
*/
|
||||
function esc($value) {
|
||||
return nl2br(htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render nested arrays/objects as HTML tables
|
||||
*
|
||||
@@ -18,7 +25,7 @@ function renderDataAsTable($data, $depth = 0) {
|
||||
$html = '';
|
||||
|
||||
if (!is_array($data)) {
|
||||
$safe = htmlspecialchars((string)$data, ENT_QUOTES, 'UTF-8');
|
||||
$safe = esc($data);
|
||||
return "<div class=\"simple-value\">$safe</div>";
|
||||
}
|
||||
|
||||
@@ -43,7 +50,7 @@ function renderDataAsTable($data, $depth = 0) {
|
||||
$html .= '</td>';
|
||||
$html .= '</tr>';
|
||||
} else {
|
||||
$valueSafe = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$html .= '<tr>';
|
||||
$html .= '<td class="metadata-label">' . $keySafe . '</td>';
|
||||
$html .= '<td class="metadata-value">' . $valueSafe . '</td>';
|
||||
@@ -53,7 +60,7 @@ function renderDataAsTable($data, $depth = 0) {
|
||||
$html .= '</table>';
|
||||
$html .= '</div>';
|
||||
} else {
|
||||
$valueSafe = htmlspecialchars((string)$item, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($item);
|
||||
$html .= '<div class="array-item-simple">• ' . $valueSafe . '</div>';
|
||||
}
|
||||
}
|
||||
@@ -77,7 +84,7 @@ function renderDataAsTable($data, $depth = 0) {
|
||||
$html .= '<td class="metadata-value"><em>empty</em></td>';
|
||||
$html .= '</tr>';
|
||||
} else {
|
||||
$valueSafe = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$html .= '<tr>';
|
||||
$html .= '<td class="metadata-label">' . $keySafe . '</td>';
|
||||
$html .= '<td class="metadata-value">' . $valueSafe . '</td>';
|
||||
@@ -139,6 +146,10 @@ function emailShell($title, $stamp, $body, $footer = 'Automatically forwarded to
|
||||
.array-item-title { font-size:14px; font-weight:800; margin:0 0 10px; color:#15151f; letter-spacing:-.01em; }
|
||||
.array-item-simple { margin:0 0 6px; padding:9px 14px; background:#f8f7fd; border:1px solid #e6e3f2; border-radius:10px; font-size:14px; }
|
||||
.simple-value { font-size:14px; padding:4px 0; }
|
||||
.commit-item { margin:0 0 10px; padding:12px 14px; background:#f8f7fd; border:1px solid #e6e3f2; border-radius:12px; }
|
||||
.commit-item .sha { display:inline-block; font-family:$mono; font-size:12px; font-weight:700; color:#be185d; background:#fdf2f8; border:1px solid #fbcfe8; border-radius:999px; padding:2px 9px; text-decoration:none; }
|
||||
.commit-item .who { font-size:12px; color:#6b6b80; margin-left:6px; }
|
||||
.commit-item .msg { margin-top:6px; font-size:14px; line-height:1.5; color:#15151f; }
|
||||
.message-box { margin:12px 0; padding:16px 18px 16px 20px; background:#f8f7fd; border-left:4px solid #8b5cf6; border-radius:0 12px 12px 0; font-size:16px; line-height:1.55; color:#15151f; }
|
||||
.error-box { margin:12px 0; padding:12px 14px; background:#fef2f2; border-left:4px solid #ef4444; border-radius:0 10px 10px 0; }
|
||||
.footer { padding:22px 34px 8px; text-align:center; color:#6a6a86; font-family:$mono; font-size:11px; letter-spacing:.06em; line-height:1.8; }
|
||||
|
||||
+22
-7
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
function jsonParse($rawBody, $headers, $metadata) {
|
||||
$payload = json_decode($rawBody, true);
|
||||
if (!$payload || !is_array($payload)) {
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -77,15 +77,30 @@ function renderJsonData($data, $path = '') {
|
||||
$html = '';
|
||||
|
||||
if (!is_array($data)) {
|
||||
$safe = htmlspecialchars((string)$data, ENT_QUOTES, 'UTF-8');
|
||||
return "<div class=\"simple-value\">$safe</div>";
|
||||
// Scalar (or null): one-row table so it lines up with everything else
|
||||
$label = htmlspecialchars($path ?: 'value', ENT_QUOTES, 'UTF-8');
|
||||
$safe = esc(is_string($data) ? $data : json_encode($data));
|
||||
return '<table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">'
|
||||
. '<tr><td class="metadata-label">' . $label . '</td><td class="metadata-value">' . $safe . '</td></tr></table>';
|
||||
}
|
||||
|
||||
// Check if it's an indexed array (numeric keys in sequence)
|
||||
$isIndexedArray = array_keys($data) === range(0, count($data) - 1);
|
||||
|
||||
if ($isIndexedArray && count($data) > 0) {
|
||||
// It's an array of items
|
||||
// Array of plain values: one table under a single breadcrumb
|
||||
if (!array_filter($data, 'is_array')) {
|
||||
if ($path) {
|
||||
$html .= '<div class="path-title">' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '</div>';
|
||||
}
|
||||
$html .= '<table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($data as $index => $item) {
|
||||
$html .= '<tr><td class="metadata-label">Item ' . ($index + 1) . '</td><td class="metadata-value">' . esc($item) . '</td></tr>';
|
||||
}
|
||||
return $html . '</table>';
|
||||
}
|
||||
|
||||
// Array of objects
|
||||
foreach ($data as $index => $item) {
|
||||
$itemPath = $path ? $path . '->[' . $index . ']' : '[' . $index . ']';
|
||||
|
||||
@@ -114,7 +129,7 @@ function renderJsonData($data, $path = '') {
|
||||
|
||||
foreach ($simpleData as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$html .= '<tr>';
|
||||
$html .= '<td class="metadata-label">' . $keySafe . '</td>';
|
||||
$html .= '<td class="metadata-value">' . $valueSafe . '</td>';
|
||||
@@ -132,7 +147,7 @@ function renderJsonData($data, $path = '') {
|
||||
}
|
||||
} else {
|
||||
// Simple value in array
|
||||
$valueSafe = htmlspecialchars((string)$item, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($item);
|
||||
$html .= '<table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
$html .= '<tr>';
|
||||
$html .= '<td class="metadata-label">Item ' . ($index + 1) . '</td>';
|
||||
@@ -165,7 +180,7 @@ function renderJsonData($data, $path = '') {
|
||||
|
||||
foreach ($simpleData as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$html .= '<tr>';
|
||||
$html .= '<td class="metadata-label">' . $keySafe . '</td>';
|
||||
$html .= '<td class="metadata-value">' . $valueSafe . '</td>';
|
||||
|
||||
@@ -119,7 +119,7 @@ function buildOutboundSmsSubmittedContent($data) {
|
||||
$messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$to = htmlspecialchars($data['to'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$message = htmlspecialchars($data['message'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$message = esc($data['message'] ?? 'N/A');
|
||||
$submittedAt = htmlspecialchars($data['submitted_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$campaignId = htmlspecialchars($data['campaign_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
|
||||
@@ -205,7 +205,7 @@ function buildOutboundSmsFailedContent($data) {
|
||||
$from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$failedAt = htmlspecialchars($data['failed_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$errorCode = htmlspecialchars($data['error_code'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$errorMessage = htmlspecialchars($data['error_message'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$errorMessage = esc($data['error_message'] ?? 'N/A');
|
||||
$campaignId = htmlspecialchars($data['campaign_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return <<<HTML
|
||||
@@ -307,7 +307,7 @@ function buildInboundSmsContent($data) {
|
||||
$messageId = htmlspecialchars($data['message_id'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$from = htmlspecialchars($data['from'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$to = htmlspecialchars($data['to'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$message = htmlspecialchars($data['message'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$message = esc($data['message'] ?? 'N/A');
|
||||
$receivedAt = htmlspecialchars($data['received_at'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
$keyword = htmlspecialchars($data['keyword'] ?? 'N/A', ENT_QUOTES, 'UTF-8');
|
||||
|
||||
@@ -402,7 +402,7 @@ function buildContactsCallbackContent($data) {
|
||||
$customFieldsHtml = '<div class="section"><div class="section-title">🏷️ Custom Fields</div><table class="metadata" role="presentation" cellspacing="0" cellpadding="0" width="100%">';
|
||||
foreach ($data['custom_fields'] as $key => $value) {
|
||||
$keySafe = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$valueSafe = esc($value);
|
||||
$customFieldsHtml .= <<<HTML
|
||||
<tr>
|
||||
<td class="metadata-label">$keySafe</td>
|
||||
|
||||
Reference in New Issue
Block a user