Bug
CustomClassInjection::parse() produces incorrect token offsets when the input contains more than one {:classname:...:} block. This causes RenderTokens to misalign <span> tags in the rendered output.
Version: 2.17.2
Root cause
CustomClassInjection.php line 40:
$content = str_replace([$startToken, $endToken], '', $content);
$endToken is always :} for every custom-class block. str_replace removes all occurrences at once, so the first iteration strips every :} in the entire string — not just the current match's.
However, $additionalOffset (lines 30, 38) only accounts for one :} per iteration. This means every subsequent token gets an incorrect offset, drifting further with each additional block.
Reproduction
Input:
Hello {:bold:world:} and {:italic:foo:} bar
Trace
preg_match_all captures from the original string:
- Match 0:
world at offset 13, start={:bold: (7 chars), end=:} (2 chars)
- Match 1:
foo at offset 33, start={:italic: (9 chars), end=:} (2 chars)
Iteration 0 ({:bold:world:}):
$additionalOffset += 7 (start token) → 7
- Token: offset = 13 − 7 = 6 ✓
$additionalOffset += 2 (end token) → 9
str_replace(["{:bold:", ":}"], '', ...) removes {:bold: (1×) and both :} (2×) = 11 chars removed, but only 9 tracked
Iteration 1 ({:italic:foo:}):
$additionalOffset += 9 → 18
- Token: offset = 33 − 18 = 15
Actual position of foo in the final content Hello world and foo bar:
H(0)e(1)l(2)l(3)o(4) (5)w(6)o(7)r(8)l(9)d(10) (11)a(12)n(13)d(14) (15)f(16)o(17)o(18)
foo is at position 16, not 15. Off by 1.
Impact
RenderTokens (line 38–41) uses the offset to substr the content and wrap tokens in <span> tags. With the wrong offset, characters adjacent to the highlighted token get swallowed or shifted:
Expected: Hello world and <span class="italic">foo</span> bar
Actual: Hello world an<span class="italic">d foo</span> bar
The error compounds — with N blocks, the Nth token is off by 2 × (N−1) characters (two for each extra :} removed prematurely).
Suggested fix
Replace the str_replace on line 40 with positional removal (e.g. substr_replace at the known match offsets, adjusted for prior removals), so only the current match's markers are stripped — not all occurrences of :} in the string.
Bug
CustomClassInjection::parse()produces incorrect token offsets when the input contains more than one{:classname:...:}block. This causesRenderTokensto misalign<span>tags in the rendered output.Version: 2.17.2
Root cause
CustomClassInjection.phpline 40:$endTokenis always:}for every custom-class block.str_replaceremoves all occurrences at once, so the first iteration strips every:}in the entire string — not just the current match's.However,
$additionalOffset(lines 30, 38) only accounts for one:}per iteration. This means every subsequent token gets an incorrect offset, drifting further with each additional block.Reproduction
Input:
Trace
preg_match_allcaptures from the original string:worldat offset 13, start={:bold:(7 chars), end=:}(2 chars)fooat offset 33, start={:italic:(9 chars), end=:}(2 chars)Iteration 0 (
{:bold:world:}):$additionalOffset += 7(start token) → 7$additionalOffset += 2(end token) → 9str_replace(["{:bold:", ":}"], '', ...)removes{:bold:(1×) and both:}(2×) = 11 chars removed, but only 9 trackedIteration 1 (
{:italic:foo:}):$additionalOffset += 9→ 18Actual position of
fooin the final contentHello world and foo bar:foois at position 16, not 15. Off by 1.Impact
RenderTokens(line 38–41) uses the offset tosubstrthe content and wrap tokens in<span>tags. With the wrong offset, characters adjacent to the highlighted token get swallowed or shifted:The error compounds — with N blocks, the Nth token is off by
2 × (N−1)characters (two for each extra:}removed prematurely).Suggested fix
Replace the
str_replaceon line 40 with positional removal (e.g.substr_replaceat the known match offsets, adjusted for prior removals), so only the current match's markers are stripped — not all occurrences of:}in the string.