fix: Defer textdomain loading to init action for WordPress 6.7+ compatibility (v0.4.8)
All checks were successful
Create Release Package / build-release (push) Successful in 1m1s

Fixes _load_textdomain_just_in_time notice and headers already sent warnings
on admin pages by deferring load_plugin_textdomain() and Settings tab label
initialization to the init action instead of plugins_loaded.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-02-07 11:39:25 +01:00
parent 63660202c4
commit b605d0c299
5 changed files with 61 additions and 17 deletions

View File

@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.8] - 2026-02-07
### Fixed
- Fixed `_load_textdomain_just_in_time` notice on admin pages (WordPress 6.7+ compatibility)
- Deferred `load_plugin_textdomain()` to `init` action instead of `plugins_loaded`
- Deferred Settings tab label initialization to avoid early translation loading
## [0.4.7] - 2026-02-03 ## [0.4.7] - 2026-02-03
### Added ### Added

View File

@@ -34,7 +34,9 @@ This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase w
**Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file. **Note for AI Assistants:** Clean this section after the specific features are done or new releases are made. Effective changes are tracked in `CHANGELOG.md`. Do not add completed versions here - document them in the Session History section at the end of this file.
*No pending roadmap items.* ### Known Bugs
*No known bugs at this time.*
## Technical Stack ## Technical Stack
@@ -288,6 +290,22 @@ add_action( 'wp_prometheus_collect_metrics', function( $collector ) {
## Session History ## Session History
### 2026-02-07 - Fix Early Textdomain Loading (v0.4.8)
- Fixed `_load_textdomain_just_in_time` warning on admin pages (WordPress 6.7+ compatibility)
- Root cause: `load_plugin_textdomain()` was called during `plugins_loaded` in `Plugin::__construct()`
- WordPress 6.7+ requires textdomain loading at the `init` action or later
- Two changes made:
- `Plugin.php`: Deferred `load_textdomain()` to `init` action hook, changed method visibility to public
- `Settings.php`: Deferred tab label initialization (which uses `__()`) to a lazy `get_tabs()` method
- Cleared Known Bugs section — no remaining known issues
- **Key Learning**: WordPress 6.7 textdomain loading requirements
- `load_plugin_textdomain()` must be called at `init` or later
- WordPress's JIT textdomain loader (`_load_textdomain_just_in_time`) also triggers too-early warnings
- Any `__()` / `_e()` calls before `init` for a plugin textdomain will trigger the notice
- The warning causes "headers already sent" errors because the notice output breaks header modifications
- Solution: Defer both explicit `load_plugin_textdomain()` and any `__()` calls to `init` or later hooks
### 2026-02-03 - Database Query Timing Documentation (v0.4.7) ### 2026-02-03 - Database Query Timing Documentation (v0.4.7)
- Added database query duration distribution panel to Grafana Runtime dashboard - Added database query duration distribution panel to Grafana Runtime dashboard

View File

@@ -49,15 +49,6 @@ class Settings {
* Constructor. * Constructor.
*/ */
public function __construct() { public function __construct() {
$this->tabs = array(
'license' => __( 'License', 'wp-prometheus' ),
'metrics' => __( 'Metrics', 'wp-prometheus' ),
'storage' => __( 'Storage', 'wp-prometheus' ),
'custom' => __( 'Custom Metrics', 'wp-prometheus' ),
'dashboards' => __( 'Dashboards', 'wp-prometheus' ),
'help' => __( 'Help', 'wp-prometheus' ),
);
$this->metric_builder = new CustomMetricBuilder(); $this->metric_builder = new CustomMetricBuilder();
$this->dashboard_provider = new DashboardProvider(); $this->dashboard_provider = new DashboardProvider();
@@ -76,14 +67,37 @@ class Settings {
add_action( 'wp_ajax_wp_prometheus_test_storage', array( $this, 'ajax_test_storage' ) ); add_action( 'wp_ajax_wp_prometheus_test_storage', array( $this, 'ajax_test_storage' ) );
} }
/**
* Get available tabs.
*
* Lazily initializes tab labels to avoid triggering textdomain loading
* before the 'init' action (required since WordPress 6.7).
*
* @return array
*/
private function get_tabs(): array {
if ( empty( $this->tabs ) ) {
$this->tabs = array(
'license' => __( 'License', 'wp-prometheus' ),
'metrics' => __( 'Metrics', 'wp-prometheus' ),
'storage' => __( 'Storage', 'wp-prometheus' ),
'custom' => __( 'Custom Metrics', 'wp-prometheus' ),
'dashboards' => __( 'Dashboards', 'wp-prometheus' ),
'help' => __( 'Help', 'wp-prometheus' ),
);
}
return $this->tabs;
}
/** /**
* Get current tab. * Get current tab.
* *
* @return string * @return string
*/ */
private function get_current_tab(): string { private function get_current_tab(): string {
$tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license'; $tabs = $this->get_tabs();
return array_key_exists( $tab, $this->tabs ) ? $tab : 'license'; $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license';
return array_key_exists( $tab, $tabs ) ? $tab : 'license';
} }
/** /**
@@ -268,7 +282,7 @@ class Settings {
?> ?>
<nav class="nav-tab-wrapper wp-clearfix"> <nav class="nav-tab-wrapper wp-clearfix">
<?php <?php
foreach ( $this->tabs as $tab_id => $tab_name ) { foreach ( $this->get_tabs() as $tab_id => $tab_name ) {
$tab_url = add_query_arg( $tab_url = add_query_arg(
array( array(
'page' => 'wp-prometheus', 'page' => 'wp-prometheus',

View File

@@ -57,7 +57,9 @@ final class Plugin {
private function __construct() { private function __construct() {
$this->init_components(); $this->init_components();
$this->init_hooks(); $this->init_hooks();
$this->load_textdomain();
// Defer textdomain loading to 'init' action (required since WordPress 6.7).
add_action( 'init', array( $this, 'load_textdomain' ) );
} }
/** /**
@@ -144,9 +146,11 @@ final class Plugin {
/** /**
* Load plugin textdomain. * Load plugin textdomain.
* *
* Hooked to 'init' action to comply with WordPress 6.7+ requirements.
*
* @return void * @return void
*/ */
private function load_textdomain(): void { public function load_textdomain(): void {
load_plugin_textdomain( load_plugin_textdomain(
'wp-prometheus', 'wp-prometheus',
false, false,

View File

@@ -3,7 +3,7 @@
* Plugin Name: WP Prometheus * Plugin Name: WP Prometheus
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus * Plugin URI: https://src.bundespruefstelle.ch/magdev/wp-prometheus
* Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics. * Description: Prometheus metrics endpoint for WordPress with extensible hooks for custom metrics.
* Version: 0.4.7 * Version: 0.4.8
* Requires at least: 6.4 * Requires at least: 6.4
* Requires PHP: 8.3 * Requires PHP: 8.3
* Author: Marco Graetsch * Author: Marco Graetsch
@@ -169,7 +169,7 @@ wp_prometheus_early_metrics_check();
* *
* @var string * @var string
*/ */
define( 'WP_PROMETHEUS_VERSION', '0.4.7' ); define( 'WP_PROMETHEUS_VERSION', '0.4.8' );
/** /**
* Plugin file path. * Plugin file path.