| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\base\Site; |
| | | use JVBase\importers\JaneAppClientImporter; |
| | | use JVBase\managers\JaneSalesImporter; |
| | | use JVBase\managers\MagicLinkManager; |
| | | use JVBase\rest\RestRouteManager; |
| | | use JVBase\importers\JaneAppSalesImporter; |
| | | use JVBase\managers\CustomTable; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\rest\Route; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | use WP_Error; |
| | |
| | | /** |
| | | * REST API routes for referral system |
| | | */ |
| | | class ReferralRoutes extends RestRouteManager |
| | | class ReferralRoutes extends Rest |
| | | { |
| | | protected string $referrals_table; |
| | | protected string $rewards_table; |
| | | protected $wpdb; |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->route = 'referrals'; |
| | | $this->cache_name = 'referrals'; |
| | | $this->cacheName = 'referrals'; |
| | | $this->cacheTtl = (int)HOUR_IN_SECONDS; |
| | | parent::__construct(); |
| | | |
| | | global $wpdb; |
| | | $this->wpdb = $wpdb; |
| | | $this->referrals_table = $wpdb->prefix . BASE . 'referrals'; |
| | | $this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards'; |
| | | |
| | | add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3); |
| | | } |
| | | |
| | | public function registerRoutes(): void |
| | | { |
| | | /** |
| | | * Main referrals endpoint |
| | | * GET: List referrals with filters |
| | | * POST: Perform actions (invite, consulted, treated, remove, resend) |
| | | */ |
| | | register_rest_route($this->namespace, "/{$this->route}", [ |
| | | [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getReferrals'], |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | 'args' => [ |
| | | 'user' => ['type' => 'integer', 'sanitize_callback' => 'absint'], |
| | | 'status' => ['type' => 'string', 'enum' => ['all', 'pending', 'consulted', 'treated', 'unused', 'registered', 'completed']], |
| | | 'date_start' => ['type' => 'string'], |
| | | 'date_end' => ['type' => 'string'], |
| | | 'limit' => ['type' => 'integer', 'default' => 50], |
| | | 'offset' => ['type' => 'integer', 'default' => 0], |
| | | 'format' => ['type' => 'string', 'enum' => ['simple', 'formatted'], 'default' => 'formatted'], |
| | | 'search' => ['type' => 'string'] |
| | | ] |
| | | ], |
| | | [ |
| | | 'methods' => 'POST', |
| | | 'callback' => [$this, 'handleAction'], |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | 'args' => [ |
| | | 'action' => [ |
| | | 'required' => true, |
| | | 'type' => 'string', |
| | | 'enum' => ['invite', 'consulted', 'treated', 'remove', 'resend'] |
| | | ] |
| | | ] |
| | | ] |
| | | ]); |
| | | // Main referrals endpoint - list and manage referrals |
| | | Route::for('referrals') |
| | | ->get([$this, 'getReferrals']) |
| | | ->args([ |
| | | 'user' => 'integer', |
| | | 'status' => 'string|enum:all,pending,consulted,treated,unused,registered,completed|default:all', |
| | | 'date_start' => 'string', |
| | | 'date_end' => 'string', |
| | | 'limit' => 'integer|default:50', |
| | | 'offset' => 'integer|default:0', |
| | | 'search' => 'string' |
| | | ]) |
| | | ->rateLimit() |
| | | ->post([$this, 'handleAction']) |
| | | ->args([ |
| | | 'action' => 'string|required|enum:invite,consulted,treated,remove,resend' |
| | | ]) |
| | | ->auth('user') |
| | | ->rateLimit(10) |
| | | ->register(); |
| | | |
| | | /** |
| | | * Referral code endpoint |
| | | * GET: Get user's referral code |
| | | * POST: Validate a referral code |
| | | */ |
| | | register_rest_route($this->namespace, "/{$this->route}/code", [ |
| | | [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getCode'], |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | 'args' => [ |
| | | 'user' => ['type' => 'integer', 'sanitize_callback' => 'absint'] |
| | | ] |
| | | ], |
| | | [ |
| | | 'methods' => 'POST', |
| | | 'callback' => [$this, 'validateCode'], |
| | | 'permission_callback' => '__return_true', // Public endpoint |
| | | 'args' => [ |
| | | 'code' => ['required' => true, 'type' => 'string'] |
| | | ] |
| | | ] |
| | | ]); |
| | | // Referral code endpoint |
| | | Route::for('referrals/code') |
| | | ->get([$this, 'getCode']) |
| | | ->args(['user' => 'integer']) |
| | | ->auth('user') |
| | | ->rateLimit(30) |
| | | ->post([$this, 'validateCode']) |
| | | ->args(['code' => 'string|required']) |
| | | ->auth('public') |
| | | ->rateLimit(10) |
| | | ->register(); |
| | | |
| | | /** |
| | | * Stats endpoint |
| | | * GET: Get user's referral statistics |
| | | */ |
| | | register_rest_route($this->namespace, "/{$this->route}/stats", [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getStats'], |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | 'args' => [ |
| | | 'user' => ['type' => 'integer', 'sanitize_callback' => 'absint'], |
| | | ] |
| | | ]); |
| | | // Stats endpoint |
| | | Route::for('referrals/stats') |
| | | ->get([$this, 'getStats']) |
| | | ->args(['user' => 'integer']) |
| | | ->auth('user') |
| | | ->rateLimit(30) |
| | | ->register(); |
| | | |
| | | /** |
| | | * Settings endpoint (admin only) |
| | | */ |
| | | register_rest_route($this->namespace, "/{$this->route}/settings", [ |
| | | [ |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getSettings'], |
| | | 'permission_callback' => [$this, 'checkAdminPermission'] |
| | | ], |
| | | [ |
| | | 'methods' => 'POST', |
| | | 'callback' => [$this, 'updateSettings'], |
| | | 'permission_callback' => [$this, 'checkAdminPermission'] |
| | | ] |
| | | ]); |
| | | // Settings endpoint (admin only) |
| | | Route::for('referrals/settings') |
| | | ->get([$this, 'getSettings']) |
| | | ->post([$this, 'updateSettings']) |
| | | ->auth('admin') |
| | | ->rateLimit(10) |
| | | ->register(); |
| | | |
| | | /** |
| | | * CSV Upload endpoints (admin only) |
| | | */ |
| | | register_rest_route($this->namespace, "/{$this->route}/upload-clients", [ |
| | | 'methods' => 'POST', |
| | | 'callback' => [$this, 'handleClientUpload'], |
| | | 'permission_callback' => [$this, 'checkAdminPermission'] |
| | | ]); |
| | | // CSV Upload endpoints (admin only) |
| | | Route::for('referrals/upload-clients') |
| | | ->post([$this, 'handleClientUpload']) |
| | | ->auth('admin') |
| | | ->rateLimit(3) |
| | | ->register(); |
| | | |
| | | register_rest_route($this->namespace, "/{$this->route}/upload-sales", [ |
| | | 'methods' => 'POST', |
| | | 'callback' => [$this, 'handleSalesUpload'], |
| | | 'permission_callback' => [$this, 'checkAdminPermission'] |
| | | ]); |
| | | Route::for('referrals/upload-sales') |
| | | ->post([$this, 'handleSalesUpload']) |
| | | ->auth('admin') |
| | | ->rateLimit(3) |
| | | ->register(); |
| | | } |
| | | |
| | | /** |
| | |
| | | { |
| | | $user_id = $request->get_param('user'); |
| | | |
| | | // Determine scope |
| | | // Determine scope: admin without user param gets all referrals |
| | | if (!$user_id) { |
| | | $current_user_id = get_current_user_id(); |
| | | $is_admin = current_user_can('manage_options'); |
| | | if ($is_admin) { |
| | | // Admin with no user param = get all referrals |
| | | if (current_user_can('manage_options')) { |
| | | return $this->getAllReferrals($request); |
| | | } |
| | | $user_id = $current_user_id; |
| | | } |
| | | |
| | | |
| | | // Get user's referrals |
| | | // Build cache key |
| | | $args = [ |
| | | 'status' => $request->get_param('status') ?? 'all', |
| | | 'limit' => $request->get_param('limit') ?? 50, |
| | |
| | | 'date_start' => $request->get_param('date_start'), |
| | | 'date_end' => $request->get_param('date_end'), |
| | | ]; |
| | | $cache_key = "user_{$user_id}_" . $this->cache->generateKey($args); |
| | | |
| | | $cache_key = "ref_{$user_id}_" . md5(serialize($args)); |
| | | // Check headers for 304 Not Modified |
| | | // Check 304 Not Modified |
| | | $cache_check = $this->checkHeaders($request, $cache_key); |
| | | if ($cache_check instanceof WP_REST_Response) { |
| | | return $cache_check; // Returns 304 if not modified |
| | | return $cache_check; |
| | | } |
| | | |
| | | // Get referrals from manager |
| | | $referrals = JVB()->referrals()->getUserReferrals($user_id, $args); |
| | | |
| | | $data = [ |
| | |
| | | 'total' => count($referrals) |
| | | ]; |
| | | |
| | | // Create response with cache headers |
| | | $response = $this->success($data); |
| | | |
| | | // Add ETag and Last-Modified headers |
| | | return $this->addCacheHeaders($response, $cache_key, $data); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | protected function actionInvite(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $data = $request->get_params(); |
| | | error_log('Send Referral Invitations:'.print_r($data, true)); |
| | | $user = absint($request->get_param('user')); |
| | | if (!$this->checkUser($user)) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'No user found' |
| | | ]); |
| | | if (!$user || !get_userdata($user)) { |
| | | return $this->error('Invalid user', 'invalid_user', 400); |
| | | } |
| | | |
| | | //Additional check to not send too many emails in an hour |
| | | $user = absint($request->get_param('user')); |
| | | $transient_key = "referral_invite_limit_{$user}"; |
| | | $recent_invites = get_transient($transient_key) ?: 0; |
| | | |
| | | if ($recent_invites >= 20) { // Max 5 batch invites per hour |
| | | return $this->error('Too many invitations sent. Please try again later.', 'rate_limit', 429); |
| | | } |
| | | set_transient($transient_key, $recent_invites + 1, HOUR_IN_SECONDS); |
| | | |
| | | $subject = sanitize_text_field($request->get_param('subject')); |
| | | $message = sanitize_textarea_field($request->get_param('message')); |
| | | $invitations = $request->get_param('invite'); |
| | | |
| | | // Validate invitation format |
| | | foreach ($invitations as $key => $invite) { |
| | | if (!array_key_exists('name', $invite) || !array_key_exists('email', $invite)) { |
| | | unset($invitations[$key]); |
| | | } else { |
| | | $temp = [ |
| | | 'name' => sanitize_text_field($invite['name']), |
| | | 'email' => sanitize_email($invite['email']) |
| | | // Validate and sanitize invitations |
| | | $sanitized_invitations = []; |
| | | foreach ($invitations as $invite) { |
| | | if (isset($invite['name'], $invite['email'])) { |
| | | $sanitized_invitations[] = [ |
| | | 'name' => sanitize_text_field($invite['name']), |
| | | 'email' => sanitize_email($invite['email']) |
| | | ]; |
| | | $invitations[$key] = $temp; |
| | | } |
| | | } |
| | | |
| | | if (empty($sanitized_invitations)) { |
| | | return $this->error('No valid invitations provided', 'no_invitations', 400); |
| | | } |
| | | |
| | | $operationID = sanitize_text_field($request->get_param('id')); |
| | | $operation = JVB()->queue()->queueOperation( |
| | | JVB()->queue()->queueOperation( |
| | | 'referral_invite', |
| | | $user, |
| | | [ |
| | | 'subject' => $subject, |
| | | 'message' => $message, |
| | | 'invitations' => $invitations |
| | | 'invitations' => $sanitized_invitations |
| | | ], |
| | | [ |
| | | 'operation_id' => $operationID |
| | | ] |
| | | ['operation_id' => $operationID] |
| | | ); |
| | | |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => 'Queued for Processing', |
| | | 'operation' => $operationID |
| | | ]); |
| | | return $this->queued($operationID, 'Referral invitations queued'); |
| | | } |
| | | |
| | | /** |
| | |
| | | protected function actionUpdateStatus(WP_REST_Request $request, string $status): WP_REST_Response |
| | | { |
| | | if (!current_user_can('manage_options')) { |
| | | return $this->error('Admin permission required', 'unauthorized', 403); |
| | | return $this->forbidden('Admin permission required'); |
| | | } |
| | | |
| | | $referral_id = $request->get_param('referral_id'); |
| | |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | } |
| | | |
| | | $referral = $this->wpdb->get_row($this->wpdb->prepare( |
| | | "SELECT * FROM {$this->referrals_table} WHERE id = %d", |
| | | $referral_id |
| | | )); |
| | | $result = JVB()->referrals()->updateStatus($referral_id, $status); |
| | | |
| | | if (!$referral) { |
| | | return $this->error('Referral not found', 'not_found', 404); |
| | | if (is_wp_error($result)) { |
| | | $code = $result->get_error_code(); |
| | | return $code === 'not_found' |
| | | ? $this->notFound($result->get_error_message()) |
| | | : $this->error($result->get_error_message(), $code, 500); |
| | | } |
| | | |
| | | // Update status |
| | | $update_data = ['status' => $status]; |
| | | $update_data["{$status}_at"] = current_time('mysql'); |
| | | |
| | | if ($status === 'treated') { |
| | | $update_data['treatment_count'] = ($referral->treatment_count ?? 0) + 1; |
| | | } |
| | | |
| | | $updated = $this->wpdb->update( |
| | | $this->referrals_table, |
| | | $update_data, |
| | | ['id' => $referral_id], |
| | | array_fill(0, count($update_data), '%s'), |
| | | ['%d'] |
| | | ); |
| | | |
| | | |
| | | |
| | | if ($updated) { |
| | | // Also create rewards if treated |
| | | if ($status === 'treated') { |
| | | $this->createRewards($referral); |
| | | } |
| | | } |
| | | |
| | | $this->cache->clear(); |
| | | |
| | | $this->cache->flush(); |
| | | return $this->success(['message' => "Referral marked as {$status}"]); |
| | | } |
| | | |
| | |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | } |
| | | |
| | | $referral = $this->wpdb->get_row($this->wpdb->prepare( |
| | | "SELECT * FROM {$this->referrals_table} WHERE id = %d", |
| | | $referral_id |
| | | )); |
| | | $result = JVB()->referrals()->removeReferral($referral_id, get_current_user_id()); |
| | | |
| | | if (!$referral) { |
| | | return $this->error('Referral not found', 'not_found', 404); |
| | | if (is_wp_error($result)) { |
| | | $code = $result->get_error_code(); |
| | | $status = match($code) { |
| | | 'not_found' => 404, |
| | | 'unauthorized' => 403, |
| | | 'invalid_status' => 400, |
| | | default => 500 |
| | | }; |
| | | return $this->error($result->get_error_message(), $code, $status); |
| | | } |
| | | |
| | | // Check ownership |
| | | $current_user_id = get_current_user_id(); |
| | | if ($referral->referrer_id != $current_user_id && !current_user_can('manage_options')) { |
| | | return $this->error('Unauthorized', 'unauthorized', 403); |
| | | } |
| | | |
| | | // Can only remove pending referrals |
| | | if ($referral->status !== 'pending') { |
| | | return $this->error('Can only remove pending referrals', 'invalid_status', 400); |
| | | } |
| | | |
| | | $this->wpdb->delete($this->referrals_table, ['id' => $referral_id], ['%d']); |
| | | $this->cache->clear(); |
| | | |
| | | $this->cache->flush(); |
| | | return $this->success(['message' => 'Referral removed']); |
| | | } |
| | | |
| | |
| | | return $this->error('referral_id required', 'missing_id', 400); |
| | | } |
| | | |
| | | $current_user_id = get_current_user_id(); |
| | | $referral = $this->wpdb->get_row($this->wpdb->prepare( |
| | | "SELECT * FROM {$this->referrals_table} WHERE id = %d AND referrer_id = %d", |
| | | $referral_id, |
| | | $current_user_id |
| | | )); |
| | | |
| | | if (!$referral) { |
| | | return $this->error('Referral not found', 'not_found', 404); |
| | | } |
| | | |
| | | // Check rate limit (once per week) |
| | | $transient_key = 'referral_last_invite_' . md5($referral->referee_email); |
| | | $last_invite = get_transient($transient_key); |
| | | |
| | | if ($last_invite && (time() - $last_invite) < WEEK_IN_SECONDS) { |
| | | return $this->error( |
| | | 'Can only resend once per week', |
| | | 'rate_limit', |
| | | 429 |
| | | ); |
| | | } |
| | | |
| | | // Resend via referral manager |
| | | $result = JVB()->referrals()->sendReferralInvitation( |
| | | $current_user_id, |
| | | $referral->referee_email, |
| | | $referral->referee_name, |
| | | sprintf('Reminder: Join %s', get_bloginfo('name')), |
| | | 'Just a friendly reminder about my invitation!' |
| | | ); |
| | | $result = JVB()->referrals()->resendInvitation($referral_id, get_current_user_id()); |
| | | |
| | | if (is_wp_error($result)) { |
| | | return $this->error($result->get_error_message(), 'send_failed', 500); |
| | | $code = $result->get_error_code(); |
| | | $status = match($code) { |
| | | 'not_found' => 404, |
| | | 'rate_limit' => 429, |
| | | default => 500 |
| | | }; |
| | | return $this->error($result->get_error_message(), $code, $status); |
| | | } |
| | | |
| | | // Set rate limit |
| | | set_transient($transient_key, time(), WEEK_IN_SECONDS); |
| | | |
| | | return $this->success(['message' => 'Invitation resent']); |
| | | } |
| | | |
| | |
| | | |
| | | // Check permission |
| | | if ($user_id != get_current_user_id() && !current_user_can('manage_options')) { |
| | | return $this->error('Unauthorized', 'unauthorized', 403); |
| | | return $this->forbidden('Unauthorized'); |
| | | } |
| | | |
| | | $code = JVB()->referrals()->getUserReferralCode($user_id); |
| | |
| | | */ |
| | | public function getStats(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $user_id = $request->get_param('user'); |
| | | |
| | | $user_id = $request->get_param('user') ?? get_current_user_id(); |
| | | $cache_key = "stats_{$user_id}"; |
| | | |
| | | // Check for 304 Not Modified |
| | | // Check 304 Not Modified |
| | | $cache_check = $this->checkHeaders($request, $cache_key); |
| | | if ($cache_check instanceof WP_REST_Response) { |
| | | return $cache_check; |
| | |
| | | $stats = JVB()->referrals()->getUserStats($user_id); |
| | | |
| | | $response = $this->success(['items' => [$stats]]); |
| | | |
| | | // Add cache headers (5 minutes for stats) |
| | | return $this->addCacheHeaders($response, $cache_key, $stats, 5 * MINUTE_IN_SECONDS); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | |
| | | /** |
| | |
| | | ]; |
| | | |
| | | update_option(BASE . 'referral_settings', $settings); |
| | | $this->cache->clear(); |
| | | $this->cache->flush(); |
| | | |
| | | return $this->success([ |
| | | 'message' => 'Settings updated', |
| | |
| | | */ |
| | | protected function getAllReferrals(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $where = ['1=1']; |
| | | $where_params = []; |
| | | |
| | | $status = $request->get_param('status'); |
| | | if ($status && $status !== 'all') { |
| | | $where[] = 'status = %s'; |
| | | $where_params[] = $status; |
| | | } |
| | | |
| | | if ($date_start = $request->get_param('date_start')) { |
| | | $where[] = 'referred_at >= %s'; |
| | | $where_params[] = $date_start; |
| | | } |
| | | |
| | | if ($date_end = $request->get_param('date_end')) { |
| | | $where[] = 'referred_at <= %s'; |
| | | $where_params[] = $date_end; |
| | | } |
| | | |
| | | $search = $request->get_param('search'); |
| | | if (!empty($search)) { |
| | | $search_term = '%' . $this->wpdb->esc_like($search) . '%'; |
| | | $where[] = '(r.referee_name LIKE %s OR r.referee_email LIKE %s OR r.referral_code LIKE %s OR u.display_name LIKE %s OR ru.display_name LIKE %s OR ru.user_email LIKE %s)'; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | $where_params[] = $search_term; |
| | | } |
| | | |
| | | $limit = $request->get_param('limit') ?? 50; |
| | | $offset = $request->get_param('offset') ?? 0; |
| | | |
| | | $where_params[] = $limit; |
| | | $where_params[] = $offset; |
| | | |
| | | $query = "SELECT r.*, u.display_name as referrer_name |
| | | FROM {$this->referrals_table} r |
| | | LEFT JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID |
| | | WHERE " . implode(' AND ', $where) . " |
| | | ORDER BY referred_at DESC |
| | | LIMIT %d OFFSET %d"; |
| | | |
| | | $items = $this->wpdb->get_results($this->wpdb->prepare($query, $where_params)); |
| | | |
| | | error_log('All Referrals result: '.print_r($items, true)); |
| | | return $this->success([ |
| | | 'items' => $items, |
| | | 'total' => count($items) |
| | | $items = JVB()->referrals()->getAllReferrals([ |
| | | 'status' => $request->get_param('status') ?? 'all', |
| | | 'search' => $request->get_param('search'), |
| | | 'date_start' => $request->get_param('date_start'), |
| | | 'date_end' => $request->get_param('date_end'), |
| | | 'limit' => $request->get_param('limit') ?? 50, |
| | | 'offset' => $request->get_param('offset') ?? 0, |
| | | ]); |
| | | |
| | | return $this->success(['items' => $items, 'total' => count($items)]); |
| | | } |
| | | |
| | | /** |
| | | * Helper: Create rewards for completed referral |
| | | */ |
| | | protected function createRewards(object $referral): void |
| | | { |
| | | $settings = JVB()->referrals()->getRewardSettings(); |
| | | |
| | | // Referrer reward |
| | | $this->wpdb->insert( |
| | | $this->rewards_table, |
| | | [ |
| | | 'referral_id' => $referral->id, |
| | | 'user_id' => $referral->referrer_id, |
| | | 'reward_type' => 'referrer', |
| | | 'amount' => $settings['referrer_reward_amount'], |
| | | 'reward_calculation' => $settings['referrer_reward_type'], |
| | | 'status' => 'available', |
| | | 'created_at' => current_time('mysql') |
| | | ], |
| | | ['%d', '%d', '%s', '%f', '%s', '%s', '%s'] |
| | | ); |
| | | |
| | | // Referee reward |
| | | if ($referral->referee_id) { |
| | | $this->wpdb->insert( |
| | | $this->rewards_table, |
| | | [ |
| | | 'referral_id' => $referral->id, |
| | | 'user_id' => $referral->referee_id, |
| | | 'reward_type' => 'referee', |
| | | 'amount' => $settings['referee_reward_amount'], |
| | | 'reward_calculation' => $settings['referee_reward_type'], |
| | | 'status' => 'available', |
| | | 'created_at' => current_time('mysql') |
| | | ], |
| | | ['%d', '%d', '%s', '%f', '%s', '%s', '%s'] |
| | | ); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Check admin permission |
| | | */ |
| | | public function checkAdminPermission(WP_REST_Request $request): bool |
| | | { |
| | | return current_user_can('manage_options') && parent::checkPermission($request); |
| | | } |
| | | |
| | | /** |
| | | * Process queued referral operations |
| | |
| | | $data['subject'], |
| | | $data['message'] |
| | | ); |
| | | |
| | | if ($result['success']) { |
| | | $this->cache->clear(); |
| | | $this->cache->flush(); |
| | | } |
| | | error_log('Result: '.print_r($result, true)); |
| | | return $result; |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'message' => sprintf( |
| | | 'Sent invitations. Success: %d. Failed: %d.', |
| | | count($result['result']['success']), |
| | | count($result['result']['failed']) |
| | | ), |
| | | 'details' => [ |
| | | 'successful' => $result['result']['success'], |
| | | 'failed' => $result['result']['failed'], |
| | | 'total' => count($data['invitations']) |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public function handleClientUpload(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $files = $request->get_file_params(); |
| | | |
| | | if (!isset($files['file'])) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'No file uploaded' |
| | | ], 400); |
| | | if (empty($_FILES['file'])) { |
| | | return $this->error('No file uploaded', 'no_file', 400); |
| | | } |
| | | |
| | | $file = $files['file']; |
| | | $file = $_FILES['file']; |
| | | |
| | | if ($file['error'] !== UPLOAD_ERR_OK) { |
| | | return $this->error('File upload error: ' . $file['error'], 'upload_error', 400); |
| | | } |
| | | |
| | | // Validate file type |
| | | $allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain']; |
| | | if (!in_array($file['type'], $allowed_types)) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'File must be a CSV' |
| | | ], 400); |
| | | $finfo = finfo_open(FILEINFO_MIME_TYPE); |
| | | $mime_type = finfo_file($finfo, $file['tmp_name']); |
| | | finfo_close($finfo); |
| | | |
| | | if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) { |
| | | return $this->error('File must be a CSV', 'invalid_file_type', 400); |
| | | } |
| | | |
| | | // Validate file size (10MB max) |
| | | if ($file['size'] > 10 * 1024 * 1024) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'File size exceeds 10MB limit' |
| | | ], 400); |
| | | return $this->error('File size exceeds 10MB limit', 'file_too_large', 400); |
| | | } |
| | | |
| | | // Import using JaneAppClientImporter |
| | | $importer = new JaneAppClientImporter(); |
| | | $default_role = get_option(BASE . 'client_import_role', JVB_USER); |
| | | $default_role = get_option(BASE . 'referral_role', Site::getDefaultReferralRole()); |
| | | |
| | | $options = [ |
| | | 'update_existing' => true, |
| | |
| | | $result = $importer->importFromCSV($file['tmp_name'], $options); |
| | | |
| | | if (is_wp_error($result)) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => $result->get_error_message() |
| | | ], 500); |
| | | return $this->error($result->get_error_message(), 'import_failed', 500); |
| | | } |
| | | |
| | | // Build detailed message |
| | | $message = sprintf( |
| | | 'Import complete: %d created, %d updated, %d skipped', |
| | | $result['created'], |
| | | $result['updated'], |
| | | $result['skipped'] |
| | | ); |
| | | $this->cache->flush(); |
| | | |
| | | $details = []; |
| | | if (!empty($result['skipped_details'])) { |
| | | $details = $result['skipped_details']; |
| | | } |
| | | |
| | | // Clear cache |
| | | $this->cache->clear(); |
| | | |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | 'message' => $message, |
| | | 'items' => $result, |
| | | 'skipped_details' => $details |
| | | return $this->success([ |
| | | 'message' => sprintf( |
| | | 'Import complete: %d created, %d updated, %d skipped', |
| | | $result['created'], |
| | | $result['updated'], |
| | | $result['skipped'] |
| | | ), |
| | | 'stats' => $result, |
| | | 'skipped_details' => $result['skipped_details'] ?? [] |
| | | ]); |
| | | } |
| | | |
| | |
| | | */ |
| | | public function handleSalesUpload(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $files = $request->get_file_params(); |
| | | |
| | | if (!isset($files['file'])) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'No file uploaded' |
| | | ], 400); |
| | | if (empty($_FILES['file'])) { |
| | | return $this->error('No file uploaded', 'no_file', 400); |
| | | } |
| | | |
| | | $file = $files['file']; |
| | | $file = $_FILES['file']; |
| | | |
| | | if ($file['error'] !== UPLOAD_ERR_OK) { |
| | | return $this->error('File upload error: ' . $file['error'], 'upload_error', 400); |
| | | } |
| | | |
| | | // Validate file type |
| | | $allowed_types = ['text/csv', 'application/vnd.ms-excel', 'text/plain']; |
| | | if (!in_array($file['type'], $allowed_types)) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'File must be a CSV' |
| | | ], 400); |
| | | $finfo = finfo_open(FILEINFO_MIME_TYPE); |
| | | $mime_type = finfo_file($finfo, $file['tmp_name']); |
| | | finfo_close($finfo); |
| | | |
| | | if (!in_array($mime_type, $allowed_types) && !in_array($file['type'], $allowed_types)) { |
| | | return $this->error('File must be a CSV', 'invalid_file_type', 400); |
| | | } |
| | | |
| | | // Validate file size (10MB max) |
| | | if ($file['size'] > 10 * 1024 * 1024) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => 'File size exceeds 10MB limit' |
| | | ], 400); |
| | | return $this->error('File size exceeds 10MB limit', 'file_too_large', 400); |
| | | } |
| | | |
| | | // Import using JaneSalesImporter |
| | | $importer = new JaneSalesImporter(); |
| | | $options = [ |
| | | 'skip_existing' => true |
| | | ]; |
| | | |
| | | $result = $importer->importFromCSV($file['tmp_name'], $options); |
| | | // Import using JaneAppSalesImporter |
| | | $importer = new JaneAppSalesImporter(); |
| | | $result = $importer->importFromCSV($file['tmp_name'], ['skip_existing' => true]); |
| | | |
| | | if (is_wp_error($result)) { |
| | | return new WP_REST_Response([ |
| | | 'success' => false, |
| | | 'message' => $result->get_error_message() |
| | | ], 500); |
| | | return $this->error($result->get_error_message(), 'import_failed', 500); |
| | | } |
| | | |
| | | // Clear cache |
| | | $this->cache->clear(); |
| | | $this->cache->flush(); |
| | | |
| | | return new WP_REST_Response([ |
| | | 'success' => true, |
| | | return $this->success([ |
| | | 'message' => 'Sales imported successfully', |
| | | 'stats' => $result |
| | | ]); |