Skip to content

Port to MediaWiki 1.43 - #1

Open
malberts wants to merge 15 commits into
mainfrom
mw143
Open

Port to MediaWiki 1.43#1
malberts wants to merge 15 commits into
mainfrom
mw143

Conversation

@malberts

@malberts malberts commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

The extension does not run on MediaWiki 1.43. A page containing a <bugzilla> tag fatals before any request is
made, and the ResourceLoader module is unloadable on every page of the wiki. This branch makes it work, and fixes
the error paths that were hiding failures rather than reporting them.

Blocking breakage

MWHttpRequest::factory() was removed from core. Called at three sites, so every page with a tag failed with
Call to undefined method MWHttpRequest::factory() and could not be saved or rendered. Replaced with
MediaWikiServices::getHttpRequestFactory()->create(), which takes the same arguments and returns the same object;
the option arrays are unchanged.

There was no extension.json, so wfLoadExtension( 'Bugzilla' ) took the whole wiki down with an HTTP 500.
Registration now lives in extension.json, and Bugzilla.php is removed rather than kept as a shim. See the
breaking change below.

This also fixes an ordering trap. Bugzilla.php assigned every $wgBugzilla* global at the bottom of the file,
so any value a wiki set before the require_once was silently discarded, $wgBugzillaRESTURL included. Registry
configuration is applied after LocalSettings.php is read and skips globals the wiki has already set, so both
orders now work.

The ext.Bugzilla module could not load. It depended on jquery.ui.core, which core renamed to jquery.ui
(keeping jquery.ui.core only as a file inside it), so ResourceLoader refused the whole module with
Unknown module: jquery.ui.core. DataTables only needs jQuery, which is always present, so the dependency is
dropped rather than pointed at the now-deprecated jquery.ui. Also removed: 'position', no longer a
ResourceLoader option, and two declared messages that were never defined in the i18n file.

The module was queued from BeforePageDisplay on every page, which is what turned an unloadable module into a
wiki-wide console warning. It is queued from the tag renderer instead.

PHP 8.2 deprecations that have present-day consequences

Only the two that cost something today; nothing here is future-proofing against an unreleased PHP.

  • "${var}" interpolation in two templates. Those templates run inside the output buffer that becomes the tag's
    return value, so the notice is captured into the buffer rather than the log and is rendered into the article
    body
    above every table and every list.
  • The properties the classes assign are now declared. MediaWiki's PHPUnit configuration converts deprecations into
    errors, so without the declarations most of the tests below fail before asserting anything. They are declared
    public because the templates and BugzillaOutput read them from outside the declaring class.

Deprecated core APIs

ObjectCache::getInstance() becomes ObjectCacheFactory. StatusValue::getErrors() becomes the combined message
the Status already builds. catch ( MWException ) becomes catch ( Exception ): MWException is deprecated
since 1.40 and HttpRequestFactory does not throw it, so both catch blocks were unreachable and a DNS failure
escaped as an uncaught Error instead of rendering the error box the code intended.

Errors that were not reaching the reader

  • A failed query still populated the cache with its empty result. For the rest of the timeout every reader saw
    No results. with nothing to indicate the query had failed. A transient Bugzilla outage looked like an empty bug
    list for five minutes after Bugzilla recovered.
  • A 200 response that is not JSON (what a proxy in front of Bugzilla returns) decoded to null and was passed
    on unchanged, also reading as zero bugs.
  • display="number" ignored the error flag and counted data['bugs'] unconditionally. On any failed query
    that key is absent, so PHP 8 raised
    count(): Argument #1 ($value) must be of type Countable|array, null given and the page could not be saved.
  • error.tpl ran print_r without the return argument, echoing the value and then echoing print_r's own
    return value, appending a stray 1 to every error box. Combined with getErrors()[0] returning a raw array, an
    HTTP 404 reached the reader as a literal Array ( [type] => error [message] => http-bad-status ... )1. Messages
    are escaped now and read as There was a problem during the HTTP request: 404 Not Found.

Escaping and tag input

  • Field names reached HTML attributes unescaped. include_fields comes from the tag body, so any user who can
    edit a page picks them, and a name containing a quote closed the class attribute and turned the rest into
    markup. Tag output reaches the reader through the strip state, which MediaWiki does not sanitise. The <th> text
    and every bug value were already escaped, so the omission looks accidental rather than deliberate. This was
    unreachable while the fetch still fataled, which is why it surfaces with the port rather than before it.
  • list.tpl read $bug[$field] and $bug['status'] without checking they are there, which is a warning and an
    htmlspecialchars(null) deprecation per row whenever include_fields does not cover them. table.tpl already
    guards both.
  • A tag body that is valid JSON but not an object (<bugzilla>123</bugzilla>) reached the include_fields
    assignment with a scalar and threw an uncaught Error, so the page could not be saved or rendered. Only a body
    that json_decode could not read at all was rejected before.

Display modes with no implementation

display="inline" mapped to a BugzillaInline class that does not exist anywhere in the repository, so the tag
fataled with Class "BugzillaInline" not found. The mapping is dropped, so it takes the same path as any other
unrecognised display value: a table.

type="count" has had no reachable template since graphing support was removed. display is normalised to one of
list, number or table before the template path is built, so templates/count/bar.tpl and
templates/count/pie.tpl could never be selected, and both still referenced the $response->image the graphing
code used to set. They are removed; type="count" reports the invalid combination instead of pointing at dead
files.

$wgBugzillaJqueryTable

Setting this to true produced only ReferenceError: $ is not defined and a plain table: the DataTables bootstrap
was a bare $(document).ready(...) emitted into the head, before jQuery exists. It moves into the module as
web/js/bugzilla.init.js, where ResourceLoader supplies $, and attaches through the wikipage.content hook. The
dead $wgVersion < 1.17 branch that loaded the bundled jQuery 1.6.2 and jQuery UI 1.8.14 goes with it.

Tests

The suite could not run: its one class extended PHPUnit_Framework_TestCase (removed in PHPUnit 6) and
phpunit.xml declared the PHPUnit 4.5 schema. Classes now extend MediaWikiIntegrationTestCase,
phpunit.xml.dist moves to the extension root, and core's entry point picks the suite up:

composer phpunit:entrypoint -- extensions/Bugzilla/tests/phpunit

The existing prepare_options and rebase_fields cases are kept. New coverage targets what a rendered page cannot
show: that a failed query leaves nothing in the cache, that display="number" reports errors rather than counting
an absent result, that errors are escaped, and that the HTTP round trip returns what Bugzilla sent, reports a 200
carrying something other than JSON, and reports an HTTP error on one line. Dev dependencies were an abandoned
package name and a 2016 codesniffer; both now track the versions core uses. No runtime dependency is added.

Behaviour changes worth a maintainer's attention

  1. Bugzilla.php is gone, so require_once no longer loads the extension. Every existing install needs
    wfLoadExtension( 'Bugzilla' ); instead. This is deliberate rather than shimmed: extension.json requires
    MediaWiki 1.43, so a shim would not help anyone on an older version (ExtensionRegistry refuses the load
    however it was reached), and on 1.43 the extension fataled on any page containing a tag, so there is no working
    install whose entry point needs preserving. A wiki that upgrades without changing its config gets
    Failed opening required '.../Bugzilla.php' on every page. Worth noting for anyone monitoring by status code:
    that fatal is served as HTTP 200, not 500.
  2. The default $wgBugzillaRESTURL changes from https://bugzilla.mozilla.org/bzapi to .../rest. The
    bzapi proxy is gone and returns 404, so the shipped default cannot work against any current Bugzilla.
  3. $wgBugzillaExtVersion is removed. The version has a single home in extension.json and the REST
    User-Agent reads it from the registry.
  4. $wgBugzillaTable['lengthMenu'] reaches DataTables as an array, accepted either as the JSON array literal
    the default ships or as a PHP array. The shipped default is unchanged. $wgBugzillaTable uses the array_plus
    merge strategy, so a wiki setting one key keeps the shipped default for the other; $wgBugzillaDefaultFields
    uses provide_default, since replacing a flat list outright is what setting it means.
  5. Every query requests the default fields as well as those the page displays. The cache key is computed over
    that combined set, so queries for subsets of it share one entry. Building the request from the raw options
    instead meant pages could share a key while asking Bugzilla for different things, and whichever rendered first
    decided what the others showed. The request carries up to four extra fields as a result.

Considered, omitted

Left alone deliberately, each still present on the branch: the vendored jQuery 1.6.2 / jQuery UI 1.8.14 /
DataTables 1.8.1 under web/; bJQueryUI: true without the jQuery UI stylesheet that would theme it; Utils.php,
whose gChartExtendedEncode() has no callers and is never included; BugzillaBaseQuery::_update_cache(), which
calls an undefined $this->_getCache() and would fatal if anything reached it; the custom.css hook, which passes a
URL path to file_exists() and is therefore always false; 'ssl_verify_peer' and 'follow_redirects', which are
not MWHttpRequest option names and are silently ignored; and the cache key, which hashes query options without the
query type, so type=bug and type=count with identical options collide.

Verification

Exercised on a real MediaWiki 1.43.9 stack: display in {table, list, number} against
$wgBugzillaJqueryTable in {false, true}, in a custom namespace pair and in main, plus display="inline" and
type="count". Bug data came from bugzilla.mozilla.org/rest over anonymous GETs. The deliberate error paths
(unreachable host, HTTP error, non-JSON body, empty result set, API error payload, invalid tag JSON) and the
cache-masking behaviour were exercised against a local fixture server. Console checks were Playwright-driven
rather than hand-verified: clean on a page with no tag, and on tag pages with the jQuery table both off and on,
where DataTables initialises and paginates.

AI-authored — Claude Code, Opus 5 (1M context); detailed spec from @malberts, run unattended, then four rounds of scope correction (PHP 9 rationale dropped, entry point removed rather than shimmed); diff not yet human-reviewed; local PHPUnit suite green, three regression tests confirmed failing without their fixes, browser checks Playwright-driven; repo has no CI.

malberts added a commit that referenced this pull request Aug 13, 2026
Three ways an errored query failed to reach the reader as an error:

A query that failed still wrote its empty result into the cache, so for the
rest of the timeout every reader got 'No results.' with nothing to say the
query had failed at all. Only successful queries are cached now. Under the
default five minute timeout this made a transient Bugzilla outage look like
an empty bug list for five minutes after Bugzilla recovered.

display="number" ignored the error flag entirely and counted
$query->data['bugs'] unconditionally. On any failed query that key is
absent, so PHP 8 raised 'count(): Argument #1 ($value) must be of type
Countable|array, null given' and the page could not be saved or rendered. It
now renders the same error the other display modes do.

The error template ran its message through print_r without the return
argument, which echoes the value and then echoes print_r's own return value,
appending a stray '1' to every error box. Messages are escaped now, and the
print_r fallback only applies to values that are not strings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@malberts
malberts marked this pull request as ready for review August 13, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant