Keyword Management Dialog Modernized - sebiboga/testlink-upgraded GitHub Wiki
Modernization of the legacy keyword popup β lib/keywords/keywordsEdit.php +
gui/templates/dashio/keywords/keywordsEdit.tpl β GitHub issue
#1599.
The legacy Smarty dialog (create / edit / delete / create-and-link in one
controller) is replaced by a standalone Dashio popup
(gui/templates/keywords/keywordsEdit.html) backed by a plain-PHP REST BFF
(api/keywordsedit/index.php).
Path: Test Case Design β Test Case Viewer β per-version Create Keyword /
Create Keyword and Link; also reachable from the Keyword Management screen
via $actions->keywordsEdit in lib/functions/common.php.
URL: gui/templates/keywords/keywordsEdit.html?mode=create|edit|cfl&tproject_id=<id>[&id=<kwId>][&tcversion_id=<v>]
BFF API: api/keywordsedit/index.php
Rights: mgt_modify_key AND mgt_view_key at test-project level
(legacy initEnv() AND-mode gate) β enforced server-side on every route and
mirrored client-side to hide the write controls.
| Feature | Legacy behavior | Modern implementation |
|---|---|---|
| Create keyword |
doAction=create form β do_create β testproject::addKeyword()
|
POST ?action=create β same addKeyword() call, Dashio card with Name + Notes |
| Edit keyword |
doAction=edit&id=N β do_update β testproject::updateKeyword()
|
GET init&mode=edit&id=N prefills the card; POST ?action=update
|
| Create and link |
doAction=cfl&tcversion_id=V β do_cfl = addKeyword() + testcase::addKeywords()
|
GET init&mode=cfl&tcversion_id=V shows the test-case context; POST ?action=create_link does the same two calls |
| Delete keyword |
do_delete β deleteKeyword(checkBeforeDelete) β refuses when linked to executed/frozen TC versions |
Delete button (edit mode) + confirm modal; POST ?action=delete keeps the executed/frozen refusal (422 DELETE_BLOCKED) |
| Name validation | client hint βquotes and commas are not allowedβ, server tlKeyword::checkKeywordName()
|
identical client hint and identical server codes (E_NAMENOTALLOWED/-1, E_NAMELENGTH/-2, E_NAMEALREADYEXISTS/-4) surfaced with the legacy messages |
| Error feedback |
$gui->user_feedback from getKeywordErrorMessage()
|
teal success box / red error box, fed by the same legacy lang keys |
| Keyword context | title only | context card: test project (and the test case name + id in cfl mode) |
| Refresh of the opener | the legacy form reloaded the opener frame |
window.opener refresh when the popup was script-opened, Open Keyword Management link otherwise |
| ASIDE entry |
keywordsEdit was a hidden dialog URL |
getActions() now exposes $actions->keywordsEdit (modern URL with the mode= context) |
Session-based auth; every route passes bffSameOriginGuard() (POST/PUT/DELETE
need the X-Requested-With: XMLHttpRequest header) and
requireKeywordRights() (mgt_modify_key AND mgt_view_key on tproject_id,
plus the legacy tproject_id > 0 precondition).
| Method & path | Purpose | Failure modes |
|---|---|---|
GET ?action=init&tproject_id=N&mode=create |
project context, empty keyword | 400 invalid project, 403 no right |
GET ?action=init&tproject_id=N&mode=edit&id=K |
keyword name/notes for the card | 400 invalid keyword id, 404 keyword not found or owned by another project, 403 |
GET ?action=init&tproject_id=N&mode=cfl&tcversion_id=V |
test case context (tcase_id, tcase_name) for the create-and-link card |
400/404 for the version, 403 |
POST ?action=create {tproject_id, keyword, notes}
|
create | 400/403, 422 + error_code
|
POST ?action=update {tproject_id, id, keyword, notes}
|
rename / re-notes | 400/403, 404 foreign keyword, 422 + error_code
|
POST ?action=create_link {tproject_id, keyword, notes, tcversion_id}
|
create + link to the TC version | 400/403, 404, 422 + error_code
|
POST ?action=delete {tproject_id, id}
|
delete (refuses when linked to executed/frozen TC versions) | 400/403, 404 foreign keyword, 422 DELETE_BLOCKED
|
The payload also returns rights.mgt_view_events so the card can offer the
event history link for the keyword, exactly like the legacy dialog did.
-
testproject::getName()is used for the context; incflmode the test case name is read from thenodes_hierarchyrow of the version's parent id (the 2.0.1 refactor has no statictestcase::getName(), see issue #1600/#1601). -
Show event history is back in edit mode for holders of
mgt_view_events(legacyshowEventHistoryFor()), opening the modern event viewer filtered byobject_type=keywordsβ keykwedit.showEventHistory. - Keyword names are truncated to the 100-character column limit before the write, as the legacy dialog did.
- The delete refusal uses the same
deleteKeyword(checkBeforeDelete:true)call as 1.9.20, so$cfg->keywords->onDeleteCheckExecutedTCVersionsandonDeleteCheckFrozenTCVersionsstill decide. -
lib/keywords/keywordsEdit.phpis kept as a session-guarded redirect shim: anonymous users get the legacytestlinkInitPage()login bounce, thecreate|edit|cfldisplay requests are mapped onto the modern popup URL, and the legacydo_create|do_update|do_delete|do_cflPOSTs are still executed server-side (with the same AND-mode gate) before a redirect to the modern screen β so an old bookmarked form cannot silently lose data. - The Test Case Viewer gained the two entry buttons inside each version card
(
gui/templates/testcases/tcView.html); the version id is passed explicitly so the keyword is linked to the version whose card was clicked, not to the βcurrentβ one.
25 kwedit.* keys + footers.keywordsEdit were added to all locale bundles
(en, ro, de, es, fr, it, pt, ru, ja, zh):
title, subtitle, context, testProject, testCase, name, notes, notesHint, charHint, create, edit, cfl, save, saving, cancel, delete, close, loading, loadError, noRight, msgCreated, msgUpdated, msgLinked, msgDeleted, deleteTitle, deleteText, deleteConfirm, deleteCancel, openManager, required.
-
AND-mode rights β legacy
initEnv()refused a view-only keyword manager (the dialog can write, somgt_modify_keyalone was not enough). The same gate is applied here and in the shim; the view-only case is covered by regression test 16 of suite 1599 (issue #1008). -
Project ownership of the keyword id β rights are evaluated for the
tproject_idsent by the caller, while the object is addressed by a bareid; neithertlKeyword::writeToDB()(UPDATE β¦ WHERE id = X) nortestproject::deleteKeyword()(id only) re-check the owner. Every route that takes a keyword id now loads it and answers404when it belongs to another project, so a manager of project A can neither rename, re-own nor delete a keyword of project B (issue #1601, fixed in both keyword BFFs). -
Create-and-link must not cross projects β the
tcversion_idis caller-supplied, sotcaseVersionContext()walks the wholenodes_hierarchyparent chain: the root has to be a test project (node_type_id = 1) equal totproject_id, and the requested node has to be a test case (3) or a test case version (4) β a suite or the project itself is refused. Everything else answers404, so neither the foreign link nor the foreign test case name leaks (issue #1603). RememberkwParentChain()is root-first:chain[0]is the project. - The legacy shim
lib/keywords/keywordsEdit.phpre-checks the project of the keyword ondo_update/do_delete, rejects cross-site POSTs (Sec-Fetch-Site/Origin), coerces request parameters to scalars and surfaces the resultingkwerrcode as a toast inkeywordsView.html(issue #1604). Its GET no longer re-points the session project (testlinkInitPage($db)withoutTRUE). - In the sibling BFF
api/keywords:GET /{id}checksmgt_view_keyon the keyword's own project and bulkPOST /importneedsmgt_modify_keyANDmgt_view_key(issue #1604). Note the legacy import parser splits on;. A comma-delimited file used to answer200 {"status":"ok"}and import nothing; since #1605 the importer reports the real outcome (imported/skipped/errors[],422when nothing landed) and a comma-delimited file is accepted β seeBugfix-Issue-1605-Keyword-CSV-Import-False-Success.md. -
CSRF β
bffSameOriginGuard()on all writes; anonymous requests get 401. - Nothing is interpolated into HTML unescaped (
esc()in the front-end, prepared statements in the model layer).
Suite 1599 in tmp/TLU_Test_Cases.md (24 cases) plus the code-review
regression suite 1599-R (23 cases), all PASS. The first suite covers init for the
three modes, 400/404/403/401 paths, all four writes, the three name-validation
errors, the executed/frozen delete refusal, the view-only and no-rights users,
the browser flows (create, duplicate-name error box, delete modal, locale
switch RO), the legacy shim redirects and the Event Viewer check (no new
ERROR/WARNING row).
Bugs found and fixed while testing:
| Issue | Symptom | Fix |
|---|---|---|
| #1008 | view-only keyword manager could open the write dialog | AND-mode gate in api/keywords/index.php + this BFF |
| #1600 | every keyword create error was a fatal HTTP 500 (tlKeyword::getErrorMessage() no longer exists) |
new legacy-parity error mapping; tlKeyword::getError() E_NAMELENGTH case + default fixed |
| #1601 | cross-project keyword rename / re-own / delete |
requireKeywordOfProject() in both keyword BFFs |
| #1602 | tcView buttons never rendered; wrong version targeted | grants added to the view action; openKeywordPopup(mode, tcversion_id)
|
| #1603 | Create-and-Link accepted a tcversion_id of another project (cross-project IDOR + foreign test case name disclosure) |
tcaseVersionContext() parent-chain + owning-project validation, 404 |
| #1604 | shim: no ownership check on update/delete, no CSRF guard, keyword[]=x β 500, errors swallowed; api/keywords read/import rights on the wrong project |
kwShimOwnedBy(), cross-site rejection, scalar coercion, kwerr toast, own-project mgt_view_key, AND-mode import gate |
| #1605 | bulk import answers 200 {"status":"ok"} while importing nothing (legacy ; delimiter, per-row errors dropped) |
fixed β importKeywordsFromCSV() reports imported/skipped/errors[], 422 when nothing landed, , accepted (see the Bugfix-1605 page) |
- Create-and-link mode after a successful link:
docs/screenshots/issue-1599-keywordsedit-cfl.png - Edit mode with the delete confirmation modal:
docs/screenshots/issue-1599-keywordsedit-delete-modal.png - Permission denied (user with
mgt_view_keyonly):docs/screenshots/issue-1599-keywordsedit-no-right.png - Test Case Viewer with the two keyword buttons:
docs/screenshots/issue-1599-tcview-keyword-buttons.png - Edit mode with the restored Show event history action (issue #1604 round):
docs/screenshots/issue-1599-keywordsedit-event-history.png - Error toast rendered from the legacy shim's
?kwerr=redirect (issue #1604):docs/screenshots/issue-1599-keywordsview-kwerr-toast.png