# WP Prometheus **Author:** Marco Graetsch **Author URL:** **Author Email:** **Repository URL:** **Issues URL:** ## Project Overview This plugin provides a Prometheus `/metrics` endpoint and an extensible way to add your own metrics in third-party plugins using hooks. It adds some default metrics like number of active accounts, number of articles, comments, and plugin status. The default metrics can be activated/deactivated in the plugin settings. ### Features - Prometheus compatible authenticated `/metrics` endpoint - Optional default metrics (users, posts, comments, plugins) - Dedicated plugin settings under 'Settings/Metrics' menu - Extensible by other plugins using `wp_prometheus_collect_metrics` action hook - License management integration ### Key Fact: 100% AI-Generated This project is proudly **"vibe-coded"** using Claude.AI - the entire codebase was created through AI assistance. ## Temporary Roadmap **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. ### Version 0.1.0 (Planned) - Add request/response timing metrics - Add HTTP status code counters - Add database query metrics ## Technical Stack - **Language:** PHP 8.3.x - **PHP-Standards:** PSR-4 - **Framework:** Latest WordPress Plugin API - **Styling:** Custom CSS - **Dependency Management:** Composer - **Internationalization:** WordPress i18n (.pot/.po/.mo files) - **Canonical Plugin Name:** `wp-prometheus` - **License Client:** `magdev/wc-licensed-product-client` Have a look at for a working admin integration - **Prometheus Client:** `promphp/prometheus_client_php` ### Security Best Practices - All user inputs are sanitized (integers for quantities/prices) - Nonce verification on form submissions - Output escaping in templates (`esc_attr`, `esc_html`, `esc_js`) - Direct file access prevention via `ABSPATH` check - XSS-safe DOM construction in JavaScript (no `innerHTML` with user data) - SQL injection prevention using `$wpdb->prepare()` throughout ### Translation Ready All user-facing strings use: ```php __('Text to translate', 'wp-prometheus') _e('Text to translate', 'wp-prometheus') ``` Text domain: `wp-prometheus` #### Translation Template - Base `.pot` file created: `languages/wp-prometheus.pot` - Ready for translation to any locale - All translatable strings properly marked with text domain #### Available Translations - `en_US` - English (United States) [base language - .pot template] - `de_CH` - German (Switzerland, formal) To compile translations to .mo files for production: ```bash for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done ``` ### Create releases - The `vendor/` directory MUST be included in releases (Dependencies required for runtime) - **CRITICAL**: Build `vendor/` for the MINIMUM supported PHP version, not the development version - Use `composer config platform.php 8.3.0` before building release packages - Run `composer update --no-dev --optimize-autoloader` to rebuild dependencies - **CRITICAL**: WordPress requires plugins in a subdirectory structure - Run zip from the `plugins/` parent directory, NOT from within the plugin directory - Package must extract to `wp-prometheus/` subdirectory with main file at `wp-prometheus/wp-prometheus.php` - Correct command: `cd /wp-content/plugins/ && zip -r wp-prometheus/releases/wp-prometheus-x.x.x.zip wp-prometheus ...` - Wrong: Running zip from inside the plugin directory creates files at root level - **CRITICAL**: Exclude symlinks explicitly - zip follows symlinks by default - Always use `-x "wp-prometheus/wp-core" -x "wp-prometheus/wp-core/*" -x "wp-prometheus/wp-plugins" -x "wp-prometheus/wp-plugins/*"` to exclude development symlinks - Otherwise the entire linked directory contents will be included in the package - Exclusion patterns must match the relative path structure used in zip command - Always verify the package structure with `unzip -l` before distribution - Check all files are prefixed with `wp-prometheus/` - Verify main file is at `wp-prometheus/wp-prometheus.php` - Check for duplicate entries (indicates multiple builds in same archive) - Test installation on the minimum supported PHP version before final deployment - Releases are stored in `releases/` including checksums - Track release changes in a single `CHANGELOG.md` file - Bump the version number to either bugfix release versions or on new features minor release versions - **CRITICAL**: WordPress reads version from TWO places - BOTH must be updated: 1. Plugin header comment `Version: x.x.x` - WordPress uses THIS for admin display 2. PHP constant `WP_PROMETHEUS_VERSION` (line ~28) - Used internally by the plugin - If only the constant is updated, WordPress will show the old version in Plugins list **Important Git Notes:** - Default branch while development is `dev` - Create releases from branch `main` after merging branch `dev` - Tags should use format `vX.X.X` (e.g., `v1.1.22`), start with v0.0.1 - Use annotated tags (`-a`) not lightweight tags - Commit messages should follow the established format with Claude Code attribution - `.claude/settings.local.json` changes are typically local-only (stash before rebasing) **CRITICAL - Release Workflow:** On every new version, ALWAYS execute this complete workflow: ```bash # 1. Commit changes to dev branch git add git commit -m "Description of changes (vX.X.X)" # 2. Merge dev to main git checkout main git merge dev --no-edit # 3. Create annotated tag git tag -a vX.X.X -m "Version X.X.X - Brief description" # 4. Push everything to origin git push origin dev main vX.X.X # 5. Switch back to dev for continued development git checkout dev ``` Never skip any of these steps. The release is not complete until all branches and the tag are pushed to origin. #### What Gets Released - All plugin source files - Compiled vendor dependencies - Translation files (.mo compiled from .po) - Assets (CSS, JS) - Documentation (README, CHANGELOG, etc.) #### What's Excluded - Git metadata (`.git/`) - Development files (`.vscode/`, `.claude/`, `CLAUDE.md`, `wp-core`, `wp-plugins`) - Logs and cache files - Previous releases - `composer.lock` (but `vendor/` is included) --- **For AI Assistants:** When starting a new session on this project: 1. Read this CLAUDE.md file first 2. Semantic versioning follows the `MAJOR.MINOR.BUGFIX` pattern 3. Check git log for recent changes 4. Verify you're on the `dev` branch before making changes 5. Run `composer install` if vendor/ is missing 6. Test changes before committing 7. Follow commit message format with Claude Code attribution 8. Update this session history section with learnings 9. Never commit backup files (`*.po~`, `*.bak`, etc.) - check `git status` before committing 10. Follow markdown linting rules (see below) Always refer to this document when starting work on this project. ### Markdown Linting Rules When editing CLAUDE.md or other markdown files, follow these rules to avoid linting errors: 1. **MD031 - Blank lines around fenced code blocks**: Always add a blank line before and after fenced code blocks, even when they follow list items. Example of correct format: - **Item label**: (blank line here) \`\`\`php code example \`\`\` (blank line here) 2. **MD056 - Table column count**: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths. 3. **MD009 - No trailing spaces**: Remove trailing whitespace from lines 4. **MD012 - No multiple consecutive blank lines**: Use only single blank lines between sections 5. **MD040 - Fenced code blocks should have a language specified**: Always add a language identifier to code blocks (e.g., `txt`, `bash`, `php`). For shortcode examples, use `txt`. 6. **MD032 - Lists should be surrounded by blank lines**: Add a blank line before AND after list blocks, including after bold labels like `**Attributes:**`. 7. **MD034 - Bare URLs**: Wrap URLs in angle brackets (e.g., ``) or use markdown link syntax `[text](url)`. 8. **Author section formatting**: Use a heading (`### Name`) instead of bold (`**Name**`) for the author name to maintain consistent document structure. ## Project Architecture ### Directory Structure ```txt wp-prometheus/ ├── .gitea/workflows/ │ └── release.yml # CI/CD pipeline ├── assets/ │ ├── css/ # Admin/Frontend styles │ └── js/ │ └── admin.js # Admin JavaScript ├── languages/ # Translation files ├── lib/ │ └── wc-licensed-product-client/ # Git submodule ├── releases/ # Release packages ├── src/ │ ├── Admin/ │ │ └── Settings.php # Settings page │ ├── Endpoint/ │ │ └── MetricsEndpoint.php # /metrics endpoint │ ├── License/ │ │ └── Manager.php # License management │ ├── Metrics/ │ │ └── Collector.php # Prometheus metrics collector │ ├── Installer.php # Activation/Deactivation │ ├── Plugin.php # Main plugin class │ └── index.php ├── CHANGELOG.md ├── CLAUDE.md ├── composer.json ├── index.php ├── PLAN.md ├── README.md ├── uninstall.php └── wp-prometheus.php # Plugin bootstrap ``` ### Implementation Details #### License Manager (`src/License/Manager.php`) - Integration with `SecureLicenseClient` or `LicenseClient` - Option storage for license key, server URL, server secret - License validation with domain binding - License activation with domain - Status caching (24-hour transient) - AJAX handlers for admin operations - Exception handling for all license states #### Metrics Endpoint Restriction Logic ```php // In Plugin::init_components() if ( LicenseManager::is_license_valid() ) { $this->collector = new Collector(); new MetricsEndpoint( $this->collector ); } ``` Admin settings always work; metrics endpoint requires valid license. #### Custom Metrics Extension ```php // Third-party plugins can add custom metrics add_action( 'wp_prometheus_collect_metrics', function( $collector ) { $gauge = $collector->register_gauge( 'my_custom_metric', 'Description of my metric', array( 'label1', 'label2' ) ); $gauge->set( 42, array( 'value1', 'value2' ) ); } ); ``` --- ## Session History ### 2026-02-01 - CI/CD Fixes (v0.0.2) - Fixed composer.json dependency configuration for CI compatibility - **Key Learning**: Git submodules with path repositories need explicit version aliases for CI: ```json "repositories": [ { "type": "path", "url": "lib/wc-licensed-product-client", "options": { "symlink": false, "versions": { "magdev/wc-licensed-product-client": "0.2.2" } } } ] ``` - Using `dev-main` constraints with `minimum-stability: dev` causes issues in CI - Path repository with `symlink: false` and explicit `versions` mapping works reliably - **Key Learning**: Gitea API returns "Release has no Tag" error when re-releasing existing tags - Fixed release.yml to check for and delete existing releases before creating new ones - Changed minimum-stability back to stable ### 2026-02-01 - Initial Setup (v0.0.1) - Created initial plugin structure based on wp-fedistream blueprint - Set up composer.json with promphp/prometheus_client_php and wc-licensed-product-client - Implemented core classes: Plugin, Installer, License/Manager, Metrics/Collector, Endpoint/MetricsEndpoint, Admin/Settings - Created authenticated /metrics endpoint with Bearer token support - Added default metrics: wordpress_info, users_total, posts_total, comments_total, plugins_total - Created extensibility via `wp_prometheus_collect_metrics` action hook - Set up Gitea CI/CD pipeline for automated releases - Created documentation: README.md, PLAN.md, CHANGELOG.md