v2.2.0: includes/Tools/RawSqlTools.php
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<?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.");
|
||||
}
|
||||
|
||||
$sql = trim($args['sql'] ?? '');
|
||||
$limit = min((int)($args['limit'] ?? 100), 500);
|
||||
|
||||
if (empty($sql)) throw new \RuntimeException("SQL query is required.");
|
||||
|
||||
// Only SELECT allowed
|
||||
if (!preg_match('/^\s*SELECT\b/i', $sql)) {
|
||||
throw new \RuntimeException("Only SELECT queries are allowed.");
|
||||
}
|
||||
|
||||
// Block dangerous patterns
|
||||
foreach (self::$BLOCKED as $pattern) {
|
||||
if (preg_match($pattern, $sql)) {
|
||||
throw new \RuntimeException("Query contains blocked pattern.");
|
||||
}
|
||||
}
|
||||
|
||||
// Inject LIMIT if not present
|
||||
if (!preg_match('/\bLIMIT\b/i', $sql)) {
|
||||
$sql .= " LIMIT $limit";
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$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']);
|
||||
// Must use allowed prefix
|
||||
if (strpos($table, $wpdb->prefix) !== 0) {
|
||||
throw new \RuntimeException("Table must use the WP prefix: $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)];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user