is_in_allowed_rspade_directory($file_path)) { return; } $sanitized_data = FileSanitizer::sanitize_javascript($file_path); $lines = $sanitized_data['lines']; // Pattern to match $.ajax({ url: '/_ajax/Controller/action' // This handles both single-line and multi-line cases $full_content = implode("\n", $lines); // Match $.ajax({ with optional whitespace/newlines, then url: with quotes around /_ajax/ // Capture controller and action names for suggestion $pattern = '/\$\.ajax\s*\(\s*\{[^}]*?url\s*:\s*[\'"](\/_ajax\/([A-Za-z_][A-Za-z0-9_]*)\/([A-Za-z_][A-Za-z0-9_]*))[^\'"]*[\'"]/s'; if (preg_match_all($pattern, $full_content, $matches, PREG_OFFSET_CAPTURE)) { foreach ($matches[0] as $index => $match) { $matched_text = $match[0]; $offset = $match[1]; $url = $matches[1][$index][0]; $controller = $matches[2][$index][0]; $action = $matches[3][$index][0]; // Find line number $line_number = substr_count(substr($full_content, 0, $offset), "\n") + 1; // Get the actual line for display $line = $lines[$line_number - 1] ?? ''; // Build suggestion $suggestion = "Instead of direct $.ajax() call to '{$url}', use:\n"; $suggestion .= " 1. Preferred: await {$controller}.{$action}(params)\n"; $suggestion .= " 2. Alternative: await Ajax.call('{$controller}', '{$action}', params)\n"; $suggestion .= "The JS stub handles session expiry, notifications, and response unwrapping."; $this->add_violation( $file_path, $line_number, "Direct $.ajax() call to internal API endpoint '{$url}' detected. Use JS controller stub instead.", trim($line), $suggestion, 'high' ); } } } /** * Check if a file in /app/RSpade/ is in an allowed subdirectory * Based on scan_directories configuration */ private function is_in_allowed_rspade_directory(string $file_path): bool { // Get allowed subdirectories from config $scan_directories = config('rsx.manifest.scan_directories', []); // Extract allowed RSpade subdirectories $allowed_subdirs = []; foreach ($scan_directories as $scan_dir) { if (str_starts_with($scan_dir, 'app/RSpade/')) { $subdir = substr($scan_dir, strlen('app/RSpade/')); if ($subdir) { $allowed_subdirs[] = $subdir; } } } // Check if file is in any allowed subdirectory foreach ($allowed_subdirs as $subdir) { if (str_contains($file_path, '/app/RSpade/' . $subdir . '/') || str_contains($file_path, '/app/RSpade/' . $subdir)) { return true; } } return false; } }