. "Safely" * carries a lot of weight — a naive str_replace over 300 hand-written posts * would corrupt shortcode attributes, nest anchors, rewrite alt text and break * Gutenberg block delimiters, and the user would not discover it for weeks. * * Two lessons are inherited from the in-body image work in the main plugin: * * 1. Never assume
exists. Classic-editor content stores paragraphs as * bare text between block elements and lets wpautop() create the
at * render time — a real post can contain zero
in post_content. This is
* the same trap documented at aicobr_prose_anchor_offset(); a linker that
* anchors on paragraphs silently no-ops on exactly the older hand-written
* posts this feature exists to serve.
*
* 2. Apply edits from the HIGHEST offset backwards, so earlier offsets stay
* valid as the string grows. Same discipline as aicobr_insert_figure().
*
* Offsets are BYTE offsets, consistent with preg_* PREG_OFFSET_CAPTURE and with
* aicobr_find_headings() in the main plugin. Multibyte safety comes from every
* pattern carrying the /u flag, so a match can only ever begin and end on a
* character boundary — never from mb_* string juggling, which would disagree
* with the rest of the codebase.
*/
if (!class_exists('AICOBR_Link_Scanner')) {
class AICOBR_Link_Scanner {
/**
* Elements whose ENTIRE contents are off limits, not merely their tags.
*
* a — anchors cannot nest, and re-linking existing link text is
* both invalid HTML and an over-optimisation signal.
* h1..h6 — linking a heading reads as spam and breaks the visual
* hierarchy the formatter builds.
* figure — covers