Skip to content

Instantly share code, notes, and snippets.

@ozgursar
Last active March 14, 2026 10:18
Show Gist options
  • Select an option

  • Save ozgursar/62ee9d1a9d5491b30c1d277489b46644 to your computer and use it in GitHub Desktop.

Select an option

Save ozgursar/62ee9d1a9d5491b30c1d277489b46644 to your computer and use it in GitHub Desktop.
WordPress Core Trac 29807 Tests Plugin
<?php
/**
* Plugin Name: #29807 Tests
* Description: Pass/fail demonstration for wp_kses picture/srcset support. Run before and after patch.
* Ozgur Sar
* Version: 1.0.0
*/
// ── Register Tools subpage ────────────────────────────────────────────
add_action( 'admin_menu', function () {
add_management_page(
'#29807 Tests',
'#29807 Tests',
'manage_options',
'kses-29807-demo',
'kses_29807_render_page'
);
} );
// ── Page renderer ─────────────────────────────────────────────────────
function kses_29807_render_page() {
global $allowedposttags;
$patched = function_exists( 'wp_kses_sanitize_uris' );
$allowed_protocols = wp_allowed_protocols();
$groups = array();
// ════════════════════════════════════════════════════════════════════
// GROUP 1 — <img> attribute allowlist
// test_wp_filter_post_kses_img + test_wp_kses_img_decoding_and_fetchpriority
// ════════════════════════════════════════════════════════════════════
$rows = array();
foreach ( array(
'srcset and sizes on &lt;img&gt;' =>
'<img src="photo.jpg" srcset="/photo-1x.jpg 1x, /photo-2x.jpg 2x" sizes="(max-width: 600px) 100vw, 50vw" />',
'decoding="async" on &lt;img&gt;' =>
'<img src="test.jpg" decoding="async" />',
'fetchpriority="high" on &lt;img&gt;' =>
'<img src="test.jpg" fetchpriority="high" />',
'Full real-world &lt;img&gt; — decoding, fetchpriority, srcset, sizes, loading' =>
'<img src="test.jpg" decoding="async" fetchpriority="high" srcset="small.jpg 1x, large.jpg 2x" sizes="100vw" loading="lazy" />',
'Complex multi-width srcset with sizes' =>
'<img src="default.jpg" srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1024w, xlarge.jpg 1440w" sizes="(max-width: 480px) 100vw, (max-width: 768px) 75vw, 50vw" alt="Responsive image" />',
) as $label => $input ) {
$output = wp_kses( $input, $allowedposttags );
$rows[] = array( 'label' => $label, 'input' => $input, 'output' => $output, 'pass' => $output === $input );
}
$groups[] = array( 'title' => 'GROUP 1 — &lt;img&gt; attribute allowlist', 'fn' => 'wp_kses( $html, $allowedposttags )', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 2 — <picture> and <source> element support
// test_wp_filter_post_kses_picture + test_wp_kses_source_element_attributes
// + test_wp_kses_picture_element_edge_cases + test_wp_kses_sizes_attribute
// + test_wp_kses_comprehensive_responsive_images
// ════════════════════════════════════════════════════════════════════
$rows = array();
// Basic picture preserved
$in = '<picture><source srcset="pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => 'Basic &lt;picture&gt; with multiple &lt;source&gt; preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// picture with absolute URLs in srcset
$in = '<picture><source srcset="https://wordpress.org/pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="https://wordpress.org/pear-tablet.jpeg 500w, https://wordpress.org/pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => '&lt;picture&gt; with absolute URLs in srcset preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// source: all four allowed attributes
$in = '<source srcset="img.jpg" type="image/webp" media="(min-width: 800px)" sizes="100vw">';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => '&lt;source&gt; srcset, type, media, sizes all preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// source: disallowed src stripped
$in = '<source srcset="img.jpg" src="fallback.jpg">';
$exp = '<source srcset="img.jpg">';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => 'Disallowed src attribute stripped from &lt;source&gt;', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// source: onclick stripped
$in = '<picture><source srcset="image.jpg" onclick="alert(1)"><img src="fallback.jpg"></picture>';
$exp = '<picture><source srcset="image.jpg"><img src="fallback.jpg"></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => 'onclick event handler stripped from &lt;source&gt;', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// source: onerror stripped (from test_wp_kses_source_element_attributes)
$in = '<source srcset="img.jpg" onerror="alert(1)">';
$exp = '<source srcset="img.jpg">';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => 'onerror event handler stripped from &lt;source&gt;', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// sizes on <source> inside <picture> (test_wp_kses_sizes_attribute)
$in = '<picture><source srcset="mobile.jpg" sizes="100vw" media="(max-width: 600px)"><img src="desktop.jpg"></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => 'sizes attribute on &lt;source&gt; inside &lt;picture&gt; preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// empty picture
$in = '<picture></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => 'Empty &lt;picture&gt; preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// picture without fallback img
$in = '<picture><source srcset="img.webp" type="image/webp"></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => '&lt;picture&gt; without fallback &lt;img&gt; preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// picture with only img
$in = '<picture><img src="only-img.jpg" /></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array( 'label' => '&lt;picture&gt; with only &lt;img&gt;, no &lt;source&gt;', 'input' => $in, 'output' => $out, 'pass' => $out === $in );
// nested picture (test_wp_kses_comprehensive_responsive_images)
$in = '<picture><picture><source srcset="inner.jpg"></picture><source srcset="outer.jpg"><img src="fallback.jpg"></picture>';
$out = wp_kses( $in, $allowedposttags );
$rows[] = array(
'label' => 'Nested &lt;picture&gt; inside &lt;picture&gt; — structure preserved',
'input' => $in,
'output' => $out,
'pass' => str_contains( $out, '<picture>' ) && str_contains( $out, '<source' ),
);
$groups[] = array( 'title' => 'GROUP 2 — &lt;picture&gt; and &lt;source&gt; element support', 'fn' => 'wp_kses( $html, $allowedposttags )', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 3 — Protocol sanitization in srcset
// test_wp_kses_srcset + test_wp_kses_one_attr_srcset
// + test_wp_kses_comprehensive_responsive_images
// ════════════════════════════════════════════════════════════════════
$rows = array();
// clean relative paths pass through
$in = "<img src='test.png' srcset='/test.png 1x, /test-2x.png 2x' />";
$exp = '<img src="test.png" srcset="/test.png 1x, /test-2x.png 2x" />';
$out = wp_kses_post( $in );
$rows[] = array( 'label' => 'Clean relative srcset paths pass through unchanged', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// bad:// in first position
$in = "<img src='test.png' srcset='bad://localhost/test.png 1x, http://localhost/test-2x.png 2x' />";
$exp = '<img src="test.png" srcset="//localhost/test.png 1x, http://localhost/test-2x.png 2x" />';
$out = wp_kses_post( $in );
$rows[] = array( 'label' => 'bad:// in first srcset entry stripped; second preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// bad:// in second position
$in = "<img src='test.png' srcset='http://localhost/test.png 1x, bad://localhost/test-2x.png 2x' />";
$exp = '<img src="test.png" srcset="http://localhost/test.png 1x, //localhost/test-2x.png 2x" />';
$out = wp_kses_post( $in );
$rows[] = array( 'label' => 'bad:// in second srcset entry stripped; first preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// comma before descriptor with no space (test_wp_kses_srcset 4th case)
$in = "<img src='test.png' srcset='http://localhost/test.png,big 1x, bad://localhost/test.png,medium 2x' />";
$exp = '<img src="test.png" srcset="http://localhost/test.png, big 1x, //localhost/test.png, medium 2x" />';
$out = wp_kses_post( $in );
$rows[] = array( 'label' => 'Comma before descriptor (no space) — comma separated out; bad:// stripped', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// bad:// in <source srcset>
$in = '<picture><source srcset="bad://pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$exp = '<picture><source srcset="//pear-mobile.jpeg" media="(max-width: 720px)" type="image/png"><source srcset="pear-tablet.jpeg" media="(max-width: 1280px)" type="image/png"><img src="pear-desktop.jpeg" alt="The pear is juicy."></picture>';
$out = wp_kses_post( $in );
$rows[] = array( 'label' => 'bad:// in &lt;source srcset&gt; stripped; valid &lt;source&gt; untouched', 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
// mixed protocols in picture with multiple sources (test_wp_kses_comprehensive_responsive_images)
$in = '<picture><source srcset="javascript:void(0) 480w, https://example.com/mobile.webp 480w" type="image/webp" media="(max-width: 600px)"><source srcset="bad://example.com/tablet.jpg 768w, https://example.com/tablet.jpg 768w" type="image/jpeg" media="(max-width: 1200px)"><img src="https://example.com/desktop.jpg" alt="Picture element test" /></picture>';
$out = wp_kses_post( $in );
$rows[] = array(
'label' => 'Mixed protocols in &lt;picture&gt; — javascript: and bad:// stripped, https:// preserved',
'input' => $in,
'output' => $out,
'pass' => str_contains( $out, 'https://example.com/mobile.webp' )
&& str_contains( $out, 'https://example.com/tablet.jpg' )
&& ! str_contains( $out, 'javascript:' )
&& ! str_contains( $out, 'bad://' ),
);
$groups[] = array( 'title' => 'GROUP 3 — Protocol sanitization in srcset', 'fn' => 'wp_kses_post()', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 3b — wp_kses_one_attr() srcset tests
// test_wp_kses_one_attr_srcset
// ════════════════════════════════════════════════════════════════════
$rows = array();
// valid srcset via wp_kses_one_attr
$in = ' srcset="image1.jpg 1x, image2.jpg 2x"';
$out = wp_kses_one_attr( $in, 'img' );
$rows[] = array( 'label' => 'Valid multi-URI srcset passes through unchanged', 'input' => trim( $in ), 'output' => trim( $out ), 'pass' => trim( $out ) === trim( $in ) );
// javascript: via wp_kses_one_attr
$in = ' srcset="javascript:alert(1) 1x, https://example.com/img.jpg 2x"';
$out = wp_kses_one_attr( $in, 'img' );
$rows[] = array(
'label' => 'javascript: stripped; https:// URL preserved',
'input' => trim( $in ),
'output' => trim( $out ),
'pass' => ! str_contains( $out, 'javascript:' ) && str_contains( $out, 'https://example.com/img.jpg' ),
);
$groups[] = array( 'title' => 'GROUP 3b — wp_kses_one_attr() srcset tests', 'fn' => 'wp_kses_one_attr()', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 4 — CDN URLs with commas in the path
// test_wp_kses_srcset_with_commas_in_urls
// ════════════════════════════════════════════════════════════════════
$rows = array();
foreach ( array(
'Single CDN URL with commas in path' =>
'https://resizer.example/cdn-cgi/image/format=auto,quality=80/https://bucket.example/img.jpg',
'Multiple CDN entries — commas inside path vs srcset separator' =>
'https://resizer.example/cdn-cgi/image/format=auto,onerror=redirect,quality=80,width=412,height=275,dpr=1,fit=crop,gravity=0.5x0.5/https://bucket.example/wp-content/uploads/2025/08/photo.jpg 412w, https://resizer.example/cdn-cgi/image/format=auto,onerror=redirect,quality=80,width=824,height=550,dpr=1,fit=crop,gravity=0.5x0.5/https://bucket.example/wp-content/uploads/2025/08/photo.jpg 824w',
'CDN URLs with commas + pixel density descriptors' =>
'https://resizer.example/cdn-cgi/image/format=auto,quality=80,width=200/https://bucket.example/img.jpg 1x, https://resizer.example/cdn-cgi/image/format=auto,quality=80,width=400/https://bucket.example/img.jpg 2x',
) as $label => $srcset ) {
$in = "<img src='test.png' srcset='{$srcset}' />";
$exp = '<img src="test.png" srcset="' . $srcset . '" />';
$out = wp_kses_post( $in );
$rows[] = array( 'label' => $label, 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
}
$groups[] = array( 'title' => 'GROUP 4 — CDN URLs with commas in path', 'fn' => 'wp_kses_post()', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 5 — Srcset spacing preserved [requires patch]
// test_wp_kses_srcset_preserves_spacing + test_wp_kses_srcset_edge_cases
// ════════════════════════════════════════════════════════════════════
$rows = array();
// spacing cases — require wp_kses_sanitize_uris
foreach ( array(
'No space after comma' => array( 'image1.jpg 1x,image2.jpg 2x', 'image1.jpg 1x,image2.jpg 2x' ),
'Single space after comma' => array( 'image1.jpg 1x, image2.jpg 2x', 'image1.jpg 1x, image2.jpg 2x' ),
'Multiple spaces after comma' => array( 'image1.jpg 1x, image2.jpg 2x', 'image1.jpg 1x, image2.jpg 2x' ),
) as $label => [ $in, $exp ] ) {
if ( $patched ) {
$out = wp_kses_sanitize_uris( 'srcset', $in, $allowed_protocols );
$rows[] = array( 'label' => $label, 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
} else {
$rows[] = array( 'label' => $label, 'skipped' => true );
}
}
// edge cases — require wp_kses_sanitize_uris
foreach ( array(
'Empty srcset' => array( '', '' ),
'Extra whitespace around entries' => array( ' image1.jpg 1x , image2.jpg 2x ', ' image1.jpg 1x, image2.jpg 2x ' ),
'Single URL no descriptor' => array( 'image.jpg', 'image.jpg' ),
'Complex width descriptors' => array( 'small.jpg 480w, medium.jpg 800w, large.jpg 1200w', 'small.jpg 480w, medium.jpg 800w, large.jpg 1200w' ),
) as $label => [ $in, $exp ] ) {
if ( $patched ) {
$out = wp_kses_sanitize_uris( 'srcset', $in, $allowed_protocols );
$rows[] = array( 'label' => $label, 'input' => $in, 'output' => $out, 'pass' => $out === $exp );
} else {
$rows[] = array( 'label' => $label, 'skipped' => true );
}
}
$groups[] = array( 'title' => 'GROUP 5 — Srcset spacing &amp; edge cases', 'fn' => 'wp_kses_sanitize_uris()', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 6 — wp_kses_sanitize_uris() direct tests [requires patch]
// test_wp_kses_sanitize_uris
// ════════════════════════════════════════════════════════════════════
$rows = array();
foreach ( array(
'Non-URI attribute (alt) passes through unchanged' => array( 'alt', 'description', 'description' ),
'Valid single URI (src) passes through' => array( 'src', 'http://example.com/image.jpg', 'http://example.com/image.jpg' ),
'javascript: in single URI (src) stripped' => array( 'src', 'javascript:alert(1)', 'alert(1)' ),
'srcset with multiple valid URIs passes through' => array( 'srcset', 'image1.jpg 1x, image2.jpg 2x', 'image1.jpg 1x, image2.jpg 2x' ),
'javascript: in srcset stripped; valid URL kept' => array( 'srcset', 'javascript:alert(1) 1x, http://example.com/image.jpg 2x', 'alert(1) 1x, http://example.com/image.jpg 2x' ),
) as $label => [ $attr, $in, $exp ] ) {
if ( $patched ) {
$out = wp_kses_sanitize_uris( $attr, $in, $allowed_protocols );
$rows[] = array( 'label' => $label, 'input' => "$attr: \"$in\"", 'output' => $out, 'pass' => $out === $exp );
} else {
$rows[] = array( 'label' => $label, 'skipped' => true );
}
}
$groups[] = array( 'title' => 'GROUP 6 — wp_kses_sanitize_uris() direct tests', 'fn' => 'wp_kses_sanitize_uris()', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// GROUP 7 — srcset as a URI attribute (root fix) [requires patch]
// test_wp_kses_uri_attributes_includes_srcset + test_wp_kses_uri_attributes_filter
// ════════════════════════════════════════════════════════════════════
$rows = array();
$uri_attrs = wp_kses_uri_attributes();
$in_list = in_array( 'srcset', $uri_attrs, true );
$rows[] = array(
'label' => 'srcset registered in wp_kses_uri_attributes()',
'input' => 'wp_kses_uri_attributes()',
'output' => $in_list ? 'srcset found in URI attributes list' : 'srcset NOT in URI attributes list',
'pass' => $in_list,
);
if ( $patched ) {
$remove_srcset = static fn( $attrs ) => array_diff( $attrs, array( 'srcset' ) );
add_filter( 'wp_kses_uri_attributes', $remove_srcset );
$without = wp_kses_sanitize_uris( 'srcset', 'javascript:alert(1) 1x', $allowed_protocols );
remove_filter( 'wp_kses_uri_attributes', $remove_srcset );
$with = wp_kses_sanitize_uris( 'srcset', 'javascript:alert(1) 1x', $allowed_protocols );
$rows[] = array(
'label' => 'wp_kses_uri_attributes filter: removing srcset disables per-URL sanitization',
'input' => 'javascript:alert(1) 1x',
'output' => 'filter removed → "' . $without . '" / filter present → "' . $with . '"',
'pass' => str_contains( $without, 'javascript:' ) && ! str_contains( $with, 'javascript:' ),
);
} else {
$rows[] = array( 'label' => 'wp_kses_uri_attributes filter controls srcset sanitization', 'skipped' => true );
}
$groups[] = array( 'title' => 'GROUP 7 — srcset as a URI attribute (root fix)', 'fn' => 'wp_kses_uri_attributes() / wp_kses_sanitize_uris()', 'rows' => $rows );
// ════════════════════════════════════════════════════════════════════
// RENDER
// ════════════════════════════════════════════════════════════════════
$all_rows = array_merge( ...array_column( $groups, 'rows' ) );
$countable = array_filter( $all_rows, fn( $r ) => empty( $r['skipped'] ) );
$total = count( $countable );
$passed = count( array_filter( $countable, fn( $r ) => $r['pass'] ) );
$skipped = count( $all_rows ) - $total;
$all_pass = $passed === $total;
?>
<div class="wrap" style="max-width: 1800px;">
<h1>
Tests for #29807 — add support for picture element and srcset attribute on img in wp_kses
</h1>
<p style="font-size: 14px; color: #444;">
<strong><?php echo $passed; ?>/<?php echo $total; ?></strong> passing
&nbsp;·&nbsp; WordPress <strong><?php echo get_bloginfo( 'version' ); ?></strong>
&nbsp;·&nbsp; Patch applied: <strong><?php echo $patched ? '✅ Yes' : '❌ No'; ?></strong>
<?php if ( $skipped ) : ?>
&nbsp;·&nbsp; <span style="color: #996800;"><strong><?php echo $skipped; ?> skipped</strong> — wp_kses_sanitize_uris() not found, apply patch to run all tests</span>
<?php endif; ?>
</p>
<?php foreach ( $groups as $group ) :
$gcountable = array_filter( $group['rows'], fn( $r ) => empty( $r['skipped'] ) );
$gpass = count( array_filter( $gcountable, fn( $r ) => $r['pass'] ) );
$gtotal = count( $gcountable );
$gskipped = count( $group['rows'] ) - $gtotal;
$gall_pass = $gtotal > 0 && $gpass === $gtotal;
?>
<h2 style="font-size: 14px; margin: 24px 0 6px; border-bottom: 1px solid #ddd; padding-bottom: 6px;">
<?php echo $gall_pass ? '✅' : ( $gtotal === 0 ? '⏭️' : '❌' ); ?>
<?php echo $group['title']; ?>
<span style="font-weight: normal; color: #888; font-size: 13px;">
(<?php echo $gpass; ?>/<?php echo $gtotal; ?>
<?php if ( $gskipped ) echo ' · ' . $gskipped . ' skipped'; ?>)
</span>
<?php if ( ! empty( $group['fn'] ) ) : ?>
<code style="font-size: 12px; background: #f0f0f1; padding: 2px 6px; border-radius: 3px; margin-left: 6px; font-weight: normal; color: #50575e;">
<?php echo esc_html( $group['fn'] ); ?>
</code>
<?php endif; ?>
</h2>
<table style="border-collapse: collapse; width: 100%; font-size: 12px; font-family: monospace; margin-bottom: 8px;">
<thead>
<tr style="background: #f6f7f7; text-align: left;">
<th style="padding: 7px 10px; border: 1px solid #ddd; font-family: sans-serif; width: 22%;">Test</th>
<th style="padding: 7px 10px; border: 1px solid #ddd; font-family: sans-serif; width: 39%;">Input</th>
<th style="padding: 7px 10px; border: 1px solid #ddd; font-family: sans-serif; width: 39%;">Output</th>
</tr>
</thead>
<tbody>
<?php foreach ( $group['rows'] as $row ) : ?>
<?php if ( ! empty( $row['skipped'] ) ) : ?>
<tr style="background: #fffbf0;">
<td colspan="3" style="padding: 6px 10px; border: 1px solid #ddd; color: #996800; font-family: sans-serif;">
⏭️ <?php echo $row['label']; ?> <em>(skipped — requires patch)</em>
</td>
</tr>
<?php else :
$bg = $row['pass'] ? '#f0faf0' : '#fef0f0';
$color = $row['pass'] ? '#00a32a' : '#d63638';
$icon = $row['pass'] ? '✅' : '❌';
?>
<tr>
<td style="padding: 6px 10px; border: 1px solid #ddd; font-family: sans-serif; font-size: 12px; vertical-align: top;">
<?php echo $row['label']; ?>
</td>
<td style="padding: 6px 10px; border: 1px solid #ddd; word-break: break-all; vertical-align: top; color: #444;">
<?php echo esc_html( $row['input'] ); ?>
</td>
<td style="padding: 6px 10px; border: 1px solid #ddd; word-break: break-all; vertical-align: top; background: <?php echo $bg; ?>;">
<span style="font-family: sans-serif; font-weight: 700; color: <?php echo $color; ?>; display: block; margin-bottom: 3px;">
<?php echo $icon; ?> <?php echo $row['pass'] ? 'Pass' : 'Fail'; ?>
</span>
<span style="color: <?php echo $color; ?>;">
<?php echo esc_html( $row['output'] ); ?>
</span>
</td>
</tr>
<?php endif; ?>
<?php endforeach; ?>
</tbody>
</table>
<?php endforeach; ?>
</div>
<?php
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment