diff --git a/includes/Extensions/Batch21.php b/includes/Extensions/Batch21.php new file mode 100644 index 0000000..3f170fc --- /dev/null +++ b/includes/Extensions/Batch21.php @@ -0,0 +1,139 @@ + '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]; + } + } +}