namespace = BASE . 'v1'; } /** * Register REST routes */ public function registerRoutes(): void { // Client import endpoint register_rest_route($this->namespace, '/jane/import-clients', [ 'methods' => 'POST', 'callback' => [$this, 'importClients'], 'permission_callback' => [$this, 'checkAdminPermission'], 'args' => [ 'file' => [ 'required' => true, 'description' => 'CSV file containing client data' ], 'options' => [ 'required' => false, 'default' => [], 'description' => 'Import options' ] ] ]); // Sales import endpoint register_rest_route($this->namespace, '/jane/import-sales', [ 'methods' => 'POST', 'callback' => [$this, 'importSales'], 'permission_callback' => [$this, 'checkAdminPermission'], 'args' => [ 'file' => [ 'required' => true, 'description' => 'CSV file containing sales data' ], 'options' => [ 'required' => false, 'default' => [], 'description' => 'Import options' ] ] ]); // Get import status register_rest_route($this->namespace, '/jane/import-status/(?P[\w-]+)', [ 'methods' => 'GET', 'callback' => [$this, 'getImportStatus'], 'permission_callback' => [$this, 'checkAdminPermission'] ]); } /** * Check if user has admin permissions */ public function checkAdminPermission(): bool { return current_user_can('manage_options'); } /** * Import clients from CSV * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public function importClients(WP_REST_Request $request) { // Get uploaded file $files = $request->get_file_params(); if (empty($files['file'])) { return new WP_Error('no_file', 'No file uploaded', ['status' => 400]); } $file = $files['file']; // Validate file type if (!$this->isValidCSV($file)) { return new WP_Error('invalid_file', 'Invalid file type. Please upload a CSV file.', ['status' => 400]); } // Get options $options = $request->get_param('options') ?: []; $default_options = [ 'update_existing' => true, 'create_users' => true, 'send_welcome_email' => false ]; $options = wp_parse_args($options, $default_options); // Process import $importer = new JaneClientImporter(); $results = $importer->importFromCSV($file['tmp_name'], $options); if (is_wp_error($results)) { return new WP_Error( 'import_failed', $results->get_error_message(), ['status' => 500] ); } // Store results in transient for status checking $import_id = wp_generate_password(12, false); set_transient('jane_import_' . $import_id, [ 'type' => 'clients', 'results' => $results, 'completed_at' => current_time('mysql') ], HOUR_IN_SECONDS); return new WP_REST_Response([ 'success' => true, 'import_id' => $import_id, 'results' => $results, 'summary' => $this->generateClientImportSummary($results) ], 200); } /** * Import sales from CSV * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public function importSales(WP_REST_Request $request) { // Get uploaded file $files = $request->get_file_params(); if (empty($files['file'])) { return new WP_Error('no_file', 'No file uploaded', ['status' => 400]); } $file = $files['file']; // Validate file type if (!$this->isValidCSV($file)) { return new WP_Error('invalid_file', 'Invalid file type. Please upload a CSV file.', ['status' => 400]); } // Get options $options = $request->get_param('options') ?: []; $default_options = [ 'skip_existing' => true ]; $options = wp_parse_args($options, $default_options); // Process import $importer = new JaneSalesImporter(); $results = $importer->importFromCSV($file['tmp_name'], $options); if (is_wp_error($results)) { return new WP_Error( 'import_failed', $results->get_error_message(), ['status' => 500] ); } // Store results in transient for status checking $import_id = wp_generate_password(12, false); set_transient('jane_import_' . $import_id, [ 'type' => 'sales', 'results' => $results, 'completed_at' => current_time('mysql') ], HOUR_IN_SECONDS); return new WP_REST_Response([ 'success' => true, 'import_id' => $import_id, 'results' => $results, 'summary' => $this->generateSalesImportSummary($results) ], 200); } /** * Get import status by ID * * @param WP_REST_Request $request * @return WP_REST_Response|WP_Error */ public function getImportStatus(WP_REST_Request $request) { $import_id = $request->get_param('id'); $import_data = get_transient('jane_import_' . $import_id); if (!$import_data) { return new WP_Error( 'import_not_found', 'Import not found or expired', ['status' => 404] ); } return new WP_REST_Response([ 'success' => true, 'data' => $import_data ], 200); } /** * Validate CSV file * * @param array $file Uploaded file data * @return bool */ protected function isValidCSV(array $file): bool { // Check file extension $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if ($ext !== 'csv') { return false; } // Check MIME type $allowed_types = ['text/csv', 'text/plain', 'application/csv', 'application/vnd.ms-excel']; if (!in_array($file['type'], $allowed_types)) { return false; } // Check if file is actually readable as CSV $handle = fopen($file['tmp_name'], 'r'); if (!$handle) { return false; } $header = fgetcsv($handle); fclose($handle); return !empty($header); } /** * Generate human-readable summary of client import * * @param array $results Import results * @return string */ protected function generateClientImportSummary(array $results): string { $summary = []; if ($results['created'] > 0) { $summary[] = "{$results['created']} new users created"; } if ($results['updated'] > 0) { $summary[] = "{$results['updated']} existing users updated"; } if ($results['skipped'] > 0) { $summary[] = "{$results['skipped']} rows skipped"; } if (count($results['errors']) > 0) { $summary[] = count($results['errors']) . " errors encountered"; } if (count($results['unmatched_emails']) > 0) { $summary[] = count($results['unmatched_emails']) . " unmatched emails"; } return implode('. ', $summary) . '.'; } /** * Generate human-readable summary of sales import * * @param array $results Import results * @return string */ protected function generateSalesImportSummary(array $results): string { $summary = []; if ($results['consultations'] > 0) { $summary[] = "{$results['consultations']} consultations processed"; } if ($results['treatments'] > 0) { $summary[] = "{$results['treatments']} treatments recorded"; } if ($results['skipped'] > 0) { $summary[] = "{$results['skipped']} rows skipped"; } if (count($results['errors']) > 0) { $summary[] = count($results['errors']) . " errors encountered"; } if (count($results['unmatched_guids']) > 0) { $summary[] = count($results['unmatched_guids']) . " unmatched patient GUIDs"; } if (count($results['no_referral']) > 0) { $summary[] = count($results['no_referral']) . " users without referral records"; } return implode('. ', $summary) . '.'; } }