Skip to content

Add ParserBeforeInternalParse hook for empty text#284

Merged
physikerwelt merged 2 commits intomainfrom
physikerwelt-patch-1
Apr 10, 2026
Merged

Add ParserBeforeInternalParse hook for empty text#284
physikerwelt merged 2 commits intomainfrom
physikerwelt-patch-1

Conversation

@physikerwelt
Copy link
Copy Markdown
Member

@physikerwelt physikerwelt commented Apr 9, 2026

Summary by CodeRabbit

  • Improvements
    • Empty pages now show site-wide/global placeholder content during page loads, ensuring users see consistent content instead of a blank page.
    • Article warning behavior preserved and refined so warning messages display reliably while the empty-content handling has been streamlined.

Use earlier hook to be able to set displaytitle
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Shifted empty-content injection from the ArticleViewHeader hook to a new ParserBeforeInternalParse hook: when internal parse $text is exactly empty and the resolved page exists with CONTENT_MODEL_WIKITEXT, $text is replaced with {{global}}. ArticleViewHeader no longer handles empty-content injection; it retains namespace gating and {{ItemWarning|$name}} injection.

Changes

Cohort / File(s) Summary
Hook registration & parser-time injection
LocalSettings.d/production/hooks.php
Added ParserBeforeInternalParse hook that replaces an exactly-empty internal-parse $text with {{global}} when the resolved page exists and uses CONTENT_MODEL_WIKITEXT. Removed the earlier empty-content injection logic from the ArticleViewHeader handler; ArticleViewHeader now only does namespace checks and unconditional `{{ItemWarning

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit nibbles at the parse,
Finds empty text and hops to vars.
Earlier now, the global’s sewn,
View stays tidy, seeds well-grown —
Hooray for hooks and tiny stars! 🐇🌿

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a ParserBeforeInternalParse hook to handle empty text, which is the primary modification in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch physikerwelt-patch-1

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b94f7f and a0a06f2.

📒 Files selected for processing (1)
  • LocalSettings.d/production/hooks.php

Comment thread LocalSettings.d/production/hooks.php Outdated
Comment on lines +6 to +11
$wgHooks['ParserBeforeInternalParse'][] =
static function ( Parser &$parser, &$text ) {
if ( $text === '' ) {
$text = '{{global}}';
}
};
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
$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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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 return null in 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() === 120 explicitly 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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 models css or javascript. 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 ArticleViewHeader hook 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()`).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 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 a Title object (which carries getContentModel()) from the PageReference that getPage() returns — this cast returns null for 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_WIKITEXT check.

(◕ᴗ◕✿)


🌐 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() ) (or castFromPageReference() 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 of getTitle().
  • 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()`).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}}';
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@physikerwelt, that's a nicely refined solution! A few observations on the improvements over my proposal:

  1. $parser->getPage() is non-null: You're right that the IDE correctly identifies Parser::getPage() always returns a PageReference (never null), so the defensive null-check I added was unnecessary.
  2. Title::newFromPageReference() over castFromPageReference(): Since $page is guaranteed non-null here, newFromPageReference() is the correct choice — castFromPageReference() is for cases where the input itself might be null.
  3. !$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.
  4. 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.
@physikerwelt physikerwelt requested a review from eloiferrer April 10, 2026 10:55
@physikerwelt
Copy link
Copy Markdown
Member Author

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between a0a06f2 and e6bf00d.

📒 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() ) {
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.php

Repository: 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.notFound
Option 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.notFound

This 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()`).

@physikerwelt physikerwelt merged commit 1410c09 into main Apr 10, 2026
8 checks passed
@physikerwelt physikerwelt deleted the physikerwelt-patch-1 branch April 10, 2026 11:34
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.

2 participants