📝 Checkpoint_CollabCore-MsgTracker_2026-09-21.mdv4.4 · 2026-09-05

Checkpoint — CollabCore MsgTracker / Autodisplay Failure — 21/09/2026


1. Problem & Resolution

Problem: [pdf=autodisplay], [lmv=autodisplay], and [ltv=autodisplay] stopped auto-embedding PDF/MD/TXT attachments on Live (2.0.19) — worked on Test, dead on Live, zero errors anywhere: no PHP error, nothing in Dev Tools, no tag left in page source.

Root cause (two layers):

  1. Immediate: CollabCore/Collabware's Package Manager registration was broken (files present on disk, install state inconsistent — evidence: "Undefined index: name" notices in Package Manager's browse screen).
  2. Real cause, found after adding visible failure output: Display.php's one parse_bbc() call site was in a previously-undocumented half-edited state — a correctly-formed CollabCore-loading guard was present, but the call line immediately after it had never been updated from stock parse_bbc() to collabcore_parse_bbc(). Net effect: CollabMsgTracker::$currentMsgId was never set, so every mod's currentMsgId() resolved to 0, silently, every time. Most likely cause: an earlier hand-edit that added the guard without finishing the call-line rename.

Resolution path:

Verification: both scripts' logic extracted into an includable test harness and run against 13 fixture scenarios (every documented state, both migrate and unmigrate, including the exact live state-6 bug) — all 13 passed, zero errors logged, unmigrate output byte-identical to genuine untouched stock in every case. php -l clean on all five changed files.

Not yet done: the actual reinstall on Live/Test/Dev — verification above is against simulated fixtures, not yet against real Display.php. Closes when Andrew runs the Package Manager reinstall and confirms the ISE post's PDF/MD/LTV autodisplay renders without a RedBox.


2. Risk Analysis

Bug class: logic/contract bug (a source-text patch drifted from its own contract via a prior hand-edit), not a portability/environment bug — Live and Test/Dev run the same 2.0.19 branch, so this isn't expected to behave differently across Andrew's environments. Confirming that is exactly what the Test/Dev-first reinstall verifies.

Residual risk, mitigated tonight: uninstalling Collaborative from its current (correct or state-6) form, before this fix, would have orphaned the CollabCore guard in Display.php and fataled every page site-wide the moment MsgTracker.php was removed. Fixed in msgtracker_display_unmigrate.php v1.1 and verified empirically — no longer a live risk once these files are installed.

Residual risk, open: the underlying mechanism is a text patch to a core file, not a real SMF hook — 2.0.x's parse_bbc() has no integrate_pre_parsebbc call to hook, confirmed, so there's no cleaner option available on this branch. That means any future hand-edit near this call site in Display.php can reintroduce a new, currently-unknown half-edited state that the script's 6-state detection doesn't recognize. The new explanatory comment directly above the guard is the mitigation — marks the block as automated and off-limits to hand-editing, for exactly this reason.

Blast radius if the reinstall goes wrong: confined to Display.php's one call site — every other file in the package (CollabCore.php, MDParser.php, MsgTracker.php, Collabware dir) is a straight file copy via <require-file>/<require-dir>, no patching logic, effectively zero risk.

Scope check: MDBBC and ListAttBBC deliberately NOT given the same RedBox treatment — neither makes an automatic, invisible decision keyed on message ID the way the three autodisplay-toggle mods do, so there's no equivalent silent-failure mode to guard against in them.


3. Code Change (with diff)

Five files changed, all lint-clean (php -l), diffed against the originally uploaded zips.

PDFBBC.php — v2.27 → v2.28

--- originals/8e91c3be-pdf_bbcode_v2.27/PDFBBC.php	2026-09-13 18:32:06.000000000 +1000
+++ /mnt/user-data/outputs/PDFBBC.php 2026-09-21 09:33:07.972294000 +1000
@@ -10,6 +10,16 @@
* Subs.php is never edited, by a human or by any installer code, on
* either branch.
*
+ * AE 21/09/2026 -- v2.28: [pdf=autodisplay]/[pdf=noautodisplay] now
+ * fail LOUDLY instead of silently when
+ * CollabMsgTracker::current() returns 0 (no message
+ * ID to record the toggle against) - renders a
+ * RedBox (RedBoxBBCode, if installed) exactly where
+ * the tag was typed, so a broken CollabCore Display.php
+ * migration is visible on the post itself instead of
+ * the tag just vanishing with zero output and zero
+ * trail. Deliberately not also logged via log_error()
+ * - the RedBox itself is the notification.
* AE 31/08/2026 -- v2.25/v3.1: no logic change in this file. Autodisplay
* now renders a post's .pdf attachments newest-
* first (by attachment ID, descending) instead of
@@ -44,7 +54,7 @@
class PDFBBCode
{
const TAG = 'pdf';
- const VERSION = '2.27 and 3.3';
+ const VERSION = '2.28 and 3.3';

/**
* Per-message autodisplay toggle state, keyed by message ID.
@@ -151,6 +161,32 @@
}

/**
+ * Convenience wrapper for surfacing a visible, in-post failure.
+ * Lazy-loads RedBoxBBC.php if it's installed (this file has no
+ * hard dependency on it - RedBox is a fully independent, optional
+ * mod), falls back to a plain inline warning string if it isn't.
+ * Added v2.28 alongside the [pdf=autodisplay] toggle-failure fix
+ * below, but written as a general-purpose helper any future code
+ * in this class can reach for the same way - not a one-off.
+ *
+ * @param string $message Plain text - escaped either by
+ * RedBoxBBCode::render() itself, or by
+ * htmlspecialchars() here in the fallback.
+ * @return string HTML - safe to assign directly into $data[0].
+ */
+ private static function redbox($message)
+ {
+ global $sourcedir;
+
+ if (!class_exists('RedBoxBBCode') && file_exists($sourcedir . '/RedBoxBBC.php'))
+ require_once($sourcedir . '/RedBoxBBC.php');
+
+ return class_exists('RedBoxBBCode')
+ ? RedBoxBBCode::render($message)
+ : '<b style="color:#b91c1c;">' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</b>';
+ }
+
+ /**
* Renders every approved .pdf attachment on the given message as
* a PDF.js iframe, newest-first (by attachment ID, descending) -
* or an empty string if autodisplay is off for this message, or
@@ -449,8 +485,20 @@
{
$msgId = self::currentMsgId();
if (!empty($msgId))
+ {
self::$autoDisplay[$msgId] = ($rawValue === 'autodisplay');
- $data[0] = '';
+ $data[0] = '';
+ return;
+ }
+
+ // v2.28: msgId capture failed - CollabMsgTracker::current()
+ // returned 0, so this toggle CANNOT take effect (nothing to
+ // key self::$autoDisplay[] on). Previously silent: the tag
+ // just vanished with no output and no error anywhere, which
+ // is exactly the "reads as working, isn't" failure this
+ // whole check exists to kill. Deliberately not also logged
+ // via log_error() - the RedBox itself is the notification.
+ $data[0] = self::redbox('[pdf=' . $rawValue . '] failed: no message ID available (CollabCore\'s MsgTracker isn\'t wired up) - autodisplay will not work here.');
return;
}

LMVBBC.php — v4.3 → v4.4

--- originals/ca9086f0-lmv_bbcode_v4.3/LMVBBC.php	2026-09-13 17:51:12.000000000 +1000
+++ /mnt/user-data/outputs/LMVBBC.php 2026-09-21 09:33:07.965779000 +1000
@@ -3,14 +3,22 @@
* ========================================================
* Module: LMVBBC.php
*
- * @version 4.2
- * @date 2026-09-05
+ * @version 4.4
+ * @date 2026-09-21
* @target SMF 2.0.x and SMF 2.1.x
* @requires CollabCore (MDParser >= 12.8)
*
* Another collaboration between Andrew.human and Claude.ai
* ========================================================
*
+ * AE 21/09/2026 -- v4.4: [lmv=autodisplay] and friends now fail LOUDLY
+ * instead of silently when CollabMsgTracker::current()
+ * returns 0 - renders a RedBox (RedBoxBBCode, if
+ * installed) exactly where the tag was typed, via a
+ * new general-purpose redbox() helper. Same fix and
+ * rationale as PDFBBCode v2.28's identical toggle
+ * branch. Deliberately not also logged via
+ * log_error() - the RedBox itself is the notification.
* AE 05/09/2026 -- v4.2: added a real second scroll-box size tier -
* [lmv=scroll-lg] and friends (1400px desktop / 95vh
* mobile) alongside the existing plain [lmv=scroll]
@@ -273,7 +281,7 @@
class LMVBBCode
{
const TAG = 'lmv';
- const VERSION = '4.3';
+ const VERSION = '4.4';
const BUILD_DATE = '2026-09-05';

/** Maximum bytes read from a remote URL or local attachment file,
@@ -365,6 +373,29 @@
}

/**
+ * Convenience wrapper for surfacing a visible, in-post failure -
+ * see PDFBBCode::redbox() for the full rationale (same helper,
+ * duplicated per-class since each mod is independently installable
+ * with no dependency on the others). Lazy-loads RedBoxBBC.php if
+ * it's installed, falls back to a plain inline warning string if
+ * it isn't.
+ *
+ * @param string $message
+ * @return string HTML - safe to assign directly into $data[0].
+ */
+ private static function redbox($message)
+ {
+ global $sourcedir;
+
+ if (!class_exists('RedBoxBBCode') && file_exists($sourcedir . '/RedBoxBBC.php'))
+ require_once($sourcedir . '/RedBoxBBC.php');
+
+ return class_exists('RedBoxBBCode')
+ ? RedBoxBBCode::render($message)
+ : '<b style="color:#b91c1c;">' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</b>';
+ }
+
+ /**
* Returns whether autodisplay for the given message should render
* with the scroll class applied. Only meaningful when
* shouldAutoDisplay() is also true; callers should check both.
@@ -531,8 +562,16 @@
{
self::$autoDisplay[$msgId] = ($rawValue !== 'noautodisplay');
self::$autoDisplayScroll[$msgId] = ($rawValue === 'autodisplay-scroll');
+ $data[0] = '';
+ return;
}
- $data[0] = '';
+
+ // v4.4: msgId capture failed - CollabMsgTracker::current()
+ // returned 0, so this toggle CANNOT take effect. Previously
+ // silent (tag just vanished, no error anywhere). Deliberately
+ // not also logged via log_error() - the RedBox itself is
+ // the notification.
+ $data[0] = self::redbox('[lmv=' . $rawValue . '] failed: no message ID available (CollabCore\'s MsgTracker isn\'t wired up) - autodisplay will not work here.');
return;
}

LTVBBC.php — v1.4 → v1.5

--- originals/e8aa5178-ltv_bbcode_v1.4/LTVBBC.php	2026-09-13 09:24:20.000000000 +1000
+++ /mnt/user-data/outputs/LTVBBC.php 2026-09-21 09:33:07.968354000 +1000
@@ -3,12 +3,25 @@
* ========================================================
* Module: LTVBBC.php
*
- * @version 1.0
- * @date 2026-09-13
+ * @version 1.5
+ * @date 2026-09-21
*
* Another collaboration between Andrew.human and Claude.ai
* ========================================================
*
+ * AE 21/09/2026 -- v1.5: [ltv=autodisplay]/[ltv=noautodisplay] now
+ * fail LOUDLY instead of silently when
+ * CollabMsgTracker::current() returns 0 - renders a
+ * RedBox (RedBoxBBCode, if installed) exactly where
+ * the tag was typed, via a new general-purpose
+ * redbox() helper. Same fix and rationale as
+ * PDFBBCode v2.28's/LMVBBCode v4.4's identical
+ * toggle branches. Deliberately not also logged via
+ * log_error() - the RedBox itself is the
+ * notification. (Also fixes this docblock's own
+ * @version, which had drifted to 1.0 while
+ * const VERSION was already at 1.4.)
+ *
* NAMING NOTE - this reuses the [ltv] tag name, but is NOT a
* revival of the old "Lightweight Text Viewer" mod (v1.0-v2.7.1,
* Hookers v1.0-1.1, QTest) that was fully retired and archived into
@@ -70,7 +83,7 @@
class LTVBBCode
{
const TAG = 'ltv';
- const VERSION = '1.4';
+ const VERSION = '1.5';

/** Extensions auto-embedded when a message's toggle is on. Lowercase, no dot. */
private static $extensions = array('txt', 'text');
@@ -167,6 +180,29 @@
}

/**
+ * Convenience wrapper for surfacing a visible, in-post failure -
+ * see PDFBBCode::redbox() for the full rationale (same helper,
+ * duplicated per-class since each mod is independently installable
+ * with no dependency on the others). Lazy-loads RedBoxBBC.php if
+ * it's installed, falls back to a plain inline warning string if
+ * it isn't.
+ *
+ * @param string $message
+ * @return string HTML - safe to assign directly into $data[0].
+ */
+ private static function redbox($message)
+ {
+ global $sourcedir;
+
+ if (!class_exists('RedBoxBBCode') && file_exists($sourcedir . '/RedBoxBBC.php'))
+ require_once($sourcedir . '/RedBoxBBC.php');
+
+ return class_exists('RedBoxBBCode')
+ ? RedBoxBBCode::render($message)
+ : '<b style="color:#b91c1c;">' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</b>';
+ }
+
+ /**
* Hook target for integrate_bbc_codes.
*
* @param array $codes Reference to SMF's internal bbc code definitions.
@@ -233,7 +269,19 @@
{
$msgId = self::currentMsgId();
if (!empty($msgId))
+ {
self::$autoDisplay[$msgId] = ($rawValue === 'autodisplay');
+ $data[0] = '';
+ return;
+ }
+
+ // v1.5: msgId capture failed - CollabMsgTracker::current()
+ // returned 0, so this toggle CANNOT take effect. Previously
+ // silent (tag just vanished, no error anywhere). Deliberately
+ // not also logged via log_error() - the RedBox itself is
+ // the notification.
+ $data[0] = self::redbox('[ltv=' . $rawValue . '] failed: no message ID available (CollabCore\'s MsgTracker isn\'t wired up) - autodisplay will not work here.');
+ return;
}

// Always discarded - this tag never renders visible content,

msgtracker_display_migrate.php — v1.1 → v1.3

--- originals/443c32c9-Collaborative_v3.0/msgtracker_display_migrate.php	2026-09-13 18:11:56.000000000 +1000
+++ /mnt/user-data/outputs/msgtracker_display_migrate.php 2026-09-21 10:26:02.751698000 +1000
@@ -3,8 +3,8 @@
* ========================================================
* Module: msgtracker_display_migrate.php
*
- * @version 1.1
- * @date 2026-09-13
+ * @version 1.3
+ * @date 2026-09-21
* Ships as part of CollabCore v2.0 (2.0.x branch install only)
*
* Another collaboration between Andrew.human and Claude.ai
@@ -12,7 +12,7 @@
*
* Runs as a Package Manager <code> install action. Migrates
* Display.php's ONE parse_bbc() call site to collabcore_parse_bbc(),
- * from whichever of FIVE states it's currently in:
+ * from whichever of SIX states it's currently in:
*
* 1. Untouched stock SMF - the original parse_bbc() call, never
* patched by anything (a genuinely fresh install).
@@ -35,7 +35,32 @@
* "collabcore_parse_bbc(" is already present.
* 5. Correctly FIXED collabcore_parse_bbc() guard (this script,
* v1.1+) - braced, with "global $sourcedir;" before the
- * require_once. Recognized as already-current, left untouched.
+ * require_once - AND the call line right after it genuinely
+ * calls collabcore_parse_bbc(). Recognized as already-current,
+ * left untouched (aside from the v1.3 comment top-up below).
+ * 6. HALF-EDITED state (found live in production, 2026-09-21): the
+ * fixed guard from state 5 is present verbatim, but the call
+ * line immediately after it was never updated - still reads
+ * "$message['body'] = parse_bbc(...)" instead of
+ * "= collabcore_parse_bbc(...)". Root cause: a hand edit (not
+ * this script) added the guard block without updating the call
+ * that follows it. Net effect: the guard harmlessly loads
+ * MsgTracker.php (or finds collabcore_parse_bbc() already
+ * defined) and then the code ignores it completely - every post
+ * still parses through stock parse_bbc(), so
+ * CollabMsgTracker::$currentMsgId is NEVER set, silently, with
+ * no error possible anywhere (confirmed live: PDFBBCode's/
+ * LMVBBCode's/LTVBBCode's toggle tags all fire, currentMsgId()
+ * resolves to 0 every time, RedBox correctly reports the
+ * failure - the toggle mechanism itself was never the bug).
+ * DANGEROUS PART: v1.1's state-5 check matches on the guard
+ * fragment ALONE, so it would see this half-edited state, think
+ * "already migrated, nothing to do," and leave it broken on
+ * every single reinstall - which is exactly how it survived
+ * undetected across multiple reinstalls before being caught.
+ * REPAIRED IN PLACE in v1.2: state 5's check now also verifies
+ * the call line itself, and a dedicated repair path swaps ONLY
+ * the stale call line, leaving the already-correct guard alone.
*
* v1.1 fix: v1.0 checked ONLY for the substring "collabcore_parse_bbc("
* to decide "already migrated, nothing to do" - which is true for
@@ -45,12 +70,39 @@
* and adds a dedicated repair path for state 4 that leaves the call
* line alone and only replaces the guard fragment.
*
+ * v1.2 fix: the fixed-guard marker's PRESENCE alone still isn't proof
+ * the migration is genuinely complete - see state 6 above. v1.2 adds
+ * a second check, gated behind the v1.1 marker match, for the actual
+ * call line - and a targeted repair for exactly that gap.
+ *
+ * v1.3 addition (Andrew's request, 2026-09-21): the guard block this
+ * script installs is a text patch to a core file, not a real SMF
+ * hook - unavoidable on the 2.0.x branch, since core's parse_bbc()
+ * has no integrate_pre_parsebbc call to hook until 2.1. A future
+ * reader of Display.php with no context could easily mistake this
+ * block for stray hand-edited cruft and "clean it up." So this
+ * version adds a short explanatory comment directly above the guard,
+ * marked with a stable, greppable string
+ * ("CollabCore MsgTracker patch (Collaborative package)") so:
+ * (a) anyone reading Display.php's source sees why the block is
+ * there and is told explicitly not to hand-edit it, and
+ * (b) this script can detect its own comment idempotently and never
+ * duplicate it on repeat installs.
+ * The comment is added both on a fresh migration (states 1-4/6) AND
+ * retroactively topped up on an already-correct state-5 install that
+ * predates v1.3 and never got one - self-healing applies to the
+ * annotation too, not just the logic.
+ *
* IMPORTANT: after this runs, ListAttBBC.php's own listatt_parse_bbc()
* function becomes dead code (Display.php no longer calls it) UNLESS
* ListAttBBC.php's own currentMsgId() is also patched to prioritize
* CollabMsgTracker::current() - see this package's readme for that
* one-line patch. Recommended to apply both in the same maintenance
* window - see listatt_bbcode v6.11+.
+ *
+ * See also: msgtracker_display_unmigrate.php (this package's matching
+ * uninstall action) and CollabCore-MsgTracker-Migration-Manual.md for
+ * the full install/uninstall lifecycle in plain language.
*/

if (!defined('SMF'))
@@ -67,15 +119,81 @@
return;
}

+// v1.3: explanatory comment installed directly above the guard, so
+// anyone reading Display.php's raw source understands what this block
+// is and why it must not be hand-edited. $patchCommentMarker is the
+// stable substring used to detect "comment already present" - kept
+// deliberately independent of the rest of the wording, so future
+// rewording of the comment text doesn't cause it to be duplicated.
+$patchCommentMarker = 'CollabCore MsgTracker patch (Collaborative package)';
+$patchComment = "// --- CollabCore MsgTracker patch (Collaborative package) - DO NOT hand-edit this block; reinstall/uninstall Collaborative via Package Manager to change it. See CollabCore-MsgTracker-Migration-Manual.md. ---\n\t\t";
+
$fixedGuard = "if (!function_exists('collabcore_parse_bbc'))\n\t\t{\n\t\t\tglobal \$sourcedir;\n\t\t\trequire_once(\$sourcedir . '/CollabCore/MsgTracker.php');\n\t\t}\n\t\t";
$callLine = "\$message['body'] = collabcore_parse_bbc(\$message['body'], \$message['smileys_enabled'], \$message['id_msg']);";
-$guardedTarget = $fixedGuard . $callLine;
+$guardedTarget = $patchComment . $fixedGuard . $callLine;

-// State 5 check first, via the DISTINCTIVE fixed-guard marker (not
-// just "collabcore_parse_bbc(" - see v1.1 fix note above) - already
-// correctly migrated, nothing to do.
+// State 5/6 check first, via the DISTINCTIVE fixed-guard marker (not
+// just "collabcore_parse_bbc(" - see v1.1 fix note above).
if (strpos($content, "global \$sourcedir;\n\t\t\trequire_once(\$sourcedir . '/CollabCore/MsgTracker.php');") !== false)
+{
+ $workingContent = $content;
+ $changed = false;
+
+ // v1.3: top up the explanatory comment if this install (fresh
+ // state 5/6, or a pre-v1.3 install that never got one) doesn't
+ // have it yet. Marker-gated so this never duplicates on repeat
+ // installs.
+ if (strpos($workingContent, $patchCommentMarker) === false)
+ {
+ $withComment = str_replace($fixedGuard, $patchComment . $fixedGuard, $workingContent, $commentCount);
+
+ if ($commentCount === 1)
+ {
+ $workingContent = $withComment;
+ $changed = true;
+ }
+ else
+ {
+ log_error('MsgTracker install: could not insert the explanatory comment above the guard in ' . $path . ' (unexpected match count: ' . $commentCount . ') - guard/call logic is unaffected, this only skips the comment annotation.');
+ }
+ }
+
+ // v1.2: the guard's presence alone doesn't prove the call line
+ // was ever updated - see state 6 in the docblock above. Only
+ // truly done if the real call line is ALSO already there.
+ if (strpos($workingContent, $callLine) !== false)
+ {
+ // State 5: guard AND call line both correct.
+ if ($changed && @file_put_contents($path, $workingContent) === false)
+ log_error('MsgTracker install: could not write ' . $path . ' - check file permissions. The explanatory comment was not added (guard/call logic is unaffected).');
+
+ return;
+ }
+
+ // State 6: guard is right, call line is still stale stock
+ // parse_bbc(). Repair ONLY the call line - the guard above it is
+ // already correct and must not be touched or duplicated.
+ $staleCallPattern = '/\$message\[\'body\'\]\s*=\s*parse_bbc\(\$message\[\'body\'\],\s*\$message\[\'smileys_enabled\'\],\s*\$message\[\'id_msg\'\]\);/';
+
+ if (preg_match($staleCallPattern, $workingContent))
+ {
+ $newContent = preg_replace($staleCallPattern, $callLine, $workingContent, 1);
+
+ if ($newContent === null || $newContent === $workingContent)
+ {
+ log_error('MsgTracker install: found the fixed guard already in place in ' . $path . ', but the call line is still stock parse_bbc() and the repair replacement failed - fix manually.');
+ return;
+ }
+
+ if (@file_put_contents($path, $newContent) === false)
+ log_error('MsgTracker install: could not write ' . $path . ' - check file permissions. The guard was correct but the stale call line was NOT repaired.');
+
+ return;
+ }
+
+ log_error('MsgTracker install: found the fixed guard already in place in ' . $path . ', but could not find the expected stock parse_bbc() call line after it to repair - Display.php may have been hand-edited further than this script recognizes. Fix requires a real code change here, not a manual edit - please share the actual line(s) around the guard.');
return;
+}

// State 4: the buggy no-global guard from v1.0 of this script.
// Matches ONLY the guard fragment (not the call line after it), so
@@ -84,7 +202,7 @@

if (preg_match($buggyGuardPattern, $content))
{
- $newContent = preg_replace($buggyGuardPattern, $fixedGuard, $content, 1);
+ $newContent = preg_replace($buggyGuardPattern, $patchComment . $fixedGuard, $content, 1);

if ($newContent === null || $newContent === $content)
{

msgtracker_display_unmigrate.php — v1.0 → v1.1

--- originals/443c32c9-Collaborative_v3.0/msgtracker_display_unmigrate.php	2026-09-13 18:11:56.000000000 +1000
+++ /mnt/user-data/outputs/msgtracker_display_unmigrate.php 2026-09-21 10:26:02.788000000 +1000
@@ -3,8 +3,8 @@
* ========================================================
* Module: msgtracker_display_unmigrate.php
*
- * @version 1.0
- * @date 2026-09-13
+ * @version 1.1
+ * @date 2026-09-21
* Ships as part of CollabCore v2.0 (2.0.x branch uninstall only)
*
* Another collaboration between Andrew.human and Claude.ai
@@ -19,6 +19,45 @@
* genuine stock is the same convention listatt_display_uninstall.php
* itself already uses (also reverts straight to stock, not to
* whatever came before listatt).
+ *
+ * v1.1 fix (2026-09-21, found while adding migrate.php v1.3's
+ * explanatory comment): v1.0's regex only matched the OLD unbraced
+ * v1.0-buggy guard form in its optional leading group ("if (...)
+ * \s* require_once(...);" - \s* cannot match the "{ global $sourcedir;
+ * ... }" bracing that migrate.php v1.1+ actually installs). Verified
+ * empirically: running v1.0's replacement against a real current-state
+ * (braced guard + collabcore_parse_bbc() call) fixture correctly
+ * reverted the CALL line to stock parse_bbc() but left the entire
+ * guard block - including its require_once($sourcedir .
+ * '/CollabCore/MsgTracker.php') - sitting untouched in Display.php.
+ * Since this package's own uninstall also removes MsgTracker.php via
+ * <remove-file>, that orphaned guard's function_exists() check would
+ * be false on every subsequent request, so its require_once would
+ * fire and fatal - EVERY page, site-wide - immediately after a normal
+ * uninstall through Package Manager, regardless of what state
+ * Display.php's call line was in beforehand. This was never triggered
+ * live only because nobody had uninstalled Collaborative yet; it was
+ * not conditional on the state-6 half-edit bug this same session
+ * fixed in migrate.php - it would have fired on ANY uninstall from a
+ * correctly-migrated state 5 too.
+ *
+ * v1.1 replaces the single call-anchored regex with two independent
+ * passes:
+ * 1. The common case - comment (if present) + guard (braced,
+ * current form) + collabcore_parse_bbc() call, all together -
+ * replaced in one shot with the stock call line.
+ * 2. A second, unconditional pass for anything pass 1 left behind -
+ * strips a lingering comment/guard (braced OR the old v1.0
+ * unbraced buggy form) even when NOT immediately followed by a
+ * collabcore_parse_bbc() call. This is what actually fixes the
+ * fatal above, and also independently covers the state-6
+ * half-edited case (guard present, call line already stock) -
+ * previously invisible to this script entirely, since there was
+ * no collabcore_parse_bbc() call anywhere in the file for the
+ * old regex to anchor on.
+ * Both passes are pure text removal, gated on the same guard/comment
+ * markers migrate.php itself uses - nothing here touches any other
+ * part of Display.php.
*/

if (!defined('SMF'))
@@ -32,14 +71,45 @@
if ($content === false)
return;

-$callPattern = '/(if \(!function_exists\(\'collabcore_parse_bbc\'\)\)\s*require_once\(\$sourcedir\s*\.\s*\'\/CollabCore\/MsgTracker\.php\'\);\s*)?\$message\[\'body\'\]\s*=\s*collabcore_parse_bbc\(\$message\[\'body\'\],\s*\$message\[\'smileys_enabled\'\],\s*\$message\[\'id_msg\'\]\);/';
+// Same marker/fragments migrate.php uses - kept in sync deliberately.
+$commentFragment = '\/\/ --- CollabCore MsgTracker patch \(Collaborative package\)[^\n]*\n\s*';
+$bracedGuardFragment = 'if \(!function_exists\(\'collabcore_parse_bbc\'\)\)\s*\{\s*global\s+\$sourcedir;\s*require_once\(\$sourcedir\s*\.\s*\'\/CollabCore\/MsgTracker\.php\'\);\s*\}\s*';
+$unbracedGuardFragment = 'if \(!function_exists\(\'collabcore_parse_bbc\'\)\)\s*require_once\(\$sourcedir\s*\.\s*\'\/CollabCore\/MsgTracker\.php\'\);\s*';
+$guardFragment = '(?:' . $bracedGuardFragment . '|' . $unbracedGuardFragment . ')';

$stockLine = "\$message['body'] = parse_bbc(\$message['body'], \$message['smileys_enabled'], \$message['id_msg']);";

-$newContent = preg_replace($callPattern, $stockLine, $content, -1, $count);
+// Pass 1: comment (optional) + guard (optional) + the live
+// collabcore_parse_bbc() call, all together - the common case when
+// Display.php is in the fully-correct migrated state.
+$pass1Pattern = '/(?:' . $commentFragment . ')?(?:' . $guardFragment . ')?\$message\[\'body\'\]\s*=\s*collabcore_parse_bbc\(\$message\[\'body\'\],\s*\$message\[\'smileys_enabled\'\],\s*\$message\[\'id_msg\'\]\);/';
+
+$afterPass1 = preg_replace($pass1Pattern, $stockLine, $content, -1, $count1);
+
+if ($afterPass1 === null)
+{
+ log_error('MsgTracker uninstall: pass 1 (guard+call) reversal failed with a regex error on ' . $path . ' - Display.php was NOT touched. Fix manually before removing MsgTracker.php, or the orphaned guard will fatal every page once it\'s gone.');
+ return;
+}
+
+// Pass 2: unconditional - strips any comment/guard pass 1 didn't
+// already consume (either because it wasn't immediately followed by
+// a collabcore_parse_bbc() call - e.g. the state-6 half-edited case,
+// call line already stock - or because pass 1's own replacement left
+// it in place). This is what prevents the orphaned-guard fatal
+// described in the v1.1 changelog above.
+$pass2Pattern = '/(?:' . $commentFragment . ')?' . $guardFragment . '/';
+
+$afterPass2 = preg_replace($pass2Pattern, '', $afterPass1, -1, $count2);
+
+if ($afterPass2 === null)
+{
+ log_error('MsgTracker uninstall: pass 2 (orphaned guard cleanup) failed with a regex error on ' . $path . ' - the call line may have been reverted but a guard block referencing MsgTracker.php could still remain. Check Display.php manually before this package finishes uninstalling.');
+ return;
+}

-if ($count === 0)
- return; // Not present - nothing to reverse.
+if ($count1 === 0 && $count2 === 0)
+ return; // Nothing to reverse - stock or already clean.

-if (@file_put_contents($path, $newContent) === false)
- log_error('MsgTracker uninstall: could not write ' . $path . ' - check file permissions. The collabcore_parse_bbc() call was not removed.');
+if (@file_put_contents($path, $afterPass2) === false)
+ log_error('MsgTracker uninstall: could not write ' . $path . ' - check file permissions. The collabcore_parse_bbc() call and/or its guard were NOT removed - do not let MsgTracker.php be deleted while this is unresolved, or every page will fatal.');

New file

CollabCore-MsgTracker-Migration-Manual.md — standalone install/uninstall lifecycle reference, did not exist before tonight.