Skip to content

Keyword Management Dialog Modernized

opencode CI edited this page Sep 27, 2026 · 3 revisions

Keyword Management Dialog (keywordsEdit) — Modernized Screen

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.


Table of Contents

  1. What the screen does
  2. REST API Reference
  3. Legacy parity notes
  4. i18n Keys
  5. Security
  6. Testing
  7. Screenshots

1. What the screen does

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)

2. REST API Reference

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.

3. Legacy parity notes

  • testproject::getName() is used for the context; in cfl mode the test case name is read from the nodes_hierarchy row of the version's parent id (the 2.0.1 refactor has no static testcase::getName(), see issue #1600/#1601).
  • Show event history is back in edit mode for holders of mgt_view_events (legacy showEventHistoryFor()), opening the modern event viewer filtered by object_type=keywords — key kwedit.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->onDeleteCheckExecutedTCVersions and onDeleteCheckFrozenTCVersions still decide.
  • lib/keywords/keywordsEdit.php is kept as a session-guarded redirect shim: anonymous users get the legacy testlinkInitPage() login bounce, the create|edit|cfl display requests are mapped onto the modern popup URL, and the legacy do_create|do_update|do_delete|do_cfl POSTs 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.

4. i18n Keys

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.

5. Security

  • AND-mode rights — legacy initEnv() refused a view-only keyword manager (the dialog can write, so mgt_modify_key alone 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_id sent by the caller, while the object is addressed by a bare id; neither tlKeyword::writeToDB() (UPDATE … WHERE id = X) nor testproject::deleteKeyword() (id only) re-check the owner. Every route that takes a keyword id now loads it and answers 404 when 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_id is caller-supplied, so tcaseVersionContext() walks the whole nodes_hierarchy parent chain: the root has to be a test project (node_type_id = 1) equal to tproject_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 answers 404, so neither the foreign link nor the foreign test case name leaks (issue #1603). Remember kwParentChain() is root-first: chain[0] is the project.
  • The legacy shim lib/keywords/keywordsEdit.php re-checks the project of the keyword on do_update/do_delete, rejects cross-site POSTs (Sec-Fetch-Site / Origin), coerces request parameters to scalars and surfaces the resulting kwerr code as a toast in keywordsView.html (issue #1604). Its GET no longer re-points the session project (testlinkInitPage($db) without TRUE).
  • In the sibling BFF api/keywords: GET /{id} checks mgt_view_key on the keyword's own project and bulk POST /import needs mgt_modify_key AND mgt_view_key (issue #1604). Note the legacy import parser splits on ;. A comma-delimited file used to answer 200 {"status":"ok"} and import nothing; since #1605 the importer reports the real outcome (imported/skipped/errors[], 422 when nothing landed) and a comma-delimited file is accepted — see Bugfix-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).

6. Testing

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)

7. Screenshots

  • 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_key only): 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

Clone this wiki locally