feat(batch21): LiteSpeed Cache deep — detect, options read/set+restore, granular purge (guarded)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8t9GEyyLZbUQu26na1JQg
This commit is contained in:
2026-10-16 02:42:03 +00:00
parent 29367e06a2
commit ebf3ba6ace
+139
View File
@@ -0,0 +1,139 @@
<?php
/**
* Batch 21 — LiteSpeed Cache deep (Pro-equivalent, net-new).
*
* Deeper LiteSpeed control beyond the generic b17 cache family: version/detect,
* read & set LiteSpeed options (with reversible backup), and granular purge
* (all / by URL / by post / by tag). Uses LiteSpeed's documented action hooks
* and its `litespeed.conf.*` option store. Guarded by LSCWP_V / LSCWP_CUR_V.
* GPL-2.0-or-later. See NOTICE.md.
*/
namespace AskTamMCP\Extensions;
defined('ABSPATH') || exit;
class Batch21 {
private const OPT_PREFIX = 'litespeed.conf.';
private const BACKUP = 'amp_ls_backup';
public static function register($registry): void {
if (!defined('LSCWP_V') && !defined('LSCWP_CUR_V')) return;
$def = function (string $name, string $desc, array $props, array $required, string $scope, string $method) {
$schema = ['type' => 'object', 'properties' => $props];
if ($required) $schema['required'] = $required;
return ['name' => $name, 'description' => $desc, 'scope' => $scope,
'handler' => 'AskTamMCP\\Extensions\\Batch21::' . $method, 'inputSchema' => $schema];
};
$registry->register($def('litespeed_detect', 'Report LiteSpeed Cache version and whether object cache / crawler are active.',
[], [], 'read', 'detect'));
$registry->register($def('litespeed_list_options', 'List LiteSpeed Cache settings (litespeed.conf.* options), optionally filtered by key substring.',
['filter' => ['type' => 'string'], 'limit' => ['type' => 'integer', 'default' => 200]], [], 'read', 'listOptions'));
$registry->register($def('litespeed_set_option', 'Set one LiteSpeed Cache setting. key may be given with or without the litespeed.conf. prefix. value is parsed as JSON when valid. Prior value auto-backed-up for litespeed_restore_option.',
['key' => ['type' => 'string'], 'value' => ['type' => 'string']], ['key', 'value'], 'write', 'setOption'));
$registry->register($def('litespeed_restore_option', 'Restore one LiteSpeed setting to the value saved before the last litespeed_set_option.',
['key' => ['type' => 'string']], ['key'], 'write', 'restoreOption'));
$registry->register($def('litespeed_purge', 'Purge LiteSpeed cache. scope = all (default) | url | post | tag. Provide url, post_id, or tag accordingly.',
['scope' => ['type' => 'string', 'enum' => ['all', 'url', 'post', 'tag']], 'url' => ['type' => 'string'], 'post_id' => ['type' => 'integer'], 'tag' => ['type' => 'string']],
[], 'write', 'purge'));
}
private static function fullKey(string $key): string {
return strpos($key, self::OPT_PREFIX) === 0 ? $key : self::OPT_PREFIX . $key;
}
public static function detect(array $args): array {
return [
'version' => defined('LSCWP_V') ? LSCWP_V : (defined('LSCWP_CUR_V') ? LSCWP_CUR_V : 'unknown'),
'object_cache' => wp_using_ext_object_cache(),
'crawler' => (bool) get_option(self::OPT_PREFIX . 'crawler', false),
];
}
public static function listOptions(array $args): array {
global $wpdb;
$filter = isset($args['filter']) ? (string) $args['filter'] : '';
$limit = max(1, (int) ($args['limit'] ?? 200));
$like = self::OPT_PREFIX . '%';
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s ORDER BY option_name LIMIT %d",
$like, $limit
), ARRAY_A);
$out = [];
foreach ((array) $rows as $r) {
$k = substr($r['option_name'], strlen(self::OPT_PREFIX));
if ($filter !== '' && stripos($k, $filter) === false) continue;
$v = maybe_unserialize($r['option_value']);
$out[] = ['key' => $k, 'value' => is_scalar($v) ? $v : $v];
}
return ['count' => count($out), 'options' => $out];
}
public static function setOption(array $args): array {
$key = (string) ($args['key'] ?? '');
if ($key === '') throw new \RuntimeException('key required.');
$opt = self::fullKey($key);
$raw = (string) ($args['value'] ?? '');
$decoded = json_decode($raw, true);
$value = (json_last_error() === JSON_ERROR_NONE && ($raw === 'null' || $decoded !== null)) ? $decoded : $raw;
$had = (get_option($opt, '__amp_absent__') !== '__amp_absent__');
$prior = $had ? get_option($opt) : null;
$backup = get_option(self::BACKUP, []);
if (!is_array($backup)) $backup = [];
$backup[$opt] = ['had' => $had, 'value' => $prior];
update_option(self::BACKUP, $backup, false);
update_option($opt, $value);
if (defined('LSCWP_V')) do_action('litespeed_purge_all');
return ['key' => $key, 'stored_as' => $opt, 'value' => $value, 'backed_up' => true, 'previously_set' => $had];
}
public static function restoreOption(array $args): array {
$key = (string) ($args['key'] ?? '');
if ($key === '') throw new \RuntimeException('key required.');
$opt = self::fullKey($key);
$backup = get_option(self::BACKUP, []);
if (!is_array($backup) || !array_key_exists($opt, $backup)) {
throw new \RuntimeException('No backup recorded for key: ' . $key);
}
$entry = $backup[$opt];
if (!empty($entry['had'])) { update_option($opt, $entry['value']); $action = 'restored'; }
else { delete_option($opt); $action = 'removed'; }
unset($backup[$opt]);
update_option(self::BACKUP, $backup, false);
if (defined('LSCWP_V')) do_action('litespeed_purge_all');
return ['key' => $key, 'action' => $action];
}
public static function purge(array $args): array {
$scope = (string) ($args['scope'] ?? 'all');
switch ($scope) {
case 'url':
$url = (string) ($args['url'] ?? '');
if ($url === '') throw new \RuntimeException('url required for scope=url.');
do_action('litespeed_purge_url', $url);
return ['scope' => 'url', 'url' => $url, 'purged' => true];
case 'post':
$pid = (int) ($args['post_id'] ?? 0);
if (!$pid) throw new \RuntimeException('post_id required for scope=post.');
do_action('litespeed_purge_post', $pid);
return ['scope' => 'post', 'post_id' => $pid, 'purged' => true];
case 'tag':
$tag = (string) ($args['tag'] ?? '');
if ($tag === '') throw new \RuntimeException('tag required for scope=tag.');
do_action('litespeed_purge', $tag);
return ['scope' => 'tag', 'tag' => $tag, 'purged' => true];
default:
do_action('litespeed_purge_all');
return ['scope' => 'all', 'purged' => true];
}
}
}