mainPage Planless Project Switch E_WARNING Fix - sebiboga/testlink-upgraded GitHub Wiki
Fixed: every load of lib/general/mainPage.php that switched/initialized the
session Test Project while the session still held a stale Test Plan (a plan
belonging to the previous test project) wrote three E_WARNING entries into
the Event Viewer whenever the newly selected project had no accessible test
plans.
Event Viewer gained three entries per switch (log_level=2):
E_WARNING Undefined array key 0 - lib/general/mainPage.php - Line 102
E_WARNING Trying to access array offset on null - lib/general/mainPage.php - Line 102
E_WARNING Undefined array key 0 - lib/general/mainPage.php - Line 105

- Fresh DB; create test project "Alpha" with one active test plan, test project "Beta" with none. Log in as admin.
- Select Alpha (session
testplanID= Plan A). - Request
lib/general/mainPage.php?tproject_id=<Beta>— any project switch via the nav bar does exactly this. - Event Viewer → the three warnings above.
The block in mainPage.php that re-validates the session test plan ran only
when $testplanID > 0, but never checked that $arrPlans was non-empty:
$arrPlans = (array)$currentUser->getAccessibleTestPlans($db,$testprojectID);
if($testplanID > 0) {
...
if( $found == 0 ) {
$index = 0;
$testplanID = $arrPlans[$index]['id']; // line 102: empty array!
}
setSessionTestPlan($arrPlans[$index]); // line 105: empty array!
$arrPlans[$index]['selected']=1;
}When switching projects, initProject() validates the old plan id against
the new project (getAccessibleTestPlans($db,$tproject,$tplan)), gets null
(the old plan is not in it), falls back to "any accessible plan of the new
project", also gets null (Beta has none) — and leaves the stale session
testplanID untouched. Back in mainPage.php the stale id is > 0, so the
block runs with $arrPlans = []:
-
$arrPlans[0]→ Undefined array key 0, evaluating to null, -
null['id']→ Trying to access array offset on null, - second
$arrPlans[0]at line 105 → Undefined array key 0.
PHP 8.x logs all three as E_WARNING into the Event Viewer.
Considered alternatives:
-
Null-coalesce / isset() around each read (the issue's minimal
suggestion). Silences the warnings but leaves the stale plan id flowing
into
$gui->testplanIDwhile$countPlans == 0— an inconsistent state (page claims a selected plan that belongs to another project). -
Guard + explicitly drop the stale session plan (chosen): when the
fallback list is empty there is nothing selectable, so reset the local
$testplanIDto 0 and callsetSessionTestPlan(null)— its documented else-branch unsets$_SESSION['testplanID']/$_SESSION['testplanName'], which is exactly what the buggy code was accidentally achieving anyway (it fednullintosetSessionTestPlan(), just with warnings).
if( $found == 0 ) {
// update test plan id
if(count($arrPlans) > 0) {
$index = 0;
$testplanID = $arrPlans[$index]['id'];
}
else {
// Session still points at a Test Plan of ANOTHER Test Project while
// the newly selected one has no accessible Test Plans. Nothing can be
// selected here: drop the stale session plan instead of reading
// offset 0 of an empty array (PHP 8.x turns this into E_WARNINGs,
// see GitHub issue #548). setSessionTestPlan(null) clears the keys.
$testplanID = 0;
setSessionTestPlan(null);
}
}
if($testplanID > 0) {
setSessionTestPlan($arrPlans[$index]);
$arrPlans[$index]['selected']=1;
}Behavior parity for the healthy paths is preserved exactly: found-match keeps the matched index; found-miss with a non-empty list still falls back to index 0; no plan in session skips the whole block as before.
-
lib/general/mainPage.php— guarded thefound == 0branch and made the session-write/selected-marking conditional on a resolved plan.
| # | Check | Result |
|---|---|---|
| 1 | Pre-fix repro → events 13/14/15 (2× line 102, 1× line 105) | reproduced |
| 2 | Post-fix: same switch via direct URL and via nav-bar selector → zero new events | PASS |
| 3 | Post-fix: bare mainPage.php on Beta renders clean empty state (no dashboard), nothing logged |
PASS |
| 4 | Regression: Beta → Alpha auto-selects Plan A again (found==0 non-empty path intact) | PASS |
| 5 | Regression: reload with explicit tproject/tplan (found==1 path intact); full round-trip ends with zero new Event Viewer entries | PASS |
