Repository navigation
Bugfix Issue 985 Guest Login HTTP500
Issue: #985
Commit: 087ace530 (fix) + 9d00f9201 (regression suite)
Status: VERIFIED-FIXED (2026-09-05)
Logging in with a user that has only the GUEST role (role id 5), or any user without
a test-project role assignment, redirects to index.php?caller=login which crashes
with HTTP 500:
PHP Fatal error: Uncaught Error: Call to a member function getDisplayName() on null
in lib/functions/common.php:1787
Stack trace:
#0 lib/functions/tlsmarty.inc.php(597): initUserEnv()
#1 lib/functions/tlsmarty.inc.php(650): TLSmarty->addMenuContext()
#2 index.php(71): TLSmarty->display()
Reproduced on a fresh DB with a test project (testprojects.id=1, GTP), a user
pts_guest/ptsguest (users.id=2, role_id=0 — no global role) and a project
GUEST role (id 5) on project 1. Both the browser (login → chrome-error://chromewebdata/
HTTP 500) and a CLI harness replicating addMenuContext() → initUserEnv() produced the
exact stack above.
-
lib/functions/tlUser.class.php:302-305—readFromDB()builds$this->globalRoleonly whenglobalRoleID(i.e.users.role_id) is truthy. Forrole_id = 0(guest accounts),$this->globalRolestays null. -
lib/functions/tlUser.class.php:670-682—getEffectiveRole()sets$effective_role = $this->globalRole;and returns it unchanged when the currenttplan_id/tproject_idmatches nouser_testplan_roles/user_testproject_rolesrow (right after logintproject_id = 0⇒ no match). Result: returns null. -
lib/functions/common.php:1779-1791— the Refs #609 guard checks$args->user !== nullbut then unconditionally calls$eRoleObj->getDisplayName()on the effective role at line 1787, with no guard on$eRoleObj. Result: fatal. -
lib/functions/tlsmarty.inc.php:597 → 650—addMenuContext()runsinitUserEnv()on every render of the modern Dashio frameset, so the guest login redirect toindex.php?caller=login500s immediately.
Two further null-role crashes surfaced on the same guest session once the main fatal was cleared:
-
lib/general/navBar.php:198—$args->user->globalRole->getDisplayName()fatal building the navbar whoami when no project role was selected. -
lib/functions/tlUser.class.php:830—$this->globalRole->rightsE_WARNING insidehasRight()whenglobalRoleis null (recorded in theeventstable,log_level=2).
-
lib/functions/common.php:1787— guard the effective-role object:$gui->whoamiRole = is_object($eRoleObj) ? $eRoleObj->getDisplayName() : '';
-
lib/general/navBar.php:196-201— guard the global role withis_object()and fall back to''when it is null:if ($gui->tproject_id && isset($args->user->tprojectRoles[$gui->tproject_id])) { $role = $args->user->tprojectRoles[$gui->tprojectID]; $testprojectRole = $role->getDisplayName(); } else if (is_object($args->user->globalRole)) { $testprojectRole = $args->user->globalRole->getDisplayName(); } else { $testprojectRole = ''; // guest account with users.role_id = 0 }
-
lib/functions/tlUser.class.php:832— treat a null global role as an empty right set inhasRight():$userGlobalRights = (array)($this->globalRole ? $this->globalRole->rights : array());
All consumers of $gui->whoamiRole use |escape on the value, so the '' fallback
renders as an empty label with no further error.
The bug has two layers: the user object is fine but the role it resolves is null.
Fixing it by hard-coding a fallback role (e.g. forcing GUEST) would silently grant an
account semantics it did not ask for and could diverge from hasRight() results. The
chosen approach mirrors the existing Refs #609 anonymous-identity fallback (''), keeping
guests as a genuine zero-right identity while rendering the shell. No refactor of role
resolution was attempted — this is a targeted null-guard.
-
Guest login (
pts_guest/ptsguest): Dashio frameset renders, no HTTP 500;navBar/asideMenu/mainPageall HTTP 200; whoami shows "Guest User". - Guest → project 1 (project-role GUEST path): renders, no error.
-
Admin regression (
admin/admin): full frameset 200, whoami "Testlink Administrator / admin", full aside — unchanged. -
Event Viewer /
eventstable: guest + admin sessions produced zero new WARNING (log_level=2) rows; only AUDIT (log_level=16) login-success rows. The 13 pre-existing WARNING rows are all from the before-fix guest session. -
php -lpasses on all three touched files. - Full 7-case regression suite appended to
tmp/TLU_Test_Cases.md(7/7 PASS).


- Before:
docs/screenshots/issue-985-guest-login-500.png - After:
docs/screenshots/issue-985-guest-login-fixed.png
-
lib/functions/common.php(+3/−1) — effective-role null guard (whoamiRole). -
lib/general/navBar.php(+3/−0) — global-role null guard (whoamiRole). -
lib/functions/tlUser.class.php(+2/−1) — empty right set for null global role. -
tmp/TLU_Test_Cases.md— regression suiteTC-985.*(7/7 PASS). -
docs/screenshots/issue-985-guest-login-{500,fixed}.png— evidence.