Advanced Search - sebiboga/testlink-upgraded GitHub Wiki
Path: ASIDE menu > Search > Advanced search
URL: gui/templates/search/searchAdvancedView.html?tproject_id=<id>
BFF API: api/search/index.php (action=fulltext; session-based auth, JSON I/O)
Prerequisite: view_tc grant on the current test project (same visibility
rule as legacy getMenuVisibility()); requirement sections appear only when
the project has requirements enabled.
Replaces: legacy lib/search/searchMgmt.php (form) +
lib/search/search.php (engine), gui/templates/dashio/search/searchGUI.inc.tpl
(ExtJS results grid). The BFF reuses TestLink's own searchCommands class, so
SQL semantics are identical to 1.9.20.

The 1.9.20 "multiple entities" free-text search: one search text is looked up across four node types of the current test project only:
| Entity | Fields searched |
|---|---|
| Test Cases | title, summary, preconditions, steps, expected results, external id |
| Test Suites | title, details |
| Requirement Specifications | title, scope |
| Requirements | title, scope, document id |
Multiple words are treated as separate terms and combined with the selected
operator (AND = all terms must match, OR = any term matches), exactly
like the legacy and_or select. HTML is stripped before matching through the
MySQL function UDFStripHTMLTags (same as legacy).
| Filter | Behavior |
|---|---|
| Created by / Edited by |
LIKE on author/updater login, first or last name |
| Keyword | exact keyword id (dropdown shown when the project has keywords) |
| Custom field + value | design-time custom fields for test cases AND requirements in one dropdown; date/datetime values converted like legacy, everything else LIKE
|
| Test case status | Draft β¦ Final domain on the latest tcversion (tcWKFStatus) |
| Requirement status | D/R/W/F/I/V/N/O domain on the latest requirement version (reqStatus, shown when requirements are enabled) |
| Creation date from/to | range on tcversions.creation_ts ("to" includes 23:59:59); dates sent in the project's configured date format |
| Modification date from/to | range on tcversions.modification_ts
|
Legacy parity notes:
- at least one field checkbox must be checked (legacy re-rendered the form otherwise; the modern screen shows a localized warning instead);
- date filters apply to test case versions only β upstream builds a
dates4rqfilter for requirements but never consumes it (searchCommands.class.php), so this screen inherits the same behavior; - an entirely empty search returns all latest test case versions server-side (legacy semantics kept in the API); the UI asks for at least one criterion before sending;
- the legacy requirement type filter is not offered: upstream never
delivers it to the engine correctly (
lib/search/search.php:74strips theRQprefix from the wrong property, so the generated SQL can never match); - requirement field checkboxes are only submitted when requirements are enabled on the project (legacy omitted the inputs entirely).
Results are rendered as one sectioned table per entity type, each with its own columns:

-
Test Cases β path, clickable
PREFIX-extid [vN] :: Name(opens the read-only tcView popup), summary snippet, edit + execution-history buttons; -
Test Suites β name (edit popup via
archiveData.php?edit=testsuite) and stripped details; -
Requirement Specs β
Name [rN](opens the specification detail screenreqSpecView.html?id=<req_spec_id>) and stripped scope; - Requirements β doc id, name + version.revision tag, full path, scope.
A global match counter shows the total across entities; warnings ("empty test project", "no records found") reuse the localized strings of the other search screens.
All labels live under the searchAdv.* namespace; shared strings are reused
from the existing search.* keys. The 18 new keys were added to all ten
locale bundles (en, de, es, fr, it, ja, pt, ro, ru, zh) and every bundle was
validated with python3 -m json.tool.
Test suite 28 in tmp/TLU_Test_Cases.md β 17/17 PASS (BFF parity,
AND/OR term logic, per-field scoping, keyword/CF/author/date filters,
validation paths, locale switching, aside link switch, Event Viewer clean).
Environment note: a fresh database import lacks the MySQL function
UDFStripHTMLTags, which makes both legacy and modernized advanced search
fail with a DB access error. Fixed in issue #547: searchCommands now
probes information_schema.routines and automatically falls back to plain
LIKE matching when the function is absent β identical semantics to upstream
option $tlCfg->UDFStripHTMLTags=false. Deployments that want HTML stripping
should provision it from install/sql/mysql/testlink_create_udf0.sql
(the sample-db restore scripts docs/db_sample/restore_sample.sh / .bat
do this automatically). Note for self-managed MySQL 8 servers with binary
logging enabled: creating routines additionally needs SUPER or
log_bin_trust_function_creators=1 (MariaDB used by this project's CI has
binary logging off).

Each of the four attribute groups of the search form now has a master
checkbox in its header (left of the group title) that toggles every field of
that group in one click β the modern port of the legacy toggle_all icon +
cs_all_checkbox_in_div() from searchGUI.inc.tpl:59-61,76-78,86-88,97-99.

Behavior:
- clicking the master checks all fields of its group when at least one is unchecked (including the indeterminate state) and unchecks them all when every field is already checked β same alternation as the legacy icon;
- the master reflects the real group state at all times: checked when all fields are on, indeterminate when only some are, unchecked when none are. It re-syncs after every individual checkbox change and after Reset;
- the master never reaches the BFF: its ids are
all_tc/all_ts/all_rs/all_rq, which do not match thetc_/ts_/rs_/rq_prefixesdoSearch()collects, so the submitted search keys are unchanged; - the tooltip comes from the new
searchAdv.checkUncheckAllkey, present in all ten locale bundles (Check / uncheck all,BifeazΔ / debifeazΔ tot, ...) and applied throughdata-i18n-title.


No BFF change was needed β api/search/index.php already consumed every
field flag. Verified 14/14 PASS (test suite Task β Issue #1095 in
tmp/TLU_Test_Cases.md): toggle/indeterminate/reset matrix, requirements
enabled project (all four groups), locale=ro reload (translations applied,
headers and masters survive TLi18n.apply()), search regression and a clean
Event Viewer. Pre-existing console error found while testing and filed
separately as #1856 (out of scope, not fixed here).
Clicking a Requirement Specifications result now opens the specific
specification at
reqSpecView.html?id=<req_spec_id>&tproject_id=<id> β the target legacy built
with openLinkedReqSpecWindow() (lib/search/search.php:316 β
gui/javascript/testlink_library.js:1156-1166).
Before (issue #1096): the handler openReqSpecEdit(rsId) received the spec id
from the result row and then ignored it, always opening the generic
management grid:
function openReqSpecEdit(rsId) {
var url = '/gui/templates/requirements/reqSpecMgmt.html?' + (tprojectId ? 'tproject_id=' + tprojectId : '');
...
}
After: the handler is renamed to openReqSpecView() (it no longer edits
anything) and passes the id through, mirroring the sibling handlers
openTcEdit / openTsEdit / openReqEdit:
function openReqSpecView(rsId) {
var url = '/gui/templates/requirements/reqSpecView.html?id=' + rsId +
(tprojectId ? '&tproject_id=' + tprojectId : '');
window.open(url, '_blank', 'width=1024,height=768,resizable=yes,scrollbars=yes');
}
Notes:
- no BFF change was needed β
api/search/index.phpalready returnedreq_spec_idfor every result row (api/search/index.php:604); -
reqSpecView.htmlreads the id fromidorreq_spec_id(reqSpecView.html:310), so the emitted?id=URL is unambiguous; - verified 11/11 PASS (test suite Task β Issue #1096 in
tmp/TLU_Test_Cases.md): both result rows open their own specification (id=9097renders SPEC Alpha,id=9098renders SPEC Beta), the Test Case / Test Suite links of the same screen still opentcView.html/suiteView.html, the i18n coverage gate passes (no new keys), and the Event Viewer gained no Error/Warning entries; - two defects found while testing were filed separately instead of being
folded into this change: #1865 (the simple Search screen
searchMgmt.html:387links results withreqspec_id=, a parameterreqSpecView.htmlnever reads) and the pre-existing console errorReferenceError: p is not defined(#1856).
When the screen URL carries a search target term
(searchAdvancedView.html?tproject_id=<id>&target=<term>), the form now
submits itself on load exactly like legacy
(lib/search/searchMgmt.php:99 forceSearch β
dashio/search/searchGUI.inc.tpl:257-259): the box is prefilled with the
term and doSearch() runs immediately, so deep links (navBar one-box
hand-off, bookmarked searches) land straight on results β no manual Find
click needed.
Before (issue #1097) the deep link was doubly broken:
-
searchAdvancedView.htmlscopedvar p = new URLSearchParams(...)inside the jQuery-ready closure, but the globalloadContext()reads it β so the URL-prefill block threwReferenceError: p is not definedand never ran (the box stayed empty). That separate bug was filed as #1856 and is closed by this same change. - even when the prefill had worked, nothing auto-submitted:
doSearch()(:368) was only reachable through the Find button, unlike the siblingsearchMgmt.htmlwhich already runs its navBar hand-off (:261-266).
The fix, gui/templates/search/searchAdvancedView.html:
-
var p = null;is now declared at global scope (the same pattern assearchMgmt.html:154); the ready-closure only reassigns it β prefill runs. - right after the URL-prefill block, the legacy forceSearch condition is
mirrored verbatim:
if ($.trim(p.get('target') || '') !== '') { doSearch(); }β a non-emptytarget(after trim) triggers the search automatically.

Notes:
- no BFF change was needed β
api/search/index.php?action=fulltextalready acceptstargetas a plain GET parameter; - a deep link without
targetkeeps the current bare-form behaviour (0action=fulltextrequests β exactly legacy, whereforceSearchwas false); - verified 10/10 PASS (test suite Task β Issue #1097 in
tmp/TLU_Test_Cases.md): deep link auto-runs with the term prefilled, the fulltext request fires with no click, manual Find/Reset still work, the siblingsearchMgmt.htmlnavBar hand-off still auto-runs, console has zero errors (theReferenceErroris gone), i18n coverage gate passes (no new keys), Event Viewer gained no Error/Warning rows.
In 1.9.20 the results were ExtGrid tables built by lib/search/search.php:
Test Cases grouped under collapsible group headers per test-suite path
(buildTCExtTable(), setGroupByColumnName('test_suite'), :225) and
Requirements grouped per requirement-spec path (buildRQExtTable(),
setGroupByColumnName('req_spec'), :402). The Ext GroupingView prefixed the
group value with the column header (showGroupName default) and counted the
rows (lib/functions/exttable.class.php:589-591 groupTextTpl):
Test Suite: Suite Alpha (2 Items)
Requirement Specification: Spec A (Smoke) (1 Item)
plus an Ext.ux.TableToolbar (gui/javascript/ext_extensions.js:184-240) with
Expand/collapse groups, default state and Reset filters.
The modern screen dropped all of that β the results were a flat list where the suite/spec path was just a repeated cell. It is now restored:
-
renderTC()/renderRQ()route throughrenderGrouped()(searchAdvancedView.html:507,:627,:544) which groups the BFF rows onrow.path(already in thefulltextpayload β no BFF change), preserving server order, and emits one collapsible group-header row per group with the item count (searchAdv.groupHeadertemplated{label}: {value} ({count}), singular/pluralgroupItem/groupItems); - clicking a group header collapses/expands only that group (
.collapsedclass + caret rotation); - a per-section grid toolbar restores the legacy
Ext.ux.TableToolbar: Expand/collapse groups toggles every group at once, Reset filters restores the default view (all groups expanded); - TS / Req.Spec sections stay flat β legacy did not group them either.
New i18n keys in all 10 bundles: searchAdv.expandCollapseGroups,
searchAdv.resetFilters, searchAdv.groupHeader, searchAdv.groupItem,
searchAdv.groupItems, searchAdv.ungrouped. i18n coverage gate PASS 9/9.


Verified in browser on fixture tmp/fixtures_1098.php (project SA1098,
suites Alpha/Beta Γ 2 Smoke cases + req specs RQ1098-A/B Γ 1 Smoke req):
TC grid β 2 groups (2 Items) each, RQ grid β 1 group each; single-group
toggle / expand-collapse-all / reset all behave; flat TS/RS fallback intact;
node --check clean; console 0 errors; Event Viewer no Error/Warning; suite
Task β Issue #1098 PASS.
In 1.9.20 the results block was closed by a "Generated by TestLink on
<date>" stamp (gui/templates/dashio/search/searchResults.tpl:38):
{if !isset($gui->warning_msg) || $gui->warning_msg == ''}
... result tables ...
{lang_get s='generated_by_TestLink_on'} {$smarty.now|date_format:$gsmarty_timestamp_format}
{else}
<div class="user_feedback"><br />{$gui->warning_msg}</div>
{/if}The timestamp was rendered by Smarty with $gsmarty_timestamp_format
(lib/functions/tlsmarty.inc.php:304 β cfg/const.inc.php:303
locales_timestamp_format[<session locale>]), i.e. in the logged-in user's
TestLink language, and only when the search produced no warning. The modern
screen showed the match count alone β measured baseline: { footer: "2 matches", matchCount: "(2 matches)" }, no date anywhere.
It is restored, following the same design as issue #1093 (searchView.html)
and issue #1081 (searchReqSpec.html):
-
BFF β
GET /api/search/index.php?action=fulltextnow returnsgenerated_on(server-rendered with the session-locale timestamp format, identical resolution block to the siblingaction=searchRefs #1093 code) plusgenerated_on_iso(Y-m-d H:i:s, used as the tooltip). A browserDate().toLocaleString()cannot honour the TestLink locale formats, so the stamp is rendered server-side. -
Screen β new footer block
#footerGenerated/#footerGenOnright after the match-count footer#footerInfo(searchAdvancedView.html);renderGeneratedFooter(r)runs insiderenderResults()and keeps the block hidden when the search produced a warning, no rows or nogenerated_onβ the exact legacy{if !warning_msg}guard.resetForm()hides the block and clears text +title, so a stale stamp can never survive a Reset. -
i18n β no bundle changed: the label reuses
common.generatedBy(the modern equivalent ofgenerated_by_TestLink_on, present in all 10 bundles; coverage gate PASS).
Behaviour: #footerInfo keeps the plain match count; the stamp below reads
Generated by TestLink on 07/10/2026 09:01:30 (legacy dd/mm/yyyy HH:MM:SS
session format) with the ISO value as hover tooltip; Reset and no-match
searches show no stamp; re-running a search refreshes the time.
Before (count only, no timestamp):

After (stamp restored below the count):

Verified in browser on SQL fixture project 1 "Demo Project" (suite
Suite Alpha, test case Alpha login smoke test, fresh zero-project DB):
search alpha β #footerInfo (2 matches) + stamp visible with ISO title;
BFF payload carries generated_on/generated_on_iso; Reset / no-match /
re-run matrix PASS; console 0 errors; php -l + node --check clean; Event
Viewer no new Error/Warning; suite Task β Issue #1099 10/10 PASS.