Last active
March 14, 2026 10:18
-
-
Save ozgursar/62ee9d1a9d5491b30c1d277489b46644 to your computer and use it in GitHub Desktop.
WordPress Core Trac 29807 Tests Plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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 <img>' => | |
| '<img src="photo.jpg" srcset="/photo-1x.jpg 1x, /photo-2x.jpg 2x" sizes="(max-width: 600px) 100vw, 50vw" />', | |
| 'decoding="async" on <img>' => | |
| '<img src="test.jpg" decoding="async" />', | |
| 'fetchpriority="high" on <img>' => | |
| '<img src="test.jpg" fetchpriority="high" />', | |
| 'Full real-world <img> — 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 — <img> 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 <picture> with multiple <source> 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' => '<picture> 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' => '<source> 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 <source>', '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 <source>', '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 <source>', '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 <source> inside <picture> preserved', 'input' => $in, 'output' => $out, 'pass' => $out === $in ); | |
| // empty picture | |
| $in = '<picture></picture>'; | |
| $out = wp_kses( $in, $allowedposttags ); | |
| $rows[] = array( 'label' => 'Empty <picture> 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' => '<picture> without fallback <img> 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' => '<picture> with only <img>, no <source>', '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 <picture> inside <picture> — structure preserved', | |
| 'input' => $in, | |
| 'output' => $out, | |
| 'pass' => str_contains( $out, '<picture>' ) && str_contains( $out, '<source' ), | |
| ); | |
| $groups[] = array( 'title' => 'GROUP 2 — <picture> and <source> 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 <source srcset> stripped; valid <source> 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 <picture> — 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 & 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 | |
| · WordPress <strong><?php echo get_bloginfo( 'version' ); ?></strong> | |
| · Patch applied: <strong><?php echo $patched ? '✅ Yes' : '❌ No'; ?></strong> | |
| <?php if ( $skipped ) : ?> | |
| · <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