Bugfix Issue 1514 ReqEdit NewVersion NestedSqlComment - sebiboga/testlink-upgraded GitHub Wiki
Bugfix — Issue #1514: reqEdit Create New Version aborts with HTTP 500 — nested SQL comment in closeOpenTCVersionOnOpenLinks
Creating a new requirement version from the modern Requirement Editor
(gui/templates/requirements/reqEdit.html → Create New Version, BFF
api/reqedit POST ?action=version) aborts with HTTP 500 on XHR (legacy
non-XHR paths die with a full HTML backtrace — CWE-200 path disclosure):
- browser console:
Failed to load resource: ... 500 (Internal Server Error) - the
eventstable (Event Viewer) gains alog_level=1(ERROR) DATABASE row:triggered from1064 - You have an error in your SQL syntax; ... near '*/ UPDATE tcversions SET is_open = 0 WHERE id IN ( SELECT tcversion_id FROM req_requirement_mgr.class.php(2511)insidecloseOpenTCVersionOnOpenLinks()←copy_version()←create_new_version().
The crash happens after the row copy has already run, so the caller is left
with a half-finished new version: log_message NULL, source version not
frozen (is_open=1), TC link freeze never applied, and the frontend never
refreshes (shows the error, stays on the old version).
Environment: http://localhost:8082 (PHP built-in server, docroot=repo root),
MariaDB testlink, PHP 8, login admin/admin. Fresh fixtures: testproject
id=1 (option_reqs=1), req_spec id=6, requirement id=8 (REQ-1514, v1 =
type 2/Feature, expected_coverage 2, scope fixture scope v1), custom field
linked + design value HIGH on version node 9.
- Open
gui/templates/requirements/reqEdit.html?id=8&tproject_id=1— the form correctly shows TYPE=Feature, EXPECTED COVERAGE=2, Version 1. - Click Create New Version, accept the prompt with any log message.
- Network panel:
POST /api/reqedit/index.php?action=version&id=8→ 500. - DB: the new
req_versionsrow exists with copied type/coverage/scope/status, butlog_messageNULL and the source version stillis_open=1.
Chain (each hop backed by file:line):
-
lib/functions/requirement_mgr.class.php:2498incloseOpenTCVersionOnOpenLinks()builds a self-delimited SQL debug comment:$debugMsg = '/* Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__ . ' */'; -
:2503wraps it again in a SQL comment:$sql = " /* $debugMsg */ UPDATE ...". - Result:
/* /* Class:requirement_mgr - Method: closeOpenTCVersionOnOpenLinks */ */ UPDATE tcversions ...— MySQL/MariaDB do not support nested block comments. The parser closes the outer/*at the first*/, leaving a dangling*/ UPDATE ...→ SQL syntax error 1064. -
lib/functions/database.class.php:206: on anXMLHttpRequestrequest,exec_query()throwsException('Database error (query failed)')instead of the legacy HTML backtrace, so the exception unwindscloseOpenTCVersionOnOpenLinks → copy_version → create_new_versiontoapi/reqedit/index.php:471's catch → HTTP 500. Legacy/non-XHR paths hitdatabase.class.php:223die()+ backtrace.
Commit f24b6d28b (Fixes #1513) changed line 2498 from the correct form
'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__; — introduced by
ac7f81cf2, which pairs it with the " /* $debugMsg */ " wrapper in the SQL —
back to the legacy self-delimited '/* Class:... */' form. Legacy 1.9.20 built
the SQL as " $debugMsg UPDATE ..." (no extra /* */ wrapper), so the
self-delimited debugMsg was fine there; the modern template double-wraps it.
Single call site: requirement_mgr.class.php:2482 inside copy_version(),
gated by freezeLinkedTCases (config defaults reqTCLinks->freezeLinkOnNewREQVersion
and freezeBothEndsOnNewREQVersion are TRUE, config.inc.php:1415/1424). Any
create_new_version() therefore crashes at this step:
- modern
api/reqeditPOST ?action=version(this issue), - legacy
reqViewVersions"create new version", -
api/reqimportcreateFromMap/createFromXMLwithactionOnHit=create_new_version.
Every other class method that self-delimits its debugMsg with /* */ builds the
SQL as " $debugMsg <verb> ..." (no extra wrapper) — verified lines 2566, 2646,
2669, 2691, 2709, 2725, 3992 — so only closeOpenTCVersionOnOpenLinks was
affected.
One line, lib/functions/requirement_mgr.class.php:2498 — drop the SQL comment
delimiters from the debugMsg so the existing " /* $debugMsg */ " template
composes a single valid comment (the class-standard pattern used everywhere
else):
- $debugMsg = '/* Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__ . ' */';
+ $debugMsg = 'Class:' . __CLASS__ . ' - Method: ' . __FUNCTION__;Rationale for the minimal approach: the SQL template already owns the comment
syntax (/* ... */), the debugMsg must stay plain text. Alternatives rejected:
(a) reverting f24b6d28b wholesale — unnecessary, only this line regressed; (b)
changing the template to " $debugMsg " — diverges from the rest of the class
and touches more code.
Regression suite in tmp/TLU_Test_Cases.md — 11/11 PASS. Key measured results:
- Browser Create New Version →
POST ?action=version&id=8HTTP 200{"status":"ok","version":2,"version_id":13,"new_version":2}; form reloads to Version 2, info bar "New version created v2". - New
req_versionsrow:type=2,expected_coverage=2,scope/statuscopied;log_messagepersisted verbatim; CF design valueHIGHcopied to the new node (copy_cfields); source version frozen (is_open=0). - With a real
req_coveragelink (link_status=1) to an open tcversion: no exception and the linked tcversion is closed (is_open=0) — thefreezeBothEndsOnNewREQVersionfreeze path now runs end-to-end. - CLI harness
requirement_mgr::create_new_version(8,1)(legacy non-XHR path) returns{id,version,msg:'ok'}with no die/backtrace. -
eventstable: 0 newlog_level=1entries; browser console: 0 JS errors. -
php -l lib/functions/requirement_mgr.class.phpclean; generated SQL is a single/* Class:requirement_mgr - Method: closeOpenTCVersionOnOpenLinks */ UPDATE ....

-
lib/functions/requirement_mgr.class.php— 1 line. -
tmp/TLU_Test_Cases.md—Suite 1514regression suite. -
docs/Bugfix-Issue-1514-ReqEdit-NewVersion-NestedSqlComment.mdmirror. -
CHANGELOG— KEY BUGFIX line (Fixes #1514).