# Search Requirement Specifications — Criteria Form (Modernized Screen) **Issue:** [#1825](https://github.com/sebiboga/testlink-upgraded/issues/1825) (enhancement) — bugs found while testing: [#1826](https://github.com/sebiboga/testlink-upgraded/issues/1826), [#1827](https://github.com/sebiboga/testlink-upgraded/issues/1827) **Path:** the criteria page in front of *Search Requirement Specifications* ([results screen](Search-Requirement-Specifications.md)) **URL:** `gui/templates/requirements/reqSpecSearchForm.html?tproject_id=[&doc_id=…&name=…&reqSpecType=…&scope=…&log_message=…&custom_field_id=…&custom_field_value=…]` **BFF API:** `api/reqspecsearchform/index.php` — `GET|HEAD ?action=init&tproject_id=…` (session-based auth, JSON I/O) **Prerequisite:** `mgt_view_req` **or** `mgt_modify_req` on the addressed test project; anything else is HTTP 403 **Replaces:** `lib/requirements/reqSpecSearchForm.php` + `gui/templates/dashio/requirements/reqSpecSearchForm.tpl` (and its still-legacy target `lib/requirements/reqSpecSearch.php`) ![Criteria form](issue-1825-reqspecsearchform-form.png) --- ## What it does Collects the **seven** requirement-specification search criteria of TestLink 1.9.20 and hands them to the modern results screen ([`searchReqSpec.html`](Search-Requirement-Specifications.md)) with `auto_search=1`, so the search runs on arrival. The criteria themselves are unchanged: | Criterion | Behavior | |-----------|----------| | Doc ID | offered **only** when the project has req specs containing requirements (`testproject::GET_NOT_EMPTY_REQSPEC`, legacy `reqSpecSearchForm.php:37`); otherwise hidden and a `no_req_specs` card explains why | | Title | `LIKE '%value%'` on the spec name | | Type | exact match; domain from `config_get('req_spec_cfg')->type_labels`, localized server-side, exactly as legacy | | Scope / Log message | `LIKE` on the revision | | Custom field + value | design-time custom fields linked to **requirement_spec** (`cfield_mgr::get_linked_cfields_at_design(..., ENABLED, null, 'requirement_spec')`); the value box appears only once a field is picked | All criteria are combined with a logical **AND**, stated above the grid. ## Context card Test project, prefix, current user, **requirements on/off** chip, **requirement specifications yes/no** chip, the result cap (`req_cfg.search.max_qty_for_display`) and the number of available custom fields — so it is never unclear *which* project a search will be restricted to. ## Toolbar **Find** (validate-free hand-off, asks first when nothing is filled in) / **Open search results** (jump to the grid without a search) / **Refresh** / **Reset** / **Back to Search Test Cases** / **Close** (closes the window, or returns to the opener). The results screen received the matching **Criteria form** button (`openCriteriaForm()`), so the two entry points now reach each other in both directions — legacy had none in either direction. ## Deep links Every criterion travels in the query string and is **echoed back trimmed and length-capped** (255 chars for the short fields, 2000 for scope / log message / custom-field value), so the page is bookmarkable and shareable — legacy 1.9.20 was none of those: it listed the criteria and POSTed them, and the URL never changed. ![Deep-linked, pre-filled form](issue-1825-reqspecsearchform-prefilled.png) An **unknown** `reqSpecType` or `custom_field_id` in the URL is dropped rather than forwarded — a form must not hand the results screen a filter the server will refuse (`400 Unknown custom field`). ## Security — the reason this screen was worth re-doing The legacy controller authorized **nothing but a session**: it read the test project from `$_SESSION['testprojectID']` and listed that project's **design-time custom fields** to *any* authenticated user, a global `` role (`role_id = 3`) included. Sibling class of #1696 / #1765 / #1770. The BFF now enforces `mgt_view_req` **or** `mgt_modify_req` on the **ADDRESSED** project, and does it **before** resolving the project, so the endpoint is not even a test-project existence oracle — an unauthorized caller gets 403, never 404 (the #1697 lesson). Both rights are tested because `propagateRights()` copies global project rights wholesale and establishes no modify→view implication of its own; a requirement **author** must reach this page too. Verified with a real guest-role login: `403 no_right`, the "Access denied" card, no project data in the payload, and the attempt recorded in the Event Viewer. ## Response contract | Situation | Status | Machine code | |---|---|---| | anonymous | 401 | `not_authenticated` | | no `mgt_view_req` / `mgt_modify_req` | 403 | `no_right` | | unknown test project | 404 | `tproject_not_found` | | `POST` / any write verb | 405 + `Allow: GET, HEAD` | `wrong_method` | | unknown `action` | 400 | `unknown_action` | | missing / non-numeric / zero `tproject_id` | 400 | `invalid_tproject` | | repeated parameter (`?scope[]=x`) | 400 | `invalid_parameter` | ## Bugs found and fixed while testing | Issue | Symptom | Fix | |---|---|---| | [#1827](https://github.com/sebiboga/testlink-upgraded/issues/1827) | a deep link opened a **blank** form — `load()` built the first `/init` from the still-empty controls, so the echo was always empty | `initQuery()` sends the incoming criteria with the first `/init`; the form is still filled from the **server** echo, never a client-side copy | | [#1826](https://github.com/sebiboga/testlink-upgraded/issues/1826) | `?scope[]=x` was cast with `trim((string)$v)` → the literal `"Array"` **plus** an `Array to string conversion` **E_WARNING** per request (an Event Viewer row), and the polluted value went on into `LIKE '%Array%'` | `is_scalar()` guard on `action`, `tproject_id` and every criterion → `400 invalid_parameter` + one WARNING; both shims drop such keys | The mandatory **code review** (no blocker) additionally found and fixed: `Refresh` silently discarded a Type / Custom-field choice (it now snapshots and restores both selects), the **Criteria form** button was labelled with another action's i18n key (new `rssf.criteria`), `$actions->reqSpecSearchForm` was dead code (now wired into `frmWorkArea.php`'s `$feature_map`), and the response carried a `grant` payload the read-only screen never read. ## Legacy controllers retired (both) | Legacy file | Now | |---|---| | `lib/requirements/reqSpecSearchForm.php` (242 lines) | session-guarded **302** to the modern page, forwarding every criterion a bookmark carried so a legacy link lands on a **pre-filled** form; `405` for a write verb; `405 retired_endpoint` JSON for an XHR/crawler; anonymous → `login.php?note=expired` | | `lib/requirements/reqSpecSearch.php` (242 lines) | session-guarded **302** to `searchReqSpec.html`, translating the legacy criterion names (`requirement_document_id` → `doc_id`) and adding `auto_search=1` when criteria were supplied. `coverage` is **deliberately dropped**: legacy `init_args()` collected it and no query ever read it, so no row changes | Neither shim holds state or runs a legacy code path. ## i18n 50 new keys (49 `rssf.*` + `footers.reqSpecSearchForm`), translated in **all 10** bundles (en, ro, de, es, fr, it, ja, pt, ru, zh). The prefix is `rssf.*` and **not** `rsf.*` — Reorder Requirements already owns the latter. ## Test coverage Suite **“Issue #1825”** (23 cases) in `tmp/TLU_Test_Cases.md`: the admin happy path and the config-driven criteria domain, the Find/Results hand-off, the empty-criteria confirm, Reset, Refresh, deep-link pre-fill, server-side length caps, the three project states (requirements disabled / no req spec / no right → 403), every error code above, both shims (navigation / write verb / XHR / anonymous) and the results↔form round trip. Browser console clean; Event Viewer gained **no** Error or Warning rows — only the intentional `mgt_view_req missing` and `BFF shim: refused POST` entries. ## See also * [Search Requirement Specifications](Search-Requirement-Specifications.md) — the results grid * [Modernize: Copy Requirement Specification](Modernize-Copy-Requirement-Specification-reqSpecCopy.md)