You've already forked wp-prometheus
Initial plugin setup (v0.0.1)
Some checks failed
Create Release Package / build-release (push) Failing after 48s
Some checks failed
Create Release Package / build-release (push) Failing after 48s
- Create initial WordPress plugin structure - Add Prometheus metrics collector with default metrics - Implement authenticated /metrics endpoint with Bearer token - Add license management integration - Create admin settings page under Settings > Metrics - Set up Gitea CI/CD pipeline for automated releases - Add extensibility via wp_prometheus_collect_metrics hook Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
406
src/Admin/Settings.php
Normal file
406
src/Admin/Settings.php
Normal file
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin settings class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus\Admin;
|
||||
|
||||
use Magdev\WpPrometheus\License\Manager as LicenseManager;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings class.
|
||||
*
|
||||
* Handles plugin settings page in the WordPress admin.
|
||||
*/
|
||||
class Settings {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_menu', array( $this, 'add_settings_page' ) );
|
||||
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings page to admin menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_settings_page(): void {
|
||||
add_options_page(
|
||||
__( 'Metrics Settings', 'wp-prometheus' ),
|
||||
__( 'Metrics', 'wp-prometheus' ),
|
||||
'manage_options',
|
||||
'wp-prometheus',
|
||||
array( $this, 'render_settings_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register plugin settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_settings(): void {
|
||||
// License settings section.
|
||||
add_settings_section(
|
||||
'wp_prometheus_license_section',
|
||||
__( 'License Settings', 'wp-prometheus' ),
|
||||
array( $this, 'render_license_section' ),
|
||||
'wp-prometheus'
|
||||
);
|
||||
|
||||
// Auth token section.
|
||||
add_settings_section(
|
||||
'wp_prometheus_auth_section',
|
||||
__( 'Authentication', 'wp-prometheus' ),
|
||||
array( $this, 'render_auth_section' ),
|
||||
'wp-prometheus'
|
||||
);
|
||||
|
||||
// Metrics section.
|
||||
add_settings_section(
|
||||
'wp_prometheus_metrics_section',
|
||||
__( 'Default Metrics', 'wp-prometheus' ),
|
||||
array( $this, 'render_metrics_section' ),
|
||||
'wp-prometheus'
|
||||
);
|
||||
|
||||
// Register settings.
|
||||
register_setting( 'wp_prometheus_settings', 'wp_prometheus_auth_token', array(
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
) );
|
||||
|
||||
register_setting( 'wp_prometheus_settings', 'wp_prometheus_enabled_metrics', array(
|
||||
'type' => 'array',
|
||||
'sanitize_callback' => array( $this, 'sanitize_metrics' ),
|
||||
) );
|
||||
|
||||
// Auth token field.
|
||||
add_settings_field(
|
||||
'wp_prometheus_auth_token',
|
||||
__( 'Auth Token', 'wp-prometheus' ),
|
||||
array( $this, 'render_auth_token_field' ),
|
||||
'wp-prometheus',
|
||||
'wp_prometheus_auth_section'
|
||||
);
|
||||
|
||||
// Enabled metrics field.
|
||||
add_settings_field(
|
||||
'wp_prometheus_enabled_metrics',
|
||||
__( 'Enabled Metrics', 'wp-prometheus' ),
|
||||
array( $this, 'render_enabled_metrics_field' ),
|
||||
'wp-prometheus',
|
||||
'wp_prometheus_metrics_section'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin scripts.
|
||||
*
|
||||
* @param string $hook_suffix Current admin page.
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_scripts( string $hook_suffix ): void {
|
||||
if ( 'settings_page_wp-prometheus' !== $hook_suffix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_script(
|
||||
'wp-prometheus-admin',
|
||||
WP_PROMETHEUS_URL . 'assets/js/admin.js',
|
||||
array( 'jquery' ),
|
||||
WP_PROMETHEUS_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script( 'wp-prometheus-admin', 'wpPrometheus', array(
|
||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'wp_prometheus_license_action' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the settings page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_settings_page(): void {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle license settings save.
|
||||
if ( isset( $_POST['wp_prometheus_license_nonce'] ) && wp_verify_nonce( sanitize_key( $_POST['wp_prometheus_license_nonce'] ), 'wp_prometheus_save_license' ) ) {
|
||||
LicenseManager::save_settings( array(
|
||||
'license_key' => isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '',
|
||||
'server_url' => isset( $_POST['license_server_url'] ) ? esc_url_raw( wp_unslash( $_POST['license_server_url'] ) ) : '',
|
||||
'server_secret' => isset( $_POST['license_server_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['license_server_secret'] ) ) : '',
|
||||
) );
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'License settings saved.', 'wp-prometheus' ) . '</p></div>';
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
||||
|
||||
<?php $this->render_license_form(); ?>
|
||||
|
||||
<form method="post" action="options.php">
|
||||
<?php
|
||||
settings_fields( 'wp_prometheus_settings' );
|
||||
do_settings_sections( 'wp-prometheus' );
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
|
||||
<?php $this->render_endpoint_info(); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render license settings form.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_license_form(): void {
|
||||
$license_key = LicenseManager::get_license_key();
|
||||
$server_url = LicenseManager::get_server_url();
|
||||
$license_status = LicenseManager::get_cached_status();
|
||||
$license_data = LicenseManager::get_cached_data();
|
||||
$last_check = LicenseManager::get_last_check();
|
||||
|
||||
$status_classes = array(
|
||||
'valid' => 'notice-success',
|
||||
'invalid' => 'notice-error',
|
||||
'expired' => 'notice-warning',
|
||||
'revoked' => 'notice-error',
|
||||
'inactive' => 'notice-warning',
|
||||
'unchecked' => 'notice-info',
|
||||
'unconfigured' => 'notice-info',
|
||||
);
|
||||
|
||||
$status_messages = array(
|
||||
'valid' => __( 'License is active and valid.', 'wp-prometheus' ),
|
||||
'invalid' => __( 'License is invalid.', 'wp-prometheus' ),
|
||||
'expired' => __( 'License has expired.', 'wp-prometheus' ),
|
||||
'revoked' => __( 'License has been revoked.', 'wp-prometheus' ),
|
||||
'inactive' => __( 'License is inactive.', 'wp-prometheus' ),
|
||||
'unchecked' => __( 'License has not been validated yet.', 'wp-prometheus' ),
|
||||
'unconfigured' => __( 'License server is not configured.', 'wp-prometheus' ),
|
||||
);
|
||||
|
||||
$status_class = $status_classes[ $license_status ] ?? 'notice-info';
|
||||
$status_message = $status_messages[ $license_status ] ?? __( 'Unknown status.', 'wp-prometheus' );
|
||||
?>
|
||||
<div class="wp-prometheus-license-status notice <?php echo esc_attr( $status_class ); ?>" style="padding: 12px;">
|
||||
<strong><?php echo esc_html( $status_message ); ?></strong>
|
||||
<?php if ( 'valid' === $license_status && ! empty( $license_data['expires_at'] ) ) : ?>
|
||||
<br><span class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: Expiration date */
|
||||
esc_html__( 'Expires: %s', 'wp-prometheus' ),
|
||||
esc_html( $license_data['expires_at'] )
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if ( $last_check > 0 ) : ?>
|
||||
<br><span class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: Time ago */
|
||||
esc_html__( 'Last checked: %s ago', 'wp-prometheus' ),
|
||||
esc_html( human_time_diff( $last_check, time() ) )
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<form method="post" action="" id="wp-prometheus-license-form">
|
||||
<?php wp_nonce_field( 'wp_prometheus_save_license', 'wp_prometheus_license_nonce' ); ?>
|
||||
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="license_server_url"><?php esc_html_e( 'License Server URL', 'wp-prometheus' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="url" name="license_server_url" id="license_server_url"
|
||||
value="<?php echo esc_attr( $server_url ); ?>"
|
||||
class="regular-text" placeholder="https://example.com">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="license_key"><?php esc_html_e( 'License Key', 'wp-prometheus' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="text" name="license_key" id="license_key"
|
||||
value="<?php echo esc_attr( $license_key ); ?>"
|
||||
class="regular-text" placeholder="XXXX-XXXX-XXXX-XXXX">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="license_server_secret"><?php esc_html_e( 'Server Secret', 'wp-prometheus' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input type="password" name="license_server_secret" id="license_server_secret"
|
||||
value="" class="regular-text" placeholder="<?php echo esc_attr( ! empty( LicenseManager::get_server_secret() ) ? '••••••••••••••••' : '' ); ?>">
|
||||
<p class="description"><?php esc_html_e( 'Leave empty to keep existing.', 'wp-prometheus' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p class="submit">
|
||||
<?php submit_button( __( 'Save License Settings', 'wp-prometheus' ), 'primary', 'submit', false ); ?>
|
||||
<button type="button" id="wp-prometheus-validate-license" class="button button-secondary" style="margin-left: 10px;">
|
||||
<?php esc_html_e( 'Validate License', 'wp-prometheus' ); ?>
|
||||
</button>
|
||||
<button type="button" id="wp-prometheus-activate-license" class="button button-secondary" style="margin-left: 10px;">
|
||||
<?php esc_html_e( 'Activate License', 'wp-prometheus' ); ?>
|
||||
</button>
|
||||
<span id="wp-prometheus-license-spinner" class="spinner" style="float: none;"></span>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<div id="wp-prometheus-license-message" style="display: none; margin-top: 10px;"></div>
|
||||
<hr>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render license section description.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_license_section(): void {
|
||||
// License section rendered separately in render_license_form().
|
||||
}
|
||||
|
||||
/**
|
||||
* Render auth section description.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_auth_section(): void {
|
||||
echo '<p>' . esc_html__( 'Configure authentication for the /metrics endpoint.', 'wp-prometheus' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render metrics section description.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_metrics_section(): void {
|
||||
echo '<p>' . esc_html__( 'Select which default metrics to expose.', 'wp-prometheus' ) . '</p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render auth token field.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_auth_token_field(): void {
|
||||
$token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
?>
|
||||
<input type="text" name="wp_prometheus_auth_token" id="wp_prometheus_auth_token"
|
||||
value="<?php echo esc_attr( $token ); ?>" class="regular-text" readonly>
|
||||
<button type="button" id="wp-prometheus-regenerate-token" class="button button-secondary">
|
||||
<?php esc_html_e( 'Regenerate', 'wp-prometheus' ); ?>
|
||||
</button>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Use this token to authenticate Prometheus scrape requests.', 'wp-prometheus' ); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render enabled metrics field.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_enabled_metrics_field(): void {
|
||||
$enabled = get_option( 'wp_prometheus_enabled_metrics', array() );
|
||||
$metrics = array(
|
||||
'wordpress_info' => __( 'WordPress Info (version, PHP version, multisite)', 'wp-prometheus' ),
|
||||
'wordpress_users_total' => __( 'Total Users by Role', 'wp-prometheus' ),
|
||||
'wordpress_posts_total' => __( 'Total Posts by Type and Status', 'wp-prometheus' ),
|
||||
'wordpress_comments_total' => __( 'Total Comments by Status', 'wp-prometheus' ),
|
||||
'wordpress_plugins_total' => __( 'Total Plugins (active/inactive)', 'wp-prometheus' ),
|
||||
);
|
||||
|
||||
foreach ( $metrics as $key => $label ) {
|
||||
?>
|
||||
<label style="display: block; margin-bottom: 5px;">
|
||||
<input type="checkbox" name="wp_prometheus_enabled_metrics[]"
|
||||
value="<?php echo esc_attr( $key ); ?>"
|
||||
<?php checked( in_array( $key, $enabled, true ) ); ?>>
|
||||
<?php echo esc_html( $label ); ?>
|
||||
</label>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render endpoint info.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function render_endpoint_info(): void {
|
||||
$token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
$endpoint_url = home_url( '/metrics/' );
|
||||
?>
|
||||
<hr>
|
||||
<h2><?php esc_html_e( 'Prometheus Configuration', 'wp-prometheus' ); ?></h2>
|
||||
<p><?php esc_html_e( 'Add the following to your prometheus.yml:', 'wp-prometheus' ); ?></p>
|
||||
<pre style="background: #f1f1f1; padding: 15px; overflow-x: auto;">
|
||||
scrape_configs:
|
||||
- job_name: 'wordpress'
|
||||
static_configs:
|
||||
- targets: ['<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_HOST ) ); ?>']
|
||||
metrics_path: '/metrics/'
|
||||
scheme: '<?php echo esc_html( wp_parse_url( home_url(), PHP_URL_SCHEME ) ); ?>'
|
||||
authorization:
|
||||
type: Bearer
|
||||
credentials: '<?php echo esc_html( $token ); ?>'</pre>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: Endpoint URL */
|
||||
esc_html__( 'Metrics endpoint: %s', 'wp-prometheus' ),
|
||||
'<code>' . esc_url( $endpoint_url ) . '</code>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize enabled metrics.
|
||||
*
|
||||
* @param mixed $input Input value.
|
||||
* @return array
|
||||
*/
|
||||
public function sanitize_metrics( $input ): array {
|
||||
if ( ! is_array( $input ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_map( 'sanitize_text_field', $input );
|
||||
}
|
||||
}
|
||||
148
src/Endpoint/MetricsEndpoint.php
Normal file
148
src/Endpoint/MetricsEndpoint.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics endpoint class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus\Endpoint;
|
||||
|
||||
use Magdev\WpPrometheus\Metrics\Collector;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MetricsEndpoint class.
|
||||
*
|
||||
* Provides the /metrics endpoint for Prometheus scraping.
|
||||
*/
|
||||
class MetricsEndpoint {
|
||||
|
||||
/**
|
||||
* Metrics collector instance.
|
||||
*
|
||||
* @var Collector
|
||||
*/
|
||||
private Collector $collector;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Collector $collector Metrics collector instance.
|
||||
*/
|
||||
public function __construct( Collector $collector ) {
|
||||
$this->collector = $collector;
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_hooks(): void {
|
||||
add_action( 'init', array( $this, 'register_endpoint' ) );
|
||||
add_action( 'template_redirect', array( $this, 'handle_request' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the metrics endpoint rewrite rule.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_endpoint(): void {
|
||||
add_rewrite_rule(
|
||||
'^metrics/?$',
|
||||
'index.php?wp_prometheus_metrics=1',
|
||||
'top'
|
||||
);
|
||||
|
||||
add_rewrite_tag( '%wp_prometheus_metrics%', '([^&]+)' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the metrics endpoint request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_request(): void {
|
||||
if ( ! get_query_var( 'wp_prometheus_metrics' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Authenticate the request.
|
||||
if ( ! $this->authenticate() ) {
|
||||
status_header( 401 );
|
||||
header( 'WWW-Authenticate: Bearer realm="WP Prometheus Metrics"' );
|
||||
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||
echo 'Unauthorized';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Output metrics.
|
||||
status_header( 200 );
|
||||
header( 'Content-Type: text/plain; version=0.0.4; charset=utf-8' );
|
||||
header( 'Cache-Control: no-cache, no-store, must-revalidate' );
|
||||
header( 'Pragma: no-cache' );
|
||||
header( 'Expires: 0' );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Prometheus format.
|
||||
echo $this->collector->render();
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the metrics request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function authenticate(): bool {
|
||||
$auth_token = get_option( 'wp_prometheus_auth_token', '' );
|
||||
|
||||
// If no token is set, deny access.
|
||||
if ( empty( $auth_token ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for Bearer token in Authorization header.
|
||||
$auth_header = $this->get_authorization_header();
|
||||
if ( ! empty( $auth_header ) && preg_match( '/Bearer\s+(.*)$/i', $auth_header, $matches ) ) {
|
||||
return hash_equals( $auth_token, $matches[1] );
|
||||
}
|
||||
|
||||
// Check for token in query parameter (less secure but useful for testing).
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Auth token check.
|
||||
if ( isset( $_GET['token'] ) && hash_equals( $auth_token, sanitize_text_field( wp_unslash( $_GET['token'] ) ) ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Authorization header from the request.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_authorization_header(): string {
|
||||
if ( isset( $_SERVER['HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
|
||||
return sanitize_text_field( wp_unslash( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) );
|
||||
}
|
||||
|
||||
if ( function_exists( 'apache_request_headers' ) ) {
|
||||
$headers = apache_request_headers();
|
||||
if ( isset( $headers['Authorization'] ) ) {
|
||||
return sanitize_text_field( $headers['Authorization'] );
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
103
src/Installer.php
Normal file
103
src/Installer.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin installer class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installer class.
|
||||
*
|
||||
* Handles plugin activation, deactivation, and uninstallation.
|
||||
*/
|
||||
final class Installer {
|
||||
|
||||
/**
|
||||
* Plugin activation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activate(): void {
|
||||
// Set default options.
|
||||
self::set_default_options();
|
||||
|
||||
// Flush rewrite rules for the metrics endpoint.
|
||||
flush_rewrite_rules();
|
||||
|
||||
// Store activation time for reference.
|
||||
update_option( 'wp_prometheus_activated', time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin deactivation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function deactivate(): void {
|
||||
// Flush rewrite rules.
|
||||
flush_rewrite_rules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin uninstallation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function uninstall(): void {
|
||||
// Remove all plugin options.
|
||||
$options = array(
|
||||
'wp_prometheus_activated',
|
||||
'wp_prometheus_license_key',
|
||||
'wp_prometheus_license_server_url',
|
||||
'wp_prometheus_license_server_secret',
|
||||
'wp_prometheus_license_status',
|
||||
'wp_prometheus_license_data',
|
||||
'wp_prometheus_license_last_check',
|
||||
'wp_prometheus_auth_token',
|
||||
'wp_prometheus_enable_default_metrics',
|
||||
'wp_prometheus_enabled_metrics',
|
||||
);
|
||||
|
||||
foreach ( $options as $option ) {
|
||||
delete_option( $option );
|
||||
}
|
||||
|
||||
// Remove transients.
|
||||
delete_transient( 'wp_prometheus_license_check' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default plugin options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function set_default_options(): void {
|
||||
// Generate a random auth token if not set.
|
||||
if ( ! get_option( 'wp_prometheus_auth_token' ) ) {
|
||||
update_option( 'wp_prometheus_auth_token', wp_generate_password( 32, false ) );
|
||||
}
|
||||
|
||||
// Enable default metrics by default.
|
||||
if ( false === get_option( 'wp_prometheus_enable_default_metrics' ) ) {
|
||||
update_option( 'wp_prometheus_enable_default_metrics', 1 );
|
||||
}
|
||||
|
||||
// Default enabled metrics.
|
||||
if ( false === get_option( 'wp_prometheus_enabled_metrics' ) ) {
|
||||
update_option( 'wp_prometheus_enabled_metrics', array(
|
||||
'wordpress_info',
|
||||
'wordpress_users_total',
|
||||
'wordpress_posts_total',
|
||||
'wordpress_comments_total',
|
||||
'wordpress_plugins_total',
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
496
src/License/Manager.php
Normal file
496
src/License/Manager.php
Normal file
@@ -0,0 +1,496 @@
|
||||
<?php
|
||||
/**
|
||||
* License management class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus\License;
|
||||
|
||||
use Magdev\WcLicensedProductClient\SecureLicenseClient;
|
||||
use Magdev\WcLicensedProductClient\Dto\LicenseState;
|
||||
use Magdev\WcLicensedProductClient\Exception\LicenseException;
|
||||
use Magdev\WcLicensedProductClient\Exception\LicenseNotFoundException;
|
||||
use Magdev\WcLicensedProductClient\Exception\LicenseExpiredException;
|
||||
use Magdev\WcLicensedProductClient\Exception\LicenseRevokedException;
|
||||
use Magdev\WcLicensedProductClient\Exception\LicenseInactiveException;
|
||||
use Magdev\WcLicensedProductClient\Exception\DomainMismatchException;
|
||||
use Magdev\WcLicensedProductClient\Exception\MaxActivationsReachedException;
|
||||
use Magdev\WcLicensedProductClient\Exception\RateLimitExceededException;
|
||||
use Magdev\WcLicensedProductClient\Security\SignatureException;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* License Manager class.
|
||||
*
|
||||
* Handles license validation, activation, and status checking.
|
||||
*/
|
||||
final class Manager {
|
||||
|
||||
/**
|
||||
* Option names for license settings.
|
||||
*/
|
||||
public const OPTION_LICENSE_KEY = 'wp_prometheus_license_key';
|
||||
public const OPTION_SERVER_URL = 'wp_prometheus_license_server_url';
|
||||
public const OPTION_SERVER_SECRET = 'wp_prometheus_license_server_secret';
|
||||
public const OPTION_LICENSE_STATUS = 'wp_prometheus_license_status';
|
||||
public const OPTION_LICENSE_DATA = 'wp_prometheus_license_data';
|
||||
public const OPTION_LAST_CHECK = 'wp_prometheus_license_last_check';
|
||||
|
||||
/**
|
||||
* Transient name for caching license validation.
|
||||
*/
|
||||
private const TRANSIENT_LICENSE_CHECK = 'wp_prometheus_license_check';
|
||||
|
||||
/**
|
||||
* Cache TTL in seconds (24 hours).
|
||||
*/
|
||||
private const CACHE_TTL = 86400;
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @var Manager|null
|
||||
*/
|
||||
private static ?Manager $instance = null;
|
||||
|
||||
/**
|
||||
* License client instance.
|
||||
*
|
||||
* @var SecureLicenseClient|null
|
||||
*/
|
||||
private ?SecureLicenseClient $client = null;
|
||||
|
||||
/**
|
||||
* Get singleton instance.
|
||||
*
|
||||
* @return Manager
|
||||
*/
|
||||
public static function get_instance(): Manager {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private function __construct() {
|
||||
$this->init_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_hooks(): void {
|
||||
add_action( 'wp_ajax_wp_prometheus_validate_license', array( $this, 'ajax_validate_license' ) );
|
||||
add_action( 'wp_ajax_wp_prometheus_activate_license', array( $this, 'ajax_activate_license' ) );
|
||||
add_action( 'wp_ajax_wp_prometheus_deactivate_license', array( $this, 'ajax_deactivate_license' ) );
|
||||
add_action( 'wp_ajax_wp_prometheus_check_license_status', array( $this, 'ajax_check_status' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the license client.
|
||||
*
|
||||
* @return bool True if client was initialized successfully.
|
||||
*/
|
||||
private function init_client(): bool {
|
||||
if ( null !== $this->client ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$server_url = self::get_server_url();
|
||||
$server_secret = self::get_server_secret();
|
||||
|
||||
if ( empty( $server_url ) || empty( $server_secret ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->client = new SecureLicenseClient(
|
||||
httpClient: HttpClient::create(),
|
||||
baseUrl: $server_url,
|
||||
serverSecret: $server_secret,
|
||||
);
|
||||
return true;
|
||||
} catch ( \Throwable $e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the current license.
|
||||
*
|
||||
* @return array{success: bool, message: string, data?: array}
|
||||
*/
|
||||
public function validate(): array {
|
||||
if ( ! $this->init_client() ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License server configuration is incomplete.', 'wp-prometheus' ),
|
||||
);
|
||||
}
|
||||
|
||||
$license_key = self::get_license_key();
|
||||
if ( empty( $license_key ) ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'No license key provided.', 'wp-prometheus' ),
|
||||
);
|
||||
}
|
||||
|
||||
$domain = wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
|
||||
try {
|
||||
$result = $this->client->validate( $license_key, $domain );
|
||||
|
||||
$this->update_cached_status( 'valid', array(
|
||||
'product_id' => $result->productId,
|
||||
'expires_at' => $result->expiresAt?->format( 'c' ),
|
||||
'version_id' => $result->versionId,
|
||||
) );
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'message' => __( 'License validated successfully.', 'wp-prometheus' ),
|
||||
'data' => array(
|
||||
'status' => 'valid',
|
||||
'product_id' => $result->productId,
|
||||
'expires_at' => $result->expiresAt?->format( 'Y-m-d' ),
|
||||
'lifetime' => $result->isLifetime(),
|
||||
),
|
||||
);
|
||||
} catch ( LicenseNotFoundException $e ) {
|
||||
$this->update_cached_status( 'invalid' );
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License key not found.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseExpiredException $e ) {
|
||||
$this->update_cached_status( 'expired' );
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Your license has expired.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseRevokedException $e ) {
|
||||
$this->update_cached_status( 'revoked' );
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Your license has been revoked.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseInactiveException $e ) {
|
||||
$this->update_cached_status( 'inactive' );
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License is inactive. Please activate it first.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( DomainMismatchException $e ) {
|
||||
$this->update_cached_status( 'invalid' );
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'This license is not activated for this domain.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( SignatureException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License verification failed. Please check your server secret.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( RateLimitExceededException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Too many requests. Please try again later.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => sprintf(
|
||||
/* translators: %s: Error message */
|
||||
__( 'License validation failed: %s', 'wp-prometheus' ),
|
||||
$e->getMessage()
|
||||
),
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Unable to verify license. Please try again later.', 'wp-prometheus' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate the license for this domain.
|
||||
*
|
||||
* @return array{success: bool, message: string, data?: array}
|
||||
*/
|
||||
public function activate(): array {
|
||||
if ( ! $this->init_client() ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License server configuration is incomplete.', 'wp-prometheus' ),
|
||||
);
|
||||
}
|
||||
|
||||
$license_key = self::get_license_key();
|
||||
if ( empty( $license_key ) ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'No license key provided.', 'wp-prometheus' ),
|
||||
);
|
||||
}
|
||||
|
||||
$domain = wp_parse_url( home_url(), PHP_URL_HOST );
|
||||
|
||||
try {
|
||||
$result = $this->client->activate( $license_key, $domain );
|
||||
|
||||
if ( $result->success ) {
|
||||
return $this->validate();
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => $result->message,
|
||||
);
|
||||
} catch ( MaxActivationsReachedException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Maximum activations reached. Please deactivate another site.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseNotFoundException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License key not found.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseExpiredException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Your license has expired.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( SignatureException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'License verification failed. Please check your server secret.', 'wp-prometheus' ),
|
||||
);
|
||||
} catch ( LicenseException $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => sprintf(
|
||||
/* translators: %s: Error message */
|
||||
__( 'License activation failed: %s', 'wp-prometheus' ),
|
||||
$e->getMessage()
|
||||
),
|
||||
);
|
||||
} catch ( \Throwable $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'message' => __( 'Unable to activate license. Please try again later.', 'wp-prometheus' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the license is currently valid.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_license_valid(): bool {
|
||||
$status = get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||
return 'valid' === $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the license key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_license_key(): string {
|
||||
return get_option( self::OPTION_LICENSE_KEY, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the license server URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_server_url(): string {
|
||||
return get_option( self::OPTION_SERVER_URL, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server secret.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_server_secret(): string {
|
||||
return get_option( self::OPTION_SERVER_SECRET, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached license status.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_cached_status(): string {
|
||||
return get_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached license data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_cached_data(): array {
|
||||
return get_option( self::OPTION_LICENSE_DATA, array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last check timestamp.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function get_last_check(): int {
|
||||
return (int) get_option( self::OPTION_LAST_CHECK, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save license settings.
|
||||
*
|
||||
* @param array $data Settings data.
|
||||
* @return bool
|
||||
*/
|
||||
public static function save_settings( array $data ): bool {
|
||||
if ( isset( $data['license_key'] ) ) {
|
||||
update_option( self::OPTION_LICENSE_KEY, sanitize_text_field( $data['license_key'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['server_url'] ) ) {
|
||||
update_option( self::OPTION_SERVER_URL, esc_url_raw( $data['server_url'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['server_secret'] ) ) {
|
||||
$secret = sanitize_text_field( $data['server_secret'] );
|
||||
if ( ! empty( $secret ) ) {
|
||||
update_option( self::OPTION_SERVER_SECRET, $secret );
|
||||
}
|
||||
}
|
||||
|
||||
update_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||
delete_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cached license status.
|
||||
*
|
||||
* @param string $status Status value.
|
||||
* @param array $data Additional data.
|
||||
* @return void
|
||||
*/
|
||||
private function update_cached_status( string $status, array $data = array() ): void {
|
||||
update_option( self::OPTION_LICENSE_STATUS, $status );
|
||||
update_option( self::OPTION_LICENSE_DATA, $data );
|
||||
update_option( self::OPTION_LAST_CHECK, time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler: Validate license.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_validate_license(): void {
|
||||
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array(
|
||||
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
|
||||
) );
|
||||
}
|
||||
|
||||
$result = $this->validate();
|
||||
|
||||
if ( $result['success'] ) {
|
||||
wp_send_json_success( $result );
|
||||
} else {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler: Activate license.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_activate_license(): void {
|
||||
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array(
|
||||
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
|
||||
) );
|
||||
}
|
||||
|
||||
$result = $this->activate();
|
||||
|
||||
if ( $result['success'] ) {
|
||||
wp_send_json_success( $result );
|
||||
} else {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler: Deactivate license.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_deactivate_license(): void {
|
||||
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array(
|
||||
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
|
||||
) );
|
||||
}
|
||||
|
||||
update_option( self::OPTION_LICENSE_STATUS, 'unchecked' );
|
||||
update_option( self::OPTION_LICENSE_DATA, array() );
|
||||
delete_transient( self::TRANSIENT_LICENSE_CHECK );
|
||||
|
||||
wp_send_json_success( array(
|
||||
'success' => true,
|
||||
'message' => __( 'License deactivated.', 'wp-prometheus' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler: Check license status.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function ajax_check_status(): void {
|
||||
check_ajax_referer( 'wp_prometheus_license_action', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array(
|
||||
'message' => __( 'You do not have permission to perform this action.', 'wp-prometheus' ),
|
||||
) );
|
||||
}
|
||||
|
||||
$result = $this->validate();
|
||||
|
||||
if ( $result['success'] ) {
|
||||
wp_send_json_success( $result );
|
||||
} else {
|
||||
wp_send_json_error( $result );
|
||||
}
|
||||
}
|
||||
}
|
||||
288
src/Metrics/Collector.php
Normal file
288
src/Metrics/Collector.php
Normal file
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics collector class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus\Metrics;
|
||||
|
||||
use Prometheus\CollectorRegistry;
|
||||
use Prometheus\Storage\InMemory;
|
||||
use Prometheus\RenderTextFormat;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collector class.
|
||||
*
|
||||
* Collects and manages Prometheus metrics.
|
||||
*/
|
||||
class Collector {
|
||||
|
||||
/**
|
||||
* Prometheus collector registry.
|
||||
*
|
||||
* @var CollectorRegistry
|
||||
*/
|
||||
private CollectorRegistry $registry;
|
||||
|
||||
/**
|
||||
* Metric namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $namespace = 'wordpress';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->registry = new CollectorRegistry( new InMemory() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collector registry.
|
||||
*
|
||||
* @return CollectorRegistry
|
||||
*/
|
||||
public function get_registry(): CollectorRegistry {
|
||||
return $this->registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metric namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace(): string {
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all enabled metrics.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collect(): void {
|
||||
$enabled_metrics = get_option( 'wp_prometheus_enabled_metrics', array() );
|
||||
|
||||
// Always collect WordPress info.
|
||||
if ( in_array( 'wordpress_info', $enabled_metrics, true ) ) {
|
||||
$this->collect_wordpress_info();
|
||||
}
|
||||
|
||||
// Collect user metrics.
|
||||
if ( in_array( 'wordpress_users_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_users_total();
|
||||
}
|
||||
|
||||
// Collect posts metrics.
|
||||
if ( in_array( 'wordpress_posts_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_posts_total();
|
||||
}
|
||||
|
||||
// Collect comments metrics.
|
||||
if ( in_array( 'wordpress_comments_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_comments_total();
|
||||
}
|
||||
|
||||
// Collect plugins metrics.
|
||||
if ( in_array( 'wordpress_plugins_total', $enabled_metrics, true ) ) {
|
||||
$this->collect_plugins_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after default metrics are collected.
|
||||
*
|
||||
* @param Collector $collector The metrics collector instance.
|
||||
*/
|
||||
do_action( 'wp_prometheus_collect_metrics', $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render metrics in Prometheus text format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
$this->collect();
|
||||
|
||||
$renderer = new RenderTextFormat();
|
||||
return $renderer->render( $this->registry->getMetricFamilySamples() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect WordPress info metric.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_wordpress_info(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'info',
|
||||
'WordPress installation information',
|
||||
array( 'version', 'php_version', 'multisite' )
|
||||
);
|
||||
|
||||
$gauge->set(
|
||||
1,
|
||||
array(
|
||||
get_bloginfo( 'version' ),
|
||||
PHP_VERSION,
|
||||
is_multisite() ? 'yes' : 'no',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect total users metric.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_users_total(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'users_total',
|
||||
'Total number of WordPress users',
|
||||
array( 'role' )
|
||||
);
|
||||
|
||||
$user_count = count_users();
|
||||
foreach ( $user_count['avail_roles'] as $role => $count ) {
|
||||
$gauge->set( $count, array( $role ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect total posts metric.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_posts_total(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'posts_total',
|
||||
'Total number of posts by type and status',
|
||||
array( 'post_type', 'status' )
|
||||
);
|
||||
|
||||
$post_types = get_post_types( array( 'public' => true ) );
|
||||
foreach ( $post_types as $post_type ) {
|
||||
$counts = wp_count_posts( $post_type );
|
||||
foreach ( get_object_vars( $counts ) as $status => $count ) {
|
||||
if ( $count > 0 ) {
|
||||
$gauge->set( (int) $count, array( $post_type, $status ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect total comments metric.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_comments_total(): void {
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'comments_total',
|
||||
'Total number of comments by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
$comments = wp_count_comments();
|
||||
$statuses = array(
|
||||
'approved' => $comments->approved,
|
||||
'moderated' => $comments->moderated,
|
||||
'spam' => $comments->spam,
|
||||
'trash' => $comments->trash,
|
||||
'total_comments' => $comments->total_comments,
|
||||
);
|
||||
|
||||
foreach ( $statuses as $status => $count ) {
|
||||
$gauge->set( (int) $count, array( $status ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect total plugins metric.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function collect_plugins_total(): void {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$gauge = $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
'plugins_total',
|
||||
'Total number of plugins by status',
|
||||
array( 'status' )
|
||||
);
|
||||
|
||||
$all_plugins = get_plugins();
|
||||
$active_plugins = get_option( 'active_plugins', array() );
|
||||
|
||||
$gauge->set( count( $all_plugins ), array( 'installed' ) );
|
||||
$gauge->set( count( $active_plugins ), array( 'active' ) );
|
||||
$gauge->set( count( $all_plugins ) - count( $active_plugins ), array( 'inactive' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom gauge metric.
|
||||
*
|
||||
* @param string $name Metric name.
|
||||
* @param string $help Metric description.
|
||||
* @param array $labels Label names.
|
||||
* @return \Prometheus\Gauge
|
||||
*/
|
||||
public function register_gauge( string $name, string $help, array $labels = array() ): \Prometheus\Gauge {
|
||||
return $this->registry->getOrRegisterGauge(
|
||||
$this->namespace,
|
||||
$name,
|
||||
$help,
|
||||
$labels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom counter metric.
|
||||
*
|
||||
* @param string $name Metric name.
|
||||
* @param string $help Metric description.
|
||||
* @param array $labels Label names.
|
||||
* @return \Prometheus\Counter
|
||||
*/
|
||||
public function register_counter( string $name, string $help, array $labels = array() ): \Prometheus\Counter {
|
||||
return $this->registry->getOrRegisterCounter(
|
||||
$this->namespace,
|
||||
$name,
|
||||
$help,
|
||||
$labels
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom histogram metric.
|
||||
*
|
||||
* @param string $name Metric name.
|
||||
* @param string $help Metric description.
|
||||
* @param array $labels Label names.
|
||||
* @param array|null $buckets Histogram buckets.
|
||||
* @return \Prometheus\Histogram
|
||||
*/
|
||||
public function register_histogram( string $name, string $help, array $labels = array(), ?array $buckets = null ): \Prometheus\Histogram {
|
||||
return $this->registry->getOrRegisterHistogram(
|
||||
$this->namespace,
|
||||
$name,
|
||||
$help,
|
||||
$labels,
|
||||
$buckets
|
||||
);
|
||||
}
|
||||
}
|
||||
150
src/Plugin.php
Normal file
150
src/Plugin.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* Main plugin class.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
namespace Magdev\WpPrometheus;
|
||||
|
||||
use Magdev\WpPrometheus\Admin\Settings;
|
||||
use Magdev\WpPrometheus\Endpoint\MetricsEndpoint;
|
||||
use Magdev\WpPrometheus\License\Manager as LicenseManager;
|
||||
use Magdev\WpPrometheus\Metrics\Collector;
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin singleton class.
|
||||
*
|
||||
* Initializes and manages all plugin components.
|
||||
*/
|
||||
final class Plugin {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*
|
||||
* @var Plugin|null
|
||||
*/
|
||||
private static ?Plugin $instance = null;
|
||||
|
||||
/**
|
||||
* Metrics collector instance.
|
||||
*
|
||||
* @var Collector|null
|
||||
*/
|
||||
private ?Collector $collector = null;
|
||||
|
||||
/**
|
||||
* Get singleton instance.
|
||||
*
|
||||
* @return Plugin
|
||||
*/
|
||||
public static function get_instance(): Plugin {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to enforce singleton pattern.
|
||||
*/
|
||||
private function __construct() {
|
||||
$this->init_components();
|
||||
$this->init_hooks();
|
||||
$this->load_textdomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent cloning.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __clone() {}
|
||||
|
||||
/**
|
||||
* Prevent unserialization.
|
||||
*
|
||||
* @throws \Exception Always throws to prevent unserialization.
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup(): void {
|
||||
throw new \Exception( 'Cannot unserialize singleton' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize plugin components.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_components(): void {
|
||||
// Initialize license manager.
|
||||
LicenseManager::get_instance();
|
||||
|
||||
// Initialize admin settings (always needed).
|
||||
if ( is_admin() ) {
|
||||
new Settings();
|
||||
}
|
||||
|
||||
// Initialize metrics endpoint (only if licensed).
|
||||
if ( LicenseManager::is_license_valid() ) {
|
||||
$this->collector = new Collector();
|
||||
new MetricsEndpoint( $this->collector );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize WordPress hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init_hooks(): void {
|
||||
// Add settings link to plugins page.
|
||||
add_filter( 'plugin_action_links_' . WP_PROMETHEUS_BASENAME, array( $this, 'add_plugin_action_links' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add action links to the plugins page.
|
||||
*
|
||||
* @param array $links Existing action links.
|
||||
* @return array Modified action links.
|
||||
*/
|
||||
public function add_plugin_action_links( array $links ): array {
|
||||
$settings_link = sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( admin_url( 'options-general.php?page=wp-prometheus' ) ),
|
||||
esc_html__( 'Settings', 'wp-prometheus' )
|
||||
);
|
||||
|
||||
// Add our link at the beginning.
|
||||
array_unshift( $links, $settings_link );
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin textdomain.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function load_textdomain(): void {
|
||||
load_plugin_textdomain(
|
||||
'wp-prometheus',
|
||||
false,
|
||||
dirname( WP_PROMETHEUS_BASENAME ) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metrics collector.
|
||||
*
|
||||
* @return Collector|null
|
||||
*/
|
||||
public function get_collector(): ?Collector {
|
||||
return $this->collector;
|
||||
}
|
||||
}
|
||||
11
src/index.php
Normal file
11
src/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Silence is golden.
|
||||
*
|
||||
* @package WP_Prometheus
|
||||
*/
|
||||
|
||||
// Prevent direct file access.
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
Reference in New Issue
Block a user