Bugfix Issue 1915 Jira Default Resolved Status - sebiboga/testlink-upgraded GitHub Wiki
Bugfix Issue #1915 β jiraCommons::initDefaultResolvedStatus() warns for JIRA status sets without literal Resolved/Closed
Follow-up of #1914 (same helper, different defect): filed while testing that fix
(see the "Related" note in docs/bugfix-issue-1914-php82-dynamic-properties.md).
Symptom
jiraCommons::initDefaultResolvedStatus() (lib/issuetrackerintegration/jiraCommons.class.php)
hardcodes the item set array('Resolved','Closed') and indexes the incoming $statusDomain
map by those exact names. JIRA status names are fully customizable (and localized), so an
instance whose status set is e.g. Done / In Review is entirely legal β and then every
call emits two diagnostics and returns a broken map:
PHP Warning: Undefined array key "Resolved" in lib/issuetrackerintegration/jiraCommons.class.php on line 93
PHP Warning: Undefined array key "Closed" in lib/issuetrackerintegration/jiraCommons.class.php on line 93
The returned entries get code => null, so nothing can ever be classified as resolved.
Unlike the E_DEPRECATED noise of #1814/#1915-sibling reports, E_WARNING is persisted:
the web-tier watchPHPErrors() handler writes each one as an Event Viewer row, so a
JIRA-linked TestLink install with custom status names gets a Warn row on every
POST /api/issuetracker/index.php/test-connection (and any legacy issue-tracker screen that
instantiates the interface).
Reachable through jiradbInterface.class.php:72, jirarestInterface.class.php:190,
jirasoapInterface.class.php:205.
Root cause
jiraCommons.class.php:87-96:
$itemSet = array('Resolved','Closed'); // Unfortunately case is important
foreach($itemSet as $st) {
$domain[] = array('code' => $statusDomain[$st], 'verbose' => $st); // line 93
}
$statusDomain is a statusName => statusId map built from the live JIRA status list:
| Back-end | Writer | Source |
|---|---|---|
| DB | jiradbInterface::getStatuses() :190 |
SELECT issuestatus.pname |
| REST | jirarestInterface :186 |
APIClient->getStatuses() |
| SOAP | jirasoapInterface :202 |
SOAP getStatuses() |
Line 93 indexes with no isset()/array_key_exists() guard, so a missing name is an
undefined-key read (PHP 8 E_WARNING) whose value is null.
The result flows into setResolvedStatusCfg() (jirarestInterface:918, jirasoapInterface:369,
base issueTrackerInterface:634) β resolvedStatus->byCode, and is consumed by
isResolved = isset($this->resolvedStatus->byCode[$issue->statusCode])
(jiradb:157, jirarest:238, jirasoap:151). A null key becomes byCode[""], which never
matches a real status code.
Same defect class as #1608 (undefined array key β PHP 8 diagnostic); not a recent regression β the hardcoding has been there since the helper was written.
Fix (minimal)
Guard the lookup; skip names that are absent.
foreach($itemSet as $st) {
// Refs #1915: JIRA status names are customizable/localizable, so a domain
// without these exact keys is legal. Guard the lookup to avoid an
// "Undefined array key" E_WARNING and a bogus null 'code' entry; missing
// names are simply skipped and can be supplied via cfg->resolvedstatus.
if( array_key_exists($st, $statusDomain) ) {
$domain[] = array('code' => $statusDomain[$st], 'verbose' => $st);
}
}
No caller changes, no assignment removed. Behavior for instances that do have
Resolved/Closed is byte-identical; an explicit cfg->resolvedstatus override in
setResolvedStatusCfg() still takes precedence.
Companion hardening (found by the mandated code review). An empty list must not break the
DB backend: jiradbInterface uses the base issueTrackerInterface::setResolvedStatusCfg()
(:634), which β unlike the REST/SOAP overrides β never initialized byCode. With the fix
returning [], the foreach is skipped and array_flip($this->resolvedStatus->byCode) reaches
array_flip(null) β fatal TypeError (not caught by catch(Exception)). With two null-code
entries this fatal was masked pre-fix, so the guard alone would have converted a warning into an
HTTP 500 on JIRA-DB integrations. The base method is now hardened identically to
jirasoapInterface::setResolvedStatusCfg():
$this->resolvedStatus->byCode = array();
if( !is_null($statusCfg['status']) ) {
foreach ($statusCfg['status'] as $cfx) { ... }
}
Alternatives rejected:
- Case-insensitive lookup β still fails for localized section names (
TerminΓ©) and would silently mis-classify. Rejected. - Treat every status actually present as resolved β would classify
Open/In Progressas resolved. Rejected. - Populate a JIRA built-in default map β statuses are per-instance; no such map exists. Rejected.
- Suppress with
@/error_reportingβ hides a realnull, keeps the broken code. Rejected.
Verification
CLI probes (error handler installed to count warnings):
$ php /tmp/probe_jiracommons.php
warnings=0
result=[] # pre-fix: warnings=2, [{"code":null,...},{"code":null,...}]
$ php /tmp/probe_jiracommons_matrix.php
both: warnings+=0 result=[{"code":"5","verbose":"Resolved"},{"code":"6","verbose":"Closed"}]
one: warnings+=0 result=[{"code":"5","verbose":"Resolved"}]
neither: warnings+=0 result=[]
total_warnings=0
| # | Step | Expected | Observed | Verdict |
|---|---|---|---|---|
| 1 | php -l lib/issuetrackerintegration/jiraCommons.class.php |
no syntax errors | No syntax errors detected | PASS |
| 2 | probe, domain {Done,In Review} |
0 warnings, [] |
0 warnings, [] |
PASS |
| 3 | probe, domain {Resolved,Closed} |
unchanged 2-entry map | [{"code":"5",...},{"code":"6",...}] |
PASS |
| 4 | probe, domain {Resolved} |
1 entry, 0 warnings | [{"code":"5",...}] |
PASS |
| 5 | static reader census | helper is the only reader | 3 writers, 1 guarded reader | PASS |
| 6 | base setResolvedStatusCfg() with empty list (/tmp/probe_base_resolvedcfg.php) |
no fatal | byCode=[] byName=[] (pre-hardening: fatal TypeError on array_flip(null)) |
PASS |
| 6b | base setResolvedStatusCfg() with both statuses |
unchanged map | byCode={"5":"Resolved","6":"Closed"} |
PASS |
| 7 | Event Viewer | no new Warn row | warning removed at source | PASS |
| 8 | merge-base gate | exit 0 | 7 PASS / 0 FAIL | PASS |
Gate: TLU_REQUIRE_SUITE="Issue #1915" bash ai/verify_test_suites.sh β exit 0.
Suite: Regression β Issue #1915 in tmp/TLU_Test_Cases.md.
Screenshots: none new β the defect is server-side (no screen state changes); the evidence is
the warning count (2 β 0) and the map ([null,null] β []).
Files changed
lib/issuetrackerintegration/jiraCommons.class.phpβ guard the$statusDomainlookup.lib/issuetrackerintegration/issueTrackerInterface.class.phpβ harden basesetResolvedStatusCfg()for an empty list.CHANGELOGβ 2.0.1 BUGFIX line.tmp/TLU_Test_Cases.mdβ regression suite (append-only).docs/mirror + this GitHub Wiki page.
Related
- #1914 β PHP 8.2 dynamic-property declarations in the issue-tracker interfaces; its docs note already flagged this #1915 warning as a separate, unfixed root cause.
- #1608 β same "undefined array key β PHP 8 E_WARNING" defect class in
tree::get_subtree().