chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* TMDO_Postmeta_Cleaner — wp_postmeta garbage cleanup (v2.9.0 Phase 0).
|
||||
*
|
||||
* Identifies and removes three classes of low-value rows from wp_postmeta
|
||||
* that bloat the table without serving any business purpose:
|
||||
*
|
||||
* - transients — stale `_transient_*` and `_transient_timeout_*` rows
|
||||
* (often left by HivePress model version cache)
|
||||
* - wp_old_date — WP core internal record of post date changes (no app value)
|
||||
* - edit_locks — `_edit_lock` rows whose lock timestamp is > 24h old
|
||||
* (orphaned from interrupted edit sessions)
|
||||
*
|
||||
* Runs before any Entity Bridge migration so subsequent ratio measurements
|
||||
* reflect real data, not garbage. Pure DB layer — no Hook Bus / Entity Bridge
|
||||
* coupling so cleanup is safe even when post entity bridge is disabled.
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
* @since 2.9.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wp_postmeta garbage cleanup (v2.9.0 Phase 0).
|
||||
*/
|
||||
class TMDO_Postmeta_Cleaner {
|
||||
|
||||
public const TARGET_TRANSIENTS = 'transients';
|
||||
public const TARGET_WP_OLD_DATE = 'wp_old_date';
|
||||
public const TARGET_EDIT_LOCKS = 'edit_locks';
|
||||
public const TARGET_ALL = 'all';
|
||||
|
||||
public const VALID_TARGETS = array(
|
||||
self::TARGET_TRANSIENTS,
|
||||
self::TARGET_WP_OLD_DATE,
|
||||
self::TARGET_EDIT_LOCKS,
|
||||
self::TARGET_ALL,
|
||||
);
|
||||
|
||||
/**
|
||||
* Seconds after which an _edit_lock is considered stale.
|
||||
* WP refreshes locks on a 15-second heartbeat; 24h is intentionally
|
||||
* conservative to avoid disturbing any active edit session.
|
||||
*/
|
||||
private const EDIT_LOCK_STALE_THRESHOLD = 86400;
|
||||
|
||||
/**
|
||||
* Count rows that would be cleaned for the given target.
|
||||
*
|
||||
* @param string $target One of TARGET_* constants.
|
||||
* @return array{transients:int, wp_old_date:int, edit_locks:int, total:int}
|
||||
* @throws InvalidArgumentException When $target is not a valid target.
|
||||
*/
|
||||
public static function count_garbage( string $target = self::TARGET_ALL ): array {
|
||||
self::assert_valid_target( $target );
|
||||
|
||||
$counts = array(
|
||||
'transients' => 0,
|
||||
'wp_old_date' => 0,
|
||||
'edit_locks' => 0,
|
||||
'total' => 0,
|
||||
);
|
||||
|
||||
if ( self::target_includes( $target, self::TARGET_TRANSIENTS ) ) {
|
||||
$counts['transients'] = self::count_transients();
|
||||
}
|
||||
if ( self::target_includes( $target, self::TARGET_WP_OLD_DATE ) ) {
|
||||
$counts['wp_old_date'] = self::count_wp_old_date();
|
||||
}
|
||||
if ( self::target_includes( $target, self::TARGET_EDIT_LOCKS ) ) {
|
||||
$counts['edit_locks'] = self::count_stale_edit_locks();
|
||||
}
|
||||
|
||||
$counts['total'] = $counts['transients'] + $counts['wp_old_date'] + $counts['edit_locks'];
|
||||
return $counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete garbage rows for the given target.
|
||||
*
|
||||
* @param string $target One of TARGET_* constants.
|
||||
* @return array{transients:int, wp_old_date:int, edit_locks:int, total:int}
|
||||
* @throws InvalidArgumentException When $target is not a valid target.
|
||||
*/
|
||||
public static function delete_garbage( string $target = self::TARGET_ALL ): array {
|
||||
self::assert_valid_target( $target );
|
||||
|
||||
$deleted = array(
|
||||
'transients' => 0,
|
||||
'wp_old_date' => 0,
|
||||
'edit_locks' => 0,
|
||||
'total' => 0,
|
||||
);
|
||||
|
||||
if ( self::target_includes( $target, self::TARGET_TRANSIENTS ) ) {
|
||||
$deleted['transients'] = self::delete_transients();
|
||||
}
|
||||
if ( self::target_includes( $target, self::TARGET_WP_OLD_DATE ) ) {
|
||||
$deleted['wp_old_date'] = self::delete_wp_old_date();
|
||||
}
|
||||
if ( self::target_includes( $target, self::TARGET_EDIT_LOCKS ) ) {
|
||||
$deleted['edit_locks'] = self::delete_stale_edit_locks();
|
||||
}
|
||||
|
||||
$deleted['total'] = $deleted['transients'] + $deleted['wp_old_date'] + $deleted['edit_locks'];
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether $target selects $bucket (i.e. target=all or target=bucket).
|
||||
*
|
||||
* @param string $target Selected target.
|
||||
* @param string $bucket Bucket constant (TARGET_TRANSIENTS / WP_OLD_DATE / EDIT_LOCKS).
|
||||
* @return bool
|
||||
*/
|
||||
private static function target_includes( string $target, string $bucket ): bool {
|
||||
return self::TARGET_ALL === $target || $bucket === $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate target parameter.
|
||||
*
|
||||
* @param string $target Target to validate.
|
||||
* @return void
|
||||
* @throws InvalidArgumentException When $target is not in VALID_TARGETS.
|
||||
*/
|
||||
private static function assert_valid_target( string $target ): void {
|
||||
if ( in_array( $target, self::VALID_TARGETS, true ) ) {
|
||||
return;
|
||||
}
|
||||
// Exception messages bubble up to PHP's error handler / WP_CLI; not user output.
|
||||
$msg = sprintf( 'Invalid target "%s". Valid: %s', $target, implode( ', ', self::VALID_TARGETS ) );
|
||||
throw new InvalidArgumentException( $msg ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Internal cleanup class: $wpdb->postmeta is WP-managed and never user input. PreparedSQL.InterpolatedNotPrepared fires on multi-line $wpdb->prepare() literals where the only interpolation is the trusted table name; user-controlled values use placeholders.
|
||||
|
||||
/**
|
||||
* Count rows matching transient meta_key patterns in wp_postmeta.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_transients(): int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->postmeta;
|
||||
return (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM `{$table}` WHERE meta_key LIKE '\\_transient\\_%' OR meta_key LIKE '\\_transient\\_timeout\\_%'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete rows matching transient meta_key patterns from wp_postmeta.
|
||||
*
|
||||
* @return int Affected row count.
|
||||
*/
|
||||
private static function delete_transients(): int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->postmeta;
|
||||
return (int) $wpdb->query(
|
||||
"DELETE FROM `{$table}` WHERE meta_key LIKE '\\_transient\\_%' OR meta_key LIKE '\\_transient\\_timeout\\_%'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count _wp_old_date rows in wp_postmeta.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_wp_old_date(): int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->postmeta;
|
||||
return (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM `{$table}` WHERE meta_key = '_wp_old_date'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete _wp_old_date rows from wp_postmeta.
|
||||
*
|
||||
* @return int Affected row count.
|
||||
*/
|
||||
private static function delete_wp_old_date(): int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->postmeta;
|
||||
return (int) $wpdb->query(
|
||||
"DELETE FROM `{$table}` WHERE meta_key = '_wp_old_date'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count stale _edit_lock rows (lock_ts older than 24h) in wp_postmeta.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function count_stale_edit_locks(): int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->postmeta;
|
||||
$cutoff = time() - self::EDIT_LOCK_STALE_THRESHOLD;
|
||||
// _edit_lock format is "<unix_ts>:<user_id>"; SUBSTRING_INDEX extracts the timestamp.
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM `{$table}` WHERE meta_key = '_edit_lock' AND CAST(SUBSTRING_INDEX(meta_value, ':', 1) AS UNSIGNED) < %d",
|
||||
$cutoff
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete stale _edit_lock rows (lock_ts older than 24h) from wp_postmeta.
|
||||
*
|
||||
* @return int Affected row count.
|
||||
*/
|
||||
private static function delete_stale_edit_locks(): int {
|
||||
global $wpdb;
|
||||
$table = $wpdb->postmeta;
|
||||
$cutoff = time() - self::EDIT_LOCK_STALE_THRESHOLD;
|
||||
return (int) $wpdb->query(
|
||||
$wpdb->prepare(
|
||||
"DELETE FROM `{$table}` WHERE meta_key = '_edit_lock' AND CAST(SUBSTRING_INDEX(meta_value, ':', 1) AS UNSIGNED) < %d",
|
||||
$cutoff
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
}
|
||||
Reference in New Issue
Block a user