Changeset 3395228
- Timestamp:
- 11/13/2025 05:13:57 PM (4 months ago)
- Location:
- pulse-chat-ai
- Files:
-
- 6 edited
-
tags/2.2.3/includes/class-license-manager.php (modified) (9 diffs)
-
tags/2.2.3/pulse-chat-ai.php (modified) (7 diffs)
-
tags/2.2.3/readme.txt (modified) (1 diff)
-
trunk/includes/class-license-manager.php (modified) (9 diffs)
-
trunk/pulse-chat-ai.php (modified) (7 diffs)
-
trunk/readme.txt (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
pulse-chat-ai/tags/2.2.3/includes/class-license-manager.php
r3395179 r3395228 15 15 private $api_url = 'https://uhnaedqfygrqdptjngqb.supabase.co/functions/v1/license-check'; 16 16 17 // Supabase Anon Key 18 // This is a public key safe to include in the plugin code 19 // It can be overridden via constant or filter if needed 20 private $supabase_anon_key = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVobmFlZHFmeWdycWRwdGpuZ3FiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjI3MDE2NjQsImV4cCI6MjA3ODI3NzY2NH0.USJDoMqD-B9rYYb4EHUnpwI99QHqJpty3IJLO8Kh7uM'; 21 22 public function __construct() { 23 // Allow anon key to be overridden via constant or filter (for custom deployments) 24 if (defined('PULSE_CHAT_AI_SUPABASE_ANON_KEY')) { 25 $this->supabase_anon_key = PULSE_CHAT_AI_SUPABASE_ANON_KEY; 26 } else { 27 $this->supabase_anon_key = apply_filters('pulse_chat_ai_supabase_anon_key', $this->supabase_anon_key); 28 } 29 30 // Hook for periodic license validation (every 24 hours) 31 add_action('admin_init', array($this, 'validate_license_periodically')); 32 33 // Hook to verify license before showing Pro features 34 add_action('plugins_loaded', array($this, 'init_license_check')); 35 } 36 17 37 // Product slug (must match EXACTLY the one used in admin/license creation) 18 38 // IMPORTANT: This must be identical character-by-character in both systems … … 23 43 private $option_name = 'pulse_chat_ai_license_key'; 24 44 private $license_data_option = 'pulse_chat_ai_license_data'; 25 26 public function __construct() {27 // Hook for periodic license validation (every 24 hours)28 add_action('admin_init', array($this, 'validate_license_periodically'));29 30 // Hook to verify license before showing Pro features31 add_action('plugins_loaded', array($this, 'init_license_check'));32 }33 45 34 46 /** … … 101 113 error_log('Domain (normalized): ' . $domain); 102 114 error_log('API URL: ' . $url); 103 } 104 105 // Make request 106 $response = wp_remote_get($url, array( 115 error_log('Request timestamp: ' . current_time('mysql')); 116 } 117 118 // Make request to Supabase Edge Function 119 // Note: Supabase Edge Functions may require Authorization header even if JWT verification is disabled 120 $headers = array( 121 'Content-Type' => 'application/json', 122 'Accept' => 'application/json', 123 ); 124 125 // Add Authorization header if anon key is configured 126 // This helps avoid "Missing authorization header" errors from Supabase gateway 127 // Even if JWT verification is disabled, Supabase gateway may still require this header 128 if (!empty($this->supabase_anon_key)) { 129 $headers['Authorization'] = 'Bearer ' . $this->supabase_anon_key; 130 if (defined('WP_DEBUG') && WP_DEBUG) { 131 error_log('Adding Authorization header with anon key (key length: ' . strlen($this->supabase_anon_key) . ')'); 132 } 133 } else { 134 if (defined('WP_DEBUG') && WP_DEBUG) { 135 error_log('WARNING: No Supabase anon key configured. Supabase may reject the request.'); 136 error_log('To fix: Add define(\'PULSE_CHAT_AI_SUPABASE_ANON_KEY\', \'your-key\') in wp-config.php'); 137 } 138 } 139 140 $request_args = array( 107 141 'timeout' => 10, 108 'sslverify' => true 109 )); 142 'sslverify' => true, 143 'headers' => $headers, 144 'user-agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(), 145 'blocking' => true, 146 ); 147 148 if (defined('WP_DEBUG') && WP_DEBUG) { 149 error_log('Request args: ' . print_r($request_args, true)); 150 } 151 152 $response = wp_remote_get($url, $request_args); 153 154 if (defined('WP_DEBUG') && WP_DEBUG) { 155 error_log('Request completed. Is WP_Error: ' . (is_wp_error($response) ? 'YES' : 'NO')); 156 } 110 157 111 158 // Check for connection errors … … 125 172 $status_code = wp_remote_retrieve_response_code($response); 126 173 $body = wp_remote_retrieve_body($response); 127 $ data = json_decode($body, true);174 $response_headers = wp_remote_retrieve_headers($response); 128 175 129 176 // Debug logging 130 177 if (defined('WP_DEBUG') && WP_DEBUG) { 131 178 error_log('Response Status Code: ' . $status_code); 132 error_log('Response Body: ' . $body); 133 error_log('Response Data: ' . print_r($data, true)); 179 error_log('Response Headers: ' . print_r($response_headers, true)); 180 error_log('Response Body (raw): ' . $body); 181 error_log('Response Body length: ' . strlen($body)); 182 } 183 184 $data = json_decode($body, true); 185 186 // Debug logging 187 if (defined('WP_DEBUG') && WP_DEBUG) { 188 error_log('Response Data (parsed): ' . print_r($data, true)); 189 error_log('JSON decode error: ' . json_last_error_msg()); 134 190 } 135 191 … … 138 194 if (defined('WP_DEBUG') && WP_DEBUG) { 139 195 error_log('Error al parsear respuesta de API: ' . $body); 196 error_log('Status Code: ' . $status_code); 140 197 } 141 198 return array( … … 143 200 'reason' => 'invalid_response', 144 201 'plan' => null, 145 'error' => 'Invalid response from server '202 'error' => 'Invalid response from server. Response body: ' . substr($body, 0, 200) 146 203 ); 147 204 } … … 162 219 } 163 220 221 // Extract error message from response 222 $error_message = 'Server error'; 223 if (isset($data['error'])) { 224 $error_message = $data['error']; 225 } elseif (isset($data['message'])) { 226 $error_message = $data['message']; 227 } elseif (isset($data['code'])) { 228 $error_message = $data['code']; 229 } 230 164 231 $reason = 'server_error'; 165 232 if (isset($data['reason'])) { … … 167 234 } else if ($status_code === 404) { 168 235 $reason = 'not_found'; 169 } 170 171 if (defined('WP_DEBUG') && WP_DEBUG) { 172 error_log('Licencia inválida. Razón: ' . $reason); 236 } else if ($status_code === 401) { 237 $reason = 'unauthorized'; 238 } else if ($status_code === 403) { 239 $reason = 'forbidden'; 240 } 241 242 if (defined('WP_DEBUG') && WP_DEBUG) { 243 error_log('Licencia inválida. Status: ' . $status_code . ', Razón: ' . $reason . ', Error: ' . $error_message); 244 error_log('Response data: ' . print_r($data, true)); 173 245 } 174 246 … … 177 249 'reason' => $reason, 178 250 'plan' => isset($data['plan']) ? $data['plan'] : null, 179 'error' => isset($data['message']) ? $data['message'] : 'Server error'251 'error' => $error_message 180 252 ); 181 253 } -
pulse-chat-ai/tags/2.2.3/pulse-chat-ai.php
r3395179 r3395228 256 256 'methods' => 'GET', 257 257 'callback' => array($this, 'get_conversations'), 258 'permission_callback' => array($this, 'check_admin_permissions'), 258 'permission_callback' => function($request) { 259 return $this->check_admin_permissions($request); 260 }, 259 261 )); 260 262 … … 262 264 'methods' => 'DELETE', 263 265 'callback' => array($this, 'delete_conversation'), 264 'permission_callback' => array($this, 'check_admin_permissions'), 266 'permission_callback' => function($request) { 267 return $this->check_admin_permissions($request); 268 }, 265 269 'args' => array( 266 270 'id' => array( … … 275 279 'methods' => 'POST', 276 280 'callback' => array($this, 'reset_usage_stats'), 277 'permission_callback' => array($this, 'check_admin_permissions'), 281 'permission_callback' => function($request) { 282 return $this->check_admin_permissions($request); 283 }, 278 284 )); 279 285 … … 282 288 'methods' => 'POST', 283 289 'callback' => array($this, 'validate_license_endpoint'), 284 'permission_callback' => array($this, 'check_admin_permissions'), 290 'permission_callback' => function($request) { 291 return $this->check_admin_permissions($request); 292 }, 285 293 'args' => array( 286 294 'license_key' => array( … … 296 304 'methods' => 'GET', 297 305 'callback' => array($this, 'get_license_status'), 298 'permission_callback' => array($this, 'check_admin_permissions'), 306 'permission_callback' => function($request) { 307 return $this->check_admin_permissions($request); 308 }, 299 309 )); 300 310 } … … 302 312 /** 303 313 * Check admin permissions for REST API 304 */ 305 public function check_admin_permissions() { 306 return current_user_can('manage_options'); 314 * 315 * @param WP_REST_Request $request The REST request object 316 * @return bool|WP_Error True if user has permission, WP_Error otherwise 317 */ 318 public function check_admin_permissions($request = null) { 319 // Check if user is logged in 320 if (!is_user_logged_in()) { 321 return new WP_Error( 322 'rest_forbidden', 323 'You must be logged in to access this endpoint.', 324 array('status' => 401) 325 ); 326 } 327 328 // Check if user has admin capabilities 329 if (!current_user_can('manage_options')) { 330 return new WP_Error( 331 'rest_forbidden', 332 'You do not have permission to access this endpoint.', 333 array('status' => 403) 334 ); 335 } 336 337 return true; 307 338 } 308 339 … … 671 702 */ 672 703 public function validate_license_endpoint($request) { 673 $license_key = $request->get_param('license_key'); 674 675 if (empty($license_key)) { 676 return new WP_Error('missing_license', 'License key is required', array('status' => 400)); 677 } 678 679 // Validate license 680 $result = $this->license_manager->validate_license($license_key, true); 681 682 return rest_ensure_response($result); 704 try { 705 $license_key = $request->get_param('license_key'); 706 707 if (empty($license_key)) { 708 return rest_ensure_response(array( 709 'valid' => false, 710 'reason' => 'missing_license', 711 'plan' => null, 712 'error' => 'License key is required' 713 )); 714 } 715 716 // Validate license 717 $result = $this->license_manager->validate_license($license_key, true); 718 719 // Ensure result is always an array with the expected structure 720 if (!is_array($result)) { 721 return rest_ensure_response(array( 722 'valid' => false, 723 'reason' => 'server_error', 724 'plan' => null, 725 'error' => 'Invalid response from license validation' 726 )); 727 } 728 729 return rest_ensure_response($result); 730 } catch (Exception $e) { 731 if (defined('WP_DEBUG') && WP_DEBUG) { 732 error_log('Pulse Chat AI: Error in validate_license_endpoint: ' . $e->getMessage()); 733 } 734 return rest_ensure_response(array( 735 'valid' => false, 736 'reason' => 'server_error', 737 'plan' => null, 738 'error' => $e->getMessage() 739 )); 740 } 683 741 } 684 742 -
pulse-chat-ai/tags/2.2.3/readme.txt
r3395179 r3395228 128 128 == Changelog == 129 129 130 = 2.2.3 - January 2026 = 130 = 2.2.3 - November 2025 = 131 * FIXED: License validation now includes Supabase Authorization header (anon key) 132 * FIXED: "Missing authorization header" error resolved - license validation works correctly 131 133 * FIXED: Domain normalization now always removes ports (no localhost exception) 132 134 * FIXED: License validation domain mismatch issues resolved 133 135 * IMPROVED: Domain normalization aligned with Supabase license system 134 136 * IMPROVED: Added important comments about product_slug matching requirements 137 * IMPROVED: Enhanced error handling and logging for license validation 138 * IMPROVED: Better error messages for license validation failures 135 139 136 140 = 2.2.0 - January 2026 = -
pulse-chat-ai/trunk/includes/class-license-manager.php
r3395179 r3395228 15 15 private $api_url = 'https://uhnaedqfygrqdptjngqb.supabase.co/functions/v1/license-check'; 16 16 17 // Supabase Anon Key 18 // This is a public key safe to include in the plugin code 19 // It can be overridden via constant or filter if needed 20 private $supabase_anon_key = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVobmFlZHFmeWdycWRwdGpuZ3FiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjI3MDE2NjQsImV4cCI6MjA3ODI3NzY2NH0.USJDoMqD-B9rYYb4EHUnpwI99QHqJpty3IJLO8Kh7uM'; 21 22 public function __construct() { 23 // Allow anon key to be overridden via constant or filter (for custom deployments) 24 if (defined('PULSE_CHAT_AI_SUPABASE_ANON_KEY')) { 25 $this->supabase_anon_key = PULSE_CHAT_AI_SUPABASE_ANON_KEY; 26 } else { 27 $this->supabase_anon_key = apply_filters('pulse_chat_ai_supabase_anon_key', $this->supabase_anon_key); 28 } 29 30 // Hook for periodic license validation (every 24 hours) 31 add_action('admin_init', array($this, 'validate_license_periodically')); 32 33 // Hook to verify license before showing Pro features 34 add_action('plugins_loaded', array($this, 'init_license_check')); 35 } 36 17 37 // Product slug (must match EXACTLY the one used in admin/license creation) 18 38 // IMPORTANT: This must be identical character-by-character in both systems … … 23 43 private $option_name = 'pulse_chat_ai_license_key'; 24 44 private $license_data_option = 'pulse_chat_ai_license_data'; 25 26 public function __construct() {27 // Hook for periodic license validation (every 24 hours)28 add_action('admin_init', array($this, 'validate_license_periodically'));29 30 // Hook to verify license before showing Pro features31 add_action('plugins_loaded', array($this, 'init_license_check'));32 }33 45 34 46 /** … … 101 113 error_log('Domain (normalized): ' . $domain); 102 114 error_log('API URL: ' . $url); 103 } 104 105 // Make request 106 $response = wp_remote_get($url, array( 115 error_log('Request timestamp: ' . current_time('mysql')); 116 } 117 118 // Make request to Supabase Edge Function 119 // Note: Supabase Edge Functions may require Authorization header even if JWT verification is disabled 120 $headers = array( 121 'Content-Type' => 'application/json', 122 'Accept' => 'application/json', 123 ); 124 125 // Add Authorization header if anon key is configured 126 // This helps avoid "Missing authorization header" errors from Supabase gateway 127 // Even if JWT verification is disabled, Supabase gateway may still require this header 128 if (!empty($this->supabase_anon_key)) { 129 $headers['Authorization'] = 'Bearer ' . $this->supabase_anon_key; 130 if (defined('WP_DEBUG') && WP_DEBUG) { 131 error_log('Adding Authorization header with anon key (key length: ' . strlen($this->supabase_anon_key) . ')'); 132 } 133 } else { 134 if (defined('WP_DEBUG') && WP_DEBUG) { 135 error_log('WARNING: No Supabase anon key configured. Supabase may reject the request.'); 136 error_log('To fix: Add define(\'PULSE_CHAT_AI_SUPABASE_ANON_KEY\', \'your-key\') in wp-config.php'); 137 } 138 } 139 140 $request_args = array( 107 141 'timeout' => 10, 108 'sslverify' => true 109 )); 142 'sslverify' => true, 143 'headers' => $headers, 144 'user-agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(), 145 'blocking' => true, 146 ); 147 148 if (defined('WP_DEBUG') && WP_DEBUG) { 149 error_log('Request args: ' . print_r($request_args, true)); 150 } 151 152 $response = wp_remote_get($url, $request_args); 153 154 if (defined('WP_DEBUG') && WP_DEBUG) { 155 error_log('Request completed. Is WP_Error: ' . (is_wp_error($response) ? 'YES' : 'NO')); 156 } 110 157 111 158 // Check for connection errors … … 125 172 $status_code = wp_remote_retrieve_response_code($response); 126 173 $body = wp_remote_retrieve_body($response); 127 $ data = json_decode($body, true);174 $response_headers = wp_remote_retrieve_headers($response); 128 175 129 176 // Debug logging 130 177 if (defined('WP_DEBUG') && WP_DEBUG) { 131 178 error_log('Response Status Code: ' . $status_code); 132 error_log('Response Body: ' . $body); 133 error_log('Response Data: ' . print_r($data, true)); 179 error_log('Response Headers: ' . print_r($response_headers, true)); 180 error_log('Response Body (raw): ' . $body); 181 error_log('Response Body length: ' . strlen($body)); 182 } 183 184 $data = json_decode($body, true); 185 186 // Debug logging 187 if (defined('WP_DEBUG') && WP_DEBUG) { 188 error_log('Response Data (parsed): ' . print_r($data, true)); 189 error_log('JSON decode error: ' . json_last_error_msg()); 134 190 } 135 191 … … 138 194 if (defined('WP_DEBUG') && WP_DEBUG) { 139 195 error_log('Error al parsear respuesta de API: ' . $body); 196 error_log('Status Code: ' . $status_code); 140 197 } 141 198 return array( … … 143 200 'reason' => 'invalid_response', 144 201 'plan' => null, 145 'error' => 'Invalid response from server '202 'error' => 'Invalid response from server. Response body: ' . substr($body, 0, 200) 146 203 ); 147 204 } … … 162 219 } 163 220 221 // Extract error message from response 222 $error_message = 'Server error'; 223 if (isset($data['error'])) { 224 $error_message = $data['error']; 225 } elseif (isset($data['message'])) { 226 $error_message = $data['message']; 227 } elseif (isset($data['code'])) { 228 $error_message = $data['code']; 229 } 230 164 231 $reason = 'server_error'; 165 232 if (isset($data['reason'])) { … … 167 234 } else if ($status_code === 404) { 168 235 $reason = 'not_found'; 169 } 170 171 if (defined('WP_DEBUG') && WP_DEBUG) { 172 error_log('Licencia inválida. Razón: ' . $reason); 236 } else if ($status_code === 401) { 237 $reason = 'unauthorized'; 238 } else if ($status_code === 403) { 239 $reason = 'forbidden'; 240 } 241 242 if (defined('WP_DEBUG') && WP_DEBUG) { 243 error_log('Licencia inválida. Status: ' . $status_code . ', Razón: ' . $reason . ', Error: ' . $error_message); 244 error_log('Response data: ' . print_r($data, true)); 173 245 } 174 246 … … 177 249 'reason' => $reason, 178 250 'plan' => isset($data['plan']) ? $data['plan'] : null, 179 'error' => isset($data['message']) ? $data['message'] : 'Server error'251 'error' => $error_message 180 252 ); 181 253 } -
pulse-chat-ai/trunk/pulse-chat-ai.php
r3395179 r3395228 256 256 'methods' => 'GET', 257 257 'callback' => array($this, 'get_conversations'), 258 'permission_callback' => array($this, 'check_admin_permissions'), 258 'permission_callback' => function($request) { 259 return $this->check_admin_permissions($request); 260 }, 259 261 )); 260 262 … … 262 264 'methods' => 'DELETE', 263 265 'callback' => array($this, 'delete_conversation'), 264 'permission_callback' => array($this, 'check_admin_permissions'), 266 'permission_callback' => function($request) { 267 return $this->check_admin_permissions($request); 268 }, 265 269 'args' => array( 266 270 'id' => array( … … 275 279 'methods' => 'POST', 276 280 'callback' => array($this, 'reset_usage_stats'), 277 'permission_callback' => array($this, 'check_admin_permissions'), 281 'permission_callback' => function($request) { 282 return $this->check_admin_permissions($request); 283 }, 278 284 )); 279 285 … … 282 288 'methods' => 'POST', 283 289 'callback' => array($this, 'validate_license_endpoint'), 284 'permission_callback' => array($this, 'check_admin_permissions'), 290 'permission_callback' => function($request) { 291 return $this->check_admin_permissions($request); 292 }, 285 293 'args' => array( 286 294 'license_key' => array( … … 296 304 'methods' => 'GET', 297 305 'callback' => array($this, 'get_license_status'), 298 'permission_callback' => array($this, 'check_admin_permissions'), 306 'permission_callback' => function($request) { 307 return $this->check_admin_permissions($request); 308 }, 299 309 )); 300 310 } … … 302 312 /** 303 313 * Check admin permissions for REST API 304 */ 305 public function check_admin_permissions() { 306 return current_user_can('manage_options'); 314 * 315 * @param WP_REST_Request $request The REST request object 316 * @return bool|WP_Error True if user has permission, WP_Error otherwise 317 */ 318 public function check_admin_permissions($request = null) { 319 // Check if user is logged in 320 if (!is_user_logged_in()) { 321 return new WP_Error( 322 'rest_forbidden', 323 'You must be logged in to access this endpoint.', 324 array('status' => 401) 325 ); 326 } 327 328 // Check if user has admin capabilities 329 if (!current_user_can('manage_options')) { 330 return new WP_Error( 331 'rest_forbidden', 332 'You do not have permission to access this endpoint.', 333 array('status' => 403) 334 ); 335 } 336 337 return true; 307 338 } 308 339 … … 671 702 */ 672 703 public function validate_license_endpoint($request) { 673 $license_key = $request->get_param('license_key'); 674 675 if (empty($license_key)) { 676 return new WP_Error('missing_license', 'License key is required', array('status' => 400)); 677 } 678 679 // Validate license 680 $result = $this->license_manager->validate_license($license_key, true); 681 682 return rest_ensure_response($result); 704 try { 705 $license_key = $request->get_param('license_key'); 706 707 if (empty($license_key)) { 708 return rest_ensure_response(array( 709 'valid' => false, 710 'reason' => 'missing_license', 711 'plan' => null, 712 'error' => 'License key is required' 713 )); 714 } 715 716 // Validate license 717 $result = $this->license_manager->validate_license($license_key, true); 718 719 // Ensure result is always an array with the expected structure 720 if (!is_array($result)) { 721 return rest_ensure_response(array( 722 'valid' => false, 723 'reason' => 'server_error', 724 'plan' => null, 725 'error' => 'Invalid response from license validation' 726 )); 727 } 728 729 return rest_ensure_response($result); 730 } catch (Exception $e) { 731 if (defined('WP_DEBUG') && WP_DEBUG) { 732 error_log('Pulse Chat AI: Error in validate_license_endpoint: ' . $e->getMessage()); 733 } 734 return rest_ensure_response(array( 735 'valid' => false, 736 'reason' => 'server_error', 737 'plan' => null, 738 'error' => $e->getMessage() 739 )); 740 } 683 741 } 684 742 -
pulse-chat-ai/trunk/readme.txt
r3395179 r3395228 128 128 == Changelog == 129 129 130 = 2.2.3 - January 2026 = 130 = 2.2.3 - November 2025 = 131 * FIXED: License validation now includes Supabase Authorization header (anon key) 132 * FIXED: "Missing authorization header" error resolved - license validation works correctly 131 133 * FIXED: Domain normalization now always removes ports (no localhost exception) 132 134 * FIXED: License validation domain mismatch issues resolved 133 135 * IMPROVED: Domain normalization aligned with Supabase license system 134 136 * IMPROVED: Added important comments about product_slug matching requirements 137 * IMPROVED: Enhanced error handling and logging for license validation 138 * IMPROVED: Better error messages for license validation failures 135 139 136 140 = 2.2.0 - January 2026 =
Note: See TracChangeset
for help on using the changeset viewer.