Bugfix Issue 1545 UsersAssignPlan Duplicate Value0 Inherited Option - sebiboga/testlink-upgraded GitHub Wiki
Issue 1545 — usersAssignPlan: duplicate value-0 options (-- no override -- + <inherited>) in Plan Role Override selects
Issue: #1545
Branch: fix/issue-1545
Status: VERIFIED-FIXED (2026-09-19)
On the modern Assign Test Plan Roles screen
(gui/templates/usermanagement/usersAssignPlan.html), every Plan Role Override
per-user <select> renders two options with value 0: -- no override --
(the hardcoded first option) plus <inherited> (the id-0 pseudo-role returned by
the BFF). Legacy renders exactly one value-0 option (<inherited> <role>,
usersAssign.tpl:262-272). As a side effect, rows without an explicit override
auto-selected the last value-0 option (<inherited>) instead of showing the
-- no override -- state.

- Fresh-import DB;
php tmp/fixtures_938.php(tprojectPLANROLES938, tplanPLAN938-R1, users u938designer/4, u938senior/6, u938tester/7, u938leader/9; overrides senior→6, tester→7). - Log in
admin/adminat http://localhost:8082, opengui/templates/usermanagement/usersAssignPlan.html. - Select project
PLANROLES938, then planPLAN938-R1. - Devtools: list each
#assignBody tr[data-uid]select'svalue === "0"options. -
Before fix: two value-0 options (
-- no override --+<inherited>). After fix: exactly one (-- no override --).
-
cfg/const.inc.php:526definesTL_ROLES_INHERITED = 0. -
lib/functions/tlRole.class.php:521-523—tlRole::getAll()injects a synthetic id-0<inherited>tlRole into every returned role set (a legacy pseudo-role used to model "inherited" in legacy selects). -
api/roles/index.php(GET /meta/tplan-roles) built$roleOptsfrom that result without filtering id 0, so the JSONrolespayload carried<inherited>(id 0). The sibling routetproject-rolesalready filters it (api/roles/index.php:619-626, the accepted Fixes #926 fix for the project screen). -
gui/templates/usermanagement/usersAssignPlan.html:268-278—loadUsers()emits a hardcoded<option value="0">-- no override --</option>and then appends every BFF role verbatim, so the id-0<inherited>landed in the select as a second value-0 option; line 276 marks itselectedfor every row withroleID = 0(0 == 0), corrupting the default display too.
Blast radius: the only consumer of the tplan-roles roles payload is
usersAssignPlan.html; buildBulkSelect() there already guards role.id <= 0
(usersAssignPlan.html:324), proving the UI never needs the pseudo-role.
api/roles/index.php — the tplan-roles GET route now skips
TL_ROLES_INHERITED when building $roleOpts, mirroring the tproject-roles
fix (issue #926):
if (intval($r->dbID) == TL_ROLES_INHERITED) continue;Frontend untouched. After the fix each row select has exactly one value-0 option
(-- no override --), which becomes the correct default for rows without an
explicit override; buildBulkSelect() behaviour is unchanged.

| Case | Result |
|---|---|
| Exactly one value-0 option on all 5 rows (admin, designer, leader, senior, tester) | PASS |
No-override rows default to -- no override -- (admin locked/disabled, designer, leader) |
PASS |
| Override rows keep explicit role selected (senior→senior tester, tester→tester) | PASS |
Bulk Set roles to test designer → Do → Save → all 4 non-admin rows role 4; one value-0 option on reload |
PASS |
Bulk revert to -- no override -- → Do → Save → all rows default back; user_testplan_roles count 0 |
PASS |
| Global-admin row still disabled with locked hint | PASS |
GET /api/roles/meta/tplan-roles payload = 9 roles, 0 with id===0
|
PASS |
Event Viewer: only INFO(16) rows, zero log_level>=32; console clean |
PASS |
Regression suite: tmp/TLU_Test_Cases.md → Regression — Issue #1545 (6/6 PASS).