3 Commits

Author SHA1 Message Date
9bfed06466 fix: Defer DashboardProvider translations to avoid early textdomain loading (v0.4.8)
All checks were successful
Create Release Package / build-release (push) Successful in 54s
DashboardProvider constructor also had __() calls during plugins_loaded.
Applied same lazy-initialization pattern as Settings tab labels.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-07 11:44:06 +01:00
b605d0c299 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>
2026-02-07 11:39:25 +01:00
63660202c4 docs: Clarify translation compilation is handled by CI/CD
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 19:33:02 +01:00
6 changed files with 110 additions and 53 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 and DashboardProvider 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
@@ -79,11 +81,7 @@ Text domain: `wp-prometheus`
- `en_US` - English (United States) [base language - .pot template] - `en_US` - English (United States) [base language - .pot template]
- `de_CH` - German (Switzerland, formal) - `de_CH` - German (Switzerland, formal)
To compile translations to .mo files for production: Translation compilation (.po → .mo) is handled automatically by CI/CD pipeline during release builds. No local compilation needed.
```bash
for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done
```
### Create releases ### Create releases
@@ -292,6 +290,23 @@ 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
- Three classes needed fixing:
- `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
- `DashboardProvider.php`: Deferred built-in dashboard definitions (with `__()` calls) to a lazy `get_builtin_dashboards()` 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

@@ -53,7 +53,18 @@ class DashboardProvider {
*/ */
public function __construct() { public function __construct() {
$this->dashboard_dir = WP_PROMETHEUS_PATH . 'assets/dashboards/'; $this->dashboard_dir = WP_PROMETHEUS_PATH . 'assets/dashboards/';
}
/**
* Get built-in dashboard definitions.
*
* Lazily initializes dashboard labels to avoid triggering textdomain loading
* before the 'init' action (required since WordPress 6.7).
*
* @return array
*/
private function get_builtin_dashboards(): array {
if ( empty( $this->builtin_dashboards ) ) {
$this->builtin_dashboards = array( $this->builtin_dashboards = array(
'wordpress-overview' => array( 'wordpress-overview' => array(
'title' => __( 'WordPress Overview', 'wp-prometheus' ), 'title' => __( 'WordPress Overview', 'wp-prometheus' ),
@@ -78,6 +89,8 @@ class DashboardProvider {
), ),
); );
} }
return $this->builtin_dashboards;
}
/** /**
* Register a third-party dashboard. * Register a third-party dashboard.
@@ -106,7 +119,7 @@ class DashboardProvider {
} }
// Check for duplicate slugs (built-in takes precedence). // Check for duplicate slugs (built-in takes precedence).
if ( isset( $this->builtin_dashboards[ $slug ] ) ) { if ( isset( $this->get_builtin_dashboards()[ $slug ] ) ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( "WP Prometheus: Dashboard slug '$slug' conflicts with built-in dashboard" ); error_log( "WP Prometheus: Dashboard slug '$slug' conflicts with built-in dashboard" );
@@ -273,7 +286,7 @@ class DashboardProvider {
$available = array(); $available = array();
// Add built-in dashboards (check file exists). // Add built-in dashboards (check file exists).
foreach ( $this->builtin_dashboards as $slug => $dashboard ) { foreach ( $this->get_builtin_dashboards() as $slug => $dashboard ) {
$file_path = $this->dashboard_dir . $dashboard['file']; $file_path = $this->dashboard_dir . $dashboard['file'];
if ( file_exists( $file_path ) ) { if ( file_exists( $file_path ) ) {
$available[ $slug ] = $dashboard; $available[ $slug ] = $dashboard;
@@ -306,8 +319,9 @@ class DashboardProvider {
$slug = sanitize_file_name( $slug ); $slug = sanitize_file_name( $slug );
// Check built-in dashboards first. // Check built-in dashboards first.
if ( isset( $this->builtin_dashboards[ $slug ] ) ) { $builtin = $this->get_builtin_dashboards();
$dashboard = $this->builtin_dashboards[ $slug ]; if ( isset( $builtin[ $slug ] ) ) {
$dashboard = $builtin[ $slug ];
$file_path = $this->dashboard_dir . $dashboard['file']; $file_path = $this->dashboard_dir . $dashboard['file'];
// Security: Ensure file is within dashboard directory. // Security: Ensure file is within dashboard directory.
@@ -377,8 +391,9 @@ class DashboardProvider {
$slug = sanitize_file_name( $slug ); $slug = sanitize_file_name( $slug );
if ( isset( $this->builtin_dashboards[ $slug ] ) ) { $builtin = $this->get_builtin_dashboards();
return $this->builtin_dashboards[ $slug ]; if ( isset( $builtin[ $slug ] ) ) {
return $builtin[ $slug ];
} }
if ( isset( $this->registered_dashboards[ $slug ] ) ) { if ( isset( $this->registered_dashboards[ $slug ] ) ) {
@@ -401,8 +416,9 @@ class DashboardProvider {
$slug = sanitize_file_name( $slug ); $slug = sanitize_file_name( $slug );
// Built-in dashboards have predefined filenames. // Built-in dashboards have predefined filenames.
if ( isset( $this->builtin_dashboards[ $slug ] ) ) { $builtin = $this->get_builtin_dashboards();
return $this->builtin_dashboards[ $slug ]['file']; if ( isset( $builtin[ $slug ] ) ) {
return $builtin[ $slug ]['file'];
} }
// Registered dashboards - use file basename or generate from slug. // Registered dashboards - use file basename or generate from slug.

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 {
$tabs = $this->get_tabs();
$tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license'; $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'license';
return array_key_exists( $tab, $this->tabs ) ? $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.