87 lines
3.0 KiB
PHP
87 lines
3.0 KiB
PHP
<?php
|
|
namespace AskTamMCP\Tools;
|
|
defined('ABSPATH') || exit;
|
|
|
|
class CliTools {
|
|
|
|
private static array $ALLOWED_COMMANDS = [
|
|
'cache', 'cron', 'db', 'eval', 'export', 'import', 'media',
|
|
'option', 'plugin', 'post', 'rewrite', 'role', 'search-replace',
|
|
'site', 'term', 'theme', 'transient', 'user', 'widget',
|
|
'elementor', 'wc', 'yoast',
|
|
];
|
|
|
|
private static array $BLOCKED_PATTERNS = [
|
|
'/\brm\b/', '/\bdd\b/', '/\bmkfs\b/', '/;/', '/&&/', '/\|\|/',
|
|
'/`/', '/\$\(/', '/>/', '/</', '/\beval\b/', '/\bexec\b/',
|
|
'/\bpasswd\b/', '/\bsudo\b/', '/\bchmod\b/', '/\bchown\b/',
|
|
];
|
|
|
|
public static function run(array $args): array {
|
|
if (!self::isWpCliAvailable()) {
|
|
throw new \RuntimeException("WP-CLI is not available on this server.");
|
|
}
|
|
$command = trim($args['command'] ?? '');
|
|
if (empty($command)) throw new \RuntimeException("Command is required.");
|
|
|
|
foreach (self::$BLOCKED_PATTERNS as $pattern) {
|
|
if (preg_match($pattern, $command)) {
|
|
throw new \RuntimeException("Command contains blocked pattern.");
|
|
}
|
|
}
|
|
|
|
$parts = preg_split('/\s+/', $command);
|
|
$first = strtolower($parts[0] ?? '');
|
|
if (!in_array($first, self::$ALLOWED_COMMANDS, true)) {
|
|
throw new \RuntimeException("Command '$first' is not allowed. Allowed: " . implode(', ', self::$ALLOWED_COMMANDS));
|
|
}
|
|
|
|
$wp_cli = self::findWpCli();
|
|
$wp_path = ABSPATH;
|
|
$full_cmd = escapeshellarg($wp_cli)
|
|
. ' ' . $command
|
|
. ' --path=' . escapeshellarg($wp_path)
|
|
. ' --allow-root --no-color 2>&1';
|
|
|
|
$output = [];
|
|
$exit_code = 0;
|
|
exec($full_cmd, $output, $exit_code);
|
|
|
|
return [
|
|
'command' => 'wp ' . $command,
|
|
'exit_code' => $exit_code,
|
|
'success' => $exit_code === 0,
|
|
'output' => implode("\n", $output),
|
|
];
|
|
}
|
|
|
|
public static function isAvailable(array $args): array {
|
|
$available = self::isWpCliAvailable();
|
|
$version = null;
|
|
if ($available) {
|
|
$wp_cli = self::findWpCli();
|
|
$version = trim((string) shell_exec(escapeshellarg($wp_cli) . ' --version --allow-root --no-color 2>&1'));
|
|
}
|
|
return [
|
|
'available' => $available,
|
|
'path' => $available ? self::findWpCli() : null,
|
|
'version' => $version,
|
|
'allowed_cmds' => self::$ALLOWED_COMMANDS,
|
|
];
|
|
}
|
|
|
|
private static function isWpCliAvailable(): bool {
|
|
return !empty(self::findWpCli());
|
|
}
|
|
|
|
private static function findWpCli(): string {
|
|
foreach (['/usr/local/bin/wp', '/usr/bin/wp'] as $path) {
|
|
if (file_exists($path) && is_executable($path)) return $path;
|
|
}
|
|
$matches = glob('/home/*/bin/wp');
|
|
if ($matches) return $matches[0];
|
|
$which = trim((string) shell_exec('which wp 2>/dev/null'));
|
|
return $which;
|
|
}
|
|
}
|