Files

338 lines
8.2 KiB
PHP

<?php
/**
* Cache Table Class
*
* Manages the cache database table for MonsterInsights.
* Provides optimized storage for report data, API responses, and other cached content.
*
* @since 9.11.0
* @package MonsterInsights
*/
// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Cache Table class.
*
* @since 9.11.0
*/
class MonsterInsights_Cache_Table extends MonsterInsights_DB_Base {
/**
* Table name (without prefix).
*
* @since 9.11.0
* @var string
*/
protected $table_name = 'monsterinsights_cache';
/**
* Table version.
*
* @since 9.11.0
* @var string
*/
protected $version = '1.0.0';
/**
* Primary key column.
*
* @since 9.11.0
* @var string
*/
protected $primary_key = 'cache_id';
/**
* Get the table schema SQL.
*
* @since 9.11.0
* @return string CREATE TABLE SQL statement.
*/
public function get_schema() {
global $wpdb;
$table_name = $this->get_table_name();
$charset_collate = $wpdb->get_charset_collate();
return "CREATE TABLE {$table_name} (
cache_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
cache_key VARCHAR(255) NOT NULL,
cache_value LONGTEXT NOT NULL,
cache_group VARCHAR(64) DEFAULT 'default',
expires_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (cache_id),
UNIQUE KEY cache_key_group (cache_key, cache_group),
KEY expires_at (expires_at),
KEY created_at (created_at)
) {$charset_collate};";
}
/**
* Get array of column names and their definitions.
*
* @since 9.11.0
* @return array Array of column definitions.
*/
public function get_columns() {
return array(
'cache_id' => 'BIGINT UNSIGNED NOT NULL AUTO_INCREMENT',
'cache_key' => 'VARCHAR(255) NOT NULL',
'cache_value' => 'LONGTEXT NOT NULL',
'cache_group' => 'VARCHAR(64) DEFAULT \'default\'',
'expires_at' => 'DATETIME NOT NULL',
'created_at' => 'DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP',
);
}
/**
* Get a cached value.
*
* @since 9.11.0
* @param string $key Cache key.
* @param string $group Cache group (default 'default').
* @return mixed|false Cached value or false if not found/expired.
*/
public function get_cache( $key, $group = 'default' ) {
global $wpdb;
$table_name = $this->get_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
$result = $wpdb->get_row(
$wpdb->prepare(
"SELECT cache_value, expires_at FROM {$table_name}
WHERE cache_key = %s AND cache_group = %s AND expires_at > NOW()",
$key,
$group
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( ! $result ) {
return false;
}
return maybe_unserialize( $result->cache_value );
}
/**
* Set a cached value.
*
* @since 9.11.0
* @param string $key Cache key.
* @param mixed $value Value to cache.
* @param string $group Cache group (default 'default').
* @param int $expiration Expiration in seconds (default 3600).
* @return bool True on success, false on failure.
*/
public function set_cache( $key, $value, $group = 'default', $expiration = 3600 ) {
global $wpdb;
$table_name = $this->get_table_name();
// Calculate expiration datetime
$expires_at = gmdate( 'Y-m-d H:i:s', time() + $expiration );
// Serialize value
$serialized = maybe_serialize( $value );
// Use REPLACE to handle insert or update
$result = $wpdb->replace(
$table_name,
array(
'cache_key' => $key,
'cache_group' => $group,
'cache_value' => $serialized,
'expires_at' => $expires_at,
'created_at' => current_time( 'mysql' ),
),
array( '%s', '%s', '%s', '%s', '%s' )
);
return $result !== false;
}
/**
* Delete a cached value.
*
* @since 9.11.0
* @param string $key Cache key.
* @param string $group Cache group (default 'default').
* @return bool True on success, false on failure.
*/
public function delete_cache( $key, $group = 'default' ) {
global $wpdb;
$table_name = $this->get_table_name();
$result = $wpdb->delete(
$table_name,
array(
'cache_key' => $key,
'cache_group' => $group,
),
array( '%s', '%s' )
);
return $result !== false;
}
/**
* Flush all cached values in a group.
*
* @since 9.11.0
* @param string $group Cache group (default 'default').
* @return bool True on success, false on failure.
*/
public function flush_group( $group = 'default' ) {
global $wpdb;
$table_name = $this->get_table_name();
$result = $wpdb->delete(
$table_name,
array( 'cache_group' => $group ),
array( '%s' )
);
return $result !== false;
}
/**
* Flush all cached values.
*
* @since 9.11.0
* @return bool True on success, false on failure.
*/
public function flush_all() {
return $this->truncate();
}
/**
* Clean up expired cache entries.
*
* @since 9.11.0
* @return int Number of entries deleted.
*/
public function cleanup_expired() {
global $wpdb;
$table_name = $this->get_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
$deleted = $wpdb->query(
"DELETE FROM {$table_name} WHERE expires_at < NOW()"
);
// Optimize table if significant deletions
if ( $deleted > 100 ) {
$wpdb->query( "OPTIMIZE TABLE {$table_name}" );
}
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return $deleted;
}
/**
* Get cache statistics.
*
* @since 9.11.0
* @return array {
* Cache statistics.
*
* @type int $total_entries Total cache entries.
* @type int $valid_entries Valid (not expired) entries.
* @type int $expired_entries Expired entries.
* @type int $total_size Total size in bytes.
* @type array $by_group Entries grouped by cache_group.
* }
*/
public function get_stats() {
global $wpdb;
$table_name = $this->get_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
// Overall stats
$stats = $wpdb->get_row(
"SELECT
COUNT(*) as total_entries,
SUM(CASE WHEN expires_at > NOW() THEN 1 ELSE 0 END) as valid_entries,
SUM(CASE WHEN expires_at <= NOW() THEN 1 ELSE 0 END) as expired_entries,
SUM(LENGTH(cache_value)) as total_size
FROM {$table_name}",
ARRAY_A
);
// Stats by group
$by_group = $wpdb->get_results(
"SELECT
cache_group,
COUNT(*) as entries,
SUM(LENGTH(cache_value)) as size
FROM {$table_name}
GROUP BY cache_group",
ARRAY_A
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$stats['by_group'] = $by_group;
return $stats;
}
/**
* Check if a cache key exists (not expired).
*
* @since 9.11.0
* @param string $key Cache key.
* @param string $group Cache group (default 'default').
* @return bool True if exists and not expired, false otherwise.
*/
public function exists( $key, $group = 'default' ) {
global $wpdb;
$table_name = $this->get_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$table_name}
WHERE cache_key = %s AND cache_group = %s AND expires_at > NOW()",
$key,
$group
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return (int) $result > 0;
}
/**
* Get the remaining time to live for a cache entry.
*
* @since 9.11.0
* @param string $key Cache key.
* @param string $group Cache group (default 'default').
* @return int|false Time to live in seconds, or false if not found/expired.
*/
public function get_ttl( $key, $group = 'default' ) {
global $wpdb;
$table_name = $this->get_table_name();
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safely constructed.
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT TIMESTAMPDIFF(SECOND, NOW(), expires_at) FROM {$table_name}
WHERE cache_key = %s AND cache_group = %s AND expires_at > NOW()",
$key,
$group
)
);
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( $result === null ) {
return false;
}
return max( 0, (int) $result );
}
}