shkola/wp-content/mu-plugins/object-cache-bootstrap.php
Dekart Deploy Bot d247b513d1 fix: re-salt Memcached keys after drop-in load to isolate multi-site cache
- WP_CACHE_KEY_SALT defined in MU-plugin is too late:
  object-cache.php (Memcached drop-in) loads before MU-plugins
- Add muplugins_loaded hook: call salt_keys(DB_NAME) directly
  on the initialized Memcached object cache instance
- Each site gets unique key prefix from its database name
- Prevents cross-site cache collision when multiple WP sites
  share one Memcached server
2026-07-17 19:23:56 +00:00

245 lines
8.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* MU-Plugin: Object Cache Bootstrap
*
* Автоопределение backend кеширования + versioned cache + автоинвалидация.
* Работает с любым active drop-in (object-cache.php).
*
* УСТАНОВКА 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.2
*/
// ============================================================
// 1. КОНСТАНТЫ ДЛЯ ПЛАГИНОВ
// ============================================================
// Уникальная соль для ключей кеша — на основе DB_NAME (разная для каждого сайта).
// Предотвращает коллизию когда несколько WP-сайтов используют один Memcached/Redis.
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' );
}
// Пересолить ключи объектного кеша после загрузки drop-in.
// WP_CACHE_KEY_SALT в MU-плагине — слишком поздно (drop-in грузится раньше).
// Вызываем 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 );
}
if ( ! defined( 'WP_REDIS_PORT' ) ) {
define( 'WP_REDIS_PORT', 6379 );
}
if ( ! defined( 'WP_REDIS_DATABASE' ) ) {
define( 'WP_REDIS_DATABASE', 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
// ============================================================
/**
* Убрать ссылку "Активировать" у плагина Memcached Object Cache.
* Плагин — только источник object-cache.php, активация вызывает фатальную ошибку.
*/
add_filter( 'plugin_action_links_memcached/object-cache.php', function ( $actions ) {
unset( $actions['activate'] );
$actions['info'] = '<span style="color:#999">⚠ установите вручную</span>';
return $actions;
} );
// ============================================================
// 6. АДМИН-НОТИС + БЛОКИРОВКА АКТИВАЦИИ MEMCACHED PLUGIN
// ============================================================
/**
* Убрать ссылку "Активировать" у плагина Memcached Object Cache —
* он содержит только object-cache.php, активация вызывает фатальную ошибку.
* Файл копируется автоматически через dekart_try_install_memcached_dropin().
*/
add_filter( 'plugin_action_links_memcached/object-cache.php', function ( $actions ) {
unset( $actions['activate'] );
$actions['info'] = '<span style="color:#999">drop-in (копируется автоматически)</span>';
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(
'<div class="notice notice-info is-dismissible"><p>%s</p></div>',
'<strong>Кеширование:</strong> Memcached доступен. Установите drop-in командой:
<code>cp ' . esc_html( str_replace( ABSPATH, '', $source ) ) . ' ' . esc_html( str_replace( ABSPATH, '', $target ) ) . '</code>
или через WP-CLI: <code>wp dekart cache-memcached</code>'
);
return;
}
// Есть расширение, но сервер не отвечает
printf(
'<div class="notice notice-warning is-dismissible"><p>%s</p></div>',
'<strong>Кеширование:</strong> PHP-расширение Memcache установлено, но сервер Memcached (127.0.0.1:11211) не отвечает.
Запустите Memcached на сервере или обратитесь в хостинг.'
);
return;
}
printf(
'<div class="notice notice-warning is-dismissible"><p>%s</p></div>',
'<strong>Кеширование:</strong> Плагин Memcached Object Cache установлен, но PHP-расширение <code>memcache</code> не найдено.
Установите его на сервере (через панель хостинга: PHP → Расширения → memcache).
<strong>Не активируйте плагин</strong> — это вызовет фатальную ошибку. Он работает как drop-in.'
);
} );
// ============================================================
// 7. 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.' );
}
} );
}
// ============================================================
// 8. ЛОГИРОВАНИЕ
// ============================================================
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 );
}