Files

97 lines
3.7 KiB
PHP

<?php
namespace AskTamMCP\Tools;
defined('ABSPATH') || exit;
class RawSqlTools {
private static array $BLOCKED = [
'/\bDROP\b/i', '/\bTRUNCATE\b/i', '/\bDELETE\b/i',
'/\bUPDATE\b/i', '/\bINSERT\b/i', '/\bALTER\b/i',
'/\bCREATE\b/i', '/\bGRANT\b/i', '/\bREVOKE\b/i',
'/\bLOAD\s+DATA\b/i', '/\bINTO\s+OUTFILE\b/i',
'/\bINFORMATION_SCHEMA\b/i', '/\bmysql\b/i',
];
public static function query(array $args): array {
$settings = get_option('asktam_mcp_settings', []);
if (empty($settings['allow_raw_sql'])) {
throw new \RuntimeException("Raw SQL is disabled. Enable it in AskTam MCP → Settings.");
}
// Accept both 'sql' and 'query' as the parameter name
$sql = trim($args['sql'] ?? $args['query'] ?? '');
$limit = min((int)($args['limit'] ?? 100), 500);
if (empty($sql)) throw new \RuntimeException("SQL query is required.");
if (!preg_match('/^\s*SELECT\b/i', $sql)) {
throw new \RuntimeException("Only SELECT queries are allowed.");
}
foreach (self::$BLOCKED as $pattern) {
if (preg_match($pattern, $sql)) {
throw new \RuntimeException("Query contains blocked pattern.");
}
}
// Auto-translate wp_ table references to the actual WP prefix
// e.g. on a site with prefix "mwp_", "wp_posts" becomes "mwp_posts"
global $wpdb;
if ($wpdb->prefix !== 'wp_') {
$sql = preg_replace('/\bwp_([a-zA-Z0-9_]+)\b/', $wpdb->prefix . '$1', $sql);
}
if (!preg_match('/\bLIMIT\b/i', $sql)) {
$sql .= " LIMIT $limit";
}
$results = $wpdb->get_results($sql, ARRAY_A);
if ($wpdb->last_error) {
throw new \RuntimeException("SQL error: " . $wpdb->last_error);
}
return ['sql' => $sql, 'rows' => $results, 'count' => count($results ?? [])];
}
public static function listTables(array $args): array {
global $wpdb;
$prefix = $args['prefix'] ?? $wpdb->prefix;
$tables = $wpdb->get_col("SHOW TABLES LIKE '{$wpdb->esc_like($prefix)}%'");
$result = [];
foreach ($tables as $t) {
$info = $wpdb->get_row("SELECT TABLE_ROWS, DATA_LENGTH+INDEX_LENGTH as SIZE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$t'", ARRAY_A);
$result[] = [
'table' => $t,
'rows' => (int)($info['TABLE_ROWS'] ?? 0),
'size' => size_format((int)($info['SIZE'] ?? 0)),
];
}
return ['tables' => $result, 'count' => count($result), 'prefix' => $prefix];
}
public static function describeTable(array $args): array {
global $wpdb;
$table = sanitize_text_field($args['table'] ?? '');
if (empty($table)) throw new \RuntimeException("table is required.");
if (strpos($table, $wpdb->prefix) !== 0) {
$stripped = preg_replace('/^wp_/', '', $table);
$candidate = $wpdb->prefix . $stripped;
$exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $candidate));
if ($exists) {
$table = $candidate;
} else {
$exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table));
if (!$exists) {
throw new \RuntimeException("Table not found: {$args['table']}. WP prefix is: {$wpdb->prefix}");
}
}
}
$columns = $wpdb->get_results("DESCRIBE `$table`", ARRAY_A);
if ($wpdb->last_error) throw new \RuntimeException($wpdb->last_error);
return ['table' => $table, 'columns' => $columns, 'count' => count($columns)];
}
}