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,285 @@
|
||||
<?php
|
||||
/**
|
||||
* TMDO_Options_Manager - wp_options 反 EAV 模組
|
||||
*
|
||||
* 功能:
|
||||
* - autoload 最佳化(掃描並 defer 過大的 autoload 項目)
|
||||
* - 選項重導向(將高頻選項搬至專屬設定表)
|
||||
* - 快取攔截
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
*/
|
||||
|
||||
// phpcs:disable Squiz.Commenting,Generic.Commenting,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,Generic.CodeAnalysis.UnusedFunctionParameter,Generic.CodeAnalysis.EmptyStatement,Squiz.PHP.DisallowMultipleAssignments,Squiz.PHP.DisallowSizeFunctionsInLoops,WordPress.WP.I18n.MissingTranslatorsComment,WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents,Squiz.PHP.CommentedOutCode,Universal.NamingConventions.NoReservedKeywordParameterNames,WordPress.PHP.YodaConditions,Squiz.Commenting.InlineComment.InvalidEndChar -- PR-1 ported from UAE; cleanup PR scheduled.
|
||||
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
final class TMDO_Options_Manager {
|
||||
|
||||
/** 管理中的選項設定 */
|
||||
private static array $redirected_options = array();
|
||||
|
||||
/** 已知可安全 defer autoload 的選項名稱清單 */
|
||||
private const KNOWN_DEFERRABLE_OPTIONS = array(
|
||||
// 大型設定類(不需頁面載入即時讀取)
|
||||
'wpseo_titles',
|
||||
'wpseo_social',
|
||||
'wpseo_internallinks',
|
||||
'wpseo_xml',
|
||||
'wpseo_flush_rewrite',
|
||||
|
||||
// WooCommerce 管理端設定
|
||||
'woocommerce_tax_classes',
|
||||
'woocommerce_shipping_debug_mode',
|
||||
'woocommerce_schema_version',
|
||||
|
||||
// 其他常見巨型選項
|
||||
'rewrite_rules',
|
||||
'_transient_doing_cron',
|
||||
'cron',
|
||||
|
||||
// 備份/匯出類
|
||||
'updraft_backup_history',
|
||||
'wpb_backup_options',
|
||||
);
|
||||
|
||||
/** autoload 合計閾值(MB),超過則警告 */
|
||||
private const AUTOLOAD_WARNING_THRESHOLD_MB = 1;
|
||||
|
||||
/** 單一選項大小閾值(bytes) */
|
||||
private const SINGLE_OPTION_SIZE_THRESHOLD = 51200; // 50KB
|
||||
|
||||
public static function init(): void {
|
||||
// 提供第三方登錄 API
|
||||
/**
|
||||
* 讓第三方外掛註冊要管理的選項
|
||||
* add_action('wpdo_register_option_groups', function() {...});
|
||||
*/
|
||||
do_action( 'wpdo_register_option_groups' );
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Autoload 分析與最佳化
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 分析當前 autoload 狀況
|
||||
*/
|
||||
public static function analyze_autoload(): array {
|
||||
global $wpdb;
|
||||
|
||||
// 總體 autoload 大小
|
||||
$total = $wpdb->get_row(
|
||||
"
|
||||
SELECT
|
||||
COUNT(*) as cnt,
|
||||
SUM(LENGTH(option_value)) as total_bytes
|
||||
FROM {$wpdb->options}
|
||||
WHERE autoload = 'yes'
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// 最大的 50 個 autoload 選項
|
||||
$largest = $wpdb->get_results(
|
||||
"
|
||||
SELECT
|
||||
option_name,
|
||||
LENGTH(option_value) AS size_bytes,
|
||||
autoload
|
||||
FROM {$wpdb->options}
|
||||
WHERE autoload = 'yes'
|
||||
ORDER BY size_bytes DESC
|
||||
LIMIT 50
|
||||
",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// 可 defer 的候選項
|
||||
$deferrable_candidates = array();
|
||||
foreach ( $largest as $row ) {
|
||||
if ( (int) $row['size_bytes'] > self::SINGLE_OPTION_SIZE_THRESHOLD ||
|
||||
in_array( $row['option_name'], self::KNOWN_DEFERRABLE_OPTIONS, true )
|
||||
) {
|
||||
$deferrable_candidates[] = $row;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'total_count' => (int) $total['cnt'],
|
||||
'total_bytes' => (int) $total['total_bytes'],
|
||||
'total_mb' => round( $total['total_bytes'] / 1024 / 1024, 2 ),
|
||||
'warning' => (int) $total['total_bytes'] > self::AUTOLOAD_WARNING_THRESHOLD_MB * 1024 * 1024,
|
||||
'largest' => $largest,
|
||||
'deferrable' => $deferrable_candidates,
|
||||
'estimated_save_mb' => round(
|
||||
array_sum( array_column( $deferrable_candidates, 'size_bytes' ) ) / 1024 / 1024,
|
||||
2
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 執行 autoload 最佳化
|
||||
*
|
||||
* @param array $options_to_defer 要設為 autoload=no 的選項名稱陣列
|
||||
* 若為空,使用內建安全清單
|
||||
* @param bool $dry_run
|
||||
*/
|
||||
public static function optimize_autoload( array $options_to_defer = array(), bool $dry_run = false ): array {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $options_to_defer ) ) {
|
||||
$options_to_defer = self::KNOWN_DEFERRABLE_OPTIONS;
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'dry_run' => $dry_run,
|
||||
'processed' => 0,
|
||||
'saved_bytes' => 0,
|
||||
'details' => array(),
|
||||
);
|
||||
|
||||
foreach ( $options_to_defer as $option_name ) {
|
||||
$row = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_id, LENGTH(option_value) AS size_bytes, autoload
|
||||
FROM {$wpdb->options}
|
||||
WHERE option_name = %s",
|
||||
$option_name
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( ! $row ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $row['autoload'] === 'no' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $dry_run ) {
|
||||
$wpdb->update(
|
||||
$wpdb->options,
|
||||
array( 'autoload' => 'no' ),
|
||||
array( 'option_name' => $option_name ),
|
||||
array( '%s' ),
|
||||
array( '%s' )
|
||||
);
|
||||
|
||||
// 清除該選項的快取(下次讀取時會重建)
|
||||
wp_cache_delete( $option_name, 'options' );
|
||||
wp_cache_delete( 'alloptions', 'options' );
|
||||
}
|
||||
|
||||
++$result['processed'];
|
||||
$result['saved_bytes'] += (int) $row['size_bytes'];
|
||||
$result['details'][] = array(
|
||||
'option_name' => $option_name,
|
||||
'saved_bytes' => (int) $row['size_bytes'],
|
||||
);
|
||||
}
|
||||
|
||||
$result['saved_mb'] = round( $result['saved_bytes'] / 1024 / 1024, 2 );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// 選項群組重導向
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 將一組選項重導向至專屬設定表
|
||||
*
|
||||
* @param string $group_name 設定群組名
|
||||
* @param array $option_keys 要管理的 option_name 清單
|
||||
*/
|
||||
public static function register_settings_group( string $group_name, array $option_keys ): void {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->prefix . TMDO_TABLE_PREFIX . 'settings_' . sanitize_key( $group_name );
|
||||
|
||||
// 建立設定專屬表
|
||||
$charset = $wpdb->get_charset_collate();
|
||||
$sql = "CREATE TABLE {$table} (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
setting_key VARCHAR(191) NOT NULL,
|
||||
setting_value LONGTEXT,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_key (setting_key)
|
||||
) {$charset};";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta( $sql );
|
||||
|
||||
// 註冊為管理中選項
|
||||
foreach ( $option_keys as $key ) {
|
||||
self::$redirected_options[ $key ] = array(
|
||||
'group' => $group_name,
|
||||
'table' => $table,
|
||||
);
|
||||
|
||||
// 攔截讀取
|
||||
add_filter(
|
||||
"pre_option_{$key}",
|
||||
function ( $value ) use ( $key, $table ) {
|
||||
return self::read_setting( $table, $key, $value );
|
||||
},
|
||||
10,
|
||||
1
|
||||
);
|
||||
|
||||
// 攔截寫入:寫入 UAE 表,並回傳 $old_value 使 WP 跳過寫 wp_options
|
||||
add_filter(
|
||||
"pre_update_option_{$key}",
|
||||
function ( $value, $old_value ) use ( $key, $table ) {
|
||||
self::write_setting( $table, $key, $value );
|
||||
// 回傳 $old_value 會讓 update_option() 判定「值未變動」進而跳過 wp_options 寫入
|
||||
return $old_value;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function read_setting( string $table, string $key, $default ) {
|
||||
global $wpdb;
|
||||
|
||||
$value = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT setting_value FROM `{$table}` WHERE setting_key = %s",
|
||||
$key
|
||||
)
|
||||
);
|
||||
|
||||
if ( $value === null ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
// v2.13.3: object-injection-safe unserialize (fixes L-DESER-1).
|
||||
$decoded = TMDO_Safe_Unserialize::run( $value );
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
private static function write_setting( string $table, string $key, $value ): void {
|
||||
global $wpdb;
|
||||
|
||||
$wpdb->replace(
|
||||
$table,
|
||||
array(
|
||||
'setting_key' => $key,
|
||||
'setting_value' => maybe_serialize( $value ),
|
||||
),
|
||||
array( '%s', '%s' )
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_redirected_options(): array {
|
||||
return self::$redirected_options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Silence is golden.
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
Reference in New Issue
Block a user