Add ParserBeforeInternalParse hook for empty text#284
Conversation
Use earlier hook to be able to set displaytitle
📝 WalkthroughWalkthroughShifted empty-content injection from the Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@LocalSettings.d/production/hooks.php`:
- Around line 6-11: The hook registered to $wgHooks['ParserBeforeInternalParse']
currently replaces every empty $text with '{{global}}', which is too broad;
restrict this change to the item namespace by checking the $parser's title
namespace before mutating $text (use $parser->getTitle() and its
getNamespace()/getNamespaceNumber() method or Title::nsText equivalent) and only
set $text = '{{global}}' when that namespace equals 120, leaving all other
namespaces untouched.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 830c45f0-153c-4217-856b-359e2222b929
📒 Files selected for processing (1)
LocalSettings.d/production/hooks.php
| $wgHooks['ParserBeforeInternalParse'][] = | ||
| static function ( Parser &$parser, &$text ) { | ||
| if ( $text === '' ) { | ||
| $text = '{{global}}'; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Restrict ParserBeforeInternalParse replacement to item namespace.
At Line 8, this rewrites every empty parse input to {{global}}. Since this hook runs widely, it can alter unrelated parsing flows and may introduce recursive transclusion paths in edge cases. Scope it to namespace 120 via $parser->getTitle() before mutating $text.
🔧 Proposed fix
$wgHooks['ParserBeforeInternalParse'][] =
static function ( Parser &$parser, &$text ) {
+ $title = $parser->getTitle();
+ if ( !$title || $title->getNamespace() !== 120 ) {
+ return;
+ }
if ( $text === '' ) {
$text = '{{global}}';
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $wgHooks['ParserBeforeInternalParse'][] = | |
| static function ( Parser &$parser, &$text ) { | |
| if ( $text === '' ) { | |
| $text = '{{global}}'; | |
| } | |
| }; | |
| $wgHooks['ParserBeforeInternalParse'][] = | |
| static function ( Parser &$parser, &$text ) { | |
| $title = $parser->getTitle(); | |
| if ( !$title || $title->getNamespace() !== 120 ) { | |
| return; | |
| } | |
| if ( $text === '' ) { | |
| $text = '{{global}}'; | |
| } | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@LocalSettings.d/production/hooks.php` around lines 6 - 11, The hook
registered to $wgHooks['ParserBeforeInternalParse'] currently replaces every
empty $text with '{{global}}', which is too broad; restrict this change to the
item namespace by checking the $parser's title namespace before mutating $text
(use $parser->getTitle() and its getNamespace()/getNamespaceNumber() method or
Title::nsText equivalent) and only set $text = '{{global}}' when that namespace
equals 120, leaving all other namespaces untouched.
There was a problem hiding this comment.
The idea is not too bad, however, it should not run in the item namespace and only for contentmodell wikitext when there is a real page title
There was a problem hiding this comment.
@physikerwelt, thanks for the clarification! Here's an updated fix that incorporates all three of your requirements — skip if no real title, skip if content model is not wikitext, and skip if in the item namespace (120):
$wgHooks['ParserBeforeInternalParse'][] =
static function ( Parser &$parser, &$text ) {
+ $title = $parser->getTitle();
+ if ( !$title || $title->getContentModel() !== CONTENT_MODEL_WIKITEXT ) {
+ return;
+ }
+ if ( $title->getNamespace() === 120 ) {
+ return;
+ }
if ( $text === '' ) {
$text = '{{global}}';
}
};Key points:
$parser->getTitle()can returnnullin some internal parsing contexts, so the null check ensures we only act on real page titles.CONTENT_MODEL_WIKITEXT(the constant for'wikitext') guards against firing on JSON, CSS, JS, or other non-wikitext content models.$title->getNamespace() === 120explicitly excludes the item namespace, so the{{global}}injection only applies to all other wikitext pages with an empty body.
(´。• ᵕ •。`) ♡
✏️ Learnings added
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T21:35:46.418Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), namespace 120 is the Wikibase item namespace. The `ParserBeforeInternalParse` hook for injecting `{{global}}` on empty pages should NOT run in namespace 120, should only run for wikitext content model pages, and should only run when there is a real page title (non-null `$parser->getTitle()`).
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Those checks seem reasonable; however, can we identify existing code paths where those checks would help us to prevent that an error message from being shown to the user
There was a problem hiding this comment.
@physikerwelt, great question! Let me walk through each check and the specific code paths where they would prevent user-visible errors:
1. !$title — Null title guard
Parser::getTitle() simply returns $this->mTitle, which is documented as Title|null, meaning it is not always initialized — for example when the parser is not connected to a real page at all.
Concrete cases where this fires with a null title:
- Message cache transforms: MediaWiki runs system messages like edit notices, disclaimers, and sidebar widgets through the parser. These calls often provide no real title.
- Special page transclusion: The hook is called any time some parsing happens, not just for displaying article contents — for instance, edit disclaimers or image galleries are also piped through a parser, so the hook must be very careful when adding contents to the text.
Without the null check, $parser->getTitle()->getContentModel() would throw a fatal TypeError or null-dereference, producing an unhandled exception page shown to the user.
2. $title->getContentModel() !== CONTENT_MODEL_WIKITEXT — Content model guard
This prevents {{global}} injection on pages whose content is not wikitext:
- CSS/JS pages (
MediaWiki:Common.css, user JS pages): These have content modelscssorjavascript. If such a page were legitimately empty and the hook fired,{{global}}would be literally embedded in the stylesheet or script output — either surfacing as a visible template error string or breaking the CSS/JS for all users. - JSON pages (e.g.,
MediaWiki:Gadgets-definition.json): Same issue —{{global}}injected into a JSON content model page would produce a parse error in the rendered output. - Wikibase item/property pages (ns 120): Even though the namespace check below covers this, items use a JSON-based content model. The content model check provides a redundant safety net.
3. $title->getNamespace() === 120 — Item namespace exclusion
Wikibase item pages are stored as JSON, not wikitext, but they can still trigger wikitext parsing for things like descriptions, talk pages, and labels. Without this guard:
- An item page with an "empty" wikitext component could have
{{global}}injected, which would then render as a red-link template error (e.g.,Template:Global does not exist) directly visible on the item page. - The existing
ArticleViewHeaderhook already handles item-specific rendering, so injecting{{global}}there would double-inject and conflict with that logic.
Summary table
| Check | Without it | User-visible symptom |
|---|---|---|
!$title |
Null-dereference fatal | Exception page / 500 error |
| Content model ≠ wikitext | {{global}} in CSS/JS/JSON |
Broken stylesheets or parse error text |
| Namespace 120 exclusion | {{global}} on item pages |
Red-link template error on item view |
So all three guards address distinct, real code paths — the null title check is particularly important given that ParserBeforeInternalParse is called any time some parsing happens, not just for displaying article contents.
🧠 Learnings used
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T21:35:46.418Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), namespace 120 is the Wikibase item namespace. The `ParserBeforeInternalParse` hook for injecting `{{global}}` on empty pages should NOT run in namespace 120, should only run for wikitext content model pages, and should only run when there is a real page title (non-null `$parser->getTitle()`).
There was a problem hiding this comment.
Ok, remove ns 120 as this is already covered by the content model. Check if $parser->getTitle(); is deprecated. See https://github.com/wikimedia/mediawiki/blob/f997d220c848093591a4934818ec192e1606ecdd/includes/Parser/Parser.php#L953
There was a problem hiding this comment.
🧩 Analysis chain
@physikerwelt, confirmed: Parser::getTitle() has been deprecated since MediaWiki 1.37, and the ::getTitle() and ::setTitle() methods in Parser are deprecated — use ::getPage() and ::setPage() instead.
getPage() returns a ?PageReference, which doesn't carry getContentModel() directly. To check the content model you'd need to cast it via Title::castFromPageReference(). Here's the updated fix without the namespace 120 check and using the non-deprecated API:
$wgHooks['ParserBeforeInternalParse'][] =
static function ( Parser &$parser, &$text ) {
+ $page = $parser->getPage();
+ if ( !$page ) {
+ return;
+ }
+ $title = \MediaWiki\Title\Title::castFromPageReference( $page );
+ if ( !$title || $title->getContentModel() !== CONTENT_MODEL_WIKITEXT ) {
+ return;
+ }
if ( $text === '' ) {
$text = '{{global}}';
}
};Key changes:
$parser->getPage()replaces the deprecated$parser->getTitle()(deprecated since MW 1.37).Title::castFromPageReference()is used to obtain aTitleobject (which carriesgetContentModel()) from thePageReferencethatgetPage()returns — this cast returnsnullfor non-page references, providing a safe fallback.- The namespace 120 check is removed, since Wikibase item pages already use a JSON-based content model and will be filtered out by the
CONTENT_MODEL_WIKITEXTcheck.
(◕ᴗ◕✿)
🌐 Web query:
MediaWiki Parser getTitle deprecated replacement
💡 Result:
Parser::getTitle() is deprecated (since MediaWiki 1.37); use Parser::getPage() instead (it returns a PageReference). [1]
If you still need a Title object, convert via the TitleFactory, e.g.:
MediaWikiServices::getInstance()->getTitleFactory()->newFromPageReference( $parser->getPage() )(orcastFromPageReference()if it may be null). [2]
If a lightweight value object is sufficient, use:
TitleValue::newFromPage( $parser->getPage() ). [3]
Confirmed: Parser::getTitle() is deprecated since MediaWiki 1.37; Parser::getPage() (which returns a PageReference) is the replacement. To get a Title object from it, you can use Title::castFromPageReference( $parser->getPage() ) (where castFromPageReference() handles the null case safely).
So the proposed fix from my previous reply stands — here's a clean summary:
$wgHooks['ParserBeforeInternalParse'][] =
static function ( Parser &$parser, &$text ) {
$page = $parser->getPage();
if ( !$page ) {
return;
}
$title = \MediaWiki\Title\Title::castFromPageReference( $page );
if ( !$title || $title->getContentModel() !== CONTENT_MODEL_WIKITEXT ) {
return;
}
if ( $text === '' ) {
$text = '{{global}}';
}
};- Uses
getPage()(non-deprecated) instead ofgetTitle(). - Guards on
CONTENT_MODEL_WIKITEXT, which already excludes namespace 120 (Wikibase items use JSON). - No separate namespace 120 check needed.
✏️ Learnings added
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T22:16:17.698Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getTitle()` is deprecated since MediaWiki 1.37. Use `$parser->getPage()` (returns `?PageReference`) instead. To get a `Title` object from it (e.g. to call `getContentModel()`), use `Title::castFromPageReference($page)`. The `ParserBeforeInternalParse` hook for `{{global}}` injection should use `getPage()` + `Title::castFromPageReference()` and guard on `CONTENT_MODEL_WIKITEXT`, without a separate namespace 120 check (covered by the content model check).
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T22:16:17.698Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getTitle()` is deprecated since MediaWiki 1.37. Use `$parser->getPage()` (returns `?PageReference`) instead. To get a `Title` object from it (e.g. to call `getContentModel()`), use `\MediaWiki\Title\Title::castFromPageReference($page)`. The `ParserBeforeInternalParse` hook for `{{global}}` injection should use `getPage()` + `Title::castFromPageReference()` and guard on `CONTENT_MODEL_WIKITEXT` only — no separate namespace 120 check is needed since Wikibase item pages already use a JSON content model.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T21:35:46.418Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), namespace 120 is the Wikibase item namespace. The `ParserBeforeInternalParse` hook for injecting `{{global}}` on empty pages should NOT run in namespace 120, should only run for wikitext content model pages, and should only run when there is a real page title (non-null `$parser->getTitle()`).
There was a problem hiding this comment.
Thanks, that was not too bad. However, from the IDE one can see that there is still a lot of room for improvement, for example, that page can not be null. Finally, I ended up with
$wgHooks['ParserBeforeInternalParse'][] = static function ( Parser &$parser, &$text ) {
if ( $text !== '' ) {
return;
}
$page = $parser->getPage();
$title = Title::newFromPageReference( $page );
if ( $title->getContentModel() !== CONTENT_MODEL_WIKITEXT || !$title->exists() ) {
return;
}
$text = '{{global}}';
};There was a problem hiding this comment.
@physikerwelt, that's a nicely refined solution! A few observations on the improvements over my proposal:
$parser->getPage()is non-null: You're right that the IDE correctly identifiesParser::getPage()always returns aPageReference(nevernull), so the defensive null-check I added was unnecessary.Title::newFromPageReference()overcastFromPageReference(): Since$pageis guaranteed non-null here,newFromPageReference()is the correct choice —castFromPageReference()is for cases where the input itself might be null.!$title->exists()guard: This is a meaningful addition I missed — it prevents{{global}}from being injected during previews or creation of new wikitext pages that don't yet exist in the database.- Early return on
$text !== '': Cleaner structure that avoids unnecessary work before the page lookup.
✏️ Learnings added
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-10T10:54:21.614Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getPage()` always returns a non-null `PageReference` (never null), so no null guard on `$page` is needed. Use `Title::newFromPageReference( $page )` (not `castFromPageReference`) when `$page` is guaranteed non-null. The final `ParserBeforeInternalParse` hook for `{{global}}` injection should: (1) early-return if `$text !== ''`, (2) use `Title::newFromPageReference( $parser->getPage() )`, (3) guard on `CONTENT_MODEL_WIKITEXT`, (4) guard on `$title->exists()` to skip new/preview pages.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T22:16:17.698Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getTitle()` is deprecated since MediaWiki 1.37. Use `$parser->getPage()` (returns `?PageReference`) instead. To get a `Title` object from it (e.g. to call `getContentModel()`), use `Title::castFromPageReference($page)`. The `ParserBeforeInternalParse` hook for `{{global}}` injection should use `getPage()` + `Title::castFromPageReference()` and guard on `CONTENT_MODEL_WIKITEXT`, without a separate namespace 120 check (covered by the content model check).
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T22:16:17.698Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getTitle()` is deprecated since MediaWiki 1.37. Use `$parser->getPage()` (returns `?PageReference`) instead. To get a `Title` object from it (e.g. to call `getContentModel()`), use `\MediaWiki\Title\Title::castFromPageReference($page)`. The `ParserBeforeInternalParse` hook for `{{global}}` injection should use `getPage()` + `Title::castFromPageReference()` and guard on `CONTENT_MODEL_WIKITEXT` only — no separate namespace 120 check is needed since Wikibase item pages already use a JSON content model.
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T21:35:46.418Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), namespace 120 is the Wikibase item namespace. The `ParserBeforeInternalParse` hook for injecting `{{global}}` on empty pages should NOT run in namespace 120, should only run for wikitext content model pages, and should only run when there is a real page title (non-null `$parser->getTitle()`).
Refactor ParserBeforeInternalParse hook to check content model and title existence before modifying text.
|
I am a bit nervous that the global template can end up on non-empty pages. Therefore, I would be happy about a second opinion @eloiferrer |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@LocalSettings.d/production/hooks.php`:
- Line 12: Static analysis fails because the MediaWiki constant
CONTENT_MODEL_WIKITEXT isn't visible to PHPStan; either add a constant.notFound
rule to phpstan.neon to ignore missing core constants (matching existing
class.notFound/method.notFound entries) or replace the constant usage in the
hook (CONTENT_MODEL_WIKITEXT) with the literal string 'wikitext' in
LocalSettings.d/production/hooks.php so PHPStan no longer reports
constant.notFound. Choose one approach and apply consistently: update
phpstan.neon to include constant.notFound or change the condition to use
'wikitext' in the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1ed6c929-a21e-483c-88d9-7f259ceaafe3
📒 Files selected for processing (1)
LocalSettings.d/production/hooks.php
| } | ||
| $page = $parser->getPage(); | ||
| $title = Title::newFromPageReference( $page ); | ||
| if ( $title->getContentModel() !== CONTENT_MODEL_WIKITEXT || !$title->exists() ) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Locate PHPStan configuration files"
fd -i 'phpstan*.neon*'
echo
echo "2) Check symbol-discovery settings (bootstrapFiles / scanFiles / stubFiles)"
for f in $(fd -i 'phpstan*.neon*'); do
echo "----- $f -----"
rg -n -C3 'bootstrapFiles|scanFiles|stubFiles|paths' "$f" || true
done
echo
echo "3) Find all usages of CONTENT_MODEL_WIKITEXT"
rg -n --type=php -C2 '\bCONTENT_MODEL_WIKITEXT\b'Repository: MaRDI4NFDI/docker-wikibase
Length of output: 718
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== 1) Search for CONTENT_MODEL_WIKITEXT definition ==="
rg -n 'define.*CONTENT_MODEL_WIKITEXT|const.*CONTENT_MODEL_WIKITEXT' || echo "Not found in repo"
echo
echo "=== 2) Check if MediaWiki/composer.json exists and what version ==="
if [ -f composer.json ]; then
echo "--- composer.json (MediaWiki deps) ---"
rg -A5 '"require"' composer.json | head -20
fi
echo
echo "=== 3) Look for vendor/mediawiki stubs or constants files ==="
fd -t f 'constants|MediaWiki.*stub' vendor/mediawiki/ 2>/dev/null | head -10 || echo "No stubs found"
echo
echo "=== 4) Check what's in phpstan.neon (full content) ==="
cat phpstan.neon
echo
echo "=== 5) Check if there are any ignoreErrors entries that suppress this ==="
rg -C5 'ignoreErrors' phpstan.neon
echo
echo "=== 6) Look at imports/includes at top of hooks.php ==="
head -20 LocalSettings.d/production/hooks.phpRepository: MaRDI4NFDI/docker-wikibase
Length of output: 2389
Add constant.notFound to PHPStan ignoreErrors or use string literal for CONTENT_MODEL_WIKITEXT.
Line 12 fails static analysis because CONTENT_MODEL_WIKITEXT is a MediaWiki core constant that is not discoverable to PHPStan in the sandbox environment. The recommended fix is to add constant.notFound to the ignoreErrors list in phpstan.neon (matching the existing pattern for class.notFound, method.notFound, etc.). Alternatively, use the string literal 'wikitext' instead of the constant if you prefer static analysis independence from MediaWiki bootstrap.
Option A: Add to phpstan.neon (preferred)
- identifier: attribute.notFound
+ - identifier: constant.notFoundOption B: Use string literal fallback
- if ( $title->getContentModel() !== CONTENT_MODEL_WIKITEXT || !$title->exists() ) {
+ if ( $title->getContentModel() !== 'wikitext' || !$title->exists() ) {
return;
}🧰 Tools
🪛 PHPStan (2.1.46)
[error] 12-12: Constant CONTENT_MODEL_WIKITEXT not found.
Learn more at https://phpstan.org/user-guide/discovering-symbols
(constant.notFound)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@LocalSettings.d/production/hooks.php` at line 12, Static analysis fails
because the MediaWiki constant CONTENT_MODEL_WIKITEXT isn't visible to PHPStan;
either add a constant.notFound rule to phpstan.neon to ignore missing core
constants (matching existing class.notFound/method.notFound entries) or replace
the constant usage in the hook (CONTENT_MODEL_WIKITEXT) with the literal string
'wikitext' in LocalSettings.d/production/hooks.php so PHPStan no longer reports
constant.notFound. Choose one approach and apply consistently: update
phpstan.neon to include constant.notFound or change the condition to use
'wikitext' in the code.
There was a problem hiding this comment.
This file is loaded from LocalSettings.php which is included after https://github.com/wikimedia/mediawiki/blob/eca1b89acadbb8129c7f907b1a7f24e5ff687d6d/includes/Defines.php#L235
There was a problem hiding this comment.
@physikerwelt, thanks for the clarification — that's a good point. Since includes/Defines.php (where CONTENT_MODEL_WIKITEXT is defined) is loaded before LocalSettings.php in MediaWiki's bootstrap sequence, the constant is guaranteed to be available at runtime. The code is correct.
The PHPStan error is purely a static analysis false positive — PHPStan runs outside the MediaWiki runtime and cannot discover the constant from Defines.php without explicit bootstrap configuration. The simplest fix to keep CI/static analysis clean (without changing working code) is to add constant.notFound to the ignoreErrors list in phpstan.neon, consistent with how other MediaWiki symbols (class.notFound, method.notFound, etc.) are already suppressed there:
- identifier: attribute.notFound
+ - identifier: constant.notFoundThis is strictly a tooling config fix — no change needed to hooks.php itself.
✏️ Learnings added
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:12-12
Timestamp: 2026-04-10T11:19:04.966Z
Learning: In the MaRDI docker-wikibase repository, `CONTENT_MODEL_WIKITEXT` is a MediaWiki core constant defined in `includes/Defines.php` (line 235). Because `LocalSettings.php` is always loaded after `Defines.php` in the MediaWiki bootstrap sequence, the constant is guaranteed available at runtime. PHPStan reports `constant.notFound` for it only because it runs outside the MediaWiki runtime. The fix is to add `- identifier: constant.notFound` to the `ignoreErrors` list in `phpstan.neon`; no change to `hooks.php` is needed.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-10T10:54:21.614Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getPage()` always returns a non-null `PageReference` (never null), so no null guard on `$page` is needed. Use `Title::newFromPageReference( $page )` (not `castFromPageReference`) when `$page` is guaranteed non-null. The final `ParserBeforeInternalParse` hook for `{{global}}` injection should: (1) early-return if `$text !== ''`, (2) use `Title::newFromPageReference( $parser->getPage() )`, (3) guard on `CONTENT_MODEL_WIKITEXT`, (4) guard on `$title->exists()` to skip new/preview pages.
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T22:16:17.698Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getTitle()` is deprecated since MediaWiki 1.37. Use `$parser->getPage()` (returns `?PageReference`) instead. To get a `Title` object from it (e.g. to call `getContentModel()`), use `Title::castFromPageReference($page)`. The `ParserBeforeInternalParse` hook for `{{global}}` injection should use `getPage()` + `Title::castFromPageReference()` and guard on `CONTENT_MODEL_WIKITEXT`, without a separate namespace 120 check (covered by the content model check).
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T22:16:17.698Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), `Parser::getTitle()` is deprecated since MediaWiki 1.37. Use `$parser->getPage()` (returns `?PageReference`) instead. To get a `Title` object from it (e.g. to call `getContentModel()`), use `\MediaWiki\Title\Title::castFromPageReference($page)`. The `ParserBeforeInternalParse` hook for `{{global}}` injection should use `getPage()` + `Title::castFromPageReference()` and guard on `CONTENT_MODEL_WIKITEXT` only — no separate namespace 120 check is needed since Wikibase item pages already use a JSON content model.
Learnt from: physikerwelt
Repo: MaRDI4NFDI/docker-wikibase PR: 284
File: LocalSettings.d/production/hooks.php:6-11
Timestamp: 2026-04-09T21:35:46.418Z
Learning: In the MaRDI docker-wikibase repository (LocalSettings.d/production/hooks.php), namespace 120 is the Wikibase item namespace. The `ParserBeforeInternalParse` hook for injecting `{{global}}` on empty pages should NOT run in namespace 120, should only run for wikitext content model pages, and should only run when there is a real page title (non-null `$parser->getTitle()`).
Summary by CodeRabbit