Bugfix Issue 1414 PlanImport BFF Count Null Crash - sebiboga/testlink-upgraded GitHub Wiki
Issue 1414 — planImport (modern BFF) HTTP 500: count(null) crash at api/planimport/index.php:208 for a missing test-case version
Issue: #1414
Branch: fix/issue-1414
Status: VERIFIED-FIXED (2026-09-11)
Symptom
POST /api/planimport/?action=import&tproject_id=<id>&tplan_id=<id> with a valid import
file crashes with HTTP 500, empty body whenever the file contains a link whose test
case EXISTS on the test project but the requested version does not. PHP 8 throws:
TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given
at api/planimport/index.php:208
This is the modern-BFF twin of the legacy crash already fixed in #1390
(lib/plan/planImport.php:372, commit 995ca7f36). It only became reachable once the
#1389 XML parse fix landed (the parse always failed first, masking this crash).

Repro steps
- Log in as admin.
- Fixture
tmp/fixtures_pimp.php: tproject 1 PIMP, plan 12 PIMP-Plan, TCs PIMP-1..3 (Login/Logout/Settings, version 1 each, tcversions 4/7/10), platform 1 PIMP-Android. POST /api/planimport/?action=import&tproject_id=1&tplan_id=12withuploadedFile=tmp/pimp_xml/repro1390.xml(2 links: Login extid 1 v1 [exists]; Login extid 1 v99 [does NOT exist]).- Before fix: HTTP 500, empty body, fatal TypeError at
api/planimport/index.php:208. After fix: HTTP 200 with{"status":"ok", ...}and aTest Case with external id 1 version 99 does not exist on Test Project PIMProw.
Expected: graceful per-link feedback; links with missing versions reported as not imported, never a server crash.
Root cause
api/planimport/index.php:206-208:
$dummy = $tcaseMgr->get_basic_info($tcaseSet[$externalID], array('number' => $version));
if (count($dummy) > 0) {
Chain (identical to #1390):
isset($tcaseSet[$externalID])passes for any test case present on the project, regardless of version.get_basic_info()(lib/functions/testcase.class.php:5740) runsSELECT ... WHERE TCV.version = <n> AND NH_TCASE.id = <id>and returns$this->db->get_recordset($sql).get_recordset()(lib/functions/database.class.php:776) initializes$output = nulland only builds an array while rows are fetched — zero rows ⇒ returnsnull.count(null)is a TypeError on PHP 8.x (silently coerced to0on PHP < 8). The intendedelsebranch at line 216 (tcversion_doesnot_exist) was unreachable.
get_basic_info has 15 call sites; the others guard the result with !is_null()
(e.g. testcase.class.php:6762) or stay inside a version-exists context. Only the legacy
planImport.php:372 (fixed in #1390) and its modern twin api/planimport/index.php:208
fed the result straight into count().
Fix (minimal)
api/planimport/index.php:208 — honor the low-level contract (null = version missing):
if (!is_null($dummy) && count($dummy) > 0) {
count() is only invoked on a non-null array; a null/empty result falls through to the
same else branch. Exactly the guard applied to the legacy path in 995ca7f36. No other
files touched. UI strings, i18n bundles, rights checks unchanged.
Verification (regression matrix, all PASS on localhost:8082)
Fixture reset before the run (php tmp/fixtures_pimp.php): tproject 1, plan 12,
platform 1, tcversions Login 4 / Logout 7 / Settings 10.
- create (existing TC + existing version): mixed.xml →
external id 1 version 1 has been linked to Test Plan for Platform PIMP-Android+ same for extid 2; DB rows tcversion 4/platform 1 (node_order 10) and tcversion 7/platform 1 (node_order 20). - missing version (the crash case): repro1390.xml Login v99 →
Test Case with external id 1 version 99 does not exist on Test Project PIMP, no crash, no link row. - non-existent TC: regr1390.xml externalid 777 →
Attention!! - Can not find test case identified by 777; no row. - no platform element on a platform-linked plan: regr1390.xml link #4 →
Test case link #4 has no platform element/ Not imported. - platform not on test project: badplat.xml →
... request platform NoSuchPlatform be linked ... does not exist on target Test Project./ Not imported, no crash. - malformed XML: invalid.xml → HTTP 200,
simplexml_load_file_wrapper_error("Failed to load XML") row, no crash. - idempotency (re-import same file): repro1390.xml →
... was already linked to Test Plan, only execution order has been updated.; no duplicate link row. - browser: upload + submit on
gui/templates/plans/planImport.html?tplan_id=12renders the 2-row report withImport completed successfully., zero console errors. HTTP 200. Screenshot:planImport-fixed-issue1414.png. - hygiene:
php -l api/planimport/index.phpclean;eventstable — only AUDIT(16) rows from the run (audit_testproject_created,audit_login_succeeded, 2×audit_tc_added_to_testplan); no new Error/Warning entries from the fixed path.
Discoveries while testing
- The earlier pre-fix 500 request in this run had already processed link 1 (Login v1) successfully before crashing on link 2 (v99), which is why the first post-fix import reports "already linked ... execution order updated" instead of "has been linked". This matches legacy behaviour and the idempotency regression case.
- No new bugs discovered while testing this run.
Evidence
Commit range: (fix), (regression suite), (docs + wiki) on branch fix/issue-1414.
Docs mirror: docs/Bugfix-Issue-1414-PlanImport-BFF-Count-Null-Crash.md
(screenshot planImport-fixed-issue1414.png).