204 lines
6.8 KiB
PHP
204 lines
6.8 KiB
PHP
<?php
|
|
// api/run_r_script.php
|
|
session_start();
|
|
header('Content-Type: application/json');
|
|
|
|
require_once '../config.php';
|
|
require_once '../includes/auth.php';
|
|
require_once '../classes/DataSource.php';
|
|
|
|
// Get the user details from the session. This relies on your login system.
|
|
$user_id = $_SESSION['user_id'] ?? null;
|
|
$person_id = $_SESSION['person_id'] ?? null;
|
|
|
|
// Only allow POST requests and authenticated users with explicit R access.
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || empty($user_id)) {
|
|
http_response_code(401);
|
|
echo json_encode(['status' => 'error', 'message' => 'Unauthorized. Please log in to run this script.']);
|
|
exit();
|
|
}
|
|
|
|
if (!has_r_access()) {
|
|
http_response_code(403);
|
|
echo json_encode(['status' => 'error', 'message' => 'You do not have permission to run R/Jupyter scripts.']);
|
|
exit();
|
|
}
|
|
|
|
if (empty($person_id)) {
|
|
http_response_code(400);
|
|
echo json_encode(['status' => 'error', 'message' => 'Missing user context for R execution.']);
|
|
exit();
|
|
}
|
|
|
|
$dataSourceManager = new DataSource($pdo);
|
|
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if ($data === null) {
|
|
http_response_code(400);
|
|
echo json_encode(['status' => 'error', 'message' => 'Invalid JSON input.']);
|
|
exit();
|
|
}
|
|
|
|
$script_name = $data['script_name'] ?? null;
|
|
$data_source_id = isset($data['data_source_id']) ? (int)$data['data_source_id'] : null;
|
|
$parameters = $data['parameters'] ?? [];
|
|
|
|
$r_script_path_dir = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'r_scripts' . DIRECTORY_SEPARATOR;
|
|
|
|
$allowed_r_scripts = [
|
|
'data_summary.R' => 'Basic Data Summary',
|
|
'descriptive_stats.R' => 'Numeric column descriptive statistics',
|
|
'category_frequency.R' => 'Categorical frequency distribution',
|
|
];
|
|
|
|
if (!isset($allowed_r_scripts[$script_name])) {
|
|
http_response_code(400);
|
|
echo json_encode(['status' => 'error', 'message' => 'Invalid R script selected.']);
|
|
exit();
|
|
}
|
|
|
|
if (empty($data_source_id)) {
|
|
http_response_code(400);
|
|
echo json_encode(['status' => 'error', 'message' => 'Data source ID is required.']);
|
|
exit();
|
|
}
|
|
|
|
try {
|
|
$data_source_details = $dataSourceManager->getDataSourceById($data_source_id);
|
|
} catch (Exception $e) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => 'Failed to lookup data source.', 'debug' => $e->getMessage()]);
|
|
exit();
|
|
}
|
|
|
|
if (!$data_source_details) {
|
|
http_response_code(404);
|
|
echo json_encode(['status' => 'error', 'message' => 'Data source not found.']);
|
|
exit();
|
|
}
|
|
|
|
$data_source_owner_person_id = $data_source_details['fkisp_id_of'] ?? null;
|
|
$has_access_to_data = ($data_source_owner_person_id && (int)$data_source_owner_person_id === (int)$person_id);
|
|
|
|
if (!$has_access_to_data) {
|
|
try {
|
|
$has_access_to_data = $dataSourceManager->hasPermission((int)$person_id, $data_source_id, 'Analyze');
|
|
} catch (Exception $e) {
|
|
error_log('Analyze permission check failed: ' . $e->getMessage());
|
|
$has_access_to_data = false;
|
|
}
|
|
}
|
|
|
|
if (!$has_access_to_data) {
|
|
http_response_code(403);
|
|
echo json_encode(['status' => 'error', 'message' => 'You are not allowed to analyze this data source.']);
|
|
exit();
|
|
}
|
|
|
|
$source_filename = $data_source_details['dspsds_filename'] ?? '';
|
|
if (empty($source_filename)) {
|
|
http_response_code(400);
|
|
echo json_encode(['status' => 'error', 'message' => 'No associated data file found for this data source.']);
|
|
exit();
|
|
}
|
|
|
|
$upload_dir = $dataSourceManager->getUploadDir();
|
|
$upload_dir_real = realpath($upload_dir) ?: $upload_dir;
|
|
$full_data_source_path = realpath($upload_dir . $source_filename);
|
|
|
|
if ($full_data_source_path === false || strpos($full_data_source_path, $upload_dir_real) !== 0 || !is_file($full_data_source_path)) {
|
|
http_response_code(404);
|
|
echo json_encode(['status' => 'error', 'message' => 'Data source file could not be located.']);
|
|
exit();
|
|
}
|
|
|
|
// --- Create a temporary file for the R script to use ---
|
|
// This is a security best practice to prevent R from accessing arbitrary files.
|
|
$temp_data_file = tempnam(sys_get_temp_dir(), 'rdata_') . '.csv';
|
|
copy($full_data_source_path, $temp_data_file);
|
|
|
|
// Log the usage of the data source
|
|
error_log("Logging usage for data source: $data_source_id by user: $user_id for action: Ran Analysis");
|
|
|
|
// --- Path and Permission Checks ---
|
|
$r_executable_path = getenv('RSCRIPT_PATH') ?: '/usr/bin/Rscript';
|
|
$r_script_full_path = $r_script_path_dir . $script_name;
|
|
|
|
if (!file_exists($r_executable_path) || !is_executable($r_executable_path)) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => "Rscript executable not found or not executable at: {$r_executable_path}"]);
|
|
exit();
|
|
}
|
|
|
|
if (!file_exists($r_script_full_path) || !is_readable($r_script_full_path)) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => "R script not found or not readable at: {$r_script_full_path}"]);
|
|
exit();
|
|
}
|
|
|
|
// Build the command, specifying the R executable directly.
|
|
$params_json_str = json_encode($parameters, JSON_UNESCAPED_SLASHES);
|
|
if ($params_json_str === false) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => 'Failed to encode parameters to JSON.']);
|
|
exit();
|
|
}
|
|
|
|
$command = escapeshellcmd($r_executable_path) . " " . escapeshellarg($r_script_full_path) . " " . escapeshellarg($temp_data_file) . " " . escapeshellarg($params_json_str);
|
|
|
|
// --- Use proc_open for better error capture ---
|
|
$descriptorspec = array(
|
|
0 => array("pipe", "r"), // stdin
|
|
1 => array("pipe", "w"), // stdout
|
|
2 => array("pipe", "w") // stderr
|
|
);
|
|
|
|
$process = proc_open($command, $descriptorspec, $pipes);
|
|
|
|
if (!is_resource($process)) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => 'Failed to open process to R.']);
|
|
exit();
|
|
}
|
|
|
|
$stdout = stream_get_contents($pipes[1]);
|
|
fclose($pipes[1]);
|
|
|
|
$stderr = stream_get_contents($pipes[2]);
|
|
fclose($pipes[2]);
|
|
|
|
$return_code = proc_close($process);
|
|
|
|
// Clean up the temporary data file
|
|
if (file_exists($temp_data_file)) {
|
|
unlink($temp_data_file);
|
|
}
|
|
|
|
if ($return_code !== 0) {
|
|
http_response_code(500);
|
|
echo json_encode([
|
|
'status' => 'error',
|
|
'message' => "R script execution failed. Exit code: {$return_code}",
|
|
'r_stdout' => $stdout,
|
|
'r_stderr' => $stderr
|
|
]);
|
|
exit();
|
|
}
|
|
|
|
$r_results = json_decode($stdout, true);
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
http_response_code(500);
|
|
echo json_encode(['status' => 'error', 'message' => 'Failed to decode JSON from R script output.', 'r_stdout' => $stdout, 'r_stderr' => $stderr]);
|
|
exit();
|
|
}
|
|
|
|
try {
|
|
$dataSourceManager->logDataSourceUsage($data_source_id, (int)$person_id, 'Ran Analysis via API', (int)$user_id);
|
|
} catch (Exception $e) {
|
|
error_log('Failed to log R analysis usage: ' . $e->getMessage());
|
|
}
|
|
|
|
echo json_encode(['status' => 'success', 'results' => $r_results]);
|