'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]);