- Fix production debug exposure in Zoho admin interface (WP_DEBUG conditional) - Implement secure credential storage with AES-256-CBC encryption - Add file upload size limits (5MB profiles, 2MB logos) with enhanced validation - Fix privilege escalation via PHP Reflection bypass with public method alternative - Add comprehensive input validation and security headers - Update plugin version to 1.0.7 with security hardening Security improvements: ✅ Debug information exposure eliminated in production ✅ API credentials now encrypted in database storage ✅ File upload security enhanced with size/type validation ✅ AJAX endpoints secured with proper capability checks ✅ SQL injection protection verified via parameterized queries ✅ CSRF protection maintained with nonce verification 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			456 lines
		
	
	
		
			No EOL
		
	
	
		
			20 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			456 lines
		
	
	
		
			No EOL
		
	
	
		
			20 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| if (!defined('ABSPATH')) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| class HVAC_Trainer_Profile_Settings {
 | |
|     
 | |
|     private static $instance = null;
 | |
|     
 | |
|     public static function get_instance() {
 | |
|         if (null === self::$instance) {
 | |
|             self::$instance = new self();
 | |
|         }
 | |
|         return self::$instance;
 | |
|     }
 | |
|     
 | |
|     private function __construct() {
 | |
|         add_action('admin_menu', [$this, 'add_settings_page']);
 | |
|         add_action('admin_init', [$this, 'register_settings']);
 | |
|         add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
 | |
|         
 | |
|         // AJAX handlers
 | |
|         add_action('wp_ajax_hvac_test_geocoding', [$this, 'ajax_test_geocoding']);
 | |
|         add_action('wp_ajax_hvac_bulk_geocode', [$this, 'ajax_bulk_geocode']);
 | |
|         add_action('wp_ajax_hvac_sync_profiles', [$this, 'ajax_sync_profiles']);
 | |
|     }
 | |
|     
 | |
|     public function add_settings_page() {
 | |
|         add_submenu_page(
 | |
|             'hvac-settings',
 | |
|             'Trainer Profile Settings',
 | |
|             'Trainer Profiles',
 | |
|             'manage_options',
 | |
|             'hvac-trainer-profiles',
 | |
|             [$this, 'render_settings_page']
 | |
|         );
 | |
|     }
 | |
|     
 | |
|     public function register_settings() {
 | |
|         // Geocoding settings
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_google_maps_api_key', [
 | |
|             'type' => 'string',
 | |
|             'sanitize_callback' => 'sanitize_text_field',
 | |
|             'default' => ''
 | |
|         ]);
 | |
|         
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_geocoding_enabled', [
 | |
|             'type' => 'boolean',
 | |
|             'default' => true
 | |
|         ]);
 | |
|         
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_geocoding_rate_limit', [
 | |
|             'type' => 'integer',
 | |
|             'default' => 50
 | |
|         ]);
 | |
|         
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_geocoding_cache_duration', [
 | |
|             'type' => 'integer',
 | |
|             'default' => DAY_IN_SECONDS
 | |
|         ]);
 | |
|         
 | |
|         // Profile visibility settings
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_default_profile_visibility', [
 | |
|             'type' => 'string',
 | |
|             'default' => 'public'
 | |
|         ]);
 | |
|         
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_require_profile_approval', [
 | |
|             'type' => 'boolean',
 | |
|             'default' => false
 | |
|         ]);
 | |
|         
 | |
|         // Sync settings
 | |
|         register_setting('hvac_trainer_profile_settings', 'hvac_sync_verification_enabled', [
 | |
|             'type' => 'boolean',
 | |
|             'default' => true
 | |
|         ]);
 | |
|     }
 | |
|     
 | |
|     public function enqueue_admin_scripts($hook) {
 | |
|         if ($hook !== 'hvac-settings_page_hvac-trainer-profiles') {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         wp_enqueue_script(
 | |
|             'hvac-trainer-profile-admin',
 | |
|             HVAC_PLUGIN_URL . 'assets/js/hvac-trainer-profile-admin.js',
 | |
|             ['jquery'],
 | |
|             HVAC_PLUGIN_VERSION,
 | |
|             true
 | |
|         );
 | |
|         
 | |
|         wp_localize_script('hvac-trainer-profile-admin', 'hvacProfileAdmin', [
 | |
|             'ajax_url' => admin_url('admin-ajax.php'),
 | |
|             'nonce' => wp_create_nonce('hvac_profile_admin_nonce')
 | |
|         ]);
 | |
|     }
 | |
|     
 | |
|     public function render_settings_page() {
 | |
|         if (isset($_POST['submit'])) {
 | |
|             $this->handle_settings_save();
 | |
|         }
 | |
|         
 | |
|         $google_maps_key = get_option('hvac_google_maps_api_key', '');
 | |
|         $geocoding_enabled = get_option('hvac_geocoding_enabled', true);
 | |
|         $rate_limit = get_option('hvac_geocoding_rate_limit', 50);
 | |
|         $cache_duration = get_option('hvac_geocoding_cache_duration', DAY_IN_SECONDS);
 | |
|         $default_visibility = get_option('hvac_default_profile_visibility', 'public');
 | |
|         $require_approval = get_option('hvac_require_profile_approval', false);
 | |
|         $sync_enabled = get_option('hvac_sync_verification_enabled', true);
 | |
|         
 | |
|         // Get statistics
 | |
|         $stats = $this->get_profile_statistics();
 | |
|         
 | |
|         ?>
 | |
|         <div class="wrap">
 | |
|             <h1>Trainer Profile Settings</h1>
 | |
|             
 | |
|             <div class="hvac-admin-content">
 | |
|                 <div class="hvac-admin-main">
 | |
|                     <form method="post" action="">
 | |
|                         <?php wp_nonce_field('hvac_profile_settings', 'hvac_profile_settings_nonce'); ?>
 | |
|                         
 | |
|                         <!-- Geocoding Settings -->
 | |
|                         <div class="hvac-settings-section">
 | |
|                             <h2>Geocoding Configuration</h2>
 | |
|                             <p>Configure Google Maps API for address geocoding and proximity search.</p>
 | |
|                             
 | |
|                             <table class="form-table">
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Google Maps API Key</th>
 | |
|                                     <td>
 | |
|                                         <input type="password" name="hvac_google_maps_api_key" 
 | |
|                                                value="<?php echo esc_attr($google_maps_key); ?>" 
 | |
|                                                class="regular-text" />
 | |
|                                         <p class="description">
 | |
|                                             Get your API key from the 
 | |
|                                             <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a>
 | |
|                                         </p>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Enable Geocoding</th>
 | |
|                                     <td>
 | |
|                                         <label>
 | |
|                                             <input type="checkbox" name="hvac_geocoding_enabled" value="1" 
 | |
|                                                    <?php checked($geocoding_enabled); ?> />
 | |
|                                             Automatically geocode trainer addresses
 | |
|                                         </label>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Rate Limit</th>
 | |
|                                     <td>
 | |
|                                         <input type="number" name="hvac_geocoding_rate_limit" 
 | |
|                                                value="<?php echo esc_attr($rate_limit); ?>" 
 | |
|                                                min="1" max="100" class="small-text" />
 | |
|                                         <span>requests per minute</span>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Cache Duration</th>
 | |
|                                     <td>
 | |
|                                         <select name="hvac_geocoding_cache_duration">
 | |
|                                             <option value="<?php echo HOUR_IN_SECONDS; ?>" <?php selected($cache_duration, HOUR_IN_SECONDS); ?>>1 Hour</option>
 | |
|                                             <option value="<?php echo DAY_IN_SECONDS; ?>" <?php selected($cache_duration, DAY_IN_SECONDS); ?>>1 Day</option>
 | |
|                                             <option value="<?php echo WEEK_IN_SECONDS; ?>" <?php selected($cache_duration, WEEK_IN_SECONDS); ?>>1 Week</option>
 | |
|                                             <option value="<?php echo MONTH_IN_SECONDS; ?>" <?php selected($cache_duration, MONTH_IN_SECONDS); ?>>1 Month</option>
 | |
|                                         </select>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                             </table>
 | |
|                             
 | |
|                             <div class="hvac-settings-actions">
 | |
|                                 <button type="button" id="test-geocoding" class="button button-secondary">
 | |
|                                     Test Geocoding
 | |
|                                 </button>
 | |
|                                 <button type="button" id="bulk-geocode" class="button button-secondary">
 | |
|                                     Bulk Geocode Profiles
 | |
|                                 </button>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                         
 | |
|                         <!-- Profile Settings -->
 | |
|                         <div class="hvac-settings-section">
 | |
|                             <h2>Profile Configuration</h2>
 | |
|                             
 | |
|                             <table class="form-table">
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Default Visibility</th>
 | |
|                                     <td>
 | |
|                                         <select name="hvac_default_profile_visibility">
 | |
|                                             <option value="public" <?php selected($default_visibility, 'public'); ?>>Public</option>
 | |
|                                             <option value="private" <?php selected($default_visibility, 'private'); ?>>Private</option>
 | |
|                                         </select>
 | |
|                                         <p class="description">Default visibility for new trainer profiles</p>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Require Approval</th>
 | |
|                                     <td>
 | |
|                                         <label>
 | |
|                                             <input type="checkbox" name="hvac_require_profile_approval" value="1" 
 | |
|                                                    <?php checked($require_approval); ?> />
 | |
|                                             Require admin approval for public profiles
 | |
|                                         </label>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                             </table>
 | |
|                         </div>
 | |
|                         
 | |
|                         <!-- Sync Settings -->
 | |
|                         <div class="hvac-settings-section">
 | |
|                             <h2>Data Synchronization</h2>
 | |
|                             
 | |
|                             <table class="form-table">
 | |
|                                 <tr>
 | |
|                                     <th scope="row">Sync Verification</th>
 | |
|                                     <td>
 | |
|                                         <label>
 | |
|                                             <input type="checkbox" name="hvac_sync_verification_enabled" value="1" 
 | |
|                                                    <?php checked($sync_enabled); ?> />
 | |
|                                             Enable automatic sync verification (hourly)
 | |
|                                         </label>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                             </table>
 | |
|                             
 | |
|                             <div class="hvac-settings-actions">
 | |
|                                 <button type="button" id="sync-profiles" class="button button-secondary">
 | |
|                                     Force Sync All Profiles
 | |
|                                 </button>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                         
 | |
|                         <?php submit_button(); ?>
 | |
|                     </form>
 | |
|                 </div>
 | |
|                 
 | |
|                 <div class="hvac-admin-sidebar">
 | |
|                     <!-- Statistics -->
 | |
|                     <div class="hvac-stats-widget">
 | |
|                         <h3>Profile Statistics</h3>
 | |
|                         <div class="hvac-stat-grid">
 | |
|                             <div class="hvac-stat-item">
 | |
|                                 <span class="hvac-stat-number"><?php echo $stats['total_profiles']; ?></span>
 | |
|                                 <span class="hvac-stat-label">Total Profiles</span>
 | |
|                             </div>
 | |
|                             <div class="hvac-stat-item">
 | |
|                                 <span class="hvac-stat-number"><?php echo $stats['public_profiles']; ?></span>
 | |
|                                 <span class="hvac-stat-label">Public Profiles</span>
 | |
|                             </div>
 | |
|                             <div class="hvac-stat-item">
 | |
|                                 <span class="hvac-stat-number"><?php echo $stats['geocoded_profiles']; ?></span>
 | |
|                                 <span class="hvac-stat-label">Geocoded</span>
 | |
|                             </div>
 | |
|                             <div class="hvac-stat-item">
 | |
|                                 <span class="hvac-stat-number"><?php echo $stats['sync_issues']; ?></span>
 | |
|                                 <span class="hvac-stat-label">Sync Issues</span>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     
 | |
|                     <!-- Recent Activity -->
 | |
|                     <div class="hvac-activity-widget">
 | |
|                         <h3>Recent Activity</h3>
 | |
|                         <div class="hvac-activity-list">
 | |
|                             <?php echo $this->get_recent_activity(); ?>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
|         <?php
 | |
|     }
 | |
|     
 | |
|     private function handle_settings_save() {
 | |
|         if (!wp_verify_nonce($_POST['hvac_profile_settings_nonce'], 'hvac_profile_settings')) {
 | |
|             wp_die('Security check failed');
 | |
|         }
 | |
|         
 | |
|         if (!current_user_can('manage_options')) {
 | |
|             wp_die('Insufficient permissions');
 | |
|         }
 | |
|         
 | |
|         // Save settings
 | |
|         update_option('hvac_google_maps_api_key', sanitize_text_field($_POST['hvac_google_maps_api_key']));
 | |
|         update_option('hvac_geocoding_enabled', isset($_POST['hvac_geocoding_enabled']));
 | |
|         update_option('hvac_geocoding_rate_limit', intval($_POST['hvac_geocoding_rate_limit']));
 | |
|         update_option('hvac_geocoding_cache_duration', intval($_POST['hvac_geocoding_cache_duration']));
 | |
|         update_option('hvac_default_profile_visibility', sanitize_text_field($_POST['hvac_default_profile_visibility']));
 | |
|         update_option('hvac_require_profile_approval', isset($_POST['hvac_require_profile_approval']));
 | |
|         update_option('hvac_sync_verification_enabled', isset($_POST['hvac_sync_verification_enabled']));
 | |
|         
 | |
|         echo '<div class="notice notice-success"><p>Settings saved successfully!</p></div>';
 | |
|     }
 | |
|     
 | |
|     public function ajax_test_geocoding() {
 | |
|         check_ajax_referer('hvac_profile_admin_nonce', 'nonce');
 | |
|         
 | |
|         if (!current_user_can('manage_options')) {
 | |
|             wp_send_json_error('Insufficient permissions');
 | |
|         }
 | |
|         
 | |
|         $test_address = sanitize_text_field($_POST['address'] ?? 'New York, NY, USA');
 | |
|         
 | |
|         $geocoding_service = HVAC_Geocoding_Service::get_instance();
 | |
|         $result = $geocoding_service->make_geocoding_request($test_address);
 | |
|         
 | |
|         if (isset($result['error'])) {
 | |
|             wp_send_json_error($result['error']);
 | |
|         }
 | |
|         
 | |
|         wp_send_json_success([
 | |
|             'address' => $test_address,
 | |
|             'coordinates' => [
 | |
|                 'lat' => $result['lat'],
 | |
|                 'lng' => $result['lng']
 | |
|             ],
 | |
|             'formatted_address' => $result['formatted_address'],
 | |
|             'confidence' => $result['confidence']
 | |
|         ]);
 | |
|     }
 | |
|     
 | |
|     public function ajax_bulk_geocode() {
 | |
|         check_ajax_referer('hvac_profile_admin_nonce', 'nonce');
 | |
|         
 | |
|         if (!current_user_can('manage_options')) {
 | |
|             wp_send_json_error('Insufficient permissions');
 | |
|         }
 | |
|         
 | |
|         $geocoding_service = HVAC_Geocoding_Service::get_instance();
 | |
|         $processed = $geocoding_service->bulk_geocode_profiles(5); // Process 5 at a time
 | |
|         
 | |
|         wp_send_json_success([
 | |
|             'processed' => $processed,
 | |
|             'message' => "Processed {$processed} profiles for geocoding"
 | |
|         ]);
 | |
|     }
 | |
|     
 | |
|     public function ajax_sync_profiles() {
 | |
|         check_ajax_referer('hvac_profile_admin_nonce', 'nonce');
 | |
|         
 | |
|         if (!current_user_can('manage_options')) {
 | |
|             wp_send_json_error('Insufficient permissions');
 | |
|         }
 | |
|         
 | |
|         $sync_handler = HVAC_Profile_Sync_Handler::get_instance();
 | |
|         $sync_handler->verify_sync_integrity();
 | |
|         
 | |
|         wp_send_json_success([
 | |
|             'message' => 'Profile synchronization completed'
 | |
|         ]);
 | |
|     }
 | |
|     
 | |
|     private function get_profile_statistics() {
 | |
|         global $wpdb;
 | |
|         
 | |
|         $total_profiles = wp_count_posts('trainer_profile')->publish;
 | |
|         
 | |
|         $public_profiles = $wpdb->get_var("
 | |
|             SELECT COUNT(*)
 | |
|             FROM {$wpdb->posts} p
 | |
|             LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'is_public_profile'
 | |
|             WHERE p.post_type = 'trainer_profile' 
 | |
|             AND p.post_status = 'publish'
 | |
|             AND pm.meta_value = '1'
 | |
|         ");
 | |
|         
 | |
|         $geocoded_profiles = $wpdb->get_var("
 | |
|             SELECT COUNT(*)
 | |
|             FROM {$wpdb->posts} p
 | |
|             LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = 'latitude'
 | |
|             WHERE p.post_type = 'trainer_profile'
 | |
|             AND p.post_status = 'publish'
 | |
|             AND pm.meta_value IS NOT NULL
 | |
|             AND pm.meta_value != ''
 | |
|         ");
 | |
|         
 | |
|         // Check for sync issues
 | |
|         $sync_issues = 0;
 | |
|         $profiles = get_posts([
 | |
|             'post_type' => 'trainer_profile',
 | |
|             'posts_per_page' => -1,
 | |
|             'fields' => 'ids'
 | |
|         ]);
 | |
|         
 | |
|         foreach ($profiles as $profile_id) {
 | |
|             $user_id = get_post_meta($profile_id, 'user_id', true);
 | |
|             if ($user_id) {
 | |
|                 $sync_handler = HVAC_Profile_Sync_Handler::get_instance();
 | |
|                 $status = $sync_handler->get_sync_status($user_id);
 | |
|                 if ($status['status'] === 'out_of_sync') {
 | |
|                     $sync_issues++;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return [
 | |
|             'total_profiles' => $total_profiles,
 | |
|             'public_profiles' => $public_profiles,
 | |
|             'geocoded_profiles' => $geocoded_profiles,
 | |
|             'sync_issues' => $sync_issues
 | |
|         ];
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Public method to get profile statistics (secure alternative to reflection)
 | |
|      * 
 | |
|      * @return array Profile statistics
 | |
|      */
 | |
|     public function get_profile_statistics_public() {
 | |
|         // Check permissions
 | |
|         if (!current_user_can('hvac_trainer') && !current_user_can('hvac_master_trainer') && !current_user_can('administrator')) {
 | |
|             return ['error' => 'Insufficient permissions'];
 | |
|         }
 | |
|         
 | |
|         return $this->get_profile_statistics();
 | |
|     }
 | |
|     
 | |
|     private function get_recent_activity() {
 | |
|         $recent_profiles = get_posts([
 | |
|             'post_type' => 'trainer_profile',
 | |
|             'posts_per_page' => 5,
 | |
|             'orderby' => 'modified',
 | |
|             'order' => 'DESC'
 | |
|         ]);
 | |
|         
 | |
|         $activity = [];
 | |
|         foreach ($recent_profiles as $profile) {
 | |
|             $user_id = get_post_meta($profile->ID, 'user_id', true);
 | |
|             $user = get_userdata($user_id);
 | |
|             
 | |
|             $activity[] = [
 | |
|                 'type' => 'profile_updated',
 | |
|                 'message' => "Profile updated: " . ($user ? $user->display_name : 'Unknown User'),
 | |
|                 'time' => $profile->post_modified
 | |
|             ];
 | |
|         }
 | |
|         
 | |
|         $output = '';
 | |
|         foreach ($activity as $item) {
 | |
|             $time_diff = human_time_diff(strtotime($item['time']), current_time('timestamp'));
 | |
|             $output .= "<div class='hvac-activity-item'>";
 | |
|             $output .= "<span class='hvac-activity-message'>{$item['message']}</span>";
 | |
|             $output .= "<span class='hvac-activity-time'>{$time_diff} ago</span>";
 | |
|             $output .= "</div>";
 | |
|         }
 | |
|         
 | |
|         return $output ?: '<p>No recent activity</p>';
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Initialize the settings
 | |
| HVAC_Trainer_Profile_Settings::get_instance();
 |