Skip to content

fix(theme): split includes/excludes on every separator, not just newlines - #227

Open
ZaredRogers wants to merge 1 commit into
developfrom
fix/included-excluded-list-rendering
Open

fix(theme): split includes/excludes on every separator, not just newlines#227
ZaredRogers wants to merge 1 commit into
developfrom
fix/included-excluded-list-rendering

Conversation

@ZaredRogers

Copy link
Copy Markdown
Contributor

Problem

The Price Includes / Price Excludes lists on single tour pages collapse into a single bullet with a single icon, instead of one checked/crossed item per line.

Reported on 21 Day Cape Town to Victoria Falls. It reappears every time a tour is imported from WETU, or the field is edited and saved in the editor.

Live markup — one <li> holding everything:

<ul class="lsx-included-list"><li><svg />Accommodation as per itinerary<br>Meals as per
itinerary<br>Transport<br>Professional guides</li></ul>

Root cause

included and not_included are CMB2 wysiwyg fields (config-tour.php) that the WETU importer also writes to verbatim, so a value arrives in one of four shapes. The theme filter split on newline characters only:

$processed_value = preg_replace('/<p[^>]*>|<\/p>/i', '', $value);  // strips <p>, leaves <br>
$lines = preg_split('/\r\n|\r|\n/', $processed_value);             // newline CHARACTERS only

Neither writer produces newlines — TinyMCE and WETU both emit <br> / <p> markup. So preg_split returned a single element.

Stored shape Rows Before
<p>One<br>Two</p> — TinyMCE soft breaks, every WETU payload 62 ❌ one <li>, all items lumped
<p>One</p><p>Two</p> — TinyMCE hard breaks ~8 ❌ one <li>, words run together (DinnerNational Park FeeLunch)
"One\r\nTwo" — legacy plain text 4 ✅ the only shape that worked
<ul><li>One</li></ul> — real list markup 6 ⚠️ passed through without the class, so never got icons

Only 4 of 76 stored values were in the shape the filter understood. The 4 that worked are legacy plain-text values nobody has opened in the editor since — which is why editing a tour "breaks" it.

Fix

All in the render path. No stored data is modified.

  • asnz_split_list_meta_value() — normalises <br>, <p>/</p> and \r\n/\r to one delimiter before splitting. Also collapses &nbsp;, so an emptied wysiwyg field renders nothing instead of a stray non-breaking space.
  • asnz_add_list_meta_class() — values already stored as list markup keep their own HTML but get the lsx-{key}-list class added via WP_HTML_Tag_Processor, so those 6 rows finally get their icons.
  • Icon injection — now matches <li> with attributes, handles <ol>, and handles nested lists. The two near-identical branches collapse into one loop over a class→icon map.
  • CSS — drops the ul qualifier (ul.lsx-included-list.lsx-included-list) so an editor-entered <ol> keeps the layout.

Verification

Bootstrapped the site locally (SQLite, real plugins, real theme) and rendered the actual bound blocks from templates/single-tour.html for every tour:

Check Before After
Rows rendering as a real multi-item list 4 / 76 76 / 76
Total <li> emitted across all tours ~76 563
Rows missing the list class (→ no icons) 6 0
Icons injected vs. list items 563 / 563, 0 mismatches
Correct icon per list type (tick vs. cross) 76 / 76

Plus a 12-case edge matrix through the same render path, all passing: XHTML <br />, hard-break paragraphs, trailing empty paragraph, inline <strong>/<em> preserved, <li class="…">, <ol>, nested lists, single item, empty value, array value.

Confirmed no stored data changed: still 76 non-empty rows, 62 with <br>, 6 with <ul>.

Scope

Only the theme's templates/single-tour.html binds these keys, so the change is confined to the Price Includes/Excludes section. The per-day itinerary includes use lsx_to_itinerary_includes(), which reads the serialised itinerary sub-field and never passes through lsx_to_custom_field_query — untouched.

Known issues left alone

  • Output is still <p><ul>…</ul></p> — the binding target is a core/paragraph. Browsers auto-close the <p> so it renders correctly, but the markup is invalid. Fixing it means changing the template binding, and unwrapping the <p> would shift the section's vertical spacing.
  • Separate pre-existing bug in tour-operator, not this repo: class-bindings.php calls wp_strip_all_tags( $value, [ 'a', 'ul', 'li', … ] ). That second parameter is $remove_breaks (a boolean), not an allow-list — the truthy array strips all tags and breaks from itinerary includes/excludes.

🤖 Generated with Claude Code

…ines

The Price Includes/Excludes lists collapsed into a single bullet whenever a
tour was imported from WETU or the field was touched in the editor.

`included` and `not_included` are CMB2 wysiwyg fields that the WETU importer
also writes to verbatim, so a value arrives in one of four shapes. The filter
split on newline characters only, but neither writer produces newlines:

- `<p>One<br>Two</p>`        TinyMCE soft breaks, and every WETU payload
- `<p>One</p><p>Two</p>`     TinyMCE hard breaks
- `"One\r\nTwo"`             legacy plain text, the only shape that worked
- `<ul><li>One</li></ul>`    real list markup, passed through without the
                             class, so it never received its icons

Only 4 of 76 stored values were in the shape the filter understood.

Normalise `<br>`, `<p>`/`</p>` and `\r\n`/`\r` to one delimiter before
splitting, and add the list class to values already stored as list markup so
those get icons too. Also collapse `&nbsp;` so an emptied wysiwyg field
renders nothing instead of a stray non-breaking space.

Icon injection now matches `<li>` with attributes, handles `<ol>` and nested
lists, and the two near-identical branches collapse into one loop. The CSS
drops its `ul` qualifier so an editor-entered `<ol>` keeps the layout.

Verified against every stored value on a local copy of the site: 76/76 rows
render as multi-item lists (563 items, up from ~76), 563/563 icons injected,
correct icon on all 76 lists, plus a 12-case edge matrix. No stored data
changed; the fix is entirely in the render path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • List-based content now handles line breaks, paragraphs, and empty entries more consistently.
    • Existing list formatting is preserved while the appropriate list styling and icons are applied automatically.
    • Matching SVG icons are now displayed for each item in supported lists.
  • Bug Fixes

    • Improved consistency when displaying included and excluded lists across different HTML structures.
    • List styling and icon presentation now work reliably beyond standard unordered-list elements.

Walkthrough

List metadata processing now supports multiple separators, removes empty values, preserves existing list markup, and adds list classes. Icon rendering uses those classes to inject SVGs into list items. CSS now targets the classes on any element type.

Changes

List rendering

Layer / File(s) Summary
Metadata normalisation and list classification
functions.php
The metadata filter validates values, normalises separators and line endings, removes empty items, preserves existing lists, and applies the appropriate list class.
Icon injection and class-based styling
functions.php, style.css
The renderer maps list classes to SVGs and prepends the matching icon to each list item. CSS applies list layout and icon styles to any element with the relevant class.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MetadataFilter
  participant ListHelpers
  participant WP_HTML_Tag_Processor
  participant IconRenderer
  participant CSS
  MetadataFilter->>ListHelpers: normalise and split metadata
  ListHelpers->>WP_HTML_Tag_Processor: add list class
  MetadataFilter->>IconRenderer: provide classified list markup
  IconRenderer->>IconRenderer: prepend matching SVG to each list item
  CSS->>IconRenderer: apply class-based list styles
Loading

Possibly related PRs

Suggested reviewers: eleshar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main fix: splitting include and exclude values on all supported separators.
Description check ✅ Passed The description directly explains the rendering problem, root cause, implementation, scope, and verification for the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/included-excluded-list-rendering

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Stylelint (17.14.0)
style.css

ConfigurationError: Could not find "@wordpress/stylelint-config". Do you need to install the package or use the "configBasedir" option?
at getModulePath (file:///usr/local/lib/node_modules/stylelint/lib/utils/getModulePath.mjs:38:9)
at loadExtendedConfig (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:285:21)
at extendConfig (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:252:25)
at augmentConfigBasic (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:85:26)
at augmentConfigFull (file:///usr/local/lib/node_modules/stylelint/lib/augmentConfig.mjs:138:30)
at getConfigForFile (file:///usr/local/lib/node_modules/stylelint/lib/getConfigForFile.mjs:102:32)
at async resolveOptionValue (file:///usr/local/lib/node_modules/stylelint/lib/utils/resolveOptionValue.mjs:27:24)
at async standalone (file:///usr/local/lib/node_modules/stylelint/lib/standalone.mjs:127:22)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai
coderabbitai Bot requested a review from eleshar August 4, 2026 16:47
@mergify

mergify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@functions.php`:
- Around line 349-352: Update the `<li>` opening-tag injection in the
bound-paragraph handling around `$block_content` so `>` characters inside quoted
attribute values do not terminate the match; use an HTML-aware insertion
approach or a pattern that consumes quoted attributes while preserving the full
tag. Add a regression test covering an attribute such as `data-label="A > B"`
and verify the icon is inserted only after the complete opening tag.
- Around line 238-240: Resolve the WordPress compatibility mismatch around the
WP_HTML_Tag_Processor guard in asnz_add_list_meta_class(): either raise the
theme’s minimum required WordPress version to 6.2, or implement a safe fallback
parser for supported 5.8 installs that still adds
lsx-included-list/lsx-not_included-list and preserves icon styling. Keep the
existing processor path unchanged where available.

In `@style.css`:
- Around line 1301-1307: Update the .lsx-included-list li and
.lsx-not_included-list li layout so direct nested ul or ol elements wrap beneath
the parent item text rather than appearing beside it; preserve the existing icon
alignment and add indentation for nested content.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4eca0c0e-f993-4e31-b4a2-7888e6b88ac5

📥 Commits

Reviewing files that changed from the base of the PR and between a1593ec and 61445a6.

📒 Files selected for processing (2)
  • functions.php
  • style.css

Comment thread functions.php
Comment on lines +238 to +240
if (!class_exists('\WP_HTML_Tag_Processor')) {
return $html;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -i -C 3 \
  'Requires at least:|Tested up to:|WP_HTML_Tag_Processor|wp_version' \
  -g '*.php' -g 'readme.txt' -g '*.md' -g 'composer.json' .

Repository: lightspeedwp/asnz-block-theme

Length of output: 1435


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== theme header =="
sed -n '1,24p' readme.txt

echo "== functions.php relevant sections =="
if [ -f functions.php ]; then
  wc -l functions.php
  sed -n '220,285p' functions.php
  echo "== references to asnz_add_list_meta_class / list renderer / css =="
  rg -n "asnz_add_list_meta_class|list_meta_class|WP_HTML_Tag_Processor|<style|wp_enqueue_style|customize_reader_visible|render" functions.php readme.txt -g '*.php' -g '*.txt'
fi

echo "== composer/package files =="
git ls-files | rg '(^composer\.(json|lock)$|package\.json$|readme\.txt$|README\.md$|LICENSE|style\.css$)' || true

Repository: lightspeedwp/asnz-block-theme

Length of output: 5786


🌐 Web query:

WordPress WP_HTML_Tag_Processor introduced version

💡 Result:

The WP_HTML_Tag_Processor class was introduced in WordPress 6.2 [1][2]. It is part of the HTML API, designed to provide a safe, reliable, and HTML5-spec-compliant way to parse HTML and modify tag attributes in PHP [3][4]. The class was developed initially within the Gutenberg repository before being merged into WordPress Core [4][5].

Citations:


Require WordPress 6.2+ or add a list-class injection fallback.

WP_HTML_Tag_Processor is available from WordPress 6.2, but the theme metadata still allows 5.8. On supported 5.8 installs, existing list markup bypasses asnz_add_list_meta_class(), so lsx-included-list / lsx-not_included-list and the icon styles are not applied. Update Requires at least: 6.2 or add an alternative parser that injects the class while remaining safe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions.php` around lines 238 - 240, Resolve the WordPress compatibility
mismatch around the WP_HTML_Tag_Processor guard in asnz_add_list_meta_class():
either raise the theme’s minimum required WordPress version to 6.2, or implement
a safe fallback parser for supported 5.8 installs that still adds
lsx-included-list/lsx-not_included-list and preserves icon styling. Keep the
existing processor path unchanged where available.

Comment thread functions.php
Comment on lines +349 to 352
// A bound paragraph holds exactly one meta value, so every list item in
// the block belongs to this list, including any the editor nested.
$block_content = preg_replace('/(<li\b[^>]*>)/i', '$1' . $icon, $block_content);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle > inside quoted <li> attributes.

A small parser gremlin is here. The matcher treats the first > as the end of the opening tag. Valid markup such as <li data-label="A > B"> becomes malformed when the SVG is inserted. Use an HTML-aware insertion method, or a matcher that recognises quoted attribute values. Add this case to the icon-injection tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@functions.php` around lines 349 - 352, Update the `<li>` opening-tag
injection in the bound-paragraph handling around `$block_content` so `>`
characters inside quoted attribute values do not terminate the match; use an
HTML-aware insertion approach or a pattern that consumes quoted attributes while
preserving the full tag. Add a regression test covering an attribute such as
`data-label="A > B"` and verify the icon is inserted only after the complete
opening tag.

Comment thread style.css
@ZaredRogers
ZaredRogers requested a lite review from Copilot and removed request for eleshar August 4, 2026 17:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the “Price Includes / Price Excludes” rendering on single tour pages by making the theme’s render-time processing correctly split WYSIWYG/imported values on HTML separators (e.g. <br>, <p>) rather than only on newline characters, and by ensuring already-stored list markup receives the expected list class so icons apply.

Changes:

  • Add helpers to normalise/split included / not_included meta values into distinct list items regardless of whether they arrive via TinyMCE/WETU HTML or legacy plain text.
  • Ensure pre-formatted <ul>/<ol> values get the lsx-{key}-list class added via WP_HTML_Tag_Processor, so icon styling/injection applies consistently.
  • Simplify icon injection to handle <li> with attributes, <ol>, and nested lists; update CSS selectors to support ordered lists as well.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
functions.php Normalises/splits includes/excludes meta into proper list output, adds list class to existing list markup, and injects the correct SVG icon per list type at render time.
style.css Makes list styling apply to both <ul> and <ol> by removing the ul qualifier from includes/excludes list selectors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

2 participants