From a6222a31ab009a16a1eecd9da2e7f98eb1721948 Mon Sep 17 00:00:00 2001 From: Dekart Deploy Bot Date: Fri, 17 Jul 2026 20:07:16 +0000 Subject: [PATCH] fix: restore MU-plugin cache logic with WP_CACHE_KEY_SALT isolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Root cause: two prod sites share one Memcached, no WP_CACHE_KEY_SALT → cache keys collide → data mixes between sites - Primary fix: WP_CACHE_KEY_SALT defined via DB_NAME in wp-config.php (manual on each prod, file is in .gitignore) - MU-plugin restored: auto-detect backend, versioned keys, auto-invalidation - Added re-salt fallback via salt_keys(DB_NAME) on muplugins_loaded - Removed duplicate admin_notice hooks --- .../mu-plugins/object-cache-bootstrap.php | 219 +++++++++++++++++- 1 file changed, 217 insertions(+), 2 deletions(-) diff --git a/wp-content/mu-plugins/object-cache-bootstrap.php b/wp-content/mu-plugins/object-cache-bootstrap.php index 2c95e22..0dfac15 100644 --- a/wp-content/mu-plugins/object-cache-bootstrap.php +++ b/wp-content/mu-plugins/object-cache-bootstrap.php @@ -2,16 +2,231 @@ /** * MU-Plugin: Object Cache Bootstrap * - * Кеширование полностью отключено — все функции закомментированы. - * Проблема с перекрёстными данными между сайтами на проде. + * Поддержка объектного кеша (Memcached/Redis) с изоляцией по сайтам. + * + * ИЗОЛЯЦИЯ КЛЮЧЕЙ (защита от смешивания данных между сайтами): + * WP_CACHE_KEY_SALT задаётся в wp-config.php на основе DB_NAME. + * Это гарантирует уникальные ключи для каждого сайта ДО загрузки + * object-cache.php (drop-in). Если в wp-config.php соль не задана, + * MU-плагин пробует выставить её через fallback + re-salt хаки. + * + * УСТАНОВКА MEMCACHED НА ПРОДЕ: + * 1. Установить PHP-расширение: sudo apt install php-memcache + * 2. wp plugin install memcached + * 3. Скопировать drop-in: + * cp wp-content/plugins/memcached/object-cache.php wp-content/object-cache.php + * 4. Плагин НЕ активировать. * * @package Dekart * @version 2.3 */ +// ============================================================ +// 1. КОНСТАНТЫ — fallback, если не заданы в wp-config.php +// ============================================================ + +// WP_CACHE_KEY_SALT должна быть определена в wp-config.php. +// Если нет — пробуем задать здесь (хуже, но хоть что-то). +if ( ! defined( 'WP_CACHE_KEY_SALT' ) && defined( 'DB_NAME' ) ) { + define( 'WP_CACHE_KEY_SALT', DB_NAME ); +} + if ( ! defined( 'WP_REDIS_HOST' ) ) { define( 'WP_REDIS_HOST', '127.0.0.1' ); } if ( ! defined( 'WP_REDIS_PORT' ) ) { define( 'WP_REDIS_PORT', 6379 ); } +if ( ! defined( 'WP_REDIS_DATABASE' ) ) { + define( 'WP_REDIS_DATABASE', 0 ); +} + +// Re-salt для Memcached drop-in: WP_CACHE_KEY_SALT в MU-плагине +// слишком поздна (object-cache.php грузится раньше MU). +// Если у object-cache.php есть salt_keys() — вызываем напрямую. +if ( defined( 'DB_NAME' ) ) { + add_action( 'muplugins_loaded', function () { + global $wp_object_cache; + if ( isset( $wp_object_cache ) && method_exists( $wp_object_cache, 'salt_keys' ) ) { + $wp_object_cache->salt_keys( DB_NAME ); + } + }, 0 ); +} + +// ============================================================ +// 2. АВТООПРЕДЕЛЕНИЕ BACKEND +// ============================================================ + +function dekart_detect_cache_backend(): string { + if ( class_exists( 'Memcache' ) || class_exists( 'Memcached' ) ) { + $sock = @fsockopen( '127.0.0.1', 11211, $errno, $errstr, 0.5 ); + if ( $sock ) { + fclose( $sock ); + return 'memcached'; + } + } + + if ( class_exists( 'Predis\Client' ) ) { + $sock = @fsockopen( '127.0.0.1', 6379, $errno, $errstr, 0.5 ); + if ( $sock ) { + fclose( $sock ); + return 'redis'; + } + } + + if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) { + return 'dropin-no-connection'; + } + + return 'none'; +} + +// ============================================================ +// 3. VERSIONED CACHE KEY +// ============================================================ + +function my_cache_version(): int { + return (int) get_option( 'my_cache_version', 1 ); +} + +function my_cache_key( string $key ): string { + return 'v:' . my_cache_version() . ':' . $key; +} + +// ============================================================ +// 4. ИНВАЛИДАЦИЯ КЕША +// ============================================================ + +function my_cache_bump_version(): void { + static $running = false; + if ( $running ) { + return; + } + $running = true; + + wp_cache_flush(); + $v = my_cache_version(); + update_option( 'my_cache_version', $v + 1, false ); + + $running = false; +} + +add_action( 'update_option', function ( $option ) { + if ( str_starts_with( $option, 'branch_' ) ) { + my_cache_bump_version(); + } +}, 10, 1 ); + +add_action( 'save_post', 'my_cache_bump_version' ); +add_action( 'carbon_fields_post_meta_container_saved', 'my_cache_bump_version' ); + +// ============================================================ +// 5. АДМИН-НОТИСЫ +// ============================================================ + +/** + * Убрать ссылку "Активировать" у плагина Memcached Object Cache. + * Плагин — только источник object-cache.php, активация вызывает фатальную ошибку. + */ +add_filter( 'plugin_action_links_memcached/object-cache.php', function ( $actions ) { + unset( $actions['activate'] ); + $actions['info'] = '⚠ установите drop-in вручную'; + return $actions; +} ); + +add_action( 'admin_notices', function () { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + $source = WP_CONTENT_DIR . '/plugins/memcached/object-cache.php'; + $target = WP_CONTENT_DIR . '/object-cache.php'; + + if ( ! file_exists( $source ) ) { + return; // плагин не установлен + } + if ( file_exists( $target ) ) { + return; // drop-in уже стоит + } + + if ( class_exists( 'Memcache' ) || class_exists( 'Memcached' ) ) { + $conn = @fsockopen( '127.0.0.1', 11211, $errno, $errstr, 0.5 ); + if ( $conn ) { + fclose( $conn ); + printf( + '

%s

', + 'Кеширование: Memcached доступен. Установите drop-in командой: + cp ' . esc_html( str_replace( ABSPATH, '', $source ) ) . ' ' . esc_html( str_replace( ABSPATH, '', $target ) ) . ' + или через WP-CLI: wp dekart cache-memcached' + ); + return; + } + printf( + '

%s

', + 'Кеширование: PHP-расширение Memcache установлено, но сервер Memcached (127.0.0.1:11211) не отвечает. + Запустите Memcached на сервере или обратитесь в хостинг.' + ); + return; + } + + printf( + '

%s

', + 'Кеширование: Плагин Memcached Object Cache установлен, но PHP-расширение memcache не найдено. + Установите его на сервере (через панель хостинга: PHP → Расширения → memcache). + Не активируйте плагин — он работает как drop-in.' + ); +} ); + +// ============================================================ +// 6. WP-CLI +// ============================================================ + +if ( defined( 'WP_CLI' ) && WP_CLI ) { + + WP_CLI::add_command( 'dekart cache-flush', function () { + my_cache_bump_version(); + WP_CLI::success( 'Cache flushed + version bumped.' ); + } ); + + WP_CLI::add_command( 'dekart cache-memcached', function () { + $source = WP_CONTENT_DIR . '/plugins/memcached/object-cache.php'; + $target = WP_CONTENT_DIR . '/object-cache.php'; + + if ( ! file_exists( $source ) ) { + WP_CLI::error( 'Memcached Object Cache plugin not installed. Run: wp plugin install memcached' ); + return; + } + + if ( file_exists( $target ) ) { + WP_CLI::success( 'Drop-in already exists at wp-content/object-cache.php' ); + return; + } + + if ( ! class_exists( 'Memcache' ) && ! class_exists( 'Memcached' ) ) { + WP_CLI::warning( 'PHP memcache extension not found. Install it:' ); + WP_CLI::line( ' sudo apt install php-memcache' ); + WP_CLI::line( ' sudo systemctl restart php8.3-fpm' ); + WP_CLI::line( 'Then run this command again.' ); + return; + } + + if ( copy( $source, $target ) ) { + WP_CLI::success( 'Memcached drop-in installed to wp-content/object-cache.php' ); + } else { + WP_CLI::error( 'Failed to copy drop-in. Check permissions.' ); + } + } ); +} + +// ============================================================ +// 7. ЛОГИРОВАНИЕ +// ============================================================ + +if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + add_action( 'init', function () { + $b = dekart_detect_cache_backend(); + if ( 'none' === $b ) { + error_log( '[Object Cache] WARNING: No cache backend available.' ); + } + }, 0 ); +}