| Server IP : 65.108.144.40 / Your IP : 216.73.217.165 Web Server : Apache/2.4.52 (Ubuntu) System : Linux ubuntu-8gb-hel1-1 5.15.0-173-generic #183-Ubuntu SMP Fri Mar 6 13:29:34 UTC 2026 x86_64 User : dev ( 1000) PHP Version : 8.2.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /var/www/html/successkpi/wp-content/mu-plugins/wpengine-common/ |
Upload File : |
<?php
/**
* Wpe_Event_Tracker class file.
*
* @package wpengine/common-mu-plugin
* @owner: wpengine/golden
*/
namespace wpe\plugin;
require_once __DIR__ . '/class-wpe-event-utils.php';
/**
* Class Wpe_Event_Tracker
*
* This class handles events related to plugin and theme updates, activations,
* deactivations, deletions, theme switches, and WordPress core upgrades.
*/
class Wpe_Event_Tracker {
/**
* Singleton instance of this class
*
* @var Wpe_Event_Tracker
*/
private static $instance = null;
/**
* Stores the version of plugins or themes before they are updated as a slug => version pair.
* It's used in the 'shutdown' hook to verify, if a version actually changed after an update.
*
* @var array
*/
private $upgraded_extensions = array(
'plugins' => array(),
'themes' => array(),
);
/**
* Keep track of the package URL for the plugin or theme being installed or updated.
*
* @var array
*/
private $package_data = array(
'plugins' => array(),
'themes' => array(),
);
/**
* The action that triggered the upgrader_process_complete hook
*
* @var string
*/
private $upgrader_process_complete_action = '';
/**
* Cached autosaves config value
*
* @var int|null
*/
private $autosaves_config = null;
const REVISION_ORDINAL_CAP = 255;
const REVISION_ORDINAL_OVERFLOW = 256;
const LATENCY_SAMPLE_PER_10K = 1;
const LATENCY_MAX_DURATION_MS = 600000;
/**
* Get the instance of the class.
*
* @return Wpe_Event_Tracker
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Wpe_Event_Tracker constructor.
*/
private function __construct() {
// Return early if we are executing in the WP-CLI context.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
return;
}
// Return early if PWP_NAME or WPE_CLUSTER_ID or WP_CACHE_KEY_SALT is not defined.
if ( ! defined( 'PWP_NAME' ) || ! defined( 'WPE_CLUSTER_ID' ) || ! defined( 'WP_CACHE_KEY_SALT' ) ) {
return;
}
// Return early if PWP_NAME or WP_CACHE_KEY_SALT are empty strings.
if ( '' === trim( (string) PWP_NAME ) || '' === trim( (string) WP_CACHE_KEY_SALT ) ) {
return;
}
add_action( 'upgrader_process_complete', array( $this, 'on_upgrader_process_complete' ), 10, 2 );
add_action( '_core_updated_successfully', array( $this, 'on_core_updated_successfully' ) );
add_action( 'activated_plugin', array( $this, 'on_activated_plugin' ), 10, 1 );
add_action( 'deactivated_plugin', array( $this, 'on_deactivated_plugin' ), 10, 1 );
add_action( 'delete_plugin', array( $this, 'on_delete_plugin' ), 10, 1 );
add_action( 'delete_theme', array( $this, 'on_delete_theme' ), 10, 1 );
add_action( 'switch_theme', array( $this, 'on_switch_theme' ), 10, 2 );
add_filter( 'upgrader_pre_install', array( $this, 'on_upgrader_pre_install' ), 10, 2 );
add_filter( 'upgrader_post_install', array( $this, 'on_upgrader_post_install' ), 10, 3 );
add_filter( 'upgrader_package_options', array( $this, 'on_upgrader_process_init' ), 10, 1 );
add_filter( 'pre_http_request', array( $this, 'on_pre_http_request' ), PHP_INT_MAX, 3 );
// Revision tracking hooks.
add_action( 'wp_restore_post_revision', array( $this, 'on_restore_revision' ), 10, 2 );
add_action( 'wp_save_post_revision', array( $this, 'on_save_revision' ), 10, 2 );
// Sampled request latency — deferred to plugins_loaded so wp_rand() is available.
if ( self::LATENCY_SAMPLE_PER_10K > 0 ) {
add_action( 'plugins_loaded', array( $this, 'maybe_register_latency_hook' ) );
}
}
/**
* When a plugin or theme is about to be installed or updated, update the internal state with
* package type and package URL.
*
* @param array $options Options used by the upgrader.
*
* @return array
*/
public function on_upgrader_process_init( $options ) {
// Return early if the parameters are invalid.
if ( ! is_array( $options ) ) {
return $options;
}
// Catch the type of the upgrade operation, also for WP-CLI driven updates.
$type = $options['hook_extra']['type'] ?? 'unknown';
$type = 'unknown' === $type && isset( $options['hook_extra']['plugin'] ) ? 'plugin' : $type;
$type = 'unknown' === $type && isset( $options['hook_extra']['theme'] ) ? 'theme' : $type;
// New upgrade operation in progress.
$this->package_data['current_package'] = $options['package'] ?? '';
$this->package_data['type'] = $type;
return $options;
}
/**
* Log on upgrader_process_complete hook
*
* @param WP_Upgrader $upgrader WP_Upgrader instance. Might be a Theme_Upgrader, Plugin_Upgrader, Core_Upgrader, or Language_Pack_Upgrader instance.
* @param array $hook_extra Array of bulk item update data.
*
* @see https://developer.wordpress.org/reference/hooks/upgrader_process_complete/
*/
public function on_upgrader_process_complete( $upgrader, $hook_extra = array() ) {
// Return early if this is a multisite installation and the current site is not the main site.
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
// Return early if the parameters are invalid.
if ( ! $upgrader instanceof \WP_Upgrader || ! is_array( $hook_extra ) || ! isset( $hook_extra['type'] ) || ! isset( $hook_extra['action'] ) ) {
return;
}
$type = $hook_extra['type'];
$action = $hook_extra['action'];
// Return early if this is not a plugin or theme install or update.
if ( ! in_array( $type, array( 'plugin', 'theme' ), true ) || ! in_array( $action, array( 'install', 'update' ), true ) ) {
return;
}
// Extract slugs of upgraded extensions from $upgrader or $hook_extra and store them in the upgraded_extensions array.
$this->store_slugs_after_upgrade( $upgrader, $type, $hook_extra );
// The action will be used in the shutdown hook to distinguish between install and update events.
$this->upgrader_process_complete_action = $action;
// Attach to the 'shutdown' hook to delay processing of installed extensions. Purposefully it's not done
// in the 'upgrader_process_complete' hook, because an update can be rolled back yet.
add_action( 'shutdown', array( $this, 'on_shutdown' ), 10, 0 );
}
/**
* Store slugs of installed or updated plugins or themes
*
* When an extension is being updated, its slug is passed to 'upgrader_pre_install' in $hook_extra so it can be stored
* in the upgraded_extensions array. But when an extension is installed (or updated via install from zip), $hook_extra
* in 'upgrader_pre_install' doesn't contain its slug. That's why store_slugs_after_upgrade() is needed to store the
* slugs of installed extensions.
*
* @param WP_Upgrader $upgrader Plugin_Upgrader instance.
* @param string $type Type of the upgrader process. Either 'plugin' or 'theme'.
* @param array $hook_extra Array of bulk item update data.
*/
private function store_slugs_after_upgrade( $upgrader, $type, $hook_extra ) {
$slugs = array();
$key = $type; // $type can be 'plugin' or 'theme'
if ( ! empty( $hook_extra[ $key ] ) ) {
$slugs[] = $hook_extra[ $key ];
} else {
$key = $type . 's'; // Changes 'plugin' to 'plugins' and 'theme' to 'themes'.
if ( isset( $hook_extra[ $key ] ) ) {
$slugs = (array) $hook_extra[ $key ];
} elseif ( is_array( $upgrader->result ) && ! empty( $upgrader->result['destination_name'] ) ) {
$slugs[] = $upgrader->result['destination_name'];
}
}
// Store the slugs of installed or updated extensions in the upgraded_extensions array.
foreach ( $slugs as $slug ) {
if ( ! array_key_exists( $slug, $this->upgraded_extensions[ $type . 's' ] ) ) {
// Saves the slug in the 'plugins' or 'themes' array.
$this->upgraded_extensions[ $type . 's' ][ $slug ] = null;
}
}
}
/**
* Callback function for the 'shutdown' hook.
*
* This method is called at the end of the script execution. It processes the plugins and themes
* that might have been installed or updated.
*
* @return void
*/
public function on_shutdown() {
if ( ! empty( $this->upgraded_extensions['plugins'] ) ) {
$this->process_plugin_updates();
}
if ( ! empty( $this->upgraded_extensions['themes'] ) ) {
$this->process_theme_updates();
}
}
/**
* Handle plugin installed or updated
*
* This method is called in the 'shutdown' hook and checks which plugins have been installed
* or updated and emmits a Telegraf message.
*/
private function process_plugin_updates() {
$plugins = array();
// Iterate through the slugs to get plugin details.
foreach ( $this->upgraded_extensions['plugins'] as $slug => $version ) {
$plugin_details = $this->get_plugin_details( $slug );
// Skip if the plugin details could not be retrieved.
if ( null === $plugin_details ) {
continue;
}
// Check if the version has changed.
if ( $version !== $plugin_details['Version'] ) {
$plugins[] = $plugin_details;
}
}
if ( ! empty( $plugins ) ) {
// Send the details of updated or installed plugins.
$this->prepare_and_send( 'plugins', $plugins );
}
}
/**
* Handle theme installed or updated
*
* This method is called in the 'shutdown' hook and checks which themes have been installed
* or updated and emmits a Telegraf message.
*/
private function process_theme_updates() {
$themes = array();
// Iterate through the slugs to get theme details.
foreach ( $this->upgraded_extensions['themes'] as $slug => $version ) {
$theme_details = $this->get_theme_details( $slug );
// Skip if the theme details could not be retrieved.
if ( null === $theme_details ) {
continue;
}
// Check if the version has changed.
if ( $version !== $theme_details['Version'] ) {
$themes[] = $theme_details;
}
}
if ( ! empty( $themes ) ) {
// Send the details of updated or installed themes.
$this->prepare_and_send( 'themes', $themes );
}
}
/**
* Get plugin details
*
* @param string $slug Plugin slug.
* @return array|null Plugin details or null if not found.
*/
private function get_plugin_details( $slug ) {
// Get the full path to the main plugin file using the provided slug.
$plugin_path = Wpe_Event_Utils::get_plugin_path( $slug );
// Return early if the plugin path could not be determined.
if ( null === $plugin_path ) {
return null;
}
// Get the plugin data from the main plugin file.
$data = get_file_data(
$plugin_path,
array(
'Name' => 'Plugin Name',
'Version' => 'Version',
)
);
// Check if the required fields are present and not empty.
if ( empty( $data['Name'] ) || empty( $data['Version'] ) ) {
return null;
}
// Get the plugin file path relative to the plugins directory.
$plugin_file = plugin_basename( $plugin_path );
// Extract the slug from the plugin file.
$plugin_slug = Wpe_Event_Utils::get_slug_from_filename( $plugin_file );
// If a plugin package was stored during the upgrade process, use it.
$plugin_package = isset( $this->package_data['plugins'][ $plugin_slug ] ) ?
$this->package_data['plugins'][ $plugin_slug ]['package'] : '';
// Is the plugin active?
$active = is_plugin_active( $plugin_file );
// Create an array with the plugin details.
return array(
'Slug' => $plugin_slug, // The slug of the plugin.
'File' => $plugin_file, // The path to the main plugin file relative to the plugins directory.
'Name' => $data['Name'], // The name of the plugin.
'Version' => $data['Version'], // The version of the plugin.
'Active' => $active, // Whether the plugin is active.
'Package' => $plugin_package, // The package URL of the plugin.
);
}
/**
* Get theme details
*
* @param string $slug Theme slug.
* @return array|null Theme details or null if not found.
*/
private function get_theme_details( $slug ) {
// Get the main theme file path using the provided slug.
$theme_path = Wpe_Event_Utils::get_theme_path( $slug );
// Return early if the theme path could not be determined.
if ( null === $theme_path ) {
return null;
}
// Get the theme data from the style.css file.
$data = get_file_data(
$theme_path,
array(
'Name' => 'Theme Name',
'Version' => 'Version',
'Parent' => 'Template',
)
);
// Check if the required fields are present and not empty.
if ( empty( $data['Name'] ) || empty( $data['Version'] ) ) {
return null;
}
// Get style.css path relative to the themes directory.
$theme_file = str_replace( WP_CONTENT_DIR . '/themes/', '', $theme_path );
// If a theme package was stored during the upgrade process, use it.
$theme_package = isset( $this->package_data['themes'][ $slug ] ) ?
$this->package_data['themes'][ $slug ]['package'] : '';
// Is the theme active?
$active = ( get_stylesheet() === $slug );
// Create an array with the theme details.
$theme_details = array(
'Slug' => $slug, // The slug of the theme.
'File' => $theme_file, // The path to style.css relative to the themes directory.
'Name' => $data['Name'], // The name of the theme.
'Version' => $data['Version'], // The version of the theme.
'Active' => $active, // Whether the theme is active.
'Package' => $theme_package, // The package URL of the theme.
);
// Add the parent theme if it exists.
if ( ! empty( $data['Parent'] ) ) {
$theme_details['Parent'] = $data['Parent'];
}
return $theme_details;
}
/**
* Handle core version change
*/
public function on_core_updated_successfully() {
$new_versions = Wpe_Event_Utils::get_current_core_version();
$wordpress = array(
'Version' => $new_versions,
);
// Send the details of the updated WordPress core.
// phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled -- The key 'wordpress' is used intentionally for data structure consistency.
$this->prepare_and_send( 'wordpress', $wordpress );
}
/**
* Handle plugin active state change
*
* @param string $slug Plugin slug.
* @param bool $active Whether the plugin is active.
*/
private function on_plugin_active_state_change( $slug, $active ) {
$plugin_details = $this->get_plugin_details( $slug );
if ( null !== $plugin_details ) {
$plugin_details['Active'] = $active;
// Plugins are always sent as an array.
$plugins = array( $plugin_details );
$this->prepare_and_send( 'plugins', $plugins );
}
}
/**
* Log on activated_plugin hook
*
* @param string $slug The plugin being activated.
*
* @see https://developer.wordpress.org/reference/hooks/activated_plugin/
*/
public function on_activated_plugin( $slug ) {
// Return early if this is a multisite installation and the current site is not the main site.
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
$this->on_plugin_active_state_change( $slug, true );
}
/**
* Log on deactivated_plugin hook
*
* @param string $slug The plugin being deactivated.
*
* @see https://developer.wordpress.org/reference/hooks/deactivated_plugin/
*/
public function on_deactivated_plugin( $slug ) {
// Return early if this is a multisite installation and the current site is not the main site.
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
$this->on_plugin_active_state_change( $slug, false );
}
/**
* Log on delete_plugin hook
*
* @param string $plugin_file The path to the plugin's main file relative to the plugins directory.
*
* @see https://developer.wordpress.org/reference/hooks/delete_plugin/
*/
public function on_delete_plugin( $plugin_file ) {
// Return early if this is a multisite installation and the current site is not the main site.
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
// Extract the slug from the plugin file.
$slug = Wpe_Event_Utils::get_slug_from_filename( $plugin_file );
$deleted_plugin = array(
'Slug' => $slug,
'File' => $plugin_file,
'Deleted' => true,
);
// Plugins are always sent as an array.
$plugins = array( $deleted_plugin );
$this->prepare_and_send( 'plugins', $plugins );
}
/**
* Log on delete_theme hook
*
* @param string $theme_slug The slug of the theme.
*
* @see https://developer.wordpress.org/reference/hooks/delete_theme/
*/
public function on_delete_theme( $theme_slug ) {
// Return early if this is a multisite installation and the current site is not the main site.
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
$deleted_theme = array(
'Slug' => $theme_slug,
'Deleted' => true,
);
// Themes are always sent as an array.
$themes = array( $deleted_theme );
$this->prepare_and_send( 'themes', $themes );
}
/**
* Log on switch_theme hook
*
* @param string $new_name The name of the new theme.
* @param WP_Theme $new_theme WP_Theme instance of the new theme.
*
* @see https://developer.wordpress.org/reference/hooks/switch_theme/
*/
public function on_switch_theme( $new_name, $new_theme ) {
// Suppress unused variable warning ($new_name is unused, but WordPress passes it to the callback anyway).
$new_name;
// Return early if this is a multisite installation and the current site is not the main site.
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
$slug = $new_theme->get_stylesheet();
$theme_details = $this->get_theme_details( $slug );
if ( null === $theme_details ) {
return;
}
// Themes are always sent as an array.
$themes = array( $theme_details );
$this->prepare_and_send( 'themes', $themes );
}
/**
* Function to store the version of the plugin or theme before it is updated.
*
* @param mixed $response The installation response.
* @param array $hook_extra Array of bulk item update data.
* @return mixed The installation response.
*
* @see https://developer.wordpress.org/reference/hooks/upgrader_pre_install/
*/
public function on_upgrader_pre_install( $response, $hook_extra ) {
// Ensure $hook_extra is an array.
if ( ! is_array( $hook_extra ) ) {
return $response;
}
$plugin_slugs = array();
// Check if the update is for a single plugin.
if ( isset( $hook_extra['plugin'] ) ) {
$plugin_slugs[] = $hook_extra['plugin'];
}
// Check if the update is for multiple plugins.
if ( isset( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ) {
$plugin_slugs = array_merge( $plugin_slugs, $hook_extra['plugins'] );
}
// Iterate through the slugs and store the versions of plugins before they are updated.
foreach ( $plugin_slugs as $slug ) {
$details = $this->get_plugin_details( $slug );
if ( null !== $details ) {
$this->upgraded_extensions['plugins'][ $slug ] = $details['Version'];
} else {
// If the plugin details could not be retrieved, it means that a new plugin will be installed.
$this->upgraded_extensions['plugins'][ $slug ] = null;
}
}
$theme_slugs = array();
// Check if the update is for a single theme.
if ( isset( $hook_extra['theme'] ) ) {
$theme_slugs[] = $hook_extra['theme'];
}
// Check if the update is for multiple themes.
if ( isset( $hook_extra['themes'] ) && is_array( $hook_extra['themes'] ) ) {
$theme_slugs = array_merge( $theme_slugs, $hook_extra['themes'] );
}
// Iterate through the slugs and store the versions of themes before they are updated.
foreach ( $theme_slugs as $slug ) {
$details = $this->get_theme_details( $slug );
if ( null !== $details ) {
$this->upgraded_extensions['themes'][ $slug ] = $details['Version'];
} else {
// If the theme details could not be retrieved, it means that a new theme will be installed.
$this->upgraded_extensions['themes'][ $slug ] = null;
}
}
return $response;
}
/**
* Function to store the package URL once the plugin or theme is installed or updated.
*
* @param bool $response Installation response.
* @param array $hook_extra Extra arguments passed to hooked filters.
* @param array $result Installation result data.
*
* @return bool
*/
public function on_upgrader_post_install( $response, $hook_extra, $result ) {
// If no upgrade operation is in progress, do nothing.
if ( ! isset( $this->package_data['type'] ) ) {
return $response;
}
$slug = $result['destination_name'];
$type = $this->package_data['type'] . 's';
$this->package_data[ $type ][ $slug ] = array(
'slug' => $slug,
'package' => $this->package_data['current_package'],
);
// Reset the current package data.
unset( $this->package_data['type'] );
unset( $this->package_data['current_package'] );
return $response;
}
/**
* If 3rd party code is changing the download URL, we need to store the new URL as the package source.
*
* @param false|array|WP_Error $response A preemptive return value of an HTTP request. Default false.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
*/
public function on_pre_http_request( $response, $args, $url ) {
// If no upgrade operation is in progress, do nothing.
if ( ! isset( $this->package_data['type'] ) ) {
return $response;
}
// Store the final package URL.
$this->package_data['package'] = $url;
return $response;
}
/**
* Handle revision restore.
*
* @param int $post_id The post ID that was restored.
* @param int $revision_id The revision ID that was restored from.
*/
public function on_restore_revision( $post_id, $revision_id ) {
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
$revision = get_post( $revision_id );
if ( ! $revision ) {
return;
}
$ordinal_result = $this->compute_ordinal_position( $post_id, $revision_id );
$date_ts = strtotime( $revision->post_date_gmt );
$seconds_back = false !== $date_ts ? time() - $date_ts : 0;
$parent_post = get_post( $post_id );
$data = array(
'event_type' => 'revision_restore',
'post_id' => (int) $post_id,
'revision_id' => (int) $revision_id,
'revisions_back' => $ordinal_result['position'],
'seconds_back' => max( 0, $seconds_back ),
'total_revisions' => $ordinal_result['total'],
'wp_post_revisions_config' => $this->get_wp_post_revisions_config(),
'autosaves_config' => $this->get_autosaves_config(),
'post_type' => $parent_post ? $parent_post->post_type : 'unknown',
);
$this->prepare_and_send( 'revision', $data );
}
/**
* Handle revision save.
*
* Note: When a revision is restored, WordPress also fires wp_save_post_revision,
* so both on_restore_revision and on_save_revision will emit events. This is
* intentional -- the save event captures the new revision created by the restore.
*
* @param int $revision_id The revision ID.
* @param \WP_Post $revision The revision post object (WP 6.4+; null in earlier versions).
*/
public function on_save_revision( $revision_id, $revision = null ) {
if ( ! Wpe_Event_Utils::is_main_site() ) {
return;
}
if ( null === $revision ) {
$revision = get_post( $revision_id );
}
if ( ! $revision ) {
return;
}
$post_id = $revision->post_parent;
if ( ! $post_id ) {
return;
}
$data = array(
'event_type' => 'revision_save',
'post_id' => (int) $post_id,
'total_revisions' => $this->count_revisions( $post_id ),
'wp_post_revisions_config' => $this->get_wp_post_revisions_config(),
'autosaves_config' => $this->get_autosaves_config(),
'is_autosave' => (bool) wp_is_post_autosave( $revision_id ),
);
$this->prepare_and_send( 'revision', $data );
}
/**
* Register the shutdown hook for sampled request latency, if this request is sampled.
*/
public function maybe_register_latency_hook() {
if ( $this->should_sample_latency() ) {
add_action( 'shutdown', array( $this, 'on_sampled_request_shutdown' ), PHP_INT_MAX );
}
}
/**
* Determine whether the current request should be sampled for latency.
*
* @return bool
*/
protected function should_sample_latency() {
return \wp_rand( 1, 10000 ) <= self::LATENCY_SAMPLE_PER_10K;
}
/**
* Emit a sampled request latency metric at shutdown.
*/
public function on_sampled_request_shutdown() {
$start = isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ? floatval( wp_unslash( $_SERVER['REQUEST_TIME_FLOAT'] ) ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
if ( null === $start ) {
return;
}
$duration_ms = max( 0, ( microtime( true ) - $start ) * 1000 );
// Discard nonsensical values (e.g. malformed REQUEST_TIME_FLOAT or clock skew).
if ( $duration_ms > self::LATENCY_MAX_DURATION_MS ) {
return;
}
$data = array(
'event_type' => 'request_latency_sample',
'duration_ms' => round( $duration_ms, 2 ),
'wp_post_revisions_config' => $this->get_wp_post_revisions_config(),
'autosaves_config' => $this->get_autosaves_config(),
);
// Uses 'revision' key to share the same Telegraf channel as revision events.
$this->prepare_and_send( 'revision', $data );
}
/**
* Compute the ordinal position of a revision in the list of all revisions for a post.
*
* Position 1 = most recent revision, 2 = second most recent, etc.
*
* @param int $post_id The parent post ID.
* @param int $revision_id The revision to find.
* @return array{position: int, total: int} The ordinal position (or REVISION_ORDINAL_OVERFLOW), and total revision count.
*/
public function compute_ordinal_position( $post_id, $revision_id ) {
$revisions = wp_get_post_revisions(
$post_id,
array(
'fields' => 'ids',
'order' => 'DESC',
'posts_per_page' => self::REVISION_ORDINAL_CAP,
)
);
$total = count( $revisions );
$position = 0;
$found = false;
foreach ( $revisions as $rev_id ) {
$position++;
if ( (int) $rev_id === (int) $revision_id ) {
$found = true;
break;
}
}
if ( ! $found ) {
return array(
'position' => self::REVISION_ORDINAL_OVERFLOW,
'total' => $total,
);
}
return array(
'position' => $position,
'total' => $total,
);
}
/**
* Count revisions for a post, capped at REVISION_ORDINAL_CAP.
*
* Returns REVISION_ORDINAL_OVERFLOW if the count exceeds the cap.
*
* @param int $post_id The post ID.
* @return int
*/
private function count_revisions( $post_id ) {
$revisions = wp_get_post_revisions(
$post_id,
array(
'fields' => 'ids',
'posts_per_page' => self::REVISION_ORDINAL_CAP + 1,
)
);
$count = count( $revisions );
return $count > self::REVISION_ORDINAL_CAP ? self::REVISION_ORDINAL_OVERFLOW : $count;
}
/**
* Get the WP_POST_REVISIONS config value.
*
* @return mixed true (unlimited), false (disabled), or int (limit).
*/
private function get_wp_post_revisions_config() {
if ( defined( 'WP_POST_REVISIONS' ) ) {
return WP_POST_REVISIONS;
}
return true;
}
/**
* Read the autosaves config from _wpeprivate/config.json.
*
* Falls back based on WordPress version if the config file or value is missing.
*
* @return int The effective autosaves value (0 = disabled, 250 = default for WP7+).
*/
public function get_autosaves_config() {
if ( null !== $this->autosaves_config ) {
return $this->autosaves_config;
}
$config_path = rtrim( ABSPATH, '/' ) . '/_wpeprivate/config.json';
if ( is_file( $config_path ) ) {
$json = file_get_contents( $config_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( false !== $json ) {
$config = json_decode( $json, true );
if ( is_array( $config ) && isset( $config['autosaves'] ) ) {
$this->autosaves_config = $this->normalize_autosaves_value( $config['autosaves'] );
return $this->autosaves_config;
}
}
}
// Missing value: fall back based on WP version.
global $wp_version;
$this->autosaves_config = version_compare( $wp_version, '7.0', '>=' ) ? 250 : 0;
return $this->autosaves_config;
}
/**
* Normalize autosaves value from config.
*
* @param mixed $val The autosaves value from config (boolean, string, or int).
* @return int The normalized autosaves value.
*/
private function normalize_autosaves_value( $val ) {
if ( true === $val || 'true' === $val ) {
return 250;
} elseif ( false === $val || 'false' === $val ) {
return 0;
}
return (int) $val;
}
/**
* Get current WordPress User
*
* @return WP_User|null
*/
private function get_current_user() {
if ( ! function_exists( 'wp_get_current_user' ) ) {
return null;
}
return wp_get_current_user();
}
/**
* Prepare event data in the Telegraf format and send it
*
* @param array $key The kind of the data to be sent (plugins, themes, WordPress).
* @param array $value The data to be sent.
*/
private function prepare_and_send( $key, $value ) {
// Prepare tags for the Telegraf message.
$tags = array(
// Add common tags.
'install_name' => PWP_NAME,
'cluster_id' => (int) WPE_CLUSTER_ID,
'sync_time' => time(),
);
// Add plugins, themes, or WordPress data.
$tags[ $key ] = $value;
// Prepare context of the message.
$context = array();
// Add user ID if available.
$user = $this->get_current_user();
if ( $user instanceof \WP_User ) {
$context['wp_user_id'] = $user->ID;
// Add user login if available.
if ( ! empty( $user->user_login ) ) {
$context['wp_user_login'] = $user->user_login;
} else {
// If user is unavailable, determine if the event was triggered from WP-CLI or by the system.
$is_wp_cli = defined( 'WP_CLI' ) && WP_CLI && class_exists( 'WP_CLI' );
$context['wp_user_login'] = $is_wp_cli ? 'wpcli' : 'system';
}
}
// Flag autoupdater events.
if ( defined( 'AUTOUPDATER_IN_PROGRESS' ) ) {
$context['autoupdater'] = true;
}
// Add user preference for updates source.
$update_source = get_option( 'wpe_uss_site_preference' );
if ( in_array( $update_source, array( 'wpengine', 'wordpress' ), true ) ) {
$context['primary_update_source'] = $update_source;
}
// Add the context tag if not empty.
if ( ! empty( $context ) ) {
$tags['context'] = $context;
}
// Sort the tags to ensure consistent order.
Wpe_Event_Utils::recursive_ksort( $tags );
// Generate the signature.
$tags['signature'] = Wpe_Event_Utils::generate_hmac_signature( $tags, WP_CACHE_KEY_SALT . ':' . PWP_NAME );
// Encode and escape complex tags for compatibility.
$tags[ $key ] = Wpe_Event_Utils::statsd_encode_and_escape( $value );
if ( ! empty( $context ) ) {
$tags['context'] = Wpe_Event_Utils::statsd_encode_and_escape( $context );
}
// Send the event data to the Telegraf agent.
$this->send( $tags );
}
/**
* Returns true if install is on an Evolve cluster, false otherwise.
*/
private function on_evolve() {
$cluster_id = intval( WPE_CLUSTER_ID );
return $cluster_id >= 200000;
}
/**
* Get the Telegraf host address
*/
private function get_telegraf_host() {
if ( defined( 'WPE_TELEGRAF_HOST' ) ) {
return WPE_TELEGRAF_HOST;
}
if ( $this->on_evolve() ) {
$host = 'pod-' . WPE_CLUSTER_ID . '.pod-' . WPE_CLUSTER_ID . '.svc.cluster.local';
} else {
$host = 'localhost';
}
return $host;
}
/**
* Send data to the Telegraf agent
*
* @param array $tags The tags to be sent.
* @return bool True if the data was sent successfully, false otherwise.
*/
protected function send( $tags ) {
// Get the Telegraf URL and port.
$host = $this->get_telegraf_host();
$port = defined( 'WPE_TELEGRAF_PORT' ) ? WPE_TELEGRAF_PORT : 8127;
// Encode tags to JSON format.
$json_data = wp_json_encode( $tags );
// Send the data to the Telegraf agent.
require_once __DIR__ . '/plugin.php';
return \WpeCommon::http_request_async( 'POST', $host, $port, null, '/telegraf', array( 'Content-Type: application/json' ), 100, $json_data );
}
}