File: /home/vitanhod/www/wp-content/plugins/system-control/api/endpoints/class-sc-files-endpoint.php
<?php
class SC_Files_Endpoint {
public function register() {
register_rest_route(SC_REST_NAMESPACE, '/files', [
[
'methods' => 'GET',
'callback' => [$this, 'list_files'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
],
[
'methods' => 'DELETE',
'callback' => [$this, 'delete_file'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
],
]);
register_rest_route(SC_REST_NAMESPACE, '/files/read', [
'methods' => 'GET',
'callback' => [$this, 'read_file'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
]);
register_rest_route(SC_REST_NAMESPACE, '/files/write', [
'methods' => 'POST',
'callback' => [$this, 'write_file'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
]);
register_rest_route(SC_REST_NAMESPACE, '/files/rename', [
'methods' => 'POST',
'callback' => [$this, 'rename_file'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
]);
// NEW: create empty file
register_rest_route(SC_REST_NAMESPACE, '/files/create', [
'methods' => 'POST',
'callback' => [$this, 'create_file'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
]);
// NEW: upload file (base64 encoded)
register_rest_route(SC_REST_NAMESPACE, '/files/upload', [
'methods' => 'POST',
'callback' => [$this, 'upload_file'],
'permission_callback' => ['SC_Api_Auth', 'verify_secure'],
]);
}
private function resolve_path($path) {
$base = ABSPATH;
$full = realpath($base . '/' . ltrim($path, '/'));
if (!$full || strpos($full, realpath($base)) !== 0) {
return false;
}
return $full;
}
private function safe_write_path($path) {
$base = realpath(ABSPATH);
$full = ABSPATH . '/' . ltrim($path, '/');
$dir = dirname($full);
if (!is_dir($dir)) {
wp_mkdir_p($dir);
}
$real_dir = realpath($dir);
if (!$real_dir || strpos($real_dir, $base) !== 0) {
return false;
}
return $full;
}
public function list_files($request) {
$path = $request->get_param('path') ?: '';
$dir = $path ? $this->resolve_path($path) : ABSPATH;
if (!$dir || !is_dir($dir)) {
return new WP_Error('invalid_dir', 'Invalid directory', ['status' => 400]);
}
$items = [];
$scan = scandir($dir);
foreach ($scan as $item) {
if ($item === '.' || $item === '..') continue;
$full = $dir . DIRECTORY_SEPARATOR . $item;
$items[] = [
'name' => $item,
'type' => is_dir($full) ? 'directory' : 'file',
'size' => is_file($full) ? filesize($full) : 0,
'modified' => date('Y-m-d H:i:s', filemtime($full)),
'perms' => substr(sprintf('%o', fileperms($full)), -4),
];
}
usort($items, function($a, $b) {
if ($a['type'] !== $b['type']) return $a['type'] === 'directory' ? -1 : 1;
return strcasecmp($a['name'], $b['name']);
});
return rest_ensure_response(['path' => $path, 'items' => $items]);
}
public function read_file($request) {
$path = $request->get_param('path');
$full = $this->resolve_path($path);
if (!$full || !is_file($full)) {
return new WP_Error('not_found', 'File not found', ['status' => 404]);
}
if (filesize($full) > 5 * 1024 * 1024) {
return new WP_Error('too_large', 'File too large (>5MB)', ['status' => 400]);
}
return rest_ensure_response([
'path' => $path,
'content' => file_get_contents($full),
'size' => filesize($full),
]);
}
public function write_file($request) {
$params = $request->get_json_params();
$path = $params['path'] ?? '';
$content = $params['content'] ?? '';
$full = $this->safe_write_path($path);
if (!$full) {
return new WP_Error('invalid_path', 'Invalid path', ['status' => 400]);
}
$result = file_put_contents($full, $content);
return rest_ensure_response(['success' => $result !== false, 'bytes' => $result]);
}
// NEW: create an empty file (or a new file with optional content)
public function create_file($request) {
$params = $request->get_json_params();
$path = $params['path'] ?? '';
$content = $params['content'] ?? '';
if (empty($path)) {
return new WP_Error('invalid_path', 'Path is required', ['status' => 400]);
}
$full = $this->safe_write_path($path);
if (!$full) {
return new WP_Error('invalid_path', 'Invalid path', ['status' => 400]);
}
if (file_exists($full)) {
return new WP_Error('exists', 'File already exists', ['status' => 409]);
}
$result = file_put_contents($full, $content);
return rest_ensure_response(['success' => $result !== false, 'path' => $path]);
}
// NEW: upload file sent as base64
public function upload_file($request) {
$params = $request->get_json_params();
$path = $params['path'] ?? ''; // destination path, e.g. "wp-content/uploads/photo.jpg"
$data_b64 = $params['data'] ?? ''; // base64-encoded file content
if (empty($path) || empty($data_b64)) {
return new WP_Error('invalid_request', 'path and data are required', ['status' => 400]);
}
$full = $this->safe_write_path($path);
if (!$full) {
return new WP_Error('invalid_path', 'Invalid path', ['status' => 400]);
}
$binary = base64_decode($data_b64, true);
if ($binary === false) {
return new WP_Error('invalid_data', 'Invalid base64 data', ['status' => 400]);
}
// 50 MB limit
if (strlen($binary) > 50 * 1024 * 1024) {
return new WP_Error('too_large', 'File too large (>50MB)', ['status' => 400]);
}
$result = file_put_contents($full, $binary);
return rest_ensure_response([
'success' => $result !== false,
'path' => $path,
'bytes' => $result,
]);
}
public function delete_file($request) {
$path = $request->get_param('path');
$full = $this->resolve_path($path);
if (!$full) {
return new WP_Error('not_found', 'Path not found', ['status' => 404]);
}
if (is_dir($full)) {
$this->delete_directory($full);
} else {
unlink($full);
}
return rest_ensure_response(['success' => true]);
}
public function rename_file($request) {
$params = $request->get_json_params();
$from = $this->resolve_path($params['from'] ?? '');
$to_path = $params['to'] ?? '';
if (!$from) {
return new WP_Error('not_found', 'Source not found', ['status' => 404]);
}
$to = ABSPATH . '/' . ltrim($to_path, '/');
$result = rename($from, $to);
return rest_ensure_response(['success' => $result]);
}
private function delete_directory($dir) {
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . DIRECTORY_SEPARATOR . $file;
is_dir($path) ? $this->delete_directory($path) : unlink($path);
}
rmdir($dir);
}
}